vitest-auto-spy 5.26.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 +61 -1
- package/README.md +9 -7
- package/dist/cli.js +242 -51
- 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,64 @@ 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
|
+
|
|
45
|
+
## [5.27.0] - 2026-09-23
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- **The `doctor` and `codemod` repository scan honours the root `.gitignore` for directories.** The
|
|
50
|
+
hardcoded skip list only knew the usual build and store names, so a repository's own generated or
|
|
51
|
+
local output (`src/generated/`, `/reports`, `tmp-*/`) was read as source: counted in the report,
|
|
52
|
+
walked by the import graph and offered to `codemod --write`. The file is parsed directly, with no
|
|
53
|
+
`git` process and no dependency: comments, `!` negations with the last match winning, anchored and
|
|
54
|
+
unanchored patterns, `*`, `?`, `**` and bracket classes. Only the root file and only directories
|
|
55
|
+
are honoured — an ignored file is still listed. A pattern with a `\` escape or a POSIX class is
|
|
56
|
+
dropped, which only means scanning more, and a `!` rule that cannot be read disables the whole
|
|
57
|
+
file rather than prune a directory git keeps. `perf-merge`'s report listing is unaffected.
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- **The `tsconfig` glob check no longer reports a pattern it could never see matched.** A pattern
|
|
62
|
+
inside a skipped directory below the root (`libs/app/out-tsc/**/*.ts`) or inside a directory the
|
|
63
|
+
root `.gitignore` excludes is exempt, and so is a `files` entry there — both were reported as
|
|
64
|
+
matching nothing or missing, although the scan had simply not descended into them.
|
|
65
|
+
- **The `prefer-create-spy-from-class` reference lists every factory whose arguments are exempt.**
|
|
66
|
+
The built-in doubles and stubs (`createComponentStub`, `createDirectiveHost`, `createWindowDouble`,
|
|
67
|
+
`provideRouterDouble` and the rest) were exempt in the rule but missing from the docs, which also
|
|
68
|
+
still said "four shapes" over a list of eleven. The Russian page gained the four exemptions it was
|
|
69
|
+
missing (`vi.hoisted`, the options bag, `createFixture`, an RxJS observer).
|
|
70
|
+
|
|
13
71
|
## [5.26.0] - 2026-09-23
|
|
14
72
|
|
|
15
73
|
### Changed
|
|
@@ -7770,7 +7828,9 @@ by hand there, in more than one place, by more than one person.
|
|
|
7770
7828
|
`mockAccessorsProp`.
|
|
7771
7829
|
- Dual ESM + CJS build with type declarations; 100% test coverage.
|
|
7772
7830
|
|
|
7773
|
-
[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
|
|
7833
|
+
[5.27.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.26.0...v5.27.0
|
|
7774
7834
|
[5.26.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.25.0...v5.26.0
|
|
7775
7835
|
[5.25.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.24.0...v5.25.0
|
|
7776
7836
|
[5.24.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.23.0...v5.24.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
|
-
import { resolve, join, dirname,
|
|
3
|
-
import { readFileSync, statSync, mkdirSync, writeFileSync,
|
|
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
|
+
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';
|
|
@@ -98,6 +99,109 @@ function flagEnabled(args, name) {
|
|
|
98
99
|
}
|
|
99
100
|
return value !== "false";
|
|
100
101
|
}
|
|
102
|
+
|
|
103
|
+
// src/cli/gitignore.ts
|
|
104
|
+
function parseGitignore(text) {
|
|
105
|
+
const rules = [];
|
|
106
|
+
for (const raw of text.split("\n")) {
|
|
107
|
+
const line = raw.trimEnd();
|
|
108
|
+
if (line === "" || line.startsWith("#")) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const negated = line.startsWith("!");
|
|
112
|
+
const pattern = toRegExp(negated ? line.slice(1) : line);
|
|
113
|
+
if (pattern !== void 0) {
|
|
114
|
+
rules.push({ negated, pattern });
|
|
115
|
+
} else if (negated) {
|
|
116
|
+
return void 0;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return rules;
|
|
120
|
+
}
|
|
121
|
+
function isIgnoredBy(sources, path) {
|
|
122
|
+
let ignored = false;
|
|
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
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return ignored;
|
|
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
|
+
}
|
|
155
|
+
function toRegExp(line) {
|
|
156
|
+
const body = line.replace(/\/+$/, "");
|
|
157
|
+
if (body === "" || line.includes("\\")) {
|
|
158
|
+
return void 0;
|
|
159
|
+
}
|
|
160
|
+
const anchored = body.includes("/");
|
|
161
|
+
const segments = (body.startsWith("/") ? body.slice(1) : body).split("/");
|
|
162
|
+
let source = "";
|
|
163
|
+
for (const [index, segment] of segments.entries()) {
|
|
164
|
+
const last = index === segments.length - 1;
|
|
165
|
+
if (segment === "**") {
|
|
166
|
+
source += last ? ".*" : "(?:[^/]+/)*";
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const converted = segmentSource(segment);
|
|
170
|
+
if (converted === void 0) {
|
|
171
|
+
return void 0;
|
|
172
|
+
}
|
|
173
|
+
source += last ? converted : `${converted}/`;
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
return new RegExp(`^${anchored ? "" : "(?:.*/)?"}${source}$`);
|
|
177
|
+
} catch {
|
|
178
|
+
return void 0;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function segmentSource(segment) {
|
|
182
|
+
let source = "";
|
|
183
|
+
let index = 0;
|
|
184
|
+
while (index < segment.length) {
|
|
185
|
+
const char = segment.charAt(index);
|
|
186
|
+
if (char === "[") {
|
|
187
|
+
const negated = /^[!^]/.test(segment.slice(index + 1));
|
|
188
|
+
const start = index + (negated ? 2 : 1);
|
|
189
|
+
const end = segment.indexOf("]", start + 1);
|
|
190
|
+
const members = segment.slice(start, end);
|
|
191
|
+
if (end === -1 || members.includes("[")) {
|
|
192
|
+
return void 0;
|
|
193
|
+
}
|
|
194
|
+
source += `[${negated ? "^" : ""}${members.replace(/[\]^]/g, "\\$&")}]`;
|
|
195
|
+
index = end + 1;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
source += char === "*" ? "[^/]*" : char === "?" ? "[^/]" : char.replace(/[$()+.^{|}]/, "\\$&");
|
|
199
|
+
index += 1;
|
|
200
|
+
}
|
|
201
|
+
return source;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/cli/fs-scan.ts
|
|
101
205
|
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
102
206
|
".angular",
|
|
103
207
|
".bun",
|
|
@@ -174,18 +278,81 @@ function removeFile(path) {
|
|
|
174
278
|
rmSync(path, { force: true });
|
|
175
279
|
}
|
|
176
280
|
function listRepositoryFiles(root, limit = scanCap()) {
|
|
177
|
-
return
|
|
281
|
+
return scan(root, limit, () => false).files;
|
|
178
282
|
}
|
|
179
|
-
function scanRepository(root, limit = scanCap()) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
283
|
+
function scanRepository(root, limit = scanCap(), filter = gitignoreFilter(root)) {
|
|
284
|
+
return scan(root, limit, filter);
|
|
285
|
+
}
|
|
286
|
+
function scan(root, limit, filter) {
|
|
287
|
+
const walker = { root, filter, found: [], ignored: [], limit };
|
|
288
|
+
const truncated = walk(walker, root);
|
|
289
|
+
return { files: walker.found.sort(), ignored: walker.ignored.sort(), truncated };
|
|
290
|
+
}
|
|
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) {
|
|
321
|
+
return (text === void 0 ? void 0 : parseGitignore(text)) ?? [];
|
|
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);
|
|
183
350
|
}
|
|
184
351
|
function isRepositoryRoot(directory) {
|
|
185
352
|
return existsSync(join(directory, ".git"));
|
|
186
353
|
}
|
|
187
|
-
function walk(
|
|
188
|
-
if (found.length >= limit) {
|
|
354
|
+
function walk(walker, directory) {
|
|
355
|
+
if (walker.found.length >= walker.limit) {
|
|
189
356
|
return true;
|
|
190
357
|
}
|
|
191
358
|
let entries;
|
|
@@ -195,22 +362,30 @@ function walk(root, directory, found, limit) {
|
|
|
195
362
|
return false;
|
|
196
363
|
}
|
|
197
364
|
for (const entry of entries) {
|
|
198
|
-
if (found.length >= limit) {
|
|
365
|
+
if (walker.found.length >= walker.limit) {
|
|
199
366
|
return true;
|
|
200
367
|
}
|
|
201
368
|
const full = join(directory, entry.name);
|
|
202
369
|
if (entry.isDirectory()) {
|
|
203
|
-
if (!isSkippedDirectory(entry.name) && !isRepositoryRoot(full) &&
|
|
370
|
+
if (!isSkippedDirectory(entry.name) && !isRepositoryRoot(full) && !isGitignored(walker, full) && walk(walker, full)) {
|
|
204
371
|
return true;
|
|
205
372
|
}
|
|
206
373
|
continue;
|
|
207
374
|
}
|
|
208
375
|
if (entry.isFile()) {
|
|
209
|
-
found.push(toPosix(relative(root, full)));
|
|
376
|
+
walker.found.push(toPosix(relative(walker.root, full)));
|
|
210
377
|
}
|
|
211
378
|
}
|
|
212
379
|
return false;
|
|
213
380
|
}
|
|
381
|
+
function isGitignored(walker, directory) {
|
|
382
|
+
const path = toPosix(relative(walker.root, directory));
|
|
383
|
+
if (!walker.filter(path)) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
walker.ignored.push(path);
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
214
389
|
function parseJsonc(text) {
|
|
215
390
|
try {
|
|
216
391
|
return JSON.parse(stripJsonComments(text));
|
|
@@ -640,7 +815,8 @@ function readProfile(cwd) {
|
|
|
640
815
|
const scripts = stringMap(packageJson["scripts"]);
|
|
641
816
|
const runner = detectRunner(dependencies, scripts);
|
|
642
817
|
const framework = detectFramework(dependencies);
|
|
643
|
-
const
|
|
818
|
+
const isIgnoredDirectory = gitignoreFilter(cwd);
|
|
819
|
+
const scan3 = scanRepository(cwd, scanCap(), isIgnoredDirectory);
|
|
644
820
|
return {
|
|
645
821
|
cwd,
|
|
646
822
|
runner,
|
|
@@ -651,8 +827,9 @@ function readProfile(cwd) {
|
|
|
651
827
|
setupFiles: detectSetupFiles(cwd),
|
|
652
828
|
dependencies,
|
|
653
829
|
scripts,
|
|
654
|
-
files:
|
|
655
|
-
filesTruncated:
|
|
830
|
+
files: scan3.files,
|
|
831
|
+
filesTruncated: scan3.truncated,
|
|
832
|
+
isIgnoredDirectory
|
|
656
833
|
};
|
|
657
834
|
}
|
|
658
835
|
var PACKAGE_NAME = "vitest-auto-spy";
|
|
@@ -1059,7 +1236,7 @@ function textOf(context, range) {
|
|
|
1059
1236
|
function group(groups, position) {
|
|
1060
1237
|
return groups.slice(position, position + 1).join("");
|
|
1061
1238
|
}
|
|
1062
|
-
function
|
|
1239
|
+
function scan2(masked, pattern) {
|
|
1063
1240
|
return [...masked.matchAll(pattern)].map((match) => ({ index: match.index, whole: group(match, 0), groups: [...match] }));
|
|
1064
1241
|
}
|
|
1065
1242
|
|
|
@@ -1139,7 +1316,7 @@ function replacement(start, end, text) {
|
|
|
1139
1316
|
function replacements(context, pattern, text) {
|
|
1140
1317
|
return {
|
|
1141
1318
|
...EMPTY_OUTPUT,
|
|
1142
|
-
edits:
|
|
1319
|
+
edits: scan2(context.masked, pattern).map((match) => ({
|
|
1143
1320
|
start: match.index,
|
|
1144
1321
|
end: match.index + match.whole.length,
|
|
1145
1322
|
text: text(match)
|
|
@@ -1182,7 +1359,7 @@ var jasmineStrategies = {
|
|
|
1182
1359
|
function withArgsOutput(context) {
|
|
1183
1360
|
const closes = spyOnCloses(context);
|
|
1184
1361
|
return mergeOutputs(
|
|
1185
|
-
|
|
1362
|
+
scan2(context.masked, WITH_ARGS).map(
|
|
1186
1363
|
(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
1364
|
)
|
|
1188
1365
|
);
|
|
@@ -1205,7 +1382,7 @@ function lastCodeBefore(masked, index) {
|
|
|
1205
1382
|
}
|
|
1206
1383
|
function strategyOutputs(context) {
|
|
1207
1384
|
return mergeOutputs(
|
|
1208
|
-
|
|
1385
|
+
scan2(context.masked, AND_STRATEGY).flatMap((match) => {
|
|
1209
1386
|
const name = group(match.groups, 1);
|
|
1210
1387
|
return HELPER_NAMES.has(name) ? [] : [strategyOutput(context, match, name)];
|
|
1211
1388
|
})
|
|
@@ -1279,12 +1456,12 @@ var jasmineSpyOn = {
|
|
|
1279
1456
|
residue: /(?:^|[^\w$.])spyOn(?:Property)?\s*\(/,
|
|
1280
1457
|
run: (context) => ({
|
|
1281
1458
|
...EMPTY_OUTPUT,
|
|
1282
|
-
edits:
|
|
1459
|
+
edits: scan2(context.masked, SPY_ON).flatMap((match) => spyOnEdits(context, match))
|
|
1283
1460
|
})
|
|
1284
1461
|
};
|
|
1285
1462
|
function spyOnCloses(context) {
|
|
1286
1463
|
return new Set(
|
|
1287
|
-
|
|
1464
|
+
scan2(context.masked, SPY_ON).flatMap((match) => {
|
|
1288
1465
|
const range = spyOnRange(context, match);
|
|
1289
1466
|
return range === void 0 ? [] : [range[1]];
|
|
1290
1467
|
})
|
|
@@ -1517,7 +1694,7 @@ var jasmineGlobals = {
|
|
|
1517
1694
|
// leftover `jasmine.SpyObj` reported twice would name a transform that never intended to touch it.
|
|
1518
1695
|
residue: /\bjasmine\s*\.\s*(?!Spy(?:Obj)?\b)/,
|
|
1519
1696
|
run: (context) => mergeOutputs(
|
|
1520
|
-
|
|
1697
|
+
scan2(context.masked, MEMBER).flatMap((match) => {
|
|
1521
1698
|
const name = group(match.groups, 1);
|
|
1522
1699
|
return TYPE_TARGETS[name] === void 0 ? [globalOutput(context, match, name)] : [];
|
|
1523
1700
|
})
|
|
@@ -1592,7 +1769,7 @@ var jasmineTypes = {
|
|
|
1592
1769
|
summary: "jasmine.Spy \u2192 Mock from vitest, and jasmine.SpyObj<T> \u2192 Spy<T> from this package.",
|
|
1593
1770
|
residue: /\bjasmine\s*\.\s*Spy(?:Obj)?\b/,
|
|
1594
1771
|
run: (context) => mergeOutputs(
|
|
1595
|
-
|
|
1772
|
+
scan2(context.masked, MEMBER).flatMap((match) => {
|
|
1596
1773
|
const target = TYPE_TARGETS[group(match.groups, 1)];
|
|
1597
1774
|
return target === void 0 ? [] : [typeOutput(context, match, target)];
|
|
1598
1775
|
})
|
|
@@ -1630,11 +1807,11 @@ var jasmineMatchers = {
|
|
|
1630
1807
|
([from, to]) => replacements(context, new RegExp(String.raw`\.\s*${from}\s*\(`, "g"), () => `.${to}(`)
|
|
1631
1808
|
),
|
|
1632
1809
|
{ ...EMPTY_OUTPUT, edits: failEdits(context) },
|
|
1633
|
-
{ ...EMPTY_OUTPUT, edits:
|
|
1810
|
+
{ ...EMPTY_OUTPUT, edits: scan2(context.masked, EXPECT).flatMap((match) => withContextEdits(context, match)) }
|
|
1634
1811
|
])
|
|
1635
1812
|
};
|
|
1636
1813
|
function failEdits(context) {
|
|
1637
|
-
return
|
|
1814
|
+
return scan2(context.masked, FAIL).filter((match) => isCallPosition(context.masked, match.index + match.whole.length - 1)).map((match) => ({
|
|
1638
1815
|
start: match.index,
|
|
1639
1816
|
end: match.index + match.whole.length,
|
|
1640
1817
|
text: `${group(match.groups, 1)}expect.fail(`
|
|
@@ -1775,7 +1952,7 @@ var jestNamespace = {
|
|
|
1775
1952
|
summary: "jest.<member> \u2192 vi.<member>, reporting the members with no vi twin instead of renaming them.",
|
|
1776
1953
|
residue: /\bjest\s*\./,
|
|
1777
1954
|
run: (context) => collect(
|
|
1778
|
-
|
|
1955
|
+
scan2(context.masked, MEMBER2).flatMap((match) => {
|
|
1779
1956
|
const name = group(match.groups, 1);
|
|
1780
1957
|
return TYPE_NAMES[name] === void 0 ? [memberOutput(context, match, name)] : [];
|
|
1781
1958
|
})
|
|
@@ -1847,7 +2024,7 @@ var jestTypes = {
|
|
|
1847
2024
|
run: (context) => {
|
|
1848
2025
|
const functionFirst = importsJestGlobals(context);
|
|
1849
2026
|
return collect([
|
|
1850
|
-
...
|
|
2027
|
+
...scan2(context.masked, MEMBER2).flatMap((match) => {
|
|
1851
2028
|
const name = group(match.groups, 1);
|
|
1852
2029
|
const target = TYPE_NAMES[name];
|
|
1853
2030
|
return target === void 0 ? [] : [typeOutput2(context, match, name, target, functionFirst)];
|
|
@@ -1863,7 +2040,7 @@ var GENERIC_CALL = /\bjest\s*\.\s*(fn|spyOn)\s*</g;
|
|
|
1863
2040
|
function genericCallNotes(context) {
|
|
1864
2041
|
return {
|
|
1865
2042
|
...EMPTY_OUTPUT,
|
|
1866
|
-
notes:
|
|
2043
|
+
notes: scan2(context.masked, GENERIC_CALL).map((match) => unreadableTypeArguments(context, match.index, group(match.groups, 1)))
|
|
1867
2044
|
};
|
|
1868
2045
|
}
|
|
1869
2046
|
var NO_ARGUMENT = /\.\s*(mockImplementation(?:Once)?)\s*\(\s*\)/g;
|
|
@@ -1874,7 +2051,7 @@ var mockImplementationArity = {
|
|
|
1874
2051
|
residue: /\.\s*mockImplementation(?:Once)?\s*\(\s*\)/,
|
|
1875
2052
|
run: (context) => ({
|
|
1876
2053
|
...EMPTY_OUTPUT,
|
|
1877
|
-
edits:
|
|
2054
|
+
edits: scan2(context.masked, NO_ARGUMENT).map((match) => ({
|
|
1878
2055
|
start: match.index,
|
|
1879
2056
|
end: match.index + match.whole.length,
|
|
1880
2057
|
text: `.${group(match.groups, 1)}(() => undefined)`
|
|
@@ -1890,7 +2067,7 @@ var jasmineAliases = {
|
|
|
1890
2067
|
run: (context) => ({
|
|
1891
2068
|
...EMPTY_OUTPUT,
|
|
1892
2069
|
edits: Object.entries(JASMINE_ALIASES).flatMap(
|
|
1893
|
-
([alias, replacement2]) =>
|
|
2070
|
+
([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
2071
|
const start = match.index + group(match.groups, 1).length;
|
|
1895
2072
|
return { start, end: start + alias.length, text: replacement2 };
|
|
1896
2073
|
})
|
|
@@ -1911,7 +2088,7 @@ var jestGlobalsImport = {
|
|
|
1911
2088
|
function globalsEdits(context, statement) {
|
|
1912
2089
|
const specifierAt = context.source.indexOf(JEST_GLOBALS, statement.start);
|
|
1913
2090
|
const [open, close] = statement.braces ?? [statement.start, statement.start];
|
|
1914
|
-
const renames =
|
|
2091
|
+
const renames = scan2(context.masked.slice(open, close), /\bjest\b/g).map((match) => ({
|
|
1915
2092
|
start: open + match.index,
|
|
1916
2093
|
end: open + match.index + "jest".length,
|
|
1917
2094
|
text: "vi"
|
|
@@ -2112,7 +2289,7 @@ var injectCast = {
|
|
|
2112
2289
|
residue: /\bas\s+Spy\s*</,
|
|
2113
2290
|
run: (context) => {
|
|
2114
2291
|
const helper = context.entries === void 0 ? void 0 : entryFor(context.entries, "asSpy", context.preferredEntry);
|
|
2115
|
-
const casts =
|
|
2292
|
+
const casts = scan2(context.masked, INJECT).flatMap((match) => {
|
|
2116
2293
|
const cast = castAt(context, match.index, match.whole.length);
|
|
2117
2294
|
return cast === void 0 ? [] : [cast];
|
|
2118
2295
|
});
|
|
@@ -2124,7 +2301,7 @@ var injectCast = {
|
|
|
2124
2301
|
};
|
|
2125
2302
|
function withHelper(context, casts, need) {
|
|
2126
2303
|
const covered = new Set(casts.map((cast) => cast.angle));
|
|
2127
|
-
const strays =
|
|
2304
|
+
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
2305
|
return {
|
|
2129
2306
|
edits: casts.map((cast) => cast.edit),
|
|
2130
2307
|
needs: casts.length === 0 ? [] : [need],
|
|
@@ -2152,7 +2329,7 @@ var TRANSFORMS = [
|
|
|
2152
2329
|
function residueOf(file, text, transforms) {
|
|
2153
2330
|
const masked = maskComments(text);
|
|
2154
2331
|
return transforms.flatMap(
|
|
2155
|
-
(transform) =>
|
|
2332
|
+
(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
2333
|
);
|
|
2157
2334
|
}
|
|
2158
2335
|
function residueNote(file, text, match, transform) {
|
|
@@ -2911,7 +3088,7 @@ function checkForeignPragma(graph) {
|
|
|
2911
3088
|
}
|
|
2912
3089
|
|
|
2913
3090
|
// src/cli/checks/export-map.generated.ts
|
|
2914
|
-
var EXPORT_MAP_VERSION = "5.
|
|
3091
|
+
var EXPORT_MAP_VERSION = "5.27.0";
|
|
2915
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";
|
|
2916
3093
|
var EXPORTED_BY = {
|
|
2917
3094
|
AccessorImplementations: "0 1 2 3 4 12 20 21 22",
|
|
@@ -3787,10 +3964,14 @@ function expandInclude(pattern) {
|
|
|
3787
3964
|
const base = last === "*" ? trimmed2 : `${trimmed2}${last === "**" ? "/*" : "/**/*"}`;
|
|
3788
3965
|
return IMPLIED_EXTENSIONS.map((extension) => `${base}${extension}`);
|
|
3789
3966
|
}
|
|
3790
|
-
function isExemptPattern(pattern) {
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3967
|
+
function isExemptPattern(pattern, isIgnored = () => false) {
|
|
3968
|
+
return /\.d\.[cm]?ts$/.test(pattern) || isOutsideScan(pattern, isIgnored);
|
|
3969
|
+
}
|
|
3970
|
+
function isOutsideScan(path, isIgnored) {
|
|
3971
|
+
const segments = path.split("/");
|
|
3972
|
+
const wildcard = segments.findIndex((segment) => /[*?[]/.test(segment));
|
|
3973
|
+
const literal = wildcard === -1 ? segments : segments.slice(0, wildcard);
|
|
3974
|
+
return literal.some(isSkippedDirectory) || literal.some((_, index) => isIgnored(literal.slice(0, index + 1).join("/")));
|
|
3794
3975
|
}
|
|
3795
3976
|
function literalTail(pattern) {
|
|
3796
3977
|
const last = pattern.slice(pattern.lastIndexOf("/") + 1);
|
|
@@ -3816,12 +3997,13 @@ function stringList(value) {
|
|
|
3816
3997
|
function resolveFromConfig(configPath, entry) {
|
|
3817
3998
|
return posix.normalize(posix.join(posix.dirname(configPath), entry)).replace(/^\.\//, "");
|
|
3818
3999
|
}
|
|
3819
|
-
function checkInclude(configPath, entry,
|
|
4000
|
+
function checkInclude(configPath, entry, profile) {
|
|
4001
|
+
const files = profile.files;
|
|
3820
4002
|
if (entry.includes("${") || entry.startsWith("/")) {
|
|
3821
4003
|
return void 0;
|
|
3822
4004
|
}
|
|
3823
4005
|
const resolved = resolveFromConfig(configPath, entry);
|
|
3824
|
-
if (isExemptPattern(resolved) || matchesAnyFile(resolved, files)) {
|
|
4006
|
+
if (isExemptPattern(resolved, profile.isIgnoredDirectory) || matchesAnyFile(resolved, files)) {
|
|
3825
4007
|
return void 0;
|
|
3826
4008
|
}
|
|
3827
4009
|
const tail = literalTail(resolved);
|
|
@@ -3842,16 +4024,16 @@ function checkInclude(configPath, entry, files) {
|
|
|
3842
4024
|
fix: "A pattern that matches nothing type-checks nothing, and `tsc --noEmit` still reports zero errors. Fix the glob or delete the entry."
|
|
3843
4025
|
};
|
|
3844
4026
|
}
|
|
3845
|
-
function checkOne(
|
|
3846
|
-
const text = readTextFile(join(cwd, configPath));
|
|
4027
|
+
function checkOne(profile, configPath) {
|
|
4028
|
+
const text = readTextFile(join(profile.cwd, configPath));
|
|
3847
4029
|
const parsed = text === void 0 ? void 0 : parseJsonc(text);
|
|
3848
4030
|
if (!isRecord(parsed)) {
|
|
3849
4031
|
return [];
|
|
3850
4032
|
}
|
|
3851
|
-
const findings = stringList(parsed["include"]).flatMap((entry) => checkInclude(configPath, entry,
|
|
4033
|
+
const findings = stringList(parsed["include"]).flatMap((entry) => checkInclude(configPath, entry, profile) ?? []);
|
|
3852
4034
|
for (const entry of stringList(parsed["files"])) {
|
|
3853
4035
|
const resolved = resolveFromConfig(configPath, entry);
|
|
3854
|
-
if (!files.includes(resolved)) {
|
|
4036
|
+
if (!profile.files.includes(resolved) && !isOutsideScan(resolved, profile.isIgnoredDirectory)) {
|
|
3855
4037
|
findings.push({
|
|
3856
4038
|
check: "tsconfig-file-missing",
|
|
3857
4039
|
severity: "error",
|
|
@@ -3865,7 +4047,7 @@ function checkOne(cwd, configPath, files) {
|
|
|
3865
4047
|
}
|
|
3866
4048
|
function checkTsconfigGlobs(profile) {
|
|
3867
4049
|
const configs = profile.files.filter((file) => TSCONFIG_NAME2.test(file));
|
|
3868
|
-
return configs.flatMap((config) => checkOne(profile
|
|
4050
|
+
return configs.flatMap((config) => checkOne(profile, config));
|
|
3869
4051
|
}
|
|
3870
4052
|
|
|
3871
4053
|
// src/cli/checks/unawaited-helper.ts
|
|
@@ -4375,6 +4557,10 @@ function planFor(target, content, profile, version, facts) {
|
|
|
4375
4557
|
}
|
|
4376
4558
|
return { target, existing, desired: applyManaged(existing ?? "", managedBlock(profile, version, facts)), note: note2 };
|
|
4377
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";
|
|
4378
4564
|
function statusOf(plan, check) {
|
|
4379
4565
|
if (plan.desired === void 0) {
|
|
4380
4566
|
return "skipped";
|
|
@@ -4385,7 +4571,7 @@ function statusOf(plan, check) {
|
|
|
4385
4571
|
if (plan.existing === plan.desired) {
|
|
4386
4572
|
return "unchanged";
|
|
4387
4573
|
}
|
|
4388
|
-
return check &&
|
|
4574
|
+
return check && isStampOnly(plan) ? "unchanged" : "updated";
|
|
4389
4575
|
}
|
|
4390
4576
|
function isSelected(path, only) {
|
|
4391
4577
|
return only === void 0 || only.some((entry) => {
|
|
@@ -4437,15 +4623,16 @@ function uninstallPlan(plan) {
|
|
|
4437
4623
|
function applyPlan(cwd, plan, options) {
|
|
4438
4624
|
const status = options.uninstall ? uninstallStatus(plan) : statusOf(plan, options.check);
|
|
4439
4625
|
const path = plan.target.path;
|
|
4626
|
+
const note2 = !options.uninstall && isStampOnly(plan) ? `${plan.note} \u2014 ${STAMP_ONLY_NOTE}` : plan.note;
|
|
4440
4627
|
if (options.check || options.dryRun || plan.desired === void 0 || status === "unchanged") {
|
|
4441
|
-
return { path, status, note:
|
|
4628
|
+
return { path, status, note: note2 };
|
|
4442
4629
|
}
|
|
4443
4630
|
if (plan.desired === "") {
|
|
4444
4631
|
removeFile(join(cwd, path));
|
|
4445
4632
|
} else {
|
|
4446
4633
|
writeTextFile(join(cwd, path), plan.desired);
|
|
4447
4634
|
}
|
|
4448
|
-
return { path, status, note:
|
|
4635
|
+
return { path, status, note: note2 };
|
|
4449
4636
|
}
|
|
4450
4637
|
function uninstallStatus(plan) {
|
|
4451
4638
|
if (plan.desired === void 0 || plan.desired === plan.existing) {
|
|
@@ -6087,8 +6274,8 @@ function configCode(graph) {
|
|
|
6087
6274
|
}
|
|
6088
6275
|
return configs;
|
|
6089
6276
|
}
|
|
6090
|
-
function configDeclares(graph,
|
|
6091
|
-
return configCode(graph).some((code) =>
|
|
6277
|
+
function configDeclares(graph, setting2) {
|
|
6278
|
+
return configCode(graph).some((code) => setting2.test(code));
|
|
6092
6279
|
}
|
|
6093
6280
|
function declaresNoIsolation(graph) {
|
|
6094
6281
|
return configDeclares(graph, NO_ISOLATION);
|
|
@@ -6948,6 +7135,10 @@ function rejectFlags(args, command, io) {
|
|
|
6948
7135
|
io.err(
|
|
6949
7136
|
`\`${command}\` accepts ${[...accepted, ...COMMON_FLAGS].sort((a, b) => a.localeCompare(b)).map((name) => `--${name}`).join(", ")}.`
|
|
6950
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
|
+
}
|
|
6951
7142
|
return true;
|
|
6952
7143
|
}
|
|
6953
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",
|