import { execFileSync, spawnSync } from "node:child_process"; import { createHash } from "node:crypto"; import { readdirSync, readFileSync, writeFileSync } from "node:fs"; import { relative, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { ESLint, type Linter } from "eslint"; import prettierConfig from "eslint-config-prettier"; import { format, resolveConfig } from "prettier"; import { parse as parseToml } from "smol-toml"; 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. 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 // a row may cover several scopes the same way: `src tests · ui tests · lib tests` for (const scope of line .split("|")[1]! .split(" · ") .map((s) => s.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 --------------------------------------------------------------------------------------- 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); // 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))); } // --- the snapshot --------------------------------------------------------------------------------- // The configs that can narrow a gate in more shapes than a check can enumerate are held to a // COMMITTED SNAPSHOT: ESLint's effective config per scope, and the lines that run the // lanes. Any change fails until the snapshot is regenerated, so a narrowing arrives as a diff of // `gates.snapshot.json` in review instead of slipping past a detector. (Three review rounds kept // finding shapes the detector this replaced did not model — a `basePath` global ignore, a widened glob // filed under a narrow row, a substring check on a gate line. A comparison against everything has no // shape to miss.) // // A scope is named by PATH (`scopeOf`), and every file of a scope must get the same effective config. // So a new file in a known place changes nothing (the snapshot once listed every file, and 31% of // commits regenerated it), while a glob that moves a file into another config splits its scope and // fails below, and a scope whose rules change is a snapshot diff. // // Regenerate: GATE_SNAPSHOT=update npx vitest run src/lib/gates.contract.test.ts // then read the diff like any gate change. A new kind of file (a new config or a new place) needs a // line in `scopeOf` and, when it narrows anything, its register rows. const SNAPSHOT = "frontend/src/lib/gates.snapshot.json"; type EslintConfig = { rules: Record; linterOptions: Record; languageOptions: Record; plugins: string[]; settings: unknown; }; type Snapshot = { eslint: Record; lanes: Record; }; /** The register scope a code file belongs to, by its path (relative to frontend/). */ function scopeOf(file: string): string { if (!file.includes("/")) return /[.][mc]?js$/.test(file) ? "js configs" : "ts configs"; if (file.startsWith("e2e/")) return "e2e"; if (file.startsWith("src/design-lab/")) return "design-lab"; // the network owners (NETWORK_OWNERS in eslint.config.js) if (file === "src/lib/http.ts") return "lib/http.ts"; if (file === "src/lib/api/core.ts") return "lib/api/core.ts"; const test = /[.]test[.]tsx?$/.test(file); if (file.startsWith("src/components/ui/")) return test ? "ui tests" : "ui"; if (file.startsWith("src/lib/")) return test ? "lib tests" : "src/lib"; return test ? "src tests" : "src"; } const sortKeys = (v: unknown): unknown => Array.isArray(v) ? v.map(sortKeys) : v !== null && typeof v === "object" ? Object.fromEntries( Object.keys(v) .sort() .map((k) => [k, sortKeys((v as Record)[k])]), ) : v; // through JSON, so a value compares as what the file can hold (a RegExp option is `{}` on both sides) const canonical = (v: T): T => JSON.parse(JSON.stringify(sortKeys(v) ?? null)) as T; function normalize(cfg: Linter.Config): EslintConfig { const lang = { ...(cfg.languageOptions ?? {}) } as Record; const parser = lang.parser as { meta?: { name?: string } } | undefined; const parserOptions = { ...(lang.parserOptions as Record | undefined) }; if (typeof parserOptions.tsconfigRootDir === "string") // absolute: differs per machine parserOptions.tsconfigRootDir = relative(FRONTEND, parserOptions.tsconfigRootDir) || "."; // ~1200 browser globals per config: held by count and digest, not listed const globals = Object.entries((lang.globals ?? {}) as Record) .map(([name, v]) => `${name}:${String(v)}`) .sort(); const digest = createHash("sha1").update(globals.join("\n")).digest("hex").slice(0, 12); return canonical({ languageOptions: { ...lang, parser: parser ? (parser.meta?.name ?? "(unnamed parser)") : null, parserOptions, globals: `${globals.length} names, sha1 ${digest}`, }, linterOptions: (cfg.linterOptions ?? {}) as Record, plugins: Object.keys(cfg.plugins ?? {}).sort(), settings: cfg.settings ?? null, rules: Object.fromEntries( Object.entries(cfg.rules ?? {}).map(([rule, v]) => [ rule, Array.isArray(v) ? [severity(v), ...v.slice(1)] : [severity(v)], ]), ), }); } // Every code file `eslint .` walks into. The skipped directories are the global ignores' generated // output (the register's `everywhere` row) — untracked, and present on one machine but not another. // Every other file is asked for its config, so a file a new ignore drops is reported as not linted. const WALK_SKIP = new Set([ "node_modules", "dist", "playwright-report", "test-results", "e2e/.auth", ]); const CODE = /[.](?:[mc]?[jt]s|[jt]sx)$/; function codeFiles(dir = ""): string[] { return readdirSync(FRONTEND + dir, { withFileTypes: true }).flatMap((d) => { const path = dir + d.name; if (d.isDirectory()) return WALK_SKIP.has(path) ? [] : codeFiles(`${path}/`); return CODE.test(d.name) ? [path] : []; }); } // On disk every config but src is written as its difference from src — the rules it sets otherwise // and the src rules it does not apply — so a scope reads like its register rows, and one rule set // is not repeated a dozen times. In memory it is always whole. type Stored = Omit & { rules: Record; notApplied?: string[]; }; function toStored(configs: Record): Record { const src = configs.src?.rules; if (!src) return configs; return Object.fromEntries( Object.entries(configs).map(([name, cfg]) => { if (name === "src") return [name, cfg]; const rules = Object.fromEntries( Object.entries(cfg.rules).filter(([r, v]) => JSON.stringify(v) !== JSON.stringify(src[r])), ); return [ name, { ...cfg, rules, notApplied: Object.keys(src).filter((r) => !(r in cfg.rules)) }, ]; }), ); } function fromStored(stored: Record): Record { const src = stored.src?.rules ?? {}; return Object.fromEntries( Object.entries(stored).map(([name, { notApplied, ...cfg }]) => { if (!notApplied) return [name, cfg]; const rules = { ...src, ...cfg.rules }; for (const r of notApplied) delete rules[r]; return [name, canonical({ ...cfg, rules })]; }), ); } const committed = (): Snapshot => { const s = JSON.parse(read(SNAPSHOT)) as Omit & { eslint: Record; }; return canonical({ ...s, eslint: fromStored(s.eslint) }); }; type EslintActual = { configs: Record; /** the files no config lints */ notLinted: string[]; /** `scope: file` for each file whose config is not its scope's (the scope's majority) */ strays: string[]; }; async function eslintActual(): Promise { const eslint = new ESLint({ cwd: FRONTEND }); const byScope = new Map>(); const notLinted: string[] = []; for (const file of codeFiles().sort()) { const raw = (await eslint.calculateConfigForFile(file)) as Linter.Config | undefined; if (!raw) { notLinted.push(file); continue; } const config = normalize(raw); const key = JSON.stringify(config); const scope = scopeOf(file); const groups = byScope.get(scope) ?? new Map(); const group = groups.get(key) ?? { config, files: [] }; group.files.push(file); groups.set(key, group); byScope.set(scope, groups); } const configs: Record = {}; const strays: string[] = []; for (const [scope, groups] of byScope) { const [first, ...rest] = [...groups.values()].sort((a, b) => b.files.length - a.files.length); configs[scope] = first!.config; for (const g of rest) strays.push(...g.files.map((f) => `${scope}: ${f}`)); } return { configs: canonical(configs), notLinted, strays }; } // the lines that run each lane, comments and blank lines aside function meaningful(path: string): string[] { return read(path) .split("\n") .map((l) => l.trimEnd()) .filter((l) => { const t = l.trim(); return t !== "" && !/^(#|\/\/|\/\*|\*)/.test(t); }); } const lanes = () => ({ "scripts/check.sh": meaningful("scripts/check.sh"), "backend/docker-compose.test.yml": meaningful("backend/docker-compose.test.yml"), "backend/Dockerfile.test": meaningful("backend/Dockerfile.test"), "frontend/package.json scripts": ( JSON.parse(read("frontend/package.json")) as { scripts: Record } ).scripts, "frontend/vitest.config.ts": meaningful("frontend/vitest.config.ts"), "frontend/.prettierignore": meaningful("frontend/.prettierignore"), }); let actual: Promise | undefined; const linted = () => (actual ??= eslintActual()); const current = async (): Promise => canonical({ eslint: (await linted()).configs, lanes: lanes() }); // --- the register, read off the snapshot ------------------------------------------------------------ const ignorable = (rule: string, all: Record) => 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 entry = (v: unknown[] | undefined) => v as Linter.RuleEntry | undefined; /** Every narrowing in the effective ESLint configs, as (scope, id). */ function eslintNarrowings({ configs, notLinted }: EslintActual): [string, string][] { const found: [string, string][] = notLinted.map((f) => ["everywhere", `${f} — not linted`]); const src = configs.src?.rules; if (!src) return [...found, ["src", "(no config named src)"]]; for (const [scope, { rules, linterOptions }] of Object.entries(configs)) { // an unused `eslint-disable` is an error on every file, or a stale suppression stays armed if (severity(linterOptions.reportUnusedDisableDirectives as Linter.RuleEntry) !== 2) found.push([scope, "reportUnusedDisableDirectives"]); const ids = new Set(); for (const [rule, v] of Object.entries(rules)) { if (ignorable(rule, rules)) continue; const mine = entry(v)!; const theirs = scope === "src" ? undefined : entry(src[rule]); const relaxed = relaxes(v.slice(1)) && JSON.stringify(v) !== JSON.stringify(src[rule]); if (theirs === undefined) { // src, or a rule src does not have (the test and e2e plugins): at off or warn, or relaxed if (severity(mine) < 2 || (scope === "src" ? relaxes(v.slice(1)) : relaxed)) ids.add(rule); } else if (severity(mine) < severity(theirs)) ids.add(rule); else if (rule.includes("no-restricted-")) { const own = restrictedEntries(mine); if ([...restrictedEntries(theirs)].some((e) => !own.has(e))) ids.add(rule); } else if (relaxed) ids.add(rule); } // a src rule not applied at all here: one id for its whole family if (scope !== "src") for (const [rule, v] of Object.entries(src)) if (!(rule in rules) && !ignorable(rule, src) && severity(entry(v)) > 0) ids.add(family(rule)); for (const id of ids) found.push([scope, id]); } return found; } describe("the gate snapshot (gates.snapshot.json)", () => { it("holds the effective ESLint config of every scope and the lines that run each lane", async () => { const now = await current(); if (process.env.GATE_SNAPSHOT === "update") { const path = REPO + SNAPSHOT; const options = { ...(await resolveConfig(path)), filepath: path }; const stored = { ...now, eslint: toStored(now.eslint) }; writeFileSync(path, await format(JSON.stringify(sortKeys(stored)), options)); return; } expect(now).toEqual(committed()); }, 120_000); // A glob that moves one file into another config (a product file into the test config) leaves its // scope's config as it was, so the snapshot alone would not see it: the file now differs from the // rest of its scope, and that is what fails here. it("gives every file of a scope the same config", async () => { expect((await linted()).strays).toEqual([]); }, 120_000); }); // Git for Windows' own bash: on a Windows PATH `bash` is often WSL's, which cannot run the script. function bash(): string { if (process.platform !== "win32") return "bash"; const exec = execFileSync("git", ["--exec-path"], { encoding: "utf8" }).trim(); return resolve(exec, "../../../bin/bash.exe"); } const checkSh = (...args: string[]) => spawnSync(bash(), ["scripts/check.sh", ...args], { cwd: REPO, encoding: "utf8" }); // A runner that schedules lanes one by one learns them from `--list` and runs each with `--only`; a // lane missing from the list would be a lane it never runs, and an `--only` that passed with nothing // run would be a green about nothing. describe("the gate script's lane list (check.sh --list / --only)", () => { it("lists every lane the script runs, in order", () => { const declared = [...read("scripts/check.sh").matchAll(/^\s*lane "([^"]+)"/gm)].map( (m) => m[1], ); const listed = checkSh("--list"); expect(listed.status).toBe(0); expect(listed.stdout.trim().split("\n")).toEqual(declared); const lint = checkSh("lint", "--list").stdout.trim().split("\n"); expect(lint.length).toBeGreaterThan(0); expect(declared.slice(0, lint.length)).toEqual(lint); }); it("refuses an unknown lane instead of passing with nothing run", () => { const r = checkSh("--only", "no such lane"); expect(r.status).toBe(2); expect(r.stdout).not.toContain("green"); }); }); describe("the gate register (docs/gates.md)", () => { it("lists every ESLint rule narrowed, by scope", async () => { expect(missingFrom(eslintNarrowings(await linted()))).toEqual([]); }, 120_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).toBe("eslint . --max-warnings 0"); }); // --- 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. 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([]); // a `.js` in `include` is dropped without a word unless allowJs, and read unchecked without // checkJs const opts = jsonc("frontend/tsconfig.node.json").compilerOptions as Record; if ([...checked].some((f) => /[.][mc]?js$/.test(f))) expect([opts.allowJs, opts.checkJs]).toEqual([true, true]); }); // --- Ruff ----------------------------------------------------------------------------------------- // Ruff, pyright, pytest and knip are read as 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 (only // `ignore` and the immutable calls used to be read). The TOML is PARSED, not scanned line by // line: a `[lint.per-file-ignores]` table or a quoted key is a key path like any other (a line // regex saw neither). const keyPaths = (o: Record, prefix = ""): string[] => Object.entries(o).flatMap(([k, v]) => v !== null && typeof v === "object" && !Array.isArray(v) ? keyPaths(v as Record, `${prefix}${k}.`) : [`${prefix}${k}`], ); it("lists every ruff ignore and immutable call, and knows every key", () => { const cfg = parseToml(read("backend/ruff.toml")); expect(keyPaths(cfg).sort()).toEqual( [ "lint.extend-select", "lint.flake8-bugbear.extend-immutable-calls", "line-length", "target-version", ].sort(), ); const lint = cfg.lint as Record; // the selection on top of ruff's defaults (E, F) — dropping one narrows the lane expect(lint["extend-select"]).toEqual([ "LOG", "G", "T20", "TRY400", "TRY401", "S110", "S112", "BLE001", "RUF100", "PLW1510", "B", ]); const immutable = (lint["flake8-bugbear"] as Record)[ "extend-immutable-calls" ]!; const found = immutable.map((c): [string, string] => ["backend + scripts", c]); expect(missingFrom(found)).toEqual([]); }); // Every ruff lane passes `--config backend/ruff.toml`, which makes ruff ignore any other config // file it would discover (measured: a nested `ruff.toml` selecting the `D` rules changed nothing). // So that flag is what makes this file the whole configuration. The snapshot holds every line of // the gate script; these two are spelled out because the reason is (a substring check once let // an extra `--config "lint.extend-ignore=…"` override through). it("runs both ruff lanes on backend/ruff.toml and nothing else", () => { const lanes = read("scripts/check.sh") .split("\n") .filter((l) => /python -m ruff (check|format)/.test(l)) .map((l) => l.trim()); expect(lanes).toEqual([ "ruff_check() { ruff_pinned && python -m ruff check --config backend/ruff.toml backend scripts; }", "ruff_format() { ruff_pinned && python -m ruff format --check --config backend/ruff.toml backend scripts; }", ]); }); // --- 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 --------------------------------------------------------------------------------------- // Held line by line, not key by key: pytest's ini reader also takes `key: value` and indented // continuation lines (`addopts = -q` + ` -m "not slow"`), and a key regex saw neither. // Every line that is not blank or a comment must be one of these, and all must be there. it("keeps pytest strict: the whole tests/ tree, no selection, no warning filters, --strict-markers", () => { const lines = read("backend/pytest.ini") .split("\n") .map((l) => l.trimEnd()) .filter((l) => l.trim() !== "" && !/^[#;]/.test(l.trim())); expect(lines).toEqual([ "[pytest]", "testpaths = tests", "pythonpath = .", // `-p tests.db_per_worker` selects nothing and filters nothing: under `-n` it hands each worker its // own database (DATABASE_URL + `_gw`), so the same tests can run side by side "addopts = -q -rs --strict-markers -p tests.db_per_worker", "db_per_worker_env = DATABASE_URL", ]); }); // --- knip ----------------------------------------------------------------------------------------- it("lists every knip exclusion and every other key", () => { const cfg = jsonc("frontend/knip.jsonc") as { project?: string[] } & Record; const found: [string, string][] = (cfg.project ?? []) .filter((p) => p.startsWith("!")) .map((p): [string, string] => ["knip", 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"); // the positive pattern is the project itself: narrowing it drops files from the lane expect((cfg.project ?? []).filter((p) => !p.startsWith("!"))).toEqual(["**/*.{ts,tsx,js}"]); // and knip.jsonc is the config knip reads: a knip.json (read first) or a package.json `knip` key // (merged in) would narrow it unseen const KNIP_FILES = /^\.?knip(\.config)?\.(json|jsonc|js|ts|mjs|cjs|mts|cts|toml|ya?ml)$/; expect(readdirSync(FRONTEND).filter((f) => KNIP_FILES.test(f))).toEqual(["knip.jsonc"]); expect("knip" in pkg).toBe(false); }); // --- 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("scripts/check.sh") .split("\n") .filter((l) => !l.trim().startsWith("#")) // the lane'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([]); }); });