120 lines
4.5 KiB
Python
120 lines
4.5 KiB
Python
import ast
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT_DIR = Path(__file__).parent.parent
|
|
|
|
class IndentationAnalyzer(ast.NodeVisitor):
|
|
def __init__(self, source_lines: list[str]):
|
|
self.source_lines = source_lines
|
|
self.violations: list[tuple[int, int, int, str]] = []
|
|
|
|
def visit_Module(self, node: ast.Module):
|
|
for child in node.body:
|
|
self._walk_node(child, 0)
|
|
self.generic_visit(node)
|
|
|
|
def _walk_node(self, node: ast.AST, depth: int):
|
|
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
|
line_no = node.lineno
|
|
expected_indent = depth
|
|
actual_indent = self._get_indent(line_no)
|
|
if actual_indent != expected_indent:
|
|
self.violations.append((line_no, actual_indent, expected_indent,
|
|
f"{node.__class__.__name__} {node.name}"))
|
|
body_depth = depth + 1
|
|
for child in node.body:
|
|
self._walk_node(child, body_depth)
|
|
elif isinstance(node, ast.If):
|
|
line_no = node.lineno
|
|
expected_indent = depth
|
|
actual_indent = self._get_indent(line_no)
|
|
if actual_indent != expected_indent:
|
|
self.violations.append((line_no, actual_indent, expected_indent, "if statement"))
|
|
for child in node.body:
|
|
self._walk_node(child, depth + 1)
|
|
if node.orelse:
|
|
self._walk_node(node.orelse, depth + 1)
|
|
elif isinstance(node, (ast.For, ast.While, ast.With)):
|
|
line_no = node.lineno
|
|
expected_indent = depth
|
|
actual_indent = self._get_indent(line_no)
|
|
if actual_indent != expected_indent:
|
|
self.violations.append((line_no, actual_indent, expected_indent,
|
|
node.__class__.__name__))
|
|
for child in node.body:
|
|
self._walk_node(child, depth + 1)
|
|
if isinstance(node, ast.For) and node.orelse:
|
|
self._walk_node(node.orelse, depth + 1)
|
|
elif isinstance(node, ast.Try):
|
|
for child in node.body:
|
|
self._walk_node(child, depth + 1)
|
|
for handler in node.handlers:
|
|
self._walk_node(handler, depth + 1)
|
|
if node.orelse:
|
|
self._walk_node(node.orelse, depth + 1)
|
|
if node.finalbody:
|
|
self._walk_node(node.finalbody, depth + 1)
|
|
elif isinstance(node, (ast.Assign, ast.Expr, ast.Return, ast.Pass, ast.Raise, ast.Assert)):
|
|
pass
|
|
else:
|
|
self.generic_visit(node)
|
|
|
|
def _get_indent(self, lineno: int) -> int:
|
|
if lineno <= 0 or lineno > len(self.source_lines):
|
|
return 0
|
|
line = self.source_lines[lineno - 1]
|
|
stripped = line.lstrip()
|
|
return len(line) - len(stripped)
|
|
|
|
def audit_file(filepath: Path) -> list[tuple[int, int, int, str]]:
|
|
try:
|
|
with open(filepath, "r", encoding="utf-8", newline="") as f:
|
|
source = f.read()
|
|
source_lines = source.splitlines()
|
|
tree = ast.parse(source, filename=str(filepath))
|
|
analyzer = IndentationAnalyzer(source_lines)
|
|
analyzer.visit(tree)
|
|
return analyzer.violations
|
|
except SyntaxError:
|
|
return []
|
|
except Exception:
|
|
return []
|
|
|
|
def main():
|
|
dirs = ["src", "tests", "scripts", "conductor"]
|
|
total_files = 0
|
|
files_with_violations = 0
|
|
total_violations = 0
|
|
|
|
results: dict[str, list[tuple]] = {}
|
|
|
|
for dir_name in dirs:
|
|
dir_path = ROOT_DIR / dir_name
|
|
if not dir_path.exists():
|
|
continue
|
|
|
|
py_files = list(dir_path.rglob("*.py"))
|
|
for py_file in sorted(py_files):
|
|
total_files += 1
|
|
violations = audit_file(py_file)
|
|
if violations:
|
|
files_with_violations += 1
|
|
total_violations += len(violations)
|
|
rel = str(py_file.relative_to(dir_path))
|
|
results[f"{dir_name}/{rel}"] = violations
|
|
|
|
print(f"Total files scanned: {total_files}")
|
|
print(f"Files with violations: {files_with_violations}")
|
|
print(f"Total violations: {total_violations}")
|
|
print()
|
|
|
|
for path, violations in sorted(results.items()):
|
|
print(f"{path}: {len(violations)} violations")
|
|
for line_no, actual, expected, desc in violations[:5]:
|
|
print(f" Line {line_no}: actual={actual}, expected={expected} for {desc}")
|
|
if len(violations) > 5:
|
|
print(f" ... and {len(violations) - 5} more")
|
|
|
|
if __name__ == "__main__":
|
|
main() |