import { execFileSync, spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; import ts from "typescript"; const BASELINE_PATH = "config/max-lines-baseline.txt"; const GIT_MAX_BUFFER = 256 * 1024 * 1024; const SOURCE_ROOTS = ["src", "ui/src", "packages", "extensions"]; const SOURCE_EXTENSIONS = new Set([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"]); const BASELINE_HEADER = [ "# Files currently allowed to exceed the oxlint max-lines budget.", "# Ratchet: this list may only shrink. Split files; never add entries.", "# Existing suppressions carry a TODO at the file site.", "", ].join("\n"); const compareStrings = (left: string, right: string) => left.localeCompare(right); export function isGovernedSourcePath(filePath: string) { const normalized = filePath.replaceAll("\\", "/"); if (!SOURCE_ROOTS.some((root) => normalized === root || normalized.startsWith(root + "/"))) { return false; } if (!SOURCE_EXTENSIONS.has(path.posix.extname(normalized))) { return false; } return !( normalized.startsWith("ui/src/i18n/locales/") || normalized.startsWith("src/wizard/i18n/locales/") || /(?:^|\/)(?:__generated__|generated|protocol-gen|dist)(?:\/|$)/u.test(normalized) || /\.generated\.[^/]+$/u.test(normalized) ); } export function collectLintDisableDirectives(source: string, filePath = "source.ts") { if (!source.includes("oxlint-disable") && !source.includes("eslint-disable")) { return []; } const directive = /^(?:eslint|oxlint)-disable(?:-next-line|-line)?(?=$|\s)([\s\S]*)$/u; const scriptKind = /\.[cm]?[jt]sx$/u.test(filePath) ? ts.ScriptKind.TSX : ts.ScriptKind.TS; const sourceFile = ts.createSourceFile( filePath, source, ts.ScriptTarget.Latest, false, scriptKind, ); const comments = new Map(); const addComments = (ranges: readonly ts.CommentRange[] | undefined) => { for (const range of ranges ?? []) { comments.set(range.pos, source.slice(range.pos, range.end)); } }; const visit = (node: ts.Node) => { addComments(ts.getLeadingCommentRanges(source, node.pos)); addComments(ts.getTrailingCommentRanges(source, node.end)); // getChildren includes delimiter tokens; forEachChild misses directives before closing tokens. for (const child of node.getChildren(sourceFile)) { visit(child); } }; visit(sourceFile); addComments(ts.getLeadingCommentRanges(source, sourceFile.endOfFileToken.pos)); const directives: string[][] = []; for (const text of comments.values()) { const comment = text.slice(2, text.startsWith("/*") ? -2 : undefined); const match = directive.exec(comment.trim()); if (!match) { continue; } const directiveBody = match[1] ?? ""; const reason = /--|(?<=\s)-(?=\s)/u.exec(directiveBody); const rules = (reason ? directiveBody.slice(0, reason.index) : directiveBody).trim(); directives.push(rules === "" ? [] : rules.split(/[\s,]+/u)); } return directives; } export function isMaxLinesRule(rule: string) { return rule === "max-lines" || rule.endsWith("/max-lines"); } export function hasMaxLinesDisable(source: string, filePath = "source.ts") { return collectLintDisableDirectives(source, filePath).some((rules) => rules.some(isMaxLinesRule)); } export function hasAllRuleDisable(source: string, filePath = "source.ts") { return collectLintDisableDirectives(source, filePath).some((rules) => rules.length === 0); } export function parseBaseline(source: string) { return new Set( source .split(/\r?\n/u) .map((line) => line.trim()) .filter((line) => line && !line.startsWith("#")), ); } export function diffBaseline(current: Iterable, baseline: ReadonlySet) { const currentSet = new Set(current); return { added: [...currentSet].filter((entry) => !baseline.has(entry)).toSorted(compareStrings), stale: [...baseline].filter((entry) => !currentSet.has(entry)).toSorted(compareStrings), }; } export function findBaselineExpansion(current: Iterable, base: ReadonlySet) { return [...current].filter((entry) => !base.has(entry)).toSorted(compareStrings); } function baselineWithVerifiedRenames( root: string, baseRef: string, staged: boolean, baseline: ReadonlySet, baseBaseline: ReadonlySet, ) { const args = ["diff", "--name-status", "-z", "--find-renames"]; if (staged) { args.push("--cached"); } args.push(baseRef, "--", ...SOURCE_ROOTS); const fields = execFileSync("git", args, { cwd: root, maxBuffer: GIT_MAX_BUFFER }) .toString("utf8") .split("\0"); const allowed = new Set(baseBaseline); for (let index = 0; index < fields.length;) { const status = fields[index++]; if (!status) { break; } const oldPath = fields[index++]; if (status.startsWith("R") || status.startsWith("C")) { const newPath = fields[index++]; if ( status.startsWith("R") && oldPath && newPath && baseBaseline.has(oldPath) && !baseline.has(oldPath) && baseline.has(newPath) ) { allowed.delete(oldPath); allowed.add(newPath); } } } return allowed; } function readSnapshotFile(root: string, filePath: string, staged: boolean) { if (staged) { return execFileSync("git", ["show", ":" + filePath], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], }); } return fs.readFileSync(path.join(root, filePath), "utf8"); } function listStagedSuppressionCandidates(root: string) { // The staged policy covers the whole index. Narrow candidates once so a one-file // check does not spawn a Git process for every governed source. const result = spawnSync( "git", [ "grep", "--cached", "-z", "-l", "-e", "oxlint-disable", "-e", "eslint-disable", "--", ...SOURCE_ROOTS, ], { cwd: root, maxBuffer: GIT_MAX_BUFFER }, ); if (result.status === 1) { return []; } if (result.status !== 0) { throw new Error(result.stderr.toString("utf8").trim() || "git grep failed"); } return result.stdout.toString("utf8").split("\0").filter(Boolean); } function readStagedSources(root: string, filePaths: string[]) { if (filePaths.length === 0) { return new Map(); } const output = execFileSync("git", ["cat-file", "--batch", "-z"], { cwd: root, input: filePaths.map((filePath) => ":" + filePath).join("\0") + "\0", maxBuffer: GIT_MAX_BUFFER, }); const sources = new Map(); let offset = 0; // -z keeps request paths NUL-framed on older Git; response headers remain newline-framed. for (const filePath of filePaths) { const headerEnd = output.indexOf(10, offset); if (headerEnd < 0) { throw new Error("Invalid git cat-file response for " + filePath); } const header = output.subarray(offset, headerEnd).toString("utf8").split(" "); const size = Number(header[2]); if (!Number.isSafeInteger(size)) { throw new Error("Could not read staged source " + filePath); } const sourceStart = headerEnd + 1; const sourceEnd = sourceStart + size; if (output[sourceEnd] !== 10) { throw new Error("Invalid git cat-file framing for " + filePath); } sources.set(filePath, output.subarray(sourceStart, sourceEnd).toString("utf8")); offset = sourceEnd + 1; } return sources; } export function collectCurrentSuppressionState( root = process.cwd(), options: { staged?: boolean } = {}, ) { const staged = options.staged === true; const filePaths = staged ? listStagedSuppressionCandidates(root) : execFileSync( "git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...SOURCE_ROOTS], { cwd: root, maxBuffer: GIT_MAX_BUFFER }, ) .toString("utf8") .split("\0"); const governedPaths = filePaths .filter(Boolean) .filter(isGovernedSourcePath) .filter((filePath) => staged || fs.existsSync(path.join(root, filePath))); const sources = staged ? [...readStagedSources(root, governedPaths)] : governedPaths.map((filePath): [string, string] => [ filePath, fs.readFileSync(path.join(root, filePath), "utf8"), ]); return { allRules: sources .filter(([filePath, source]) => hasAllRuleDisable(source, filePath)) .map(([filePath]) => filePath) .toSorted(compareStrings), explicit: sources .filter(([filePath, source]) => hasMaxLinesDisable(source, filePath)) .map(([filePath]) => filePath) .toSorted(compareStrings), }; } export function collectCurrentSuppressions( root = process.cwd(), options: { staged?: boolean } = {}, ) { return collectCurrentSuppressionState(root, options).explicit; } function readBaselineAtRef(root: string, ref: string) { execFileSync("git", ["rev-parse", "--verify", ref + "^{commit}"], { cwd: root, stdio: "ignore", }); const entry = execFileSync("git", ["ls-tree", "--name-only", ref, "--", BASELINE_PATH], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], }).trim(); if (entry !== BASELINE_PATH) { return null; } return parseBaseline( execFileSync("git", ["show", ref + ":" + BASELINE_PATH], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], }), ); } function resolveDefaultBase(root: string, staged: boolean) { const candidates = staged ? ["HEAD"] : ["origin/main", "HEAD"]; const resolved = candidates.find((ref) => { try { execFileSync("git", ["rev-parse", "--verify", ref + "^{commit}"], { cwd: root, stdio: "ignore", }); return true; } catch { return false; } }); if (!resolved || staged || resolved !== "origin/main") { return resolved ?? null; } // A release or long-lived branch owns the suppression debt from its fork. // Comparing against moving main would turn unrelated debt cleanup into a blocker. try { return execFileSync("git", ["merge-base", "HEAD", resolved], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], }).trim(); } catch { return resolved; } } function writeBaseline(root: string, entries: string[]) { fs.writeFileSync(path.join(root, BASELINE_PATH), BASELINE_HEADER + entries.join("\n") + "\n"); } function parseArgs(argv: string[]) { const args: { base?: string; prune: boolean; staged: boolean } = { prune: false, staged: false }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (arg === "--prune") { args.prune = true; continue; } if (arg === "--staged") { args.staged = true; continue; } if (arg === "--base" && argv[index + 1]) { args.base = argv[index + 1]; index += 1; continue; } throw new Error("Unknown or incomplete argument: " + arg); } return args; } function printEntries(title: string, entries: string[]) { console.error(title); for (const entry of entries) { console.error(" " + entry); } } export function main(root = process.cwd(), argv: string[] = process.argv.slice(2)) { try { const args = parseArgs(argv); if (args.staged && args.prune) { throw new Error("--prune cannot be combined with --staged"); } let baselineSource; try { baselineSource = readSnapshotFile(root, BASELINE_PATH, args.staged); } catch { throw new Error("Missing " + BASELINE_PATH + (args.staged ? " in the index" : "")); } const baseline = parseBaseline(baselineSource); const { allRules, explicit: current } = collectCurrentSuppressionState(root, { staged: args.staged, }); const { added, stale } = diffBaseline(current, baseline); const baseRef = args.base ?? resolveDefaultBase(root, args.staged); const baseBaseline = baseRef ? readBaselineAtRef(root, baseRef) : null; const allowedBaseline = baseRef && baseBaseline ? baselineWithVerifiedRenames(root, baseRef, args.staged, baseline, baseBaseline) : baseBaseline; const expanded = allowedBaseline ? findBaselineExpansion(baseline, allowedBaseline) : []; if (added.length > 0) { printEntries("New max-lines suppressions are forbidden; split these files:", added); } if (expanded.length > 0) { printEntries("The max-lines baseline may only shrink; remove these entries:", expanded); } if (allRules.length > 0) { printEntries("All-rule lint disables are forbidden; name only the required rules:", allRules); } if (added.length > 0 || expanded.length > 0 || allRules.length > 0) { return 1; } if (args.prune) { const kept = [...baseline] .filter((entry) => current.includes(entry)) .toSorted(compareStrings); writeBaseline(root, kept); console.log("Pruned " + BASELINE_PATH + ": " + baseline.size + " -> " + kept.length + "."); return 0; } if (stale.length > 0) { printEntries("Remove stale max-lines baseline entries (or run with --prune):", stale); return 1; } console.log("max-lines ratchet OK: " + current.length + " grandfathered suppressions."); return 0; } catch (error) { console.error(error instanceof Error ? error.message : String(error)); return 1; } } if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { process.exitCode = main(); }