@hublo/sentinel 1.5.0-alpha.8 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +9 -7
  2. package/dist/bin/sentinel.js +61 -16
  3. package/dist/{chunk-KLEXJFJ6.js → chunk-43HQ6BVJ.js} +61 -15
  4. package/dist/{chunk-YQRDKWSH.js → chunk-IL5PPLXE.js} +11 -1
  5. package/dist/chunk-IL5PPLXE.js.map +1 -0
  6. package/dist/chunk-MOELAQPC.js +10365 -0
  7. package/dist/chunk-WBDBRGLV.js +214 -0
  8. package/dist/chunk-WBDBRGLV.js.map +1 -0
  9. package/dist/{chunk-AMCGMLPS.js → chunk-Y57KA4QS.js} +2 -2
  10. package/dist/index.d.ts +28 -1
  11. package/dist/index.js +4 -4
  12. package/dist/roles/build/nest/toolchain.js +68 -18
  13. package/dist/roles/build/nest/toolchain.js.map +1 -1
  14. package/dist/roles/static-analysis/config.d.ts +15 -1
  15. package/dist/roles/static-analysis/config.js +1 -1
  16. package/dist/roles/test/setup/jest-parity.js +2 -2
  17. package/dist/roles/test/setup/workspace-entry.js +2 -2
  18. package/dist/{validate-AWWMBSH5.js → validate-FRMENKHM.js} +2 -2
  19. package/docs/bundle-analysis-spec.md +272 -0
  20. package/docs/sql-verification-study.md +401 -0
  21. package/docs/static-analysis-adoption.md +285 -0
  22. package/docs/static-analysis-study.md +824 -18
  23. package/docs/typescript-adoption.md +1 -1
  24. package/docs/typescript-traces.md +27 -12
  25. package/docs/using-sentinel.md +22 -12
  26. package/docs/validating-a-change.md +185 -0
  27. package/lint/nest.json +98 -18
  28. package/lint/node.json +83 -3
  29. package/lint/react-lib.json +121 -31
  30. package/lint/react.json +101 -2
  31. package/package.json +27 -6
  32. package/plugins/hublo/no-palette-common.js +76 -0
  33. package/plugins/hublo/no-regex-lookbehind.js +70 -0
  34. package/plugins/hublo/require-reload-document.js +70 -0
  35. package/plugins/hublo.js +20 -145
  36. package/plugins/sonar-cognitive.js +108 -0
  37. package/dist/chunk-DKLQYGPI.js +0 -6466
  38. package/dist/chunk-EDFIVLUA.js +0 -100
  39. package/dist/chunk-EDFIVLUA.js.map +0 -1
  40. package/dist/chunk-YQRDKWSH.js.map +0 -1
  41. /package/dist/{chunk-AMCGMLPS.js.map → chunk-Y57KA4QS.js.map} +0 -0
@@ -147,7 +147,7 @@ $ sentinel --run --typescript --json --max-diagnostics 1
147
147
  | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
148
148
  | `target(s) not available yet: --test` | only wired types run; `--help` lists what is available now vs planned |
149
149
  | `preset is ambiguous, detected "react" ... also found sails` | a mixed stack; pass `--preset` to be explicit |
150
- | `sentinel requires Node >= 20.12` | run sentinel with a modern Node via `fnm`/`nvm`; the project's own Node is untouched |
150
+ | `sentinel requires Node >= 24.15` | run sentinel with a modern Node via `fnm`/`nvm`; the project's own Node is untouched |
151
151
  | fresh version will not install (`minimumReleaseAge`) | `--init` allow-lists sentinel automatically when the root uses the gate; if you adopted by hand, add the package to `minimumReleaseAgeExclude` (or `--config.minimumReleaseAge=0`) until it ages |
152
152
  | a translation-singleton test goes red after adopting a front module | `--init` handles this automatically when the root runs more than one TypeScript: adopting re-resolves pnpm and can duplicate a package with an optional, type-only `typescript` peer (e.g. i18next); `--init` pins that peer so it collapses to one instance |
153
153
 
@@ -1072,7 +1072,7 @@ $ sentinel --report --typescript --max-diagnostics 3.9 --module bff-admin
1072
1072
  $ sentinel --init --dry-run
