vitest-auto-spy 5.51.0 → 5.53.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 CHANGED
@@ -10,6 +10,46 @@ The latest released version here must always match the one published on
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [5.53.0] - 2026-09-29
14
+
15
+ ### Added
16
+
17
+ - **`no-inline-test-data`, in `recommended` as `warn`**: a data literal longer than 20 lines, or
18
+ the same literal written three times in one spec, belongs in a `*.mock.ts` file next to the spec.
19
+ `maxLines`, `repeats` and `minValues` tune it. Module wiring (`providers`, `imports`…), `it.each`
20
+ tables, `vi.mock` factories, literals holding a function and the mock files themselves are never
21
+ reported. On an 886-file Angular suite it flags 215 files at the defaults.
22
+ `/eslint-plugin` grows by 1.50 kB (50.96 → 52.46 kB, +2.9 %): 1.08 kB for this rule, the rest for
23
+ reading `externalDependencies` below.
24
+
25
+ ### Fixed
26
+
27
+ - **`no-relative-mock-under-builder` reads `externalDependencies` of the target's `buildTarget`.** The
28
+ builder builds with the options `context.getTargetOptions` returns for `buildTarget` (the target's
29
+ options, then each named configuration over them) and leaves those specifiers out of the bundle, so
30
+ a `vi.mock` of such a path alias does replace the module. The rule now reports an alias only when
31
+ some unit-test run of the file builds without it; esbuild's matching applies (exact name, a subpath,
32
+ one `*`). A relative specifier is still reported: the builder throws on it whatever is external. On
33
+ an 886-file Angular suite that externalises its mocked utilities this removes 69 false reports.
34
+ - **`no-outer-binding-in-mock-factory` treats an instance field initialiser as deferred.**
35
+ `class { listen = mockListen; }` returned from a factory reads `mockListen` when a test constructs
36
+ the class, not while the factory runs. A static field, a computed key and a static block still run
37
+ with the class declaration and are reported.
38
+
39
+ ## [5.52.0] - 2026-09-29
40
+
41
+ ### Added
42
+
43
+ - **`init` says when git will not show what it wrote.** A created or updated file that is ignored
44
+ and not tracked (`.git/info/exclude`, a global excludes file) ends its row with _not tracked by
45
+ git, so `git diff` will not show this change_, and an empty `git diff` no longer reads as "nothing
46
+ changed".
47
+
48
+ ### Documentation
49
+
50
+ - **When `prepareShallow` pays off**: only when the tests share non-empty `providers` or `imports`.
51
+ With a different `inputs` per call, or none, it only renames `renderShallow`.
52
+
13
53
  ## [5.51.0] - 2026-09-29
14
54
 
15
55
  ### Changed
@@ -6617,7 +6657,9 @@ moved under 1 %: `/angular` +192 B (+1.0 %) for the override queue and the HTTP
6617
6657
  This copy ships with the package and carries 5.x only. Releases before 5.0.0 are in the
