import os import re import ast from collections import Counter class ScopeAuditor(ast.NodeVisitor): def __init__(self, findings): self.scope_stack = [([], "")] self.findings = findings def visit_ClassDef(self, node): self.scope_stack[-1][0].append(node.name) parent_label = self.scope_stack[-1][1] new_label = f"{parent_label}.{node.name}" if parent_label else node.name self.scope_stack.append(([], new_label)) self.generic_visit(node) defs, label = self.scope_stack.pop() self.check_duplicates(defs, label) def visit_FunctionDef(self, node): self.scope_stack[-1][0].append(node.name) parent_label = self.scope_stack[-1][1] new_label = f"{parent_label}.{node.name}" if parent_label else node.name self.scope_stack.append(([], new_label)) self.generic_visit(node) defs, label = self.scope_stack.pop() self.check_duplicates(defs, label) def visit_AsyncFunctionDef(self, node): self.visit_FunctionDef(node) def check_duplicates(self, defs, label): counts = Counter(defs) for name, count in counts.items(): if count > 1: scope_str = f" in scope '{label}'" if label else " at top-level" self.findings.append(f"Duplicate definition{scope_str}: '{name}' ({count} times)") def audit_file(path): with open(path, 'r', encoding='utf-8') as f: lines = f.readlines() content = "".join(lines) findings = [] # 1. Detect multiple identical import lines imports = [line.strip() for line in lines if line.strip().startswith('import ')] import_counts = Counter(imports) for imp, count in import_counts.items(): if count > 1: findings.append(f"Duplicate import: '{imp}' ({count} times)") # 2. Detect multiple 'from X import Y' lines for the same module X and symbol Y from_imports = [line.strip() for line in lines if line.strip().startswith('from ')] from_counts = Counter(from_imports) for imp, count in from_counts.items(): if count > 1: findings.append(f"Duplicate from-import: '{imp}' ({count} times)") # 3. Detect mixed indentation (look for 4-space blocks) four_spaces = " " for i, line in enumerate(lines): if line.startswith(four_spaces): findings.append(f"Mixed indentation: 4-space block found at line {i+1}") break # Only report once per file # 4. List all functions and classes that appear more than once in the same scope try: tree = ast.parse(content) auditor = ScopeAuditor(findings) auditor.visit(tree) if auditor.scope_stack: defs, label = auditor.scope_stack.pop() auditor.check_duplicates(defs, label) except Exception as e: findings.append(f"AST Parse Error: {e}") return findings def main(): src_dir = 'src' if not os.path.exists(src_dir): print(f"Directory {src_dir} not found.") return for root, dirs, files in os.walk(src_dir): for file in files: if file.endswith('.py'): path = os.path.join(root, file) findings = audit_file(path) if findings: print(f"--- {path} ---") for f in findings: print(f" {f}") print() if __name__ == "__main__": main()