@t09tanaka/test-gates 0.4.0 → 0.4.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/dist/index.cjs CHANGED
@@ -691,14 +691,17 @@ function parseImports(source) {
691
691
  const clause = match[3];
692
692
  const module2 = match[5];
693
693
  const braces = /\{([^}]*)\}/.exec(clause);
694
- const names = braces ? braces[1].split(",").map((part) => part.trim()).filter((part) => part !== "" && !/^type\s/.test(part)).map((part) => part.split(/\s+as\s+/)[0].trim()) : [];
694
+ const entries = braces ? braces[1].split(",").map((part) => part.trim()).filter((part) => part !== "") : [];
695
+ const names = entries.filter((part) => !/^type\s/.test(part)).map((part) => part.split(/\s+as\s+/)[0].trim());
695
696
  const outsideBraces = clause.replace(/\{[^}]*\}/, "").replace(/,/g, "").trim();
696
697
  found.push({
697
698
  module: module2,
698
699
  line: lineAt(source, match.index + match[0].lastIndexOf(module2)),
699
700
  typeOnly: match[2] !== void 0,
700
701
  names,
701
- whole: outsideBraces !== ""
702
+ whole: outsideBraces !== "",
703
+ ...match[2] === void 0 && outsideBraces === "" && entries.length > 0 && // `type as kind` imports the value named `type`.
704
+ entries.every((part) => /^type\s+(?!as\s)[\w$]/.test(part)) ? { inlineTypes: true } : {}
702
705
  });
703
706
  }
