@systemfsoftware/stryker-js-mutation-run 1.2.5

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.
Files changed (53) hide show
  1. package/LICENSE +212 -0
  2. package/README.md +38 -0
  3. package/dist/checker-C4tGiXtZ.mjs +588 -0
  4. package/dist/checker-resource-DTYEKXdg.d.mts +108 -0
  5. package/dist/checker-worker.d.mts +16 -0
  6. package/dist/checker-worker.mjs +2 -0
  7. package/dist/child-process-proxy-worker-lx1M5Mz3.mjs +567 -0
  8. package/dist/child-process-proxy-worker-main.d.mts +1 -0
  9. package/dist/child-process-proxy-worker-main.mjs +6 -0
  10. package/dist/child-process-test-runner-worker-BTWRYb8V.mjs +50 -0
  11. package/dist/child-process-test-runner-worker.d.mts +16 -0
  12. package/dist/child-process-test-runner-worker.mjs +2 -0
  13. package/dist/config/base.d.mts +21 -0
  14. package/dist/config/base.mjs +47 -0
  15. package/dist/config/config-resolution.d.mts +80 -0
  16. package/dist/config/config-resolution.mjs +2 -0
  17. package/dist/config/fork-schema.d.mts +5 -0
  18. package/dist/config/fork-schema.mjs +2 -0
  19. package/dist/errors-BkjxkY_S.d.mts +6 -0
  20. package/dist/errors.d.mts +2 -0
  21. package/dist/errors.mjs +10 -0
  22. package/dist/exit-classification.d.mts +28 -0
  23. package/dist/exit-classification.mjs +39 -0
  24. package/dist/fork-schema-BHuTbRBR.mjs +62 -0
  25. package/dist/incremental-differ-DDVwtXBn.d.mts +72 -0
  26. package/dist/incremental-differ-DY8AA09Q.mjs +579 -0
  27. package/dist/index-CyOSz6LC.d.mts +104 -0
  28. package/dist/index.d.mts +87 -0
  29. package/dist/index.mjs +1885 -0
  30. package/dist/mutants/incremental-differ.d.mts +2 -0
  31. package/dist/mutants/incremental-differ.mjs +2 -0
  32. package/dist/output-mode-CfGHDpi5.d.mts +17 -0
  33. package/dist/output-mode.d.mts +2 -0
  34. package/dist/output-mode.mjs +1 -0
  35. package/dist/plugins-Nx86IJQW.mjs +1618 -0
  36. package/dist/plugins.d.mts +2 -0
  37. package/dist/plugins.mjs +2 -0
  38. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  39. package/dist/run-event-CzRz9Gtk.d.mts +121 -0
  40. package/dist/run-event.d.mts +2 -0
  41. package/dist/run-event.mjs +1 -0
  42. package/dist/stryker-package-CTcsIZ4S.mjs +23 -0
  43. package/dist/stryker-package.d.mts +7 -0
  44. package/dist/stryker-package.mjs +2 -0
  45. package/dist/timer-BMrE1SQ3.d.mts +20 -0
  46. package/dist/timer.d.mts +2 -0
  47. package/dist/timer.mjs +48 -0
  48. package/dist/verdict-envelope-BWc61mHm.mjs +229 -0
  49. package/dist/verdict-envelope-CKLZPjxC.d.mts +94 -0
  50. package/dist/verdict-envelope.d.mts +2 -0
  51. package/dist/verdict-envelope.mjs +2 -0
  52. package/package.json +102 -0
  53. package/schema/stryker-schema.json +903 -0
