supercov 4.16.0 → 4.17.1

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.
@@ -133,6 +133,11 @@ never asked; the statement view (`assertions <file>:<line>`) says how many were,
133
133
  and names the test files none of whose tests were asked, which is where to look
134
134
  before writing a test that may already exist.
135
135
 
136
+ A statement at a module's top level in JavaScript, TypeScript or Python runs
137
+ once, when the first test loads the module, and is recorded for that test
138
+ alone. Every test that runs the module's functions relies on it too, so those
139
+ tests count as running it, after the one that loaded it.
140
+
136
141
  An asserted statement is a judgment too: some test was judged to fail if it
137
142
  changed, and the pass stops at the first that is. How strong that answer was
138
143
  says how far to trust it. Checked by applying each change and running the
package/docs/cli.md CHANGED
@@ -246,6 +246,7 @@ after source changes.
246
246
  npx supercov runs <run-id> assertions assess --dry-run
247
247
  npx supercov runs <run-id> assertions assess
248
248
  npx supercov runs <run-id> assertions assess --refresh # ask again instead of reusing saved answers
249
+ npx supercov runs <run-id> assertions assess --tag @e2e --dry-run
249
250
  npx supercov runs <run-id> assertions
250
251
  npx supercov runs <run-id> assertions --all --json
251
252
  ```
@@ -258,6 +259,13 @@ and JSON reports expose it under `data.assertionCoverage`. See
258
259
  [Assertion coverage](assertions.md) for what counts as asserted and how answers
259
260
  are reused across runs.
260
261
 
262
+ `--kind`, `--runner`, `--tag`, `--title` and `--test-file` assess or read some of
263
+ the tests, as they narrow every query. `assess --tag @e2e` asks only those tests
264
+ about the statements they ran and saves the result apart from the run's whole
265
+ assessment; its answers are kept, so a later `assess` asks only what is left.
266
+ `assertions --tag @e2e` reads what those tests assert: a statement that only
267
+ other tests catch is not asserted there.
268
+
261
269
  ## Fail CI below a coverage floor
262
270
 
263
271
  ```sh supercov-example
@@ -621,7 +629,10 @@ pass in the run is listed regardless.
621
629
  per line, for a runner's filter. A Vitest, Playwright or node:test test inside
622
630
  `describe` blocks is printed as the blocks and the test joined by spaces
623
631
  (`small charges`), which is what `vitest -t`, `--grep` and
624
- `--test-name-pattern` match. A helper beside the tests, or source outside what is
632
+ `--test-name-pattern` match. `--kind`, `--runner`, `--tag`, `--title` and
633
+ `--test-file` list only the affected tests they pick: from a run that merged two
634
+ runners' suites, `tests affected --names --runner playwright` gives Playwright
635
+ only its own tests. A helper beside the tests, or source outside what is
625
636
  measured, that changed affects the tests whose files import it (a JavaScript or
626
637
  TypeScript helper that only gained functions affects none), and every test
627
638
  where Supercov cannot read the imports. A dependency,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supercov",
3
- "version": "4.16.0",
3
+ "version": "4.17.1",
4
4
  "description": "Coverage, security and code quality for coding agents",
5
5
  "keywords": [
6
6
  "coverage",
@@ -119,14 +119,14 @@
119
119
  "test:windows-tls": "node scripts/windows-tls-test.mjs"
120
120
  },
121
121
  "optionalDependencies": {
122
- "@supercov/cli-darwin-arm64": "4.16.0",
123
- "@supercov/cli-darwin-x64": "4.16.0",
124
- "@supercov/cli-linux-arm64-gnu": "4.16.0",
125
- "@supercov/cli-linux-arm64-musl": "4.16.0",
126
- "@supercov/cli-linux-x64-gnu": "4.16.0",
127
- "@supercov/cli-linux-x64-musl": "4.16.0",
128
- "@supercov/cli-win32-arm64": "4.16.0",
129
- "@supercov/cli-win32-x64": "4.16.0"
122
+ "@supercov/cli-darwin-arm64": "4.17.1",
123
+ "@supercov/cli-darwin-x64": "4.17.1",
124
+ "@supercov/cli-linux-arm64-gnu": "4.17.1",
125
+ "@supercov/cli-linux-arm64-musl": "4.17.1",
126
+ "@supercov/cli-linux-x64-gnu": "4.17.1",
127
+ "@supercov/cli-linux-x64-musl": "4.17.1",
128
+ "@supercov/cli-win32-arm64": "4.17.1",
129
+ "@supercov/cli-win32-x64": "4.17.1"
130
130
  },
