vitest-auto-spy 5.52.0 → 5.54.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.
- package/CHANGELOG.md +45 -1
- package/README.md +3 -2
- package/agent-docs/checklist.md +5 -2
- package/agent-docs/eslint.md +7 -5
- package/dist/cli.js +104 -17
- package/dist/eslint-plugin.cjs +239 -46
- package/dist/perf-reporter.d.ts +10 -0
- package/dist/perf-reporter.js +10 -3
- package/package.json +6 -1
- package/skills/vitest-auto-spy/SKILL.md +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,48 @@ The latest released version here must always match the one published on
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [5.54.0] - 2026-09-30
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`perf --json <reports> --gate` re-measures its suspects without `--command`.** The perf reporter
|
|
18
|
+
now records the Vitest config file the suite ran with (`configFile`, relative to `root`), and the
|
|
19
|
+
confirmation pass re-runs each suspect with `vitest run --config <that file>` over the suspects
|
|
20
|
+
only: one run per config when the reports came from several (`vitest.config.ts` and
|
|
21
|
+
`libs/tooling/vitest.config.ts`, say), read back as one second reading. A single slow reading on a
|
|
22
|
+
loaded runner now comes back as not reproduced instead of needing `--no-confirm` to fail anything.
|
|
23
|
+
The re-run has coverage off (`--coverage.enabled=false`), even when the first reading had it on:
|
|
24
|
+
coverage thresholds fail a run of a few files, and instrumentation is the harness's cost rather
|
|
25
|
+
than the test's, so a body over budget only because of instrumentation is not reproduced.
|
|
26
|
+
`--command` still takes precedence, and a report without `configFile` is not re-measured, as
|
|
27
|
+
before. The perf report format is version 6; versions 1–6 are read.
|
|
28
|
+
|
|
29
|
+
## [5.53.0] - 2026-09-29
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- **`no-inline-test-data`, in `recommended` as `warn`**: a data literal longer than 20 lines, or
|
|
34
|
+
the same literal written three times in one spec, belongs in a `*.mock.ts` file next to the spec.
|
|
35
|
+
`maxLines`, `repeats` and `minValues` tune it. Module wiring (`providers`, `imports`…), `it.each`
|
|
36
|
+
tables, `vi.mock` factories, literals holding a function and the mock files themselves are never
|
|
37
|
+
reported. On an 886-file Angular suite it flags 215 files at the defaults.
|
|
38
|
+
`/eslint-plugin` grows by 1.50 kB (50.96 → 52.46 kB, +2.9 %): 1.08 kB for this rule, the rest for
|
|
39
|
+
reading `externalDependencies` below.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **`no-relative-mock-under-builder` reads `externalDependencies` of the target's `buildTarget`.** The
|
|
44
|
+
builder builds with the options `context.getTargetOptions` returns for `buildTarget` (the target's
|
|
45
|
+
options, then each named configuration over them) and leaves those specifiers out of the bundle, so
|
|
46
|
+
a `vi.mock` of such a path alias does replace the module. The rule now reports an alias only when
|
|
47
|
+
some unit-test run of the file builds without it; esbuild's matching applies (exact name, a subpath,
|
|
48
|
+
one `*`). A relative specifier is still reported: the builder throws on it whatever is external. On
|
|
49
|
+
an 886-file Angular suite that externalises its mocked utilities this removes 69 false reports.
|
|
50
|
+
- **`no-outer-binding-in-mock-factory` treats an instance field initialiser as deferred.**
|
|
51
|
+
`class { listen = mockListen; }` returned from a factory reads `mockListen` when a test constructs
|
|
52
|
+
the class, not while the factory runs. A static field, a computed key and a static block still run
|
|
53
|
+
with the class declaration and are reported.
|
|
54
|
+
|
|
13
55
|
## [5.52.0] - 2026-09-29
|
|
14
56
|
|
|
15
57
|
### Added
|
|
@@ -6631,7 +6673,9 @@ moved under 1 %: `/angular` +192 B (+1.0 %) for the override queue and the HTTP
|
|
|
6631
6673
|
This copy ships with the package and carries 5.x only. Releases before 5.0.0 are in the
|
|
6632
6674
|
[full changelog](https://github.com/ASDAlexey/vitest-auto-spy/blob/master/CHANGELOG.md).
|
|
6633
6675
|
|
|
6634
|
-
[Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.
|
|
6676
|
+
[Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.54.0...HEAD
|
|
6677
|
+
[5.54.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.53.0...v5.54.0
|
|
6678
|
+
[5.53.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.52.0...v5.53.0
|
|
6635
6679
|
[5.52.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.51.0...v5.52.0
|
|
6636
6680
|
[5.51.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.50.0...v5.51.0
|
|
6637
6681
|
[5.50.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.49.0...v5.50.0
|
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ replacement for [`jest-auto-spies`](https://www.npmjs.com/package/jest-auto-spie
|
|
|
48
48
|
- **One API on every runner**: Vitest, Bun, `node:test` and Rstest; helpers for Angular, NestJS,
|
|
49
49
|
React, Vue / Pinia and Svelte.
|
|
50
50
|
- **Checks that make silent tests fail**: strict mode, Observable assertions, cleanup between tests,
|
|
51
|
-
|
|
51
|
+
57 ESLint rules.
|
|
52
52
|
- **Zero runtime dependencies.**
|
|
53
53
|
|
|
54
54
|
## Contents
|
|
@@ -3515,7 +3515,7 @@ Full page: [Observer stubs](https://asdalexey.github.io/vitest-auto-spy/utilitie
|
|
|
3515
3515
|
|
|
3516
3516
|
## ESLint plugin
|
|
3517
3517
|
|
|
3518
|
-
`vitest-auto-spy/eslint-plugin` has
|
|
3518
|
+
`vitest-auto-spy/eslint-plugin` has 57 lint rules that catch test code which passes but checks
|
|
3519
3519
|
nothing, or drifts from the real class. Each message links to the fix.
|
|
3520
3520
|
|
|
3521
3521
|
```js
|
|
@@ -3587,6 +3587,7 @@ Rolling it out on a large suite, and the options each rule takes:
|
|
|
3587
3587
|
| [`no-outer-binding-in-mock-factory`](https://asdalexey.github.io/vitest-auto-spy/utilities/eslint-rules#no-outer-binding-in-mock-factory) | `error` | — | Declare what a vi.mock factory reads through vi.hoisted — vi.mock runs before the file’s declarations |
|
|
3588
3588
|
| [`no-relative-mock-under-builder`](https://asdalexey.github.io/vitest-auto-spy/utilities/eslint-rules#no-relative-mock-under-builder) | `error` | — | Do not vi.mock() a relative path in a spec the Angular unit-test builder runs — the builder throws on it |
|
|
3589
3589
|
| [`no-real-wait-in-test`](https://asdalexey.github.io/vitest-auto-spy/utilities/eslint-rules#no-real-wait-in-test) | `warn` | — | Do not sleep on the real clock in a spec — drive fake timers or wait for the outcome |
|
|
3590
|
+
| [`no-inline-test-data`](https://asdalexey.github.io/vitest-auto-spy/utilities/eslint-rules#no-inline-test-data) | `warn` | — | Keep long or repeated test data in a *.mock.ts file next to the spec |
|
|
3590
3591
|
| [`no-disabled-testbed-teardown`](https://asdalexey.github.io/vitest-auto-spy/utilities/eslint-rules#no-disabled-testbed-teardown) | `error` | — | Keep TestBed teardown on — destroyAfterEach: false leaks every fixture into the tests after it |
|
|
3591
3592
|
| [`no-import-time-console-spies`](https://asdalexey.github.io/vitest-auto-spy/utilities/eslint-rules#no-import-time-console-spies) | `error` | — | Install the console spies where the file needs them — the import installs them once per worker |
|
|
3592
3593
|
| [`no-mistyped-use-value`](https://asdalexey.github.io/vitest-auto-spy/utilities/eslint-rules#no-mistyped-use-value) | `error` | — | Give a primitive-typed InjectionToken a useValue of its declared type — Angular types useValue as any, so nothing else checks it |
|
package/agent-docs/checklist.md
CHANGED
|
@@ -254,8 +254,11 @@ test bodies, and the gate prints the slowest tests, the share in hooks against b
|
|
|
254
254
|
time went in the spec, in your code and by package. The report ends in two tables of what is over
|
|
255
255
|
budget and nothing else. And every candidate is re-measured on its own before it may fail anything: a
|
|
256
256
|
file that is fast when it has the machine to itself is reported as _not reproduced_, an `info` rather
|
|
257
|
-
than a finding.
|
|
258
|
-
|
|
257
|
+
than a finding. `--json` without `--command` re-measures too: the report records its Vitest config
|
|
258
|
+
file, and the gate runs `vitest run --config <it>` over the suspects, one run per config when the
|
|
259
|
+
reports came from several, with coverage off. Without a way to re-measure (a `--command` without
|
|
260
|
+
`{paths}`, a report from before the config was recorded), findings are warnings that fail nothing
|
|
261
|
+
unless `--no-confirm` says one reading is enough. Exit `1` is "over budget", exit `2` is "there was nothing
|
|
259
262
|
to judge" — including a red or unfinished (`partial`) suite, which the gate will not judge at all, since a failed test is
|
|
260
263
|
measured until its timeout and 30 s of timeout looks exactly like 30 s of slow code.
|
|
261
264
|
|
package/agent-docs/eslint.md
CHANGED
|
@@ -98,8 +98,9 @@ export default [
|
|
|
98
98
|
| `prefer-create-mock` | `warn` | suggest | an object literal under `as SomeType` — `{ id: '1', isOffline: false } as Device`, `<Device>{ … }` too → `createMock<Device>({ … })`. A cast asks whether the two types overlap rather than whether the value is one of them, so it passes a key the type does not declare (the excess-property check is skipped) **and** a required field the fixture never sets; both type gates stay silent, and the same object is then spread into the expected payload of a call assertion. Where the literal already sits in a typed slot the first repair is to delete the cast. Silent on `as const`, `as unknown` / `as any` and the double cast built from them, on a cast of anything but a literal, and on a literal inside one of this library's own factories. `warn`, because accepting the suggestion makes a drifted fixture red on the next type-check — the finding, and a migration taken file by file |
|
|
99
99
|
| `no-mock-cast` | `error` | suggest | a cast to Vitest's `Mock` / `MockInstance` over a **member access** — `TestBed.inject(S).m as Mock` → `injectSpy(S).m`, which the suggestion writes whenever the token is in view (directly, or through a `const` the file settled with a `TestBed.inject`). `Mock` with no parameters is `Mock<any>`: `mockReturnValue` takes anything and `toHaveBeenCalledWith` stops comparing arguments. `(spy.m.mockReturnValue as Mock)(…)` gets its own message — the cast is on the member that installs the answer, so neither the value nor the method's return type is checked. The name must resolve to an import from `vitest` / `@rstest/core` / `bun:test` / `jest`, or to nothing at all, so a domain `Mock` is left alone |
|
|
100
100
|
| `no-outer-binding-in-mock-factory` | `error` | — | a `vi.mock(path, factory)` factory reading a top-level `const` / `let` / `var` / `class` declared outside `vi.hoisted` — the factory is hoisted above it, so the read is `Cannot access 'x' before initialization` → `const { user } = vi.hoisted(() => ({ user: … }))`. Only reads made while the factory runs count; function declarations, imports, type positions and `vi.doMock` are not reported |
|
|
101
|
-
| `no-relative-mock-under-builder` | `error` | — | `vi.mock` / `doMock` / `importMock` / `unmock` / `doUnmock` with a specifier starting `.` or `/` in a spec `@angular/build:unit-test` runs — the builder throws on it → `provideAutoSpy(X)`, or `overrideComponentProvider` for a component's own provider. **Silent until** an `@angular/build:unit-test` / `@nx/angular:unit-test` target serving the file is found (the `no-redundant-mock-reset` search) or `{ builder: 'unit-test' }` says so; a tsconfig path alias
|
|
101
|
+
| `no-relative-mock-under-builder` | `error` | — | `vi.mock` / `doMock` / `importMock` / `unmock` / `doUnmock` with a specifier starting `.` or `/` in a spec `@angular/build:unit-test` runs — the builder throws on it → `provideAutoSpy(X)`, or `overrideComponentProvider` for a component's own provider. **Silent until** an `@angular/build:unit-test` / `@nx/angular:unit-test` target serving the file is found (the `no-redundant-mock-reset` search) or `{ builder: 'unit-test' }` says so; a tsconfig path alias to a workspace file is reported too (the builder bundles it, so the mock silently does nothing) unless every run's `buildTarget` lists it in `externalDependencies` |
|
|
102
102
|
| `no-real-wait-in-test` | `warn` | — | a sleep on the real clock — `new Promise((r) => setTimeout(r, N))` in any spelling where `resolve` reaches the timer, `setTimeout(N)` from `node:timers/promises`; a `sleep(ms)` helper once, at its definition; a missing or `0` delay is left alone → `setupFakeTimers()` + `await advanceTimers(N)`, or wait for the outcome (`vi.waitFor`, `fixture.whenStable()`) |
|
|
103
|
+
| `no-inline-test-data` | `warn` | — | test data inline in a spec — a data literal over `maxLines` (20) lines, or the same literal `repeats` (3) times in one file with at least `minValues` (2) values, one spelled out → export it once from `<name>.mock.ts` next to the spec. Never reported: module wiring (`providers`, `imports`…), `it.each` tables, `vi.mock` factories, literals holding a function, and files named `*.mock.ts` / `*.fixture.ts` / under `__mocks__/` |
|
|
103
104
|
| `no-disabled-testbed-teardown` | `error` | — | `destroyAfterEach: false`, in `initTestEnvironment` or one `configureTestingModule` — every fixture outlives its test, `ngOnDestroy` never runs → delete it (Angular's default since v13) and fix the test that then fails. A config scoped to `**/*.spec.ts` must add the setup file to `files` |
|
|
104
105
|
| `no-redundant-mock-reset` | `error` | `--fix` / suggest | a mock reset the runner already performs between tests — `vi.clearAllMocks()` / `resetAllMocks()` / `restoreAllMocks()`, `x.mockClear()` / `mockReset()` / `mockRestore()` — standing where nothing the file wrote ran since the runner's own: the first statement of a `beforeEach` no other `beforeEach` precedes, or a clear as the last statement of an `afterEach` → delete it, and the hook with it where that is all it held. A restore or reset in `afterEach` / `afterAll` is never reported: after the last test it is what takes a spy off before the `afterAll` hooks. **Silent** until the flags are known: options `{ clearMocks, restoreMocks, mockReset }` (or `{ configFile }` naming the runner config) first, then a `vitest.config.*` / `vite.config.*` searched upwards from the file and read as text; a config that leaves `clearMocks` out counts it on from Vitest 5 (its default). Where an `@angular/build:unit-test` / `@nx/angular:unit-test` target (found in `angular.json` / `project.json` above the file) also serves the spec, a config flag counts only if that target's `runnerConfig` applies it too — without `runnerConfig` the builder runs Vitest's defaults, so `restoreMocks` / `mockReset` from `vitest.config.ts` do not make a reset dead there, while a clear stays reported on Vitest 5; the rule's inline flags are not narrowed. A config built by a factory or `mergeConfig` from another module is not read through — name its flags as `{ configFile, configFlags: { clearMocks: true } }`, read as if the file set them, so the builder narrowing still applies (`configFlags` needs `configFile`; `doctor` notes the case as `mock-reset-config-unread`). `setupAutoSpy({ restoreMocks: true })` is not the runner's `restoreMocks` (it restores in an `afterEach`, after the test) — never pass the rule a flag only the setup file sets. Flag matches call, never family — `restoreMocks` walks only the spies `vi.spyOn` installed. `--fix` only in a file whose only before-hook it is; a suggestion otherwise; never inside a test body |
|
|
105
106
|
| `no-unasserted-argument` | `warn` | — | a bare `expect(spy).toHaveBeenCalled()` on evidence from the file: the same subject asserted with `toHaveBeenCalledWith(…)` in another test, or a title containing `with` over a body whose every assertion is a bare call → `toHaveBeenCalledWith(…)` / `mustBeCalledWith(…)`. `not.toHaveBeenCalled()` and the counting matchers are never reported |
|
|
@@ -110,20 +111,21 @@ export default [
|
|
|
110
111
|
| `no-save-arguments-by-value` | `error` | — | `spy.calls.saveArgumentsByValue()` — a no-op here, so the spec silently asserts on post-mutation state |
|
|
111
112
|
| `prefer-native-spy-api` | `error` | `--fix` / suggest | `.and` / `.calls` where the spy's own API says the same thing — turn it on for the last mile off the jasmine shim. The fix renames only the member (`and.returnValue` → `mockReturnValue`, `withArgs` → `calledWith`), so comments stay; a rewrite that would drop one is a suggestion |
|
|
112
113
|
|
|
113
|
-
Fifty-
|
|
114
|
+
Fifty-seven rules, **every one an `error` except `prefer-render-shallow`,
|
|
114
115
|
`no-stub-class-double`, `no-structural-double`, `prefer-create-mock`, `no-instance-lifecycle-spy`,
|
|
115
116
|
`prefer-set-inputs`, `no-unasserted-argument`, `prefer-spy-on-own-method`, `prefer-to-have-signal-value`,
|
|
116
|
-
`no-unasserted-console-spy
|
|
117
|
+
`no-unasserted-console-spy`, `no-real-wait-in-test` and `no-inline-test-data`**; eight fix on their own, nineteen offer suggestions. Fifty-three are syntactic; `no-private-member-access`, `no-mistyped-use-value`,
|
|
117
118
|
`no-unknown-use-value-key` and `prefer-to-have-signal-value` read types, and all four report nothing at all without `parserOptions.project` / `projectService`
|
|
118
119
|
rather than guessing. `no-compile-components` waits the same way for a fact no file holds — which
|
|
119
120
|
builder the project has — and reports nothing until `{ builder: 'inline-resources' }` states it;
|
|
120
121
|
`no-relative-mock-under-builder` waits for a builder target serving the file, or `{ builder: 'unit-test' }`.
|
|
121
122
|
|
|
122
|
-
The
|
|
123
|
+
The twelve `warn`s do not rank findings by importance. Each names something other than a defect: a
|
|
123
124
|
cheaper render (`prefer-render-shallow`), a heuristic reading with no `provide:` to settle it
|
|
124
125
|
(`no-stub-class-double`, `no-structural-double`, `no-instance-lifecycle-spy`), a repair that turns
|
|
125
126
|
many files red at once (`prefer-create-mock`), a question only the author can answer
|
|
126
|
-
(`no-unasserted-argument`, `no-unasserted-console-spy`, `no-real-wait-in-test`),
|
|
127
|
+
(`no-unasserted-argument`, `no-unasserted-console-spy`, `no-real-wait-in-test`), a place data should
|
|
128
|
+
live rather than a defect (`no-inline-test-data`), or a preferred spelling
|
|
127
129
|
of working code (`prefer-spy-on-own-method`, `prefer-set-inputs`, `prefer-to-have-signal-value`). Set
|
|
128
130
|
any of the `warn`s to `'error'` once the batch is done.
|
|
129
131
|
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { ownVersion, isDirectory, readProfile, formatFindings, outputWidth, painterFor, summarize, DOCTOR_CHECKS, writeTextFile, REPORT_SCHEMA, sortFindings, findingJson, tallyOf,
|
|
2
|
+
import { ownVersion, isDirectory, readProfile, formatFindings, outputWidth, painterFor, summarize, ownPackageRoot, DOCTOR_CHECKS, writeTextFile, REPORT_SCHEMA, sortFindings, findingJson, tallyOf, readTextFile, SCAN_CAP_ENV, scanCap, COVERAGE_MATCHING_DOCS, VITEST_5_PERF_DOCS, filterBySeverity, isSymlink, skillFrontmatter, toPosix, pathExists, NOTHING_TO_READ_DOCS, removeFile, parseJsonc, isRecord, extractSetupFiles, captures, gitignoreFilter, BARE_RUN_DOCS, MONOCHROME, findStringEnd, listRepositoryFiles, wrapText, isSkippedDirectory, versionFrom, stripColor, padStart, padEnd } from './cli-SELXGLDQ.js';
|
|
3
3
|
import { resolve, join, posix, relative, isAbsolute, dirname } from 'node:path';
|
|
4
4
|
import { createHash } from 'node:crypto';
|
|
5
5
|
import { createRequire } from 'node:module';
|
|
@@ -1654,7 +1654,7 @@ function foreignPragmaPass() {
|
|
|
1654
1654
|
}
|
|
1655
1655
|
|
|
1656
1656
|
// src/cli/checks/export-map.generated.ts
|
|
1657
|
-
var EXPORT_MAP_VERSION = "5.
|
|
1657
|
+
var EXPORT_MAP_VERSION = "5.53.0";
|
|
1658
1658
|
var ENTRY_SPECIFIERS = "vitest-auto-spy vitest-auto-spy/bun vitest-auto-spy/bun-angular vitest-auto-spy/node vitest-auto-spy/rstest vitest-auto-spy/rxjs vitest-auto-spy/console vitest-auto-spy/dom-stubs vitest-auto-spy/diagnostics vitest-auto-spy/jasmine vitest-auto-spy/jasmine-compat vitest-auto-spy/observer-spy vitest-auto-spy/angular vitest-auto-spy/angular/diagnostics vitest-auto-spy/angular/doubles vitest-auto-spy/angular/matchers vitest-auto-spy/angular-http vitest-auto-spy/angular-router vitest-auto-spy/signal-forms vitest-auto-spy/nestjs vitest-auto-spy/react vitest-auto-spy/vue vitest-auto-spy/svelte vitest-auto-spy/setup vitest-auto-spy/zone vitest-auto-spy/eslint-plugin vitest-auto-spy/perf-reporter";
|
|
1659
1659
|
var EXPORTED_BY = {
|
|
1660
1660
|
AccessorImplementations: "0 1 2 3 4 12 20 21 22",
|
|
@@ -4665,8 +4665,8 @@ var PERF_OUTPUT_ENV = "VITEST_AUTO_SPY_PERF_OUT";
|
|
|
4665
4665
|
var PERF_REPORTER_ENV = "VITEST_AUTO_SPY_PERF_REPORTER";
|
|
4666
4666
|
var PERF_PROFILE_ENV = "VITEST_AUTO_SPY_PERF_PROFILE";
|
|
4667
4667
|
var PERF_ISOLATE_ENV = "VITEST_AUTO_SPY_PERF_ISOLATE";
|
|
4668
|
-
var PERF_FORMAT_VERSION =
|
|
4669
|
-
var READABLE_VERSIONS = [1, 2, 3, 4, 5];
|
|
4668
|
+
var PERF_FORMAT_VERSION = 6;
|
|
4669
|
+
var READABLE_VERSIONS = [1, 2, 3, 4, 5, 6];
|
|
4670
4670
|
var CASE_FLOOR_MS = 100;
|
|
4671
4671
|
var CASES_PER_FILE = 5;
|
|
4672
4672
|
var OPTIONAL_FILE_NUMBERS = ["workerId", "lane", "start", "fetch", "setupFetch", "retries", "autoSpy"];
|
|
@@ -4811,7 +4811,8 @@ function parsePerfRun(text) {
|
|
|
4811
4811
|
...parsed["partial"] === true ? { partial: true } : {},
|
|
4812
4812
|
...end === "failed" || end === "interrupted" || end === "passed" ? { end } : {},
|
|
4813
4813
|
...parsed["hung"] === true ? { hung: true } : {},
|
|
4814
|
-
...coverage === void 0 ? {} : { coverage }
|
|
4814
|
+
...coverage === void 0 ? {} : { coverage },
|
|
4815
|
+
...typeof parsed["configFile"] === "string" && parsed["configFile"] !== "" ? { configFile: parsed["configFile"] } : {}
|
|
4815
4816
|
};
|
|
4816
4817
|
}
|
|
4817
4818
|
function sumOf(run, key) {
|
|
@@ -5385,7 +5386,8 @@ function finding3(candidate, severity, tail) {
|
|
|
5385
5386
|
};
|
|
5386
5387
|
}
|
|
5387
5388
|
var UNCONFIRMED = [
|
|
5388
|
-
"Measured once and never confirmed, so it fails nothing. Run the gate where the files can be re-measured
|
|
5389
|
+
"Measured once and never confirmed, so it fails nothing. Run the gate where the files can be re-measured \u2014",
|
|
5390
|
+
"a --command with {paths}, or a report whose perf reporter recorded the Vitest config \u2014",
|
|
5389
5391
|
"or pass --no-confirm to gate on a single reading."
|
|
5390
5392
|
].join(" ");
|
|
5391
5393
|
function rowOf(candidate, outcome, again) {
|
|
@@ -7450,6 +7452,11 @@ function reroot(path, from, to) {
|
|
|
7450
7452
|
return rest.startsWith("/") || rest.startsWith("\\") ? `${to}${rest}` : path;
|
|
7451
7453
|
}
|
|
7452
7454
|
var END_ORDER = ["interrupted", "failed", "passed"];
|
|
7455
|
+
function sharedConfigFile(inputs) {
|
|
7456
|
+
const configFiles = new Set(inputs.map((input) => input.run.configFile));
|
|
7457
|
+
const [configFile] = configFiles;
|
|
7458
|
+
return { shared: configFiles.size <= 1, configFile };
|
|
7459
|
+
}
|
|
7453
7460
|
function mergedRun(inputs) {
|
|
7454
7461
|
const runs2 = inputs.map((input) => input.run);
|
|
7455
7462
|
const vitest = runs2.find((run) => run.vitest !== void 0)?.vitest;
|
|
@@ -7457,6 +7464,7 @@ function mergedRun(inputs) {
|
|
|
7457
7464
|
const startups = runs2.flatMap((run) => run.startup === void 0 ? [] : [run.startup]);
|
|
7458
7465
|
const end = END_ORDER.find((reason) => runs2.some((run) => run.end === reason));
|
|
7459
7466
|
const coverages = runs2.flatMap((run) => run.coverage === void 0 ? [] : [run.coverage]);
|
|
7467
|
+
const configFile = sharedConfigFile(inputs);
|
|
7460
7468
|
const startup = startups.reduce((total, each) => ({ ms: total.ms + each.ms, workers: total.workers + each.workers }), {
|
|
7461
7469
|
ms: 0,
|
|
7462
7470
|
workers: 0
|
|
@@ -7474,7 +7482,8 @@ function mergedRun(inputs) {
|
|
|
7474
7482
|
...runs2.some((run) => run.partial === true) ? { partial: true } : {},
|
|
7475
7483
|
...end === void 0 ? {} : { end },
|
|
7476
7484
|
...runs2.some((run) => run.hung === true) ? { hung: true } : {},
|
|
7477
|
-
...coverages.length === 0 ? {} : { coverage: Math.max(...coverages) }
|
|
7485
|
+
...coverages.length === 0 ? {} : { coverage: Math.max(...coverages) },
|
|
7486
|
+
...configFile.shared && configFile.configFile !== void 0 ? { configFile: configFile.configFile } : {}
|
|
7478
7487
|
};
|
|
7479
7488
|
}
|
|
7480
7489
|
function mergeRuns(inputs) {
|
|
@@ -7483,15 +7492,18 @@ function mergeRuns(inputs) {
|
|
|
7483
7492
|
const carriedBy = /* @__PURE__ */ new Map();
|
|
7484
7493
|
const empty = [];
|
|
7485
7494
|
const offset = { workerId: 0, lane: 0 };
|
|
7495
|
+
const perFile = !sharedConfigFile(inputs).shared;
|
|
7486
7496
|
for (const input of inputs) {
|
|
7487
7497
|
const from = trimRoot(input.run.root);
|
|
7488
7498
|
const shift = { ...offset };
|
|
7499
|
+
const configFile = perFile ? input.run.configFile : void 0;
|
|
7489
7500
|
if (input.run.files.length === 0) {
|
|
7490
7501
|
empty.push(input.path);
|
|
7491
7502
|
}
|
|
7492
7503
|
for (const file of input.run.files) {
|
|
7493
7504
|
const path = reroot(file.file, from, root);
|
|
7494
|
-
const
|
|
7505
|
+
const placed2 = { ...file, file: path, ...configFile === void 0 ? {} : { configFile } };
|
|
7506
|
+
const moved = shifted(placed2, shift, offset);
|
|
7495
7507
|
const previous = kept.get(path);
|
|
7496
7508
|
carriedBy.set(path, [...carriedBy.get(path) ?? [], input.path]);
|
|
7497
7509
|
if (previous === void 0 || fileTotal(moved) > fileTotal(previous)) {
|
|
@@ -7726,13 +7738,22 @@ function vitestMajor3(cwd) {
|
|
|
7726
7738
|
function versionFlags(cwd) {
|
|
7727
7739
|
return (vitestMajor3(cwd) ?? 0) >= 5 ? ["--experimental.diagnostics=false"] : [];
|
|
7728
7740
|
}
|
|
7741
|
+
function configArgs(config) {
|
|
7742
|
+
return config === void 0 ? [] : ["--config", config, "--coverage.enabled=false"];
|
|
7743
|
+
}
|
|
7729
7744
|
function fromRun(options, spawn, packageRoot) {
|
|
7730
7745
|
const entry = join(options.cwd, "node_modules", "vitest", "vitest.mjs");
|
|
7731
7746
|
const reporter = reporterPath(packageRoot);
|
|
7732
|
-
const mismatch = bareRunWouldMeasureSomethingElse(options.profile);
|
|
7747
|
+
const mismatch = options.config === void 0 ? bareRunWouldMeasureSomethingElse(options.profile) : void 0;
|
|
7733
7748
|
if (mismatch !== void 0) {
|
|
7734
7749
|
return failed(mismatch);
|
|
7735
7750
|
}
|
|
7751
|
+
if (options.config !== void 0 && !pathExists(resolve(options.cwd, options.config))) {
|
|
7752
|
+
return failed(
|
|
7753
|
+
`The report was measured with the Vitest config ${options.config}, and there is no such file in ${options.cwd}. Run the gate in the checkout the report was written for, or pass --command.
|
|
7754
|
+
Docs: ${NOTHING_TO_READ_DOCS}`
|
|
7755
|
+
);
|
|
7756
|
+
}
|
|
7736
7757
|
if (!pathExists(entry)) {
|
|
7737
7758
|
return failed(
|
|
7738
7759
|
`No Vitest is installed in ${options.cwd}, so there is nothing to run. Install vitest there, then measure again.
|
|
@@ -7749,7 +7770,16 @@ Docs: ${NOTHING_TO_READ_DOCS}`
|
|
|
7749
7770
|
removeFile(target);
|
|
7750
7771
|
const spawned = timed(spawn, {
|
|
7751
7772
|
command: process.execPath,
|
|
7752
|
-
args: [
|
|
7773
|
+
args: [
|
|
7774
|
+
entry,
|
|
7775
|
+
"run",
|
|
7776
|
+
...configArgs(options.config),
|
|
7777
|
+
"--reporter=default",
|
|
7778
|
+
`--reporter=${reporter}`,
|
|
7779
|
+
"--logHeapUsage",
|
|
7780
|
+
...versionFlags(options.cwd),
|
|
7781
|
+
...options.paths
|
|
7782
|
+
],
|
|
7753
7783
|
cwd: options.cwd,
|
|
7754
7784
|
env: runEnv(options, target, reporter),
|
|
7755
7785
|
shell: false
|
|
@@ -7809,15 +7839,66 @@ function readPerfRun(options, spawn = spawnProcess, packageRoot = ownPackageRoot
|
|
|
7809
7839
|
const source = options.json === void 0 ? fromCommand(options, command, spawn, packageRoot) : fromFile(options.json, options.cwd);
|
|
7810
7840
|
return source.ok ? { ...source, command } : source;
|
|
7811
7841
|
}
|
|
7812
|
-
function
|
|
7842
|
+
function localRoot(root, cwd) {
|
|
7843
|
+
const here = relative(cwd, root);
|
|
7844
|
+
return root !== "" && !here.startsWith("..") && !isAbsolute(here) ? root : cwd;
|
|
7845
|
+
}
|
|
7846
|
+
function recordedConfigs(recorded, cwd) {
|
|
7847
|
+
const root = localRoot(recorded.root, cwd);
|
|
7848
|
+
const configs = /* @__PURE__ */ new Map();
|
|
7849
|
+
for (const [path, file] of measuredFiles(recorded, cwd)) {
|
|
7850
|
+
const configFile = file.configFile ?? recorded.configFile;
|
|
7851
|
+
if (configFile !== void 0) {
|
|
7852
|
+
configs.set(path, toPosix(relative(cwd, resolve(root, configFile))));
|
|
7853
|
+
}
|
|
7854
|
+
}
|
|
7855
|
+
return configs;
|
|
7856
|
+
}
|
|
7857
|
+
function mergeMeasured(measured) {
|
|
7858
|
+
return {
|
|
7859
|
+
ok: true,
|
|
7860
|
+
run: mergeRuns(measured.map((each, index) => ({ path: String(index), run: each.run }))).run,
|
|
7861
|
+
runFailed: measured.some((each) => each.runFailed),
|
|
7862
|
+
profiles: new Map(measured.flatMap((each) => [...new Map(each.profiles)]))
|
|
7863
|
+
};
|
|
7864
|
+
}
|
|
7865
|
+
function underRecordedConfigs(configs, remeasure) {
|
|
7866
|
+
return (paths) => {
|
|
7867
|
+
const groups = /* @__PURE__ */ new Map();
|
|
7868
|
+
for (const path of paths) {
|
|
7869
|
+
const config = configs.get(path);
|
|
7870
|
+
if (config !== void 0) {
|
|
7871
|
+
groups.set(config, [...groups.get(config) ?? [], path]);
|
|
7872
|
+
}
|
|
7873
|
+
}
|
|
7874
|
+
if (groups.size === 0) {
|
|
7875
|
+
return failed(
|
|
7876
|
+
`None of the files to re-measure is in a report that recorded its Vitest config. Pass --command to re-measure them.
|
|
7877
|
+
Docs: ${NOTHING_TO_READ_DOCS}`
|
|
7878
|
+
);
|
|
7879
|
+
}
|
|
7880
|
+
const measured = [];
|
|
7881
|
+
for (const [config, group] of groups) {
|
|
7882
|
+
const source = remeasure(group, config);
|
|
7883
|
+
if (!source.ok) {
|
|
7884
|
+
return source;
|
|
7885
|
+
}
|
|
7886
|
+
measured.push(source);
|
|
7887
|
+
}
|
|
7888
|
+
return mergeMeasured(measured);
|
|
7889
|
+
};
|
|
7890
|
+
}
|
|
7891
|
+
function perfRemeasure(options, spawn = spawnProcess, packageRoot = ownPackageRoot(), recorded) {
|
|
7813
7892
|
if (options.command !== void 0 && !commandTakesPaths(options.command)) {
|
|
7814
7893
|
return void 0;
|
|
7815
7894
|
}
|
|
7895
|
+
const profileDir = join(options.cwd, "node_modules", ".cache", "vitest-auto-spy", `profiles-${process.pid}`);
|
|
7896
|
+
const scoped = { ...options, json: void 0, out: void 0, profileDir };
|
|
7816
7897
|
if (options.command === void 0 && options.json !== void 0) {
|
|
7817
|
-
|
|
7898
|
+
const configs = recorded === void 0 ? /* @__PURE__ */ new Map() : recordedConfigs(recorded, options.cwd);
|
|
7899
|
+
return configs.size === 0 ? void 0 : underRecordedConfigs(configs, (paths, config) => readPerfRun({ ...scoped, paths, config }, spawn, packageRoot));
|
|
7818
7900
|
}
|
|
7819
|
-
|
|
7820
|
-
return (paths) => readPerfRun({ ...options, json: void 0, out: void 0, paths, profileDir }, spawn, packageRoot);
|
|
7901
|
+
return (paths) => readPerfRun({ ...scoped, paths }, spawn, packageRoot);
|
|
7821
7902
|
}
|
|
7822
7903
|
function perfAbIsolate(options, spawn, packageRoot = ownPackageRoot()) {
|
|
7823
7904
|
if (options.command === void 0 && options.json !== void 0) {
|
|
@@ -7901,7 +7982,7 @@ function baselineRequest(args, cwd) {
|
|
|
7901
7982
|
}
|
|
7902
7983
|
};
|
|
7903
7984
|
}
|
|
7904
|
-
var PROFILE_DIR_UNUSED = "warning --profile-dir keeps the profiles the gate's confirmation pass records, and this run has no such pass: add --gate, without --no-confirm, on a source it can run again.\n";
|
|
7985
|
+
var PROFILE_DIR_UNUSED = "warning --profile-dir keeps the profiles the gate's confirmation pass records, and this run has no such pass: add --gate, without --no-confirm, on a source it can run again \u2014 a run, a --command with {paths}, or a report that recorded its Vitest config.\n";
|
|
7905
7986
|
function perfCommand(cwd, argv, io) {
|
|
7906
7987
|
const args = parseArgs(argv);
|
|
7907
7988
|
const format = formatOf(args);
|
|
@@ -7918,7 +7999,13 @@ function perfCommand(cwd, argv, io) {
|
|
|
7918
7999
|
...keepProfiles === void 0 ? {} : { keepProfiles: resolve(cwd, keepProfiles) }
|
|
7919
8000
|
};
|
|
7920
8001
|
const trustSingle = flagEnabled(args, "no-confirm");
|
|
7921
|
-
const
|
|
8002
|
+
const handed = options.json === void 0 ? void 0 : readPerfRun(options, spawn);
|
|
8003
|
+
const recorded = handed?.ok === true ? handed.run : void 0;
|
|
8004
|
+
const gate = flagEnabled(args, "gate") ? {
|
|
8005
|
+
options: gateOptions(args),
|
|
8006
|
+
remeasure: trustSingle ? void 0 : perfRemeasure(options, spawn, ownPackageRoot(), recorded),
|
|
8007
|
+
trustSingle
|
|
8008
|
+
} : void 0;
|
|
7922
8009
|
if (keepProfiles !== void 0 && gate?.remeasure === void 0) {
|
|
7923
8010
|
io.err(PROFILE_DIR_UNUSED);
|
|
7924
8011
|
}
|
|
@@ -7927,7 +8014,7 @@ function perfCommand(cwd, argv, io) {
|
|
|
7927
8014
|
const minSeverity = minSeverityOf(args);
|
|
7928
8015
|
const failOn = failOnOf(args);
|
|
7929
8016
|
const codeQuality = flagValue(args, "code-quality");
|
|
7930
|
-
return renderPerf(readPerfRun(options, spawn), profile, io, {
|
|
8017
|
+
return renderPerf(handed ?? readPerfRun(options, spawn), profile, io, {
|
|
7931
8018
|
budgets: gateOptions(args),
|
|
7932
8019
|
...format === "json" || format === "markdown" ? { format } : {},
|
|
7933
8020
|
...gate === void 0 ? {} : { gate },
|
package/dist/eslint-plugin.cjs
CHANGED
|
@@ -109,14 +109,14 @@ function rootCall(node) {
|
|
|
109
109
|
}
|
|
110
110
|
return current;
|
|
111
111
|
}
|
|
112
|
-
function isRunnerCall(node,
|
|
113
|
-
return isMemberCall(node, RUNNERS,
|
|
112
|
+
function isRunnerCall(node, members2) {
|
|
113
|
+
return isMemberCall(node, RUNNERS, members2);
|
|
114
114
|
}
|
|
115
|
-
function isMemberCall(node, objects,
|
|
115
|
+
function isMemberCall(node, objects, members2) {
|
|
116
116
|
if (!isCallExpression(node) || !isMemberExpression(node.callee) || !isIdentifier(node.callee.object) || !isIdentifier(node.callee.property)) {
|
|
117
117
|
return false;
|
|
118
118
|
}
|
|
119
|
-
return objects.has(node.callee.object.name) &&
|
|
119
|
+
return objects.has(node.callee.object.name) && members2.has(node.callee.property.name);
|
|
120
120
|
}
|
|
121
121
|
var FUNCTION_TYPES = /* @__PURE__ */ new Set(["ArrowFunctionExpression", "FunctionDeclaration", "FunctionExpression"]);
|
|
122
122
|
function isFunctionNode(node) {
|
|
@@ -887,24 +887,44 @@ function configOf(value, projectRoot, workspaceRoot) {
|
|
|
887
887
|
}
|
|
888
888
|
return typeof value === "string" ? node_path.resolve(workspaceRoot, value) : void 0;
|
|
889
889
|
}
|
|
890
|
-
function
|
|
891
|
-
|
|
890
|
+
function targetsOf(project) {
|
|
891
|
+
return recordAt(project, "architect") ?? recordAt(project, "targets") ?? {};
|
|
892
|
+
}
|
|
893
|
+
function stringsAt(value, key) {
|
|
894
|
+
const found = value?.[key];
|
|
895
|
+
return Array.isArray(found) ? found.filter((entry) => typeof entry === "string") : [];
|
|
896
|
+
}
|
|
897
|
+
function externalsOf(buildTarget, workspace) {
|
|
898
|
+
if (typeof buildTarget !== "string") {
|
|
899
|
+
return [];
|
|
900
|
+
}
|
|
901
|
+
const [projectName, targetName = "", configurationNames = ""] = buildTarget.split(":");
|
|
902
|
+
const target = recordAt(targetsOf(workspace.projects.get(projectName)), targetName);
|
|
903
|
+
const configurations = recordAt(target, "configurations");
|
|
904
|
+
return [recordAt(target, "options"), ...configurationNames.split(",").map((name) => recordAt(configurations, name.trim()))].reduce(
|
|
905
|
+
(externals, block) => block !== void 0 && "externalDependencies" in block ? stringsAt(block, "externalDependencies") : externals,
|
|
906
|
+
[]
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
function lastSet(blocks, key, fallback) {
|
|
910
|
+
return blocks.reduce((value, block) => block !== void 0 && key in block ? block[key] : value, fallback);
|
|
911
|
+
}
|
|
912
|
+
function targetRuns(target, name, projectRoot, workspace) {
|
|
913
|
+
const byName = recordAt(workspace.defaults, name);
|
|
892
914
|
const builder = target["builder"] ?? target["executor"] ?? byName?.["executor"];
|
|
893
915
|
if (typeof builder !== "string" || !UNIT_TEST_BUILDERS.has(builder)) {
|
|
894
916
|
return [];
|
|
895
917
|
}
|
|
896
|
-
const blocks = [recordAt(recordAt(defaults, builder), "options"), recordAt(byName, "options"), recordAt(target, "options")];
|
|
897
|
-
const
|
|
898
|
-
|
|
899
|
-
false
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
return [base, ...overrides].map((value) => configOf(value, projectRoot, workspaceRoot));
|
|
918
|
+
const blocks = [recordAt(recordAt(workspace.defaults, builder), "options"), recordAt(byName, "options"), recordAt(target, "options")];
|
|
919
|
+
const overrides = Object.values(recordAt(target, "configurations") ?? {}).filter(isJson).filter((configuration) => "runnerConfig" in configuration || "buildTarget" in configuration);
|
|
920
|
+
return [blocks, ...overrides.map((configuration) => [...blocks, configuration])].map((chain) => ({
|
|
921
|
+
config: configOf(lastSet(chain, "runnerConfig", false), projectRoot, workspace.root),
|
|
922
|
+
externals: externalsOf(lastSet(chain, "buildTarget", void 0), workspace)
|
|
923
|
+
}));
|
|
903
924
|
}
|
|
904
|
-
function
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
([name, target]) => isJson(target) ? targetConfigs(target, name, defaults, projectRoot, workspaceRoot) : []
|
|
925
|
+
function projectRuns(project, projectRoot, workspace) {
|
|
926
|
+
return Object.entries(targetsOf(project)).flatMap(
|
|
927
|
+
([name, target]) => isJson(target) ? targetRuns(target, name, projectRoot, workspace) : []
|
|
908
928
|
);
|
|
909
929
|
}
|
|
910
930
|
function locate(directory) {
|
|
@@ -926,18 +946,27 @@ function runsFor(directory) {
|
|
|
926
946
|
if (workspaceRoot === void 0) {
|
|
927
947
|
return [];
|
|
928
948
|
}
|
|
929
|
-
const defaults = recordAt(readJson(node_path.join(workspaceRoot, "nx.json")), "targetDefaults");
|
|
930
949
|
const projects = WORKSPACE_FILES.map((name) => recordAt(readJson(node_path.join(workspaceRoot, name)), "projects")).find(
|
|
931
950
|
(found) => found !== void 0
|
|
932
951
|
);
|
|
952
|
+
const ownProject = projectDir === void 0 ? void 0 : readJson(node_path.join(projectDir, "project.json")) ?? {};
|
|
953
|
+
const byName = new Map(Object.entries(projects ?? {}).filter((entry) => isJson(entry[1])));
|
|
954
|
+
if (ownProject !== void 0 && typeof ownProject["name"] === "string" && !byName.has(ownProject["name"])) {
|
|
955
|
+
byName.set(ownProject["name"], ownProject);
|
|
956
|
+
}
|
|
957
|
+
const workspace = {
|
|
958
|
+
root: workspaceRoot,
|
|
959
|
+
defaults: recordAt(readJson(node_path.join(workspaceRoot, "nx.json")), "targetDefaults"),
|
|
960
|
+
projects: byName
|
|
961
|
+
};
|
|
933
962
|
const fromWorkspace = Object.values(projects ?? {}).filter(isJson).flatMap((project) => {
|
|
934
963
|
const root = node_path.resolve(workspaceRoot, typeof project["root"] === "string" ? project["root"] : "");
|
|
935
|
-
return contains(root, directory) ?
|
|
964
|
+
return contains(root, directory) ? projectRuns(project, root, workspace) : [];
|
|
936
965
|
});
|
|
937
|
-
const fromProject = projectDir === void 0 ? [] :
|
|
966
|
+
const fromProject = projectDir === void 0 || ownProject === void 0 ? [] : projectRuns(ownProject, projectDir, workspace);
|
|
938
967
|
return [...fromWorkspace, ...fromProject];
|
|
939
968
|
}
|
|
940
|
-
function
|
|
969
|
+
function builderRuns(filename) {
|
|
941
970
|
const directory = node_path.resolve(node_path.dirname(filename));
|
|
942
971
|
const cached = runsCache.get(directory);
|
|
943
972
|
if (cached !== void 0) {
|
|
@@ -947,6 +976,9 @@ function builderConfigs(filename) {
|
|
|
947
976
|
runsCache.set(directory, runs);
|
|
948
977
|
return runs;
|
|
949
978
|
}
|
|
979
|
+
function builderConfigs(filename) {
|
|
980
|
+
return builderRuns(filename).map((run) => run.config);
|
|
981
|
+
}
|
|
950
982
|
|
|
951
983
|
// src/lib/jsonc.ts
|
|
952
984
|
function parseJsonc(text) {
|
|
@@ -1116,7 +1148,19 @@ var VITEST = /* @__PURE__ */ new Set(["vi", "vitest"]);
|
|
|
1116
1148
|
var PATCHED = /* @__PURE__ */ new Set(["mock", "doMock", "importMock", "unmock", "doUnmock"]);
|
|
1117
1149
|
var BLOCKED_SPECIFIER = /^[./]/;
|
|
1118
1150
|
function runsUnderBuilder(context) {
|
|
1119
|
-
return Reflect.get(Object(context.options[0]), "builder") === "unit-test" ||
|
|
1151
|
+
return Reflect.get(Object(context.options[0]), "builder") === "unit-test" || builderRuns(context.filename).length > 0;
|
|
1152
|
+
}
|
|
1153
|
+
function matchesExternal(specifier, external) {
|
|
1154
|
+
const star = external.indexOf("*");
|
|
1155
|
+
if (star === -1) {
|
|
1156
|
+
return specifier === external || specifier.startsWith(`${external}/`);
|
|
1157
|
+
}
|
|
1158
|
+
const prefix = external.slice(0, star);
|
|
1159
|
+
const suffix = external.slice(star + 1);
|
|
1160
|
+
return specifier.length >= prefix.length + suffix.length && specifier.startsWith(prefix) && specifier.endsWith(suffix);
|
|
1161
|
+
}
|
|
1162
|
+
function externalInEveryRun(runs, specifier) {
|
|
1163
|
+
return runs.length > 0 && runs.every((run) => run.externals.some((external) => matchesExternal(specifier, external)));
|
|
1120
1164
|
}
|
|
1121
1165
|
function specifierOf(node) {
|
|
1122
1166
|
if (node?.type === "ImportExpression") {
|
|
@@ -1135,7 +1179,7 @@ var noRelativeMockUnderBuilder = defineRule({
|
|
|
1135
1179
|
schema: [{ type: "object", properties: { builder: { enum: ["unit-test"] } }, additionalProperties: false }],
|
|
1136
1180
|
messages: {
|
|
1137
1181
|
noRelativeMockUnderBuilder: "`vi.{{method}}('{{specifier}}')` throws under `@angular/build:unit-test`: the builder patches `vi.{{method}}` to reject every relative specifier (`The \"vi.mock\" and related methods are not supported for relative imports`), and no option lifts it. Replace the dependency through TestBed instead \u2014 `provideAutoSpy(Service)`, or `overrideComponentProvider` for a component\u2019s own provider.",
|
|
1138
|
-
aliasMockUnderBuilder: "`vi.{{method}}('{{specifier}}')` does nothing under `@angular/build:unit-test`: `{{specifier}}` is the tsconfig path alias `{{alias}}` for a file of this workspace,
|
|
1182
|
+
aliasMockUnderBuilder: "`vi.{{method}}('{{specifier}}')` does nothing under `@angular/build:unit-test`: `{{specifier}}` is the tsconfig path alias `{{alias}}` for a file of this workspace, so the builder bundles it before Vitest can replace it, and the real module runs without an error. List it in `externalDependencies` of the `buildTarget`, or replace it through TestBed: `provideAutoSpy(Service)`, or `overrideComponentProvider` for a component\u2019s own provider."
|
|
1139
1183
|
},
|
|
1140
1184
|
create: (context) => runsUnderBuilder(context) ? {
|
|
1141
1185
|
CallExpression: (node) => {
|
|
@@ -1149,7 +1193,7 @@ var noRelativeMockUnderBuilder = defineRule({
|
|
|
1149
1193
|
return;
|
|
1150
1194
|
}
|
|
1151
1195
|
const alias = localAliasOf(context.filename, specifier);
|
|
1152
|
-
if (alias !== void 0) {
|
|
1196
|
+
if (alias !== void 0 && !externalInEveryRun(builderRuns(context.filename), specifier)) {
|
|
1153
1197
|
context.report({ node, messageId: "aliasMockUnderBuilder", data: { method, specifier, alias: alias.pattern } });
|
|
1154
1198
|
}
|
|
1155
1199
|
}
|
|
@@ -2697,6 +2741,150 @@ var noHandAssignedGlobal = defineRule({
|
|
|
2697
2741
|
}
|
|
2698
2742
|
});
|
|
2699
2743
|
|
|
2744
|
+
// src/lib/eslint/inline-test-data.ts
|
|
2745
|
+
var DEFAULT_MAX_LINES = 20;
|
|
2746
|
+
var DEFAULT_REPEATS = 3;
|
|
2747
|
+
var DEFAULT_MIN_VALUES = 2;
|
|
2748
|
+
var MOCK_FILE = /(?:^|[/\\])__mocks__[/\\]|\.(?:fixtures?|mocks?)\.[cm]?[jt]sx?$/;
|
|
2749
|
+
var WIRING_KEYS = /* @__PURE__ */ new Set([
|
|
2750
|
+
"bootstrap",
|
|
2751
|
+
"declarations",
|
|
2752
|
+
"exports",
|
|
2753
|
+
"extraImports",
|
|
2754
|
+
"extraProviders",
|
|
2755
|
+
"imports",
|
|
2756
|
+
"providers",
|
|
2757
|
+
"schemas",
|
|
2758
|
+
"viewProviders"
|
|
2759
|
+
]);
|
|
2760
|
+
var MOCK_FACTORIES = /* @__PURE__ */ new Set(["doMock", "mock"]);
|
|
2761
|
+
var WRAPPERS = /* @__PURE__ */ new Set(["TSAsExpression", "TSNonNullExpression", "TSSatisfiesExpression", "TSTypeAssertion"]);
|
|
2762
|
+
function isLiteral(node) {
|
|
2763
|
+
return isObjectExpression(node) || isArrayExpression(node);
|
|
2764
|
+
}
|
|
2765
|
+
function members(node) {
|
|
2766
|
+
return isObjectExpression(node) ? node.properties : node.elements.filter((element) => element !== null);
|
|
2767
|
+
}
|
|
2768
|
+
function container(node) {
|
|
2769
|
+
let current = node.parent;
|
|
2770
|
+
while (WRAPPERS.has(current.type) || current.type === "Property" || current.type === "SpreadElement") {
|
|
2771
|
+
current = current.parent;
|
|
2772
|
+
}
|
|
2773
|
+
return current;
|
|
2774
|
+
}
|
|
2775
|
+
function isWiring(node) {
|
|
2776
|
+
return WIRING_KEYS.has(propertyName(node) ?? "");
|
|
2777
|
+
}
|
|
2778
|
+
function isEachTable(node) {
|
|
2779
|
+
return isCallExpression(node) && memberName(node.callee) === "each";
|
|
2780
|
+
}
|
|
2781
|
+
function isInMockFactory(node) {
|
|
2782
|
+
return isFunctionNode(node) && isCallExpression(node.parent) && isRunnerCall(node.parent, MOCK_FACTORIES);
|
|
2783
|
+
}
|
|
2784
|
+
function holdsBehaviour(node) {
|
|
2785
|
+
return members(node).some((member) => {
|
|
2786
|
+
const value = propertyName(member) === void 0 ? member : propertyValue(member);
|
|
2787
|
+
return Reflect.get(member, "method") === true || isFunctionNode(value) || isLiteral(value) && holdsBehaviour(value);
|
|
2788
|
+
});
|
|
2789
|
+
}
|
|
2790
|
+
function isData(node) {
|
|
2791
|
+
return members(node).length > 0 && !members(node).some(isWiring) && !holdsBehaviour(node) && !hasAncestor(node.parent, (ancestor) => isWiring(ancestor) || isEachTable(ancestor) || isInMockFactory(ancestor));
|
|
2792
|
+
}
|
|
2793
|
+
function leaves(node) {
|
|
2794
|
+
return members(node).flatMap((member) => {
|
|
2795
|
+
const value = propertyName(member) === void 0 ? member : propertyValue(member);
|
|
2796
|
+
return isLiteral(value) ? leaves(value) : [value];
|
|
2797
|
+
});
|
|
2798
|
+
}
|
|
2799
|
+
var isPrimitive = (node) => node.type === "Literal" || node.type === "UnaryExpression" && Reflect.get(Object(Reflect.get(node, "argument")), "type") === "Literal";
|
|
2800
|
+
function isRecord(node, minValues) {
|
|
2801
|
+
const values = leaves(node);
|
|
2802
|
+
return values.length >= minValues && values.some(isPrimitive);
|
|
2803
|
+
}
|
|
2804
|
+
function limits(context) {
|
|
2805
|
+
const option = (name, fallback) => {
|
|
2806
|
+
const value = Reflect.get(Object(context.options[0]), name);
|
|
2807
|
+
return typeof value === "number" ? value : fallback;
|
|
2808
|
+
};
|
|
2809
|
+
return {
|
|
2810
|
+
maxLines: option("maxLines", DEFAULT_MAX_LINES),
|
|
2811
|
+
repeats: option("repeats", DEFAULT_REPEATS),
|
|
2812
|
+
minValues: option("minValues", DEFAULT_MIN_VALUES)
|
|
2813
|
+
};
|
|
2814
|
+
}
|
|
2815
|
+
var lineCount = (node) => node.loc.end.line - node.loc.start.line + 1;
|
|
2816
|
+
var within = (node, outer) => node.range[0] >= outer.range[0] && node.range[1] <= outer.range[1];
|
|
2817
|
+
var span = (node) => node.range[1] - node.range[0];
|
|
2818
|
+
var noInlineTestData = defineRule({
|
|
2819
|
+
name: "no-inline-test-data",
|
|
2820
|
+
description: "Keep long or repeated test data in a *.mock.ts file next to the spec",
|
|
2821
|
+
schema: [
|
|
2822
|
+
{
|
|
2823
|
+
type: "object",
|
|
2824
|
+
properties: {
|
|
2825
|
+
maxLines: { type: "integer", minimum: 1 },
|
|
2826
|
+
repeats: { type: "integer", minimum: 2 },
|
|
2827
|
+
minValues: { type: "integer", minimum: 1 }
|
|
2828
|
+
},
|
|
2829
|
+
additionalProperties: false
|
|
2830
|
+
}
|
|
2831
|
+
],
|
|
2832
|
+
messages: {
|
|
2833
|
+
longLiteral: "This literal spans {{lines}} lines of test data (the limit is {{max}}). Move it to a `*.mock.ts` file next to the spec and import it, so the test shows what it checks rather than the data it feeds in.",
|
|
2834
|
+
repeatedLiteral: "`{{literal}}` is written {{count}} times in this file (first at line {{first}}). Export it once from a `*.mock.ts` file next to the spec and import it, so one edit reaches every test that uses it."
|
|
2835
|
+
},
|
|
2836
|
+
create: (context) => {
|
|
2837
|
+
if (MOCK_FILE.test(context.filename)) {
|
|
2838
|
+
return {};
|
|
2839
|
+
}
|
|
2840
|
+
const { maxLines, repeats, minValues } = limits(context);
|
|
2841
|
+
const seen = /* @__PURE__ */ new Map();
|
|
2842
|
+
const visit = (node) => {
|
|
2843
|
+
if (isLiteral(container(node)) || !isData(node)) {
|
|
2844
|
+
return;
|
|
2845
|
+
}
|
|
2846
|
+
if (lineCount(node) > maxLines) {
|
|
2847
|
+
context.report({ node, messageId: "longLiteral", data: { lines: String(lineCount(node)), max: String(maxLines) } });
|
|
2848
|
+
}
|
|
2849
|
+
};
|
|
2850
|
+
const collect = (node) => {
|
|
2851
|
+
if (!isData(node) || !isRecord(node, minValues)) {
|
|
2852
|
+
return;
|
|
2853
|
+
}
|
|
2854
|
+
const key = context.sourceCode.getText(node).replace(/\s+/g, "");
|
|
2855
|
+
const group = seen.get(key);
|
|
2856
|
+
if (group) {
|
|
2857
|
+
group.rest.push(node);
|
|
2858
|
+
} else {
|
|
2859
|
+
seen.set(key, { first: node, rest: [] });
|
|
2860
|
+
}
|
|
2861
|
+
};
|
|
2862
|
+
return {
|
|
2863
|
+
"ObjectExpression, ArrayExpression": (node) => {
|
|
2864
|
+
visit(node);
|
|
2865
|
+
collect(node);
|
|
2866
|
+
},
|
|
2867
|
+
"Program:exit": () => {
|
|
2868
|
+
const reported = [];
|
|
2869
|
+
const groups = [...seen.values()].filter((group) => group.rest.length + 1 >= repeats).sort((a, b) => span(b.first) - span(a.first));
|
|
2870
|
+
for (const { first, rest } of groups) {
|
|
2871
|
+
if (reported.some((outer) => within(first, outer))) {
|
|
2872
|
+
continue;
|
|
2873
|
+
}
|
|
2874
|
+
reported.push(first, ...rest);
|
|
2875
|
+
for (const node of rest) {
|
|
2876
|
+
context.report({
|
|
2877
|
+
node,
|
|
2878
|
+
messageId: "repeatedLiteral",
|
|
2879
|
+
data: { literal: excerpt(context, node, 40), count: String(rest.length + 1), first: String(first.loc.start.line) }
|
|
2880
|
+
});
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
};
|
|
2885
|
+
}
|
|
2886
|
+
});
|
|
2887
|
+
|
|
2700
2888
|
// src/lib/eslint/jasmine-compat.ts
|
|
2701
2889
|
var PACKAGES = ["vitest-auto-spy", "vitest-auto-spies"];
|
|
2702
2890
|
function entries(...subpaths) {
|
|
@@ -3228,20 +3416,23 @@ function initialisedLate(variable) {
|
|
|
3228
3416
|
const declarator = definition.node;
|
|
3229
3417
|
return isTopLevel(declarator.parent) && !isHoisted(declarator.init);
|
|
3230
3418
|
}
|
|
3419
|
+
function isInstanceFieldValue(node, child) {
|
|
3420
|
+
return (node.type === "PropertyDefinition" || node.type === "AccessorProperty") && Reflect.get(node, "static") !== true && Reflect.get(node, "value") === child;
|
|
3421
|
+
}
|
|
3231
3422
|
function isDeferred(identifier, factory) {
|
|
3232
|
-
for (let current = identifier.parent; current !== factory; current = current.parent) {
|
|
3233
|
-
if (isFunctionNode(current)) {
|
|
3423
|
+
for (let child = identifier, current = identifier.parent; current !== factory; child = current, current = current.parent) {
|
|
3424
|
+
if (isFunctionNode(current) || isInstanceFieldValue(current, child)) {
|
|
3234
3425
|
return true;
|
|
3235
3426
|
}
|
|
3236
3427
|
}
|
|
3237
3428
|
return false;
|
|
3238
3429
|
}
|
|
3239
|
-
function
|
|
3240
|
-
return node.range[0] >=
|
|
3430
|
+
function within2(node, container2) {
|
|
3431
|
+
return node.range[0] >= container2.range[0] && node.range[1] <= container2.range[1];
|
|
3241
3432
|
}
|
|
3242
3433
|
function eagerRead(variable, factory) {
|
|
3243
3434
|
return variable.references.find(
|
|
3244
|
-
(reference) => !reference.writeExpr && Reflect.get(reference, "isValueReference") !== false &&
|
|
3435
|
+
(reference) => !reference.writeExpr && Reflect.get(reference, "isValueReference") !== false && within2(reference.identifier, factory) && !isDeferred(reference.identifier, factory)
|
|
3245
3436
|
)?.identifier;
|
|
3246
3437
|
}
|
|
3247
3438
|
function outerScopes(context, factory) {
|
|
@@ -3605,11 +3796,11 @@ function isTestCallbackParameter(context, node, callbacks) {
|
|
|
3605
3796
|
function readsTheTestContext(context, callback, parameter) {
|
|
3606
3797
|
const scope = context.sourceCode.getScope(callback);
|
|
3607
3798
|
const references = scope.variables.flatMap((variable) => variable.name === parameter.name ? variable.references : []);
|
|
3608
|
-
const
|
|
3799
|
+
const members2 = references.flatMap(({ identifier }) => {
|
|
3609
3800
|
const member = identifier.parent;
|
|
3610
3801
|
return isMemberExpression(member) && member.object === identifier ? [memberName(member)] : [];
|
|
3611
3802
|
});
|
|
3612
|
-
return
|
|
3803
|
+
return members2.length > 0 && !members2.includes("fail") && !references.some(({ identifier }) => isCallee(identifier));
|
|
3613
3804
|
}
|
|
3614
3805
|
var noDoneCallback = defineRule({
|
|
3615
3806
|
name: "no-done-callback",
|
|
@@ -3813,13 +4004,13 @@ var OVERRIDES = /* @__PURE__ */ new Set([
|
|
|
3813
4004
|
"overrideTemplateUsingTestingModule"
|
|
3814
4005
|
]);
|
|
3815
4006
|
var RESETS = /* @__PURE__ */ new Set(["resetTestingModule"]);
|
|
3816
|
-
function testBedCall(
|
|
3817
|
-
return `CallExpression[callee.object.name="TestBed"][callee.property.name=/^(${[...
|
|
4007
|
+
function testBedCall(members2) {
|
|
4008
|
+
return `CallExpression[callee.object.name="TestBed"][callee.property.name=/^(${[...members2].join("|")})$/]`;
|
|
3818
4009
|
}
|
|
3819
4010
|
var OVERRIDES_THE_MODULE = `${testBedCall(OVERRIDES)},CallExpression[callee.type="Identifier"][callee.name="overrideComponentProvider"]`;
|
|
3820
4011
|
var RESETS_THE_MODULE = testBedCall(RESETS);
|
|
3821
|
-
function
|
|
3822
|
-
return node.range[0] >=
|
|
4012
|
+
function within3(node, container2) {
|
|
4013
|
+
return node.range[0] >= container2.range[0] && node.range[1] <= container2.range[1];
|
|
3823
4014
|
}
|
|
3824
4015
|
var HOOKS4 = /* @__PURE__ */ new Set(["beforeAll", "beforeEach"]);
|
|
3825
4016
|
var SUITES = /* @__PURE__ */ new Set(["describe", "suite"]);
|
|
@@ -3851,10 +4042,10 @@ function runsBeforeEveryTest(node) {
|
|
|
3851
4042
|
return enclosing !== void 0 && isNamedCall(enclosing.parent, HOOKS4);
|
|
3852
4043
|
}
|
|
3853
4044
|
function resetsTheTestingModule(resets, suite) {
|
|
3854
|
-
return resets.some((reset) =>
|
|
4045
|
+
return resets.some((reset) => within3(reset, suite));
|
|
3855
4046
|
}
|
|
3856
4047
|
function runsFirst(override, injection, hook) {
|
|
3857
|
-
return
|
|
4048
|
+
return within3(override, injection) || within3(override, hook) && override.range[1] < injection.range[0];
|
|
3858
4049
|
}
|
|
3859
4050
|
function enclosingBeforeCreate(node) {
|
|
3860
4051
|
const setup = enclosingFunction(node);
|
|
@@ -3862,7 +4053,7 @@ function enclosingBeforeCreate(node) {
|
|
|
3862
4053
|
}
|
|
3863
4054
|
function injectsBeforeSetupOverride(injection, { overrides }) {
|
|
3864
4055
|
const setup = enclosingBeforeCreate(injection);
|
|
3865
|
-
return setup !== void 0 && overrides.some((override) =>
|
|
4056
|
+
return setup !== void 0 && overrides.some((override) => within3(override, setup) && override.range[0] > injection.range[1]);
|
|
3866
4057
|
}
|
|
3867
4058
|
function breaksAnOverride(injection, { overrides, resets }) {
|
|
3868
4059
|
const hook = enclosingCallOf(injection, HOOKS4);
|
|
@@ -3873,7 +4064,7 @@ function breaksAnOverride(injection, { overrides, resets }) {
|
|
|
3873
4064
|
if (resetsTheTestingModule(resets, suite)) {
|
|
3874
4065
|
return false;
|
|
3875
4066
|
}
|
|
3876
|
-
return overrides.some((override) =>
|
|
4067
|
+
return overrides.some((override) => within3(override, suite) && !runsFirst(override, injection, hook));
|
|
3877
4068
|
}
|
|
3878
4069
|
|
|
3879
4070
|
// src/lib/eslint/no-inject-before-override.ts
|
|
@@ -5934,18 +6125,18 @@ var SKIPPED_MEMBERS = /* @__PURE__ */ new Set(["skip", "todo"]);
|
|
|
5934
6125
|
var EXISTS = /* @__PURE__ */ new Set(["toBeDefined", "toBeInstanceOf", "toBeTruthy"]);
|
|
5935
6126
|
var MISSING = /* @__PURE__ */ new Set(["toBeFalsy", "toBeNull", "toBeUndefined"]);
|
|
5936
6127
|
function calleeChain(node) {
|
|
5937
|
-
const
|
|
6128
|
+
const members2 = [];
|
|
5938
6129
|
let current = node.callee;
|
|
5939
6130
|
for (; ; ) {
|
|
5940
6131
|
if (isIdentifier(current)) {
|
|
5941
|
-
return { members, root: current.name };
|
|
6132
|
+
return { members: members2, root: current.name };
|
|
5942
6133
|
}
|
|
5943
6134
|
if (isMemberExpression(current)) {
|
|
5944
6135
|
const name = memberName(current);
|
|
5945
6136
|
if (name === void 0) {
|
|
5946
6137
|
return void 0;
|
|
5947
6138
|
}
|
|
5948
|
-
|
|
6139
|
+
members2.unshift(name);
|
|
5949
6140
|
current = current.object;
|
|
5950
6141
|
continue;
|
|
5951
6142
|
}
|
|
@@ -6291,9 +6482,9 @@ function variableRewrite(context, call, id, method) {
|
|
|
6291
6482
|
}
|
|
6292
6483
|
const declaration = declarator.parent;
|
|
6293
6484
|
const writes = variable.references.filter((reference) => reference.writeExpr);
|
|
6294
|
-
const
|
|
6295
|
-
const reads =
|
|
6296
|
-
if (writes.length !== 1 || !writes.every((write) => write.writeExpr === call) || reads.length !==
|
|
6485
|
+
const members2 = variable.references.flatMap((reference) => reference.writeExpr ? [] : [reference.identifier.parent]);
|
|
6486
|
+
const reads = members2.filter((member) => isMemberExpression(member) && readsMethod(member, method));
|
|
6487
|
+
if (writes.length !== 1 || !writes.every((write) => write.writeExpr === call) || reads.length !== members2.length || !DECLARATION_KINDS.has(String(Reflect.get(declaration, "kind"))) || declaration.parent.type === "ExportNamedDeclaration") {
|
|
6297
6488
|
return void 0;
|
|
6298
6489
|
}
|
|
6299
6490
|
const annotation = annotationEdit(context, declarator.id, method);
|
|
@@ -6725,6 +6916,7 @@ var rules = {
|
|
|
6725
6916
|
"no-relative-mock-under-builder": noRelativeMockUnderBuilder,
|
|
6726
6917
|
"no-real-wait-in-test": noRealWaitInTest,
|
|
6727
6918
|
"no-disabled-testbed-teardown": noDisabledTestbedTeardown,
|
|
6919
|
+
"no-inline-test-data": noInlineTestData,
|
|
6728
6920
|
...jasmineRules
|
|
6729
6921
|
};
|
|
6730
6922
|
|
|
@@ -6786,7 +6978,8 @@ var recommendedRules = {
|
|
|
6786
6978
|
[`${PLUGIN_NAME}/no-outer-binding-in-mock-factory`]: "error",
|
|
6787
6979
|
[`${PLUGIN_NAME}/no-relative-mock-under-builder`]: "error",
|
|
6788
6980
|
[`${PLUGIN_NAME}/no-real-wait-in-test`]: "warn",
|
|
6789
|
-
[`${PLUGIN_NAME}/no-disabled-testbed-teardown`]: "error"
|
|
6981
|
+
[`${PLUGIN_NAME}/no-disabled-testbed-teardown`]: "error",
|
|
6982
|
+
[`${PLUGIN_NAME}/no-inline-test-data`]: "warn"
|
|
6790
6983
|
};
|
|
6791
6984
|
var typeErrorRules = {
|
|
6792
6985
|
[`${PLUGIN_NAME}/prefer-as-spy`]: "error",
|
package/dist/perf-reporter.d.ts
CHANGED
|
@@ -64,6 +64,8 @@ interface PerfFile {
|
|
|
64
64
|
readonly setupFetch?: number;
|
|
65
65
|
/** Failed attempts before a pass, summed over the file's tests. */
|
|
66
66
|
readonly retries?: number;
|
|
67
|
+
/** Set only by `mergeRuns`, when the merged reports ran different configs: this file's, as `PerfRun.configFile`. */
|
|
68
|
+
readonly configFile?: string;
|
|
67
69
|
}
|
|
68
70
|
/** What the run was configured with, read from Vitest's resolved config rather than the config text. */
|
|
69
71
|
interface PerfConfig {
|
|
@@ -108,6 +110,8 @@ interface PerfRun {
|
|
|
108
110
|
readonly hung?: boolean;
|
|
109
111
|
/** Milliseconds coverage took after the last file: generating the map, then writing the reports. */
|
|
110
112
|
readonly coverage?: number;
|
|
113
|
+
/** The Vitest config file the run loaded, relative to `root` with `/` separators; absent without one. */
|
|
114
|
+
readonly configFile?: string;
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
interface PerfDiagnostic {
|
|
@@ -208,6 +212,12 @@ interface PerfVitest {
|
|
|
208
212
|
readonly workersSpawned?: number;
|
|
209
213
|
};
|
|
210
214
|
readonly projects?: readonly PerfProject[];
|
|
215
|
+
/** Vite's resolved config holds the file Vitest loaded, on Vitest 4 and 5 alike; `configFile` is unset without one. */
|
|
216
|
+
readonly vite?: {
|
|
217
|
+
readonly config?: {
|
|
218
|
+
readonly configFile?: string | undefined;
|
|
219
|
+
};
|
|
220
|
+
};
|
|
211
221
|
}
|
|
212
222
|
declare class PerfReporter {
|
|
213
223
|
#private;
|
package/dist/perf-reporter.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { join, dirname, sep, basename } from 'node:path';
|
|
1
|
+
import { join, dirname, relative, sep, basename } from 'node:path';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
import { mkdirSync, writeFileSync, renameSync, rmSync, realpathSync, statSync } from 'node:fs';
|
|
4
4
|
|
|
@@ -39,7 +39,7 @@ var PERF_OUTPUT_ENV = "VITEST_AUTO_SPY_PERF_OUT";
|
|
|
39
39
|
var PERF_PROFILE_ENV = "VITEST_AUTO_SPY_PERF_PROFILE";
|
|
40
40
|
var PERF_ISOLATE_ENV = "VITEST_AUTO_SPY_PERF_ISOLATE";
|
|
41
41
|
var AUTO_SPY_META_KEY = "autoSpyMs";
|
|
42
|
-
var PERF_FORMAT_VERSION =
|
|
42
|
+
var PERF_FORMAT_VERSION = 6;
|
|
43
43
|
var CASE_FLOOR_MS = 100;
|
|
44
44
|
var CASES_PER_FILE = 5;
|
|
45
45
|
|
|
@@ -208,6 +208,10 @@ function configOf(vitest) {
|
|
|
208
208
|
...provided === void 0 ? {} : { provided: Object.keys(provided).filter((key) => provided[key] === true) }
|
|
209
209
|
};
|
|
210
210
|
}
|
|
211
|
+
function configFileOf(vitest) {
|
|
212
|
+
const file = vitest.vite?.config?.configFile;
|
|
213
|
+
return file === void 0 ? void 0 : toPosix(relative(vitest.config.root, file));
|
|
214
|
+
}
|
|
211
215
|
function startupOf(vitest) {
|
|
212
216
|
const { startupTime, workersSpawned } = vitest.state;
|
|
213
217
|
return startupTime === void 0 || workersSpawned === void 0 ? void 0 : { ms: startupTime, workers: workersSpawned };
|
|
@@ -232,9 +236,11 @@ var PerfReporter = class {
|
|
|
232
236
|
#ended;
|
|
233
237
|
#coverage;
|
|
234
238
|
#hung = false;
|
|
239
|
+
#configFile;
|
|
235
240
|
onInit(vitest) {
|
|
236
241
|
this.#vitest = vitest;
|
|
237
242
|
this.#start = Date.now();
|
|
243
|
+
this.#configFile = configFileOf(vitest);
|
|
238
244
|
if (target() === void 0 && !profiling()) {
|
|
239
245
|
return;
|
|
240
246
|
}
|
|
@@ -349,7 +355,8 @@ var PerfReporter = class {
|
|
|
349
355
|
...partial ? { partial: true } : {},
|
|
350
356
|
...end === void 0 ? {} : { end },
|
|
351
357
|
...this.#hung ? { hung: true } : {},
|
|
352
|
-
...this.#coverage === void 0 ? {} : { coverage: this.#coverage }
|
|
358
|
+
...this.#coverage === void 0 ? {} : { coverage: this.#coverage },
|
|
359
|
+
...this.#configFile === void 0 ? {} : { configFile: this.#configFile }
|
|
353
360
|
};
|
|
354
361
|
}
|
|
355
362
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitest-auto-spy",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.54.0",
|
|
4
4
|
"description": "Auto-generate fully-typed test spies from a class — across Vitest, Bun, node:test and Rstest, with Angular/NestJS/React/Vue/Svelte recipes. Drop-in replacement for jest-auto-spies and jasmine-auto-spies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auto-mock",
|
|
@@ -302,6 +302,8 @@
|
|
|
302
302
|
"test:happy-dom": "vitest run --config vitest.happy-dom.config.mts",
|
|
303
303
|
"test:invariants": "vitest run --config vitest.invariants.config.mts",
|
|
304
304
|
"test:types": "vitest run --config vitest.types.config.mts",
|
|
305
|
+
"test:mutation": "stryker run --incremental",
|
|
306
|
+
"test:mutation:survivors": "node scripts/mutation-survivors.mjs",
|
|
305
307
|
"types:budget": "node scripts/check-type-budget.mjs",
|
|
306
308
|
"test:node": "node --import ./scripts/node-ts-resolve.mjs --test \"src/node-tests/*.node.test.ts\"",
|
|
307
309
|
"test:bun": "bun test ./src/bun-tests/core.bun.test.ts ./src/bun-tests/rxjs.bun.test.ts ./src/bun-tests/jasmine.bun.test.ts ./src/bun-tests/console-nest.bun.test.ts ./src/bun-tests/adopt-mock.bun.test.ts ./src/bun-tests/adapter-contract.bun.test.ts",
|
|
@@ -388,6 +390,9 @@
|
|
|
388
390
|
"@eslint/js": "^10.0.1",
|
|
389
391
|
"@happy-dom/global-registrator": "^20.14.0",
|
|
390
392
|
"@rstest/core": "^0.11.12",
|
|
393
|
+
"@stryker-mutator/api": "10.0.0",
|
|
394
|
+
"@stryker-mutator/core": "10.0.0",
|
|
395
|
+
"@stryker-mutator/vitest-runner": "10.0.0",
|
|
391
396
|
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
|
392
397
|
"@types/jsdom": "^30.0.0",
|
|
393
398
|
"@types/node": "^24.13.3",
|
|
@@ -501,7 +501,7 @@ npx vitest-auto-spy codemod --verify # after a migration: anything the transfor
|
|
|
501
501
|
Most of this library's guarantees are type-level, so a green run that does not type-check is not
|
|
502
502
|
done. Report failures with their output rather than describing them as passing.
|
|
503
503
|
|
|
504
|
-
**After any `eslint --fix` over specs, run `npx tsc --noEmit`.** The fifty-
|
|
504
|
+
**After any `eslint --fix` over specs, run `npx tsc --noEmit`.** The fifty-seven rules in
|
|
505
505
|
`vitest-auto-spy/eslint-plugin` are lint, not typecheck: `no-mocked-for-spy` rewrites a declaration
|
|
506
506
|
to `Spy<T>` and cannot see what the name is assigned two lines below, so a clean lint pass is not
|
|
507
507
|
evidence that the types still hold. Where it cannot prove the rename it downgrades to a suggestion —
|
|
@@ -540,6 +540,8 @@ with `createMock<…>()`. A value outside the declared type on purpose keeps the
|
|
|
540
540
|
`// eslint-disable-next-line vitest-auto-spy/no-ts-expect-error-on-double -- <why>`.
|
|
541
541
|
`no-constant-expect` reports `expect(true).toBe(true)` and its relatives — assert on what the code
|
|
542
542
|
produced, or `expect.fail(…)` for a branch the test must not reach.
|
|
543
|
+
`no-inline-test-data` (`warn`) reports a data literal over 20 lines, or the same literal three times
|
|
544
|
+
in one spec — export it once from `<name>.mock.ts` next to the spec and import it there.
|
|
543
545
|
`no-redundant-smoke-test` reports the generated `it('should create', () => expect(x).toBeTruthy())`
|
|
544
546
|
where the block — its nested `describe`s counted — already has tests that run the same `beforeEach`:
|
|
545
547
|
they fail first on a subject that came back nullish, and say what they were doing. Delete it; keep it
|