f6feab9243
The code after the 'prior session' return block was incorrectly indented at 1 space, placing it inside the 'if is_viewing_prior_session' block instead of after it. This caused 'total_cost' and 'perc' to be undefined when viewing an active session, triggering an IM_ASSERT error. Fix: Moved 'track_name', 'track_stats', and 'total_cost' to the correct 2-space indentation (method body level).
99 lines
3.0 KiB
Python
99 lines
3.0 KiB
Python
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()
|