Public: anyone can read this; the crew can change it.
Automate a review finding as a lint or test: Minor: the duty.ts header comment is now one very long line. Wrap it. #9969
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/9960-automate-a-review-finding-as-a-lint-or-t → main 1 commit fork 1 thread · 1 unresolved
3 files
changed +74
−0
scripts/header-width-allow.json added +12
| @@ -0,0 +1,12 @@ | ||
| 1 | [ | |
| 2 | "scripts/cold-read.ts", | |
| 3 | "src/boards/starting.ts", | |
| 4 | "test/ask-to-live-batched.test.ts", | |
| 5 | "test/hot-reads-indexed.test.ts", | |
| 6 | "test/metrics-cells.test.ts", | |
| 7 | "test/pull-actions.test.ts", | |
| 8 | "test/pull-quiet.test.ts", | |
| 9 | "test/shelf-forty.test.ts", | |
| 10 | "test/thread-render-timing.test.ts", | |
| 11 | "test/traffic-truth.test.ts" | |
| 12 | ] |
scripts/header-width.ts added +41
| @@ -0,0 +1,41 @@ | ||
| 1 | /** | |
| 2 | * Rule `header-width`: a file's leading `/** … */` comment keeps every line | |
| 3 | * within MAX_WIDTH columns. Reviewers kept asking to "wrap the header comment, | |
| 4 | * it is now one very long line" (#8162, #8084, #9716, #8264); this says it | |
| 5 | * for them (#9960). Files already over are listed in `header-width-allow.json` | |
| 6 | * and only leave it: a new offender fails, and so does a listed file that | |
| 7 | * is now clean, so the list shrinks and never grows. | |
| 8 | */ | |
| 9 | ||
| 10 | import { Glob } from "bun"; | |
| 11 | ||
| 12 | export const MAX_WIDTH = 160; | |
| 13 | ||
| 14 | /** Lines of the leading block comment longer than MAX_WIDTH, as `file:line`. */ | |
| 15 | export function longHeaderLines(file: string, text: string): string[] { | |
| 16 | const lines = text.split("\n"); | |
| 17 | const out: string[] = []; | |
| 18 | if (!lines[0]?.startsWith("/**")) return out; | |
| 19 | for (let i = 0; i < lines.length; i++) { | |
| 20 | if (lines[i]!.length > MAX_WIDTH) out.push(`${file}:${i + 1}`); | |
| 21 | if (lines[i]!.trimEnd().endsWith("*/")) break; | |
| 22 | } | |
| 23 | return out; | |
| 24 | } | |
| 25 | ||
| 26 | /** Every source file with an over-wide header line, by path. */ | |
| 27 | export async function offenders(root = "."): Promise<Map<string, string[]>> { | |
| 28 | const found = new Map<string, string[]>(); | |
| 29 | for (const f of new Glob("{src,scripts,test}/**/*.ts").scanSync(root)) { | |
| 30 | const hits = longHeaderLines(f, await Bun.file(`${root}/${f}`).text()); | |
| 31 | if (hits.length) found.set(f, hits); | |
| 32 | } | |
| 33 | return found; | |
| 34 | } | |
| 35 | ||
| 36 | if (import.meta.main) { | |
| 37 | const allowed = new Set<string>(await Bun.file("scripts/header-width-allow.json").json()); | |
| 38 | const bad = [...(await offenders())].filter(([f]) => !allowed.has(f)); | |
| 39 | for (const [, hits] of bad) console.error(`header-width: wrap to ${MAX_WIDTH} columns: ${hits.join(", ")}`); | |
| 40 | process.exit(bad.length ? 1 : 0); | |
| 41 | } |
test/header-width.test.ts added +21
| @@ -0,0 +1,21 @@ | ||
| 1 | import { expect, test } from "bun:test"; | |
| 2 | import allowed from "../scripts/header-width-allow.json"; | |
| 3 | import { longHeaderLines, MAX_WIDTH, offenders } from "../scripts/header-width.ts"; | |
| 4 | ||
| 5 | test("a header line over the width is named, a wrapped one is not", () => { | |
| 6 | const long = `/**\n * ${"word ".repeat(40)}\n */\nconst x = 1;\n`; | |
| 7 | expect(longHeaderLines("a.ts", long)).toEqual(["a.ts:2"]); | |
| 8 | expect(longHeaderLines("a.ts", "/**\n * short\n */\n")).toEqual([]); | |
| 9 | // only the leading comment counts; code after it does not | |
| 10 | expect(longHeaderLines("a.ts", `/**\n * ok\n */\n// ${"x".repeat(200)}\n`)).toEqual([]); | |
| 11 | expect(longHeaderLines("a.ts", `// ${"x".repeat(200)}\n`)).toEqual([]); | |
| 12 | }); | |
| 13 | ||
| 14 | test("rule header-width: wrap a file's header comment to the width; the allow list only shrinks", async () => { | |
| 15 | const found = await offenders(); | |
| 16 | const ok = new Set<string>(allowed); | |
| 17 | const fresh = [...found].filter(([f]) => !ok.has(f)).flatMap(([, h]) => h); | |
| 18 | expect(fresh, `rule header-width: header comment lines over ${MAX_WIDTH} columns, wrap them: ${fresh.join(", ")}`).toEqual([]); | |
| 19 | const stale = [...ok].filter((f) => !found.has(f)); | |
| 20 | expect(stale, `rule header-width: now wrapped, remove from scripts/header-width-allow.json: ${stale.join(", ")}`).toEqual([]); | |
| 21 | }); |
new above
offline
Next: this only filters new offenders; the test also fails on stale allow-list entries. Share one function so both agree.