返回 CodeWhale
check-dead-code-budget.py
根目录 / scripts / check-dead-code-budget.py
1 #!/usr/bin/env python3
2 """Ratchet on `#[allow(dead_code)]` so the wall can shrink but never regrow (#4785).
3
4 Why this exists rather than a one-time sweep:
5
6 issue filed 2026-07-?? 464 attributes / 143 files
7 audit 2026-07-26 426 / 111
8 audit 2026-07-28 481 / 155
9
10 The sweep was working and the count still went *up*, because two large landings
11 added state whose accessors only their own tests read. A sweep is a snapshot; a
12 budget is a direction. This gate makes the number a one-way door.
13
14 It deliberately does NOT judge whether any individual attribute is justified —
15 plenty are. It only refuses to let the total rise, which is the property the
16 issue actually needs and the only one that can be checked mechanically.
17
18 Note the blind spot this compensates for: CI's clippy runs without
19 `--all-targets`, so it never lints `cfg(test)` or integration-test code. A prior
20 strip-and-check measured 197 attributes alive *only* because a test references
21 them — exactly the ones a test-blind lint can never adjudicate.
22
23 Usage:
24 python3 scripts/check-dead-code-budget.py # enforce
25 python3 scripts/check-dead-code-budget.py --update # rewrite the budget file
26 """
27
28 from __future__ import annotations
29
30 import argparse
31 import json
32 import re
33 import sys
34 from pathlib import Path
35
36 REPO_ROOT = Path(__file__).resolve().parent.parent
37 CRATES_DIR = REPO_ROOT / "crates"
38 BUDGET_PATH = REPO_ROOT / "scripts" / "dead-code-budget.json"
39
40 # Matches `#[allow(dead_code)]`, `#![allow(dead_code)]`, and combined forms like
41 # `#[allow(dead_code, clippy::large_enum_variant)]`.
42 PATTERN = re.compile(r"allow\(\s*dead_code\b")
43
44
45 def measure() -> tuple[int, dict[str, int]]:
46 """Return (total occurrences, per-crate counts)."""
47 per_crate: dict[str, int] = {}
48 total = 0
49 for path in sorted(CRATES_DIR.rglob("*.rs")):
50 try:
51 text = path.read_text(encoding="utf-8")
52 except (OSError, UnicodeDecodeError):
53 continue
54 hits = len(PATTERN.findall(text))
55 if not hits:
56 continue
57 crate = path.relative_to(CRATES_DIR).parts[0]
58 per_crate[crate] = per_crate.get(crate, 0) + hits
59 total += hits
60 return total, per_crate
61
62
63 def load_budget() -> dict:
64 if not BUDGET_PATH.exists():
65 sys.exit(f"missing budget file: {BUDGET_PATH.relative_to(REPO_ROOT)}")
66 return json.loads(BUDGET_PATH.read_text(encoding="utf-8"))
67
68
69 def write_budget(total: int, per_crate: dict[str, int]) -> None:
70 payload = {
71 "_comment": (
72 "Ceiling for `#[allow(dead_code)]` across crates/. This number may "
73 "go down freely; raising it needs a reviewer to say why in the PR. "
74 "Regenerate with: python3 scripts/check-dead-code-budget.py --update"
75 ),
76 "_issue": "https://github.com/Hmbown/CodeWhale/issues/4785",
77 "total": total,
78 "per_crate": dict(sorted(per_crate.items())),
79 }
80 BUDGET_PATH.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
81
82
83 def main() -> int:
84 parser = argparse.ArgumentParser(description=__doc__)
85 parser.add_argument(
86 "--update",
87 action="store_true",
88 help="rewrite the budget file from the working tree",
89 )
90 args = parser.parse_args()
91
92 total, per_crate = measure()
93
94 if args.update:
95 write_budget(total, per_crate)
96 rel = BUDGET_PATH.relative_to(REPO_ROOT)
97 print(f"[dead-code-budget] wrote {rel}: total={total}")
98 return 0
99
100 budget = load_budget()
101 ceiling = int(budget["total"])
102
103 if total > ceiling:
104 print(
105 f"[dead-code-budget] FAIL: {total} `#[allow(dead_code)]` attributes, "
106 f"budget is {ceiling} (+{total - ceiling}).",
107 file=sys.stderr,
108 )
109 print("", file=sys.stderr)
110 print("Per crate now vs. budget:", file=sys.stderr)
111 recorded = budget.get("per_crate", {})
112 for crate in sorted(set(per_crate) | set(recorded)):
113 now = per_crate.get(crate, 0)
114 was = int(recorded.get(crate, 0))
115 marker = " <-- grew" if now > was else ""
116 print(f" {crate:<16} {now:>4} (budget {was}){marker}", file=sys.stderr)
117 print("", file=sys.stderr)
118 print(
119 "Either delete the dead item, or narrow the attribute to the one item\n"
120 "that needs it instead of a whole module. If the growth is genuinely\n"
121 "justified, run `python3 scripts/check-dead-code-budget.py --update`\n"
122 "and say why in the PR description — the point of this gate is that\n"
123 "raising the number is a visible decision, not an accident.",
124 file=sys.stderr,
125 )
126 return 1
127
128 if total < ceiling:
129 print(
130 f"[dead-code-budget] {total} attributes, budget {ceiling} "
131 f"({ceiling - total} under). Lower the budget to lock in the win:\n"
132 f" python3 scripts/check-dead-code-budget.py --update"
133 )
134 return 0
135
136 print(f"[dead-code-budget] PASS: {total} attributes, exactly at budget.")
137 return 0
138
139
140 if __name__ == "__main__":
141 sys.exit(main())
142
142 lines PYTHON