@@ -0,0 +1,579 @@
1
+ import { b as dryRunResult } from "./plugins-Nx86IJQW.mjs";
2
+ import "@systemfsoftware/stryker-js-plugin-api/core";
3
+ import { commonTokens } from "@systemfsoftware/stryker-js-plugin-api/plugin";
4
+ import { normalizeFileName, normalizeLineEndings, notEmpty } from "@stryker-mutator/util";
5
+ import path from "path";
6
+ import "@systemfsoftware/stryker-js-plugin-api/logging";
7
+ import "mutation-testing-report-schema";
8
+ import { TestStatus } from "@systemfsoftware/stryker-js-plugin-api/test-runner";
9
+ import { diff_match_patch } from "diff-match-patch";
10
+ //#region src/mutants/diff-statistics-collector.ts
11
+ var DiffChanges = class {
12
+ added = 0;
13
+ removed = 0;
14
+ toString() {
15
+ return `+${this.added} -${this.removed}`;
16
+ }
17
+ };
18
+ var DiffStatisticsCollector = class {
19
+ changesByFile = /* @__PURE__ */ new Map();
20
+ total = new DiffChanges();
21
+ count(file, change, amount = 1) {
22
+ if (amount === 0) return;
23
+ let changes = this.changesByFile.get(file);
24
+ if (!changes) {
25
+ changes = new DiffChanges();
26
+ this.changesByFile.set(file, changes);
27
+ }
28
+ switch (change) {
29
+ case "added":
30
+ changes.added += amount;
31
+ this.total.added += amount;
32
+ break;
33
+ case "removed":
34
+ changes.removed += amount;
35
+ this.total.removed += amount;
36
+ }
37
+ }
38
+ createDetailedReport() {
39
+ return [...this.changesByFile.entries()].map(([fileName, changes]) => `${fileName} ${changes.toString()}`);
40
+ }
41
+ createTotalsReport() {
42
+ return `${this.changesByFile.size} files changed (${this.total.toString()})`;
43
+ }
44
+ };
45
+ //#endregion
46
+ //#region src/mutants/test-coverage.ts
47
+ var TestCoverage = class {
48
+ #testsByMutantId;
49
+ #testsById;
50
+ #staticCoverage;
51
+ #hitsByMutantId;
52
+ constructor(testsByMutantId, testsById, staticCoverage, hitsByMutantId) {
53
+ this.#testsByMutantId = testsByMutantId;
54
+ this.#testsById = testsById;
55
+ this.#staticCoverage = staticCoverage;
56
+ this.#hitsByMutantId = hitsByMutantId;
57
+ }
58
+ get testsByMutantId() {
59
+ return this.#testsByMutantId;
60
+ }
61
+ get testsById() {
62
+ return this.#testsById;
63
+ }
64
+ get hitsByMutantId() {
65
+ return this.#hitsByMutantId;
66
+ }
67
+ get hasCoverage() {
68
+ return !!this.#staticCoverage;
69
+ }
70
+ hasStaticCoverage(mutantId) {
71
+ const coverage = this.#staticCoverage?.[mutantId];
72
+ return coverage !== void 0 && coverage > 0;
73
+ }
74
+ addTest(testResult) {
75
+ this.#testsById.set(testResult.id, testResult);
76
+ }
77
+ addCoverage(mutantId, testIds) {
78
+ const tests = this.#testsByMutantId.get(mutantId) ?? /* @__PURE__ */ new Set();
79
+ this.#testsByMutantId.set(mutantId, tests);
80
+ testIds.map((testId) => this.#testsById.get(testId)).filter(notEmpty).forEach((test) => tests.add(test));
81
+ }
82
+ forMutant(mutantId) {
83
+ return this.#testsByMutantId.get(mutantId);
84
+ }
85
+ static from = testCoverageFrom;
86
+ };
87
+ function testCoverageFrom({ tests, mutantCoverage }, logger) {
88
+ const hitsByMutantId = /* @__PURE__ */ new Map();
89
+ const testsByMutantId = /* @__PURE__ */ new Map();
90
+ const testsById = tests.reduce((acc, test) => acc.set(test.id, test), /* @__PURE__ */ new Map());
91
+ if (mutantCoverage) {
92
+ Object.entries(mutantCoverage.perTest).forEach(([testId, coverage]) => {
93
+ const foundTest = testsById.get(testId);
94
+ if (!foundTest) {
95
+ logger.warn(`Found test with id "${testId}" in coverage data, but not in the test results of the dry run. Not taking coverage data for this test into account.`);
96
+ return;
97
+ }
98
+ Object.entries(coverage).forEach(([mutantId, count]) => {
99
+ if (count > 0) {
100
+ let cov = testsByMutantId.get(mutantId);
101
+ if (!cov) {
102
+ cov = /* @__PURE__ */ new Set();
103
+ testsByMutantId.set(mutantId, cov);
104
+ }
105
+ cov.add(foundTest);
106
+ }
107
+ });
108
+ });
109
+ [mutantCoverage.static, ...Object.values(mutantCoverage.perTest)].forEach((coverageByMutantId) => {
110
+ Object.entries(coverageByMutantId).forEach(([mutantId, count]) => {
111
+ hitsByMutantId.set(mutantId, (hitsByMutantId.get(mutantId) ?? 0) + count);
112
+ });
113
+ });
114
+ }
115
+ return new TestCoverage(testsByMutantId, testsById, mutantCoverage?.static, hitsByMutantId);
116
+ }
117
+ testCoverageFrom.inject = [dryRunResult, commonTokens.logger];
118
+ //#endregion
119
+ //#region src/mutants/incremental-differ.ts
120
+ /**
121
+ * The 'diff match patch' high-performant 'diffing' of files.
122
+ * @see https://github.com/google/diff-match-patch
123
+ */
124
+ const diffMatchPatch = new diff_match_patch();
125
+ /**
126
+ * This class is responsible for calculating the diff between a run and a previous run based on the incremental report.
127
+ *
128
+ * Since the ids of tests and mutants can differ across reports (they are only unique within 1 report), this class
129
+ * identifies mutants and tests by attributes that make them unique:
130
+ * - Mutant: file name, mutator name, location and replacement
131
+ * - Test: test name, test file name (if present) and location (if present).
132
+ *
133
+ * We're storing these identifiers in local variables (maps and sets) as strings.
134
+ * We should move to 'records' for these when they come available: https://github.com/tc39/proposal-record-tuple
135
+ *
136
+ * A mutant result from the previous run is reused if the following conditions were met:
137
+ * - The location of the mutant refers to a piece of code that didn't change
138
+ * - If mutant was killed:
139
+ * - The culprit test wasn't changed
140
+ * - If the mutant survived:
141
+ * - No test was added, and the mutant is not static. Static mutants are
142
+ * attributed to no test, so no test change can ever invalidate one; reusing
143
+ * a non-killed verdict for one would freeze it permanently.
144
+ *
145
+ * It uses google's "diff-match-patch" project to calculate the new locations for tests and mutants, see https://github.com/google/diff-match-patch.
146
+ */
147
+ var IncrementalDiffer = class {
148
+ logger;
149
+ options;
150
+ mutantStatisticsCollector;
151
+ testStatisticsCollector;
152
+ mutateDescriptionByRelativeFileName;
153
+ static inject = [
154
+ commonTokens.logger,
155
+ commonTokens.options,
156
+ commonTokens.fileDescriptions
157
+ ];
158
+ constructor(logger, options, fileDescriptions) {
159
+ this.logger = logger;
160
+ this.options = options;
161
+ this.mutateDescriptionByRelativeFileName = new Map(Object.entries(fileDescriptions).map(([name, description]) => [toRelativeNormalizedFileName(name), description.mutate]));
162
+ }
163
+ isInMutatedScope(relativeFileName, mutant) {
164
+ const mutate = this.mutateDescriptionByRelativeFileName.get(relativeFileName);
165
+ return mutate === true || Array.isArray(mutate) && mutate.some((range) => locationIncluded(range, mutant.location));
166
+ }
167
+ diff(currentMutants, testCoverage, incrementalReport, currentRelativeFiles) {
168
+ const { files, testFiles } = incrementalReport;
169
+ const mutantStatisticsCollector = new DiffStatisticsCollector();
170
+ const testStatisticsCollector = new DiffStatisticsCollector();
171
+ this.mutantStatisticsCollector = mutantStatisticsCollector;
172
+ this.testStatisticsCollector = testStatisticsCollector;
173
+ const reusableMutantsByKey = collectReusableMutantsByKey(this.logger);
174
+ const { byId: oldTestsById, byKey: oldTestInfoByKey } = collectReusableTestInfo(this.logger);
175
+ const { oldCoverageByMutantKey: oldCoverageTestKeysByMutantKey, oldKilledByMutantKey: oldKilledTestKeysByMutantKey } = collectOldKilledAndCoverageMatrix();
176
+ const oldTestKeys = new Set([...oldTestsById.values()].map(({ key }) => key));
177
+ const newTestKeys = new Set([...testCoverage.testsById].map(([, test]) => testToIdentifyingKey(test, toRelativeNormalizedFileName(test.fileName))));
178
+ const testInfoByKey = collectCurrentTestInfo();
179
+ for (const [key, { relativeFileName }] of testInfoByKey) if (!oldTestKeys.has(key)) testStatisticsCollector.count(relativeFileName, "added");
180
+ for (const [testKey, { test: { name, location }, relativeFileName }] of oldTestInfoByKey) if (!testInfoByKey.has(testKey)) {
181
+ const test = {
182
+ status: TestStatus.Success,
183
+ id: testKey,
184
+ name,
185
+ ...location?.start === void 0 ? {} : { startPosition: location.start },
186
+ timeSpentMs: 0,
187
+ fileName: path.resolve(relativeFileName)
188
+ };
189
+ testInfoByKey.set(testKey, {
190
+ test,
191
+ relativeFileName
192
+ });
193
+ testCoverage.addTest(test);
194
+ }
195
+ let reusedMutantCount = 0;
196
+ const currentMutantKeys = /* @__PURE__ */ new Set();
197
+ const mutants = currentMutants.map((mutant) => {
198
+ const relativeFileName = toRelativeNormalizedFileName(mutant.fileName);
199
+ const mutantKey = mutantToIdentifyingKey(mutant, relativeFileName);
200
+ currentMutantKeys.add(mutantKey);
201
+ if (!mutant.status && !this.options.force) {
202
+ const oldMutant = reusableMutantsByKey.get(mutantKey);
203
+ if (oldMutant) {
204
+ const coveringTests = testCoverage.forMutant(mutant.id);
205
+ const killedByTestKeys = oldKilledTestKeysByMutantKey.get(mutantKey);
206
+ if (mutantCanBeReused(mutant, oldMutant, mutantKey, coveringTests, killedByTestKeys)) {
207
+ reusedMutantCount++;
208
+ const { status, statusReason, testsCompleted } = oldMutant;
209
+ return {
210
+ ...mutant,
211
+ ...status === void 0 ? {} : { status },
212
+ ...statusReason === void 0 ? {} : { statusReason },
213
+ ...testsCompleted === void 0 ? {} : { testsCompleted },
214
+ coveredBy: [...coveringTests ?? []].map(({ id }) => id),
215
+ killedBy: testKeysToId(killedByTestKeys)
216
+ };
217
+ }
218
+ } else mutantStatisticsCollector.count(relativeFileName, "added");
219
+ }
220
+ return mutant;
221
+ });
222
+ for (const [mutantKey, oldResult] of reusableMutantsByKey) if (!currentMutantKeys.has(mutantKey) && !this.isInMutatedScope(oldResult.relativeFileName, oldResult)) {
223
+ const coverage = oldCoverageTestKeysByMutantKey.get(mutantKey) ?? [];
224
+ const killed = oldKilledTestKeysByMutantKey.get(mutantKey) ?? [];
225
+ const coveredBy = testKeysToId(coverage);
226
+ const killedBy = testKeysToId(killed);
227
+ const reusedMutant = {
228
+ ...oldResult,
229
+ id: mutantKey,
230
+ fileName: path.resolve(oldResult.relativeFileName),
231
+ replacement: oldResult.replacement ?? oldResult.mutatorName,
232
+ coveredBy,
233
+ killedBy
234
+ };
235
+ mutants.push(reusedMutant);
236
+ testCoverage.addCoverage(reusedMutant.id, coveredBy);
237
+ }
238
+ if (this.logger.isInfoEnabled()) {
239
+ const testInfo = testCoverage.hasCoverage ? `\n\tTests:\t\t${testStatisticsCollector.createTotalsReport()}` : "";
240
+ this.logger.info(`Incremental report:\n\tMutants:\t${mutantStatisticsCollector.createTotalsReport()}` + testInfo + `\n\tResult:\t\t${reusedMutantCount} of ${currentMutants.length} mutant result(s) are reused.`);
241
+ }
242
+ if (this.logger.isDebugEnabled()) {
243
+ const lineSeparator = "\n ";
244
+ const noChanges = "No changes";
245
+ const detailedMutantSummary = `${lineSeparator}${mutantStatisticsCollector.createDetailedReport().join(lineSeparator) || noChanges}`;
246
+ const detailedTestsSummary = `${lineSeparator}${testStatisticsCollector.createDetailedReport().join(lineSeparator) || noChanges}`;
247
+ this.logger.debug(`Detailed incremental report:\n\tMutants: ${detailedMutantSummary}\n\tTests: ${detailedTestsSummary}`);
248
+ }
249
+ return mutants;
250
+ function testKeysToId(testKeys) {
251
+ return [...testKeys ?? []].map((id) => testInfoByKey.get(id)).filter(notEmpty).map(({ test: { id } }) => id);
252
+ }
253
+ function collectReusableMutantsByKey(log) {
254
+ return new Map(Object.entries(files).flatMap(([fileName, oldFile]) => {
255
+ const relativeFileName = toRelativeNormalizedFileName(fileName);
256
+ const currentFileSource = currentRelativeFiles.get(relativeFileName);
257
+ if (currentFileSource) {
258
+ log.trace("Diffing %s", relativeFileName);
259
+ const { results, removeCount } = performFileDiff(oldFile.source, currentFileSource, oldFile.mutants);
260
+ mutantStatisticsCollector.count(relativeFileName, "removed", removeCount);
261
+ return results.map((m) => [mutantToIdentifyingKey(m, relativeFileName), {
262
+ ...m,
263
+ relativeFileName
264
+ }]);
265
+ }
266
+ mutantStatisticsCollector.count(relativeFileName, "removed", oldFile.mutants.length);
267
+ return [];
268
+ }));
269
+ }
270
+ function collectReusableTestInfo(log) {
271
+ const byId = /* @__PURE__ */ new Map();
272
+ const byKey = /* @__PURE__ */ new Map();
273
+ Object.entries(testFiles ?? {}).forEach(([fileName, oldTestFile]) => {
274
+ const relativeFileName = toRelativeNormalizedFileName(fileName);
275
+ const currentFileSource = currentRelativeFiles.get(relativeFileName);
276
+ if (currentFileSource === void 0 && fileName !== "") {
277
+ log.debug("Test file removed: %s", relativeFileName);
278
+ testStatisticsCollector.count(relativeFileName, "removed", oldTestFile.tests.length);
279
+ } else if (currentFileSource !== void 0 && oldTestFile.source !== void 0) {
280
+ log.trace("Diffing %s", relativeFileName);
281
+ const locatedTests = closeLocations(oldTestFile);
282
+ const { results, removeCount } = performFileDiff(oldTestFile.source, currentFileSource, locatedTests);
283
+ testStatisticsCollector.count(relativeFileName, "removed", removeCount);
284
+ results.forEach((test) => {
285
+ const key = testToIdentifyingKey(test, relativeFileName);
286
+ const testInfo = {
287
+ key,
288
+ test,
289
+ relativeFileName
290
+ };
291
+ byId.set(test.id, testInfo);
292
+ byKey.set(key, testInfo);
293
+ });
294
+ } else oldTestFile.tests.map((test) => {
295
+ const key = testToIdentifyingKey(test, relativeFileName);
296
+ const testInfo = {
297
+ key,
298
+ test,
299
+ relativeFileName
300
+ };
301
+ byId.set(test.id, testInfo);
302
+ byKey.set(key, testInfo);
303
+ });
304
+ });
305
+ return {
306
+ byId,
307
+ byKey
308
+ };
309
+ }
310
+ function collectOldKilledAndCoverageMatrix() {
311
+ const oldCoverageByMutantKey = /* @__PURE__ */ new Map();
312
+ const oldKilledByMutantKey = /* @__PURE__ */ new Map();
313
+ for (const [key, mutant] of reusableMutantsByKey) {
314
+ const killedRow = new Set(mutant.killedBy?.map((testId) => oldTestsById.get(testId)?.key).filter(notEmpty));
315
+ const coverageRow = new Set(mutant.coveredBy?.map((testId) => oldTestsById.get(testId)?.key).filter(notEmpty));
316
+ killedRow.forEach((killed) => coverageRow.add(killed));
317
+ oldCoverageByMutantKey.set(key, coverageRow);
318
+ oldKilledByMutantKey.set(key, killedRow);
319
+ }
320
+ return {
321
+ oldCoverageByMutantKey,
322
+ oldKilledByMutantKey
323
+ };
324
+ }
325
+ function collectCurrentTestInfo() {
326
+ const byTestKey = /* @__PURE__ */ new Map();
327
+ for (const testResult of testCoverage.testsById.values()) {
328
+ const relativeFileName = toRelativeNormalizedFileName(testResult.fileName);
329
+ const key = testToIdentifyingKey(testResult, relativeFileName);
330
+ const info = {
331
+ relativeFileName,
332
+ test: testResult,
333
+ key
334
+ };
335
+ byTestKey.set(key, info);
336
+ }
337
+ return byTestKey;
338
+ }
339
+ function mutantCanBeReused(mutant, oldMutant, mutantKey, coveringTests, oldKillingTests) {
340
+ if (!testCoverage.hasCoverage) return true;
341
+ if (oldMutant.status === "Ignored") return false;
342
+ if (coveringTests === void 0 && testCoverage.hasStaticCoverage(mutant.id) && (oldMutant.status === "Survived" || oldMutant.status === "NoCoverage")) return false;
343
+ const testsDiff = diffTestCoverage(mutant.id, oldCoverageTestKeysByMutantKey.get(mutantKey), coveringTests);
344
+ if (oldMutant.status === "Killed") {
345
+ if (oldKillingTests) {
346
+ for (const killingTest of oldKillingTests) if (testsDiff.get(killingTest) === "same") return true;
347
+ }
348
+ return false;
349
+ }
350
+ for (const action of testsDiff.values()) if (action === "added") return false;
351
+ return true;
352
+ }
353
+ /**
354
+ * Determines if there is a diff between old test coverage and new test coverage.
355
+ */
356
+ function diffTestCoverage(mutantId, oldCoveringTestKeys, newCoveringTests) {
357
+ const result = /* @__PURE__ */ new Map();
358
+ if (newCoveringTests) for (const newTest of newCoveringTests) {
359
+ const key = testToIdentifyingKey(newTest, toRelativeNormalizedFileName(newTest.fileName));
360
+ result.set(key, oldCoveringTestKeys?.has(key) ? "same" : "added");
361
+ }
362
+ if (oldCoveringTestKeys) {
363
+ const isStatic = testCoverage.hasStaticCoverage(mutantId);
364
+ for (const oldTestKey of oldCoveringTestKeys) if (!result.has(oldTestKey)) {
365
+ if (isStatic && newTestKeys.has(oldTestKey)) result.set(oldTestKey, "same");
366
+ else result.set(oldTestKey, "removed");
367
+ }
368
+ }
369
+ return result;
370
+ }
371
+ }
372
+ };
373
+ /**
374
+ * Finds the diff of mutants and tests. Removes mutants / tests that no longer exist (changed or removed). Updates locations of mutants or tests that do still exist.
375
+ * @param oldCode The old code to use for the diff
376
+ * @param newCode The new (current) code to use for the diff
377
+ * @param items The mutants or tests to be looked . These will be treated as immutable.
378
+ * @returns A list of items with updated locations, without items that are changed.
379
+ */
380
+ function performFileDiff(oldCode, newCode, items) {
381
+ const oldSourceNormalized = normalizeLineEndings(oldCode);
382
+ const currentSrcNormalized = normalizeLineEndings(newCode);
383
+ const diffChanges = diffMatchPatch.diff_main(oldSourceNormalized, currentSrcNormalized);
384
+ const toDo = new Set(items.map((m) => ({
385
+ ...m,
386
+ location: deepClone(m.location)
387
+ })));
388
+ const [added, removed] = [1, -1];
389
+ const done = [];
390
+ const currentPosition = {
391
+ column: 0,
392
+ line: 0
393
+ };
394
+ let removeCount = 0;
395
+ for (const [change, text] of diffChanges) {
396
+ if (toDo.size === 0) break;
397
+ const offset = calculateOffset(text);
398
+ if (change === added) {
399
+ for (const test of toDo) {
400
+ const { location } = test;
401
+ if (gte(currentPosition, location.start) && gte(location.end, currentPosition)) {
402
+ removeCount++;
403
+ toDo.delete(test);
404
+ } else locationAdd(location, offset, currentPosition.line === location.start.line);
405
+ }
406
+ positionMove(currentPosition, offset);
407
+ } else if (change === removed) for (const item of toDo) {
408
+ const { location: { start } } = item;
409
+ if (gte(positionMove({ ...currentPosition }, offset), start)) {
410
+ removeCount++;
411
+ toDo.delete(item);
412
+ } else locationAdd(item.location, negate(offset), currentPosition.line === start.line);
413
+ }
414
+ else {
415
+ positionMove(currentPosition, offset);
416
+ toDo.forEach((item) => {
417
+ const { end } = item.location;
418
+ if (gte(currentPosition, end)) {
419
+ toDo.delete(item);
420
+ done.push(item);
421
+ }
422
+ });
423
+ }
424
+ }
425
+ done.push(...toDo);
426
+ return {
427
+ results: done,
428
+ removeCount
429
+ };
430
+ }
431
+ /**
432
+ * A greater-than-equals implementation for positions
433
+ */
434
+ function gte(a, b) {
435
+ return a.line > b.line || a.line === b.line && a.column >= b.column;
436
+ }
437
+ function locationIncluded(haystack, needle) {
438
+ const startIncluded = gte(needle.start, haystack.start);
439
+ const endIncluded = gte(haystack.end, needle.end);
440
+ return startIncluded && endIncluded;
441
+ }
442
+ function deepClone(loc) {
443
+ return {
444
+ start: { ...loc.start },
445
+ end: { ...loc.end }
446
+ };
447
+ }
448
+ /**
449
+ * Reduces a mutant to a string that identifies the mutant across reports.
450
+ * Consists of the relative file name, mutator name, replacement, and location
451
+ */
452
+ function mutantToIdentifyingKey({ mutatorName, replacement, location: { start, end } }, relativeFileName) {
453
+ return `${relativeFileName}@${start.line}:${start.column}-${end.line}:${end.column}\n${mutatorName}: ${replacement}`;
454
+ }
455
+ function testToIdentifyingKey({ name, location, startPosition }, relativeFileName) {
456
+ startPosition = startPosition ?? location?.start ?? {
457
+ line: 0,
458
+ column: 0
459
+ };
460
+ return `${relativeFileName}@${startPosition.line}:${startPosition.column}\n${name}`;
461
+ }
462
+ function toRelativeNormalizedFileName(fileName) {
463
+ return normalizeFileName(path.relative(process.cwd(), fileName ?? ""));
464
+ }
465
+ function calculateOffset(text) {
466
+ const pos = {
467
+ line: 0,
468
+ column: 0
469
+ };
470
+ for (const char of text) if (char === "\n") {
471
+ pos.line++;
472
+ pos.column = 0;
473
+ } else pos.column++;
474
+ return pos;
475
+ }
476
+ function positionMove(pos, diff) {
477
+ pos.line += diff.line;
478
+ if (diff.line === 0) pos.column += diff.column;
479
+ else pos.column = diff.column;
480
+ return pos;
481
+ }
482
+ function locationAdd({ start, end }, { line, column }, currentLine) {
483
+ start.line += line;
484
+ if (currentLine) start.column += column;
485
+ end.line += line;
486
+ if (line === 0 && currentLine) end.column += column;
487
+ }
488
+ function negate({ line, column }) {
489
+ return {
490
+ line: -1 * line,
491
+ column: -1 * column
492
+ };
493
+ }
494
+ /**
495
+ * Sets the end position of each test to the start position of the next test.
496
+ * This is an educated guess and necessary.
497
+ * If a test has no location, it is assumed it spans the entire file (line 0 to Infinity)
498
+ *
499
+ * Knowing the end location of tests is necessary in order to know if the test was changed.
500
+ */
501
+ function closeLocations(testFile) {
502
+ const locatedTests = [];
503
+ const openEndedTests = [];
504
+ testFile.tests.forEach((test) => {
505
+ if (testHasLocation(test)) {
506
+ if (isClosed(test)) locatedTests.push(test);
507
+ else openEndedTests.push(test);
508
+ } else locatedTests.push({
509
+ ...test,
510
+ location: {
511
+ start: {
512
+ line: 0,
513
+ column: 0
514
+ },
515
+ end: {
516
+ line: Number.POSITIVE_INFINITY,
517
+ column: 0
518
+ }
519
+ }
520
+ });
521
+ });
522
+ if (openEndedTests.length) {
523
+ openEndedTests.sort((a, b) => a.location.start.line - b.location.start.line);
524
+ const openEndedTestSet = new Set(openEndedTests);
525
+ const startPositions = uniqueStartPositions(openEndedTests);
526
+ let currentPositionIndex = 0;
527
+ openEndedTestSet.forEach((test) => {
528
+ if (eqPosition(test.location.start, startPositions[currentPositionIndex])) currentPositionIndex++;
529
+ const nextPosition = startPositions[currentPositionIndex];
530
+ if (nextPosition) {
531
+ locatedTests.push({
532
+ ...test,
533
+ location: {
534
+ start: test.location.start,
535
+ end: nextPosition
536
+ }
537
+ });
538
+ openEndedTestSet.delete(test);
539
+ }
540
+ });
541
+ openEndedTestSet.forEach((lastTest) => {
542
+ locatedTests.push({
543
+ ...lastTest,
544
+ location: {
545
+ start: lastTest.location.start,
546
+ end: {
547
+ line: Number.POSITIVE_INFINITY,
548
+ column: 0
549
+ }
550
+ }
551
+ });
552
+ });
553
+ }
554
+ return locatedTests;
555
+ }
556
+ /**
557
+ * Determines the unique start positions of a sorted list of tests in order
558
+ */
559
+ function uniqueStartPositions(sortedTests) {
560
+ let current;
561
+ return sortedTests.reduce((collector, { location: { start } }) => {
562
+ if (!current || current.line !== start.line || current.column !== start.column) {
563
+ current = start;
564
+ collector.push(current);
565
+ }
566
+ return collector;
567
+ }, []);
568
+ }
569
+ function testHasLocation(test) {
570
+ return !!test.location?.start;
571
+ }
572
+ function isClosed(test) {
573
+ return !!test.location.end;
574
+ }
575
+ function eqPosition(start, end) {
576
+ return start.column === end?.column && start.line === end.line;
577
+ }
578
+ //#endregion
579
+ export { toRelativeNormalizedFileName as n, TestCoverage as r, IncrementalDiffer as t };
@@ -0,0 +1,104 @@
1
+ import { Injector, Plugin, PluginContext, PluginInterfaces, PluginKind, Plugins } from "@systemfsoftware/stryker-js-plugin-api/plugin";
2
+ import { Logger } from "@systemfsoftware/stryker-js-plugin-api/logging";
3
+ declare namespace injection_tokens_d_exports {
4
+ export { checkerConcurrencyTokens, checkerFactory, checkerPool, clearTextEnabled, concurrencyTokenProvider, disableTypeChecksHelper, dryRunResult, execa, execaSync, fs, incrementalDiffer, loggerActiveLevel, loggerConsoleOut, loggerShowColors, loggingServer, loggingServerAddress, loggingSink, mutantTestPlanner, mutants, mutationTestReportHelper, optionsValidator, pluginCreator, pluginModulePaths, pluginsByKind, process, progressEnabled, project, reporter, reporterOverride, reporterPluginModules, requireFromCwd, resolveFromCwd, resolvedMode, runEventSink, runId, runStartedAt, sandbox, temporaryDirectory, testCoverage, testRunnerConcurrencyTokens, testRunnerFactory, testRunnerPool, timeOverheadMS, timer, unexpectedExitRegistry, validationSchema, workerIdGenerator };
5
+ }
6
+ declare const checkerPool = "checkerPool";
7
+ declare const checkerFactory = "checkerFactory";
8
+ declare const checkerConcurrencyTokens = "checkerConcurrencyTokens";
9
+ declare const disableTypeChecksHelper = "disableTypeChecksHelper";
10
+ declare const execa = "execa";
11
+ declare const execaSync = "execaSync";
12
+ declare const dryRunResult = "dryRunResult";
13
+ declare const mutants = "mutants";
14
+ declare const mutantTestPlanner = "mutantTestPlanner";
15
+ declare const process = "process";
16
+ declare const pluginModulePaths = "pluginModulePaths";
17
+ declare const temporaryDirectory = "temporaryDirectory";
18
+ declare const unexpectedExitRegistry = "unexpectedExitRegistry";
19
+ declare const timer = "timer";
20
+ declare const timeOverheadMS = "timeOverheadMS";
21
+ declare const loggingServerAddress = "loggingServerAddress";
22
+ declare const loggerActiveLevel = "loggerActiveLevel";
23
+ declare const loggerConsoleOut = "loggerConsoleOut";
24
+ declare const loggerShowColors = "loggerShowColors";
25
+ declare const loggingSink = "loggingSink";
26
+ declare const runEventSink = "runEventSink";
27
+ declare const runId = "runId";
28
+ declare const resolvedMode = "resolvedMode";
29
+ declare const progressEnabled = "progressEnabled";
30
+ declare const clearTextEnabled = "clearTextEnabled";
31
+ declare const runStartedAt = "runStartedAt";
32
+ declare const reporterPluginModules = "reporterPluginModules";
33
+ declare const loggingServer = "loggingServer";
34
+ declare const mutationTestReportHelper = "mutationTestReportHelper";
35
+ declare const sandbox = "sandbox";
36
+ declare const concurrencyTokenProvider = "concurrencyTokenProvider";
37
+ declare const testRunnerFactory = "testRunnerFactory";
38
+ declare const testRunnerPool = "testRunnerPool";
39
+ declare const testRunnerConcurrencyTokens = "testRunnerConcurrencyTokens";
40
+ declare const reporter = "reporter";
41
+ declare const reporterOverride = "reporterOverride";
42
+ declare const pluginCreator = "pluginCreator";
43
+ declare const pluginsByKind = "pluginsByKind";
44
+ declare const validationSchema = "validationSchema";
45
+ declare const optionsValidator = "optionsValidator";
46
+ declare const requireFromCwd = "requireFromCwd";
47
+ declare const resolveFromCwd = "resolveFromCwd";
48
+ declare const fs = "fs";
49
+ declare const testCoverage = "testCoverage";
50
+ declare const incrementalDiffer = "incrementalDiffer";
51
+ declare const project = "project";
52
+ declare const workerIdGenerator = "worker-id-generator";
53
+ //#endregion
54
+ //#region src/plugins/plugin-creator.d.ts
55
+ declare class PluginCreator {
56
+ private readonly pluginsByKind;
57
+ private readonly injector;
58
+ static readonly inject: ["pluginsByKind", "$injector"];
59
+ constructor(pluginsByKind: Map<PluginKind, Plugin<PluginKind>[]>, injector: Injector<PluginContext>);
60
+ create<TPlugin extends keyof Plugins>(kind: TPlugin, name: string): PluginInterfaces[TPlugin];
61
+ private findPlugin;
62
+ }
63
+ //#endregion
64
+ //#region src/plugins/plugin-loader.d.ts
65
+ /**
66
+ * Represents a collection of loaded plugins and metadata
67
+ */
68
+ interface LoadedPlugins {
69
+ /**
70
+ * The JSON schema contributions loaded
71
+ */
72
+ schemaContributions: Record<string, unknown>[];
73
+ /**
74
+ * The actual Stryker plugins loaded, sorted by type
75
+ */
76
+ pluginsByKind: Map<PluginKind, Plugin<PluginKind>[]>;
77
+ /**
78
+ * The import specifiers or full URL paths to the actual plugins
79
+ */
80
+ pluginModulePaths: string[];
81
+ }
82
+ /**
83
+ * Can resolve modules and pull them into memory
84
+ */
85
+ declare class PluginLoader {
86
+ private readonly log;
87
+ static inject: ["logger"];
88
+ constructor(log: Logger);
89
+ /**
90
+ * Loads plugins based on configured plugin descriptors.
91
+ * A plugin descriptor can be:
92
+ * * A full url: "file:///home/nicojs/github/my-plugin.js"
93
+ * * An absolute file path: "/home/nicojs/github/my-plugin.js"
94
+ * * A relative path: "./my-plugin.js"
95
+ * * A bare import expression: "@stryker-mutator/karma-runner"
96
+ * * A simple glob expression (only wild cards are supported): "@stryker-mutator/*"
97
+ */
98
+ load(pluginDescriptors: readonly string[]): Promise<LoadedPlugins>;
99
+ private resolvePluginModules;
100
+ private globPluginModules;
101
+ private loadPlugin;
102
+ }
103
+ //#endregion
104
+ export { injection_tokens_d_exports as a, progressEnabled as c, resolvedMode as d, runEventSink as f, clearTextEnabled as i, reporterOverride as l, runStartedAt as m, PluginLoader as n, loggingServerAddress as o, runId as p, PluginCreator as r, loggingSink as s, LoadedPlugins as t, reporterPluginModules as u };