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 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` | Review included, excluded, and ambiguous source files |
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
- | `SUPERCOV_BUILD_COMMAND` | The build a JavaScript project's tests need, as words or a JSON array, such as `yarn workspace web build` in a monorepo whose root `build` builds every package. Without it Supercov runs the project's `build` script, through the package manager that started the tests, when the tests need built output |
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
 
@@ -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
 
@@ -1,8 +1,9 @@
1
1
  # Speed and storage
2
2
 
3
- A Supercov run includes your test command, an instrumented build, and evidence
4
- publication. The first pass is usually the slowest; repeated passes can reuse
5
- the isolated build when the relevant inputs have not changed.
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
 
@@ -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, and an instrumented build. Repeated runs can reuse the
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. Two
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 automatically). Changes inside any
49
- `node_modules` directory are neither applied nor reported: dependency trees are
50
- not command outputs.
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
- When source, dependencies, configuration, toolchain, and build mode still
71
- match, Supercov can reuse the isolated instrumented build. Test-only changes do
72
- not force an unrelated application rebuild.
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.0.4",
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": "3.0.4",
122
- "@supercov/cli-darwin-x64": "3.0.4",
123
- "@supercov/cli-linux-arm64-gnu": "3.0.4",
124
- "@supercov/cli-linux-arm64-musl": "3.0.4",
125
- "@supercov/cli-linux-x64-gnu": "3.0.4",
126
- "@supercov/cli-linux-x64-musl": "3.0.4",
127
- "@supercov/cli-win32-arm64": "3.0.4",
128
- "@supercov/cli-win32-x64": "3.0.4"
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
- register(new URL("./resolve-loader.mjs", import.meta.url));
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 { resolve as resolvePath } from "node:path";
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,