Did some things.

Signed-off-by: Cliff Hill <xlorep@darkhelm.org>
This commit is contained in:
2025-10-18 21:29:28 -04:00
parent 0e23b38eeb
commit 8ab2e1dd28
10 changed files with 312 additions and 11 deletions

View File

@@ -0,0 +1,138 @@
#!/usr/bin/env python3
"""Check that docstrings do not contain type annotations.
This script enforces that Google-style docstrings contain NO type information,
since types should be declared in function signatures using type hints.
"""
import ast
import re
import sys
from pathlib import Path
def extract_docstring(node: ast.FunctionDef | ast.AsyncFunctionDef) -> str | None:
"""Extract docstring from a function node."""
if (
node.body
and isinstance(node.body[0], ast.Expr)
and isinstance(node.body[0].value, ast.Constant)
and isinstance(node.body[0].value.value, str)
):
return node.body[0].value.value
return None
def check_docstring_for_types(docstring: str, func_name: str, filename: str, lineno: int) -> list[str]:
"""Check if docstring contains type annotations and return errors."""
errors: list[str] = []
lines = docstring.split('\n')
in_args_section = False
in_returns_section = False
in_yields_section = False
for i, line in enumerate(lines, 1):
line_stripped = line.strip()
# Track which section we're in
if re.match(r'^\s*Args?\s*:', line_stripped, re.IGNORECASE):
in_args_section = True
in_returns_section = False
in_yields_section = False
continue
elif re.match(r'^\s*Returns?\s*:', line_stripped, re.IGNORECASE):
in_args_section = False
in_returns_section = True
in_yields_section = False
continue
elif re.match(r'^\s*Yields?\s*:', line_stripped, re.IGNORECASE):
in_args_section = False
in_returns_section = False
in_yields_section = True
continue
elif re.match(r'^\s*(Raises?|Examples?|Note|Notes)\s*:', line_stripped, re.IGNORECASE):
in_args_section = False
in_returns_section = False
in_yields_section = False
continue
# Check for type annotations in Args section
if in_args_section and re.match(r'^\s*\w+\s*\([^)]+\)\s*:', line_stripped):
errors.append(
f"{filename}:{lineno + i}:{func_name}: "
f"Type annotation found in Args section: '{line_stripped}'. "
f"Remove type information and use function signature type hints instead."
)
# Check for type annotations in Returns section
if in_returns_section and re.match(r'^\s*\w+\s*:', line_stripped) and not re.match(r'^\s*Returns?\s*:', line_stripped, re.IGNORECASE):
errors.append(
f"{filename}:{lineno + i}:{func_name}: "
f"Type annotation found in Returns section: '{line_stripped}'. "
f"Remove type information and use function signature return type hints instead."
)
# Check for type annotations in Yields section
if in_yields_section and re.match(r'^\s*\w+\s*:', line_stripped) and not re.match(r'^\s*Yields?\s*:', line_stripped, re.IGNORECASE):
errors.append(
f"{filename}:{lineno + i}:{func_name}: "
f"Type annotation found in Yields section: '{line_stripped}'. "
f"Remove type information and use function signature type hints instead."
)
return errors
def check_file(filepath: Path) -> list[str]:
"""Check a single Python file for type annotations in docstrings."""
try:
content = filepath.read_text(encoding='utf-8')
tree = ast.parse(content, filename=str(filepath))
except (SyntaxError, UnicodeDecodeError) as e:
return [f"{filepath}: Failed to parse file: {e}"]
errors: list[str] = []
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
docstring = extract_docstring(node)
if docstring:
func_errors = check_docstring_for_types(
docstring,
node.name,
str(filepath),
node.lineno
)
errors.extend(func_errors)
return errors
def main() -> int:
"""Main function to check all provided files."""
if len(sys.argv) < 2:
print("Usage: check_no_docstring_types.py <file1> [file2] ...")
return 1
all_errors: list[str] = []
for filepath_str in sys.argv[1:]:
filepath = Path(filepath_str)
if filepath.suffix == '.py':
errors = check_file(filepath)
all_errors.extend(errors)
if all_errors:
print("❌ Found type annotations in docstrings:")
for error in all_errors:
print(f" {error}")
print("\n💡 Tip: Use type hints in function signatures instead of docstring type annotations.")
return 1
print("✅ No type annotations found in docstrings.")
return 0
if __name__ == '__main__':
sys.exit(main())