supercov 3.0.4 → 4.0.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/docs/cli.md +25 -3
- package/docs/coverage-model.md +37 -0
- package/docs/performance.md +8 -5
- package/docs/troubleshooting.md +23 -5
- package/docs/workspace-isolation.md +38 -7
- package/package.json +10 -9
- package/runtime/javascript/register.mjs +28 -1
- package/runtime/javascript/resolve-loader.mjs +38 -2
- package/runtime/javascript/runtime.mjs +49 -1
package/docs/cli.md
CHANGED
|
@@ -142,14 +142,14 @@ npx supercov runs <run-id> [query] [options]
|
|
|
142
142
|
| --- | --- |
|
|
143
143
|
| no query | Read the overall result, test outcome, completeness, and timings |
|
|
144
144
|
| `gaps` | See only files with uncovered behavior or measurement limits |
|
|
145
|
-
| `files` | See every included file, including fully covered files |
|
|
145
|
+
| `files` | See every included file, including fully covered files; `--group dir` shows coverage by directory, split by test kind |
|
|
146
146
|
| `file <path>` | Inspect the open obligations in one file |
|
|
147
147
|
| `decision <id \| path:line>` | Understand missing boolean outcomes and MC/DC witnesses |
|
|
148
148
|
| `line <path:line>` | See one line's state, obligations, and covering tests |
|
|
149
149
|
| `test <id \| name>` | See the coverage attributed to one test |
|
|
150
150
|
| `kinds` | Group coverage by test level, such as unit or E2E |
|
|
151
151
|
| `runners` | Group coverage by test runner |
|
|
152
|
-
| `scope` |
|
|
152
|
+
| `scope` | See what is source and what is not, by directory and reason; `--files` lists every file |
|
|
153
153
|
| `assertions` | Read the share of executed statements the tests assert, and the ones they do not; `assertions assess` works it out |
|
|
154
154
|
| `source <path>` | Read matching current project source with line numbers |
|
|
155
155
|
| `minimize` | Find a small test subset that preserves a coverage target |
|
|
@@ -164,6 +164,27 @@ npx supercov runs latest line app/routes/checkout.ts:57
|
|
|
164
164
|
npx supercov runs latest test "checkout retry"
|
|
165
165
|
```
|
|
166
166
|
|
|
167
|
+
To see where the coverage is, read it by directory. Each row is a directory,
|
|
168
|
+
with what it holds and how much of it each kind of test covers; `--depth`
|
|
169
|
+
keeps more levels, and `--metric` picks branches, functions, statements or
|
|
170
|
+
MC/DC instead of lines:
|
|
171
|
+
|
|
172
|
+
```sh supercov
|
|
173
|
+
npx supercov runs latest files --group dir
|
|
174
|
+
npx supercov runs latest files --group dir --depth 2 --metric branches
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
Directory Files Lines e2e unit All
|
|
179
|
+
app 4 15 86.67% 0.00% 100.00%
|
|
180
|
+
lib 1 10 90.00% 40.00% 90.00%
|
|
181
|
+
components 1 2 100.00% 0.00% 100.00%
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
In `--json`, every file of `files` and `gaps` carries `totals` and `covered`
|
|
185
|
+
for each metric beside what is missing, so a percentage can be worked out for
|
|
186
|
+
any file or set of files.
|
|
187
|
+
|
|
167
188
|
Run any query with `--help` to see only the options valid for that query:
|
|
168
189
|
|
|
169
190
|
```sh supercov
|
|
@@ -509,7 +530,8 @@ terminal or offline environment after the package has been downloaded.
|
|
|
509
530
|
| --- | --- |
|
|
510
531
|
| `SUPERCOV_SOURCE_ROOTS` | Comma-separated directories or files that hold your own code, in any language; everything else is left out |
|
|
511
532
|
| `SUPERCOV_TEST_KIND` | Label the wrapped command as a test level such as `unit` or `e2e` |
|
|
512
|
-
| `
|
|
533
|
+
| `SUPERCOV_SOURCE_TOOLS` | Comma-separated names of tools the test command runs that read source as text, such as a linter or a spell checker, besides Biome, oxlint, dprint, cspell and knip. Each is run on your source as you wrote it instead of on the instrumented copy |
|
|
534
|
+
| `SUPERCOV_KEEP_WORKSPACE` | Set to `1` to leave the instrumented copy of the project in `.supercov/workspaces/` after the run, to inspect what the command ran |
|
|
513
535
|
|
|
514
536
|
Examples:
|
|
515
537
|
|
package/docs/coverage-model.md
CHANGED
|
@@ -94,6 +94,18 @@ Both are useful:
|
|
|
94
94
|
- exact evidence helps you inspect or minimize individual tests;
|
|
95
95
|
- aggregate evidence still shows whether the whole suite reached the source.
|
|
96
96
|
|
|
97
|
+
Code that runs while no test is running counts in the run's total and in no
|
|
98
|
+
test's: a server starting before the first test, modules loading, work between
|
|
99
|
+
tests. When a run has any, the summary's `By test kind` table ends with a
|
|
100
|
+
`no test` row for what only that covered, which is why a run with one kind of
|
|
101
|
+
test can show a total above that kind's own:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
By test kind
|
|
105
|
+
e2e 3 test(s) lines 80.00% branches 66.67% MC/DC 25.00%
|
|
106
|
+
no test lines 17.14% branches 0.00% MC/DC 0.00%
|
|
107
|
+
```
|
|
108
|
+
|
|
97
109
|
See [Supported suites](supported-suites.md) for the attribution available from
|
|
98
110
|
each runner.
|
|
99
111
|
|
|
@@ -128,6 +140,17 @@ If the summary reports ambiguous source scope, inspect it:
|
|
|
128
140
|
npx supercov runs latest scope
|
|
129
141
|
```
|
|
130
142
|
|
|
143
|
+
It prints the scope by directory and reason, the ambiguous places first, and
|
|
144
|
+
the `SUPERCOV_SOURCE_ROOTS` value that would settle them. The summary names
|
|
145
|
+
the same places under `Instrumentation`. Add `--files` to list every file.
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
Files Status Directory Reason
|
|
149
|
+
79 AMBIGUOUS components unclassified first-party source
|
|
150
|
+
227 INCLUDED app discovered package source root
|
|
151
|
+
311 EXCLUDED tests test or fixture source
|
|
152
|
+
```
|
|
153
|
+
|
|
131
154
|
When first-party source lives in unusual directories, declare it explicitly:
|
|
132
155
|
|
|
133
156
|
```sh supercov
|
|
@@ -145,6 +168,20 @@ without a conventional source directory is measured as a whole. Functions passed
|
|
|
145
168
|
to compile-time style macros (`stylex.create(...)`) are left as written because
|
|
146
169
|
the bundler consumes them at build time; nothing about them runs.
|
|
147
170
|
|
|
171
|
+
Inside the roots, tests, fixtures, tool scripts and configuration files are
|
|
172
|
+
still recognised by their path and left out. A root you name settles what it
|
|
173
|
+
names: with `SUPERCOV_SOURCE_ROOTS=src,scripts` the `scripts/` directory is
|
|
174
|
+
source, and so is `src/test-utils` when it is named as a root. Only what lies
|
|
175
|
+
below a named root is still judged by its path, and a root that is a single
|
|
176
|
+
file is always measured.
|
|
177
|
+
|
|
178
|
+
In a Next.js application, root `components/` and `pages/`, and the root files
|
|
179
|
+
Next.js reads by name (`proxy`, `middleware`, `instrumentation`) are source
|
|
180
|
+
without being declared. Under `app/` a folder is a URL segment, so the files
|
|
181
|
+
the App Router serves (`route.ts`, `page.tsx`, `layout.tsx` and the rest) are
|
|
182
|
+
source whatever their folders are called: `app/api/wordpress/test/route.ts` is
|
|
183
|
+
the handler of `/api/wordpress/test`, not a test.
|
|
184
|
+
|
|
148
185
|
Choose roots that describe code the repository owns. Do not include dependencies
|
|
149
186
|
or generated output merely to make a warning disappear.
|
|
150
187
|
|
package/docs/performance.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Speed and storage
|
|
2
2
|
|
|
3
|
-
A Supercov run includes your test command
|
|
4
|
-
|
|
5
|
-
the
|
|
3
|
+
A Supercov run includes your test command and evidence publication, and for a
|
|
4
|
+
compiled language an instrumented build, which repeated passes can reuse when
|
|
5
|
+
the relevant inputs have not changed. A JavaScript project has no such build:
|
|
6
|
+
the command runs as given, and a build inside it is part of the command's time.
|
|
6
7
|
|
|
7
8
|
## See where the time went
|
|
8
9
|
|
|
@@ -17,7 +18,7 @@ The summary separates:
|
|
|
17
18
|
| Initialization | Recovery, project discovery, and input checks |
|
|
18
19
|
| Workspace preparation | Refreshing the isolated project copy |
|
|
19
20
|
| Adapter setup | Preparing the runner integration |
|
|
20
|
-
| Instrumented build | Building measured source, or almost nothing on a cache hit |
|
|
21
|
+
| Instrumented build | Building measured source for a compiled language, or almost nothing on a cache hit; 0 for JavaScript |
|
|
21
22
|
| Test command | The wrapped command, including browser, VM, or remote latency |
|
|
22
23
|
| Evidence publication | Validating and storing the completed run |
|
|
23
24
|
|
|
@@ -41,7 +42,9 @@ before Supercov starts and is not coverage-engine overhead.
|
|
|
41
42
|
|
|
42
43
|
Supercov reuses an instrumented build only when source, dependencies,
|
|
43
44
|
configuration, toolchain, build mode, and instrumenter identity match. A
|
|
44
|
-
possible mismatch triggers a fresh build rather than risking stale coverage.
|
|
45
|
+
possible mismatch triggers a fresh build rather than risking stale coverage. A
|
|
46
|
+
build inside a JavaScript project's command is the command's own and runs every
|
|
47
|
+
time, as it does without Supercov.
|
|
45
48
|
|
|
46
49
|
## Use focused runs carefully
|
|
47
50
|
|
package/docs/troubleshooting.md
CHANGED
|
@@ -32,7 +32,8 @@ unusual directory, declare the source roots explicitly:
|
|
|
32
32
|
SUPERCOV_SOURCE_ROOTS=src,app npx supercov -- npm test
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
Then inspect what Supercov included and excluded
|
|
35
|
+
Then inspect what Supercov included and excluded, by directory, or file by
|
|
36
|
+
file with `--files`:
|
|
36
37
|
|
|
37
38
|
```sh supercov
|
|
38
39
|
npx supercov runs latest scope
|
|
@@ -41,6 +42,10 @@ npx supercov runs latest scope
|
|
|
41
42
|
Do not broaden the roots to dependencies or generated output merely to remove a
|
|
42
43
|
warning. The goal is an honest boundary around code the repository owns.
|
|
43
44
|
|
|
45
|
+
A file `scope` lists as `test or fixture source` or `conventional tool script`
|
|
46
|
+
was recognised by its path. If it is your code, name its directory, or the file
|
|
47
|
+
itself, as a root: a named root is source whatever it is called.
|
|
48
|
+
|
|
44
49
|
## Too much is measured
|
|
45
50
|
|
|
46
51
|
Vendored or generated code beside your own is measured like the rest of the
|
|
@@ -137,6 +142,21 @@ Its lines, methods and simple branches stay measured; everything that needs a
|
|
|
137
142
|
probe is declared as a measurement limit for that file. Please report the file,
|
|
138
143
|
since Supercov aims to instrument every Ruby source correctly.
|
|
139
144
|
|
|
145
|
+
## The tests cannot find `dist/` or a production build
|
|
146
|
+
|
|
147
|
+
Supercov runs your command in a copy of the project, and the copy starts
|
|
148
|
+
without build output: `dist/`, `build/`, `.next/` and the like are not copied,
|
|
149
|
+
because they hold code that was never instrumented. Supercov does not run your
|
|
150
|
+
`build` script for you (earlier versions did). If the tests read build output, put
|
|
151
|
+
the build in the command:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
npx supercov -- sh -c "npm run build && npm test"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
A `pretest` script, or a Playwright `webServer` that builds, does the same. The
|
|
158
|
+
build then compiles the instrumented copy, and what the tests run is measured.
|
|
159
|
+
|
|
140
160
|
## A test that reads your source fails under Supercov
|
|
141
161
|
|
|
142
162
|
A test that opens your source files and asserts on their text sees the probes,
|
|
@@ -215,10 +235,8 @@ runs remain intact.
|
|
|
215
235
|
|
|
216
236
|
## The first run is slow
|
|
217
237
|
|
|
218
|
-
The first run may include the npm download, browser or toolchain startup,
|
|
219
|
-
workspace creation
|
|
220
|
-
isolated build when source, dependencies, configuration, toolchain, and build
|
|
221
|
-
mode still match.
|
|
238
|
+
The first run may include the npm download, browser or toolchain startup, and
|
|
239
|
+
workspace creation.
|
|
222
240
|
|
|
223
241
|
Inspect the recorded phases with:
|
|
224
242
|
|
|
@@ -34,6 +34,15 @@ and the instrumented copy is not what you wrote:
|
|
|
34
34
|
|
|
35
35
|
- Linters, formatters and `tsc --noEmit` (ESLint, Prettier, standard, xo and
|
|
36
36
|
the like) read each file as you wrote it, and don't see `.supercov`.
|
|
37
|
+
- The type check `next build` runs reads each file as you wrote it too, while
|
|
38
|
+
the build itself compiles the instrumented copy.
|
|
39
|
+
- Biome, oxlint, dprint, cspell and knip run on your source as you wrote it:
|
|
40
|
+
the copy's `node_modules/.bin` starts each of them in a view of the copy
|
|
41
|
+
that holds every file in its original text. A file the tool writes there
|
|
42
|
+
(a report, a cache) is the command's output like any other. A change it
|
|
43
|
+
makes to a source file (`--write`, `--fix`) is not applied during a measured
|
|
44
|
+
run, and the run says so. To have another tool that reads source as text run
|
|
45
|
+
the same way, name it: `SUPERCOV_SOURCE_TOOLS=typos,stylelint`.
|
|
37
46
|
- Coverage tools the command runs itself (tap, c8, nyc, Jest's and Vitest's
|
|
38
47
|
`--coverage`) still collect and report coverage. They measure the
|
|
39
48
|
instrumented copy, and report close to what they report without Supercov,
|
|
@@ -42,12 +51,33 @@ and the instrumented copy is not what you wrote:
|
|
|
42
51
|
|
|
43
52
|
Files the wrapped command creates or changes inside the isolated workspace are
|
|
44
53
|
synced back to the project after the run, so `supercov -- npm test -- -u`
|
|
45
|
-
updates snapshots in the repository exactly as `npm test -- -u` would.
|
|
54
|
+
updates snapshots in the repository exactly as `npm test -- -u` would. Three
|
|
46
55
|
exceptions are reported instead of applied: changes the command makes to
|
|
47
56
|
instrumented source files (the instrumented copies must never overwrite your
|
|
48
|
-
sources) and deletions (never propagated
|
|
49
|
-
|
|
50
|
-
not
|
|
57
|
+
sources), a build the command made from them, and deletions (never propagated
|
|
58
|
+
automatically). Such a build stays behind whole: when a file in `.next/`,
|
|
59
|
+
`dist/` or another directory that git ignores or the project does not have was
|
|
60
|
+
built from instrumented source, nothing in that directory is copied, so the
|
|
61
|
+
project never holds an instrumented build. Changes inside any `node_modules`
|
|
62
|
+
directory are neither applied nor reported: dependency trees are not command
|
|
63
|
+
outputs.
|
|
64
|
+
|
|
65
|
+
The run prints where the synced files went, what stayed behind and what the
|
|
66
|
+
command deleted, by top-level directory (`test-results/ 6`, `.next/ 2547`).
|
|
67
|
+
|
|
68
|
+
The copy starts without build output: `dist/`, `build/`, `.next/` and the
|
|
69
|
+
like are not copied, because they hold code that was never instrumented.
|
|
70
|
+
Supercov runs the command you give it and no build of its own, so a suite that
|
|
71
|
+
needs build output has the build in its command:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npx supercov -- sh -c "npm run build && npm test"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
A `pretest` script, or a Playwright `webServer` that builds, does the same.
|
|
78
|
+
When a run fails in a project that has a `build` script the command does not
|
|
79
|
+
reach, it says this. `SUPERCOV_KEEP_WORKSPACE=1` leaves the instrumented copy
|
|
80
|
+
in `.supercov/workspaces/` after a run, to inspect.
|
|
51
81
|
|
|
52
82
|
## Files Supercov creates
|
|
53
83
|
|
|
@@ -67,9 +97,10 @@ fallback instead of adopting or deleting it.
|
|
|
67
97
|
|
|
68
98
|
## Repeated runs and the build cache
|
|
69
99
|
|
|
70
|
-
|
|
71
|
-
match, Supercov can reuse the isolated instrumented build
|
|
72
|
-
not force an unrelated
|
|
100
|
+
For a compiled language, when source, dependencies, configuration, toolchain,
|
|
101
|
+
and build mode still match, Supercov can reuse the isolated instrumented build,
|
|
102
|
+
so test-only changes do not force an unrelated rebuild. A JavaScript project's
|
|
103
|
+
build is part of its command and runs every time.
|
|
73
104
|
|
|
74
105
|
Workspace refreshes are prepared separately and become active only when
|
|
75
106
|
complete. An interrupted refresh does not replace the last complete cache with
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supercov",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Coverage, security and code quality for coding agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"coverage",
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"sync:rust-assets": "node scripts/sync-rust-package-assets.mjs",
|
|
67
67
|
"test:rust-assets": "node scripts/sync-rust-package-assets.mjs --check",
|
|
68
68
|
"test": "cargo test --workspace",
|
|
69
|
+
"test:next-playwright": "cargo build -p supercov && npm --prefix tests/fixtures/next-playwright ci && node scripts/next-playwright-integration.mjs",
|
|
69
70
|
"test:react": "cargo build -p supercov && node scripts/rust-vitest-projects-integration.mjs && npm --prefix examples/react-verification ci && npm --prefix examples/react-verification run demo && node scripts/react-hydration-compatibility.mjs",
|
|
70
71
|
"test:release-tooling": "node --test tests/release/*.test.mjs",
|
|
71
72
|
"test:runtime": "node --test tests/runtime/*.test.mjs",
|
|
@@ -118,14 +119,14 @@
|
|
|
118
119
|
"test:windows-tls": "node scripts/windows-tls-test.mjs"
|
|
119
120
|
},
|
|
120
121
|
"optionalDependencies": {
|
|
121
|
-
"@supercov/cli-darwin-arm64": "
|
|
122
|
-
"@supercov/cli-darwin-x64": "
|
|
123
|
-
"@supercov/cli-linux-arm64-gnu": "
|
|
124
|
-
"@supercov/cli-linux-arm64-musl": "
|
|
125
|
-
"@supercov/cli-linux-x64-gnu": "
|
|
126
|
-
"@supercov/cli-linux-x64-musl": "
|
|
127
|
-
"@supercov/cli-win32-arm64": "
|
|
128
|
-
"@supercov/cli-win32-x64": "
|
|
122
|
+
"@supercov/cli-darwin-arm64": "4.0.0",
|
|
123
|
+
"@supercov/cli-darwin-x64": "4.0.0",
|
|
124
|
+
"@supercov/cli-linux-arm64-gnu": "4.0.0",
|
|
125
|
+
"@supercov/cli-linux-arm64-musl": "4.0.0",
|
|
126
|
+
"@supercov/cli-linux-x64-gnu": "4.0.0",
|
|
127
|
+
"@supercov/cli-linux-x64-musl": "4.0.0",
|
|
128
|
+
"@supercov/cli-win32-arm64": "4.0.0",
|
|
129
|
+
"@supercov/cli-win32-x64": "4.0.0"
|
|
129
130
|
},
|
|
130
131
|
"peerDependencies": {
|
|
131
132
|
"@playwright/test": ">=1.55.0",
|
|
@@ -178,6 +178,24 @@ const isAnalysisEntrypoint = new RegExp(`/node_modules/(?:\\.bin/(?:${analysisTo
|
|
|
178
178
|
process.argv.includes("--noEmit"));
|
|
179
179
|
if (isAnalysisEntrypoint)
|
|
180
180
|
installAuthoredSourceView();
|
|
181
|
+
// `next build` type-checks in a worker of its own instead of running tsc. A
|
|
182
|
+
// probe in a condition stops TypeScript narrowing through it, so an inferred
|
|
183
|
+
// return type widens, and a file outside the source roots that calls the
|
|
184
|
+
// function fails the build on code nobody wrote ("'client' is possibly
|
|
185
|
+
// 'null'"). Only that worker gets the authored view: the bundler, and `next
|
|
186
|
+
// dev`, which sets TypeScript up in its main process, must read the copies.
|
|
187
|
+
else if (/\/node_modules\/next\/dist\/compiled\/jest-worker\/processChild\.js$/.test(entrypoint) ||
|
|
188
|
+
(!workerThreads.isMainThread && /\/node_modules\/(?:\.bin\/next$|next\/dist\/)/.test(entrypoint))) {
|
|
189
|
+
const compile = Module.prototype._compile;
|
|
190
|
+
let installed = false;
|
|
191
|
+
Module.prototype._compile = function _compile(content, filename, ...rest) {
|
|
192
|
+
if (!installed && /[\\/]next[\\/]dist[\\/]lib[\\/]verify-typescript-setup\.js$/.test(filename)) {
|
|
193
|
+
installed = true;
|
|
194
|
+
installAuthoredSourceView();
|
|
195
|
+
}
|
|
196
|
+
return Reflect.apply(compile, this, [content, filename, ...rest]);
|
|
197
|
+
};
|
|
198
|
+
}
|
|
181
199
|
function installAuthoredSourceView() {
|
|
182
200
|
let authored;
|
|
183
201
|
try {
|
|
@@ -372,7 +390,16 @@ function skipCoverageThresholds(tool) {
|
|
|
372
390
|
console.error(`[supercov] ${tool}'s coverage thresholds stay as configured`, error);
|
|
373
391
|
}
|
|
374
392
|
}
|
|
375
|
-
|
|
393
|
+
// A handler for `.ts` in the CommonJS loader that is not Node's own is a
|
|
394
|
+
// transpiler's require hook: ts-node/register, @swc/register and the like.
|
|
395
|
+
// It was there before this preload, and it is how the project's TypeScript
|
|
396
|
+
// loads. Any `--import` sends the entry point through the module loader
|
|
397
|
+
// instead, where such a file is read as an ES module and the hook never
|
|
398
|
+
// sees it; the loader is told so it can hand those files back.
|
|
399
|
+
const typescriptHandler = Module._extensions[".ts"];
|
|
400
|
+
register(new URL("./resolve-loader.mjs", import.meta.url), {
|
|
401
|
+
data: { typescriptRequireHook: typeof typescriptHandler === "function" && typescriptHandler.name !== "loadTS" },
|
|
402
|
+
});
|
|
376
403
|
if (process.env.SUPERCOV_DEBUG === "1") {
|
|
377
404
|
console.error("[supercov] preload", { entrypoint });
|
|
378
405
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve as resolvePath } from "node:path";
|
|
2
3
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
4
|
const GENERATED_TARGET = "__SUPERCOV_PLAYWRIGHT_MODULE__";
|
|
4
5
|
const TARGET = process.env.SUPERCOV_PLAYWRIGHT_MODULE ??
|
|
@@ -25,6 +26,41 @@ function belongsToProject(parentURL) {
|
|
|
25
26
|
const generatedURL = `${projectURL}.supercov/`;
|
|
26
27
|
return (parentURL.startsWith(projectURL) && !parentURL.startsWith(generatedURL));
|
|
27
28
|
}
|
|
29
|
+
let typescriptRequireHook = false;
|
|
30
|
+
export function initialize(data) {
|
|
31
|
+
typescriptRequireHook = data?.typescriptRequireHook === true;
|
|
32
|
+
}
|
|
33
|
+
const packageTypes = new Map();
|
|
34
|
+
function packageType(directory) {
|
|
35
|
+
if (packageTypes.has(directory))
|
|
36
|
+
return packageTypes.get(directory);
|
|
37
|
+
let type;
|
|
38
|
+
try {
|
|
39
|
+
type = JSON.parse(readFileSync(resolvePath(directory, "package.json"), "utf8")).type ?? "commonjs";
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
const parent = dirname(directory);
|
|
43
|
+
type = parent === directory ? "commonjs" : packageType(parent);
|
|
44
|
+
}
|
|
45
|
+
packageTypes.set(directory, type);
|
|
46
|
+
return type;
|
|
47
|
+
}
|
|
48
|
+
// `node -r ts-node/register --test tests/a.test.ts` in a package without
|
|
49
|
+
// `"type"` passed alone and failed here with ERR_MODULE_NOT_FOUND on an
|
|
50
|
+
// extensionless import: the preload made Node load the entry point as an ES
|
|
51
|
+
// module, past the require hook that compiles it. A TypeScript file of a
|
|
52
|
+
// CommonJS package is given back to the CommonJS loader, where that hook is.
|
|
53
|
+
function throughRequireHook(resolved) {
|
|
54
|
+
if (!typescriptRequireHook || !resolved?.url?.startsWith("file:") || resolved.url.includes("/node_modules/"))
|
|
55
|
+
return resolved;
|
|
56
|
+
if (!/\.(?:ts|tsx|cts)$/.test(resolved.url) || /\.d\.c?ts$/.test(resolved.url))
|
|
57
|
+
return resolved;
|
|
58
|
+
if (resolved.format === "module" || resolved.format === "module-typescript")
|
|
59
|
+
return resolved;
|
|
60
|
+
if (!resolved.url.endsWith(".cts") && packageType(dirname(fileURLToPath(resolved.url))) === "module")
|
|
61
|
+
return resolved;
|
|
62
|
+
return { ...resolved, format: "commonjs" };
|
|
63
|
+
}
|
|
28
64
|
export async function resolve(specifier, context, nextResolve) {
|
|
29
65
|
// Some transpilers preserve the source-relative runtime import while moving
|
|
30
66
|
// only the transformed application file to an output directory. The
|
|
@@ -73,7 +109,7 @@ export async function resolve(specifier, context, nextResolve) {
|
|
|
73
109
|
}
|
|
74
110
|
return nextResolve(REPLACEMENT, context);
|
|
75
111
|
}
|
|
76
|
-
return nextResolve(specifier, context);
|
|
112
|
+
return throughRequireHook(await nextResolve(specifier, context));
|
|
77
113
|
}
|
|
78
114
|
|
|
79
115
|
// Node's own test coverage filters files by `--test-coverage-include` and
|
|
@@ -890,7 +890,7 @@ function cleanInstrumentationStack(error) {
|
|
|
890
890
|
if (!error || typeof error !== "object" || typeof error.stack !== "string")
|
|
891
891
|
return error;
|
|
892
892
|
const lines = error.stack.split("\n");
|
|
893
|
-
const visible = lines.filter((line, index) => index === 0 || !/[\\/]\.supercov[\\/](?:playwright|nodeTest|vitest|runtime|launchSupervisor|nodeAssert|nodeAssertStrict|nodeAssertAdapter|register|resolve-loader)\.(?:js|mjs)(?::|\))/u.test(line));
|
|
893
|
+
const visible = lines.filter((line, index) => index === 0 || !/[\\/]\.supercov[\\/](?:node_modules[\\/])?(?:playwright|nodeTest|vitest|runtime|launchSupervisor|nodeAssert|nodeAssertStrict|nodeAssertAdapter|register|resolve-loader)\.(?:js|mjs)(?::|\))/u.test(line));
|
|
894
894
|
if (visible.length !== lines.length) {
|
|
895
895
|
try {
|
|
896
896
|
error.stack = visible.join("\n");
|
|
@@ -1402,6 +1402,10 @@ function registerProbeV2(definition) {
|
|
|
1402
1402
|
// A selection's outcomes -- short falsy, short truthy, right falsy, right
|
|
1403
1403
|
// truthy -- are four consecutive points per site from the first index.
|
|
1404
1404
|
selectionPoints: definition.selectionPoints,
|
|
1405
|
+
// What selectEndV2 and selectNextV2 read, for each tree of more than two
|
|
1406
|
+
// leaves.
|
|
1407
|
+
selectionSteps: (definition.selectionTrees || []).map((tree) => tree[0]),
|
|
1408
|
+
selectionChoices: (definition.selectionTrees || []).map((tree) => tree[1]),
|
|
1405
1409
|
none: optionalCallEmptySpread
|
|
1406
1410
|
};
|
|
1407
1411
|
state.probeV2Files.add(file);
|
|
@@ -1626,6 +1630,44 @@ function selectPathV2(file, value) {
|
|
|
1626
1630
|
}
|
|
1627
1631
|
return value;
|
|
1628
1632
|
}
|
|
1633
|
+
// A selection tree whose leaves stay the program's own expressions: each is
|
|
1634
|
+
// the last operand of a comma that names it in the site's frame, so a type
|
|
1635
|
+
// checker reading the instrumented copy narrows through `a && a.b` as it does
|
|
1636
|
+
// in the source, and the tree's value is recorded here, once, for the leaf
|
|
1637
|
+
// that produced it. What a leaf before the last decided on the way is known
|
|
1638
|
+
// from where evaluation went next, and is recorded there as plain hits.
|
|
1639
|
+
// Two leaves: the left one is last only when it decided the result.
|
|
1640
|
+
function selectEnd2V2(file, first, value, right) {
|
|
1641
|
+
coverageHitV2(file, first + (right ? 2 : 0) + (value ? 1 : 0));
|
|
1642
|
+
return value;
|
|
1643
|
+
}
|
|
1644
|
+
// More: the leaf's steps, the ones selectPathV2 takes as arguments, come from
|
|
1645
|
+
// the file's table, whose entry for a tree is `[steps of each leaf, for each
|
|
1646
|
+
// leaf the points each leaf before it has decided by then]`.
|
|
1647
|
+
function selectEndV2(file, tree, value, leaf) {
|
|
1648
|
+
const steps = file.selectionSteps[tree][leaf];
|
|
1649
|
+
for (let index = 0; index < steps.length; index += 1) {
|
|
1650
|
+
const step = steps[index];
|
|
1651
|
+
const code = step & 3;
|
|
1652
|
+
const first = (step - code) / 4;
|
|
1653
|
+
if (code === 3)
|
|
1654
|
+
coverageHitV2(file, value ? first + 3 : first + 2);
|
|
1655
|
+
else if (code === 0 ? value : code === 1 ? !value : value !== null && value !== void 0)
|
|
1656
|
+
coverageHitV2(file, value ? first + 1 : first);
|
|
1657
|
+
else
|
|
1658
|
+
break;
|
|
1659
|
+
}
|
|
1660
|
+
return value;
|
|
1661
|
+
}
|
|
1662
|
+
// Where one of several leaves can come before a leaf, the frame says which.
|
|
1663
|
+
// Written out in the program as a choice, it would be a branch of the user's
|
|
1664
|
+
// line to a coverage tool the tests run.
|
|
1665
|
+
function selectNextV2(file, tree, from, to) {
|
|
1666
|
+
const points = file.selectionChoices[tree][to][from];
|
|
1667
|
+
if (points)
|
|
1668
|
+
for (let index = 0; index < points.length; index += 1)
|
|
1669
|
+
coverageHitV2(file, points[index]);
|
|
1670
|
+
}
|
|
1629
1671
|
// `x ||= y`, `x &&= y`, `x ??= y` keep their operator and their single
|
|
1630
1672
|
// evaluation of the target: the right side goes through selectRightV2 (or the
|
|
1631
1673
|
// named form, for an anonymous function the assignment would have named), and
|
|
@@ -1851,6 +1893,9 @@ const directRuntimeApi = {
|
|
|
1851
1893
|
selectNamedRightV2,
|
|
1852
1894
|
selectAssignEndV2,
|
|
1853
1895
|
selectPathV2,
|
|
1896
|
+
selectEnd2V2,
|
|
1897
|
+
selectEndV2,
|
|
1898
|
+
selectNextV2,
|
|
1854
1899
|
takeNodeAssertionPhases,
|
|
1855
1900
|
tryBegin,
|
|
1856
1901
|
tryCatch,
|
|
@@ -1913,6 +1958,9 @@ export {
|
|
|
1913
1958
|
selectNamedRightV2,
|
|
1914
1959
|
selectAssignEndV2,
|
|
1915
1960
|
selectPathV2,
|
|
1961
|
+
selectEnd2V2,
|
|
1962
|
+
selectEndV2,
|
|
1963
|
+
selectNextV2,
|
|
1916
1964
|
takeNodeAssertionPhases,
|
|
1917
1965
|
tryBegin,
|
|
1918
1966
|
tryCatch,
|