6618
6658
  [full changelog](https://github.com/ASDAlexey/vitest-auto-spy/blob/master/CHANGELOG.md).
6619
6659
 
6620
- [Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.51.0...HEAD
6660
+ [Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.53.0...HEAD
6661
+ [5.53.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.52.0...v5.53.0
6662
+ [5.52.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.51.0...v5.52.0
6621
6663
  [5.51.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.50.0...v5.51.0
6622
6664
  [5.50.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.49.0...v5.50.0
6623
6665
  [5.49.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.48.0...v5.49.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
- 56 ESLint rules.
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 56 lint rules that catch test code which passes but checks
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 |
@@ -267,6 +267,8 @@ describe renders the same component with the same providers, `prepareShallow(Com
267
267
  them once and each test calls `.create({ inputs: { … } })` — the overrides replace a key outright,
268
268
  one instance per test. `create({ providers })` replaces the prepared list; `create({ extraProviders,
269
269
  extraImports })` appends to it, so for one token the per-test provider wins (type `ShallowOverrides<T>`).
270
+ It pays off only on shared non-empty `providers` / `imports`: when each call passes its own `inputs`
271
+ or none, `prepareShallow(X)` only renames `renderShallow(X)` — keep `renderShallow`.
270
272
 
271
273
  ### Observers the component constructs itself
272
274
 
@@ -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 slips past both the builder and the rule |
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-six rules, **every one an `error` except `prefer-render-shallow`,
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` and `no-real-wait-in-test`**; eight fix on their own, nineteen offer suggestions. Fifty-two are syntactic; `no-private-member-access`, `no-mistyped-use-value`,
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 eleven `warn`s do not rank findings by importance. Each names something other than a defect: a
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`), or a preferred spelling
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/angular.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import './vitest-mock-types-B17-xaWt.js';
2
- export { w as AngularTokenProvider, A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, E as ElementConstructor, N as NativeElementHolder, P as PreparedShallow, R as RenderShallowOptions, c as ResourceStatusLike, d as RunCounter, S as SettleResourceOptions, e as ShallowOverrides, f as ShallowRender, g as SpyRegistry, h as StableOptions, i as createWithAutoSpies, j as flushEffects, k as hostElement, l as injectSpy, p as prepareShallow, m as provideAutoSpy, x as provideAutoSpyForToken, q as queryElement, r as renderShallow, n as runEffect, s as setInputs, o as settleResource, t as stable, u as trackEffectRuns, v as trackRecomputations } from './track-signal-runs-DDzbFmGc.js';
2
+ export { w as AngularTokenProvider, A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, E as ElementConstructor, N as NativeElementHolder, P as PreparedShallow, R as RenderShallowOptions, c as ResourceStatusLike, d as RunCounter, S as SettleResourceOptions, e as ShallowOverrides, f as ShallowRender, g as SpyRegistry, h as StableOptions, i as createWithAutoSpies, j as flushEffects, k as hostElement, l as injectSpy, p as prepareShallow, m as provideAutoSpy, x as provideAutoSpyForToken, q as queryElement, r as renderShallow, n as runEffect, s as setInputs, o as settleResource, t as stable, u as trackEffectRuns, v as trackRecomputations } from './track-signal-runs-CM-leczv.js';
3
3
  import { o as DeepPartial, m as ClassType, C as ClassSpyConfiguration, S as Spy, t as OnlyMethodKeysOf, h as AddSpyMethodsByReturnTypes, r as NotAPublicKey } from './types-qYoxQi5C.js';
4
4
  export { J as SubjectOf } from './types-qYoxQi5C.js';
5
5
  import { InjectionToken, Provider, Type, WritableSignal, Signal } from '@angular/core';
@@ -4,7 +4,7 @@ export { A as AutoMockConfiguration, a as AutoMocked, b as AutoSpyDefaultEntry,
4
4
  export { c as createFunctionSpy, d as describeDuplicateCopies, g as getPackageCopies } from './package-identity-COBYU0WJ.js';
5
5
  export { A as AccessorImplementations, O as OutsideHookReaction, R as RestoreProp, c as countMockedProps, m as mockAccessorsProp, a as mockReadonlyProp, b as mockReadonlyPropGetter, d as mockValueProp, r as reportPropsOutsideHooks, e as restoreMockedProps } from './prop-mock-CE9gNMPY.js';
6
6
  export { C as CreateNestUnitOptions, N as NestUnit, a as NestUnitClass, b as NestUnitProvider, c as NestUnitSpies, d as createNestUnit } from './nest-unit-Bg6JWPgu.js';
7
- export { A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, E as ElementConstructor, N as NativeElementHolder, P as PreparedShallow, R as RenderShallowOptions, c as ResourceStatusLike, d as RunCounter, S as SettleResourceOptions, e as ShallowOverrides, f as ShallowRender, g as SpyRegistry, h as StableOptions, i as createWithAutoSpies, j as flushEffects, k as hostElement, l as injectSpy, p as prepareShallow, m as provideAutoSpy, q as queryElement, r as renderShallow, n as runEffect, s as setInputs, o as settleResource, t as stable, u as trackEffectRuns, v as trackRecomputations } from './track-signal-runs-DDzbFmGc.js';
7
+ export { A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, E as ElementConstructor, N as NativeElementHolder, P as PreparedShallow, R as RenderShallowOptions, c as ResourceStatusLike, d as RunCounter, S as SettleResourceOptions, e as ShallowOverrides, f as ShallowRender, g as SpyRegistry, h as StableOptions, i as createWithAutoSpies, j as flushEffects, k as hostElement, l as injectSpy, p as prepareShallow, m as provideAutoSpy, q as queryElement, r as renderShallow, n as runEffect, s as setInputs, o as settleResource, t as stable, u as trackEffectRuns, v as trackRecomputations } from './track-signal-runs-CM-leczv.js';
8
8
  import '@angular/core';
9
9
  import '@angular/core/testing';
10
10
 
package/dist/cli.js CHANGED
@@ -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.50.0";
1657
+ var EXPORT_MAP_VERSION = "5.52.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",
@@ -3853,6 +3853,13 @@ Options
3853
3853
  -v, --version Print the installed version.
3854
3854
 
3855
3855
  Docs https://asdalexey.github.io/vitest-auto-spy`;
3856
+ function ignoredByGit(cwd, paths) {
3857
+ if (paths.length === 0) {
3858
+ return /* @__PURE__ */ new Set();
3859
+ }
3860
+ const result = spawnSync("git", ["check-ignore", "--", ...paths], { cwd, encoding: "utf8" });
3861
+ return new Set(result.status === 0 ? result.stdout.split("\n").filter((line) => line !== "") : []);
3862
+ }
3856
3863
  var MARKER_BEGIN = /<!-- vitest-auto-spy:begin[^>]*-->/;
3857
3864
  var MARKER_END = "<!-- vitest-auto-spy:end -->";
3858
3865
  function digest(body) {
@@ -4305,10 +4312,20 @@ function unmatchedWarnings(only) {
4305
4312
  return guess === void 0 ? `--only ${entry} selects no file init writes. Known targets: ${known.join(", ")}.` : `--only ${entry} selects no file init writes. Did you mean ${guess}?`;
4306
4313
  });
4307
4314
  }
4315
+ var UNTRACKED_NOTE = "not tracked by git, so `git diff` will not show this change";
4316
+ function markUntracked(cwd, actions) {
4317
+ const ignored = ignoredByGit(
4318
+ cwd,
4319
+ actions.filter((action) => action.status === "created" || action.status === "updated").map((action) => action.path)
4320
+ );
4321
+ return actions.map((action) => ignored.has(action.path) ? { ...action, note: `${action.note} \u2014 ${UNTRACKED_NOTE}` } : action);
4322
+ }
4308
4323
  var PENDING = /* @__PURE__ */ new Set(["created", "edited", "stale", "updated"]);
4309
4324
  function runInit(profile, version, options) {
4310
4325
  const plans = buildPlans(profile, version, options.only).map((plan) => options.uninstall ? uninstallPlan(plan) : guardHandEdited(plan));
4311
- const { actions, failures } = applyPlans(profile.cwd, plans, options);
4326
+ const applied = applyPlans(profile.cwd, plans, options);
4327
+ const actions = markUntracked(profile.cwd, applied.actions);
4328
+ const { failures } = applied;
4312
4329
  const pending = options.check && actions.some((action) => PENDING.has(action.status));
4313
4330
  return {
4314
4331
  actions,
@@ -109,14 +109,14 @@ function rootCall(node) {
109
109
  }
110
110
  return current;
111
111
  }
112
- function isRunnerCall(node, members) {
113
- return isMemberCall(node, RUNNERS, members);
112
+ function isRunnerCall(node, members2) {
113
+ return isMemberCall(node, RUNNERS, members2);
114
114
  }
115
- function isMemberCall(node, objects, members) {
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) && members.has(node.callee.property.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 targetConfigs(target, name, defaults, projectRoot, workspaceRoot) {
891
- const byName = recordAt(defaults, name);
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 base = blocks.reduce(
898
- (value, block) => block !== void 0 && "runnerConfig" in block ? block["runnerConfig"] : value,
899
- false
900
- );
901
- const overrides = Object.values(recordAt(target, "configurations") ?? {}).filter(isJson).filter((configuration) => "runnerConfig" in configuration).map((configuration) => configuration["runnerConfig"]);
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 projectConfigs(project, defaults, projectRoot, workspaceRoot) {
905
- const targets = recordAt(project, "architect") ?? recordAt(project, "targets") ?? {};
906
- return Object.entries(targets).flatMap(
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) ? projectConfigs(project, defaults, root, workspaceRoot) : [];
964
+ return contains(root, directory) ? projectRuns(project, root, workspace) : [];
936
965
  });
937
- const fromProject = projectDir === void 0 ? [] : projectConfigs(readJson(node_path.join(projectDir, "project.json")) ?? {}, defaults, projectDir, workspaceRoot);
966
+ const fromProject = projectDir === void 0 || ownProject === void 0 ? [] : projectRuns(ownProject, projectDir, workspace);
938
967
  return [...fromWorkspace, ...fromProject];
939
968
  }
940
- function builderConfigs(filename) {
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" || builderConfigs(context.filename).length > 0;
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, which the builder compiles into the test bundle before Vitest could replace it \u2014 no error, and the real module runs. Replace the dependency through TestBed instead \u2014 `provideAutoSpy(Service)`, or `overrideComponentProvider` for a component\u2019s own provider."
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 within(node, container) {
3240
- return node.range[0] >= container.range[0] && node.range[1] <= container.range[1];
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 && within(reference.identifier, factory) && !isDeferred(reference.identifier, factory)
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 members = references.flatMap(({ identifier }) => {
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 members.length > 0 && !members.includes("fail") && !references.some(({ identifier }) => isCallee(identifier));
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(members) {
3817
- return `CallExpression[callee.object.name="TestBed"][callee.property.name=/^(${[...members].join("|")})$/]`;
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 within2(node, container) {
3822
- return node.range[0] >= container.range[0] && node.range[1] <= container.range[1];
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) => within2(reset, suite));
4045
+ return resets.some((reset) => within3(reset, suite));
3855
4046
  }
3856
4047
  function runsFirst(override, injection, hook) {
3857
- return within2(override, injection) || within2(override, hook) && override.range[1] < injection.range[0];
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) => within2(override, setup) && override.range[0] > injection.range[1]);
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) => within2(override, suite) && !runsFirst(override, injection, hook));
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 members = [];
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
- members.unshift(name);
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 members = variable.references.flatMap((reference) => reference.writeExpr ? [] : [reference.identifier.parent]);
6295
- const reads = members.filter((member) => isMemberExpression(member) && readsMethod(member, method));
6296
- if (writes.length !== 1 || !writes.every((write) => write.writeExpr === call) || reads.length !== members.length || !DECLARATION_KINDS.has(String(Reflect.get(declaration, "kind"))) || declaration.parent.type === "ExportNamedDeclaration") {
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",
@@ -379,6 +379,9 @@ interface PreparedShallow<T> {
379
379
  * `TestBed.resetTestingModule()`, because Angular refuses a second `configureTestingModule` on an
380
380
  * already-instantiated module.
381
381
  *
382
+ * It pays off only when the tests share non-empty `providers` / `imports`. When each call passes its
383
+ * own `inputs` or none, it only renames `renderShallow(Component)`.
384
+ *
382
385
  * ```ts
383
386
  * const prepare = prepareShallow(TaskListComponent, { providers: [provideAutoSpy(TaskService)] });
384
387
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitest-auto-spy",
3
- "version": "5.51.0",
3
+ "version": "5.53.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-six rules in
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