vitest-auto-spy 5.26.0 → 5.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/dist/cli.js +144 -45
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -10,6 +10,32 @@ The latest released version here must always match the one published on
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [5.27.0] - 2026-09-23
14
+
15
+ ### Changed
16
+
17
+ - **The `doctor` and `codemod` repository scan honours the root `.gitignore` for directories.** The
18
+ hardcoded skip list only knew the usual build and store names, so a repository's own generated or
19
+ local output (`src/generated/`, `/reports`, `tmp-*/`) was read as source: counted in the report,
20
+ walked by the import graph and offered to `codemod --write`. The file is parsed directly, with no
21
+ `git` process and no dependency: comments, `!` negations with the last match winning, anchored and
22
+ unanchored patterns, `*`, `?`, `**` and bracket classes. Only the root file and only directories
23
+ are honoured — an ignored file is still listed. A pattern with a `\` escape or a POSIX class is
24
+ dropped, which only means scanning more, and a `!` rule that cannot be read disables the whole
25
+ file rather than prune a directory git keeps. `perf-merge`'s report listing is unaffected.
26
+
27
+ ### Fixed
28
+
29
+ - **The `tsconfig` glob check no longer reports a pattern it could never see matched.** A pattern
30
+ inside a skipped directory below the root (`libs/app/out-tsc/**/*.ts`) or inside a directory the
31
+ root `.gitignore` excludes is exempt, and so is a `files` entry there — both were reported as
32
+ matching nothing or missing, although the scan had simply not descended into them.
33
+ - **The `prefer-create-spy-from-class` reference lists every factory whose arguments are exempt.**
34
+ The built-in doubles and stubs (`createComponentStub`, `createDirectiveHost`, `createWindowDouble`,
35
+ `provideRouterDouble` and the rest) were exempt in the rule but missing from the docs, which also
36
+ still said "four shapes" over a list of eleven. The Russian page gained the four exemptions it was
37
+ missing (`vi.hoisted`, the options bag, `createFixture`, an RxJS observer).
38
+
13
39
  ## [5.26.0] - 2026-09-23
14
40
 
15
41
  ### Changed
@@ -7770,7 +7796,8 @@ by hand there, in more than one place, by more than one person.
7770
7796
  `mockAccessorsProp`.
7771
7797
  - Dual ESM + CJS build with type declarations; 100% test coverage.
7772
7798
 
7773
- [Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.26.0...HEAD
7799
+ [Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.27.0...HEAD
7800
+ [5.27.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.26.0...v5.27.0
7774
7801
  [5.26.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.25.0...v5.26.0
7775
7802
  [5.25.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.24.0...v5.25.0
7776
7803
  [5.24.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.23.0...v5.24.0
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { resolve, join, dirname, relative, posix, isAbsolute, sep } from 'node:path';
3
- import { readFileSync, statSync, mkdirSync, writeFileSync, readdirSync, existsSync, lstatSync, rmSync } from 'node:fs';
2
+ import { resolve, join, dirname, posix, isAbsolute, relative, sep } from 'node:path';
3
+ import { readFileSync, statSync, mkdirSync, writeFileSync, existsSync, lstatSync, rmSync, readdirSync } from 'node:fs';
4
4
  import { createHash } from 'node:crypto';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { createRequire } from 'node:module';
@@ -98,6 +98,84 @@ function flagEnabled(args, name) {
98
98
  }
99
99
  return value !== "false";
100
100
  }
101
+
102
+ // src/cli/gitignore.ts
103
+ function parseGitignore(text) {
104
+ const rules = [];
105
+ for (const raw of text.split("\n")) {
106
+ const line = raw.trimEnd();
107
+ if (line === "" || line.startsWith("#")) {
108
+ continue;
109
+ }
110
+ const negated = line.startsWith("!");
111
+ const pattern = toRegExp(negated ? line.slice(1) : line);
112
+ if (pattern !== void 0) {
113
+ rules.push({ negated, pattern });
114
+ } else if (negated) {
115
+ return void 0;
116
+ }
117
+ }
118
+ return rules;
119
+ }
120
+ function isIgnoredDirectory(rules, path) {
121
+ let ignored = false;
122
+ for (const rule of rules) {
123
+ if (rule.pattern.test(path)) {
124
+ ignored = !rule.negated;
125
+ }
126
+ }
127
+ return ignored;
128
+ }
129
+ function toRegExp(line) {
130
+ const body = line.replace(/\/+$/, "");
131
+ if (body === "" || line.includes("\\")) {
132
+ return void 0;
133
+ }
134
+ const anchored = body.includes("/");
135
+ const segments = (body.startsWith("/") ? body.slice(1) : body).split("/");
136
+ let source = "";
137
+ for (const [index, segment] of segments.entries()) {
138
+ const last = index === segments.length - 1;
139
+ if (segment === "**") {
140
+ source += last ? ".*" : "(?:[^/]+/)*";
141
+ continue;
142
+ }
143
+ const converted = segmentSource(segment);
144
+ if (converted === void 0) {
145
+ return void 0;
146
+ }
147
+ source += last ? converted : `${converted}/`;
148
+ }
149
+ try {
150
+ return new RegExp(`^${anchored ? "" : "(?:.*/)?"}${source}$`);
151
+ } catch {
152
+ return void 0;
153
+ }
154
+ }
155
+ function segmentSource(segment) {
156
+ let source = "";
157
+ let index = 0;
158
+ while (index < segment.length) {
159
+ const char = segment.charAt(index);
160
+ if (char === "[") {
161
+ const negated = /^[!^]/.test(segment.slice(index + 1));
162
+ const start = index + (negated ? 2 : 1);
163
+ const end = segment.indexOf("]", start + 1);
164
+ const members = segment.slice(start, end);
165
+ if (end === -1 || members.includes("[")) {
166
+ return void 0;
167
+ }
168
+ source += `[${negated ? "^" : ""}${members.replace(/[\]^]/g, "\\$&")}]`;
169
+ index = end + 1;
170
+ continue;
171
+ }
172
+ source += char === "*" ? "[^/]*" : char === "?" ? "[^/]" : char.replace(/[$()+.^{|}]/, "\\$&");
173
+ index += 1;
174
+ }
175
+ return source;
176
+ }
177
+
178
+ // src/cli/fs-scan.ts
101
179
  var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
102
180
  ".angular",
103
181
  ".bun",
@@ -174,18 +252,25 @@ function removeFile(path) {
174
252
  rmSync(path, { force: true });
175
253
  }
176
254
  function listRepositoryFiles(root, limit = scanCap()) {
177
- return scanRepository(root, limit).files;
255
+ return scan(root, limit, []).files;
178
256
  }
179
257
  function scanRepository(root, limit = scanCap()) {
180
- const found = [];
181
- const truncated = walk(root, root, found, limit);
182
- return { files: found.sort(), truncated };
258
+ return scan(root, limit, readGitignoreRules(root));
259
+ }
260
+ function scan(root, limit, rules) {
261
+ const walker = { root, rules, found: [], ignored: [], limit };
262
+ const truncated = walk(walker, root);
263
+ return { files: walker.found.sort(), ignored: walker.ignored.sort(), truncated };
264
+ }
265
+ function readGitignoreRules(root) {
266
+ const text = readTextFile(join(root, ".gitignore"));
267
+ return (text === void 0 ? void 0 : parseGitignore(text)) ?? [];
183
268
  }
184
269
  function isRepositoryRoot(directory) {
185
270
  return existsSync(join(directory, ".git"));
186
271
  }
187
- function walk(root, directory, found, limit) {
188
- if (found.length >= limit) {
272
+ function walk(walker, directory) {
273
+ if (walker.found.length >= walker.limit) {
189
274
  return true;
190
275
  }
191
276
  let entries;
@@ -195,22 +280,30 @@ function walk(root, directory, found, limit) {
195
280
  return false;
196
281
  }
197
282
  for (const entry of entries) {
198
- if (found.length >= limit) {
283
+ if (walker.found.length >= walker.limit) {
199
284
  return true;
200
285
  }
201
286
  const full = join(directory, entry.name);
202
287
  if (entry.isDirectory()) {
203
- if (!isSkippedDirectory(entry.name) && !isRepositoryRoot(full) && walk(root, full, found, limit)) {
288
+ if (!isSkippedDirectory(entry.name) && !isRepositoryRoot(full) && !isGitignored(walker, full) && walk(walker, full)) {
204
289
  return true;
205
290
  }
206
291
  continue;
207
292
  }
208
293
  if (entry.isFile()) {
209
- found.push(toPosix(relative(root, full)));
294
+ walker.found.push(toPosix(relative(walker.root, full)));
210
295
  }
211
296
  }
212
297
  return false;
213
298
  }
299
+ function isGitignored(walker, directory) {
300
+ const path = toPosix(relative(walker.root, directory));
301
+ if (!isIgnoredDirectory(walker.rules, path)) {
302
+ return false;
303
+ }
304
+ walker.ignored.push(path);
305
+ return true;
306
+ }
214
307
  function parseJsonc(text) {
215
308
  try {
216
309
  return JSON.parse(stripJsonComments(text));
@@ -640,7 +733,7 @@ function readProfile(cwd) {
640
733
  const scripts = stringMap(packageJson["scripts"]);
641
734
  const runner = detectRunner(dependencies, scripts);
642
735
  const framework = detectFramework(dependencies);
643
- const scan2 = scanRepository(cwd);
736
+ const scan3 = scanRepository(cwd);
644
737
  return {
645
738
  cwd,
646
739
  runner,
@@ -651,8 +744,9 @@ function readProfile(cwd) {
651
744
  setupFiles: detectSetupFiles(cwd),
652
745
  dependencies,
653
746
  scripts,
654
- files: scan2.files,
655
- filesTruncated: scan2.truncated
747
+ files: scan3.files,
748
+ filesTruncated: scan3.truncated,
749
+ ignoredDirectories: scan3.ignored
656
750
  };
657
751
  }
658
752
  var PACKAGE_NAME = "vitest-auto-spy";
@@ -1059,7 +1153,7 @@ function textOf(context, range) {
1059
1153
  function group(groups, position) {
1060
1154
  return groups.slice(position, position + 1).join("");
1061
1155
  }
1062
- function scan(masked, pattern) {
1156
+ function scan2(masked, pattern) {
1063
1157
  return [...masked.matchAll(pattern)].map((match) => ({ index: match.index, whole: group(match, 0), groups: [...match] }));
1064
1158
  }
1065
1159
 
@@ -1139,7 +1233,7 @@ function replacement(start, end, text) {
1139
1233
  function replacements(context, pattern, text) {
1140
1234
  return {
1141
1235
  ...EMPTY_OUTPUT,
1142
- edits: scan(context.masked, pattern).map((match) => ({
1236
+ edits: scan2(context.masked, pattern).map((match) => ({
1143
1237
  start: match.index,
1144
1238
  end: match.index + match.whole.length,
1145
1239
  text: text(match)
@@ -1182,7 +1276,7 @@ var jasmineStrategies = {
1182
1276
  function withArgsOutput(context) {
1183
1277
  const closes = spyOnCloses(context);
1184
1278
  return mergeOutputs(
1185
- scan(context.masked, WITH_ARGS).map(
1279
+ scan2(context.masked, WITH_ARGS).map(
1186
1280
  (match) => closes.has(lastCodeBefore(context.masked, match.index)) ? { ...EMPTY_OUTPUT, notes: [withArgsOnSpyOnNote(context, match.index)] } : replacement(match.index, match.index + match.whole.length, ".calledWith(")
1187
1281
  )
1188
1282
  );
@@ -1205,7 +1299,7 @@ function lastCodeBefore(masked, index) {
1205
1299
  }
1206
1300
  function strategyOutputs(context) {
1207
1301
  return mergeOutputs(
1208
- scan(context.masked, AND_STRATEGY).flatMap((match) => {
1302
+ scan2(context.masked, AND_STRATEGY).flatMap((match) => {
1209
1303
  const name = group(match.groups, 1);
1210
1304
  return HELPER_NAMES.has(name) ? [] : [strategyOutput(context, match, name)];
1211
1305
  })
@@ -1279,12 +1373,12 @@ var jasmineSpyOn = {
1279
1373
  residue: /(?:^|[^\w$.])spyOn(?:Property)?\s*\(/,
1280
1374
  run: (context) => ({
1281
1375
  ...EMPTY_OUTPUT,
1282
- edits: scan(context.masked, SPY_ON).flatMap((match) => spyOnEdits(context, match))
1376
+ edits: scan2(context.masked, SPY_ON).flatMap((match) => spyOnEdits(context, match))
1283
1377
  })
1284
1378
  };
1285
1379
  function spyOnCloses(context) {
1286
1380
  return new Set(
1287
- scan(context.masked, SPY_ON).flatMap((match) => {
1381
+ scan2(context.masked, SPY_ON).flatMap((match) => {
1288
1382
  const range = spyOnRange(context, match);
1289
1383
  return range === void 0 ? [] : [range[1]];
1290
1384
  })
@@ -1517,7 +1611,7 @@ var jasmineGlobals = {
1517
1611
  // leftover `jasmine.SpyObj` reported twice would name a transform that never intended to touch it.
1518
1612
  residue: /\bjasmine\s*\.\s*(?!Spy(?:Obj)?\b)/,
1519
1613
  run: (context) => mergeOutputs(
1520
- scan(context.masked, MEMBER).flatMap((match) => {
1614
+ scan2(context.masked, MEMBER).flatMap((match) => {
1521
1615
  const name = group(match.groups, 1);
1522
1616
  return TYPE_TARGETS[name] === void 0 ? [globalOutput(context, match, name)] : [];
1523
1617
  })
@@ -1592,7 +1686,7 @@ var jasmineTypes = {
1592
1686
  summary: "jasmine.Spy \u2192 Mock from vitest, and jasmine.SpyObj<T> \u2192 Spy<T> from this package.",
1593
1687
  residue: /\bjasmine\s*\.\s*Spy(?:Obj)?\b/,
1594
1688
  run: (context) => mergeOutputs(
1595
- scan(context.masked, MEMBER).flatMap((match) => {
1689
+ scan2(context.masked, MEMBER).flatMap((match) => {
1596
1690
  const target = TYPE_TARGETS[group(match.groups, 1)];
1597
1691
  return target === void 0 ? [] : [typeOutput(context, match, target)];
1598
1692
  })
@@ -1630,11 +1724,11 @@ var jasmineMatchers = {
1630
1724
  ([from, to]) => replacements(context, new RegExp(String.raw`\.\s*${from}\s*\(`, "g"), () => `.${to}(`)
1631
1725
  ),
1632
1726
  { ...EMPTY_OUTPUT, edits: failEdits(context) },
1633
- { ...EMPTY_OUTPUT, edits: scan(context.masked, EXPECT).flatMap((match) => withContextEdits(context, match)) }
1727
+ { ...EMPTY_OUTPUT, edits: scan2(context.masked, EXPECT).flatMap((match) => withContextEdits(context, match)) }
1634
1728
  ])
1635
1729
  };
1636
1730
  function failEdits(context) {
1637
- return scan(context.masked, FAIL).filter((match) => isCallPosition(context.masked, match.index + match.whole.length - 1)).map((match) => ({
1731
+ return scan2(context.masked, FAIL).filter((match) => isCallPosition(context.masked, match.index + match.whole.length - 1)).map((match) => ({
1638
1732
  start: match.index,
1639
1733
  end: match.index + match.whole.length,
1640
1734
  text: `${group(match.groups, 1)}expect.fail(`
@@ -1775,7 +1869,7 @@ var jestNamespace = {
1775
1869
  summary: "jest.<member> \u2192 vi.<member>, reporting the members with no vi twin instead of renaming them.",
1776
1870
  residue: /\bjest\s*\./,
1777
1871
  run: (context) => collect(
1778
- scan(context.masked, MEMBER2).flatMap((match) => {
1872
+ scan2(context.masked, MEMBER2).flatMap((match) => {
1779
1873
  const name = group(match.groups, 1);
1780
1874
  return TYPE_NAMES[name] === void 0 ? [memberOutput(context, match, name)] : [];
1781
1875
  })
@@ -1847,7 +1941,7 @@ var jestTypes = {
1847
1941
  run: (context) => {
1848
1942
  const functionFirst = importsJestGlobals(context);
1849
1943
  return collect([
1850
- ...scan(context.masked, MEMBER2).flatMap((match) => {
1944
+ ...scan2(context.masked, MEMBER2).flatMap((match) => {
1851
1945
  const name = group(match.groups, 1);
1852
1946
  const target = TYPE_NAMES[name];
1853
1947
  return target === void 0 ? [] : [typeOutput2(context, match, name, target, functionFirst)];
@@ -1863,7 +1957,7 @@ var GENERIC_CALL = /\bjest\s*\.\s*(fn|spyOn)\s*</g;
1863
1957
  function genericCallNotes(context) {
1864
1958
  return {
1865
1959
  ...EMPTY_OUTPUT,
1866
- notes: scan(context.masked, GENERIC_CALL).map((match) => unreadableTypeArguments(context, match.index, group(match.groups, 1)))
1960
+ notes: scan2(context.masked, GENERIC_CALL).map((match) => unreadableTypeArguments(context, match.index, group(match.groups, 1)))
1867
1961
  };
1868
1962
  }
1869
1963
  var NO_ARGUMENT = /\.\s*(mockImplementation(?:Once)?)\s*\(\s*\)/g;
@@ -1874,7 +1968,7 @@ var mockImplementationArity = {
1874
1968
  residue: /\.\s*mockImplementation(?:Once)?\s*\(\s*\)/,
1875
1969
  run: (context) => ({
1876
1970
  ...EMPTY_OUTPUT,
1877
- edits: scan(context.masked, NO_ARGUMENT).map((match) => ({
1971
+ edits: scan2(context.masked, NO_ARGUMENT).map((match) => ({
1878
1972
  start: match.index,
1879
1973
  end: match.index + match.whole.length,
1880
1974
  text: `.${group(match.groups, 1)}(() => undefined)`
@@ -1890,7 +1984,7 @@ var jasmineAliases = {
1890
1984
  run: (context) => ({
1891
1985
  ...EMPTY_OUTPUT,
1892
1986
  edits: Object.entries(JASMINE_ALIASES).flatMap(
1893
- ([alias, replacement2]) => scan(context.masked, new RegExp(`(^|[^\\w$.])${alias}\\s*\\(`, "g")).filter((match) => isCallPosition(context.masked, match.index + match.whole.length - 1)).map((match) => {
1987
+ ([alias, replacement2]) => scan2(context.masked, new RegExp(`(^|[^\\w$.])${alias}\\s*\\(`, "g")).filter((match) => isCallPosition(context.masked, match.index + match.whole.length - 1)).map((match) => {
1894
1988
  const start = match.index + group(match.groups, 1).length;
1895
1989
  return { start, end: start + alias.length, text: replacement2 };
1896
1990
  })
@@ -1911,7 +2005,7 @@ var jestGlobalsImport = {
1911
2005
  function globalsEdits(context, statement) {
1912
2006
  const specifierAt = context.source.indexOf(JEST_GLOBALS, statement.start);
1913
2007
  const [open, close] = statement.braces ?? [statement.start, statement.start];
1914
- const renames = scan(context.masked.slice(open, close), /\bjest\b/g).map((match) => ({
2008
+ const renames = scan2(context.masked.slice(open, close), /\bjest\b/g).map((match) => ({
1915
2009
  start: open + match.index,
1916
2010
  end: open + match.index + "jest".length,
1917
2011
  text: "vi"
@@ -2112,7 +2206,7 @@ var injectCast = {
2112
2206
  residue: /\bas\s+Spy\s*</,
2113
2207
  run: (context) => {
2114
2208
  const helper = context.entries === void 0 ? void 0 : entryFor(context.entries, "asSpy", context.preferredEntry);
2115
- const casts = scan(context.masked, INJECT).flatMap((match) => {
2209
+ const casts = scan2(context.masked, INJECT).flatMap((match) => {
2116
2210
  const cast = castAt(context, match.index, match.whole.length);
2117
2211
  return cast === void 0 ? [] : [cast];
2118
2212
  });
@@ -2124,7 +2218,7 @@ var injectCast = {
2124
2218
  };
2125
2219
  function withHelper(context, casts, need) {
2126
2220
  const covered = new Set(casts.map((cast) => cast.angle));
2127
- const strays = scan(context.masked, ANY_SPY_CAST).filter((match) => !covered.has(match.index + match.whole.length - 1)).map((match) => castNote(context, match.index));
2221
+ const strays = scan2(context.masked, ANY_SPY_CAST).filter((match) => !covered.has(match.index + match.whole.length - 1)).map((match) => castNote(context, match.index));
2128
2222
  return {
2129
2223
  edits: casts.map((cast) => cast.edit),
2130
2224
  needs: casts.length === 0 ? [] : [need],
@@ -2152,7 +2246,7 @@ var TRANSFORMS = [
2152
2246
  function residueOf(file, text, transforms) {
2153
2247
  const masked = maskComments(text);
2154
2248
  return transforms.flatMap(
2155
- (transform) => scan(masked, new RegExp(transform.residue.source, `${transform.residue.flags.replace("g", "")}g`)).filter((match) => transform.residueIgnores?.(masked, match) !== true).map((match) => residueNote(file, text, match, transform))
2249
+ (transform) => scan2(masked, new RegExp(transform.residue.source, `${transform.residue.flags.replace("g", "")}g`)).filter((match) => transform.residueIgnores?.(masked, match) !== true).map((match) => residueNote(file, text, match, transform))
2156
2250
  );
2157
2251
  }
2158
2252
  function residueNote(file, text, match, transform) {
@@ -2911,7 +3005,7 @@ function checkForeignPragma(graph) {
2911
3005
  }
2912
3006
 
2913
3007
  // src/cli/checks/export-map.generated.ts
2914
- var EXPORT_MAP_VERSION = "5.25.0";
3008
+ var EXPORT_MAP_VERSION = "5.26.0";
2915
3009
  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";
2916
3010
  var EXPORTED_BY = {
2917
3011
  AccessorImplementations: "0 1 2 3 4 12 20 21 22",
@@ -3787,10 +3881,14 @@ function expandInclude(pattern) {
3787
3881
  const base = last === "*" ? trimmed2 : `${trimmed2}${last === "**" ? "/*" : "/**/*"}`;
3788
3882
  return IMPLIED_EXTENSIONS.map((extension) => `${base}${extension}`);
3789
3883
  }
3790
- function isExemptPattern(pattern) {
3791
- const slash = pattern.indexOf("/");
3792
- const root = slash === -1 ? pattern : pattern.slice(0, slash);
3793
- return /\.d\.[cm]?ts$/.test(pattern) || isSkippedDirectory(root);
3884
+ function isExemptPattern(pattern, ignoredDirectories = []) {
3885
+ return /\.d\.[cm]?ts$/.test(pattern) || isOutsideScan(pattern, ignoredDirectories);
3886
+ }
3887
+ function isOutsideScan(path, ignoredDirectories) {
3888
+ const segments = path.split("/");
3889
+ const wildcard = segments.findIndex((segment) => /[*?[]/.test(segment));
3890
+ const literal = wildcard === -1 ? segments : segments.slice(0, wildcard);
3891
+ return literal.some(isSkippedDirectory) || ignoredDirectories.some((directory) => `${path}/`.startsWith(`${directory}/`));
3794
3892
  }
3795
3893
  function literalTail(pattern) {
3796
3894
  const last = pattern.slice(pattern.lastIndexOf("/") + 1);
@@ -3816,12 +3914,13 @@ function stringList(value) {
3816
3914
  function resolveFromConfig(configPath, entry) {
3817
3915
  return posix.normalize(posix.join(posix.dirname(configPath), entry)).replace(/^\.\//, "");
3818
3916
  }
3819
- function checkInclude(configPath, entry, files) {
3917
+ function checkInclude(configPath, entry, profile) {
3918
+ const files = profile.files;
3820
3919
  if (entry.includes("${") || entry.startsWith("/")) {
3821
3920
  return void 0;
3822
3921
  }
3823
3922
  const resolved = resolveFromConfig(configPath, entry);
3824
- if (isExemptPattern(resolved) || matchesAnyFile(resolved, files)) {
3923
+ if (isExemptPattern(resolved, profile.ignoredDirectories) || matchesAnyFile(resolved, files)) {
3825
3924
  return void 0;
3826
3925
  }
3827
3926
  const tail = literalTail(resolved);
@@ -3842,16 +3941,16 @@ function checkInclude(configPath, entry, files) {
3842
3941
  fix: "A pattern that matches nothing type-checks nothing, and `tsc --noEmit` still reports zero errors. Fix the glob or delete the entry."
3843
3942
  };
3844
3943
  }
3845
- function checkOne(cwd, configPath, files) {
3846
- const text = readTextFile(join(cwd, configPath));
3944
+ function checkOne(profile, configPath) {
3945
+ const text = readTextFile(join(profile.cwd, configPath));
3847
3946
  const parsed = text === void 0 ? void 0 : parseJsonc(text);
3848
3947
  if (!isRecord(parsed)) {
3849
3948
  return [];
3850
3949
  }
3851
- const findings = stringList(parsed["include"]).flatMap((entry) => checkInclude(configPath, entry, files) ?? []);
3950
+ const findings = stringList(parsed["include"]).flatMap((entry) => checkInclude(configPath, entry, profile) ?? []);
3852
3951
  for (const entry of stringList(parsed["files"])) {
3853
3952
  const resolved = resolveFromConfig(configPath, entry);
3854
- if (!files.includes(resolved)) {
3953
+ if (!profile.files.includes(resolved) && !isOutsideScan(resolved, profile.ignoredDirectories)) {
3855
3954
  findings.push({
3856
3955
  check: "tsconfig-file-missing",
3857
3956
  severity: "error",
@@ -3865,7 +3964,7 @@ function checkOne(cwd, configPath, files) {
3865
3964
  }
3866
3965
  function checkTsconfigGlobs(profile) {
3867
3966
  const configs = profile.files.filter((file) => TSCONFIG_NAME2.test(file));
3868
- return configs.flatMap((config) => checkOne(profile.cwd, config, profile.files));
3967
+ return configs.flatMap((config) => checkOne(profile, config));
3869
3968
  }
3870
3969
 
3871
3970
  // src/cli/checks/unawaited-helper.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitest-auto-spy",
3
- "version": "5.26.0",
3
+ "version": "5.27.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",