vitest-auto-spy 5.27.0 → 5.28.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/AGENTS.md +3 -1
- package/CHANGELOG.md +34 -1
- package/README.md +9 -7
- package/dist/cli.js +118 -26
- package/dist/eslint-plugin.cjs +25 -1
- package/dist/perf-profiler.js +2 -2
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -4421,7 +4421,9 @@ Full reference: <https://asdalexey.github.io/vitest-auto-spy/utilities/cli>.
|
|
|
4421
4421
|
- **The scan does not descend into a nested repository or a git worktree.** A tree carrying
|
|
4422
4422
|
worktrees under it listed every file twice, so `doctor` reported each import graph in duplicate and
|
|
4423
4423
|
`codemod --write` would have rewritten specs on another branch. A `.git` entry is a stop, whether
|
|
4424
|
-
it is a directory (a nested clone) or a file (a worktree).
|
|
4424
|
+
it is a directory (a nested clone) or a file (a worktree). Directories git ignores are skipped too
|
|
4425
|
+
— every `.gitignore` from the scan root down, `.git/info/exclude` and the per-user
|
|
4426
|
+
`core.excludesFile`, directories only, so an ignored file is still listed. Past 50 000 files the scan still
|
|
4425
4427
|
truncates, and `doctor` reports that as a `scan-cap-reached` warning (exit 1) rather than a clean
|
|
4426
4428
|
result; `VITEST_AUTO_SPY_SCAN_CAP` raises the cap.
|
|
4427
4429
|
- **A path that matches no file is an error, exit 2.** _Nothing left to migrate_ off a path nobody
|
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,38 @@ The latest released version here must always match the one published on
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [5.28.0] - 2026-09-23
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **The `doctor` and `codemod` repository scan honours git's exclude rules below the root, not only
|
|
18
|
+
the root `.gitignore`.** A nested `.gitignore` is read relative to its own directory and overrides a
|
|
19
|
+
shallower one; below every `.gitignore` sit the root's `.git/info/exclude` (followed through a
|
|
20
|
+
worktree's `.git` file) and the per-user `core.excludesFile` (else `$XDG_CONFIG_HOME/git/ignore`),
|
|
21
|
+
as in git. A monorepo keeping `/generated` in `libs/app/.gitignore`, or a checkout excluding local
|
|
22
|
+
directories through `info/exclude` or a global ignore, had them counted, walked and offered to
|
|
23
|
+
`codemod --write` — so a local run reported more files than CI did. Still no `git` process and no
|
|
24
|
+
dependency, and still directories only: an ignored file is listed. A `.gitignore` above the scan
|
|
25
|
+
root is not read, and `[include]` in git config is not followed.
|
|
26
|
+
- **`doctor --json` and `doctor --markdown` name the flag they meant.** Both are still refused, exit 2,
|
|
27
|
+
like any unknown flag; the error now ends with ``Did you mean `--format json`?``.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **`init --check` and `init --dry-run` no longer seem to disagree over a version-only change.** A
|
|
32
|
+
block whose marker differs only in `v=` is `unchanged` under `--check` (by design since 5.19.0: CI
|
|
33
|
+
must not go red on a release that changed no advice) and `updated` under `--dry-run`, which read as
|
|
34
|
+
a contradiction. Both now say _only the version stamp differs_ in the file's note.
|
|
35
|
+
- **The `tsconfig` glob check exempts a pattern into an ignored directory that does not exist yet.**
|
|
36
|
+
`src/generated/**/*.ts` in `include`, or a `files` entry there, was reported as matching nothing or
|
|
37
|
+
missing whenever `doctor` ran before the code generator — in CI, typically — although
|
|
38
|
+
`src/generated/` is in `.gitignore`: the exemption only knew directories the scan had pruned.
|
|
39
|
+
- **`prefer-create-spy-from-class` at `{ minRunnerFns: 1 }` leaves a one-member literal passed to a
|
|
40
|
+
typed parameter of a helper the same file declares alone.** `createDefaultOptions({ changeOptionsCallback:
|
|
41
|
+
callback })` over `const createDefaultOptions = (overrides?: Partial<InfoboxOptions>) => …` is
|
|
42
|
+
checked against that type already. An untyped parameter, a rest parameter, an inline object type
|
|
43
|
+
of mocks and an imported helper still report — the last is out of reach of a syntax-only rule.
|
|
44
|
+
|
|
13
45
|
## [5.27.0] - 2026-09-23
|
|
14
46
|
|
|
15
47
|
### Changed
|
|
@@ -7796,7 +7828,8 @@ by hand there, in more than one place, by more than one person.
|
|
|
7796
7828
|
`mockAccessorsProp`.
|
|
7797
7829
|
- Dual ESM + CJS build with type declarations; 100% test coverage.
|
|
7798
7830
|
|
|
7799
|
-
[Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.
|
|
7831
|
+
[Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.28.0...HEAD
|
|
7832
|
+
[5.28.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.27.0...v5.28.0
|
|
7800
7833
|
[5.27.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.26.0...v5.27.0
|
|
7801
7834
|
[5.26.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.25.0...v5.26.0
|
|
7802
7835
|
[5.25.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.24.0...v5.25.0
|
package/README.md
CHANGED
|
@@ -139,7 +139,7 @@ includes — that one import is what keeps `returnSubject()` typed as rxjs's own
|
|
|
139
139
|
- [Which file your agent reads](#which-file-your-agent-reads)
|
|
140
140
|
- [Install it in your agent](#install-it-in-your-agent)
|
|
141
141
|
- [OpenAI Codex](#openai-codex)
|
|
142
|
-
- [GLM (z.ai), Kimi
|
|
142
|
+
- [GLM (z.ai), Kimi K3 and other Claude-compatible models](#glm-zai-kimi-k3-and-other-claude-compatible-models)
|
|
143
143
|
- [Gemini CLI](#gemini-cli)
|
|
144
144
|
- [Claude Code plugin](#claude-code-plugin)
|
|
145
145
|
- [Availability](#availability)
|
|
@@ -372,8 +372,10 @@ that matches no file is the same kind of error, because _Nothing left to migrate
|
|
|
372
372
|
read is not a result. A red suite under `perf --gate` lands there too: a failing test is measured
|
|
373
373
|
until its timeout, and 30 s of timeout looks exactly like 30 s of slow code.
|
|
374
374
|
|
|
375
|
-
The repository scan behind `doctor` and `codemod`
|
|
376
|
-
`.
|
|
375
|
+
The repository scan behind `doctor` and `codemod` skips the directories git ignores — every
|
|
376
|
+
`.gitignore` from the root down, `.git/info/exclude` and the per-user `core.excludesFile`, read as
|
|
377
|
+
files with no `git` process — and stops at a nested repository or a git worktree — a `.git` entry,
|
|
378
|
+
whether it is a directory or a file. A tree carrying worktrees under it used to be
|
|
377
379
|
listed twice, so `doctor` reported every import graph in duplicate and `codemod --write` could have
|
|
378
380
|
rewritten specs on someone else's branch.
|
|
379
381
|
|
|
@@ -653,7 +655,7 @@ served — including the ones your teammates use and you do not.
|
|
|
653
655
|
| ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
|
|
654
656
|
| **Claude Code** | `CLAUDE.md` — project, `.claude/CLAUDE.md` and `~/.claude/CLAUDE.md`, all concatenated | **No.** Bridge with an `@AGENTS.md` import line, or a symlink |
|
|
655
657
|
| **OpenAI Codex** — the `codex` CLI, the IDE extension, Codex cloud | `AGENTS.md`, one per directory from the git root down to the cwd ([below](#openai-codex)) | native |
|
|
656
|
-
| **GLM (z.ai coding plan)**, **Kimi
|
|
658
|
+
| **GLM (z.ai coding plan)**, **Kimi K3** | whatever their client reads — inside Claude Code that is `CLAUDE.md` ([below](#glm-zai-kimi-k3-and-other-claude-compatible-models)) | through the client |
|
|
657
659
|
| **Cursor** | root `AGENTS.md`; `.cursor/rules/*.mdc` for glob-scoped rules | native — and it applies a root `CLAUDE.md` the same always-on way |
|
|
658
660
|
| **GitHub Copilot** | root `AGENTS.md`; `.github/copilot-instructions.md` | native, coding agent included |
|
|
659
661
|
| **OpenCode** | `AGENTS.md`, then `CLAUDE.md`, per directory upwards | native |
|
|
@@ -708,7 +710,7 @@ Then, per tool — everything in the right-hand column is optional on top of tho
|
|
|
708
710
|
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
709
711
|
| **Claude Code** | `/plugin marketplace add ASDAlexey/vitest-auto-spy`, then `/plugin install vitest-auto-spy@vitest-auto-spy` — [the skill](#claude-code-plugin), no project files touched |
|
|
710
712
|
| **OpenAI Codex** | nothing more; optionally `~/.codex/config.toml` from [below](#openai-codex) |
|
|
711
|
-
| **GLM (z.ai)**, **Kimi
|
|
713
|
+
| **GLM (z.ai)**, **Kimi K3** | identical to Claude Code — same client, same plugin command |
|
|
712
714
|
| **Cursor** | `.cursor/rules/vitest-auto-spy.mdc` to load it only for spec files (see below) |
|
|
713
715
|
| **GitHub Copilot** | `.github/instructions/vitest-auto-spy.instructions.md` (see below) |
|
|
714
716
|
| **Cline** | `.clinerules/vitest-auto-spy.md` — the same three lines, plus `paths: ["**/*.spec.ts","**/*.test.ts"]` |
|
|
@@ -788,14 +790,14 @@ which is exactly why this reference ships inside the tarball rather than only on
|
|
|
788
790
|
`node_modules/vitest-auto-spy/AGENTS.md` is on disk the moment the setup script has installed
|
|
789
791
|
dependencies, so nothing has to be fetched.
|
|
790
792
|
|
|
791
|
-
### GLM (z.ai), Kimi
|
|
793
|
+
### GLM (z.ai), Kimi K3 and other Claude-compatible models
|
|
792
794
|
|
|
793
795
|
GLM is a **model**, not an agent — the thing that reads files is the client you run it in.
|
|
794
796
|
|
|
795
797
|
The z.ai coding plan runs GLM **inside Claude Code**, by pointing `ANTHROPIC_BASE_URL` (with
|
|
796
798
|
`ANTHROPIC_AUTH_TOKEN`) at z.ai's Anthropic-compatible endpoint. File discovery is untouched by
|
|
797
799
|
that: `CLAUDE.md`, `.claude/skills/` and the [plugin](#claude-code-plugin) below behave exactly as
|
|
798
|
-
they do on Claude, because it is the same client. Kimi
|
|
800
|
+
they do on Claude, because it is the same client. Kimi K3 driven through Claude Code is the same
|
|
799
801
|
story — and there the skill and the plugin are worth more than a pasted snippet, because they load
|
|
800
802
|
only when a spec actually mentions the library and cost no context the rest of the time.
|
|
801
803
|
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { resolve, join, dirname, posix, isAbsolute, relative, sep } from 'node:path';
|
|
3
3
|
import { readFileSync, statSync, mkdirSync, writeFileSync, existsSync, lstatSync, rmSync, readdirSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
4
5
|
import { createHash } from 'node:crypto';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
6
7
|
import { createRequire } from 'node:module';
|
|
@@ -117,15 +118,40 @@ function parseGitignore(text) {
|
|
|
117
118
|
}
|
|
118
119
|
return rules;
|
|
119
120
|
}
|
|
120
|
-
function
|
|
121
|
+
function isIgnoredBy(sources, path) {
|
|
121
122
|
let ignored = false;
|
|
122
|
-
for (const
|
|
123
|
-
if (
|
|
124
|
-
|
|
123
|
+
for (const { base, rules } of sources) {
|
|
124
|
+
if (base === "" || path.startsWith(`${base}/`)) {
|
|
125
|
+
const local = base === "" ? path : path.slice(base.length + 1);
|
|
126
|
+
for (const rule of rules) {
|
|
127
|
+
if (rule.pattern.test(local)) {
|
|
128
|
+
ignored = !rule.negated;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
125
131
|
}
|
|
126
132
|
}
|
|
127
133
|
return ignored;
|
|
128
134
|
}
|
|
135
|
+
function excludesFileSetting(config) {
|
|
136
|
+
let section2 = "";
|
|
137
|
+
let found;
|
|
138
|
+
for (const line of config.split("\n")) {
|
|
139
|
+
const header2 = /^\s*\[\s*([\w.-]+)/.exec(line)?.[1];
|
|
140
|
+
if (header2 !== void 0) {
|
|
141
|
+
section2 = header2.toLowerCase();
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const value = /^\s*excludesfile\s*=\s*(.*)$/i.exec(line)?.[1];
|
|
145
|
+
if (section2 === "core" && value !== void 0) {
|
|
146
|
+
found = configValue(value);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return found;
|
|
150
|
+
}
|
|
151
|
+
function configValue(raw) {
|
|
152
|
+
const quoted = /^"((?:[^"\\]|\\.)*)"/.exec(raw)?.[1];
|
|
153
|
+
return quoted === void 0 ? raw.replace(/\s*[#;].*$/, "").trim() : quoted.replace(/\\(.)/g, "$1");
|
|
154
|
+
}
|
|
129
155
|
function toRegExp(line) {
|
|
130
156
|
const body = line.replace(/\/+$/, "");
|
|
131
157
|
if (body === "" || line.includes("\\")) {
|
|
@@ -252,20 +278,76 @@ function removeFile(path) {
|
|
|
252
278
|
rmSync(path, { force: true });
|
|
253
279
|
}
|
|
254
280
|
function listRepositoryFiles(root, limit = scanCap()) {
|
|
255
|
-
return scan(root, limit,
|
|
281
|
+
return scan(root, limit, () => false).files;
|
|
256
282
|
}
|
|
257
|
-
function scanRepository(root, limit = scanCap()) {
|
|
258
|
-
return scan(root, limit,
|
|
283
|
+
function scanRepository(root, limit = scanCap(), filter = gitignoreFilter(root)) {
|
|
284
|
+
return scan(root, limit, filter);
|
|
259
285
|
}
|
|
260
|
-
function scan(root, limit,
|
|
261
|
-
const walker = { root,
|
|
286
|
+
function scan(root, limit, filter) {
|
|
287
|
+
const walker = { root, filter, found: [], ignored: [], limit };
|
|
262
288
|
const truncated = walk(walker, root);
|
|
263
289
|
return { files: walker.found.sort(), ignored: walker.ignored.sort(), truncated };
|
|
264
290
|
}
|
|
265
|
-
function
|
|
266
|
-
const
|
|
291
|
+
function gitignoreFilter(root) {
|
|
292
|
+
const gitDirectory = commonGitDirectory(root);
|
|
293
|
+
const excludes = [
|
|
294
|
+
{ base: "", rules: rulesOf(readTextFile(resolve(root, globalExcludesFile(gitDirectory)))) },
|
|
295
|
+
{ base: "", rules: rulesOf(gitDirectory === void 0 ? void 0 : readTextFile(join(gitDirectory, "info", "exclude"))) }
|
|
296
|
+
];
|
|
297
|
+
const files = /* @__PURE__ */ new Map();
|
|
298
|
+
const verdicts = /* @__PURE__ */ new Map();
|
|
299
|
+
const sourceIn = (directory) => {
|
|
300
|
+
const cached = files.get(directory) ?? { base: directory, rules: rulesOf(readTextFile(join(root, directory, ".gitignore"))) };
|
|
301
|
+
files.set(directory, cached);
|
|
302
|
+
return cached;
|
|
303
|
+
};
|
|
304
|
+
const isIgnored = (path) => {
|
|
305
|
+
if (path === "" || path === ".." || path.startsWith("../")) {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
const cached = verdicts.get(path);
|
|
309
|
+
if (cached !== void 0) {
|
|
310
|
+
return cached;
|
|
311
|
+
}
|
|
312
|
+
const segments = path.split("/");
|
|
313
|
+
const parents = segments.slice(0, -1).map((_, index) => segments.slice(0, index + 1).join("/"));
|
|
314
|
+
const verdict = isIgnored(parents.at(-1) ?? "") || isIgnoredBy([...excludes, sourceIn(""), ...parents.map(sourceIn)], path);
|
|
315
|
+
verdicts.set(path, verdict);
|
|
316
|
+
return verdict;
|
|
317
|
+
};
|
|
318
|
+
return isIgnored;
|
|
319
|
+
}
|
|
320
|
+
function rulesOf(text) {
|
|
267
321
|
return (text === void 0 ? void 0 : parseGitignore(text)) ?? [];
|
|
268
322
|
}
|
|
323
|
+
function commonGitDirectory(root) {
|
|
324
|
+
const dotGit = join(root, ".git");
|
|
325
|
+
const gitDirectory = isDirectory(dotGit) ? dotGit : linkedGitDirectory(root, dotGit);
|
|
326
|
+
if (gitDirectory === void 0) {
|
|
327
|
+
return void 0;
|
|
328
|
+
}
|
|
329
|
+
const common = readTextFile(join(gitDirectory, "commondir"))?.trim();
|
|
330
|
+
return common ? resolve(gitDirectory, common) : gitDirectory;
|
|
331
|
+
}
|
|
332
|
+
function linkedGitDirectory(root, dotGit) {
|
|
333
|
+
const [target] = captures(readTextFile(dotGit) ?? "", /^gitdir:\s*(.+?)\s*$/m);
|
|
334
|
+
return target === void 0 ? void 0 : resolve(root, target);
|
|
335
|
+
}
|
|
336
|
+
function setting(name) {
|
|
337
|
+
const value = process.env[name];
|
|
338
|
+
return value === "" ? void 0 : value;
|
|
339
|
+
}
|
|
340
|
+
function globalExcludesFile(gitDirectory) {
|
|
341
|
+
const home = setting("HOME") ?? homedir();
|
|
342
|
+
const xdg = setting("XDG_CONFIG_HOME") ?? join(home, ".config");
|
|
343
|
+
const override = process.env["GIT_CONFIG_GLOBAL"];
|
|
344
|
+
const configs = [
|
|
345
|
+
...override === void 0 ? [join(xdg, "git", "config"), join(home, ".gitconfig")] : [override],
|
|
346
|
+
...gitDirectory === void 0 ? [] : [join(gitDirectory, "config")]
|
|
347
|
+
];
|
|
348
|
+
const configured = configs.flatMap((config) => excludesFileSetting(readTextFile(config) ?? "") ?? []).at(-1);
|
|
349
|
+
return configured === void 0 ? join(xdg, "git", "ignore") : configured.replace(/^~(?=\/|$)/, home);
|
|
350
|
+
}
|
|
269
351
|
function isRepositoryRoot(directory) {
|
|
270
352
|
return existsSync(join(directory, ".git"));
|
|
271
353
|
}
|
|
@@ -298,7 +380,7 @@ function walk(walker, directory) {
|
|
|
298
380
|
}
|
|
299
381
|
function isGitignored(walker, directory) {
|
|
300
382
|
const path = toPosix(relative(walker.root, directory));
|
|
301
|
-
if (!
|
|
383
|
+
if (!walker.filter(path)) {
|
|
302
384
|
return false;
|
|
303
385
|
}
|
|
304
386
|
walker.ignored.push(path);
|
|
@@ -733,7 +815,8 @@ function readProfile(cwd) {
|
|
|
733
815
|
const scripts = stringMap(packageJson["scripts"]);
|
|
734
816
|
const runner = detectRunner(dependencies, scripts);
|
|
735
817
|
const framework = detectFramework(dependencies);
|
|
736
|
-
const
|
|
818
|
+
const isIgnoredDirectory = gitignoreFilter(cwd);
|
|
819
|
+
const scan3 = scanRepository(cwd, scanCap(), isIgnoredDirectory);
|
|
737
820
|
return {
|
|
738
821
|
cwd,
|
|
739
822
|
runner,
|
|
@@ -746,7 +829,7 @@ function readProfile(cwd) {
|
|
|
746
829
|
scripts,
|
|
747
830
|
files: scan3.files,
|
|
748
831
|
filesTruncated: scan3.truncated,
|
|
749
|
-
|
|
832
|
+
isIgnoredDirectory
|
|
750
833
|
};
|
|
751
834
|
}
|
|
752
835
|
var PACKAGE_NAME = "vitest-auto-spy";
|
|
@@ -3005,7 +3088,7 @@ function checkForeignPragma(graph) {
|
|
|
3005
3088
|
}
|
|
3006
3089
|
|
|
3007
3090
|
// src/cli/checks/export-map.generated.ts
|
|
3008
|
-
var EXPORT_MAP_VERSION = "5.
|
|
3091
|
+
var EXPORT_MAP_VERSION = "5.27.0";
|
|
3009
3092
|
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";
|
|
3010
3093
|
var EXPORTED_BY = {
|
|
3011
3094
|
AccessorImplementations: "0 1 2 3 4 12 20 21 22",
|
|
@@ -3881,14 +3964,14 @@ function expandInclude(pattern) {
|
|
|
3881
3964
|
const base = last === "*" ? trimmed2 : `${trimmed2}${last === "**" ? "/*" : "/**/*"}`;
|
|
3882
3965
|
return IMPLIED_EXTENSIONS.map((extension) => `${base}${extension}`);
|
|
3883
3966
|
}
|
|
3884
|
-
function isExemptPattern(pattern,
|
|
3885
|
-
return /\.d\.[cm]?ts$/.test(pattern) || isOutsideScan(pattern,
|
|
3967
|
+
function isExemptPattern(pattern, isIgnored = () => false) {
|
|
3968
|
+
return /\.d\.[cm]?ts$/.test(pattern) || isOutsideScan(pattern, isIgnored);
|
|
3886
3969
|
}
|
|
3887
|
-
function isOutsideScan(path,
|
|
3970
|
+
function isOutsideScan(path, isIgnored) {
|
|
3888
3971
|
const segments = path.split("/");
|
|
3889
3972
|
const wildcard = segments.findIndex((segment) => /[*?[]/.test(segment));
|
|
3890
3973
|
const literal = wildcard === -1 ? segments : segments.slice(0, wildcard);
|
|
3891
|
-
return literal.some(isSkippedDirectory) ||
|
|
3974
|
+
return literal.some(isSkippedDirectory) || literal.some((_, index) => isIgnored(literal.slice(0, index + 1).join("/")));
|
|
3892
3975
|
}
|
|
3893
3976
|
function literalTail(pattern) {
|
|
3894
3977
|
const last = pattern.slice(pattern.lastIndexOf("/") + 1);
|
|
@@ -3920,7 +4003,7 @@ function checkInclude(configPath, entry, profile) {
|
|
|
3920
4003
|
return void 0;
|
|
3921
4004
|
}
|
|
3922
4005
|
const resolved = resolveFromConfig(configPath, entry);
|
|
3923
|
-
if (isExemptPattern(resolved, profile.
|
|
4006
|
+
if (isExemptPattern(resolved, profile.isIgnoredDirectory) || matchesAnyFile(resolved, files)) {
|
|
3924
4007
|
return void 0;
|
|
3925
4008
|
}
|
|
3926
4009
|
const tail = literalTail(resolved);
|
|
@@ -3950,7 +4033,7 @@ function checkOne(profile, configPath) {
|
|
|
3950
4033
|
const findings = stringList(parsed["include"]).flatMap((entry) => checkInclude(configPath, entry, profile) ?? []);
|
|
3951
4034
|
for (const entry of stringList(parsed["files"])) {
|
|
3952
4035
|
const resolved = resolveFromConfig(configPath, entry);
|
|
3953
|
-
if (!profile.files.includes(resolved) && !isOutsideScan(resolved, profile.
|
|
4036
|
+
if (!profile.files.includes(resolved) && !isOutsideScan(resolved, profile.isIgnoredDirectory)) {
|
|
3954
4037
|
findings.push({
|
|
3955
4038
|
check: "tsconfig-file-missing",
|
|
3956
4039
|
severity: "error",
|
|
@@ -4474,6 +4557,10 @@ function planFor(target, content, profile, version, facts) {
|
|
|
4474
4557
|
}
|
|
4475
4558
|
return { target, existing, desired: applyManaged(existing ?? "", managedBlock(profile, version, facts)), note: note2 };
|
|
4476
4559
|
}
|
|
4560
|
+
function isStampOnly(plan) {
|
|
4561
|
+
return plan.existing !== void 0 && plan.desired !== void 0 && plan.existing !== plan.desired && withoutVersion(plan.existing) === withoutVersion(plan.desired);
|
|
4562
|
+
}
|
|
4563
|
+
var STAMP_ONLY_NOTE = "only the version stamp differs, which `--check` does not count; a plain `init` refreshes it";
|
|
4477
4564
|
function statusOf(plan, check) {
|
|
4478
4565
|
if (plan.desired === void 0) {
|
|
4479
4566
|
return "skipped";
|
|
@@ -4484,7 +4571,7 @@ function statusOf(plan, check) {
|
|
|
4484
4571
|
if (plan.existing === plan.desired) {
|
|
4485
4572
|
return "unchanged";
|
|
4486
4573
|
}
|
|
4487
|
-
return check &&
|
|
4574
|
+
return check && isStampOnly(plan) ? "unchanged" : "updated";
|
|
4488
4575
|
}
|
|
4489
4576
|
function isSelected(path, only) {
|
|
4490
4577
|
return only === void 0 || only.some((entry) => {
|
|
@@ -4536,15 +4623,16 @@ function uninstallPlan(plan) {
|
|
|
4536
4623
|
function applyPlan(cwd, plan, options) {
|
|
4537
4624
|
const status = options.uninstall ? uninstallStatus(plan) : statusOf(plan, options.check);
|
|
4538
4625
|
const path = plan.target.path;
|
|
4626
|
+
const note2 = !options.uninstall && isStampOnly(plan) ? `${plan.note} \u2014 ${STAMP_ONLY_NOTE}` : plan.note;
|
|
4539
4627
|
if (options.check || options.dryRun || plan.desired === void 0 || status === "unchanged") {
|
|
4540
|
-
return { path, status, note:
|
|
4628
|
+
return { path, status, note: note2 };
|
|
4541
4629
|
}
|
|
4542
4630
|
if (plan.desired === "") {
|
|
4543
4631
|
removeFile(join(cwd, path));
|
|
4544
4632
|
} else {
|
|
4545
4633
|
writeTextFile(join(cwd, path), plan.desired);
|
|
4546
4634
|
}
|
|
4547
|
-
return { path, status, note:
|
|
4635
|
+
return { path, status, note: note2 };
|
|
4548
4636
|
}
|
|
4549
4637
|
function uninstallStatus(plan) {
|
|
4550
4638
|
if (plan.desired === void 0 || plan.desired === plan.existing) {
|
|
@@ -6186,8 +6274,8 @@ function configCode(graph) {
|
|
|
6186
6274
|
}
|
|
6187
6275
|
return configs;
|
|
6188
6276
|
}
|
|
6189
|
-
function configDeclares(graph,
|
|
6190
|
-
return configCode(graph).some((code) =>
|
|
6277
|
+
function configDeclares(graph, setting2) {
|
|
6278
|
+
return configCode(graph).some((code) => setting2.test(code));
|
|
6191
6279
|
}
|
|
6192
6280
|
function declaresNoIsolation(graph) {
|
|
6193
6281
|
return configDeclares(graph, NO_ISOLATION);
|
|
@@ -7047,6 +7135,10 @@ function rejectFlags(args, command, io) {
|
|
|
7047
7135
|
io.err(
|
|
7048
7136
|
`\`${command}\` accepts ${[...accepted, ...COMMON_FLAGS].sort((a, b) => a.localeCompare(b)).map((name) => `--${name}`).join(", ")}.`
|
|
7049
7137
|
);
|
|
7138
|
+
const formats = unknown.filter((name) => accepted.includes("format") && (name === "json" || name === "markdown"));
|
|
7139
|
+
if (formats.length > 0) {
|
|
7140
|
+
io.err(`Did you mean ${formats.map((name) => `\`--format ${name}\``).join(", ")}?`);
|
|
7141
|
+
}
|
|
7050
7142
|
return true;
|
|
7051
7143
|
}
|
|
7052
7144
|
function guardBrokenPipe(stream, quit) {
|
package/dist/eslint-plugin.cjs
CHANGED
|
@@ -1406,6 +1406,30 @@ function declaredTypeOf(context, node) {
|
|
|
1406
1406
|
const declared = definition && isVariableDeclarator(definition) ? definition.id : void 0;
|
|
1407
1407
|
return declared && isIdentifier(declared) ? declared.typeAnnotation?.typeAnnotation : void 0;
|
|
1408
1408
|
}
|
|
1409
|
+
function localFunction2(context, callee) {
|
|
1410
|
+
const scope = context.sourceCode.getScope(callee);
|
|
1411
|
+
const declared = findBinding(scope, callee.name)?.defs.map((definition) => definition.node).find(isFunctionNode);
|
|
1412
|
+
const bound = declared ?? boundValueOf(scope, callee);
|
|
1413
|
+
return bound && isFunctionNode(bound) ? bound : void 0;
|
|
1414
|
+
}
|
|
1415
|
+
function isAssignmentPattern(node) {
|
|
1416
|
+
return node.type === "AssignmentPattern";
|
|
1417
|
+
}
|
|
1418
|
+
function isAnnotatedParameter(node) {
|
|
1419
|
+
return node.type === "Identifier" || node.type === "ObjectPattern";
|
|
1420
|
+
}
|
|
1421
|
+
function parameterType(parameter) {
|
|
1422
|
+
const target = parameter && isAssignmentPattern(parameter) ? parameter.left : parameter;
|
|
1423
|
+
return target && isAnnotatedParameter(target) ? target.typeAnnotation?.typeAnnotation : void 0;
|
|
1424
|
+
}
|
|
1425
|
+
function declaredParameterTypeOf(context, node) {
|
|
1426
|
+
const call = node.parent;
|
|
1427
|
+
if (!isCallExpression(call) || !isIdentifier(call.callee)) {
|
|
1428
|
+
return void 0;
|
|
1429
|
+
}
|
|
1430
|
+
const helper = localFunction2(context, call.callee);
|
|
1431
|
+
return parameterType(helper?.params[call.arguments.indexOf(node)]);
|
|
1432
|
+
}
|
|
1409
1433
|
function outermostData(node) {
|
|
1410
1434
|
let current = node;
|
|
1411
1435
|
for (; ; ) {
|
|
@@ -1426,7 +1450,7 @@ function isMemberStubValue(node) {
|
|
|
1426
1450
|
}
|
|
1427
1451
|
function isCheckedByDeclaration(context, node) {
|
|
1428
1452
|
const data = outermostData(node);
|
|
1429
|
-
const declared = declaredTypeOf(context, data);
|
|
1453
|
+
const declared = declaredTypeOf(context, data) ?? declaredParameterTypeOf(context, data);
|
|
1430
1454
|
return isMemberStubValue(data) || declared !== void 0 && declared.type !== "TSTypeLiteral";
|
|
1431
1455
|
}
|
|
1432
1456
|
var SIGNAL_WRITERS = /* @__PURE__ */ new Set(["set", "update"]);
|
package/dist/perf-profiler.js
CHANGED
|
@@ -19,8 +19,8 @@ function isRecord(value) {
|
|
|
19
19
|
var PERF_PROFILE_ENV = "VITEST_AUTO_SPY_PERF_PROFILE";
|
|
20
20
|
var SAMPLING_MICROSECONDS = 500;
|
|
21
21
|
function send(session, method, params = {}) {
|
|
22
|
-
return new Promise((
|
|
23
|
-
session.post(method, params, (error, result) => error === null ?
|
|
22
|
+
return new Promise((resolve2, reject) => {
|
|
23
|
+
session.post(method, params, (error, result) => error === null ? resolve2(result) : reject(error));
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
var written = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitest-auto-spy",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.28.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",
|