1073
1073
  dry run: no files written
1074
1074
  wrote .oxlintrc.json extending ./node_modules/@hublo/sentinel/lint/nest.json (@hublo/sentinel@<version>)
1075
- 2 rule(s) not enforced by this preset (1 no-barrel-files, 1 core). Run --inspect --lint for each rule and why.
1075
+ 20 rule(s) not enforced by this preset (17 jest, 1 no-barrel-files, 1 core, 1 vitest). Run --inspect --lint for each rule and why.
1076
1076
  run `pnpm install`, then `pnpm run lint`
1077
1077
 
1078
1078
  create .oxlintrc.json
@@ -1156,7 +1156,7 @@ $ sentinel --init --dry-run
1156
1156
  build: no Vite config in this module, so there is nothing to point at sentinel. This role moves an existing config's imports; it does not introduce a bundler. If this module builds another way (38 services here build through `@nx/webpack`), the build role does not apply to it.
1157
1157
  test: this module has no test and no jest leftovers, so there is nothing to do here.
1158
1158
  dry run: no files written
1159
- added the three `static-analysis` scripts and static-analysis.config.mjs; declare this module's entry points and journeys there, everything else comes from the role
1159
+ added the three `static-analysis` scripts and static-analysis.config.mjs; declare this module's entry points there, everything else comes from the role
1160
1160
 
1161
1161
  create static-analysis.config.mjs
1162
1162
  + import { defineStaticAnalysis } from '@hublo/sentinel/static-analysis'
@@ -1168,17 +1168,32 @@ $ sentinel --init --dry-run
1168
1168
  + // the patterns sentinel ships, never replacing them.
1169
1169
  + entry: [],
1170
1170
  +
1171
- + // Named product journeys, as { 'the name product uses': ['src/some/path/**'] }. Paths are
1172
- + // relative to this module, because one run measures one module.
1173
- + journeys: {},
1174
- +
1175
- + // Everything else comes from the role, and this is the door when a module must move one of its
1176
- + // values. Anything set here is printed under "measured with" and marked `(module)`, so nobody
1177
- + // compares this module's figures with its neighbour's without seeing that they differ:
1171
+ + // ---------------------------------------------------------------------------------------------
1172
+ + // What this module can move, with the role's value today. A setting you leave out keeps that
1173
+ + // value; a setting you write REPLACES it, including inside a list. Anything you set is printed
1174
+ + // under "measured with" in the report and marked `(module)`, because two modules measured with
1175
+ + // different settings can no longer be compared.
1176
+ + //
1177
+ + // ignore 19 patterns
1178
+ + // languages javascript, typescript, jsx, tsx, css, scss
1179
+ + // duplication.minTokens 50
1180
+ + // duplication.minLines 5
1181
+ + // duplication.tests 3 patterns
1182
+ + // deadCode.minConfidence 60
1183
+ + // deadCode.categories unused-file, unused-export, unused-symbol, unused-import
1184
+ + // graph.resolution 1
1185
+ + //
1186
+ + // A list shown as a count is not copyable, and replacing one means writing back what you keep,
1187
+ + // so read the values in full with:
1188
+ + //
1189
+ + // pnpm exec sentinel --inspect --static-analysis --json
1190
+ + //
1191
+ + // duplication.kinds: only `exact` is supported: any other kind overlaps it and double-counts
1192
+ + // tools: sentinel's own passthrough to the tool underneath; a module never names the vendor
1178
1193
  + //
1179
- + // overrides: {
1180
- + // duplication: { minTokens: 100 },
1181
- + // },
1194
+ + // overrides: {
1195
+ + // duplication: { minTokens: 100 },
1196
+ + // },
1182
1197
  + })
1183
1198
 
1184
1199
  update package.json
@@ -4,9 +4,10 @@ The commands, what an adopted module ends up looking like, and how versions move
4
4
  migrating a module see [`lint-adoption.md`](lint-adoption.md),
5
5
  [`format-adoption.md`](format-adoption.md),
6
6
  [`typescript-adoption.md`](typescript-adoption.md),
