titan-iac/testing/quality_docs.py

60 lines
2.3 KiB
Python
Raw Normal View History

"""Documentation-oriented validation for the testing contract."""
from __future__ import annotations
import ast
from pathlib import Path
from typing import Any
def _module_has_docstring(path: Path) -> bool:
source = path.read_text(encoding="utf-8")
return ast.get_docstring(ast.parse(source)) is not None
def _iter_contract_paths(contract: dict[str, Any]) -> list[str]:
paths: list[str] = []
for item in contract.get("required_docs", []):
paths.append(item["path"])
paths.extend(contract.get("managed_modules", []))
paths.extend(contract.get("lint_paths", []))
for suite in contract.get("pytest_suites", {}).values():
paths.extend(suite.get("paths", []))
for item in contract.get("manual_scripts", []):
paths.append(item["path"])
return paths
def run_check(contract: dict[str, Any], root: Path) -> list[str]:
"""Return human-readable issues for contract/documentation violations."""
issues: list[str] = []
for item in contract.get("required_docs", []):
path = root / item["path"]
if not path.exists():
issues.append(f"required doc missing: {item['path']}")
continue
if path.is_file() and not path.read_text(encoding="utf-8").strip():
issues.append(f"required doc empty: {item['path']}")
if not item.get("description", "").strip():
issues.append(f"required doc missing description: {item['path']}")
for relative_path in sorted(set(_iter_contract_paths(contract))):
if not (root / relative_path).exists():
issues.append(f"contract path missing: {relative_path}")
for suite_name, suite in contract.get("pytest_suites", {}).items():
if not suite.get("description", "").strip():
issues.append(f"pytest suite missing description: {suite_name}")
for item in contract.get("manual_scripts", []):
if not item.get("description", "").strip():
issues.append(f"manual script missing description: {item['path']}")
for relative_path in contract.get("managed_modules", []):
path = root / relative_path
if path.exists() and path.suffix == ".py" and not _module_has_docstring(path):
issues.append(f"module docstring missing: {relative_path}")
return issues