131
131
  "peerDependencies": {
132
132
  "@playwright/test": ">=1.55.0",
@@ -604,13 +604,57 @@ else {
604
604
  return print.apply(this, [kept.join("\\n"), ...message.slice(1)]);
605
605
  })(warnOnce)); } catch {}
606
606
  `.replace(/\n\s*/g, " ");
607
+ // A project's `turbopack.root`, set to its own directory, names the copy
608
+ // here, while the copy's dependencies are links into the project's
609
+ // node_modules. Turbopack resolves no package outside its root and
610
+ // panicked "Next.js package not found". The root Next settles on is
611
+ // widened to the nearest directory that holds the real `next` package
612
+ // too -- the project, which holds the copy; one that holds it is kept.
613
+ const widened = `
614
+ ;try { loadConfig = ((load) => async function loadConfig(...args) {
615
+ const config = await load.apply(this, args);
616
+ try {
617
+ const path = require("path"), fs = require("fs");
618
+ const root = config && config.turbopack && config.turbopack.root;
619
+ if (typeof root === "string") {
620
+ const dir = path.resolve(args[1] || process.cwd());
621
+ const held = [fs.realpathSync(path.dirname(require.resolve("next/package.json", { paths: [dir] })))];
622
+ for (let at = dir; at.includes(path.sep + ".supercov" + path.sep); at = path.dirname(at)) {
623
+ try { held.push(fs.realpathSync(path.join(at, "node_modules"))); } catch {}
624
+ }
625
+ let common = fs.realpathSync(root).split(path.sep);
626
+ for (const target of held) {
627
+ const parts = target.split(path.sep);
628
+ let i = 0;
629
+ while (i < common.length && i < parts.length && common[i] === parts[i]) i++;
630
+ common = common.slice(0, i);
631
+ }
632
+ const widened = common.join(path.sep) || path.sep;
633
+ if (widened !== fs.realpathSync(root)) {
634
+ config.turbopack.root = widened;
635
+ config.outputFileTracingRoot = widened;
636
+ }
637
+ }
638
+ } catch {}
639
+ return config;
640
+ })(loadConfig); } catch {}
641
+ `.replace(/\n\s*/g, " ");
642
+ const append = (content, code) => {
643
+ // After the last statement, before the source map's line.
644
+ const map = content.lastIndexOf("\n//# sourceMappingURL=");
645
+ return map < 0 ? `${content}${code}` : `${content.slice(0, map)}${code}${content.slice(map)}`;
646
+ };
607
647
  const compile = Module.prototype._compile;
608
648
  Module.prototype._compile = function _compile(content, filename, ...rest) {
609
- if (typeof content === "string" && typeof filename === "string" && filename.endsWith("log.js") &&
610
- /[\\/]next[\\/]dist[\\/]build[\\/]output[\\/]log\.js$/.test(filename)) {
611
- // After the last statement, before the source map's line.
612
- const map = content.lastIndexOf("\n//# sourceMappingURL=");
613
- content = map < 0 ? `${content}${quieted}` : `${content.slice(0, map)}${quieted}${content.slice(map)}`;
649
+ if (typeof content === "string" && typeof filename === "string") {
650
+ if (filename.endsWith("log.js") &&
651
+ /[\\/]next[\\/]dist[\\/]build[\\/]output[\\/]log\.js$/.test(filename)) {
652
+ content = append(content, quieted);
653
+ }
654
+ else if (filename.endsWith("config.js") &&
655
+ /[\\/]next[\\/]dist[\\/]server[\\/]config\.js$/.test(filename)) {
656
+ content = append(content, widened);
657
+ }
614
658
  }
615
659
  return Reflect.apply(compile, this, [content, filename, ...rest]);
616
660
  };
@@ -1096,12 +1140,38 @@ if (process.env.SUPERCOV_CJS_INTERCEPT === "1") {
1096
1140
  // Avoid paying to import three runner adapters in every launcher.
1097
1141
  }
1098
1142
  else {
1099
- const nodeTestAdapter = await import(__rewriteRelativeImportExtension(nodeTestWrapper));
1100
- const cjsNodeTestAdapter = Object.assign(nodeTestAdapter.test, nodeTestAdapter);
1101
- const nodeAssertAdapter = await import(__rewriteRelativeImportExtension(nodeAssertWrapper));
1102
- const cjsNodeAssertAdapter = Object.assign(nodeAssertAdapter.default, nodeAssertAdapter);
1103
- const nodeAssertStrictAdapter = await import(__rewriteRelativeImportExtension(nodeAssertStrictWrapper));
1104
- const cjsNodeAssertStrictAdapter = Object.assign(nodeAssertStrictAdapter.default, nodeAssertStrictAdapter);
1143
+ // Node's module mocks keep the CJS resolver they find when node:test first
1144
+ // loads (`const { _resolveFilename } = Module` in its mock loader). This
1145
+ // preload runs before the command's own `--import`s, so loading node:test
1146
+ // here handed the mock loader the resolver from before `--import tsx`
1147
+ // mapped tsconfig paths: every `@/` import failed in a test that mocked a
1148
+ // module. Where Node requires an ES module without a warning, each adapter
1149
+ // loads on its first request instead, after every preload; elsewhere, as
1150
+ // before, now.
1151
+ const [major, minor] = process.versions.node.split(".").map(Number);
1152
+ const lazily = process.features?.require_module === true &&
1153
+ (major > 23 || (major === 23 && minor >= 5) || (major === 22 && minor >= 13) || (major === 20 && minor >= 19));
1154
+ const requireAdapter = Module.createRequire(import.meta.url);
1155
+ const adapters = new Map();
1156
+ const shapes = [
1157
+ ["test", nodeTestWrapper, (module) => module.test],
1158
+ ["assert", nodeAssertWrapper, (module) => module.default],
1159
+ ["assert/strict", nodeAssertStrictWrapper, (module) => module.default],
1160
+ ];
1161
+ const adapter = (name) => {
1162
+ if (!adapters.has(name)) {
1163
+ const [, url, main] = shapes.find(([shape]) => shape === name);
1164
+ const module = requireAdapter(fileURLToPath(url));
1165
+ adapters.set(name, Object.assign(main(module), module));
1166
+ }
1167
+ return adapters.get(name);
1168
+ };
1169
+ if (!lazily) {
1170
+ for (const [name, url, main] of shapes) {
1171
+ const module = await import(__rewriteRelativeImportExtension(url));
1172
+ adapters.set(name, Object.assign(main(module), module));
1173
+ }
1174
+ }
1105
1175
  const originalLoad = Module._load;
1106
1176
  Module._load = function supercovNodeTestLoad(request, parent, isMain) {
1107
1177
  const parentFile = parent?.filename?.replaceAll("\\", "/");
@@ -1113,12 +1183,10 @@ if (process.env.SUPERCOV_CJS_INTERCEPT === "1") {
1113
1183
  (request === "node:test" || request === "test"))
1114
1184
  console.error("[supercov] node:test CJS request", { parentFile, projectRoot, belongsToProject });
1115
1185
  if ((request === "node:test" || request === "test") && belongsToProject)
1116
- return cjsNodeTestAdapter;
1186
+ return adapter("test");
1117
1187
  if (["assert", "node:assert", "assert/strict", "node:assert/strict"].includes(request) &&
1118
1188
  belongsToProject)
1119
- return request.endsWith("/strict")
1120
- ? cjsNodeAssertStrictAdapter
1121
- : cjsNodeAssertAdapter;
1189
+ return adapter(request.endsWith("/strict") ? "assert/strict" : "assert");
1122
1190
  return originalLoad.call(this, request, parent, isMain);
1123
1191
  };
1124
1192
  }