7
- [`build-adoption.md`](build-adoption.md) and
8
- [`test-adoption.md`](test-adoption.md), which also carries the reference gate `--test` runs a
9
- migration against.
7
+ [`build-adoption.md`](build-adoption.md),
8
+ [`test-adoption.md`](test-adoption.md) and
9
+ [`static-analysis-adoption.md`](static-analysis-adoption.md). The reference gate `--test` runs a
10
+ migration against is in `test-adoption.md`, not the static-analysis page.
10
11
 
11
12
  ## The grid
12
13
 
@@ -22,18 +23,25 @@ sentinel --run # no target -> every wired tar
22
23
  Read it by target, since that is what you pick first. `--report` and `--status` are deprecated
23
24
  everywhere (`--run --json` and `--inspect` answer them), and `--migrate` is refused everywhere.
24
25
 
25
- | Target | `--init` writes | `--run` does | `--inspect` shows |
26
- | -------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------- |
27
- | `--lint` | stub + scripts + nx metadata, removes the ESLint config and the module's ESLint deps, then autofixes once | lints; `--fix` autofixes in the same pass | rule count, what is disabled or downgraded and **why**, drift |
28
- | `--format` | materializes the preset + scripts + nx metadata, removes the Prettier config and deps, then formats once | **checks**; `--fix` writes | effective options, every departure from the standard and why |
29
- | `--typescript` | writes/extends the tsconfig + `typecheck` script | typechecks | resolved options, what is deferred, drift |
30
- | `--build` | points the module's Vite config at sentinel's toolchain + scripts + nx metadata | builds | runner, config file, whose Vite, how many overrides, adoption |
31
- | `--dev` | adopts the BUILD too: one plan writes both `build` and `serve` | serves; long-running, so never swept by an unqualified `--run` | refuses, and sends you to `--inspect --build`: it is that config |
32
- | `--test` | the Vitest config + scripts + nx metadata, and migrates every spec file | runs Vitest | runner, which configs were found, adoption state, drift |
26
+ | Target | `--init` writes | `--run` does | `--inspect` shows |
27
+ | ------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------- |
28
+ | `--lint` | stub + scripts + nx metadata, removes the ESLint config and the module's ESLint deps, then autofixes once | lints; `--fix` autofixes in the same pass | rule count, what is disabled or downgraded and **why**, drift |
29
+ | `--format` | materializes the preset + scripts + nx metadata, removes the Prettier config and deps, then formats once | **checks**; `--fix` writes | effective options, every departure from the standard and why |
30
+ | `--typescript` | writes/extends the tsconfig + `typecheck` script | typechecks | resolved options, what is deferred, drift |
31
+ | `--build` | points the module's Vite config at sentinel's toolchain + scripts + nx metadata | builds | runner, config file, whose Vite, how many overrides, adoption |
32
+ | `--dev` | adopts the BUILD too: one plan writes both `build` and `serve` | serves; long-running, so never swept by an unqualified `--run` | refuses, and sends you to `--inspect --build`: it is that config |
33
+ | `--test` | the Vitest config + scripts + nx metadata, and migrates every spec file | runs Vitest | runner, which configs were found, adoption state, drift |
34
+ | `--static-analysis` | the config stub + three scripts + nx metadata, and ignores the report directory at the repository root | measures and reports; never exits non-zero on a finding | runner, config file, resolved settings, what this module moved |
33
35
 
34
36
  `--json` works for every verb, and stdout carries **only** the envelope, so `| jq` always
35
37
  parses. `--dry-run` applies to `--init` and writes nothing.
36
38
 
39
+ `--output <shape>` is different, and only a role that WRITES a report reads it: `console` is the
40
+ default and writes no file, `html` is the page you open and send, `json` is for CI and for a
41
+ script, `markdown` is the six lines a workflow posts on a pull request. Today `--static-analysis`
42
+ is the role that has them. ⚠️ `--output html` pays a graph layout the other three skip, which is
43
+ fine for a page somebody opens and wrong for a commit hook.
44
+
37
45
  **`--validate` is a verb of its own**, and the proof of a migration lives there rather than on
38
46
  `--init` or `--run`. Run before a migration it records what the module does today; run after, it
39
47
  compares by test NAME and refuses a run that lost one or invented one. What separates it from
@@ -100,7 +108,9 @@ These options can weaken a check as easily as strengthen it, and `--run` is what
100
108
 
101
109
  ## What a fully adopted module looks like
