@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/CHANGELOG.md +47 -0
- package/README.md +15 -15
- package/dist/cli.js +1253 -371
- package/dist/index.cjs +61 -44
- package/dist/index.js +61 -44
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -319,14 +319,17 @@ function parseImports(source) {
|
|
|
319
319
|
const clause = match[3];
|
|
320
320
|
const module = match[5];
|
|
321
321
|
const braces = /\{([^}]*)\}/.exec(clause);
|
|
322
|
-
const
|
|
322
|
+
const entries = braces ? braces[1].split(",").map((part) => part.trim()).filter((part) => part !== "") : [];
|
|
323
|
+
const names = entries.filter((part) => !/^type\s/.test(part)).map((part) => part.split(/\s+as\s+/)[0].trim());
|
|
323
324
|
const outsideBraces = clause.replace(/\{[^}]*\}/, "").replace(/,/g, "").trim();
|
|
324
325
|
found.push({
|
|
325
326
|
module,
|
|
326
327
|
line: lineAt(source, match.index + match[0].lastIndexOf(module)),
|
|
327
328
|
typeOnly: match[2] !== void 0,
|
|
328
329
|
names,
|
|
329
|
-
whole: outsideBraces !== ""
|
|
330
|
+
whole: outsideBraces !== "",
|
|
331
|
+
...match[2] === void 0 && outsideBraces === "" && entries.length > 0 && // `type as kind` imports the value named `type`.
|
|
332
|
+
entries.every((part) => /^type\s+(?!as\s)[\w$]/.test(part)) ? { inlineTypes: true } : {}
|
|
330
333
|
});
|
|
331
334
|
}
|
|
332
335
|
for (const match of source.matchAll(SIDE_EFFECT_IMPORT)) {
|
|
@@ -341,6 +344,9 @@ function parseImports(source) {
|
|
|
341
344
|
}
|
|
342
345
|
for (const match of source.matchAll(CALL_IMPORT)) {
|
|
343
346
|
const module = match[2];
|
|
347
|
+
if (match[1] === "`" && module.includes("${")) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
344
350
|
found.push({
|
|
345
351
|
module,
|
|
346
352
|
line: lineAt(source, match.index + match[0].lastIndexOf(module)),
|
|
@@ -351,7 +357,7 @@ function parseImports(source) {
|
|
|
351
357
|
}
|
|
352
358
|
return found.sort((a, b) => a.line - b.line);
|
|
353
359
|
}
|
|
354
|
-
var COMPUTED_CALL_IMPORT = /(?<![.\w$])(?:require|import)\s*\(
|
|
360
|
+
var COMPUTED_CALL_IMPORT = /(?<![.\w$])(?:require|import)\s*\((?!\s*(?:\)|(?:'(?:[^'\\\n]|\\.)*'|"(?:[^"\\\n]|\\.)*"|`(?:[^`\\$]|\\.|\$(?!\{))*`)\s*\)))/g;
|
|
355
361
|
function findUncheckableImports(source) {
|
|
356
362
|
return [...source.matchAll(COMPUTED_CALL_IMPORT)].map((match) => lineAt(source, match.index));
|
|
357
363
|
}
|
|
@@ -378,7 +384,11 @@ function resolveSpecifier(specifier, fromFile, aliases) {
|
|
|
378
384
|
const segments = [];
|
|
379
385
|
for (const segment of joined.split("/")) {
|
|
380
386
|
if (segment === "..") {
|
|
381
|
-
segments.
|
|
387
|
+
if (segments.length === 0 || segments[segments.length - 1] === "..") {
|
|
388
|
+
segments.push(segment);
|
|
389
|
+
} else {
|
|
390
|
+
segments.pop();
|
|
391
|
+
}
|
|
382
392
|
} else if (segment !== "." && segment !== "") {
|
|
383
393
|
segments.push(segment);
|
|
384
394
|
}
|
|
@@ -423,8 +433,8 @@ function findImporters(gatePath, others, aliases) {
|
|
|
423
433
|
if (imported.typeOnly) {
|
|
424
434
|
return false;
|
|
425
435
|
}
|
|
426
|
-
const
|
|
427
|
-
return
|
|
436
|
+
const resolved2 = resolveSpecifier(imported.module, other.path, aliases);
|
|
437
|
+
return resolved2 === target || resolved2 === asDirectory;
|
|
428
438
|
})
|
|
429
439
|
).map((other) => other.path);
|
|
430
440
|
}
|
|
@@ -555,8 +565,8 @@ function scanSpecSource(gatePath, specPath, source, settings) {
|
|
|
555
565
|
const gateDirectory = gateModule.replace(/\/index$/, "");
|
|
556
566
|
for (const match of source.matchAll(MODULE_MOCK)) {
|
|
557
567
|
const specifier = match[2];
|
|
558
|
-
const
|
|
559
|
-
if (
|
|
568
|
+
const resolved2 = resolveSpecifier(specifier, specPath, settings.importAliases);
|
|
569
|
+
if (resolved2 === gateModule || resolved2 === gateDirectory) {
|
|
560
570
|
let line = 1;
|
|
561
571
|
for (let i = 0; i < match.index; i += 1) {
|
|
562
572
|
if (source[i] === "\n") {
|
|
@@ -1183,8 +1193,8 @@ function planControls(input) {
|
|
|
1183
1193
|
if (silent !== void 0) {
|
|
1184
1194
|
return everyoneAlone(`the gate run left no note for the spec of ${silent.path}`);
|
|
1185
1195
|
}
|
|
1186
|
-
const
|
|
1187
|
-
const short = all.find((gate) => accountedFor(totals[gate.file],
|
|
1196
|
+
const partsOf2 = (gate) => notes.flatMap((note) => gate.file in note.files ? [note.files[gate.file]] : []);
|
|
1197
|
+
const short = all.find((gate) => accountedFor(totals[gate.file], partsOf2(gate)) === "no");
|
|
1188
1198
|
if (short !== void 0) {
|
|
1189
1199
|
return everyoneAlone(
|
|
1190
1200
|
`the notes of the specs do not add up to what the runner counted for ${short.path}`
|
|
@@ -1198,7 +1208,7 @@ function planControls(input) {
|
|
|
1198
1208
|
if (all.some((other) => other.path !== gate.path && other.path.includes(gate.path))) {
|
|
1199
1209
|
return false;
|
|
1200
1210
|
}
|
|
1201
|
-
if (accountedFor(totals[gate.file],
|
|
1211
|
+
if (accountedFor(totals[gate.file], partsOf2(gate)) !== "yes") {
|
|
1202
1212
|
return false;
|
|
1203
1213
|
}
|
|
1204
1214
|
const touching = notes.filter((note) => {
|
|
@@ -1413,6 +1423,279 @@ import { spawnSync as spawnSync3 } from "child_process";
|
|
|
1413
1423
|
import fs9 from "fs";
|
|
1414
1424
|
import path9 from "path";
|
|
1415
1425
|
|
|
1426
|
+
// src/core/mutation-result.ts
|
|
1427
|
+
var DETECTED = /* @__PURE__ */ new Set(["Killed", "Timeout"]);
|
|
1428
|
+
var SURVIVED = /* @__PURE__ */ new Set(["Survived", "NoCoverage"]);
|
|
1429
|
+
var NOT_EVALUABLE = /* @__PURE__ */ new Set(["RuntimeError", "CompileError"]);
|
|
1430
|
+
function normalizeCode(text) {
|
|
1431
|
+
return text.replace(/\s+/g, " ").trim();
|
|
1432
|
+
}
|
|
1433
|
+
function normalizePath(file) {
|
|
1434
|
+
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1435
|
+
}
|
|
1436
|
+
function normalizeNewlines(text) {
|
|
1437
|
+
return text.replace(/\r\n/g, "\n");
|
|
1438
|
+
}
|
|
1439
|
+
function sliceByLocation(source, location) {
|
|
1440
|
+
const lines = source.split("\n");
|
|
1441
|
+
const { start, end } = location;
|
|
1442
|
+
const first = lines[start.line - 1];
|
|
1443
|
+
const last = lines[end.line - 1];
|
|
1444
|
+
if (first === void 0 || last === void 0 || start.column < 1 || end.column < 1 || end.line < start.line || end.line === start.line && end.column < start.column) {
|
|
1445
|
+
return null;
|
|
1446
|
+
}
|
|
1447
|
+
if (start.line === end.line) {
|
|
1448
|
+
return first.slice(start.column - 1, end.column - 1);
|
|
1449
|
+
}
|
|
1450
|
+
return [
|
|
1451
|
+
first.slice(start.column - 1),
|
|
1452
|
+
...lines.slice(start.line, end.line - 1),
|
|
1453
|
+
last.slice(0, end.column - 1)
|
|
1454
|
+
].join("\n");
|
|
1455
|
+
}
|
|
1456
|
+
function keyOf(mutator, original, replacement) {
|
|
1457
|
+
return JSON.stringify([mutator, normalizeCode(original), normalizeCode(replacement)]);
|
|
1458
|
+
}
|
|
1459
|
+
function locateMutants(source, mutants) {
|
|
1460
|
+
const located = [];
|
|
1461
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1462
|
+
const outside = [];
|
|
1463
|
+
const sorted2 = [...mutants].sort(
|
|
1464
|
+
(a, b) => a.location.start.line - b.location.start.line || a.location.start.column - b.location.start.column
|
|
1465
|
+
);
|
|
1466
|
+
for (const mutant of sorted2) {
|
|
1467
|
+
const original = sliceByLocation(normalizeNewlines(source), mutant.location);
|
|
1468
|
+
if (original === null) {
|
|
1469
|
+
outside.push(mutant);
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
const replacement = mutant.replacement ?? "";
|
|
1473
|
+
const key = keyOf(mutant.mutatorName, original, replacement);
|
|
1474
|
+
const group = byKey.get(key) ?? [];
|
|
1475
|
+
const entry = { mutant, original, replacement, occurrence: group.length + 1, sameKeyCount: 0 };
|
|
1476
|
+
group.push(entry);
|
|
1477
|
+
byKey.set(key, group);
|
|
1478
|
+
located.push(entry);
|
|
1479
|
+
}
|
|
1480
|
+
for (const group of byKey.values()) {
|
|
1481
|
+
for (const entry of group) {
|
|
1482
|
+
entry.sameKeyCount = group.length;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
return { located, byKey, outside };
|
|
1486
|
+
}
|
|
1487
|
+
function resolveEntry(entry, byKey) {
|
|
1488
|
+
const group = byKey.get(keyOf(entry.mutator, entry.original, entry.replacement)) ?? [];
|
|
1489
|
+
if (entry.occurrence === void 0 && group.length > 1) {
|
|
1490
|
+
return { ambiguous: group.length };
|
|
1491
|
+
}
|
|
1492
|
+
return { target: group[(entry.occurrence ?? 1) - 1] };
|
|
1493
|
+
}
|
|
1494
|
+
function expectedTimeoutMutants(gate, source, mutants) {
|
|
1495
|
+
const pointed = /* @__PURE__ */ new Set();
|
|
1496
|
+
if (source === void 0 || !Array.isArray(gate.expectedTimeouts)) {
|
|
1497
|
+
return pointed;
|
|
1498
|
+
}
|
|
1499
|
+
const { byKey, outside } = locateMutants(source, mutants);
|
|
1500
|
+
if (outside.length > 0) {
|
|
1501
|
+
return pointed;
|
|
1502
|
+
}
|
|
1503
|
+
for (const entry of gate.expectedTimeouts) {
|
|
1504
|
+
if (!isUsableEquivalentMutant(entry)) {
|
|
1505
|
+
continue;
|
|
1506
|
+
}
|
|
1507
|
+
const found = resolveEntry(entry, byKey);
|
|
1508
|
+
if ("target" in found && found.target !== void 0) {
|
|
1509
|
+
pointed.add(found.target.mutant);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
return pointed;
|
|
1513
|
+
}
|
|
1514
|
+
function describeMutant(file, entry) {
|
|
1515
|
+
const { mutant } = entry;
|
|
1516
|
+
return {
|
|
1517
|
+
file,
|
|
1518
|
+
line: mutant.location.start.line,
|
|
1519
|
+
column: mutant.location.start.column,
|
|
1520
|
+
status: mutant.status,
|
|
1521
|
+
mutator: mutant.mutatorName,
|
|
1522
|
+
original: normalizeCode(entry.original),
|
|
1523
|
+
replacement: normalizeCode(entry.replacement),
|
|
1524
|
+
occurrence: entry.occurrence,
|
|
1525
|
+
sameKeyCount: entry.sameKeyCount,
|
|
1526
|
+
allowance: {
|
|
1527
|
+
mutator: mutant.mutatorName,
|
|
1528
|
+
original: normalizeCode(entry.original),
|
|
1529
|
+
replacement: normalizeCode(entry.replacement),
|
|
1530
|
+
...entry.sameKeyCount > 1 ? { occurrence: entry.occurrence } : {},
|
|
1531
|
+
reason: ""
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
function describeAllowance(entry) {
|
|
1536
|
+
const occurrence = entry.occurrence === void 0 ? "" : ` / occurrence ${entry.occurrence}`;
|
|
1537
|
+
return `${entry.mutator} / ${normalizeCode(entry.original)} \u2192 ${normalizeCode(entry.replacement)}${occurrence}`;
|
|
1538
|
+
}
|
|
1539
|
+
function judgeMutationReport(input) {
|
|
1540
|
+
const violations = [];
|
|
1541
|
+
const survivors = [];
|
|
1542
|
+
const unexpectedTimeouts = [];
|
|
1543
|
+
const summary = {
|
|
1544
|
+
total: 0,
|
|
1545
|
+
detected: 0,
|
|
1546
|
+
timeout: 0,
|
|
1547
|
+
allowed: 0,
|
|
1548
|
+
notEvaluable: 0,
|
|
1549
|
+
unallowed: 0,
|
|
1550
|
+
expectedTimeout: 0
|
|
1551
|
+
};
|
|
1552
|
+
const files = /* @__PURE__ */ new Map();
|
|
1553
|
+
for (const [file, result] of Object.entries(input.report.files ?? {})) {
|
|
1554
|
+
files.set(normalizePath(file), result);
|
|
1555
|
+
}
|
|
1556
|
+
for (const gate of input.gates) {
|
|
1557
|
+
const gatePath = normalizePath(gate.path);
|
|
1558
|
+
const shapeViolations = validateEquivalentMutants(gate.path, gate.equivalentMutants);
|
|
1559
|
+
violations.push(...shapeViolations);
|
|
1560
|
+
violations.push(
|
|
1561
|
+
...validateEquivalentMutants(gate.path, gate.expectedTimeouts, "expectedTimeouts")
|
|
1562
|
+
);
|
|
1563
|
+
const result = files.get(gatePath);
|
|
1564
|
+
if (!result || !Array.isArray(result.mutants) || result.mutants.length === 0) {
|
|
1565
|
+
violations.push({
|
|
1566
|
+
file: gate.path,
|
|
1567
|
+
message: "no mutants in the report (the gate was not mutation tested)"
|
|
1568
|
+
});
|
|
1569
|
+
continue;
|
|
1570
|
+
}
|
|
1571
|
+
const diskSource = input.readSource(gate.path);
|
|
1572
|
+
const source = result.source ?? diskSource;
|
|
1573
|
+
if (source === void 0) {
|
|
1574
|
+
violations.push({
|
|
1575
|
+
file: gate.path,
|
|
1576
|
+
message: "source is neither in the report nor readable on disk"
|
|
1577
|
+
});
|
|
1578
|
+
continue;
|
|
1579
|
+
}
|
|
1580
|
+
if (result.source !== void 0 && diskSource !== void 0 && normalizeNewlines(result.source) !== normalizeNewlines(diskSource)) {
|
|
1581
|
+
violations.push({
|
|
1582
|
+
file: gate.path,
|
|
1583
|
+
message: "the report was made from a different version of this file. Run Stryker again"
|
|
1584
|
+
});
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
const { located, byKey, outside } = locateMutants(source, result.mutants);
|
|
1588
|
+
for (const mutant of outside) {
|
|
1589
|
+
violations.push({
|
|
1590
|
+
file: gate.path,
|
|
1591
|
+
line: mutant.location.start.line,
|
|
1592
|
+
message: `the report location of a ${mutant.mutatorName} mutant is outside the source`
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
if (outside.length > 0) {
|
|
1596
|
+
continue;
|
|
1597
|
+
}
|
|
1598
|
+
const claim = (field, entries, noun, accept) => {
|
|
1599
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
1600
|
+
if (!Array.isArray(entries)) {
|
|
1601
|
+
return claimed;
|
|
1602
|
+
}
|
|
1603
|
+
entries.forEach((entry, index) => {
|
|
1604
|
+
if (!isUsableEquivalentMutant(entry)) {
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
const at = `${gate.path}: ${field}[${index}] (${describeAllowance(entry)})`;
|
|
1608
|
+
const found = resolveEntry(entry, byKey);
|
|
1609
|
+
if ("ambiguous" in found) {
|
|
1610
|
+
violations.push({
|
|
1611
|
+
file: MANIFEST_FILE,
|
|
1612
|
+
message: `${at}: matches ${found.ambiguous} mutants in the file. Add "occurrence" (1-based) to pick one`
|
|
1613
|
+
});
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
const target = found.target;
|
|
1617
|
+
const refusal = target ? accept(target.mutant.status) : `stale ${noun} (no such mutant in this run). Remove it`;
|
|
1618
|
+
if (!target || refusal !== null) {
|
|
1619
|
+
violations.push({ file: MANIFEST_FILE, message: `${at}: ${refusal}` });
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
if (claimed.has(target)) {
|
|
1623
|
+
violations.push({
|
|
1624
|
+
file: MANIFEST_FILE,
|
|
1625
|
+
message: `${at}: another ${noun} already covers this mutant`
|
|
1626
|
+
});
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
claimed.add(target);
|
|
1630
|
+
});
|
|
1631
|
+
return claimed;
|
|
1632
|
+
};
|
|
1633
|
+
const allowed = claim(
|
|
1634
|
+
"equivalentMutants",
|
|
1635
|
+
gate.equivalentMutants,
|
|
1636
|
+
"allowance",
|
|
1637
|
+
(status) => SURVIVED.has(status) ? null : `stale allowance (the mutant is now ${status}). Remove it`
|
|
1638
|
+
);
|
|
1639
|
+
const expected = claim(
|
|
1640
|
+
"expectedTimeouts",
|
|
1641
|
+
gate.expectedTimeouts,
|
|
1642
|
+
"entry",
|
|
1643
|
+
(status) => SURVIVED.has(status) ? `the mutant ${status === "NoCoverage" ? "has no coverage" : "survived"} instead of timing out. Detect it with a test` : null
|
|
1644
|
+
);
|
|
1645
|
+
for (const entry of located) {
|
|
1646
|
+
const { mutant } = entry;
|
|
1647
|
+
summary.total += 1;
|
|
1648
|
+
if (DETECTED.has(mutant.status)) {
|
|
1649
|
+
summary.detected += 1;
|
|
1650
|
+
if (mutant.status === "Timeout") {
|
|
1651
|
+
summary.timeout += 1;
|
|
1652
|
+
if (expected.has(entry)) {
|
|
1653
|
+
summary.expectedTimeout += 1;
|
|
1654
|
+
} else {
|
|
1655
|
+
unexpectedTimeouts.push(describeMutant(gate.path, entry));
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
} else if (NOT_EVALUABLE.has(mutant.status)) {
|
|
1659
|
+
summary.notEvaluable += 1;
|
|
1660
|
+
} else if (SURVIVED.has(mutant.status)) {
|
|
1661
|
+
if (allowed.has(entry)) {
|
|
1662
|
+
summary.allowed += 1;
|
|
1663
|
+
continue;
|
|
1664
|
+
}
|
|
1665
|
+
summary.unallowed += 1;
|
|
1666
|
+
survivors.push(describeMutant(gate.path, entry));
|
|
1667
|
+
} else {
|
|
1668
|
+
violations.push({
|
|
1669
|
+
file: gate.path,
|
|
1670
|
+
line: mutant.location.start.line,
|
|
1671
|
+
message: mutant.status === "Ignored" ? `a ${mutant.mutatorName} mutant was ignored. Taking mutants out with Stryker comments or mutator exclusions is not allowed` : `a ${mutant.mutatorName} mutant was not evaluated (status: ${mutant.status})`
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
if (summary.total === 0) {
|
|
1677
|
+
violations.push({
|
|
1678
|
+
file: MANIFEST_FILE,
|
|
1679
|
+
message: "no mutants were evaluated (the mutation test ran on nothing)"
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
const maxTimeouts = input.maxTimeouts ?? 0;
|
|
1683
|
+
if (unexpectedTimeouts.length > maxTimeouts) {
|
|
1684
|
+
violations.push({
|
|
1685
|
+
file: MANIFEST_FILE,
|
|
1686
|
+
message: `${unexpectedTimeouts.length} mutant(s) timed out outside expectedTimeouts; settings.mutation.maxTimeouts allows ${maxTimeouts}`
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
return { violations, survivors, unexpectedTimeouts, summary };
|
|
1690
|
+
}
|
|
1691
|
+
function formatSurvivor(survivor) {
|
|
1692
|
+
const occurrence = survivor.sameKeyCount > 1 ? ` / occurrence ${survivor.occurrence} of ${survivor.sameKeyCount}` : "";
|
|
1693
|
+
return `${survivor.file}:${survivor.line}:${survivor.column} / ${survivor.mutator} / ${survivor.original} \u2192 ${survivor.replacement}${occurrence} (${survivor.status})`;
|
|
1694
|
+
}
|
|
1695
|
+
function formatSummary(summary) {
|
|
1696
|
+
return `mutants ${summary.total} / detected ${summary.detected} (timeout ${summary.timeout}) / allowed equivalent ${summary.allowed} / not evaluable ${summary.notEvaluable} / unallowed survivors ${summary.unallowed}`;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1416
1699
|
// src/core/reuse-guard.ts
|
|
1417
1700
|
var RECORD_FORMAT = 2;
|
|
1418
1701
|
function isStringRecord(value) {
|
|
@@ -1531,6 +1814,53 @@ function incrementalOptions(config, args) {
|
|
|
1531
1814
|
function comparableArgs(args) {
|
|
1532
1815
|
return args.filter((arg) => arg !== "--force");
|
|
1533
1816
|
}
|
|
1817
|
+
function objectOf(value) {
|
|
1818
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
1819
|
+
}
|
|
1820
|
+
function reportFiles(config) {
|
|
1821
|
+
const options = objectOf(config);
|
|
1822
|
+
const fileName = (reporter, fallback) => {
|
|
1823
|
+
const named = objectOf(options[reporter]).fileName;
|
|
1824
|
+
return typeof named === "string" ? named : fallback;
|
|
1825
|
+
};
|
|
1826
|
+
return [
|
|
1827
|
+
fileName("htmlReporter", "reports/mutation/mutation.html"),
|
|
1828
|
+
fileName("jsonReporter", "reports/mutation/mutation.json")
|
|
1829
|
+
];
|
|
1830
|
+
}
|
|
1831
|
+
var WILDCARDS = /* @__PURE__ */ new Map([
|
|
1832
|
+
["**/", "(?:.*/)?"],
|
|
1833
|
+
["*", "[^/]*"],
|
|
1834
|
+
["?", "[^/]"]
|
|
1835
|
+
]);
|
|
1836
|
+
function globSource(glob) {
|
|
1837
|
+
return glob.split(/(\*\*\/|\*|\?)/).map((part) => WILDCARDS.get(part) ?? part.replace(/\W/g, "\\$&")).join("");
|
|
1838
|
+
}
|
|
1839
|
+
function strykerIgnores(config) {
|
|
1840
|
+
const patterns = [objectOf(config).ignorePatterns].flat().filter((pattern) => typeof pattern === "string");
|
|
1841
|
+
if (patterns.some((pattern) => pattern.startsWith("!"))) {
|
|
1842
|
+
return () => false;
|
|
1843
|
+
}
|
|
1844
|
+
const rules = patterns.map((pattern) => {
|
|
1845
|
+
const directoryOnly = pattern.endsWith("/");
|
|
1846
|
+
const body = pattern.replace(/\/+$/, "");
|
|
1847
|
+
const anchored = body.includes("/");
|
|
1848
|
+
const expression = new RegExp(`^${globSource(body.replace(/^\//, ""))}$`, "i");
|
|
1849
|
+
return { directoryOnly, anchored, expression };
|
|
1850
|
+
});
|
|
1851
|
+
return (relative) => {
|
|
1852
|
+
const parts = relative.split("/");
|
|
1853
|
+
return rules.some(
|
|
1854
|
+
(rule) => parts.some((part, index) => {
|
|
1855
|
+
if (rule.directoryOnly && index === parts.length - 1) {
|
|
1856
|
+
return false;
|
|
1857
|
+
}
|
|
1858
|
+
const target = rule.anchored ? parts.slice(0, index + 1).join("/") : part;
|
|
1859
|
+
return rule.expression.test(target);
|
|
1860
|
+
})
|
|
1861
|
+
);
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1534
1864
|
|
|
1535
1865
|
// src/core/affected.ts
|
|
1536
1866
|
var CONFIG_NAMES = [
|
|
@@ -1545,25 +1875,33 @@ var CONFIG_NAMES = [
|
|
|
1545
1875
|
// Declarations need no import to take effect.
|
|
1546
1876
|
/\.d\.[cm]?ts$/
|
|
1547
1877
|
];
|
|
1548
|
-
var
|
|
1878
|
+
var ROOT_MOCKS = "__mocks__";
|
|
1549
1879
|
var DOCUMENT = /\.(?:md|markdown|txt|rst|adoc)$/i;
|
|
1550
1880
|
var PASSIVE = /\.(?:[cm]?[jt]sx?|json|snap|css|scss|sass|less|svg|png|jpe?g|gif|webp|avif|ico)$/i;
|
|
1551
1881
|
function kindOf(key, subproject) {
|
|
1552
|
-
const
|
|
1553
|
-
const local = inside && subproject !== "" ? key.slice(subproject.length + 1) : key;
|
|
1554
|
-
const segments = local.split("/");
|
|
1882
|
+
const segments = localSegments(key, subproject);
|
|
1555
1883
|
const name = segments[segments.length - 1];
|
|
1556
|
-
if (segments
|
|
1884
|
+
if (segments[0] === ROOT_MOCKS || segments.includes("node_modules") || CONFIG_NAMES.some((pattern) => pattern.test(name))) {
|
|
1557
1885
|
return "config";
|
|
1558
1886
|
}
|
|
1887
|
+
if (isNestedMock(key, subproject)) {
|
|
1888
|
+
return "other";
|
|
1889
|
+
}
|
|
1559
1890
|
if (DOCUMENT.test(name)) {
|
|
1560
1891
|
return "document";
|
|
1561
1892
|
}
|
|
1893
|
+
const inside = subproject === "" || key.startsWith(`${subproject}/`);
|
|
1562
1894
|
if (!inside || segments.length === 1 || !PASSIVE.test(name)) {
|
|
1563
1895
|
return "other";
|
|
1564
1896
|
}
|
|
1565
1897
|
return "passive";
|
|
1566
1898
|
}
|
|
1899
|
+
function localSegments(key, subproject) {
|
|
1900
|
+
return (key.startsWith(`${subproject}/`) ? key.slice(subproject.length + 1) : key).split("/");
|
|
1901
|
+
}
|
|
1902
|
+
function isNestedMock(key, subproject) {
|
|
1903
|
+
return localSegments(key, subproject).slice(1, -1).includes(ROOT_MOCKS);
|
|
1904
|
+
}
|
|
1567
1905
|
function gateDependencies(input) {
|
|
1568
1906
|
const closureOf = (key) => Object.hasOwn(input.closures, key) ? input.closures[key] : [key];
|
|
1569
1907
|
const result = /* @__PURE__ */ Object.create(null);
|
|
@@ -1587,7 +1925,7 @@ function planAffected(input) {
|
|
|
1587
1925
|
const recorded = input.before.affected;
|
|
1588
1926
|
if (recorded === void 0) {
|
|
1589
1927
|
return {
|
|
1590
|
-
full: "the last run did not write down what each gate depends on (it ran
|
|
1928
|
+
full: "the last run did not write down what each gate depends on (it ran in CI, with an older version, or could not tell)"
|
|
1591
1929
|
};
|
|
1592
1930
|
}
|
|
1593
1931
|
const changed = differingKeys(input.before.files, input.now);
|
|
@@ -1596,7 +1934,8 @@ function planAffected(input) {
|
|
|
1596
1934
|
const config = new Set(input.config);
|
|
1597
1935
|
const everywhere = changed.filter((key) => {
|
|
1598
1936
|
const kind = kindOf(key, input.subproject);
|
|
1599
|
-
return config.has(key) || kind === "config" || kind === "other" && !reached.has(key)
|
|
1937
|
+
return config.has(key) || kind === "config" || kind === "other" && !reached.has(key) || // A mock that is gone may have been one of a package, which is used by name for every spec.
|
|
1938
|
+
isNestedMock(key, input.subproject) && !Object.hasOwn(input.now, key);
|
|
1600
1939
|
});
|
|
1601
1940
|
if (everywhere.length > 0) {
|
|
1602
1941
|
return { full: `changed since the last run, and may affect every gate: ${listed(everywhere)}` };
|
|
@@ -1624,23 +1963,33 @@ function withoutDots(file) {
|
|
|
1624
1963
|
}
|
|
1625
1964
|
function timeoutRanges(last, gates) {
|
|
1626
1965
|
const ranges = [];
|
|
1966
|
+
const rerun = /* @__PURE__ */ new Set();
|
|
1967
|
+
const taken = /* @__PURE__ */ new Set();
|
|
1627
1968
|
const files = last.files ?? {};
|
|
1628
1969
|
for (const named of gates) {
|
|
1629
|
-
const gate = withoutDots(named);
|
|
1630
|
-
const
|
|
1631
|
-
if (mutants === void 0) {
|
|
1970
|
+
const gate = withoutDots(named.path);
|
|
1971
|
+
const result = Object.hasOwn(files, gate) ? files[gate] : void 0;
|
|
1972
|
+
if (result?.mutants === void 0) {
|
|
1632
1973
|
continue;
|
|
1633
1974
|
}
|
|
1975
|
+
const { mutants } = result;
|
|
1976
|
+
const expected = expectedTimeoutMutants(named, result.source, mutants);
|
|
1634
1977
|
for (const mutant of mutants) {
|
|
1635
|
-
if (mutant.status
|
|
1636
|
-
|
|
1637
|
-
|
|
1978
|
+
if (mutant.status !== "Timeout") {
|
|
1979
|
+
continue;
|
|
1980
|
+
}
|
|
1981
|
+
if (expected.has(mutant)) {
|
|
1982
|
+
taken.add(mutant);
|
|
1983
|
+
continue;
|
|
1638
1984
|
}
|
|
1985
|
+
rerun.add(mutant);
|
|
1986
|
+
const { start, end } = mutant.location;
|
|
1987
|
+
ranges.push(`${gate}:${start.line}:${start.column - 1}-${end.line}:${end.column}`);
|
|
1639
1988
|
}
|
|
1640
1989
|
}
|
|
1641
|
-
return [...new Set(ranges)];
|
|
1990
|
+
return { ranges: [...new Set(ranges)], rerun: rerun.size, expected: taken.size };
|
|
1642
1991
|
}
|
|
1643
|
-
function describePlan(plan, shown = 5, timeouts = 0) {
|
|
1992
|
+
function describePlan(plan, shown = 5, timeouts = 0, expected = 0) {
|
|
1644
1993
|
if ("full" in plan) {
|
|
1645
1994
|
return [
|
|
1646
1995
|
`test-gates mutation --affected: running every mutant again (--force), the results of the last run are not reused: ${plan.full}`
|
|
@@ -1661,8 +2010,14 @@ function describePlan(plan, shown = 5, timeouts = 0) {
|
|
|
1661
2010
|
if (timeouts > 0) {
|
|
1662
2011
|
lines.push(` and ${timeouts} mutant(s) of reused gates that timed out in the last run`);
|
|
1663
2012
|
}
|
|
2013
|
+
if (expected > 0) {
|
|
2014
|
+
lines.push(` ${describeExpected(expected)}`);
|
|
2015
|
+
}
|
|
1664
2016
|
return lines;
|
|
1665
2017
|
}
|
|
2018
|
+
function describeExpected(expected) {
|
|
2019
|
+
return `${expected} mutant(s) that timed out in the last run are taken over: expectedTimeouts lists them`;
|
|
2020
|
+
}
|
|
1666
2021
|
|
|
1667
2022
|
// src/core/doctor.ts
|
|
1668
2023
|
var NOT_THE_HELPER = "the runner config is not made with";
|
|
@@ -1697,6 +2052,13 @@ function diagnose(facts) {
|
|
|
1697
2052
|
fix: "write such a path from the directory of its file (path.resolve(__dirname, \u2026)) so that it can be followed, or keep it out of the files the run loads"
|
|
1698
2053
|
});
|
|
1699
2054
|
}
|
|
2055
|
+
if (facts.blind !== null) {
|
|
2056
|
+
findings.push({
|
|
2057
|
+
level: facts.hooks.some((hook) => hook.affected) ? "problem" : "note",
|
|
2058
|
+
text: `with --affected, a change of any file runs every mutant: what a gate depends on cannot be told (${facts.blind})`,
|
|
2059
|
+
fix: "keep what reads files or loads modules by a computed name out of what the gates and their specs import, or run without --affected"
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
1700
2062
|
if (facts.scope !== null && "every" in facts.scope) {
|
|
1701
2063
|
findings.push({
|
|
1702
2064
|
level: "note",
|
|
@@ -1711,7 +2073,7 @@ function diagnose(facts) {
|
|
|
1711
2073
|
fix: null
|
|
1712
2074
|
});
|
|
1713
2075
|
}
|
|
1714
|
-
for (const hook of facts.hooks.filter((one) => !one.affected)) {
|
|
2076
|
+
for (const hook of facts.hooks.filter((one) => !one.affected && facts.blind === null)) {
|
|
1715
2077
|
findings.push({
|
|
1716
2078
|
level: "problem",
|
|
1717
2079
|
text: `${hook.file} runs test-gates mutation without --affected: a change of any file of the subproject runs every mutant`,
|
|
@@ -1774,23 +2136,50 @@ var SCRIPT = /\.[cm]?[jt]sx?$/;
|
|
|
1774
2136
|
var COMPONENT = /\.(?:vue|svelte|astro|mdx)$/;
|
|
1775
2137
|
var EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
1776
2138
|
var QUOTED = /(['"`])([^'"`\n]+)\1/g;
|
|
1777
|
-
var RUNNER_LOAD = /\b(?:requireActual|requireMock|importActual|importMock|mock|doMock|unstable_mockModule)\(\s*(['"`])([^'"`\n]+)\1/g;
|
|
2139
|
+
var RUNNER_LOAD = /\b(?:requireActual|requireMock|importActual|importMock|mock|doMock|unstable_mockModule|createMockFromModule|genMockFromModule)\s*(?:<[^()\n]*>)?\s*\(\s*(['"`])([^'"`\n]+)\1/g;
|
|
1778
2140
|
function isFile(file) {
|
|
1779
2141
|
try {
|
|
1780
|
-
return fs4.statSync(file)
|
|
2142
|
+
return fs4.statSync(file, { throwIfNoEntry: false })?.isFile() === true;
|
|
1781
2143
|
} catch {
|
|
1782
2144
|
return false;
|
|
1783
2145
|
}
|
|
1784
2146
|
}
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
2147
|
+
var resolved = null;
|
|
2148
|
+
function resolving(read2) {
|
|
2149
|
+
if (resolved !== null) {
|
|
2150
|
+
return read2();
|
|
2151
|
+
}
|
|
2152
|
+
resolved = /* @__PURE__ */ new Map();
|
|
2153
|
+
try {
|
|
2154
|
+
return read2();
|
|
2155
|
+
} finally {
|
|
2156
|
+
resolved = null;
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
var DECLARATIONS = [".d.ts", ".d.mts", ".d.cts"];
|
|
2160
|
+
function resolveFiles(base, directory = true, declarations = true) {
|
|
2161
|
+
const key = `${Number(directory)}${Number(declarations)}${base}`;
|
|
2162
|
+
const known = resolved?.get(key);
|
|
2163
|
+
if (known !== void 0) {
|
|
2164
|
+
return [...known];
|
|
2165
|
+
}
|
|
2166
|
+
const found = filesOf(base, directory, declarations);
|
|
2167
|
+
resolved?.set(key, found);
|
|
2168
|
+
return [...found];
|
|
2169
|
+
}
|
|
2170
|
+
function filesOf(base, directory, declarations) {
|
|
2171
|
+
const candidates = (extensions) => [
|
|
2172
|
+
...extensions.map((extension) => base + extension),
|
|
2173
|
+
// `./fee.js` written in a TypeScript file is `./fee.ts`
|
|
2174
|
+
...extensions.map((extension) => base.replace(/\.[cm]?jsx?$/, extension)),
|
|
2175
|
+
...directory ? extensions.map((extension) => path4.join(base, `index${extension}`)) : []
|
|
1792
2176
|
];
|
|
1793
|
-
|
|
2177
|
+
const data = [`${base}.json`, ...directory ? [path4.join(base, "index.json")] : []];
|
|
2178
|
+
const scripts = [.../* @__PURE__ */ new Set([base, ...candidates(EXTENSIONS), ...data])].filter(isFile);
|
|
2179
|
+
return scripts.length > 0 || !declarations ? scripts : [...new Set(candidates(DECLARATIONS))].filter(isFile);
|
|
2180
|
+
}
|
|
2181
|
+
function resolveFile(base, directory = true, declarations = true) {
|
|
2182
|
+
return resolveFiles(base, directory, declarations)[0] ?? null;
|
|
1794
2183
|
}
|
|
1795
2184
|
function installed(specifier, from) {
|
|
1796
2185
|
const name = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
|
|
@@ -1804,14 +2193,17 @@ function installed(specifier, from) {
|
|
|
1804
2193
|
}
|
|
1805
2194
|
}
|
|
1806
2195
|
function loadedFiles(manifest, configFile) {
|
|
2196
|
+
return resolving(() => loadedFilesNow(manifest, configFile));
|
|
2197
|
+
}
|
|
2198
|
+
function loadedFilesNow(manifest, configFile) {
|
|
1807
2199
|
const aliases = manifest.settings.importAliases;
|
|
1808
2200
|
const relative = (file) => path4.relative(manifest.dir, file).split(path4.sep).join("/");
|
|
1809
2201
|
const target = (specifier, from) => {
|
|
1810
2202
|
if (specifier.startsWith(".")) {
|
|
1811
|
-
return
|
|
2203
|
+
return resolveFiles(path4.resolve(path4.dirname(from), specifier));
|
|
1812
2204
|
}
|
|
1813
2205
|
const alias = aliases.find((candidate) => specifier.startsWith(candidate.prefix));
|
|
1814
|
-
return alias === void 0 ?
|
|
2206
|
+
return alias === void 0 ? [] : resolveFiles(
|
|
1815
2207
|
path4.resolve(manifest.dir, alias.target + specifier.slice(alias.prefix.length))
|
|
1816
2208
|
);
|
|
1817
2209
|
};
|
|
@@ -1836,16 +2228,18 @@ function loadedFiles(manifest, configFile) {
|
|
|
1836
2228
|
const paths = strings.filter((text) => file === entry || /^(?:\.|<rootDir>\/)/.test(text));
|
|
1837
2229
|
for (const text of paths) {
|
|
1838
2230
|
const stripped = text.replace(/^<rootDir>\//, "");
|
|
1839
|
-
const
|
|
1840
|
-
|
|
1841
|
-
|
|
2231
|
+
const here = resolveFiles(path4.resolve(path4.dirname(file), stripped), false);
|
|
2232
|
+
const found = here.length > 0 ? here : resolveFiles(path4.resolve(manifest.dir, stripped), false);
|
|
2233
|
+
for (const one of found.filter((candidate) => SCRIPT.test(candidate))) {
|
|
2234
|
+
named.add(one);
|
|
1842
2235
|
}
|
|
1843
2236
|
}
|
|
1844
2237
|
for (const specifier of specifiers.filter((one) => isProjectImport(one, aliases))) {
|
|
1845
|
-
const found
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
2238
|
+
for (const found of target(specifier, file)) {
|
|
2239
|
+
if (SCRIPT.test(found) && !seenConfigs.has(found)) {
|
|
2240
|
+
seenConfigs.add(found);
|
|
2241
|
+
configs.push(found);
|
|
2242
|
+
}
|
|
1849
2243
|
}
|
|
1850
2244
|
}
|
|
1851
2245
|
}
|
|
@@ -1862,10 +2256,10 @@ function loadedFiles(manifest, configFile) {
|
|
|
1862
2256
|
for (const specifier of specifiers) {
|
|
1863
2257
|
if (isProjectImport(specifier, aliases)) {
|
|
1864
2258
|
const found = target(specifier, file);
|
|
1865
|
-
if (found ===
|
|
2259
|
+
if (found.length === 0) {
|
|
1866
2260
|
lost ??= `${specifier} (imported by ${relative(file)})`;
|
|
1867
2261
|
} else {
|
|
1868
|
-
next.push(found);
|
|
2262
|
+
next.push(...found);
|
|
1869
2263
|
}
|
|
1870
2264
|
} else if (specifier.startsWith("node:") || builtinModules.includes(specifier)) {
|
|
1871
2265
|
imports.push({ specifier, builtin: true });
|
|
@@ -1876,10 +2270,7 @@ function loadedFiles(manifest, configFile) {
|
|
|
1876
2270
|
}
|
|
1877
2271
|
}
|
|
1878
2272
|
for (const text of strings.filter((one) => one.startsWith("."))) {
|
|
1879
|
-
|
|
1880
|
-
if (found !== null) {
|
|
1881
|
-
next.push(found);
|
|
1882
|
-
}
|
|
2273
|
+
next.push(...resolveFiles(path4.resolve(path4.dirname(file), text)));
|
|
1883
2274
|
}
|
|
1884
2275
|
const visited = {
|
|
1885
2276
|
file: { path: relative(file), source, imports },
|
|
@@ -1950,10 +2341,11 @@ var NEVER = ["node_modules", ".git"];
|
|
|
1950
2341
|
var WIDENED_SHOWN = 5;
|
|
1951
2342
|
var SCAN_LIMIT = 8 * 1024 * 1024;
|
|
1952
2343
|
var UPWARD = /(['"`])((?:[^'"`\n]*\/)?\.\.(?:\/[^'"`\n]*)?)\1/g;
|
|
1953
|
-
var DOCUMENTS = [".md", ".markdown", ".txt", ".rst", ".adoc"];
|
|
2344
|
+
var DOCUMENTS = [".md", ".markdown", ".txt", ".rst", ".adoc", ".html", ".htm"];
|
|
1954
2345
|
var HERE = /^\$\{(?:__dirname|import\.meta\.dirname)\}(?=\/)/;
|
|
1955
2346
|
var FROM_HERE = /\b(?:__dirname|import\.meta\.dirname)((?:\s*,\s*(['"])[^'"`\n]*\2)+)(\s*\))?/g;
|
|
1956
2347
|
var LITERAL = /(['"])([^'"`\n]*)\1/g;
|
|
2348
|
+
var DOWNWARD = /(['"`])(\.\/[^'"`\n]*)\1/g;
|
|
1957
2349
|
var isUpward = (segment) => segment.split("/").includes("..");
|
|
1958
2350
|
function hashOf(content) {
|
|
1959
2351
|
return createHash("sha256").update(content).digest("hex");
|
|
@@ -1989,9 +2381,12 @@ function git(cwd, args) {
|
|
|
1989
2381
|
});
|
|
1990
2382
|
return result.error || result.status !== 0 ? null : result.stdout;
|
|
1991
2383
|
}
|
|
2384
|
+
function globLiteral(relative) {
|
|
2385
|
+
return relative.replace(/[*?[\]\\]/g, "\\$&");
|
|
2386
|
+
}
|
|
1992
2387
|
function gitLister(root) {
|
|
1993
2388
|
return (directory, recursive) => {
|
|
1994
|
-
const relative = toPosix(path5.relative(root, directory));
|
|
2389
|
+
const relative = globLiteral(toPosix(path5.relative(root, directory)));
|
|
1995
2390
|
const pattern = recursive ? `${relative === "" ? "" : `${relative}/`}**` : `${relative === "" ? "" : `${relative}/`}*`;
|
|
1996
2391
|
const listed2 = git(root, [
|
|
1997
2392
|
"ls-files",
|
|
@@ -2115,11 +2510,24 @@ function installedVersion(start, name) {
|
|
|
2115
2510
|
}
|
|
2116
2511
|
}
|
|
2117
2512
|
}
|
|
2513
|
+
function nodeOnPath(cwd) {
|
|
2514
|
+
const result = spawnSync("node", ["--version"], {
|
|
2515
|
+
cwd,
|
|
2516
|
+
encoding: "utf8",
|
|
2517
|
+
shell: process.platform === "win32",
|
|
2518
|
+
timeout: 3e4
|
|
2519
|
+
});
|
|
2520
|
+
const version = result.status === 0 ? result.stdout.trim() : "";
|
|
2521
|
+
return version === "" ? {} : { "Node.js on PATH": version };
|
|
2522
|
+
}
|
|
2118
2523
|
function ownVersion() {
|
|
2119
2524
|
const { version } = createRequire(import.meta.url)("../package.json");
|
|
2120
2525
|
return version;
|
|
2121
2526
|
}
|
|
2122
2527
|
function snapshotOf(input) {
|
|
2528
|
+
return resolving(() => snapshotNow(input));
|
|
2529
|
+
}
|
|
2530
|
+
function snapshotNow(input) {
|
|
2123
2531
|
const dir = fs5.realpathSync(input.dir);
|
|
2124
2532
|
const top = git(dir, ["rev-parse", "--show-toplevel"]);
|
|
2125
2533
|
let root = top === null ? dir : fs5.realpathSync(top.trim());
|
|
@@ -2173,19 +2581,74 @@ function snapshotOf(input) {
|
|
|
2173
2581
|
}
|
|
2174
2582
|
};
|
|
2175
2583
|
const follow = (fixed, open, anchored, file, reference) => {
|
|
2176
|
-
const
|
|
2177
|
-
|
|
2584
|
+
const from = (start) => {
|
|
2585
|
+
const target = path5.resolve(start, fixed);
|
|
2586
|
+
let isDirectory2 = false;
|
|
2587
|
+
try {
|
|
2588
|
+
isDirectory2 = fs5.statSync(target).isDirectory();
|
|
2589
|
+
} catch {
|
|
2590
|
+
}
|
|
2591
|
+
return {
|
|
2592
|
+
target,
|
|
2593
|
+
isDirectory: isDirectory2,
|
|
2594
|
+
found: isDirectory2 || resolveFile(target, true, false) !== null
|
|
2595
|
+
};
|
|
2596
|
+
};
|
|
2597
|
+
const fromFile = from(path5.dirname(file));
|
|
2598
|
+
const targets = [fromFile];
|
|
2599
|
+
if (!anchored && path5.dirname(file) !== dir) {
|
|
2600
|
+
const fromRun = from(dir);
|
|
2601
|
+
if (fromRun.found && !fromFile.found) {
|
|
2602
|
+
targets[0] = fromRun;
|
|
2603
|
+
} else if (fromRun.found && isInside(fromRun.target, listRoot)) {
|
|
2604
|
+
enqueue(listRoot, file, reference);
|
|
2605
|
+
} else if (fromRun.found) {
|
|
2606
|
+
targets.push(fromRun);
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
for (const { target, isDirectory: isDirectory2 } of targets) {
|
|
2610
|
+
const beside = (isDirectory2 || open ? [] : resolveFiles(target, false, false)).filter(
|
|
2611
|
+
(one) => besideAncestors.has(one)
|
|
2612
|
+
);
|
|
2613
|
+
if (beside.length > 0) {
|
|
2614
|
+
beside.forEach(reach);
|
|
2615
|
+
continue;
|
|
2616
|
+
}
|
|
2617
|
+
if (!isDirectory2 && !isInside(listRoot, target) && resolveFile(target, true, false) === null) {
|
|
2618
|
+
files[toPosix(path5.relative(listRoot, target))] = "missing";
|
|
2619
|
+
continue;
|
|
2620
|
+
}
|
|
2621
|
+
const directory = isDirectory2 ? target : path5.dirname(target);
|
|
2622
|
+
if (fs5.existsSync(directory)) {
|
|
2623
|
+
enqueue(directory, file, reference, anchored && !open);
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
};
|
|
2627
|
+
const reached = /* @__PURE__ */ new Set();
|
|
2628
|
+
const reach = (file) => {
|
|
2629
|
+
if (reached.has(file) || DOCUMENTS.includes(path5.extname(file).toLowerCase())) {
|
|
2630
|
+
return;
|
|
2631
|
+
}
|
|
2632
|
+
reached.add(file);
|
|
2633
|
+
let text;
|
|
2178
2634
|
try {
|
|
2179
|
-
|
|
2635
|
+
const content = fs5.readFileSync(file);
|
|
2636
|
+
if (content.length > SCAN_LIMIT || content.subarray(0, 8e3).includes(0)) {
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
text = content.toString("utf8").replace(/\\[\\/]/g, "/");
|
|
2180
2640
|
} catch {
|
|
2181
|
-
}
|
|
2182
|
-
if (!isDirectory && !isInside(listRoot, target) && resolveFile(target) === null) {
|
|
2183
|
-
files[toPosix(path5.relative(listRoot, target))] = "missing";
|
|
2184
2641
|
return;
|
|
2185
2642
|
}
|
|
2186
|
-
const
|
|
2187
|
-
|
|
2188
|
-
|
|
2643
|
+
for (const match of text.matchAll(DOWNWARD)) {
|
|
2644
|
+
const literal = match[2];
|
|
2645
|
+
const open = literal.includes("${");
|
|
2646
|
+
const fixed = open ? literal.slice(0, literal.lastIndexOf("/", literal.indexOf("${")) + 1) : literal;
|
|
2647
|
+
if (open && isUpward(literal.slice(literal.indexOf("${")))) {
|
|
2648
|
+
enqueue(listRoot, file, literal);
|
|
2649
|
+
} else {
|
|
2650
|
+
follow(fixed, open, false, file, literal);
|
|
2651
|
+
}
|
|
2189
2652
|
}
|
|
2190
2653
|
};
|
|
2191
2654
|
const scan = (text, file) => {
|
|
@@ -2257,8 +2720,17 @@ function snapshotOf(input) {
|
|
|
2257
2720
|
}
|
|
2258
2721
|
};
|
|
2259
2722
|
const ancestors = upTo(dir, listRoot).slice(1);
|
|
2260
|
-
|
|
2261
|
-
|
|
2723
|
+
const besideAncestors = /* @__PURE__ */ new Set();
|
|
2724
|
+
const ancestorFiles = ancestors.flatMap((directory) => list(directory, false));
|
|
2725
|
+
for (const relative of ancestorFiles) {
|
|
2726
|
+
besideAncestors.add(path5.join(listRoot, relative));
|
|
2727
|
+
}
|
|
2728
|
+
ancestorFiles.forEach(add);
|
|
2729
|
+
for (const directory of [dir, ...ancestors]) {
|
|
2730
|
+
const patches = path5.join(directory, "patches");
|
|
2731
|
+
if (fs5.existsSync(patches) && fs5.statSync(patches).isDirectory()) {
|
|
2732
|
+
enqueue(patches, directory);
|
|
2733
|
+
}
|
|
2262
2734
|
}
|
|
2263
2735
|
for (const directory of [dir, ...ancestors]) {
|
|
2264
2736
|
for (const linked of linkedPackages(path5.join(directory, "node_modules"), listRoot)) {
|
|
@@ -2280,11 +2752,23 @@ function snapshotOf(input) {
|
|
|
2280
2752
|
}
|
|
2281
2753
|
}
|
|
2282
2754
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2755
|
+
let position = 0;
|
|
2756
|
+
const drain = () => {
|
|
2757
|
+
for (; position < regions.length; position += 1) {
|
|
2758
|
+
list(regions[position], true).forEach(add);
|
|
2759
|
+
}
|
|
2760
|
+
};
|
|
2761
|
+
drain();
|
|
2762
|
+
for (const file of followed ?? []) {
|
|
2763
|
+
const parts = path5.relative(listRoot, file).split(path5.sep);
|
|
2764
|
+
if (isInside(listRoot, file) && !parts.some((part) => NEVER.includes(part)) && fs5.statSync(file, { throwIfNoEntry: false })?.isFile() === true) {
|
|
2765
|
+
add(toPosix(path5.relative(listRoot, file)));
|
|
2766
|
+
}
|
|
2285
2767
|
}
|
|
2768
|
+
drain();
|
|
2286
2769
|
const environment = {
|
|
2287
2770
|
"Node.js": process.version,
|
|
2771
|
+
...nodeOnPath(dir),
|
|
2288
2772
|
platform: `${process.platform} ${process.arch}`,
|
|
2289
2773
|
"test-gates": ownVersion()
|
|
2290
2774
|
};
|
|
@@ -2297,19 +2781,23 @@ function snapshotOf(input) {
|
|
|
2297
2781
|
return { snapshot: { mode, environment, args: input.args, files }, root: listRoot, widened };
|
|
2298
2782
|
}
|
|
2299
2783
|
|
|
2300
|
-
// src/commands/mutation.ts
|
|
2301
|
-
import { spawnSync as spawnSync2 } from "child_process";
|
|
2302
|
-
import fs8 from "fs";
|
|
2303
|
-
import path8 from "path";
|
|
2304
|
-
import { pathToFileURL } from "url";
|
|
2305
|
-
|
|
2306
2784
|
// src/affected-files.ts
|
|
2307
2785
|
import fs6 from "fs";
|
|
2308
2786
|
import { builtinModules as builtinModules2 } from "module";
|
|
2309
2787
|
import path6 from "path";
|
|
2310
2788
|
var AffectedError = class extends Error {
|
|
2311
2789
|
};
|
|
2312
|
-
var RUNTIME_PATHS = /\b(?:
|
|
2790
|
+
var RUNTIME_PATHS = /\b(?:require\.(?:context|resolve)|process\.cwd|createRequire|toMatchFileSnapshot|readFileSync|readdirSync)\b|\bimport\.meta\.(?:glob|resolve)\b/;
|
|
2791
|
+
var LOCATION = /\b(?:__dirname|__filename)\b|\bimport\.meta\.(?:url|dirname|filename)\b/;
|
|
2792
|
+
var LOCATION_READS = /\b(?:readFile|readdir|readlink|existsSync|stat|statSync|lstat|lstatSync|access|accessSync|realpath|realpathSync|open|openSync|opendir|opendirSync|createReadStream|watch|watchFile|glob|globSync|promises)\b|\b(?:requireActual|requireMock|importActual|importMock|mock|doMock|unstable_mockModule|setMock|createMockFromModule)\(\s*(?![\s'"`)])|\bWorker\(/;
|
|
2793
|
+
var LOCATION_SAFE = /* @__PURE__ */ new Set([
|
|
2794
|
+
"path",
|
|
2795
|
+
"path/posix",
|
|
2796
|
+
"path/win32",
|
|
2797
|
+
"url",
|
|
2798
|
+
"vitest",
|
|
2799
|
+
"@jest/globals"
|
|
2800
|
+
]);
|
|
2313
2801
|
var FILE_MODULES = /* @__PURE__ */ new Set([
|
|
2314
2802
|
"fs",
|
|
2315
2803
|
"fs/promises",
|
|
@@ -2365,10 +2853,98 @@ function runnerConfigs(dir, stryker, options) {
|
|
|
2365
2853
|
}
|
|
2366
2854
|
}
|
|
2367
2855
|
}
|
|
2368
|
-
return {
|
|
2856
|
+
return {
|
|
2857
|
+
files: files.filter((file) => fs6.existsSync(file)).map(realpath),
|
|
2858
|
+
runner: options.testRunner,
|
|
2859
|
+
checkers: Array.isArray(options.checkers) && options.checkers.length > 0
|
|
2860
|
+
};
|
|
2861
|
+
}
|
|
2862
|
+
var CHECKS_TYPES = /\bcheckers\b|typescript-checker|\btypecheck\b/;
|
|
2863
|
+
var TS_JEST_STRIPS = /["']?(?:isolatedModules["']?\s*:\s*true|diagnostics["']?\s*:\s*false)/;
|
|
2864
|
+
var TS_JEST_UNSURE = /\b(?:projects|compilerOptions)\b|\btsconfig["']?\s*:\s*\{/;
|
|
2865
|
+
var STRIPPING_TRANSFORMS = /@swc\/jest|babel-jest|esbuild-jest|@sucrase\/jest/;
|
|
2866
|
+
var KEEPS_IMPORTS = /\b(?:verbatimModuleSyntax|preserveValueImports|importsNotUsedAsValues|onlyRemoveTypeImports)\b/;
|
|
2867
|
+
var COMPILER_CONFIG = /^(?:[jt]sconfig(?:\..+)?\.json|\.swcrc|\.babelrc(?:\..+)?|babel\.config\..+)$/;
|
|
2868
|
+
function typesErased(configs, texts, compiler) {
|
|
2869
|
+
const all = texts.map(withoutComments).join("\n");
|
|
2870
|
+
let erased = false;
|
|
2871
|
+
if (configs.checkers !== true && !CHECKS_TYPES.test(all)) {
|
|
2872
|
+
if (configs.runner === "vitest") {
|
|
2873
|
+
erased = true;
|
|
2874
|
+
} else if (configs.runner === "jest") {
|
|
2875
|
+
erased = /\bts-jest\b/.test(all) ? TS_JEST_STRIPS.test(all) && !TS_JEST_UNSURE.test(all) : STRIPPING_TRANSFORMS.test(all) && !/\bpreset\b/.test(all);
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
const inline = erased && !KEEPS_IMPORTS.test(all) && compiler.some(({ file }) => /^tsconfig/.test(path6.basename(file))) && compiler.every(({ text }) => text !== null && !KEEPS_IMPORTS.test(text));
|
|
2879
|
+
return { erased, inline };
|
|
2880
|
+
}
|
|
2881
|
+
function compilerConfigs(dir, root, keys) {
|
|
2882
|
+
const above = /* @__PURE__ */ new Set();
|
|
2883
|
+
for (let current = path6.dirname(dir); ; current = path6.dirname(current)) {
|
|
2884
|
+
const relative = path6.relative(root, current);
|
|
2885
|
+
if (relative.startsWith("..") || path6.isAbsolute(relative)) {
|
|
2886
|
+
break;
|
|
2887
|
+
}
|
|
2888
|
+
above.add(current);
|
|
2889
|
+
if (current === path6.dirname(current)) {
|
|
2890
|
+
break;
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
const compared = new Set(keys.map((key) => path6.join(root, key)));
|
|
2894
|
+
const starts = [...compared].filter(
|
|
2895
|
+
(file) => (COMPILER_CONFIG.test(path6.basename(file)) || path6.basename(file) === "package.json") && (file.startsWith(`${dir}${path6.sep}`) || above.has(path6.dirname(file)))
|
|
2896
|
+
);
|
|
2897
|
+
const found = [];
|
|
2898
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2899
|
+
const queue = [...starts];
|
|
2900
|
+
for (let file = queue.shift(); file !== void 0; file = queue.shift()) {
|
|
2901
|
+
if (seen.has(file)) {
|
|
2902
|
+
continue;
|
|
2903
|
+
}
|
|
2904
|
+
seen.add(file);
|
|
2905
|
+
let text = null;
|
|
2906
|
+
if (compared.has(file) || file.split(path6.sep).includes("node_modules")) {
|
|
2907
|
+
try {
|
|
2908
|
+
text = fs6.readFileSync(file, "utf8");
|
|
2909
|
+
} catch {
|
|
2910
|
+
text = null;
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
found.push({ file, text });
|
|
2914
|
+
for (const base of text === null ? [] : valuesOf(text, "extends")) {
|
|
2915
|
+
queue.push(extended(base, file) ?? `${file} extends ${base}`);
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
return found;
|
|
2919
|
+
}
|
|
2920
|
+
function extended(base, from) {
|
|
2921
|
+
const candidates = (start) => [
|
|
2922
|
+
start,
|
|
2923
|
+
`${start}.json`,
|
|
2924
|
+
path6.join(start, "tsconfig.json")
|
|
2925
|
+
];
|
|
2926
|
+
if (base.startsWith(".") || path6.isAbsolute(base)) {
|
|
2927
|
+
return candidates(path6.resolve(path6.dirname(from), base)).find(isFile2) ?? null;
|
|
2928
|
+
}
|
|
2929
|
+
for (let current = path6.dirname(from); ; current = path6.dirname(current)) {
|
|
2930
|
+
const file = candidates(path6.join(current, "node_modules", base)).find(isFile2);
|
|
2931
|
+
if (file !== void 0) {
|
|
2932
|
+
return file;
|
|
2933
|
+
}
|
|
2934
|
+
if (current === path6.dirname(current)) {
|
|
2935
|
+
return null;
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
function isFile2(file) {
|
|
2940
|
+
try {
|
|
2941
|
+
return fs6.statSync(file).isFile();
|
|
2942
|
+
} catch {
|
|
2943
|
+
return false;
|
|
2944
|
+
}
|
|
2369
2945
|
}
|
|
2370
2946
|
function fileEdges(file, context) {
|
|
2371
|
-
const { dir, aliases, cache } = context;
|
|
2947
|
+
const { dir, aliases, cache, types } = context;
|
|
2372
2948
|
const known = cache.get(file);
|
|
2373
2949
|
if (known !== void 0) {
|
|
2374
2950
|
return known;
|
|
@@ -2376,10 +2952,10 @@ function fileEdges(file, context) {
|
|
|
2376
2952
|
const shown = (one) => toPosix2(path6.relative(dir, one));
|
|
2377
2953
|
const target = (specifier, from) => {
|
|
2378
2954
|
if (specifier.startsWith(".")) {
|
|
2379
|
-
return
|
|
2955
|
+
return resolveFiles(path6.resolve(path6.dirname(from), specifier));
|
|
2380
2956
|
}
|
|
2381
2957
|
const alias = aliases.find((candidate) => specifier.startsWith(candidate.prefix));
|
|
2382
|
-
return alias === void 0 ?
|
|
2958
|
+
return alias === void 0 ? [] : resolveFiles(path6.resolve(dir, alias.target + specifier.slice(alias.prefix.length)));
|
|
2383
2959
|
};
|
|
2384
2960
|
const found = { loads: [], unknown: null, computed: false, unnamed: [] };
|
|
2385
2961
|
cache.set(file, found);
|
|
@@ -2391,41 +2967,61 @@ function fileEdges(file, context) {
|
|
|
2391
2967
|
if (fs6.existsSync(snapshotFile)) {
|
|
2392
2968
|
found.loads.push(realpath(snapshotFile));
|
|
2393
2969
|
}
|
|
2970
|
+
const mocks = path6.join(path6.dirname(file), "__mocks__");
|
|
2971
|
+
const name = path6.basename(file);
|
|
2972
|
+
const candidates = isDirectory(mocks) ? [
|
|
2973
|
+
path6.join(mocks, name),
|
|
2974
|
+
...resolveFiles(path6.join(mocks, name.replace(/\.[^.]+$/, "")), false, false)
|
|
2975
|
+
] : [];
|
|
2976
|
+
for (const mock of new Set(candidates)) {
|
|
2977
|
+
if (isFile2(mock)) {
|
|
2978
|
+
found.loads.push(realpath(mock));
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2394
2981
|
if (!SCRIPT.test(file) && !COMPONENT.test(file)) {
|
|
2395
2982
|
return found;
|
|
2396
2983
|
}
|
|
2397
2984
|
const source = fs6.readFileSync(file, "utf8");
|
|
2985
|
+
const code = withoutComments(source);
|
|
2398
2986
|
if (COMPONENT.test(file)) {
|
|
2399
2987
|
found.unknown = `${shown(file)} is a component, which can use others that it does not import`;
|
|
2400
2988
|
}
|
|
2401
|
-
const uncheckable = findUncheckableImports(
|
|
2402
|
-
const runtime = RUNTIME_PATHS.exec(
|
|
2989
|
+
const uncheckable = findUncheckableImports(code)[0];
|
|
2990
|
+
const runtime = RUNTIME_PATHS.exec(code);
|
|
2403
2991
|
if (uncheckable !== void 0) {
|
|
2404
2992
|
found.unknown = `${shown(file)}:${uncheckable} imports a module whose path is not written out`;
|
|
2405
2993
|
found.computed = true;
|
|
2406
2994
|
} else if (runtime !== null) {
|
|
2407
2995
|
found.unknown = `${shown(file)} uses ${runtime[0]}, which can reach files that no import names`;
|
|
2408
2996
|
}
|
|
2997
|
+
const location = LOCATION.exec(code);
|
|
2998
|
+
const refs = parseImports(code);
|
|
2999
|
+
const dropped = (ref) => types !== void 0 && types.erased && (ref.typeOnly || types.inline && ref.inlineTypes === true);
|
|
2409
3000
|
const specifiers = /* @__PURE__ */ new Set([
|
|
2410
|
-
...
|
|
2411
|
-
...[...
|
|
3001
|
+
...refs.filter((ref) => !dropped(ref)).map((ref) => ref.module),
|
|
3002
|
+
...[...code.matchAll(RUNNER_LOAD)].map((match) => match[2])
|
|
2412
3003
|
]);
|
|
3004
|
+
const typeLines = /* @__PURE__ */ new Map();
|
|
3005
|
+
for (const ref of refs.filter(dropped)) {
|
|
3006
|
+
const key = `${ref.line}:${ref.module}`;
|
|
3007
|
+
typeLines.set(key, (typeLines.get(key) ?? 0) + 1);
|
|
3008
|
+
}
|
|
2413
3009
|
for (const specifier of specifiers) {
|
|
2414
3010
|
if (path6.isAbsolute(specifier) || specifier.startsWith("file:")) {
|
|
2415
|
-
const
|
|
3011
|
+
const resolved2 = resolveFiles(
|
|
2416
3012
|
specifier.startsWith("file:") ? new URL(specifier).pathname : specifier
|
|
2417
3013
|
);
|
|
2418
|
-
if (
|
|
3014
|
+
if (resolved2.length === 0) {
|
|
2419
3015
|
found.unknown ??= `${shown(file)} imports ${specifier}, which leads to no file`;
|
|
2420
3016
|
} else {
|
|
2421
|
-
found.loads.push(realpath
|
|
3017
|
+
found.loads.push(...resolved2.map(realpath));
|
|
2422
3018
|
}
|
|
2423
3019
|
} else if (isProjectImport(specifier, aliases)) {
|
|
2424
|
-
const
|
|
2425
|
-
if (
|
|
3020
|
+
const resolved2 = target(specifier, file);
|
|
3021
|
+
if (resolved2.length === 0) {
|
|
2426
3022
|
found.unknown ??= `${shown(file)} imports ${specifier}, which leads to no file`;
|
|
2427
3023
|
} else {
|
|
2428
|
-
found.loads.push(realpath
|
|
3024
|
+
found.loads.push(...resolved2.map(realpath));
|
|
2429
3025
|
}
|
|
2430
3026
|
} else if (FILE_MODULES.has(bare(specifier))) {
|
|
2431
3027
|
found.unknown ??= `${shown(file)} imports ${specifier}, which can reach files that no import names`;
|
|
@@ -2439,18 +3035,40 @@ function fileEdges(file, context) {
|
|
|
2439
3035
|
}
|
|
2440
3036
|
}
|
|
2441
3037
|
}
|
|
3038
|
+
if (location !== null) {
|
|
3039
|
+
const reads = LOCATION_READS.exec(code);
|
|
3040
|
+
const reader = [...specifiers].find(
|
|
3041
|
+
(specifier) => !specifier.startsWith(".") && !path6.isAbsolute(specifier) && !specifier.startsWith("file:") && !isProjectImport(specifier, aliases) && !LOCATION_SAFE.has(bare(specifier))
|
|
3042
|
+
);
|
|
3043
|
+
if (reads !== null) {
|
|
3044
|
+
found.unknown ??= `${shown(file)} uses ${location[0]} and ${reads[0].replace(/\(\s*$/, "")}, which can reach files that no import names`;
|
|
3045
|
+
} else if (reader !== void 0) {
|
|
3046
|
+
found.unknown ??= `${shown(file)} uses ${location[0]} and imports ${reader}, which can read files by it that no import names`;
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
let line = 1;
|
|
3050
|
+
let counted = 0;
|
|
2442
3051
|
for (const match of source.matchAll(QUOTED)) {
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
if (resolved !== null) {
|
|
2447
|
-
found.loads.push(realpath(resolved));
|
|
3052
|
+
for (; counted < match.index; counted += 1) {
|
|
3053
|
+
if (source[counted] === "\n") {
|
|
3054
|
+
line += 1;
|
|
2448
3055
|
}
|
|
2449
3056
|
}
|
|
3057
|
+
const text = match[2];
|
|
3058
|
+
const key = `${line}:${text}`;
|
|
3059
|
+
const left = typeLines.get(key) ?? 0;
|
|
3060
|
+
if (left > 0) {
|
|
3061
|
+
typeLines.set(key, left - 1);
|
|
3062
|
+
} else if (text.startsWith(".") && !specifiers.has(text)) {
|
|
3063
|
+
found.loads.push(...resolveFiles(path6.resolve(path6.dirname(file), text)).map(realpath));
|
|
3064
|
+
}
|
|
2450
3065
|
}
|
|
2451
3066
|
return found;
|
|
2452
3067
|
}
|
|
2453
3068
|
function affectedInputs(manifest, input) {
|
|
3069
|
+
return resolving(() => inputsOf(manifest, input));
|
|
3070
|
+
}
|
|
3071
|
+
function inputsOf(manifest, input) {
|
|
2454
3072
|
const dir = realpath(manifest.dir);
|
|
2455
3073
|
const aliases = manifest.settings.importAliases;
|
|
2456
3074
|
const placed = [...input.tests, ...Object.values(input.covering).flat()];
|
|
@@ -2466,8 +3084,43 @@ function affectedInputs(manifest, input) {
|
|
|
2466
3084
|
return toPosix2(relative);
|
|
2467
3085
|
};
|
|
2468
3086
|
const shown = (file) => toPosix2(path6.relative(dir, file));
|
|
3087
|
+
const plain2 = /* @__PURE__ */ new Map();
|
|
3088
|
+
const configTexts = [];
|
|
3089
|
+
const runnerSources = [];
|
|
3090
|
+
const reached = new Set(input.configs.files);
|
|
3091
|
+
const pending = [...input.configs.files];
|
|
3092
|
+
for (let file = pending.shift(); file !== void 0; file = pending.shift()) {
|
|
3093
|
+
const text = fs6.readFileSync(file, "utf8");
|
|
3094
|
+
configTexts.push(text);
|
|
3095
|
+
runnerSources.push({ file, text });
|
|
3096
|
+
for (const next of fileEdges(file, { dir, aliases, cache: plain2 }).loads) {
|
|
3097
|
+
if (!reached.has(next) && SCRIPT.test(next)) {
|
|
3098
|
+
reached.add(next);
|
|
3099
|
+
pending.push(next);
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
const compiler = compilerConfigs(dir, realpath(input.root), Object.keys(input.files));
|
|
3104
|
+
const types = typesErased(input.configs, configTexts, compiler);
|
|
3105
|
+
const maps = unexplainedMaps({
|
|
3106
|
+
dir,
|
|
3107
|
+
aliases,
|
|
3108
|
+
// Not what lies outside of the compared files: this package, imported by path, is installed.
|
|
3109
|
+
runner: [
|
|
3110
|
+
...runnerSources.filter(({ file }) => {
|
|
3111
|
+
const relative = path6.relative(input.root, realpath(file));
|
|
3112
|
+
return !relative.startsWith("..") && toPosix2(relative) in input.files;
|
|
3113
|
+
}),
|
|
3114
|
+
...jestInPackage(dir, input.configs)
|
|
3115
|
+
],
|
|
3116
|
+
compiler,
|
|
3117
|
+
...input.configs.runner === void 0 ? {} : { testRunner: input.configs.runner }
|
|
3118
|
+
});
|
|
3119
|
+
if (maps !== null) {
|
|
3120
|
+
throw new AffectedError(maps);
|
|
3121
|
+
}
|
|
2469
3122
|
const edges = /* @__PURE__ */ new Map();
|
|
2470
|
-
const edgesOf = (file) => fileEdges(file, { dir, aliases, cache: edges });
|
|
3123
|
+
const edgesOf = (file) => fileEdges(file, { dir, aliases, cache: edges, types });
|
|
2471
3124
|
const closure = (start, strict) => {
|
|
2472
3125
|
const seen = /* @__PURE__ */ new Set([start]);
|
|
2473
3126
|
const queue = [start];
|
|
@@ -2508,9 +3161,17 @@ function affectedInputs(manifest, input) {
|
|
|
2508
3161
|
}
|
|
2509
3162
|
for (const match of fs6.readFileSync(loaded, "utf8").matchAll(QUOTED)) {
|
|
2510
3163
|
const text = match[2].replace(/^<rootDir>\//, "");
|
|
2511
|
-
const
|
|
2512
|
-
|
|
2513
|
-
|
|
3164
|
+
const here = resolveFiles(path6.resolve(path6.dirname(loaded), text), false, false);
|
|
3165
|
+
const candidates = here.length > 0 ? here : resolveFiles(path6.resolve(dir, text), false, false);
|
|
3166
|
+
for (const found of candidates) {
|
|
3167
|
+
if (text.includes("*") || !isCompared(found)) {
|
|
3168
|
+
continue;
|
|
3169
|
+
}
|
|
3170
|
+
if (SCRIPT.test(found)) {
|
|
3171
|
+
named.push(realpath(found));
|
|
3172
|
+
} else {
|
|
3173
|
+
config.add(realpath(found));
|
|
3174
|
+
}
|
|
2514
3175
|
}
|
|
2515
3176
|
}
|
|
2516
3177
|
}
|
|
@@ -2578,7 +3239,22 @@ function affectedInputs(manifest, input) {
|
|
|
2578
3239
|
files: found.files
|
|
2579
3240
|
};
|
|
2580
3241
|
}
|
|
2581
|
-
|
|
3242
|
+
const packageMocks = Object.keys(input.files).filter(
|
|
3243
|
+
(key) => isNestedMock(key, subproject) && mockNames(key).some(
|
|
3244
|
+
(name) => builtinModules2.includes(name) || installed(name, path6.join(input.root, key))
|
|
3245
|
+
)
|
|
3246
|
+
);
|
|
3247
|
+
return {
|
|
3248
|
+
subproject,
|
|
3249
|
+
config: [.../* @__PURE__ */ new Set([...keysOf([...config]), ...packageMocks])].sort(),
|
|
3250
|
+
gates
|
|
3251
|
+
};
|
|
3252
|
+
}
|
|
3253
|
+
function mockNames(key) {
|
|
3254
|
+
const segments = key.replace(/\.[^./]+$/, "").split("/");
|
|
3255
|
+
return segments.flatMap(
|
|
3256
|
+
(segment, index) => segment === "__mocks__" && index < segments.length - 1 ? [segments.slice(index + 1).join("/")] : []
|
|
3257
|
+
);
|
|
2582
3258
|
}
|
|
2583
3259
|
var NOT_THE_HELPER2 = "the runner config is not made with createJestGatesConfig or createVitestGatesConfig";
|
|
2584
3260
|
var OWN = /@t09tanaka\/test-gates\/(?:jest|vitest)\b/;
|
|
@@ -2614,6 +3290,9 @@ var READ_BY_NAME = [
|
|
|
2614
3290
|
/^\.swcrc$/
|
|
2615
3291
|
];
|
|
2616
3292
|
function loadedScope(manifest, configs, listed2) {
|
|
3293
|
+
return resolving(() => scopeOf(manifest, configs, listed2));
|
|
3294
|
+
}
|
|
3295
|
+
function scopeOf(manifest, configs, listed2) {
|
|
2617
3296
|
const dir = realpath(manifest.dir);
|
|
2618
3297
|
const cache = /* @__PURE__ */ new Map();
|
|
2619
3298
|
const context = { dir, aliases: manifest.settings.importAliases, cache };
|
|
@@ -2670,19 +3349,16 @@ function loadedScope(manifest, configs, listed2) {
|
|
|
2670
3349
|
for (const specifier of edges.unnamed) {
|
|
2671
3350
|
endingAs(specifier, listed2).forEach(add);
|
|
2672
3351
|
}
|
|
2673
|
-
const mock = path6.join(path6.dirname(file), "__mocks__", path6.basename(file));
|
|
2674
|
-
if (fs6.existsSync(mock)) {
|
|
2675
|
-
add(mock);
|
|
2676
|
-
}
|
|
2677
3352
|
if (naming.has(file) || SCRIPT.test(file) && path6.dirname(file) === dir) {
|
|
2678
3353
|
for (const named2 of namedIn(file)) {
|
|
2679
3354
|
const text = named2.replace(/^<rootDir>\//, "");
|
|
2680
|
-
const
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
3355
|
+
const here = resolveFiles(path6.resolve(path6.dirname(file), text), false);
|
|
3356
|
+
const found = here.length > 0 ? here : resolveFiles(path6.resolve(dir, text), false);
|
|
3357
|
+
for (const one of text.includes("*") ? [] : found) {
|
|
3358
|
+
if (!SCRIPT.test(one)) {
|
|
3359
|
+
naming.add(realpath(one));
|
|
2684
3360
|
}
|
|
2685
|
-
add(
|
|
3361
|
+
add(one);
|
|
2686
3362
|
}
|
|
2687
3363
|
}
|
|
2688
3364
|
}
|
|
@@ -2739,280 +3415,420 @@ function valuesOf(text, key) {
|
|
|
2739
3415
|
return found;
|
|
2740
3416
|
}
|
|
2741
3417
|
function withoutComments(text) {
|
|
2742
|
-
|
|
3418
|
+
const out = [];
|
|
3419
|
+
const before = { last: "", word: "" };
|
|
3420
|
+
let space = false;
|
|
3421
|
+
const keep = (part) => {
|
|
3422
|
+
out.push(part);
|
|
3423
|
+
for (const one of part) {
|
|
3424
|
+
if (/\s/.test(one)) {
|
|
3425
|
+
space = true;
|
|
3426
|
+
continue;
|
|
3427
|
+
}
|
|
3428
|
+
const word = /[\w$]/.test(one);
|
|
3429
|
+
before.word = !word ? "" : space || !/[\w$]/.test(before.last) ? one : before.word + one;
|
|
3430
|
+
before.last = one;
|
|
3431
|
+
space = false;
|
|
3432
|
+
}
|
|
3433
|
+
};
|
|
2743
3434
|
for (let at = 0; at < text.length; at += 1) {
|
|
2744
3435
|
const char = text[at];
|
|
3436
|
+
const next = text[at + 1];
|
|
2745
3437
|
if (char === "'" || char === '"' || char === "`") {
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
end += text[end] === "\\" ? 2 : 1;
|
|
2749
|
-
}
|
|
2750
|
-
out += text.slice(at, end + 1);
|
|
3438
|
+
const end = stringEnd(text, at);
|
|
3439
|
+
keep(text.slice(at, end + 1));
|
|
2751
3440
|
at = end;
|
|
2752
|
-
} else if (char === "/" &&
|
|
3441
|
+
} else if (char === "/" && next === "/" && !SCHEME.test(text.slice(Math.max(0, at - 40), at))) {
|
|
2753
3442
|
const end = text.indexOf("\n", at);
|
|
2754
3443
|
at = end === -1 ? text.length : end - 1;
|
|
2755
|
-
} else if (char === "/" &&
|
|
3444
|
+
} else if (char === "/" && next === "*") {
|
|
2756
3445
|
const end = text.indexOf("*/", at + 2);
|
|
2757
|
-
|
|
3446
|
+
const stop = end === -1 ? text.length : end + 2;
|
|
3447
|
+
keep(` ${text.slice(at, stop).replace(/[^\n]/g, "")}`);
|
|
3448
|
+
at = stop - 1;
|
|
3449
|
+
} else if (char === "/" && next !== "/") {
|
|
3450
|
+
const end = patternEnd(text, at, mayStartPattern(before));
|
|
3451
|
+
keep(text.slice(at, end + 1));
|
|
3452
|
+
at = end;
|
|
3453
|
+
} else {
|
|
3454
|
+
keep(char);
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
return out.join("");
|
|
3458
|
+
}
|
|
3459
|
+
var SCHEME = /(?:^|[^\w$.+-])[A-Za-z][\w+.-]*:$/;
|
|
3460
|
+
function patternEnd(text, at, pattern) {
|
|
3461
|
+
let inClass = false;
|
|
3462
|
+
let end = at + 1;
|
|
3463
|
+
for (; end < text.length && text[end] !== "\n"; end += 1) {
|
|
3464
|
+
const char = text[end];
|
|
3465
|
+
if (char === "\\") {
|
|
3466
|
+
end += 1;
|
|
3467
|
+
} else if (char === "[") {
|
|
3468
|
+
inClass = true;
|
|
3469
|
+
} else if (char === "]") {
|
|
3470
|
+
inClass = false;
|
|
3471
|
+
} else if (char === "/" && !inClass) {
|
|
3472
|
+
const comment = text[end + 1] === "/" || text[end + 1] === "*";
|
|
3473
|
+
return pattern || !comment ? end : at;
|
|
3474
|
+
} else if (!pattern && (char === "'" || char === '"' || char === "`")) {
|
|
3475
|
+
return at;
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
return pattern ? end : at;
|
|
3479
|
+
}
|
|
3480
|
+
function stringEnd(text, at) {
|
|
3481
|
+
const quote = text[at];
|
|
3482
|
+
let end = at + 1;
|
|
3483
|
+
while (end < text.length && text[end] !== quote && (quote === "`" || text[end] !== "\n")) {
|
|
3484
|
+
if (text[end] === "\\") {
|
|
3485
|
+
end += 2;
|
|
3486
|
+
} else if (quote === "`" && text.startsWith("${", end)) {
|
|
3487
|
+
end = placeholderEnd(text, end + 2) + 1;
|
|
2758
3488
|
} else {
|
|
2759
|
-
|
|
3489
|
+
end += 1;
|
|
2760
3490
|
}
|
|
2761
3491
|
}
|
|
2762
|
-
return
|
|
3492
|
+
return end;
|
|
2763
3493
|
}
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
}
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
}
|
|
2779
|
-
|
|
2780
|
-
|
|
3494
|
+
function placeholderEnd(text, at) {
|
|
3495
|
+
let depth = 0;
|
|
3496
|
+
for (let index = at; index < text.length; index += 1) {
|
|
3497
|
+
const char = text[index];
|
|
3498
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
3499
|
+
index = stringEnd(text, index);
|
|
3500
|
+
} else if (char === "{") {
|
|
3501
|
+
depth += 1;
|
|
3502
|
+
} else if (char === "}") {
|
|
3503
|
+
if (depth === 0) {
|
|
3504
|
+
return index;
|
|
3505
|
+
}
|
|
3506
|
+
depth -= 1;
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
return text.length;
|
|
3510
|
+
}
|
|
3511
|
+
var BEFORE_PATTERN = /* @__PURE__ */ new Set([
|
|
3512
|
+
"return",
|
|
3513
|
+
"typeof",
|
|
3514
|
+
"instanceof",
|
|
3515
|
+
"in",
|
|
3516
|
+
"of",
|
|
3517
|
+
"new",
|
|
3518
|
+
"delete",
|
|
3519
|
+
"void",
|
|
3520
|
+
"throw",
|
|
3521
|
+
"case",
|
|
3522
|
+
"do",
|
|
3523
|
+
"else",
|
|
3524
|
+
"yield",
|
|
3525
|
+
"await"
|
|
3526
|
+
]);
|
|
3527
|
+
function mayStartPattern(before) {
|
|
3528
|
+
if (before.word !== "") {
|
|
3529
|
+
return BEFORE_PATTERN.has(before.word);
|
|
3530
|
+
}
|
|
3531
|
+
return before.last === "" || !")]".includes(before.last);
|
|
3532
|
+
}
|
|
3533
|
+
function jestInPackage(dir, configs) {
|
|
3534
|
+
const file = path6.join(dir, "package.json");
|
|
3535
|
+
return configs.runner === "jest" && configs.files.length === 1 && fs6.existsSync(file) ? [{ file, text: fs6.readFileSync(file, "utf8"), mapperOnly: true }] : [];
|
|
3536
|
+
}
|
|
3537
|
+
var STRING_LITERAL = String.raw`'(?:[^'\\\n]|\\.)*'|"(?:[^"\\\n]|\\.)*"`;
|
|
3538
|
+
function stringValue(text) {
|
|
3539
|
+
const match = new RegExp(`^\\s*(${STRING_LITERAL})\\s*$`).exec(text);
|
|
3540
|
+
return match === null ? null : match[1].slice(1, -1).replace(/\\(.)/g, "$1");
|
|
3541
|
+
}
|
|
3542
|
+
function partsOf(text, at) {
|
|
3543
|
+
const parts = [];
|
|
3544
|
+
let depth = 0;
|
|
3545
|
+
let start = at + 1;
|
|
3546
|
+
for (let index = at; index < text.length; index += 1) {
|
|
3547
|
+
const char = text[index];
|
|
3548
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
3549
|
+
let end = index + 1;
|
|
3550
|
+
while (end < text.length && text[end] !== char) {
|
|
3551
|
+
end += text[end] === "\\" ? 2 : 1;
|
|
3552
|
+
}
|
|
3553
|
+
index = end;
|
|
3554
|
+
} else if ("{[(".includes(char)) {
|
|
3555
|
+
depth += 1;
|
|
3556
|
+
} else if ("}])".includes(char)) {
|
|
3557
|
+
depth -= 1;
|
|
3558
|
+
if (depth === 0) {
|
|
3559
|
+
parts.push(text.slice(start, index));
|
|
3560
|
+
return { parts: parts.filter((part) => part.trim() !== ""), end: index };
|
|
3561
|
+
}
|
|
3562
|
+
} else if (char === "," && depth === 1) {
|
|
3563
|
+
parts.push(text.slice(start, index));
|
|
3564
|
+
start = index + 1;
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
return null;
|
|
2781
3568
|
}
|
|
2782
|
-
function
|
|
2783
|
-
const
|
|
2784
|
-
|
|
2785
|
-
const first = lines[start.line - 1];
|
|
2786
|
-
const last = lines[end.line - 1];
|
|
2787
|
-
if (first === void 0 || last === void 0 || start.column < 1 || end.column < 1 || end.line < start.line || end.line === start.line && end.column < start.column) {
|
|
3569
|
+
function property(part) {
|
|
3570
|
+
const match = new RegExp(`^\\s*(${STRING_LITERAL}|[\\w$]+)\\s*:([\\s\\S]*)$`).exec(part);
|
|
3571
|
+
if (match === null) {
|
|
2788
3572
|
return null;
|
|
2789
3573
|
}
|
|
2790
|
-
|
|
2791
|
-
|
|
3574
|
+
const key = match[1];
|
|
3575
|
+
return { key: stringValue(key) ?? key, value: match[2].trim() };
|
|
3576
|
+
}
|
|
3577
|
+
function aliasPath(expression, file, text, depth = 0) {
|
|
3578
|
+
const value = expression.trim();
|
|
3579
|
+
const here = path6.dirname(file);
|
|
3580
|
+
if (value === "__dirname" || value === "import.meta.dirname" || /^(?:path\.)?dirname\(\s*(?:url\.)?fileURLToPath\(\s*import\.meta\.url\s*\)\s*\)$/.test(value)) {
|
|
3581
|
+
return here;
|
|
3582
|
+
}
|
|
3583
|
+
const url = new RegExp(
|
|
3584
|
+
`^(?:(?:url\\.)?fileURLToPath\\(\\s*new URL\\(\\s*(${STRING_LITERAL})\\s*,\\s*import\\.meta\\.url\\s*\\)\\s*\\)|new URL\\(\\s*(${STRING_LITERAL})\\s*,\\s*import\\.meta\\.url\\s*\\)\\.pathname)$`
|
|
3585
|
+
).exec(value);
|
|
3586
|
+
if (url !== null) {
|
|
3587
|
+
return path6.resolve(here, stringValue(url[1] ?? url[2]));
|
|
3588
|
+
}
|
|
3589
|
+
const call = /^(?:path\.)?(?:resolve|join)\s*\(/.exec(value);
|
|
3590
|
+
if (call !== null) {
|
|
3591
|
+
const found = partsOf(value, call[0].length - 1);
|
|
3592
|
+
if (found === null || value.slice(found.end + 1).trim() !== "" || found.parts.length === 0) {
|
|
3593
|
+
return null;
|
|
3594
|
+
}
|
|
3595
|
+
const [first, ...rest] = found.parts;
|
|
3596
|
+
const base = aliasPath(first, file, text, depth + 1);
|
|
3597
|
+
const parts = rest.map(stringValue);
|
|
3598
|
+
return base === null || parts.some((part) => part === null) ? null : path6.resolve(base, ...parts);
|
|
2792
3599
|
}
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
3600
|
+
if (/^[A-Za-z_$][\w$]*$/.test(value) && depth < 4) {
|
|
3601
|
+
const declarations = text.match(
|
|
3602
|
+
new RegExp(`\\b(?:const|let|var|function|class)\\s+${value.replace(/\$/g, "\\$")}\\b`, "g")
|
|
3603
|
+
);
|
|
3604
|
+
const bound = new RegExp(`\\bconst\\s+${value.replace(/\$/g, "\\$")}\\s*=\\s*([^;\\n]+)`).exec(
|
|
3605
|
+
text
|
|
3606
|
+
);
|
|
3607
|
+
return declarations?.length === 1 && bound !== null ? aliasPath(bound[1], file, text, depth + 1) : null;
|
|
3608
|
+
}
|
|
3609
|
+
return null;
|
|
2798
3610
|
}
|
|
2799
|
-
|
|
2800
|
-
|
|
3611
|
+
var PACKAGE_NAME = /^(?:@[\w-][\w.-]*\/)?[\w-][\w.-]*$/;
|
|
3612
|
+
function isDirectory(file) {
|
|
3613
|
+
try {
|
|
3614
|
+
return fs6.statSync(file).isDirectory();
|
|
3615
|
+
} catch {
|
|
3616
|
+
return false;
|
|
3617
|
+
}
|
|
2801
3618
|
}
|
|
2802
|
-
function
|
|
2803
|
-
const
|
|
2804
|
-
return {
|
|
2805
|
-
file,
|
|
2806
|
-
line: mutant.location.start.line,
|
|
2807
|
-
column: mutant.location.start.column,
|
|
2808
|
-
status: mutant.status,
|
|
2809
|
-
mutator: mutant.mutatorName,
|
|
2810
|
-
original: normalizeCode(entry.original),
|
|
2811
|
-
replacement: normalizeCode(entry.replacement),
|
|
2812
|
-
occurrence: entry.occurrence,
|
|
2813
|
-
sameKeyCount: entry.sameKeyCount,
|
|
2814
|
-
allowance: {
|
|
2815
|
-
mutator: mutant.mutatorName,
|
|
2816
|
-
original: normalizeCode(entry.original),
|
|
2817
|
-
replacement: normalizeCode(entry.replacement),
|
|
2818
|
-
...entry.sameKeyCount > 1 ? { occurrence: entry.occurrence } : {},
|
|
2819
|
-
reason: ""
|
|
2820
|
-
}
|
|
2821
|
-
};
|
|
3619
|
+
function regexPrefix(literal) {
|
|
3620
|
+
const match = /^\/\^((?:[\w@~#%-]|\\[./@~#-])*)\\\/\/$/.exec(literal.trim());
|
|
3621
|
+
return match === null ? null : `${match[1].replace(/\\(.)/g, "$1")}/`;
|
|
2822
3622
|
}
|
|
2823
|
-
function
|
|
2824
|
-
const
|
|
2825
|
-
return
|
|
3623
|
+
function mapperPrefix(pattern) {
|
|
3624
|
+
const match = /^\^((?:[\w@~#%/-]|\\[./@~#-])*)\(\.\*\)\$?$/.exec(pattern);
|
|
3625
|
+
return match === null ? null : match[1].replace(/\\(.)/g, "$1");
|
|
2826
3626
|
}
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
3627
|
+
var ROOT_DIR_HERE = /* @__PURE__ */ new Set([
|
|
3628
|
+
"__dirname",
|
|
3629
|
+
"import.meta.dirname",
|
|
3630
|
+
"import.meta.url",
|
|
3631
|
+
"'.'",
|
|
3632
|
+
'"."',
|
|
3633
|
+
"'./'",
|
|
3634
|
+
'"./"'
|
|
3635
|
+
]);
|
|
3636
|
+
function unexplainedMaps(input) {
|
|
3637
|
+
const { dir, aliases } = input;
|
|
3638
|
+
const reasons = [];
|
|
3639
|
+
const mapped = [];
|
|
3640
|
+
const shown = (file) => toPosix2(path6.relative(dir, file));
|
|
3641
|
+
const under = (base, rest) => rest === "" ? `${base}/` : path6.join(base, rest);
|
|
3642
|
+
const named = (prefix, target) => {
|
|
3643
|
+
const alias = aliases.find((candidate) => prefix.startsWith(candidate.prefix));
|
|
3644
|
+
if (alias === void 0) {
|
|
3645
|
+
return false;
|
|
3646
|
+
}
|
|
3647
|
+
const full = alias.target + prefix.slice(alias.prefix.length);
|
|
3648
|
+
const slash = full === "" || full.endsWith("/");
|
|
3649
|
+
return path6.resolve(dir, full) === path6.resolve(target) && slash === target.endsWith("/");
|
|
2839
3650
|
};
|
|
2840
|
-
const
|
|
2841
|
-
|
|
2842
|
-
|
|
3651
|
+
const advice = (prefix, target) => {
|
|
3652
|
+
const relative = toPosix2(path6.relative(dir, target));
|
|
3653
|
+
const shownTarget = relative === "" ? "./" : `${relative}/`;
|
|
3654
|
+
const shorter = aliases.find((alias) => prefix.startsWith(alias.prefix));
|
|
3655
|
+
const before = shorter === void 0 ? "" : shorter.prefix === prefix ? `, in place of the alias of ${prefix}` : `, before the alias of ${shorter.prefix}`;
|
|
3656
|
+
return ` (add { "prefix": "${prefix}", "target": "${shownTarget}" } to settings.importAliases${before})`;
|
|
3657
|
+
};
|
|
3658
|
+
const missing = (file, key, word, prefix, target) => `${shown(file)} maps ${key} with ${word} in a way settings.importAliases does not name` + (prefix !== void 0 && target !== void 0 && isDirectory(target) ? advice(prefix, target) : "");
|
|
3659
|
+
for (const { file, text: raw, mapperOnly } of input.runner) {
|
|
3660
|
+
let viteAlias2 = function(part, list) {
|
|
3661
|
+
let key = null;
|
|
3662
|
+
let prefix = null;
|
|
3663
|
+
let to = null;
|
|
3664
|
+
if (list) {
|
|
3665
|
+
const object = /^\s*\{/.test(part) ? partsOf(part, part.indexOf("{")) : null;
|
|
3666
|
+
const fields = Object.fromEntries(
|
|
3667
|
+
(object?.parts ?? []).map(property).map((one) => [one?.key ?? "", one?.value ?? ""])
|
|
3668
|
+
);
|
|
3669
|
+
if (object === null || Object.keys(fields).sort().join() !== "find,replacement" || part.slice(object.end + 1).trim() !== "") {
|
|
3670
|
+
return unreadableAlias2();
|
|
3671
|
+
}
|
|
3672
|
+
const find = fields.find;
|
|
3673
|
+
const replacement = fields.replacement;
|
|
3674
|
+
key = stringValue(find);
|
|
3675
|
+
if (key !== null) {
|
|
3676
|
+
prefix = key.endsWith("/") ? null : `${key}/`;
|
|
3677
|
+
const target = aliasPath(replacement, file, text);
|
|
3678
|
+
to = target === null ? null : `${target}/`;
|
|
3679
|
+
} else {
|
|
3680
|
+
key = find.trim();
|
|
3681
|
+
prefix = regexPrefix(find);
|
|
3682
|
+
const slashed = new RegExp(`^([\\s\\S]+?)\\s*\\+\\s*(?:'/'|"/")$`).exec(replacement);
|
|
3683
|
+
const target = slashed === null ? null : aliasPath(slashed[1], file, text);
|
|
3684
|
+
to = target === null ? null : `${target}/`;
|
|
3685
|
+
if (prefix === null && to === null) {
|
|
3686
|
+
return unreadableAlias2();
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
} else {
|
|
3690
|
+
const entry = property(part);
|
|
3691
|
+
if (entry === null) {
|
|
3692
|
+
return unreadableAlias2();
|
|
3693
|
+
}
|
|
3694
|
+
key = entry.key;
|
|
3695
|
+
prefix = key.endsWith("/") ? null : `${key}/`;
|
|
3696
|
+
const target = aliasPath(entry.value, file, text);
|
|
3697
|
+
to = target === null ? null : `${target}/`;
|
|
3698
|
+
}
|
|
3699
|
+
if (prefix === null || to === null || PACKAGE_NAME.test(key) && installed(key, file)) {
|
|
3700
|
+
return missing(file, key, "alias");
|
|
3701
|
+
}
|
|
3702
|
+
if (!named(prefix, to)) {
|
|
3703
|
+
return missing(file, key, "alias", prefix, to);
|
|
3704
|
+
}
|
|
3705
|
+
mapped.push(prefix);
|
|
3706
|
+
return null;
|
|
3707
|
+
}, unreadableAlias2 = function() {
|
|
3708
|
+
return `${shown(file)} names alias in a way that cannot be read as a list of aliases`;
|
|
3709
|
+
};
|
|
3710
|
+
var viteAlias = viteAlias2, unreadableAlias = unreadableAlias2;
|
|
3711
|
+
const text = withoutComments(raw);
|
|
3712
|
+
const words = mapperOnly === true ? /\bmoduleNameMapper\b/g : /\b(?:alias|moduleNameMapper)\b/g;
|
|
3713
|
+
for (const match of text.matchAll(words)) {
|
|
3714
|
+
const word = match[0];
|
|
3715
|
+
const opening = /^['"]?\s*:\s*/.exec(text.slice(match.index + word.length));
|
|
3716
|
+
const at = opening === null ? -1 : match.index + word.length + opening[0].length;
|
|
3717
|
+
const found = at === -1 ? null : partsOf(text, at);
|
|
3718
|
+
const unreadable = `${shown(file)} names ${word} in a way that cannot be read as a list of aliases`;
|
|
3719
|
+
if (found === null || text[at] !== "{" && !(word === "alias" && text[at] === "[")) {
|
|
3720
|
+
reasons.push(unreadable);
|
|
3721
|
+
continue;
|
|
3722
|
+
}
|
|
3723
|
+
for (const part of found.parts) {
|
|
3724
|
+
if (word === "alias") {
|
|
3725
|
+
const reason = viteAlias2(part, text[at] === "[");
|
|
3726
|
+
if (reason !== null) {
|
|
3727
|
+
reasons.push(reason);
|
|
3728
|
+
}
|
|
3729
|
+
continue;
|
|
3730
|
+
}
|
|
3731
|
+
const entry = property(part);
|
|
3732
|
+
if (entry === null) {
|
|
3733
|
+
reasons.push(unreadable);
|
|
3734
|
+
continue;
|
|
3735
|
+
}
|
|
3736
|
+
const value = stringValue(entry.value);
|
|
3737
|
+
const root = [...text.matchAll(/\brootDir\b['"]?\s*:\s*([^,}\n]+)/g)].map((one) => one[1].trim()).find((set) => !ROOT_DIR_HERE.has(set));
|
|
3738
|
+
if (root !== void 0 && (value === null || value.includes("<rootDir>"))) {
|
|
3739
|
+
reasons.push(`${shown(file)} sets rootDir to a directory that cannot be told: ${root}`);
|
|
3740
|
+
continue;
|
|
3741
|
+
}
|
|
3742
|
+
if (value !== null && !value.includes("$")) {
|
|
3743
|
+
continue;
|
|
3744
|
+
}
|
|
3745
|
+
const prefix = mapperPrefix(entry.key);
|
|
3746
|
+
const target = value === null ? null : /^<rootDir>\/(.*)\$1$/.exec(value);
|
|
3747
|
+
const to = target === null ? null : under(path6.dirname(file), target[1]);
|
|
3748
|
+
if (prefix === null || to === null) {
|
|
3749
|
+
reasons.push(missing(file, entry.key, word));
|
|
3750
|
+
} else if (!named(prefix, to)) {
|
|
3751
|
+
reasons.push(missing(file, entry.key, word, prefix, to));
|
|
3752
|
+
} else {
|
|
3753
|
+
mapped.push(prefix);
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
2843
3757
|
}
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
violations.push(
|
|
2849
|
-
...validateEquivalentMutants(gate.path, gate.expectedTimeouts, "expectedTimeouts")
|
|
2850
|
-
);
|
|
2851
|
-
const result = files.get(gatePath);
|
|
2852
|
-
if (!result || !Array.isArray(result.mutants) || result.mutants.length === 0) {
|
|
2853
|
-
violations.push({
|
|
2854
|
-
file: gate.path,
|
|
2855
|
-
message: "no mutants in the report (the gate was not mutation tested)"
|
|
2856
|
-
});
|
|
3758
|
+
const tsconfigs = input.compiler.filter(({ file }) => /^[jt]sconfig/.test(path6.basename(file)));
|
|
3759
|
+
const withBase = tsconfigs.filter(({ text }) => text !== null && /"baseUrl"\s*:/.test(text));
|
|
3760
|
+
for (const { file, text } of input.compiler) {
|
|
3761
|
+
if (text === null) {
|
|
2857
3762
|
continue;
|
|
2858
3763
|
}
|
|
2859
|
-
const
|
|
2860
|
-
const
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
3764
|
+
const plain2 = withoutComments(text);
|
|
3765
|
+
const name = path6.basename(file);
|
|
3766
|
+
const swc = name === ".swcrc";
|
|
3767
|
+
if (!/^[jt]sconfig/.test(name) && !swc) {
|
|
3768
|
+
const babel = /"babel"\s*:\s*/.exec(plain2);
|
|
3769
|
+
const own2 = name !== "package.json" ? plain2 : babel === null ? "" : (() => {
|
|
3770
|
+
const found2 = partsOf(plain2, babel.index + babel[0].length);
|
|
3771
|
+
return found2 === null ? plain2 : found2.parts.join(",");
|
|
3772
|
+
})();
|
|
3773
|
+
const maps = /module-resolver|\balias\b/.exec(own2);
|
|
3774
|
+
if (maps !== null) {
|
|
3775
|
+
reasons.push(`${shown(file)} maps modules (${maps[0]}), which cannot be told`);
|
|
3776
|
+
}
|
|
2866
3777
|
continue;
|
|
2867
3778
|
}
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
file: gate.path,
|
|
2871
|
-
message: "the report was made from a different version of this file. Run Stryker again"
|
|
2872
|
-
});
|
|
3779
|
+
const paths = /"paths"\s*:\s*/.exec(plain2);
|
|
3780
|
+
if (paths === null || !swc && input.testRunner === "jest") {
|
|
2873
3781
|
continue;
|
|
2874
3782
|
}
|
|
2875
|
-
const
|
|
2876
|
-
const
|
|
2877
|
-
|
|
2878
|
-
(
|
|
2879
|
-
|
|
2880
|
-
let locationsValid = true;
|
|
2881
|
-
for (const mutant of sorted2) {
|
|
2882
|
-
const original = sliceByLocation(normalizeNewlines(source), mutant.location);
|
|
2883
|
-
if (original === null) {
|
|
2884
|
-
violations.push({
|
|
2885
|
-
file: gate.path,
|
|
2886
|
-
line: mutant.location.start.line,
|
|
2887
|
-
message: `the report location of a ${mutant.mutatorName} mutant is outside the source`
|
|
2888
|
-
});
|
|
2889
|
-
locationsValid = false;
|
|
2890
|
-
continue;
|
|
2891
|
-
}
|
|
2892
|
-
const replacement = mutant.replacement ?? "";
|
|
2893
|
-
const key = keyOf(mutant.mutatorName, original, replacement);
|
|
2894
|
-
const group = byKey.get(key) ?? [];
|
|
2895
|
-
const entry = {
|
|
2896
|
-
mutant,
|
|
2897
|
-
original,
|
|
2898
|
-
replacement,
|
|
2899
|
-
occurrence: group.length + 1,
|
|
2900
|
-
sameKeyCount: 0
|
|
2901
|
-
};
|
|
2902
|
-
group.push(entry);
|
|
2903
|
-
byKey.set(key, group);
|
|
2904
|
-
located.push(entry);
|
|
2905
|
-
}
|
|
2906
|
-
for (const group of byKey.values()) {
|
|
2907
|
-
for (const entry of group) {
|
|
2908
|
-
entry.sameKeyCount = group.length;
|
|
2909
|
-
}
|
|
3783
|
+
const at = paths.index + paths[0].length;
|
|
3784
|
+
const found = plain2[at] === "{" ? partsOf(plain2, at) : null;
|
|
3785
|
+
if (found === null) {
|
|
3786
|
+
reasons.push(`${shown(file)} names paths in a way that cannot be read as a list of aliases`);
|
|
3787
|
+
continue;
|
|
2910
3788
|
}
|
|
2911
|
-
|
|
3789
|
+
const own = /"baseUrl"\s*:\s*"([^"]*)"/.exec(plain2);
|
|
3790
|
+
if (own === null && !swc && withBase.length > 0) {
|
|
3791
|
+
reasons.push(`${shown(file)} names paths, and baseUrl is set in another file`);
|
|
2912
3792
|
continue;
|
|
2913
3793
|
}
|
|
2914
|
-
const
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
3794
|
+
const base = path6.resolve(path6.dirname(file), own?.[1] ?? ".");
|
|
3795
|
+
for (const part of found.parts) {
|
|
3796
|
+
const entry = property(part);
|
|
3797
|
+
if (entry === null) {
|
|
3798
|
+
reasons.push(
|
|
3799
|
+
`${shown(file)} names paths in a way that cannot be read as a list of aliases`
|
|
3800
|
+
);
|
|
3801
|
+
continue;
|
|
2918
3802
|
}
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
const refusal = target ? accept(target.mutant.status) : `stale ${noun} (no such mutant in this run). Remove it`;
|
|
2934
|
-
if (!target || refusal !== null) {
|
|
2935
|
-
violations.push({ file: MANIFEST_FILE, message: `${at}: ${refusal}` });
|
|
2936
|
-
return;
|
|
2937
|
-
}
|
|
2938
|
-
if (claimed.has(target)) {
|
|
2939
|
-
violations.push({
|
|
2940
|
-
file: MANIFEST_FILE,
|
|
2941
|
-
message: `${at}: another ${noun} already covers this mutant`
|
|
2942
|
-
});
|
|
2943
|
-
return;
|
|
2944
|
-
}
|
|
2945
|
-
claimed.add(target);
|
|
2946
|
-
});
|
|
2947
|
-
return claimed;
|
|
2948
|
-
};
|
|
2949
|
-
const allowed = claim(
|
|
2950
|
-
"equivalentMutants",
|
|
2951
|
-
gate.equivalentMutants,
|
|
2952
|
-
"allowance",
|
|
2953
|
-
(status) => SURVIVED.has(status) ? null : `stale allowance (the mutant is now ${status}). Remove it`
|
|
2954
|
-
);
|
|
2955
|
-
const expected = claim(
|
|
2956
|
-
"expectedTimeouts",
|
|
2957
|
-
gate.expectedTimeouts,
|
|
2958
|
-
"entry",
|
|
2959
|
-
(status) => SURVIVED.has(status) ? `the mutant ${status === "NoCoverage" ? "has no coverage" : "survived"} instead of timing out. Detect it with a test` : null
|
|
2960
|
-
);
|
|
2961
|
-
for (const entry of located) {
|
|
2962
|
-
const { mutant } = entry;
|
|
2963
|
-
summary.total += 1;
|
|
2964
|
-
if (DETECTED.has(mutant.status)) {
|
|
2965
|
-
summary.detected += 1;
|
|
2966
|
-
if (mutant.status === "Timeout") {
|
|
2967
|
-
summary.timeout += 1;
|
|
2968
|
-
if (expected.has(entry)) {
|
|
2969
|
-
summary.expectedTimeout += 1;
|
|
2970
|
-
} else {
|
|
2971
|
-
unexpectedTimeouts.push(describeMutant(gate.path, entry));
|
|
2972
|
-
}
|
|
2973
|
-
}
|
|
2974
|
-
} else if (NOT_EVALUABLE.has(mutant.status)) {
|
|
2975
|
-
summary.notEvaluable += 1;
|
|
2976
|
-
} else if (SURVIVED.has(mutant.status)) {
|
|
2977
|
-
if (allowed.has(entry)) {
|
|
2978
|
-
summary.allowed += 1;
|
|
2979
|
-
continue;
|
|
2980
|
-
}
|
|
2981
|
-
summary.unallowed += 1;
|
|
2982
|
-
survivors.push(describeMutant(gate.path, entry));
|
|
2983
|
-
} else {
|
|
2984
|
-
violations.push({
|
|
2985
|
-
file: gate.path,
|
|
2986
|
-
line: mutant.location.start.line,
|
|
2987
|
-
message: mutant.status === "Ignored" ? `a ${mutant.mutatorName} mutant was ignored. Taking mutants out with Stryker comments or mutator exclusions is not allowed` : `a ${mutant.mutatorName} mutant was not evaluated (status: ${mutant.status})`
|
|
2988
|
-
});
|
|
3803
|
+
const list = entry.value.startsWith("[") ? partsOf(entry.value, 0) : null;
|
|
3804
|
+
const target = list?.parts.length === 1 ? stringValue(list.parts[0]) : null;
|
|
3805
|
+
const prefix = entry.key.endsWith("*") ? entry.key.slice(0, -1) : null;
|
|
3806
|
+
if (prefix === null && target !== null && !target.includes("*") && path6.resolve(base, target).split(path6.sep).includes("node_modules")) {
|
|
3807
|
+
continue;
|
|
3808
|
+
}
|
|
3809
|
+
const directory = target?.endsWith("*") ? target.slice(0, -1) : null;
|
|
3810
|
+
if (!swc && prefix !== null && mapped.some((one) => prefix.startsWith(one))) {
|
|
3811
|
+
continue;
|
|
3812
|
+
}
|
|
3813
|
+
if (prefix === null || directory === null) {
|
|
3814
|
+
reasons.push(missing(file, entry.key, "paths"));
|
|
3815
|
+
} else if (!named(prefix, under(base, directory))) {
|
|
3816
|
+
reasons.push(missing(file, entry.key, "paths", prefix, under(base, directory)));
|
|
2989
3817
|
}
|
|
2990
3818
|
}
|
|
2991
3819
|
}
|
|
2992
|
-
|
|
2993
|
-
violations.push({
|
|
2994
|
-
file: MANIFEST_FILE,
|
|
2995
|
-
message: "no mutants were evaluated (the mutation test ran on nothing)"
|
|
2996
|
-
});
|
|
2997
|
-
}
|
|
2998
|
-
const maxTimeouts = input.maxTimeouts ?? 0;
|
|
2999
|
-
if (unexpectedTimeouts.length > maxTimeouts) {
|
|
3000
|
-
violations.push({
|
|
3001
|
-
file: MANIFEST_FILE,
|
|
3002
|
-
message: `${unexpectedTimeouts.length} mutant(s) timed out outside expectedTimeouts; settings.mutation.maxTimeouts allows ${maxTimeouts}`
|
|
3003
|
-
});
|
|
3004
|
-
}
|
|
3005
|
-
return { violations, survivors, unexpectedTimeouts, summary };
|
|
3006
|
-
}
|
|
3007
|
-
function formatSurvivor(survivor) {
|
|
3008
|
-
const occurrence = survivor.sameKeyCount > 1 ? ` / occurrence ${survivor.occurrence} of ${survivor.sameKeyCount}` : "";
|
|
3009
|
-
return `${survivor.file}:${survivor.line}:${survivor.column} / ${survivor.mutator} / ${survivor.original} \u2192 ${survivor.replacement}${occurrence} (${survivor.status})`;
|
|
3010
|
-
}
|
|
3011
|
-
function formatSummary(summary) {
|
|
3012
|
-
return `mutants ${summary.total} / detected ${summary.detected} (timeout ${summary.timeout}) / allowed equivalent ${summary.allowed} / not evaluable ${summary.notEvaluable} / unallowed survivors ${summary.unallowed}`;
|
|
3820
|
+
return reasons.length === 0 ? null : [...new Set(reasons)].join("; ");
|
|
3013
3821
|
}
|
|
3014
3822
|
|
|
3823
|
+
// src/commands/mutation.ts
|
|
3824
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
3825
|
+
import fs8 from "fs";
|
|
3826
|
+
import path8 from "path";
|
|
3827
|
+
import { pathToFileURL } from "url";
|
|
3828
|
+
|
|
3015
3829
|
// src/commands/mutation-result.ts
|
|
3830
|
+
import fs7 from "fs";
|
|
3831
|
+
import path7 from "path";
|
|
3016
3832
|
function reportPathOf(manifest, override) {
|
|
3017
3833
|
return path7.resolve(manifest.dir, override ?? manifest.settings.strykerReportFile);
|
|
3018
3834
|
}
|
|
@@ -3117,7 +3933,7 @@ function loadStrykerConfig(manifest, config) {
|
|
|
3117
3933
|
return void 0;
|
|
3118
3934
|
}
|
|
3119
3935
|
}
|
|
3120
|
-
const script = `const loaded = await import(${JSON.stringify(pathToFileURL(file).href)});const config = loaded.default?.default ?? loaded.default ?? loaded;process.stdout.write(${JSON.stringify(CONFIG_MARK)} + JSON.stringify({incremental: config.incremental, force: config.force,incrementalFile: config.incrementalFile, tempDirName: config.tempDirName,testRunner: config.testRunner, vitestConfig: config.vitest?.configFile,jestConfig: config.jest?.configFile,}) + ${JSON.stringify(CONFIG_MARK)});`;
|
|
3936
|
+
const script = `const loaded = await import(${JSON.stringify(pathToFileURL(file).href)});const config = loaded.default?.default ?? loaded.default ?? loaded;process.stdout.write(${JSON.stringify(CONFIG_MARK)} + JSON.stringify({incremental: config.incremental, force: config.force,incrementalFile: config.incrementalFile, tempDirName: config.tempDirName,testRunner: config.testRunner, vitestConfig: config.vitest?.configFile,jestConfig: config.jest?.configFile, checkers: config.checkers,htmlReporter: config.htmlReporter, jsonReporter: config.jsonReporter,ignorePatterns: config.ignorePatterns,}) + ${JSON.stringify(CONFIG_MARK)});`;
|
|
3121
3937
|
const result = spawnSync2(process.execPath, ["--input-type=module", "--eval", script], {
|
|
3122
3938
|
cwd: manifest.dir,
|
|
3123
3939
|
encoding: "utf8",
|
|
@@ -3170,7 +3986,13 @@ function decideReuse(manifest, config, strykerArgs, part, affected) {
|
|
|
3170
3986
|
const loaded = loadStrykerConfig(manifest, config);
|
|
3171
3987
|
const options = incrementalOptions(loaded, strykerArgs);
|
|
3172
3988
|
const base = fs8.realpathSync(manifest.dir);
|
|
3173
|
-
const
|
|
3989
|
+
const checkersArg = strykerArgs.some(
|
|
3990
|
+
(arg) => arg === "--checkers" || arg.startsWith("--checkers=")
|
|
3991
|
+
);
|
|
3992
|
+
const configs = () => runnerConfigs(base, config, {
|
|
3993
|
+
...loaded ?? {},
|
|
3994
|
+
...checkersArg ? { checkers: ["--checkers"] } : {}
|
|
3995
|
+
});
|
|
3174
3996
|
const decision = {
|
|
3175
3997
|
incremental: false,
|
|
3176
3998
|
incrementalFile: null,
|
|
@@ -3186,6 +4008,7 @@ function decideReuse(manifest, config, strykerArgs, part, affected) {
|
|
|
3186
4008
|
plan: null,
|
|
3187
4009
|
last: void 0,
|
|
3188
4010
|
ci: affected ? affectedRefused(process.env) : null,
|
|
4011
|
+
unchanged: null,
|
|
3189
4012
|
configs
|
|
3190
4013
|
};
|
|
3191
4014
|
if (loaded !== void 0 && !options.incremental) {
|
|
@@ -3199,12 +4022,24 @@ function decideReuse(manifest, config, strykerArgs, part, affected) {
|
|
|
3199
4022
|
let snapshot = null;
|
|
3200
4023
|
let root = base;
|
|
3201
4024
|
let failure = "the Stryker config could not be loaded to see where the incremental file is";
|
|
4025
|
+
const reports = reportFiles(loaded).map((file) => path8.resolve(base, file));
|
|
4026
|
+
const ignores = strykerIgnores(loaded);
|
|
4027
|
+
const read2 = (file) => {
|
|
4028
|
+
const relative = path8.relative(base, file);
|
|
4029
|
+
const inside = !relative.startsWith("..") && !path8.isAbsolute(relative);
|
|
4030
|
+
return !reports.includes(file) && !(inside && ignores(relative.split(path8.sep).join("/")));
|
|
4031
|
+
};
|
|
3202
4032
|
if (loaded !== void 0) {
|
|
3203
4033
|
const scope = (listed2) => {
|
|
3204
4034
|
try {
|
|
3205
4035
|
const found = loadedScope(manifest, configs(), listed2);
|
|
3206
|
-
|
|
3207
|
-
|
|
4036
|
+
if (!("files" in found)) {
|
|
4037
|
+
decision.scope = found;
|
|
4038
|
+
return null;
|
|
4039
|
+
}
|
|
4040
|
+
const files = new Set([...found.files].filter(read2));
|
|
4041
|
+
decision.scope = { files: files.size };
|
|
4042
|
+
return files;
|
|
3208
4043
|
} catch (error) {
|
|
3209
4044
|
decision.scope = { every: error.message };
|
|
3210
4045
|
return null;
|
|
@@ -3219,7 +4054,8 @@ function decideReuse(manifest, config, strykerArgs, part, affected) {
|
|
|
3219
4054
|
incrementalFile,
|
|
3220
4055
|
recordFile,
|
|
3221
4056
|
path8.resolve(base, options.tempDirName),
|
|
3222
|
-
path8.resolve(base, manifest.settings.strykerReportFile)
|
|
4057
|
+
path8.resolve(base, manifest.settings.strykerReportFile),
|
|
4058
|
+
...reports
|
|
3223
4059
|
],
|
|
3224
4060
|
args: comparableArgs(strykerArgs)
|
|
3225
4061
|
}));
|
|
@@ -3252,14 +4088,18 @@ function decideReuse(manifest, config, strykerArgs, part, affected) {
|
|
|
3252
4088
|
force = failure;
|
|
3253
4089
|
} else if (!affected) {
|
|
3254
4090
|
force = refuseReuse({ record: stored, incremental, snapshot });
|
|
4091
|
+
if (force === null) {
|
|
4092
|
+
const read3 = readRecord({ record: stored, incremental, snapshot });
|
|
4093
|
+
last = "record" in read3 ? read3.record.affected : void 0;
|
|
4094
|
+
}
|
|
3255
4095
|
} else if (ci !== null) {
|
|
3256
4096
|
force = ci;
|
|
3257
4097
|
} else {
|
|
3258
|
-
const
|
|
3259
|
-
if ("refused" in
|
|
3260
|
-
force =
|
|
4098
|
+
const read3 = readRecord({ record: stored, incremental, snapshot });
|
|
4099
|
+
if ("refused" in read3) {
|
|
4100
|
+
force = read3.refused;
|
|
3261
4101
|
} else {
|
|
3262
|
-
last =
|
|
4102
|
+
last = read3.record.affected;
|
|
3263
4103
|
try {
|
|
3264
4104
|
const inputs = affectedInputs(manifest, {
|
|
3265
4105
|
root,
|
|
@@ -3271,7 +4111,7 @@ function decideReuse(manifest, config, strykerArgs, part, affected) {
|
|
|
3271
4111
|
)
|
|
3272
4112
|
});
|
|
3273
4113
|
plan = planAffected({
|
|
3274
|
-
before: { files:
|
|
4114
|
+
before: { files: read3.record.snapshot.files, affected: last },
|
|
3275
4115
|
now: snapshot.files,
|
|
3276
4116
|
subproject: inputs.subproject,
|
|
3277
4117
|
config: inputs.config,
|
|
@@ -3287,7 +4127,12 @@ function decideReuse(manifest, config, strykerArgs, part, affected) {
|
|
|
3287
4127
|
plan = { full: `what a gate depends on cannot be told: ${error.message}` };
|
|
3288
4128
|
}
|
|
3289
4129
|
if ("full" in plan) {
|
|
3290
|
-
|
|
4130
|
+
if (refuseReuse({ record: stored, incremental, snapshot }) === null) {
|
|
4131
|
+
decision.unchanged = plan.full;
|
|
4132
|
+
plan = null;
|
|
4133
|
+
} else {
|
|
4134
|
+
force = plan.full;
|
|
4135
|
+
}
|
|
3291
4136
|
}
|
|
3292
4137
|
}
|
|
3293
4138
|
}
|
|
@@ -3310,7 +4155,7 @@ function guardIncremental(manifest, config, strykerArgs, part, affected) {
|
|
|
3310
4155
|
]
|
|
3311
4156
|
} : nothing;
|
|
3312
4157
|
}
|
|
3313
|
-
const { snapshot, root, plan, last,
|
|
4158
|
+
const { snapshot, root, plan, last, configs, force } = decision;
|
|
3314
4159
|
const incrementalFile = decision.incrementalFile;
|
|
3315
4160
|
const recordFile = decision.recordFile;
|
|
3316
4161
|
const incremental = decision.incrementalHash;
|
|
@@ -3324,29 +4169,46 @@ function guardIncremental(manifest, config, strykerArgs, part, affected) {
|
|
|
3324
4169
|
const rerun = plan !== null && "rerun" in plan ? plan.rerun.map((entry) => entry.gate) : null;
|
|
3325
4170
|
const everyGate = force ?? (rerun !== null && rerun.length > 0 && rerun.length === manifest.gates.length ? "every gate is affected" : null);
|
|
3326
4171
|
let ranges = [];
|
|
4172
|
+
let timedOut = 0;
|
|
4173
|
+
let expected = 0;
|
|
3327
4174
|
if (everyGate === null && incremental !== null && !options.force) {
|
|
3328
|
-
const kept = rerun === null ? manifest.gates
|
|
4175
|
+
const kept = rerun === null ? manifest.gates : manifest.gates.filter((gate) => !rerun.includes(gate.path));
|
|
3329
4176
|
try {
|
|
3330
|
-
|
|
4177
|
+
({
|
|
4178
|
+
ranges,
|
|
4179
|
+
rerun: timedOut,
|
|
4180
|
+
expected
|
|
4181
|
+
} = timeoutRanges(
|
|
3331
4182
|
JSON.parse(fs8.readFileSync(incrementalFile, "utf8")),
|
|
3332
4183
|
kept
|
|
3333
|
-
);
|
|
4184
|
+
));
|
|
3334
4185
|
} catch {
|
|
3335
|
-
ranges = [];
|
|
3336
4186
|
}
|
|
3337
4187
|
}
|
|
3338
4188
|
const subset = rerun !== null && rerun.length > 0 && everyGate === null ? rerun : [];
|
|
4189
|
+
const ran = affected ? rerun : everyGate !== null || options.force || incremental === null ? null : [];
|
|
3339
4190
|
let said = [];
|
|
3340
4191
|
if (affected && !options.force) {
|
|
3341
4192
|
if (force !== null) {
|
|
3342
4193
|
said = describePlan({ full: force });
|
|
3343
4194
|
} else if (plan !== null) {
|
|
3344
|
-
said = describePlan(plan, 5,
|
|
4195
|
+
said = describePlan(plan, 5, timedOut, expected);
|
|
4196
|
+
} else if (decision.unchanged !== null) {
|
|
4197
|
+
said = [
|
|
4198
|
+
`test-gates mutation --affected: nothing changed since the last run, every result is reused (gate by gate, ${decision.unchanged.replace(/^what a gate/, "what each gate")})`,
|
|
4199
|
+
...ranges.length > 0 ? [` and ${timedOut} mutant(s) that timed out in the last run are run again`] : [],
|
|
4200
|
+
...expected > 0 ? [` ${describeExpected(expected)}`] : []
|
|
4201
|
+
];
|
|
4202
|
+
}
|
|
4203
|
+
} else {
|
|
4204
|
+
if (ranges.length > 0) {
|
|
4205
|
+
said.push(
|
|
4206
|
+
`test-gates mutation: ${timedOut} mutant(s) that timed out in the last run are run again, the other results are reused`
|
|
4207
|
+
);
|
|
4208
|
+
}
|
|
4209
|
+
if (expected > 0) {
|
|
4210
|
+
said.push(`test-gates mutation: ${describeExpected(expected)}`);
|
|
3345
4211
|
}
|
|
3346
|
-
} else if (ranges.length > 0) {
|
|
3347
|
-
said = [
|
|
3348
|
-
`test-gates mutation: ${ranges.length} mutant(s) that timed out in the last run are run again, the other results are reused`
|
|
3349
|
-
];
|
|
3350
4212
|
}
|
|
3351
4213
|
return {
|
|
3352
4214
|
// Every gate is run again: the same as a run of every mutant.
|
|
@@ -3359,8 +4221,8 @@ function guardIncremental(manifest, config, strykerArgs, part, affected) {
|
|
|
3359
4221
|
return;
|
|
3360
4222
|
}
|
|
3361
4223
|
const record = { format: RECORD_FORMAT, incremental: written, snapshot };
|
|
3362
|
-
if (
|
|
3363
|
-
record.affected = affectedRecord(judged,
|
|
4224
|
+
if (affectedRefused(process.env) === null && judged !== null) {
|
|
4225
|
+
record.affected = affectedRecord(judged, ran, last);
|
|
3364
4226
|
}
|
|
3365
4227
|
try {
|
|
3366
4228
|
fs8.writeFileSync(recordFile, `${JSON.stringify(record)}
|
|
@@ -3369,7 +4231,7 @@ function guardIncremental(manifest, config, strykerArgs, part, affected) {
|
|
|
3369
4231
|
}
|
|
3370
4232
|
}
|
|
3371
4233
|
};
|
|
3372
|
-
function affectedRecord(judged,
|
|
4234
|
+
function affectedRecord(judged, ran2, previous) {
|
|
3373
4235
|
const gates = manifest.gates.map((gate) => gate.path);
|
|
3374
4236
|
const dirty = new Set(
|
|
3375
4237
|
[...judged.verdict.survivors, ...judged.verdict.unexpectedTimeouts].map((one) => one.file)
|
|
@@ -3377,7 +4239,7 @@ function guardIncremental(manifest, config, strykerArgs, part, affected) {
|
|
|
3377
4239
|
const fresh = coveringSpecs(judged.report, gates);
|
|
3378
4240
|
const covering = /* @__PURE__ */ Object.create(null);
|
|
3379
4241
|
for (const gate of gates) {
|
|
3380
|
-
const specs =
|
|
4242
|
+
const specs = ran2 === null || ran2.includes(gate) ? fresh[gate] : previous?.gates[gate]?.specs ?? null;
|
|
3381
4243
|
if (specs !== null && specs !== void 0 && !dirty.has(gate)) {
|
|
3382
4244
|
covering[gate] = specs;
|
|
3383
4245
|
}
|
|
@@ -3399,7 +4261,7 @@ function guardIncremental(manifest, config, strykerArgs, part, affected) {
|
|
|
3399
4261
|
}
|
|
3400
4262
|
return { tests: Object.keys(judged.report.testFiles ?? {}).sort(), gates: recorded };
|
|
3401
4263
|
} catch (error) {
|
|
3402
|
-
if (error instanceof AffectedError) {
|
|
4264
|
+
if (error instanceof AffectedError || !affected) {
|
|
3403
4265
|
return void 0;
|
|
3404
4266
|
}
|
|
3405
4267
|
throw error;
|
|
@@ -3562,11 +4424,30 @@ function runDoctor(dir, io2) {
|
|
|
3562
4424
|
` next run with --affected: ${first.replace(/^test-gates mutation --affected: /, "")}`
|
|
3563
4425
|
);
|
|
3564
4426
|
rest.forEach((line) => io2.out(` ${line}`));
|
|
4427
|
+
} else if (affected.unchanged !== null) {
|
|
4428
|
+
io2.out(" next run with --affected: reuses the results of the last run (nothing changed)");
|
|
3565
4429
|
} else {
|
|
3566
4430
|
const why = affected.forced ? "the Stryker config sets force" : affected.force;
|
|
3567
4431
|
io2.out(` next run with --affected: every mutant, ${why ?? "there is nothing to reuse"}`);
|
|
3568
4432
|
}
|
|
3569
4433
|
}
|
|
4434
|
+
let blind = null;
|
|
4435
|
+
if (plain2.snapshot !== null) {
|
|
4436
|
+
try {
|
|
4437
|
+
affectedInputs(manifest, {
|
|
4438
|
+
root: plain2.root,
|
|
4439
|
+
files: plain2.snapshot.files,
|
|
4440
|
+
configs: plain2.configs(),
|
|
4441
|
+
tests: [],
|
|
4442
|
+
covering: {}
|
|
4443
|
+
});
|
|
4444
|
+
} catch (error) {
|
|
4445
|
+
if (!(error instanceof AffectedError)) {
|
|
4446
|
+
throw error;
|
|
4447
|
+
}
|
|
4448
|
+
blind = error.message;
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
3570
4451
|
const { lines, status } = formatFindings(
|
|
3571
4452
|
diagnose({
|
|
3572
4453
|
incremental: plain2.incremental,
|
|
@@ -3574,7 +4455,8 @@ function runDoctor(dir, io2) {
|
|
|
3574
4455
|
failure: plain2.failure,
|
|
3575
4456
|
widened: plain2.widened,
|
|
3576
4457
|
scope: plain2.scope,
|
|
3577
|
-
hooks: hooksOf(fs9.realpathSync(manifest.dir))
|
|
4458
|
+
hooks: hooksOf(fs9.realpathSync(manifest.dir)),
|
|
4459
|
+
blind
|
|
3578
4460
|
})
|
|
3579
4461
|
);
|
|
3580
4462
|
lines.forEach((line) => io2.out(line));
|