60 lines
2.3 KiB
Python
60 lines
2.3 KiB
Python
|
|
"""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
|