102
110
 
103
- Four files, and none of them holds a rule.
111
+ Five files once a module has adopted every role, and none of them holds a rule. The fifth is
112
+ `static-analysis.config.mjs`, which holds the only thing that role asks a team to declare: the files
113
+ that run although nothing imports them.
104
114
 
105
115
  ```jsonc
106
116
  // .oxlintrc.json — the rules come from the preset; the ignores must be here, because oxlint
@@ -132,3 +132,188 @@ package that looks missing.
132
132
  what a module looks like once you have thought about it. Measured on the real monorepo: 22 of 37
133
133
  Nest services have none at all. A change that looked complete against 64 test files refused the
134
134
  majority of the migration, and only a sweep over every real module found it.
135
+
136
+ ---
137
+
138
+ ## The static analysis role's validation matrix, 19 October 2026
139
+
140
+ Re-run from scratch because **53 commits landed on the role since 13 October**, and three of them
141
+ added an AXIS the earlier matrix has no column for: the `markdown` recap, `--output` taking a list,
142
+ and a front whose build produced no files. A matrix that does not grow with the tool is a claim of
143
+ coverage that stopped being true.
144
+
145
+ 39 cells, six real modules, one log per cell.
146
+
147
+ ### output shape × module, from the module's own directory
148
+
149
+ | | console | json | html | markdown |
150
+ | --------------------------------- | ------- | ---- | ---- | -------- |
151
+ | `console`, react with a build | 0 | 0 | 0 | 0 |
152
+ | `host-admin`, react with NO build | 0 | 0 | 0 | 0 |
153
+ | `institution`, nest | 0 | 0 | 0 | 0 |
154
+ | `hr-management`, svelte | 0 | 0 | 0 | 0 |
155
+ | `libs/front/components`, library | 0 | 0 | 0 | 0 |
156
+ | `libs/nest/auth`, nest library | 0 | 0 | 0 | 0 |
157
+
158
+ ⭐ The second row is new and it earned its place: the two defects found this week both live there.
159
+ A front that declares a build and produced no files is not a front without a bundle, and the role
160
+ said the same sentence to both until it was pointed at one.
161
+
162
+ ### `--output` taking a LIST, which did not exist on 13 October
163
+
164
+ | | exit | files written |
165
+ | -------------------- | ---: | ---------------------------------- |
166
+ | `html,json` | 0 | html, json, svg |
167
+ | `json,html` | 0 | html, json, svg, same as above |
168
+ | `json,json` | 0 | **json only**, the duplicate drops |
169
+ | `markdown,html,json` | 0 | html, json, md, svg |
170
+
171
+ ### from the workspace ROOT rather than the module
172
+
173
+ | | exit |
174
+ | ---------------------------------------------- | ---: |
175
+ | `--run --static-analysis --module console` | 0 |
176
+ | `--run --static-analysis --module institution` | 0 |
177
+ | `--inspect --static-analysis --json` | 0 |
178
+
179
+ ### the forms that must REFUSE
180
+
181
+ | | exit | what it says |
182
+ | ------------------- | ----: | ------------------------------------------------------------------ |
183
+ | `--output pdf` | **1** | `unknown --output "pdf" (known: console, json, html, markdown; …)` |
184
+ | `--output html,pdf` | **1** | the same, naming the whole list it was given |
185
+ | `--output ,` | **1** | an empty list refuses rather than falling back to the console |
186
+ | `--module <absent>` | **1** | `sentinel: module "…" not found in the workspace` |
187
+
188
+ ⚠️ The first cell is where the earlier matrix had gone stale: it answered `known: console, json,
189
+ html`, and `markdown` had been shippable for days.
190
+
191
+ ### ⭐ What the matrix proves, beyond an exit code
192
+
193
+ | invariant | result |
194
+ | --------------------------------------------------------- | --------------------------------------------------------------------------------------- |
195
+ | json, markdown and html report the SAME figures | **174 figures compared, 0 disagreement** |
196
+ | a list writes one file per shape, and a repeat writes one | held on both modules |
197
+ | a list costs ONE measurement, not one per shape | `console` 6.4 s in three runs, **2.6 s** in one; `institution` 10.9 s against **6.3 s** |
198
+
199
+ The third one is why `--output` learned to take a list: the measurement is the whole cost of this
200
+ role and rendering is under a second, so asking for the page and the recap used to pay for the
201
+ analysis twice.
202
+
203
+ ### ⚠️ The bench lied twice before it told the truth
204
+
205
+ Both times it would have been reported as a defect of the role:
206
+
207
+ | what the bench did | what it would have claimed |
208
+ | ------------------------------------------------------------- | -------------------------- |
209
+ | a character class that swallowed the newline, reading `0\n>` | one figure disagreeing |
210
+ | comparing `29 entries` from the recap with `29` from the page | **150 disagreements** |
211
+
212
+ Comparing the NUMBER rather than the string is what made the invariant mean something. An
213
+ instrument is validated before its measurement is believed, and a matrix that reports a defect of
214
+ itself is the most expensive kind.
215
+
216
+ ### What this matrix does NOT cover, said so nobody assumes it does
217
+
218
+ - `--init --static-analysis`: it WRITES, and this matrix runs against a shared tree. Covered by the
219
+ adoption pull requests and the e2e suite.
220
+ - a module that has ADOPTED the role, for the same reason: no module on this branch carries a
221
+ `static-analysis.config.mjs`. The four adoption pull requests are where that axis lives.
222
+ - `--ci`, which needs a diff against a base to mean anything. The pull-request recap covers it, on
223
+ three probe pull requests of 1, 2 and 5 modules.
224
+ - the `node` and `tools` presets: no module of either carries the evidence the branches react to.
225
+
226
+ ## The static analysis role's validation matrix, 13 October 2026
227
+
228
+ A matrix is a claim of coverage, so a partial one is worse than none: everyone then believes the
229
+ thing was checked. The axes are the tool's, not a sample of them.
230
+
231
+ **Run on real modules, never on fixtures.** The branches that break are reached by what a real
232
+ module carries, a `.svelte` file, a module with no `src/`, a library whose reachability is decided
233
+ outside it. A synthetic temp module cannot reach them, so running it a thousand times proves
234
+ nothing. Logs: one per cell.
235
+
236
+ ### verb × output shape × preset, from the module's own directory
237
+
238
+ | | console | json | html | `--inspect` | `--inspect --json` |
239
+ | ----------------------- | ------- | ---- | ---- | ----------- | ------------------ |
240
+ | `console`, react | 0 | 0 | 0 | 0 | 0 |
241
+ | `contract`, nest | 0 | 0 | 0 | 0 | 0 |
242
+ | `hr-management`, svelte | 0 | 0 | 0 | 0 | 0 |
243
+
244
+ ### the module states that take a different path
245
+
246
+ | | | exit |
247
+ | ---------------- | -------------------------------------------------------- | ---: |
248
+ | `network` | a module that has NOT adopted | 0 |
249
+ | `libs/nest/auth` | a library, whose dead code is decided outside the module | 0 |
250
+ | `libs/nest/auth` | `--inspect` on the same | 0 |
251
+
252
+ ### from the workspace ROOT rather than the module
253
+
254
+ | | exit |
255
+ | ------------------------------------------- | ---: |
256
+ | `--run --static-analysis --module console` | 0 |
257
+ | `--run --static-analysis --module contract` | 0 |
258
+ | `--inspect --static-analysis --json` | 0 |
259
+
260
+ ### the forms that must REFUSE, and what they say
261
+
262
+ | | exit | message |
263
+ | --------------------------------- | ----: | ----------------------------------------------------- |
264
+ | `--output pdf` | **1** | `unknown --output "pdf" (known: console, json, html)` |
265
+ | `--module ce-module-n-existe-pas` | **1** | `sentinel: module "…" not found in the workspace` |
266
+
267
+ ### ⭐ What the matrix actually proves, beyond an exit code
268
+
269
+ An exit code is a smoke test. Two invariants were checked by comparing the sorted figure lines of
270
+ each run, as a hash:
271
+
272
+ | invariant | result |
273
+ | -------------------------------------------------------------------------- | ----------------------------------- |
274
+ | the three output shapes report the SAME figures | identical on react, nest and svelte |
275
+ | a module measured from its own directory and from the root with `--module` | **identical** |
276
+
277
+ The second one matters because the two paths resolve the module differently, and a report that
278
+ changed with the caller's working directory would make every figure uncomparable.
279
+
280
+ ### Two timings the cells show, neither of them a defect
281
+
282
+ | | first run of a session | run again |
283
+ | --------------------------------------- | ---------------------: | ------------: |
284
+ | `console` from its own directory | 22 188 ms | **2 648 ms** |
285
+ | `console` from the root with `--module` | 45 046 ms | **18 374 ms** |
286
+
287
+ **The first cell of a session pays for the consumer index**, the one scan of the workspace that
288
+ decides a library's reachability; every cell after it reads the cache. Reading the first number as
289
+ the role's cost is the mistake this table exists to prevent.
290
+
291
+ **From the root costs about 16 seconds more, whatever the module**, warm: 18.4 s against 2.6 s on
292
+ react, 17.9 s on nest. The root form resolves the workspace before it can target one module. It is
293
+ the right form for CI and the wrong one for a developer in a loop, and the adoption guide says to
294
+ run the script from the module.
295
+
296
+ ### What this matrix does NOT cover, said so nobody assumes it does
297
+
298
+ - `--init --static-analysis` on a module that has never adopted. Covered by the adoption branch and
299
+ by the e2e suite, not re-run here, because it WRITES and this matrix runs against a shared tree.
300
+ - `--ci` (affected only), which needs a diff against a base to mean anything.
301
+ - the `node` and `tools` presets: no module of either carries the evidence the branches react to,
302
+ so the cells would be green without proving anything.
303
+ - a module whose oxlint binary cannot be resolved. The role reports "not measured" there rather than
304
+ zero, and that path is covered by a unit test instead.
305
+
306
+ ### Beside it: the whole-repository sweep, same day
307
+
308
+ | | modules | exit 0 | defects |
309
+ | -------------------------- | ------: | ------: | ------: |
310
+ | `apps/front` | 5 | 5 | 0 |
311
+ | `apps/nest` + `apps/cloud` | 241 | 241 | 0 |
312
+ | `libs` | 150 | 150 | 0 |
313
+ | **total** | **396** | **396** | **0** |
314
+
315
+ ⚠️ The sweep found no defect in the role and two in what the role was SAYING: a help line claiming
316
+ the gauge and the graph read the same population, false for any module with a stylesheet, and the
317
+ same line again on `apps/cloud/interop`, where a bare `staffea` in the repository's `.gitignore`
318
+ hides 34 tracked files from every tool that consumes gitignore patterns. Both are corrected; the
319
+ second is monorepo #22358.
package/lint/nest.json CHANGED
@@ -3,6 +3,7 @@
3
3
  "plugins": [
4
4
  "typescript",
5
5
  "jest",
6
+ "vitest",
6
7
  "import",
7
8
  "unicorn",
8
9
  "oxc",
@@ -93,27 +94,27 @@
93
94
  "prefer-inline": true
94
95
  }
95
96
  ],
96
- "jest/expect-expect": "warn",
97
- "jest/no-alias-methods": "error",
98
- "jest/no-commented-out-tests": "warn",
97
+ "jest/expect-expect": "off",
98
+ "jest/no-alias-methods": "off",
99
+ "jest/no-commented-out-tests": "off",
99
100
  "jest/no-deprecated-functions": "error",
100
- "jest/no-disabled-tests": "warn",
101
+ "jest/no-disabled-tests": "off",
101
102
  "jest/no-done-callback": "error",
102
103
  "jest/no-export": "error",
103
- "jest/no-focused-tests": "error",
104
- "jest/no-identical-title": "error",
105
- "jest/no-interpolation-in-snapshots": "error",
104
+ "jest/no-focused-tests": "off",
105
+ "jest/no-identical-title": "off",
106
+ "jest/no-interpolation-in-snapshots": "off",
106
107
  "jest/no-jasmine-globals": "error",
107
- "jest/no-mocks-import": "error",
108
- "jest/no-standalone-expect": "error",
109
- "jest/no-test-prefixes": "error",
110
- "jest/prefer-to-be": "error",
111
- "jest/prefer-to-contain": "error",
112
- "jest/prefer-to-have-length": "error",
113
- "jest/valid-describe-callback": "error",
114
- "jest/valid-expect": "warn",
115
- "jest/valid-expect-in-promise": "error",
116
- "jest/valid-title": "error",
108
+ "jest/no-mocks-import": "off",
109
+ "jest/no-standalone-expect": "off",
110
+ "jest/no-test-prefixes": "off",
111
+ "jest/prefer-to-be": "off",
112
+ "jest/prefer-to-contain": "off",
113
+ "jest/prefer-to-have-length": "off",
114
+ "jest/valid-describe-callback": "off",
115
+ "jest/valid-expect": "off",
116
+ "jest/valid-expect-in-promise": "off",
117
+ "jest/valid-title": "off",
117
118
  "no-async-promise-executor": "error",
118
119
  "no-case-declarations": "error",
119
120
  "no-compare-neg-zero": "error",
@@ -347,12 +348,91 @@
347
348
  {
348
349
  "requireStringLiterals": false
349
350
  }
350
- ]
351
+ ],
352
+ "vitest/expect-expect": "warn",
353
+ "vitest/no-alias-methods": "error",
354
+ "vitest/no-commented-out-tests": "warn",
355
+ "vitest/no-conditional-expect": "warn",
356
+ "vitest/no-disabled-tests": "warn",
357
+ "vitest/no-focused-tests": "error",
358
+ "vitest/no-identical-title": "error",
359
+ "vitest/no-interpolation-in-snapshots": "error",
360
+ "vitest/no-mocks-import": "error",
361
+ "vitest/no-standalone-expect": "error",
362
+ "vitest/no-test-prefixes": "error",
363
+ "vitest/prefer-to-be": "error",
364
+ "vitest/prefer-to-contain": "error",
365
+ "vitest/prefer-to-have-length": "error",
366
+ "vitest/require-mock-type-parameters": "off",
367
+ "vitest/require-to-throw-message": "warn",
368
+ "vitest/valid-describe-callback": "error",
369
+ "vitest/valid-expect": "warn",
370
+ "vitest/valid-expect-in-promise": "error",
371
+ "vitest/valid-title": "error",
372
+ "vitest/warn-todo": "warn"
351
373
  },
352
374
  "jsPlugins": [
353
375
  {
354
376
  "name": "nx",
355
377
  "specifier": "@nx/eslint-plugin"
356
378
  }
379
+ ],
380
+ "overrides": [
381
+ {
382
+ "files": [
383
+ "**/*.test.ts",
384
+ "**/*.test.tsx",
385
+ "**/*.spec.ts",
386
+ "**/*.spec.tsx",
387
+ "**/*.integration-spec.ts",
388
+ "**/*.e2e-spec.ts"
389
+ ],
390
+ "rules": {
391
+ "no-restricted-properties": [
392
+ "warn",
393
+ {
394
+ "object": "Math",
395
+ "property": "random",
396
+ "message": "No Math.random() in a test: use a deterministic fixture, or stub the random source."
397
+ },
398
+ {
399
+ "object": "Date",
400
+ "property": "now",
401
+ "message": "No Date.now() in a test: use vi.useFakeTimers() with vi.setSystemTime()."
402
+ },
403
+ {
404
+ "object": "performance",
405
+ "property": "now",
406
+ "message": "No performance.now() in a test: use vi.useFakeTimers() with vi.setSystemTime()."
407
+ },
408
+ {
409
+ "object": "process",
410
+ "property": "hrtime",
411
+ "message": "No process.hrtime in a test: use vi.useFakeTimers() with vi.setSystemTime()."
412
+ },
413
+ {
414
+ "object": "crypto",
415
+ "property": "randomUUID",
416
+ "message": "No crypto.randomUUID() in a test: use a fixed id, or stub the random source."
417
+ },
418
+ {
419
+ "object": "crypto",
420
+ "property": "getRandomValues",
421
+ "message": "No crypto.getRandomValues() in a test: use a fixed buffer, or stub the random source."
422
+ }
423
+ ],
424
+ "no-restricted-globals": [
425
+ "warn",
426
+ {
427
+ "name": "setTimeout",
428
+ "message": "No real timer in a test: vi.useFakeTimers() then vi.advanceTimersByTime()."
429
+ },
430
+ {
431
+ "name": "setInterval",
432
+ "message": "No real timer in a test: vi.useFakeTimers() then vi.advanceTimersByTime()."
433
+ }
434
+ ]
435
+ }
436
+ }
357
437
  ]
358
438
  }
package/lint/node.json CHANGED
@@ -2,7 +2,8 @@
2
2
  "$schema": "./node_modules/oxlint/configuration_schema.json",
3
3
  "plugins": [
4
4
  "typescript",
5
- "oxc"
5
+ "oxc",
6
+ "vitest"
6
7
  ],
7
8
  "categories": {
8
9
  "correctness": "off",
@@ -47,6 +48,85 @@
47
48
  "typescript/no-wrapper-object-types": "error",
48
49
  "typescript/prefer-as-const": "error",
49
50
  "typescript/prefer-namespace-keyword": "error",
50
- "typescript/triple-slash-reference": "error"
51
- }
51
+ "typescript/triple-slash-reference": "error",
52
+ "vitest/expect-expect": "warn",
53
+ "vitest/no-alias-methods": "warn",
54
+ "vitest/no-commented-out-tests": "warn",
55
+ "vitest/no-conditional-expect": "warn",
56
+ "vitest/no-disabled-tests": "warn",
57
+ "vitest/no-focused-tests": "warn",
58
+ "vitest/no-identical-title": "warn",
59
+ "vitest/no-interpolation-in-snapshots": "warn",
60
+ "vitest/no-mocks-import": "warn",
61
+ "vitest/no-standalone-expect": "warn",
62
+ "vitest/no-test-prefixes": "warn",
63
+ "vitest/prefer-to-be": "warn",
64
+ "vitest/prefer-to-contain": "warn",
65
+ "vitest/prefer-to-have-length": "warn",
66
+ "vitest/require-mock-type-parameters": "off",
67
+ "vitest/require-to-throw-message": "warn",
68
+ "vitest/valid-describe-callback": "warn",
69
+ "vitest/valid-expect": "warn",
70
+ "vitest/valid-expect-in-promise": "warn",
71
+ "vitest/valid-title": "warn",
72
+ "vitest/warn-todo": "warn"
73
+ },
74
+ "overrides": [
75
+ {
76
+ "files": [
77
+ "**/*.test.ts",
78
+ "**/*.test.tsx",
79
+ "**/*.spec.ts",
80
+ "**/*.spec.tsx",
81
+ "**/*.integration-spec.ts",
82
+ "**/*.e2e-spec.ts"
83
+ ],
84
+ "rules": {
85
+ "no-restricted-properties": [
86
+ "warn",
87
+ {
88
+ "object": "Math",
89
+ "property": "random",
90
+ "message": "No Math.random() in a test: use a deterministic fixture, or stub the random source."
91
+ },
92
+ {
93
+ "object": "Date",
94
+ "property": "now",
95
+ "message": "No Date.now() in a test: use vi.useFakeTimers() with vi.setSystemTime()."
96
+ },
97
+ {
98
+ "object": "performance",
99
+ "property": "now",
100
+ "message": "No performance.now() in a test: use vi.useFakeTimers() with vi.setSystemTime()."
101
+ },
102
+ {
103
+ "object": "process",
104
+ "property": "hrtime",
105
+ "message": "No process.hrtime in a test: use vi.useFakeTimers() with vi.setSystemTime()."
106
+ },
107
+ {
108
+ "object": "crypto",
109
+ "property": "randomUUID",
110
+ "message": "No crypto.randomUUID() in a test: use a fixed id, or stub the random source."
111
+ },
112
+ {
113
+ "object": "crypto",
114
+ "property": "getRandomValues",
115
+ "message": "No crypto.getRandomValues() in a test: use a fixed buffer, or stub the random source."
116
+ }
117
+ ],
118
+ "no-restricted-globals": [
119
+ "warn",
120
+ {
121
+ "name": "setTimeout",
122
+ "message": "No real timer in a test: vi.useFakeTimers() then vi.advanceTimersByTime()."
123
+ },
124
+ {
125
+ "name": "setInterval",
126
+ "message": "No real timer in a test: vi.useFakeTimers() then vi.advanceTimersByTime()."
127
+ }
128
+ ]
129
+ }
130
+ }
131
+ ]
52
132
  }