69 lines
2.8 KiB
Python
69 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Scan tools/*.py for DRY smells: repeated sum(1 for ...)/list-comprehension patterns,
|
|
duplicated function bodies, and other refactor candidates."""
|
|
import ast, os, hashlib, collections, sys
|
|
|
|
REPO = "/root/troupe-standup"
|
|
TOOLS = os.path.join(REPO, "tools")
|
|
|
|
def scan_file(path):
|
|
src = open(path, encoding="utf-8").read()
|
|
lines = src.splitlines()
|
|
info = collections.Counter()
|
|
|
|
# sum(1 for ... patterns - potential DRY
|
|
info["sum_gen"] = sum(1 for l in lines if "sum(1 for" in l)
|
|
# len([...]) vs len([...]) patterns
|
|
info["len_listcomp"] = sum(1 for l in lines if "len([" in l or "len([" in l)
|
|
# list comprehensions that filter
|
|
info["listcomp_filter"] = sum(1 for l in lines if "if r[" in l)
|
|
# count() calls on lists
|
|
info["count_method"] = sum(1 for l in lines if ".count(" in l)
|
|
# manual counting loops
|
|
info["manual_count"] = sum(1 for l in lines if "+= 1" in l)
|
|
|
|
# Find repeated string literals / patterns
|
|
# Look for duplicated .strip() calls, split() calls etc.
|
|
|
|
# Check for duplicate function bodies using AST
|
|
try:
|
|
tree = ast.parse(src)
|
|
funcs = [(node.name, node.body) for node in ast.walk(tree) if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))]
|
|
# hash each function body
|
|
body_hashes = {}
|
|
for name, body in funcs:
|
|
h = hashlib.sha256(ast.unparse(body).encode()).hexdigest()[:12]
|
|
body_hashes.setdefault(h, []).append(name)
|
|
dup_funcs = {h: names for h, names in body_hashes.items() if len(names) > 1}
|
|
except Exception as e:
|
|
dup_funcs = {}
|
|
funcs = []
|
|
|
|
# Find repeated string patterns (lines that appear 3+ times)
|
|
line_counts = collections.Counter(l.strip() for l in lines if l.strip())
|
|
repeated_lines = [(l, c) for l, c in line_counts.items() if c >= 3 and len(l) > 15]
|
|
|
|
return info, dup_funcs, repeated_lines, len(lines), funcs
|
|
|
|
for f in sorted(os.listdir(TOOLS)):
|
|
if not f.endswith(".py") or f.startswith("_"):
|
|
continue
|
|
path = os.path.join(TOOLS, f)
|
|
info, dup_funcs, repeated, nlines, funcs = scan_file(path)
|
|
if info["sum_gen"] >= 2 or dup_funcs or repeated:
|
|
print(f"\n=== {f} ({nlines} lines, {len(funcs)} funcs) ===")
|
|
if info["sum_gen"]:
|
|
print(f" sum(1 for ...) x{info['sum_gen']}")
|
|
if info["len_listcomp"]:
|
|
print(f" len([...]) x{info['len_listcomp']}")
|
|
if info["listcomp_filter"]:
|
|
print(f" listcomp with if-filter x{info['listcomp_filter']}")
|
|
if info["count_method"]:
|
|
print(f" .count() calls x{info['count_method']}")
|
|
if info["manual_count"]:
|
|
print(f" manual +=1 count x{info['manual_count']}")
|
|
if dup_funcs:
|
|
print(f" DUPLICATE function bodies: {dup_funcs}")
|
|
for l, c in repeated[:5]:
|
|
print(f" repeated x{c}: {l[:90]}")
|