from __future__ import annotations
import os
import re
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
EXEMPT_FILE = REPO_ROOT / ".line-count-exempt"
FAIL_THRESHOLD = 800
WARN_THRESHOLD = 500
SKIP_DIRS = {".git", "target", "node_modules", "tests", "benches", "examples"}
CFG_TEST_RE = re.compile(r"#\[cfg\((?![^)]*\bnot\(test\))[^)]*\btest\b")
def is_test_path(rel: Path) -> bool:
name = rel.name
return (
name == "tests.rs"
or name.endswith("_tests.rs")
or name.endswith("_test.rs")
or "tests" in rel.parts[:-1]
or "benches" in rel.parts[:-1]
or "examples" in rel.parts[:-1]
)
def strip_literals(text: str) -> list[str]:
out: list[list[str]] = [[]]
def emit(ch: str) -> None:
if ch == "\n":
out.append([])
else:
out[-1].append(ch)
i = 0
n = len(text)
while i < n:
ch = text[i]
m = re.match(r'(?:b?r)(#*)"', text[i:])
if m and (i == 0 or not (text[i - 1].isalnum() or text[i - 1] == "_")):
hashes = m.group(1)
terminator = '"' + hashes
end = text.find(terminator, i + m.end())
end = n if end == -1 else end + len(terminator)
for c in text[i:end]:
emit(" " if c != "\n" else "\n")
i = end
continue
if ch == '"':
i += 1
emit(" ")
while i < n:
if text[i] == "\\":
emit(" ")
i += 1
if i < n:
emit(" " if text[i] != "\n" else "\n")
i += 1
continue
if text[i] == '"':
emit(" ")
i += 1
break
emit(" " if text[i] != "\n" else "\n")
i += 1
continue
if ch == "'":
m = re.match(r"'(?:\\.|\\u\{[0-9a-fA-F]+\}|[^\\'])'", text[i:])
if m:
for _ in range(m.end()):
emit(" ")
i += m.end()
continue
emit(ch)
i += 1
continue
if text.startswith("//", i):
while i < n and text[i] != "\n":
emit(" ")
i += 1
continue
if text.startswith("/*", i):
depth = 0
while i < n:
if text.startswith("/*", i):
depth += 1
emit(" ")
emit(" ")
i += 2
continue
if text.startswith("*/", i):
depth -= 1
emit(" ")
emit(" ")
i += 2
if depth == 0:
break
continue
emit(text[i] if text[i] == "\n" else " ")
i += 1
continue
emit(ch)
i += 1
return ["".join(line) for line in out]
def production_lines(path: Path) -> int:
text = path.read_text(encoding="utf-8", errors="replace")
if "#[cfg(" not in text:
return len(text.splitlines())
code = strip_literals(text)
if code and code[-1] == "":
code.pop()
excluded: set[int] = set()
i = 0
total = len(code)
while i < total:
if not CFG_TEST_RE.search(code[i].strip()):
i += 1
continue
depth = 0
opened = False
j = i
while j < total:
line = code[j]
depth += line.count("{") - line.count("}")
if "{" in line:
opened = True
if opened and depth <= 0:
break
if not opened and ";" in line and (j > i or ";" in line.split("]", 1)[-1]):
break
j += 1
excluded.update(range(i, min(j, total - 1) + 1))
i = j + 1
return total - len(excluded)
def load_exemptions() -> dict[str, str]:
exemptions: dict[str, str] = {}
if not EXEMPT_FILE.exists():
return exemptions
for lineno, line in enumerate(
EXEMPT_FILE.read_text(encoding="utf-8").splitlines(), start=1
):
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
if ":" not in stripped:
print(
f"{EXEMPT_FILE.name}:{lineno}: entry needs a `path: reason`, got {stripped!r}",
file=sys.stderr,
)
sys.exit(2)
path, reason = stripped.split(":", 1)
reason = reason.strip()
if not reason:
print(
f"{EXEMPT_FILE.name}:{lineno}: exemption for {path.strip()} has no reason",
file=sys.stderr,
)
sys.exit(2)
exemptions[path.strip()] = reason
return exemptions
def main() -> int:
exemptions = load_exemptions()
violations: list[tuple[str, int, str]] = []
warnings: list[tuple[str, int]] = []
exempt_hits: set[str] = set()
files = []
for dirpath, dirnames, filenames in os.walk(REPO_ROOT):
dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
for filename in filenames:
if not filename.endswith(".rs"):
continue
path = Path(dirpath) / filename
rel = path.relative_to(REPO_ROOT)
if is_test_path(rel):
continue
files.append((rel, path))
files.sort()
for rel, path in files:
count = production_lines(path)
key = rel.as_posix()
if count > FAIL_THRESHOLD:
if key in exemptions:
exempt_hits.add(key)
print(f"exempt {key}: {count} lines ({exemptions[key]})")
else:
violations.append((key, count, ""))
elif count > WARN_THRESHOLD:
warnings.append((key, count))
if key in exemptions:
exempt_hits.add(key)
elif key in exemptions:
exempt_hits.add(key)
if warnings:
print(
f"\n{len(warnings)} file(s) over the {WARN_THRESHOLD}-line target "
f"(warning only, largest first):"
)
for key, count in sorted(warnings, key=lambda item: -item[1])[:10]:
print(f" {key}: {count} lines")
if len(warnings) > 10:
print(f" ... and {len(warnings) - 10} more")
stale = sorted(set(exemptions) - exempt_hits)
if stale:
print("\nExemptions that are no longer needed (remove them):")
for key in stale:
print(f" {key}")
if violations:
print(
f"\nFAIL: {len(violations)} production file(s) exceed the "
f"{FAIL_THRESHOLD}-line refactor threshold from CLAUDE.md:"
)
for key, count, _ in sorted(violations, key=lambda item: -item[1]):
print(f" {key}: {count} production lines (limit {FAIL_THRESHOLD})")
print(
"\nSplit the file, or add it to .line-count-exempt with a reason "
"if it is a generated table or has no natural seam."
)
return 1
print(
f"\nOK: {len(files)} production file(s) checked, none over "
f"{FAIL_THRESHOLD} lines."
)
return 0
if __name__ == "__main__":
sys.exit(main())