test(gates): hold the lint and lane configs to a committed snapshot
The register test detected narrowings by modelling config shapes, and each review round found one more shape it did not model (a basePath global ignore, a rule src never declares, a widened glob filed under a narrow row, a substring check on a CI line, knip.json beside knip.jsonc). It now compares instead: gates.snapshot.json holds ESLint's effective config for every code file and the lines that run each lane, and any difference fails until it is regenerated. The register is read off the snapshot's configs, which surfaced the ui/ and lib/ test scopes the sampling never reached. The complexity ratchet also names a wrapped function by its binding (const onSave = useCallback(() => ...)), not as "useCallback callback".
This commit is contained in:
+25
-14
@@ -13,19 +13,30 @@ name its invariant. Whatever is left narrowed is listed here, and
|
||||
**effective** configuration and fails when a narrowing is not on this page. A row here is a decision,
|
||||
not an oversight; a narrowing missing from here is a test failure.
|
||||
|
||||
How the test matches: each narrowing must appear in a table row below as its `code-quoted` id, in a
|
||||
row whose **Scope** cell names the scope exactly as the test reports it. For ESLint it reads each
|
||||
scope's effective config through its sample files AND every config block on its own: a block glob
|
||||
that no sample matches fails as an `unsampled block` (add a sample), a rule a block or src sets
|
||||
weaker than src counts, so does a `no-restricted-*` list that leaves out an entry the src block
|
||||
declares, so does an option that lets something through (`allow…`, `ignore…`, `except…`), and so
|
||||
does any `linterOptions` but an erroring `reportUnusedDisableDirectives` (E32 review). Ruff's TOML
|
||||
is parsed and held to a whitelist of key paths, pytest.ini to a whitelist of whole lines.
|
||||
**The snapshot.** ESLint's effective config for every code file `eslint .` reaches, and the lines
|
||||
that run the lanes (`.gitea/workflows/check.yml`, `backend/docker-compose.test.yml`,
|
||||
`backend/Dockerfile.test`, the npm scripts, `vitest.config.ts`, `.prettierignore`), are committed in
|
||||
[`frontend/src/lib/gates.snapshot.json`](../frontend/src/lib/gates.snapshot.json). The test fails on
|
||||
ANY difference — a rule, an option, a glob that moves one file to another config, a new ignore, a CI
|
||||
flag. So a change to a gate is always a diff of that file in review, never a shape a detector failed
|
||||
to model (E32 review: three rounds of a cleverer detector each found a bypass of the last one's
|
||||
fixes). Regenerate it with `GATE_SNAPSHOT=update npx vitest run src/lib/gates.contract.test.ts`
|
||||
(from `frontend/`), and read its diff as a gate change: a new file only adds its path; anything else
|
||||
needs a reason. A new code file is part of that diff by design.
|
||||
|
||||
**How the register is matched:** each narrowing must appear in a table row below as its
|
||||
`code-quoted` id, in a row whose **Scope** cell names the scope exactly as the test reports it (a cell
|
||||
may list several, separated by ` · `). For ESLint the scopes are the snapshot's config names, and a
|
||||
narrowing is read off each config against `src`: a rule at off or warn, or one src applies that a
|
||||
scope does not (one id per plugin family), a `no-restricted-*` list that leaves out an entry src's
|
||||
declares, an option that lets something through (`allow…`, `ignore…`, `except…`), an unused-disable
|
||||
report that is not an error, and a file no config lints. Ruff's TOML is parsed and held to a
|
||||
whitelist of key paths, pytest.ini to a whitelist of whole lines.
|
||||
|
||||
## ESLint (frontend)
|
||||
|
||||
`npm run lint` is `eslint . --max-warnings 0`, so a rule at **warn** fails the lane exactly like an
|
||||
error (the test checks the flag is still there). The warn rows are listed so that claim stays checked.
|
||||
error (the test holds that exact script). The warn rows are listed so that claim stays checked.
|
||||
A core rule switched off because its typescript-eslint twin replaces it (`no-unused-vars` →
|
||||
`@typescript-eslint/no-unused-vars`) is not listed separately; neither are the core rules
|
||||
typescript-eslint's compatibility layer switches off for TS files because the type checker reports
|
||||
@@ -45,10 +56,10 @@ them itself (`no-undef`, `no-redeclare`, `no-dupe-keys`, …), nor the formattin
|
||||
| lib/http.ts | `no-restricted-globals` — narrowed | A network owner (`NETWORK_OWNERS` in eslint.config.js): the one module that may call `fetch` (`send`, which `req` and the public pages go through). Every other network global stays restricted there. | 1 global |
|
||||
| lib/api/core.ts | `no-restricted-globals` — narrowed | A network owner: `uploadReq` is the one `XMLHttpRequest` (upload progress). | 1 global |
|
||||
| lib/messagesSocket.ts | `no-restricted-globals` — narrowed | A network owner: the chat socket is the one `WebSocket`. | 1 global |
|
||||
| src tests | `no-restricted-globals`, `no-restricted-properties` — off | A test stands a double in for the network (`vi.stubGlobal("fetch", …)`) and may reach it directly. | — |
|
||||
| src tests | `local/literal-a11y-text`, `local/button-primitive`, `local/input-primitive`, `local/surface-role`, `local/glyph-link` — off | A test renders throwaway markup (a raw button as a focus target, an untranslated label) that is not product UI. | — |
|
||||
| FocusSafeButton.tsx | `local/focus-safe-disabled` — off | It IS the focus-safe primitive the rule points everyone else to. | — |
|
||||
| AnchoredPopover.tsx | `local/focus-safe-disabled` — off | The popover's menu row: its native `disabled` is what floating-ui's roving focus reads to skip it. | — |
|
||||
| src tests · ui tests · lib tests | `no-restricted-globals`, `no-restricted-properties` — off | A test stands a double in for the network (`vi.stubGlobal("fetch", …)`) and may reach it directly. | — |
|
||||
| src tests · ui tests · lib tests | `local/literal-a11y-text`, `local/button-primitive`, `local/input-primitive`, `local/surface-role`, `local/glyph-link` — off | A test renders throwaway markup (a raw button as a focus target, an untranslated label) that is not product UI. | — |
|
||||
| FocusSafeButton.tsx + AnchoredPopover.tsx | `local/focus-safe-disabled` — off | FocusSafeButton: it IS the focus-safe primitive the rule points everyone else to. | — |
|
||||
| FocusSafeButton.tsx + AnchoredPopover.tsx | `local/focus-safe-disabled` — off | AnchoredPopover: the popover's menu row: its native `disabled` is what floating-ui's roving focus reads to skip it. | — |
|
||||
| design-lab | `local/literal-a11y-text` — off | The dev-only design lab (never in the bundle — devOnlyGuard fails the build if it is) holds the owner's working copy, not product copy. | — |
|
||||
| e2e | `react-hooks/*`, `react-refresh/*`, `local/*`, `no-console`, `no-restricted-globals`, `no-restricted-properties`, `no-restricted-syntax` — not applied | Playwright specs drive a browser from Node: no React, no product UI, and they print to the console on purpose. They get the base + typed rules + the playwright rules. | — |
|
||||
| ts configs | `react-hooks/*`, `react-refresh/*`, `local/*`, `no-console`, `no-restricted-globals`, `no-restricted-properties`, `no-restricted-syntax` — not applied | The build configs (vite, vitest, playwright, reactPlugin, devOnlyGuard) run in Node at build time. | — |
|
||||
@@ -112,7 +123,7 @@ project checks needs a row.
|
||||
| npm audit (CI) | `--omit=dev --audit-level=high` | What ships to the browser, at high or above. The dev toolchain's advisories were burned down by hand in E32 S1 (18 → 0). | 0 |
|
||||
| React Compiler | the functions it skips, pinned | `components/compilerCoverage.contract.test.ts` — the list may only shrink. End state: only VirtualGrid (TanStack Virtual, an IncompatibleLibrary) remains. | 93 (E32 S1) → 29 (E32 S3) → 1, VirtualGrid (E32 S4) |
|
||||
| size / complexity | today's offenders as ceilings | `src/lib/sourceSize.contract.test.ts`, `backend/tests/test_source_size.py`: listed files and functions may not grow, nothing new may cross the limit, an entry back under it must be deleted. A function is held by its name, not its rank in the file. | files >800 lines: 13 FE / 9 BE; functions over the limit: 64 FE / 32 BE (E32 review) |
|
||||
| size / complexity | shipped code only — not tests, test kits, e2e or `scripts/` | The ratchet guards what runs in production. A test file is long because it is a list of cases, and a test kit is test code; splitting either moves lines without making anything easier to change. `scripts/` is release and seed tooling, not in the image. | outside it: tests >800 lines 2 FE / 5 BE; functions over the limit 2 FE test code (`gates.contract.test.ts` 30, `compilerSkips.testkit.ts` 25), 1 BE test (11), 1 script (`e2e_seed.py` `main` 13) |
|
||||
| size / complexity | shipped code only — not tests, test kits, e2e or `scripts/` | The ratchet guards what runs in production. A test file is long because it is a list of cases, and a test kit is test code; splitting either moves lines without making anything easier to change. `scripts/` is release and seed tooling, not in the image. | outside it: tests >800 lines 2 FE / 5 BE; functions over the limit 2 FE test code (`gates.contract.test.ts` `eslintNarrowings` 23, E32 review round 3; `compilerSkips.testkit.ts` 25), 1 BE test (11), 1 script (`e2e_seed.py` `main` 13) |
|
||||
|
||||
## Accepted findings
|
||||
|
||||
|
||||
Reference in New Issue
Block a user