@t09tanaka/test-gates 0.5.0 → 0.5.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.2
4
+
5
+ ### Fixed
6
+
7
+ - `selfcheck --jobs` really no longer keeps every other control apart because of the word `require` in a string, a template, a comment or a regular expression. 0.5.1 fixed the reason given for a spec, but the plan of which controls run on their own still looked at the whole text.
8
+
9
+ ## 0.5.1
10
+
11
+ ### Fixed
12
+
13
+ - `selfcheck --jobs` no longer keeps every other control apart because the word `require` appears inside a string, a template, a comment or a regular expression (`'sslmode=require'`, `// pass it to require`). Only code is looked at now. Handing `require` on under another name (`const r = require`) still counts as loading a module whose name is computed.
14
+
3
15
  ## 0.5.0
4
16
 
5
17
  This release makes the gates quicker to check in CI: `test-gates mutation --shard <index>/<total>` splits the mutation run over parallel jobs, and `selfcheck --jobs` runs more controls side by side. Nothing changes for a run without the new option:
package/dist/cli.js CHANGED
@@ -5447,6 +5447,9 @@ var REACHES = [
5447
5447
  ];
5448
5448
  var COMPUTED_LOAD = /(?:require|import)\((?!['"])|\brequire(?!\(|\.resolve\b|[\w$])|\bimport\.meta\.glob\b/;
5449
5449
  var COMPUTED = "loads a module whose name is computed";
5450
+ function loadsComputed(source) {
5451
+ return COMPUTED_LOAD.test(withoutLiterals(source).replace(/\s/g, ""));
5452
+ }
5450
5453
  var STRING = /(^|[:,([={?+&|;])(\s*)(['"])((?:\\.|(?!\3)[^\\\n])*)\3(?=\s*(?:[,}\]);:+?&|]|$))/gm;
5451
5454
  function withoutStrings(source) {
5452
5455
  return source.replace(
@@ -5458,6 +5461,70 @@ function withoutStrings(source) {
5458
5461
  )
5459
5462
  );
5460
5463
  }
5464
+ var BEFORE_REGEX = /(?:^|[(,=:[!&|?{};+\-*%<>~^]|\b(?:return|typeof|case|in|of|void|delete|throw|new|yield|await))\s*$/;
5465
+ var COMMENT_OR_STRING = /\/\/[^\n]*|\/\*[\s\S]*?(?:\*\/|$)|(['"])(?:\\.|(?!\1)[^\\\n])*\1?/y;
5466
+ var REGEX = /\/(?:\\.|\[(?:\\.|[^\]\\\n])*\]?|[^/\\\n[])*\/?/y;
5467
+ var TEMPLATE_TEXT = /(?:\\[\s\S]|[^`\\$]|\$(?!\{))*/y;
5468
+ function withoutLiterals(source) {
5469
+ let out = "";
5470
+ let i = 0;
5471
+ const blank = (text) => text.replace(/[^\n]/g, " ");
5472
+ const enclosed = (literal) => {
5473
+ const closed = literal.length > 1 && literal.endsWith(literal.charAt(0));
5474
+ return literal.charAt(0) + blank(literal.slice(1, closed ? -1 : void 0)) + (closed ? literal.charAt(0) : "");
5475
+ };
5476
+ const take = (pattern) => {
5477
+ pattern.lastIndex = i;
5478
+ const found = pattern.exec(source)?.[0] ?? null;
5479
+ if (found !== null) {
5480
+ i += found.length;
5481
+ }
5482
+ return found;
5483
+ };
5484
+ const code = (inBraces) => {
5485
+ while (i < source.length) {
5486
+ const c = source[i];
5487
+ const literal = take(COMMENT_OR_STRING);
5488
+ if (literal !== null) {
5489
+ out += literal.startsWith("/") ? blank(literal) : enclosed(literal);
5490
+ } else if (c === "/" && BEFORE_REGEX.test(out)) {
5491
+ out += enclosed(take(REGEX));
5492
+ } else if (c === "`") {
5493
+ i += 1;
5494
+ out += "`";
5495
+ template();
5496
+ } else if (c === "}" && inBraces) {
5497
+ return;
5498
+ } else if (c === "{" && inBraces) {
5499
+ braces("{");
5500
+ } else {
5501
+ out += c;
5502
+ i += 1;
5503
+ }
5504
+ }
5505
+ };
5506
+ const braces = (open) => {
5507
+ i += open.length;
5508
+ out += open;
5509
+ code(true);
5510
+ if (i < source.length) {
5511
+ i += 1;
5512
+ out += "}";
5513
+ }
5514
+ };
5515
+ const template = () => {
5516
+ out += blank(take(TEMPLATE_TEXT));
5517
+ if (source.startsWith("${", i)) {
5518
+ braces("${");
5519
+ template();
5520
+ } else if (i < source.length) {
5521
+ i += 1;
5522
+ out += "`";
5523
+ }
5524
+ };
5525
+ code(false);
5526
+ return out;
5527
+ }
5461
5528
  var PURE_BUILTINS = [
5462
5529
  "assert",
5463
5530
  "assert/strict",
@@ -5473,13 +5540,12 @@ var PURE_BUILTINS = [
5473
5540
  ];
5474
5541
  var RUNNER_PACKAGES = ["vitest", "@jest/globals"];
5475
5542
  function reachesOutside(file, trusted) {
5476
- const dense = file.source.replace(/\s/g, "");
5477
5543
  const code = withoutStrings(file.source).replace(/\s/g, "");
5478
5544
  const mark = REACHES.find((text) => code.includes(text));
5479
5545
  if (mark !== void 0) {
5480
5546
  return `uses ${mark.replace(/[.?[(:]+$/, "")}`;
5481
5547
  }
5482
- if (COMPUTED_LOAD.test(dense)) {
5548
+ if (loadsComputed(file.source)) {
5483
5549
  return COMPUTED;
5484
5550
  }
5485
5551
  for (const { specifier, builtin } of file.imports) {
@@ -5519,7 +5585,7 @@ function jobsPlan(input) {
5519
5585
  const reaches = loaded.flatMap((file) => {
5520
5586
  const reach = reachesOutside(file, input.trusted);
5521
5587
  const said = reach === null ? [] : [`${file.path} ${reach}`];
5522
- if (!COMPUTED_LOAD.test(file.source.replace(/\s/g, ""))) {
5588
+ if (!loadsComputed(file.source)) {
5523
5589
  return said;
5524
5590
  }
5525
5591
  computed = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@t09tanaka/test-gates",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Test gates for the code that must not be wrong: per-file 100% coverage as the floor, mutation testing with a per-mutant allow list as the real check.",
5
5
  "author": "t09tanaka",
6
6
  "license": "MIT",