import { readdirSync, readFileSync } from "node:fs"; import { fileURLToPath, pathToFileURL } from "node:url"; import { ESLint, type Linter } from "eslint"; import prettierConfig from "eslint-config-prettier"; import ts from "typescript"; import tseslint from "typescript-eslint"; import { describe, expect, it } from "vitest"; // The gate register (docs/gates.md) is held to the EFFECTIVE configuration (E32). A narrowing that // is not on the register fails here — a rule switched off for a scope, a weak compiler flag, a ruff // ignore, a knip exclusion, an accepted CVE. So "the gate checks X" is a claim this test verifies, // not one a comment makes. (The comment that claimed the React Compiler's rules "are also linted" // stood for months while the rule that would have said so was off.) // // Each narrowing is a (scope, id) pair; the register must hold a table row whose first cell is the // scope and which names the id in backticks. const REPO = fileURLToPath(new URL("../../../", import.meta.url)); const FRONTEND = `${REPO}frontend/`; const read = (path: string) => readFileSync(REPO + path, "utf8"); /** register scope → every backticked id on its rows. */ function register(): Map> { const rows = new Map>(); for (const line of read("docs/gates.md").split("\n")) { if (!line.startsWith("|") || /^\|(\s*-+\s*\|)+\s*$/.test(line)) continue; // not the |---| rule const scope = line.split("|")[1]!.trim(); const ids = rows.get(scope) ?? new Set(); for (const m of line.matchAll(/`([^`]+)`/g)) ids.add(m[1]!); rows.set(scope, ids); } return rows; } function missingFrom(found: [scope: string, id: string][]): string[] { const reg = register(); return found.filter(([scope, id]) => !reg.get(scope)?.has(id)).map(([s, id]) => `${s}: ${id}`); } const jsonc = (path: string) => { const { config, error } = ts.parseConfigFileTextToJson(path, read(path)); if (error) throw new Error(`${path}: ${ts.flattenDiagnosticMessageText(error.messageText, "\n")}`); return config as Record; }; // --- ESLint --------------------------------------------------------------------------------------- type Rules = Partial; const severity = (v: Linter.RuleEntry | undefined): number => { if (v === undefined) return -1; // not applied at all const s = Array.isArray(v) ? v[0] : v; return s === 2 || s === "error" ? 2 : s === 1 || s === "warn" ? 1 : 0; }; // Formatting is Prettier's (its own lane): the rules eslint-config-prettier switches off are not a // narrowing of anything. const PRETTIER_OFF = new Set( Object.entries(prettierConfig.rules ?? {}) .filter(([, v]) => severity(v as Linter.RuleEntry) === 0) .map(([r]) => r), ); // …and the core rules typescript-eslint's compatibility layer switches off for TS files because the // type checker reports them itself (no-undef, no-redeclare, no-dupe-keys, …). const TSC_OWNED = new Set( Object.entries(tseslint.configs.eslintRecommended.rules ?? {}) .filter(([, v]) => severity(v as Linter.RuleEntry) === 0) .map(([r]) => r), ); const family = (rule: string) => (rule.includes("/") ? `${rule.split("/")[0]}/*` : rule); // Each scope is checked through a sample file's EFFECTIVE config (what a rule family not applied there // means), and every config block that targets files is checked on its own (below) — so a block no // sample reaches fails rather than passing unseen (E32 review: a narrowing scoped to any other file // changed none of the sampled configs and needed no row). const SCOPES: Record = { src: "src/components/Feed.tsx", "src/lib": "src/lib/format.ts", "lib/http.ts": "src/lib/http.ts", "lib/api/core.ts": "src/lib/api/core.ts", "lib/messagesSocket.ts": "src/lib/messagesSocket.ts", "src tests": "src/components/Feed.ytsort.dom.test.tsx", "FocusSafeButton.tsx": "src/components/ui/FocusSafeButton.tsx", "AnchoredPopover.tsx": "src/components/ui/AnchoredPopover.tsx", "design-lab": "src/design-lab/batchA.tsx", e2e: "e2e/nav.spec.ts", "ts configs": "vite.config.ts", "js configs": "eslint.config.js", }; /** A flat-config `files` glob as a regex — the shapes this config uses: `**`, `*`, `{a,b}`. */ function globRegex(glob: string): RegExp { let out = ""; for (let i = 0; i < glob.length; i++) { const c = glob[i]!; if (glob.startsWith("**/", i)) { out += "(?:.*/)?"; i += 2; } else if (c === "*") out += "[^/]*"; else if (c === "{") { const end = glob.indexOf("}", i); out += `(?:${glob .slice(i + 1, end) .split(",") .join("|")})`; i = end; } else out += /[.+?^$()|[\]\\]/.test(c) ? `\\${c}` : c; } return new RegExp(`^${out}$`); } type Block = Linter.Config & { files?: (string | string[])[]; ignores?: string[] }; const blocks = async (): Promise => ((await import(pathToFileURL(`${FRONTEND}eslint.config.js`).href)) as { default: Block[] }) .default; // A rule option whose key says it lets something through (`allowShortCircuit`, // `allowForKnownSafeCalls`, `ignoreRestSiblings`, `exceptAfterSingleLine`) narrows the rule, and a // `no-restricted-*` list re-declared for a scope narrows it by every entry it leaves out. const RELAXING_KEY = /^(allow|ignore|except)/; const relaxes = (options: unknown[]): boolean => options.some( (o) => o !== null && typeof o === "object" && Object.entries(o).some( ([k, v]) => RELAXING_KEY.test(k) && v !== false && !(Array.isArray(v) && v.length === 0), ), ); function restrictedEntries(entry: Linter.RuleEntry | undefined): Set { const opts: unknown[] = Array.isArray(entry) ? entry.slice(1) : []; const flat = opts.flatMap((o) => { if (o === null || typeof o !== "object" || Array.isArray(o)) return [o]; const { paths = [], patterns = [] } = o as { paths?: unknown[]; patterns?: unknown[] }; return "paths" in o || "patterns" in o ? [...paths, ...patterns] : [o]; }); return new Set(flat.map((e) => JSON.stringify(e))); } async function eslintNarrowings(): Promise<[string, string][]> { const eslint = new ESLint({ cwd: FRONTEND }); const rules: Record = {}; for (const [scope, file] of Object.entries(SCOPES)) rules[scope] = ((await eslint.calculateConfigForFile(file)) as Linter.Config).rules ?? {}; const src = rules.src!; const ignorable = (rule: string, all: Rules) => PRETTIER_OFF.has(rule) || TSC_OWNED.has(rule) || // a core rule off because its typescript-eslint twin replaces it is reported as the twin (!rule.includes("/") && `@typescript-eslint/${rule}` in all); const found: [string, string][] = []; // src: everything at off or warn, and every rule whose options let something through for (const [rule, v] of Object.entries(src)) { if (ignorable(rule, src)) continue; if (severity(v) < 2 || (Array.isArray(v) && relaxes(v.slice(1)))) found.push(["src", rule]); } // every other scope: what is WEAKER there than in src (a rule family not applied at all is one // id). A src rule at warn counts too: switched off for a scope, it is narrowed there. for (const scope of Object.keys(SCOPES).filter((s) => s !== "src")) { const ids = new Set(); for (const [rule, v] of Object.entries(src)) { if (ignorable(rule, src) || severity(v) === 0) continue; const there = severity(rules[scope]![rule]); if (there < severity(v)) ids.add(there < 0 ? family(rule) : rule); } for (const id of ids) found.push([scope, id]); } // every config block: a global `ignores` is a narrowing everywhere; one that targets files must // reach a sample, and what it re-declares is held to src for (const block of await blocks()) { if (!block.files) { if (Object.keys(block).join() === "ignores") for (const ignored of block.ignores ?? []) found.push(["everywhere", ignored]); continue; } const globs = block.files.flat().map(globRegex); const scope = Object.keys(SCOPES).find((s) => globs.some((g) => g.test(SCOPES[s]!))); if (!scope) { found.push(["unsampled block", block.files.flat().join(", ")]); continue; } if (block.ignores?.length) found.push([scope, `ignores: ${block.ignores.join(", ")}`]); for (const [rule, v] of Object.entries(block.rules ?? {})) { if (ignorable(rule, src)) continue; if (rule.includes("no-restricted-")) { const own = restrictedEntries(v); if ([...restrictedEntries(src[rule])].some((e) => !own.has(e))) found.push([scope, rule]); } else if (Array.isArray(v) && relaxes(v.slice(1))) found.push([scope, rule]); } } return found; } describe("the gate register (docs/gates.md)", () => { it("lists every ESLint rule narrowed, by scope", async () => { expect(missingFrom(await eslintNarrowings())).toEqual([]); }, 60_000); it("keeps --max-warnings 0, which is what makes a warn-level rule fail", () => { const pkg = JSON.parse(read("frontend/package.json")) as { scripts: Record }; expect(pkg.scripts.lint).toMatch(/--max-warnings 0\b/); }); // --- TypeScript --------------------------------------------------------------------------------- const PROJECTS = [ "frontend/tsconfig.json", "frontend/tsconfig.node.json", "frontend/e2e/tsconfig.json", ]; const REQUIRED_ON = [ "strict", "noUncheckedIndexedAccess", "noUnusedLocals", "noUnusedParameters", "noFallthroughCasesInSwitch", ]; it.each(PROJECTS)("%s keeps the strict flags on", (path) => { const opts = jsonc(path).compilerOptions as Record; expect(REQUIRED_ON.filter((flag) => opts[flag] !== true)).toEqual([]); }); // `strict: true` is a bundle: a sub-flag set false beside it, an `extends` this test cannot see // through, or a narrowed `include` / an `exclude` weakens the project while the five flags above // still read true (E32 review). const STRICT_FAMILY = [ "noImplicitAny", "strictNullChecks", "strictFunctionTypes", "strictBindCallApply", "strictPropertyInitialization", "noImplicitThis", "alwaysStrict", "useUnknownInCatchVariables", "strictBuiltinIteratorReturn", ]; const INCLUDE: Record = { "frontend/tsconfig.json": ["src"], "frontend/tsconfig.node.json": [ "vite.config.ts", "vitest.config.ts", "reactPlugin.ts", "devOnlyGuard.ts", "playwright.config.ts", "postcss.config.js", ], "frontend/e2e/tsconfig.json": [".", "../playwright.config.ts"], }; it("lists every weakening compiler flag", () => { const found: [string, string][] = []; for (const path of PROJECTS) { const cfg = jsonc(path); const opts = cfg.compilerOptions as Record; if (opts.skipLibCheck === true) found.push(["all three projects", "skipLibCheck"]); for (const flag of STRICT_FAMILY) if (opts[flag] === false) found.push([path, flag]); for (const key of ["extends", "exclude", "files"]) if (key in cfg) found.push([path, key]); expect(cfg.include, path).toEqual(INCLUDE[path]); } expect(missingFrom(found)).toEqual([]); }); it("type-checks every config file at the frontend root, or lists it", () => { const checked = new Set(INCLUDE["frontend/tsconfig.node.json"]); const roots = readdirSync(FRONTEND).filter((f) => /[.](ts|js|mjs|cjs)$/.test(f)); const found = roots .filter((f) => !checked.has(f)) .map((f): [string, string] => ["config files", `${f} — not type-checked`]); expect(missingFrom(found)).toEqual([]); }); // --- Ruff ----------------------------------------------------------------------------------------- // Ruff, pyright, pytest and knip are read as KEY whitelists: a key this test does not know (an // `extend-exclude`, a `lint.extend-ignore`, a narrowing `select`, a pyright `exclude`, a pytest // `-m "not slow"`, a knip `rules`) fails until someone decides whether it narrows (E32 review — // only `ignore` and the immutable calls used to be read). const tomlKeys = (toml: string) => toml .split("\n") .map((l) => /^\s*([A-Za-z0-9_.-]+)\s*=/.exec(l)?.[1]) .filter((k): k is string => k !== undefined); it("lists every ruff ignore and immutable call, and knows every key", () => { const toml = read("backend/ruff.toml"); const list = (key: string) => { const m = new RegExp(`^${key}\\s*=\\s*\\[([^\\]]*)\\]`, "m").exec(toml); return m ? [...m[1]!.matchAll(/"([^"]+)"/g)].map((x) => x[1]!) : []; }; expect(tomlKeys(toml).sort()).toEqual( ["extend-immutable-calls", "extend-select", "ignore", "line-length", "target-version"].sort(), ); // the selection on top of ruff's defaults (E, F) — dropping one narrows the lane expect(list("extend-select")).toEqual([ "LOG", "G", "T20", "TRY400", "TRY401", "S110", "S112", "BLE001", "RUF100", "PLW1510", "B", ]); const found: [string, string][] = [ ...list("ignore").map((code): [string, string] => ["backend + scripts", code]), ...list("extend-immutable-calls").map((c): [string, string] => ["backend + scripts", c]), ]; expect(missingFrom(found)).toEqual([]); }); // --- basedpyright --------------------------------------------------------------------------------- const PYRIGHT_KEYS = [ "include", "extraPaths", "typeCheckingMode", "pythonVersion", "pythonPlatform", ]; it("lists the type-check mode, the baseline and every rule override, and knows every key", () => { const cfg = jsonc("backend/pyrightconfig.json"); const found: [string, string][] = []; if (cfg.typeCheckingMode !== "all" && cfg.typeCheckingMode !== "strict") found.push(["backend/app + alembic", "typeCheckingMode"]); found.push(["backend/app + alembic", "baseline.json"]); const weak = [false, "none", "warning", "information", "hint"]; for (const [key, value] of Object.entries(cfg)) if (key.startsWith("report") && weak.includes(value as string | boolean)) found.push(["backend/app + alembic", key]); expect( Object.keys(cfg).filter((k) => !k.startsWith("report") && !PYRIGHT_KEYS.includes(k)), ).toEqual([]); expect(cfg.include).toEqual(["app", "alembic"]); // tests/ out — its row says why expect(missingFrom(found)).toEqual([]); }); // --- pytest --------------------------------------------------------------------------------------- it("keeps pytest strict: the whole tests/ tree, no selection, no warning filters, --strict-markers", () => { const ini = read("backend/pytest.ini"); const keys = ini .split("\n") .filter((l) => !l.trim().startsWith("#")) .map((l) => /^([a-z_]+)\s*=\s*(.*)$/.exec(l)) .filter((m) => m !== null); expect(Object.fromEntries(keys.map((m) => [m[1], m[2]!.trim()]))).toEqual({ testpaths: "tests", pythonpath: ".", addopts: "-q -rs --strict-markers", }); }); // --- knip ----------------------------------------------------------------------------------------- it("lists every knip production exclusion and every other key, and runs both modes", () => { const cfg = jsonc("frontend/knip.jsonc") as { project?: string[] } & Record; const found: [string, string][] = (cfg.project ?? []) .filter((p) => p.startsWith("!")) .map((p): [string, string] => ["--production", p]); for (const key of Object.keys(cfg).filter( (k) => !["$schema", "project", "playwright"].includes(k), )) found.push(["knip", key]); expect(missingFrom(found)).toEqual([]); const pkg = JSON.parse(read("frontend/package.json")) as { scripts: Record }; expect(pkg.scripts.knip).toBe("knip && knip --production"); }); // --- accepted findings ---------------------------------------------------------------------------- it("accepts a CVE only with a statement, an expiry, and a row", () => { const yaml = read(".trivyignore.yaml"); const entries = yaml .split(/\n\s*- id:\s*/) .slice(1) .map((chunk) => ({ id: chunk.split("\n")[0]!.trim(), statement: /\n\s*statement:\s*\S/.test(chunk), expires: /\n\s*expired_at:\s*\d{4}-\d{2}-\d{2}/.test(chunk), })); expect(entries.filter((e) => !e.statement || !e.expires).map((e) => e.id)).toEqual([]); const pipIgnores = read(".gitea/workflows/check.yml") .split("\n") .filter((l) => !l.trim().startsWith("#")) // the step's comment explains the flag; it is not one .flatMap((l) => [...l.matchAll(/--ignore-vuln[ =](\S+)/g)].map((m) => m[1]!)); const found = [...entries.map((e) => e.id), ...pipIgnores].map((id): [string, string] => [ "accepted", id, ]); expect(missingFrom(found)).toEqual([]); }); });