704
707
  for (const match of source.matchAll(SIDE_EFFECT_IMPORT)) {
@@ -713,6 +716,9 @@ function parseImports(source) {
713
716
  }
714
717
  for (const match of source.matchAll(CALL_IMPORT)) {
715
718
  const module2 = match[2];
719
+ if (match[1] === "`" && module2.includes("${")) {
720
+ continue;
721
+ }
716
722
  found.push({
717
723
  module: module2,
718
724
  line: lineAt(source, match.index + match[0].lastIndexOf(module2)),
@@ -723,7 +729,7 @@ function parseImports(source) {
723
729
  }
724
730
  return found.sort((a, b) => a.line - b.line);
725
731
  }
726
- var COMPUTED_CALL_IMPORT = /(?<![.\w$])(?:require|import)\s*\(\s*(?!['"`\s)])/g;
732
+ var COMPUTED_CALL_IMPORT = /(?<![.\w$])(?:require|import)\s*\((?!\s*(?:\)|(?:'(?:[^'\\\n]|\\.)*'|"(?:[^"\\\n]|\\.)*"|`(?:[^`\\$]|\\.|\$(?!\{))*`)\s*\)))/g;
727
733
  function findUncheckableImports(source) {
728
734
  return [...source.matchAll(COMPUTED_CALL_IMPORT)].map((match) => lineAt(source, match.index));
729
735
  }
@@ -750,7 +756,11 @@ function resolveSpecifier(specifier, fromFile, aliases) {
750
756
  const segments = [];
751
757
  for (const segment of joined.split("/")) {
752
758
  if (segment === "..") {
753
- segments.pop();
759
+ if (segments.length === 0 || segments[segments.length - 1] === "..") {
760
+ segments.push(segment);
761
+ } else {
762
+ segments.pop();
763
+ }
754
764
  } else if (segment !== "." && segment !== "") {
755
765
  segments.push(segment);
756
766
  }
@@ -1121,6 +1131,41 @@ function sliceByLocation(source, location) {
1121
1131
  function keyOf(mutator, original, replacement) {
1122
1132
  return JSON.stringify([mutator, normalizeCode(original), normalizeCode(replacement)]);
1123
1133
  }
1134
+ function locateMutants(source, mutants) {
1135
+ const located = [];
1136
+ const byKey = /* @__PURE__ */ new Map();
1137
+ const outside = [];
1138
+ const sorted = [...mutants].sort(
1139
+ (a, b) => a.location.start.line - b.location.start.line || a.location.start.column - b.location.start.column
1140
+ );
1141
+ for (const mutant of sorted) {
1142
+ const original = sliceByLocation(normalizeNewlines(source), mutant.location);
1143
+ if (original === null) {
1144
+ outside.push(mutant);
1145
+ continue;
1146
+ }
1147
+ const replacement = mutant.replacement ?? "";
1148
+ const key = keyOf(mutant.mutatorName, original, replacement);
1149
+ const group = byKey.get(key) ?? [];
1150
+ const entry = { mutant, original, replacement, occurrence: group.length + 1, sameKeyCount: 0 };
1151
+ group.push(entry);
1152
+ byKey.set(key, group);
1153
+ located.push(entry);
1154
+ }
1155
+ for (const group of byKey.values()) {
1156
+ for (const entry of group) {
1157
+ entry.sameKeyCount = group.length;
1158
+ }
1159
+ }
1160
+ return { located, byKey, outside };
1161
+ }
1162
+ function resolveEntry(entry, byKey) {
1163
+ const group = byKey.get(keyOf(entry.mutator, entry.original, entry.replacement)) ?? [];
1164
+ if (entry.occurrence === void 0 && group.length > 1) {
1165
+ return { ambiguous: group.length };
1166
+ }
1167
+ return { target: group[(entry.occurrence ?? 1) - 1] };
1168
+ }
1124
1169
  function describeMutant(file, entry) {
1125
1170
  const { mutant } = entry;
1126
1171
  return {
@@ -1194,43 +1239,15 @@ function judgeMutationReport(input) {
1194
1239
  });
1195
1240
  continue;
1196
1241
  }
1197
- const located = [];
1198
- const byKey = /* @__PURE__ */ new Map();
1199
- const sorted = [...result.mutants].sort(
1200
- (a, b) => a.location.start.line - b.location.start.line || a.location.start.column - b.location.start.column
1201
- );
1202
- let locationsValid = true;
1203
- for (const mutant of sorted) {
1204
- const original = sliceByLocation(normalizeNewlines(source), mutant.location);
1205
- if (original === null) {
1206
- violations.push({
1207
- file: gate.path,
1208
- line: mutant.location.start.line,
1209
- message: `the report location of a ${mutant.mutatorName} mutant is outside the source`
1210
- });
1211
- locationsValid = false;
1212
- continue;
1213
- }
1214
- const replacement = mutant.replacement ?? "";
1215
- const key = keyOf(mutant.mutatorName, original, replacement);
1216
- const group = byKey.get(key) ?? [];
1217
- const entry = {
1218
- mutant,
1219
- original,
1220
- replacement,
1221
- occurrence: group.length + 1,
1222
- sameKeyCount: 0
1223
- };
1224
- group.push(entry);
1225
- byKey.set(key, group);
1226
- located.push(entry);
1227
- }
1228
- for (const group of byKey.values()) {
1229
- for (const entry of group) {
1230
- entry.sameKeyCount = group.length;
1231
- }
1242
+ const { located, byKey, outside } = locateMutants(source, result.mutants);
1243
+ for (const mutant of outside) {
1244
+ violations.push({
1245
+ file: gate.path,
1246
+ line: mutant.location.start.line,
1247
+ message: `the report location of a ${mutant.mutatorName} mutant is outside the source`
1248
+ });
1232
1249
  }
1233
- if (!locationsValid) {
1250
+ if (outside.length > 0) {
1234
1251
  continue;
1235
1252
  }
1236
1253
  const claim = (field, entries, noun, accept) => {
@@ -1243,15 +1260,15 @@ function judgeMutationReport(input) {
1243
1260
  return;
1244
1261
  }
1245
1262
  const at = `${gate.path}: ${field}[${index}] (${describeAllowance(entry)})`;
1246
- const group = byKey.get(keyOf(entry.mutator, entry.original, entry.replacement)) ?? [];
1247
- if (entry.occurrence === void 0 && group.length > 1) {
1263
+ const found = resolveEntry(entry, byKey);
1264
+ if ("ambiguous" in found) {
1248
1265
  violations.push({
1249
1266
  file: MANIFEST_FILE,
1250
- message: `${at}: matches ${group.length} mutants in the file. Add "occurrence" (1-based) to pick one`
1267
+ message: `${at}: matches ${found.ambiguous} mutants in the file. Add "occurrence" (1-based) to pick one`
1251
1268
  });
1252
1269
  return;
1253
1270
  }
1254
- const target = group[(entry.occurrence ?? 1) - 1];
1271
+ const target = found.target;
1255
1272
  const refusal = target ? accept(target.mutant.status) : `stale ${noun} (no such mutant in this run). Remove it`;
1256
1273
  if (!target || refusal !== null) {
1257
1274
  violations.push({ file: MANIFEST_FILE, message: `${at}: ${refusal}` });
package/dist/index.js CHANGED
@@ -45,14 +45,17 @@ function parseImports(source) {
45
45
  const clause = match[3];
46
46
  const module = match[5];
47
47
  const braces = /\{([^}]*)\}/.exec(clause);
48
- const names = braces ? braces[1].split(",").map((part) => part.trim()).filter((part) => part !== "" && !/^type\s/.test(part)).map((part) => part.split(/\s+as\s+/)[0].trim()) : [];
48
+ const entries = braces ? braces[1].split(",").map((part) => part.trim()).filter((part) => part !== "") : [];
49
+ const names = entries.filter((part) => !/^type\s/.test(part)).map((part) => part.split(/\s+as\s+/)[0].trim());
49
50
  const outsideBraces = clause.replace(/\{[^}]*\}/, "").replace(/,/g, "").trim();
50
51
  found.push({
51
52
  module,
52
53
  line: lineAt(source, match.index + match[0].lastIndexOf(module)),
53
54
  typeOnly: match[2] !== void 0,
54
55
  names,
55
- whole: outsideBraces !== ""
56
+ whole: outsideBraces !== "",
57
+ ...match[2] === void 0 && outsideBraces === "" && entries.length > 0 && // `type as kind` imports the value named `type`.
58
+ entries.every((part) => /^type\s+(?!as\s)[\w$]/.test(part)) ? { inlineTypes: true } : {}
56
59
  });
57
60
  }
58
61
  for (const match of source.matchAll(SIDE_EFFECT_IMPORT)) {
@@ -67,6 +70,9 @@ function parseImports(source) {
67
70
  }
68
71
  for (const match of source.matchAll(CALL_IMPORT)) {
69
72
  const module = match[2];
73
+ if (match[1] === "`" && module.includes("${")) {
74
+ continue;
75
+ }
70
76
  found.push({
71
77
  module,
72
78
  line: lineAt(source, match.index + match[0].lastIndexOf(module)),
@@ -77,7 +83,7 @@ function parseImports(source) {
77
83
  }
78
84
  return found.sort((a, b) => a.line - b.line);
79
85
  }
80
- var COMPUTED_CALL_IMPORT = /(?<![.\w$])(?:require|import)\s*\(\s*(?!['"`\s)])/g;
86
+ var COMPUTED_CALL_IMPORT = /(?<![.\w$])(?:require|import)\s*\((?!\s*(?:\)|(?:'(?:[^'\\\n]|\\.)*'|"(?:[^"\\\n]|\\.)*"|`(?:[^`\\$]|\\.|\$(?!\{))*`)\s*\)))/g;
81
87
  function findUncheckableImports(source) {
82
88
  return [...source.matchAll(COMPUTED_CALL_IMPORT)].map((match) => lineAt(source, match.index));
83
89
  }
@@ -104,7 +110,11 @@ function resolveSpecifier(specifier, fromFile, aliases) {
104
110
  const segments = [];
105
111
  for (const segment of joined.split("/")) {
106
112
  if (segment === "..") {
107
- segments.pop();
113
+ if (segments.length === 0 || segments[segments.length - 1] === "..") {
114
+ segments.push(segment);
115
+ } else {
116
+ segments.pop();
117
+ }
108
118
  } else if (segment !== "." && segment !== "") {
109
119
  segments.push(segment);
110
120
  }
@@ -475,6 +485,41 @@ function sliceByLocation(source, location) {
475
485
  function keyOf(mutator, original, replacement) {
476
486
  return JSON.stringify([mutator, normalizeCode(original), normalizeCode(replacement)]);
477
487
  }
488
+ function locateMutants(source, mutants) {
489
+ const located = [];
490
+ const byKey = /* @__PURE__ */ new Map();
491
+ const outside = [];
492
+ const sorted = [...mutants].sort(
493
+ (a, b) => a.location.start.line - b.location.start.line || a.location.start.column - b.location.start.column
494
+ );
495
+ for (const mutant of sorted) {
496
+ const original = sliceByLocation(normalizeNewlines(source), mutant.location);
497
+ if (original === null) {
498
+ outside.push(mutant);
499
+ continue;
500
+ }
501
+ const replacement = mutant.replacement ?? "";
502
+ const key = keyOf(mutant.mutatorName, original, replacement);
503
+ const group = byKey.get(key) ?? [];
504
+ const entry = { mutant, original, replacement, occurrence: group.length + 1, sameKeyCount: 0 };
505
+ group.push(entry);
506
+ byKey.set(key, group);
507
+ located.push(entry);
508
+ }
509
+ for (const group of byKey.values()) {
510
+ for (const entry of group) {
511
+ entry.sameKeyCount = group.length;
512
+ }
513
+ }
514
+ return { located, byKey, outside };
515
+ }
516
+ function resolveEntry(entry, byKey) {
517
+ const group = byKey.get(keyOf(entry.mutator, entry.original, entry.replacement)) ?? [];
518
+ if (entry.occurrence === void 0 && group.length > 1) {
519
+ return { ambiguous: group.length };
520
+ }
521
+ return { target: group[(entry.occurrence ?? 1) - 1] };
522
+ }
478
523
  function describeMutant(file, entry) {
479
524
  const { mutant } = entry;
480
525
  return {
@@ -548,43 +593,15 @@ function judgeMutationReport(input) {
548
593
  });
549
594
  continue;
550
595
  }
551
- const located = [];
552
- const byKey = /* @__PURE__ */ new Map();
553
- const sorted = [...result.mutants].sort(
554
- (a, b) => a.location.start.line - b.location.start.line || a.location.start.column - b.location.start.column
555
- );
556
- let locationsValid = true;
557
- for (const mutant of sorted) {
558
- const original = sliceByLocation(normalizeNewlines(source), mutant.location);
559
- if (original === null) {
560
- violations.push({
561
- file: gate.path,
562
- line: mutant.location.start.line,
563
- message: `the report location of a ${mutant.mutatorName} mutant is outside the source`
564
- });
565
- locationsValid = false;
566
- continue;
567
- }
568
- const replacement = mutant.replacement ?? "";
569
- const key = keyOf(mutant.mutatorName, original, replacement);
570
- const group = byKey.get(key) ?? [];
571
- const entry = {
572
- mutant,
573
- original,
574
- replacement,
575
- occurrence: group.length + 1,
576
- sameKeyCount: 0
577
- };
578
- group.push(entry);
579
- byKey.set(key, group);
580
- located.push(entry);
581
- }
582
- for (const group of byKey.values()) {
583
- for (const entry of group) {
584
- entry.sameKeyCount = group.length;
585
- }
596
+ const { located, byKey, outside } = locateMutants(source, result.mutants);
597
+ for (const mutant of outside) {
598
+ violations.push({
599
+ file: gate.path,
600
+ line: mutant.location.start.line,
601
+ message: `the report location of a ${mutant.mutatorName} mutant is outside the source`
602
+ });
586
603
  }
587
- if (!locationsValid) {
604
+ if (outside.length > 0) {
588
605
  continue;
589
606
  }
590
607
  const claim = (field, entries, noun, accept) => {
@@ -597,15 +614,15 @@ function judgeMutationReport(input) {
597
614
  return;
598
615
  }
599
616
  const at = `${gate.path}: ${field}[${index}] (${describeAllowance(entry)})`;
600
- const group = byKey.get(keyOf(entry.mutator, entry.original, entry.replacement)) ?? [];
601
- if (entry.occurrence === void 0 && group.length > 1) {
617
+ const found = resolveEntry(entry, byKey);
618
+ if ("ambiguous" in found) {
602
619
  violations.push({
603
620
  file: MANIFEST_FILE,
604
- message: `${at}: matches ${group.length} mutants in the file. Add "occurrence" (1-based) to pick one`
621
+ message: `${at}: matches ${found.ambiguous} mutants in the file. Add "occurrence" (1-based) to pick one`
605
622
  });
606
623
  return;
607
624
  }
608
- const target = group[(entry.occurrence ?? 1) - 1];
625
+ const target = found.target;
609
626
  const refusal = target ? accept(target.mutant.status) : `stale ${noun} (no such mutant in this run). Remove it`;
610
627
  if (!target || refusal !== null) {
611
628
  violations.push({ file: MANIFEST_FILE, message: `${at}: ${refusal}` });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@t09tanaka/test-gates",
3
- "version": "0.4.0",
3
+ "version": "0.4.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",