@systemfsoftware/stryker-js 3.0.2 → 5.0.0

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 (94) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/LICENSE +3 -15
  3. package/README.md +69 -21
  4. package/dist/Plugins-sA_9VKus.d.mts +96 -0
  5. package/dist/builtin-reporters-wAuKFSsd.mjs +672 -0
  6. package/dist/builtin-reporters.d.mts +14 -0
  7. package/dist/builtin-reporters.mjs +2 -0
  8. package/dist/compiler-Bq9Nelu8.mjs +48406 -0
  9. package/dist/config.d.mts +11 -0
  10. package/dist/config.mjs +7 -0
  11. package/dist/dist-B3nT7z4G.mjs +5155 -0
  12. package/dist/execAsync-CfPZgJSD.mjs +10 -0
  13. package/dist/execAsync-DdCu5QLN.mjs +10 -0
  14. package/dist/getMachineId-bsd-C6bLIaiv.mjs +27 -0
  15. package/dist/getMachineId-bsd-CXSiHDga.mjs +27 -0
  16. package/dist/getMachineId-darwin-D3cIKeqx.mjs +24 -0
  17. package/dist/getMachineId-darwin-DPZ0kdKD.mjs +24 -0
  18. package/dist/getMachineId-linux-CQwLSlZk.mjs +20 -0
  19. package/dist/getMachineId-linux-Ci8OVJU0.mjs +20 -0
  20. package/dist/getMachineId-unsupported-PX0hnW6X.mjs +15 -0
  21. package/dist/getMachineId-unsupported-_ExL3Avl.mjs +15 -0
  22. package/dist/getMachineId-win-BCsS9pC_.mjs +26 -0
  23. package/dist/getMachineId-win-D9Tb0p7w.mjs +26 -0
  24. package/dist/index.d.mts +1009 -8
  25. package/dist/index.mjs +3 -9
  26. package/dist/main.mjs +104333 -0
  27. package/dist/merge-config-BrrNQWje.mjs +16 -0
  28. package/dist/mutation-test.cell-RmF-sBUm.d.mts +509 -0
  29. package/dist/output-mode-CfGHDpi5.d.mts +17 -0
  30. package/dist/parser.wasm32-wasi.wasm +0 -0
  31. package/dist/plugin-loader.d.mts +106 -0
  32. package/dist/plugin-loader.mjs +2 -0
  33. package/dist/plugin-worker-entry-DNu0SHO8.mjs +1413 -0
  34. package/dist/promises.d.mts +5 -0
  35. package/dist/promises.mjs +14 -0
  36. package/dist/rolldown-runtime-Dr7-SnC6.mjs +45 -0
  37. package/dist/src-Bzh7ykDp.mjs +4724 -0
  38. package/dist/stryker-config-BlfHxS7n.d.mts +14 -0
  39. package/dist/wasm-Dr7kZatW.mjs +13017 -0
  40. package/package.json +81 -38
  41. package/dist/Checker-A8o5B3Ub.d.mts +0 -40
  42. package/dist/Checker-Bq8NXD8d.mjs +0 -25
  43. package/dist/Checker.d.mts +0 -2
  44. package/dist/Checker.mjs +0 -2
  45. package/dist/Evaluator-CnAN8-pZ.d.mts +0 -19
  46. package/dist/Evaluator.d.mts +0 -3
  47. package/dist/Evaluator.mjs +0 -9
  48. package/dist/ExitClass.d.mts +0 -9
  49. package/dist/ExitClass.mjs +0 -59
  50. package/dist/ExitClass.schema-BmOIK5zT.d.mts +0 -18
  51. package/dist/Ignorer-BUY68FZQ.d.mts +0 -21
  52. package/dist/Ignorer.d.mts +0 -2
  53. package/dist/Ignorer.mjs +0 -7
  54. package/dist/Metrics.d.mts +0 -7
  55. package/dist/Metrics.mjs +0 -79
  56. package/dist/Metrics.schema-BaVd6rXs.d.mts +0 -50
  57. package/dist/Metrics.schema-DZdoc46X.mjs +0 -27
  58. package/dist/Module-DHKEMAFI.d.mts +0 -31
  59. package/dist/Module.d.mts +0 -2
  60. package/dist/Module.mjs +0 -16
  61. package/dist/Mutant-Ci25PsWA.d.mts +0 -351
  62. package/dist/Mutant.d.mts +0 -3
  63. package/dist/Mutant.mjs +0 -90
  64. package/dist/Mutant.schema-H3r0ho_R.mjs +0 -34
  65. package/dist/Plugin-BtItMchw.d.mts +0 -153
  66. package/dist/Plugin-DXFdJP1l.mjs +0 -105
  67. package/dist/Plugin.d.mts +0 -2
  68. package/dist/Plugin.mjs +0 -2
  69. package/dist/Report.d.mts +0 -2
  70. package/dist/Report.mjs +0 -98
  71. package/dist/Report.schema-_buHXdnZ.d.mts +0 -251
  72. package/dist/Reporter-BWTwlSJf.mjs +0 -25
  73. package/dist/Reporter-C7ysGxqX.d.mts +0 -16
  74. package/dist/Reporter.d.mts +0 -4
  75. package/dist/Reporter.mjs +0 -3
  76. package/dist/ReporterEvent.d.mts +0 -2
  77. package/dist/ReporterEvent.mjs +0 -57
  78. package/dist/ReporterEvent.schema-BhtyYCZo.d.mts +0 -189
  79. package/dist/Run-BUpNSmcM.d.mts +0 -172
  80. package/dist/Run.d.mts +0 -2
  81. package/dist/Run.mjs +0 -56
  82. package/dist/Run.schema-DZve3k2m.mjs +0 -138
  83. package/dist/Schema-4oCWQ0P0.d.mts +0 -158
  84. package/dist/Schema-CMKdWMUN.mjs +0 -244
  85. package/dist/Schema.d.mts +0 -2
  86. package/dist/Schema.mjs +0 -2
  87. package/dist/TestRunner.d.mts +0 -2
  88. package/dist/TestRunner.mjs +0 -57
  89. package/dist/TestRunner.schema-Dy0Wdro2.mjs +0 -119
  90. package/dist/output-file.d.mts +0 -8
  91. package/dist/output-file.mjs +0 -8
  92. package/dist/provided-options.d.mts +0 -16
  93. package/dist/provided-options.mjs +0 -1
  94. package/dist/rolldown-runtime-D7D4PA-g.mjs +0 -13
@@ -0,0 +1,672 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as FileSystem from "effect/FileSystem";
3
+ import * as Path from "effect/Path";
4
+ import "effect/Stdio";
5
+ import { MetricsResultSchema, MutationTestResultSchema, ReporterFailed } from "@systemfsoftware/stryker-js-plugin-interface";
6
+ import * as Match from "effect/Match";
7
+ import * as Option from "effect/Option";
8
+ import * as S from "effect/Schema";
9
+ import * as Stream from "effect/Stream";
10
+ import { errorToString } from "@systemfsoftware/stryker-js-instrumenter/mutants";
11
+ import * as Predicate from "effect/Predicate";
12
+ import "effect/Sink";
13
+ //#region src/reporter-output.ts
14
+ const sinksOf = (stdio) => ({
15
+ stdout: () => stdio.stdout({ endOnDone: false }),
16
+ stderr: () => stdio.stderr({ endOnDone: false })
17
+ });
18
+ const sinkFor = (stdio, channel) => sinksOf(stdio)[channel]();
19
+ const write = (stdio, channel, chunks) => Stream.run(Stream.fromIterable(chunks), sinkFor(stdio, channel));
20
+ /**
21
+ * The one Effect→Promise bridge for the sync-loop progress bar (the only reporter
22
+ * that cannot compose Effects — its writes fire inside a synchronous for-await).
23
+ * Write errors are ignored like a broken pipe on a stream handle.
24
+ *
25
+ * Note: effect 4.0.0-rc.112 exports the runner as `Effect.runPromise`; there is no
26
+ * `Runtime.runPromise` in this version (verified against the installed source) —
27
+ * do not invent that import.
28
+ */
29
+ const writeAsync = (stdio, channel, chunks) => Effect.runPromise(Effect.ignore(write(stdio, channel, chunks)));
30
+ //#endregion
31
+ //#region src/Reporter.ansi.ts
32
+ const codes = {
33
+ red: "\x1B[31m",
34
+ green: "\x1B[32m",
35
+ yellow: "\x1B[33m",
36
+ grey: "\x1B[90m",
37
+ cyan: "\x1B[36m",
38
+ greenBright: "\x1B[92m",
39
+ redBright: "\x1B[91m",
40
+ blueBright: "\x1B[94m"
41
+ };
42
+ const reset = "\x1B[39m";
43
+ function wrap(color, text) {
44
+ return `${codes[color]}${text}${reset}`;
45
+ }
46
+ const ansi = {
47
+ wrap,
48
+ red: (text) => wrap("red", text),
49
+ green: (text) => wrap("green", text),
50
+ yellow: (text) => wrap("yellow", text),
51
+ grey: (text) => wrap("grey", text),
52
+ cyan: (text) => wrap("cyan", text),
53
+ greenBright: (text) => wrap("greenBright", text),
54
+ redBright: (text) => wrap("redBright", text),
55
+ blueBright: (text) => wrap("blueBright", text)
56
+ };
57
+ //#endregion
58
+ //#region src/mutation-score-table.ts
59
+ const KNOWN_EMOJI = {
60
+ "✅": true,
61
+ "🙈": true,
62
+ "🤥": true,
63
+ "👽": true,
64
+ "⏰": true,
65
+ "⌛": true,
66
+ "💥": true
67
+ };
68
+ const charWidth = (char) => Match.value(char).pipe(Match.when((candidate) => KNOWN_EMOJI[candidate] === true, () => 2), Match.when((candidate) => (candidate.codePointAt(0) ?? 0) > 65535, () => 2), Match.orElse(() => 1));
69
+ function stringWidth(input) {
70
+ return Array.from(input).reduce((width, char) => width + charWidth(char), 0);
71
+ }
72
+ const FILES_ROOT_NAME = "All files";
73
+ const repeat = (char, nTimes) => {
74
+ if (nTimes > -1) return char.repeat(nTimes);
75
+ return char.repeat(0);
76
+ };
77
+ const spaces = (n) => repeat(" ", n);
78
+ const statusHeader = (allowEmojis, emoji, label) => {
79
+ if (allowEmojis) return `${emoji} ${label}`;
80
+ return `# ${label}`;
81
+ };
82
+ const maxOf = (values) => values.reduce((acc, cur) => {
83
+ if (cur > acc) return cur;
84
+ return acc;
85
+ }, Number.NEGATIVE_INFINITY);
86
+ const widthOrZero = (width) => Match.value(width === Number.NEGATIVE_INFINITY).pipe(Match.when(true, () => 0), Match.when(false, () => width), Match.exhaustive);
87
+ const determineContentWidth = (row, valueFactory, ancestorCount = 0) => {
88
+ const head = valueFactory(row, ancestorCount).length;
89
+ const childWidths = row.childResults.map((child) => determineContentWidth(child, valueFactory, ancestorCount + 1));
90
+ return widthOrZero(maxOf([head, ...childWidths]));
91
+ };
92
+ const columnWidth = (column) => {
93
+ if (column.isFirstColumn) return column.netWidth + 1;
94
+ return column.netWidth + 2;
95
+ };
96
+ const padColumn = (column, input) => Match.value(column.kind === "file").pipe(Match.when(true, () => `${input}${spaces(columnWidth(column) - stringWidth(input))}`), Match.when(false, () => Match.value(column.isFirstColumn).pipe(Match.when(true, () => `${spaces(column.netWidth - stringWidth(input))}${input} `), Match.when(false, () => `${spaces(column.netWidth - stringWidth(input))} ${input} `), Match.exhaustive)), Match.exhaustive);
97
+ const drawLine = (column) => repeat("-", columnWidth(column));
98
+ const drawHeader = (column) => padColumn(column, column.header);
99
+ const mutationScoreOf = (scoreType, metrics) => Match.value(scoreType).pipe(Match.when("total", () => metrics.mutationScore), Match.when("covered", () => metrics.mutationScoreBasedOnCoveredCode), Match.exhaustive);
100
+ const thresholdColor = (thresholds, value) => Match.value(value).pipe(Match.when((present) => Number.isNaN(present), () => ansi.grey), Match.when((present) => present >= thresholds.high, () => ansi.green), Match.when((present) => present >= thresholds.low, () => ansi.yellow), Match.orElse(() => ansi.red));
101
+ const scoreColor = (column, score) => Match.value(column.allowColor).pipe(Match.when(true, () => thresholdColor(column.thresholds, mutationScoreOf(column.scoreType, score.metrics))), Match.when(false, () => (input) => input), Match.exhaustive);
102
+ const colorFor = (column, score) => Match.value(column).pipe(Match.discriminator("kind")("mutationScore", (scored) => scoreColor(scored, score)), Match.orElse(() => (input) => input));
103
+ const drawTableCell = (column, score, ancestorCount) => {
104
+ switch (column.kind) {
105
+ case "group": return column.columns.map((c) => drawTableCell(c, score, ancestorCount)).join("|");
106
+ case "single":
107
+ case "file":
108
+ case "mutationScore": {
109
+ const raw = column.valueFactory(score, ancestorCount);
110
+ const padded = padColumn(column, raw);
111
+ return colorFor(column, score)(padded);
112
+ }
113
+ }
114
+ };
115
+ const drawColumnHeaders = (column) => {
116
+ if (column.kind !== "group") return drawHeader(column);
117
+ return column.columns.map((c) => drawHeader(c)).join("|");
118
+ };
119
+ const drawColumnLines = (column) => {
120
+ if (column.kind !== "group") return drawLine(column);
121
+ return column.columns.map((c) => drawLine(c)).join("|");
122
+ };
123
+ const makeSingleColumn = (header, isFirstColumn, valueFactory, rows) => {
124
+ const maxContentSize = determineContentWidth(rows, valueFactory);
125
+ const netWidth = maxOf([maxContentSize, stringWidth(header)]);
126
+ return {
127
+ kind: "single",
128
+ header,
129
+ isFirstColumn,
130
+ netWidth: widthOrZero(netWidth),
131
+ valueFactory,
132
+ rows
133
+ };
134
+ };
135
+ const makeFileColumn = (rows) => {
136
+ const valueFactory = (row, ancestorCount) => {
137
+ if (ancestorCount === 0) return spaces(ancestorCount) + FILES_ROOT_NAME;
138
+ return spaces(ancestorCount) + row.name;
139
+ };
140
+ const netWidth = maxOf([determineContentWidth(rows, valueFactory), stringWidth("File")]);
141
+ return {
142
+ kind: "file",
143
+ header: "File",
144
+ isFirstColumn: true,
145
+ netWidth: widthOrZero(netWidth),
146
+ valueFactory,
147
+ rows
148
+ };
149
+ };
150
+ const makeMutationScoreColumn = (rows, thresholds, scoreType, allowColor) => {
151
+ const valueFactory = (row) => Match.value(mutationScoreOf(scoreType, row.metrics)).pipe(Match.when((present) => Number.isNaN(present), () => "n/a"), Match.orElse((present) => present.toFixed(2)));
152
+ const netWidth = maxOf([determineContentWidth(rows, valueFactory), stringWidth(scoreType)]);
153
+ return {
154
+ kind: "mutationScore",
155
+ header: scoreType,
156
+ isFirstColumn: false,
157
+ netWidth: widthOrZero(netWidth),
158
+ valueFactory,
159
+ rows,
160
+ thresholds,
161
+ scoreType,
162
+ allowColor
163
+ };
164
+ };
165
+ const paddingWidth = (isFirstColumn) => Match.value(isFirstColumn).pipe(Match.when(true, () => 1), Match.when(false, () => 2), Match.exhaustive);
166
+ const widthAdjustedColumns = (columns, first, columnsWidth, netWidth) => Match.value(netWidth > columnsWidth + 1).pipe(Match.when(true, () => [{
167
+ ...first,
168
+ netWidth: first.netWidth + (netWidth - columnsWidth - 1)
169
+ }, ...columns.slice(1)]), Match.when(false, () => columns), Match.exhaustive);
170
+ const makeGroupColumn = (groupName, ...columns) => {
171
+ const [first, ...rest] = columns;
172
+ if (first === void 0) throw new Error("a group column needs at least one column");
173
+ const columnsWidth = rest.reduce((acc, cur) => acc + columnWidth(cur), columnWidth(first)) - paddingWidth(first.isFirstColumn);
174
+ const netWidth = widthOrZero(maxOf([stringWidth(groupName), columnsWidth]));
175
+ return {
176
+ kind: "group",
177
+ header: groupName,
178
+ isFirstColumn: first.isFirstColumn,
179
+ netWidth,
180
+ columns: widthAdjustedColumns(columns, first, columnsWidth, netWidth)
181
+ };
182
+ };
183
+ const createColumns = (metricsResult, options) => {
184
+ const allowColor = options.clearTextReporter.allowColor;
185
+ const allowEmojis = options.clearTextReporter.allowEmojis;
186
+ return [
187
+ makeGroupColumn("", makeFileColumn(metricsResult)),
188
+ makeGroupColumn("% Mutation score", makeMutationScoreColumn(metricsResult, options.thresholds, "total", allowColor), makeMutationScoreColumn(metricsResult, options.thresholds, "covered", allowColor)),
189
+ makeGroupColumn("", makeSingleColumn(statusHeader(allowEmojis, "✅", "killed"), false, (row) => row.metrics.killed.toString(), metricsResult)),
190
+ makeGroupColumn("", makeSingleColumn(statusHeader(allowEmojis, "⌛️", "timeout"), false, (row) => row.metrics.timeout.toString(), metricsResult)),
191
+ makeGroupColumn("", makeSingleColumn(statusHeader(allowEmojis, "👽", "survived"), false, (row) => row.metrics.survived.toString(), metricsResult)),
192
+ makeGroupColumn("", makeSingleColumn(statusHeader(allowEmojis, "🙈", "no cov"), false, (row) => row.metrics.noCoverage.toString(), metricsResult)),
193
+ makeGroupColumn("", makeSingleColumn(statusHeader(allowEmojis, "💥", "errors"), false, (row) => (row.metrics.runtimeErrors + row.metrics.compileErrors).toString(), metricsResult))
194
+ ];
195
+ };
196
+ const drawRow = (columns, toDraw) => `${columns.map(toDraw).join("|")}|`;
197
+ const drawGroupHeader = (columns) => drawRow(columns, (c) => drawHeader(c));
198
+ const drawGroupLine = (columns) => drawRow(columns, (c) => drawLine(c));
199
+ const drawLineRow = (columns) => drawRow(columns, (c) => drawColumnLines(c));
200
+ const drawColumnHeader = (columns) => drawRow(columns, (c) => drawColumnHeaders(c));
201
+ const drawTableBody = (columns, options, current, ancestorCount) => {
202
+ const ownRow = Match.value(options.clearTextReporter.skipFull === false || current.metrics.mutationScore !== 100).pipe(Match.when(true, () => [drawRow(columns, (column) => drawTableCell(column, current, ancestorCount))]), Match.when(false, () => []), Match.exhaustive);
203
+ const childRows = current.childResults.flatMap((child) => drawTableBody(columns, options, child, ancestorCount + 1));
204
+ return [...ownRow, ...childRows];
205
+ };
206
+ const EOL = "\n";
207
+ const drawMutationScoreTable = (metricsResult, options) => {
208
+ const columns = createColumns(metricsResult, options);
209
+ return [
210
+ drawGroupLine(columns),
211
+ drawGroupHeader(columns),
212
+ drawColumnHeader(columns),
213
+ drawLineRow(columns),
214
+ drawTableBody(columns, options, metricsResult, 0).join(EOL),
215
+ drawLineRow(columns)
216
+ ].join(EOL);
217
+ };
218
+ //#endregion
219
+ //#region src/Reporter.schema.ts
220
+ var ClearTextReportCommand = class extends S.TaggedClass()("ClearTextReportCommand", {
221
+ report: MutationTestResultSchema,
222
+ metrics: MetricsResultSchema
223
+ }) {};
224
+ //#endregion
225
+ //#region src/clear-text-report.ts
226
+ function plural(items) {
227
+ if (items > 1) return "s";
228
+ else return "";
229
+ }
230
+ function getEmojiForStatus(status) {
231
+ switch (status) {
232
+ case "Killed": return "✅";
233
+ case "NoCoverage": return "🙈";
234
+ case "Ignored": return "🤥";
235
+ case "Survived": return "👽";
236
+ case "Timeout": return "⏰";
237
+ case "Pending": return "⌛";
238
+ case "RuntimeError":
239
+ case "CompileError": return "💥";
240
+ }
241
+ }
242
+ function sourceLocation(fileName, position, allowColor) {
243
+ return [
244
+ (() => {
245
+ if (allowColor) return ansi.cyan(fileName);
246
+ return fileName;
247
+ })(),
248
+ (() => {
249
+ if (allowColor) return ansi.yellow(String(position.line));
250
+ return String(position.line);
251
+ })(),
252
+ (() => {
253
+ if (allowColor) return ansi.yellow(String(position.column));
254
+ return String(position.column);
255
+ })()
256
+ ].join(":");
257
+ }
258
+ function mutantLabel(status, allowEmojis) {
259
+ if (allowEmojis) return `${getEmojiForStatus(status)} ${status}`;
260
+ return status;
261
+ }
262
+ /**
263
+ * Where a mutant's block is written: the diagnostics channel for mutants that
264
+ * failed to run, the report for the ones that inform, and nowhere for the rest.
265
+ */
266
+ const REPORT_CHANNEL = {
267
+ Killed: "debug",
268
+ Timeout: "debug",
269
+ RuntimeError: "debug",
270
+ CompileError: "debug",
271
+ Survived: "stdout",
272
+ NoCoverage: "stdout",
273
+ Ignored: "none",
274
+ Pending: "none"
275
+ };
276
+ function extractReportMutants(report) {
277
+ if (!Predicate.hasProperty(report, "files")) return [];
278
+ return Object.entries(report.files).flatMap(([fileName, file]) => file.mutants.map((mutant) => ({
279
+ fileName,
280
+ mutant: {
281
+ ...mutant,
282
+ fileName
283
+ },
284
+ source: file.source
285
+ })));
286
+ }
287
+ function sourceLine(source, position) {
288
+ const raw = source.split("\n")[position.line - 1];
289
+ if (raw === void 0) return "";
290
+ return raw;
291
+ }
292
+ function tailFromColumn(raw, column) {
293
+ if (raw.length === 0) return [];
294
+ return [raw.slice(column)];
295
+ }
296
+ function sliceSource(source, position) {
297
+ if (source === void 0) return [];
298
+ return tailFromColumn(sourceLine(source, position), position.column);
299
+ }
300
+ function originalLines(source, position, allowColor) {
301
+ return sliceSource(source, position).map((l) => {
302
+ if (allowColor) return ansi.red(`- ${l}`);
303
+ return `- ${l}`;
304
+ });
305
+ }
306
+ function replacementLines(replacement, allowColor) {
307
+ if (replacement === void 0) return [];
308
+ return replacement.split("\n").filter(Boolean).map((l) => {
309
+ if (allowColor) return ansi.green(`+ ${l}`);
310
+ return `+ ${l}`;
311
+ });
312
+ }
313
+ const logsCoveredTests = (mutant, options) => {
314
+ if (!options.clearTextReporter.logTests) return false;
315
+ return mutant.coveredBy !== void 0;
316
+ };
317
+ const coveredTestsTail = (mutant, options) => {
318
+ if (!logsCoveredTests(mutant, options)) return [];
319
+ return formatCoveredTests(mutant.coveredBy, options);
320
+ };
321
+ const survivorTail = (mutant, options) => {
322
+ if (mutant.static === true) return ["Ran all tests for this mutant."];
323
+ return coveredTestsTail(mutant, options);
324
+ };
325
+ const killedByLine = (killer) => {
326
+ if (killer === void 0) return [];
327
+ return [`Killed by: ${killer}`];
328
+ };
329
+ const killerTail = (mutant) => {
330
+ const killedBy = mutant.killedBy;
331
+ if (killedBy === void 0) return [];
332
+ return killedByLine(killedBy[0]);
333
+ };
334
+ const statusReasonTail = (mutant) => {
335
+ if (mutant.statusReason === void 0) return [];
336
+ return [`Error message: ${mutant.statusReason}`];
337
+ };
338
+ /**
339
+ * The lines below a mutant block: one tail per status, chosen by the status the
340
+ * mutant settled at.
341
+ */
342
+ const statusTail = (mutant, options) => Match.value(mutant.status).pipe(Match.when("Survived", () => survivorTail(mutant, options)), Match.when("Killed", () => killerTail(mutant)), Match.whenOr("RuntimeError", "CompileError", () => statusReasonTail(mutant)), Match.orElse(() => []));
343
+ const overflowNotice = (hidden) => {
344
+ if (hidden <= 0) return [];
345
+ return [` and ${hidden} more test${plural(hidden)}!`];
346
+ };
347
+ function formatCoveredTests(tests, options) {
348
+ const maxLog = options.clearTextReporter.maxTestsToLog;
349
+ const logged = Math.min(maxLog, tests.length);
350
+ if (logged <= 0) return [];
351
+ return [
352
+ "Tests ran:",
353
+ ...tests.slice(0, logged).map((test) => ` ${test}`),
354
+ ...overflowNotice(tests.length - maxLog),
355
+ ""
356
+ ];
357
+ }
358
+ function mutantBlock(fileName, mutant, source, options) {
359
+ const allowColor = options.clearTextReporter.allowColor;
360
+ const allowEmojis = options.clearTextReporter.allowEmojis;
361
+ const out = [];
362
+ out.push(`[${mutantLabel(mutant.status, allowEmojis)}] ${mutant.mutatorName}`);
363
+ out.push(sourceLocation(fileName, mutant.location.start, allowColor));
364
+ out.push(...originalLines(source, mutant.location.start, allowColor));
365
+ out.push(...replacementLines(mutant.replacement, allowColor));
366
+ out.push(...statusTail(mutant, options));
367
+ out.push("");
368
+ return out;
369
+ }
370
+ const linesForChannel = (entries, channel, options) => {
371
+ return entries.filter((entry) => REPORT_CHANNEL[entry.mutant.status] === channel).flatMap((entry) => mutantBlock(entry.fileName, entry.mutant, entry.source, options));
372
+ };
373
+ function collectMutants(report, options) {
374
+ const entries = extractReportMutants(report);
375
+ return {
376
+ stdout: linesForChannel(entries, "stdout", options),
377
+ debug: linesForChannel(entries, "debug", options),
378
+ totalTests: entries.reduce((total, entry) => total + (entry.mutant.testsCompleted ?? 0), 0)
379
+ };
380
+ }
381
+ const partialScoresVisible = (metrics, options) => {
382
+ if (!options.clearTextReporter.skipFull) return true;
383
+ return metrics.childResults.some((child) => child.metrics.mutationScore !== 100);
384
+ };
385
+ const drawsScoreTable = (metrics, options) => {
386
+ if (!options.clearTextReporter.reportScoreTable) return false;
387
+ return partialScoresVisible(metrics, options);
388
+ };
389
+ function scoreTable(metrics, options) {
390
+ if (!drawsScoreTable(metrics, options)) return void 0;
391
+ return drawMutationScoreTable(metrics, options);
392
+ }
393
+ const EMPTY_REPORT_LINES = {
394
+ stdout: [],
395
+ debug: []
396
+ };
397
+ const testsPerMutant = (metrics, totalTests) => {
398
+ const total = metrics.metrics.totalMutants;
399
+ if (total === 0) return "0.00";
400
+ return (totalTests / total).toFixed(2);
401
+ };
402
+ const lineIfPresent = (line) => {
403
+ if (line === void 0) return [];
404
+ return [line];
405
+ };
406
+ const mutantReportSection = (report, metrics, options) => {
407
+ if (!options.clearTextReporter.reportMutants) return EMPTY_REPORT_LINES;
408
+ const blocks = collectMutants(report, options);
409
+ return {
410
+ stdout: [
411
+ "",
412
+ ...blocks.stdout,
413
+ `Ran ${testsPerMutant(metrics, blocks.totalTests)} tests per mutant on average.`
414
+ ],
415
+ debug: blocks.debug
416
+ };
417
+ };
418
+ function renderClearText(report, metrics, options) {
419
+ const section = mutantReportSection(report, metrics, options);
420
+ return {
421
+ stdout: [
422
+ "",
423
+ ...section.stdout,
424
+ ...lineIfPresent(scoreTable(metrics, options))
425
+ ],
426
+ debug: [...section.debug]
427
+ };
428
+ }
429
+ const rememberTerminalReport = (seen, event) => {
430
+ Match.value(event).pipe(Match.tag("mutationTestReportReady", (ready) => {
431
+ seen.terminal = {
432
+ report: ready.report,
433
+ metrics: ready.metrics
434
+ };
435
+ }), Match.orElse(() => void 0));
436
+ };
437
+ const decodeClearTextReport = (terminal) => S.decodeUnknownEffect(ClearTextReportCommand)({
438
+ _tag: "ClearTextReportCommand",
439
+ report: terminal.report,
440
+ metrics: terminal.metrics
441
+ }).pipe(Effect.mapError((cause) => ReporterFailed.make({
442
+ reporterName: "clear-text",
443
+ event: "mutationTestReportReady",
444
+ cause: errorToString(cause)
445
+ })));
446
+ const streamErrorOf$1 = (cause) => ReporterFailed.make({
447
+ reporterName: "clear-text",
448
+ event: "mutationTestReportReady",
449
+ cause: errorToString(cause)
450
+ });
451
+ const failAsClearText = (cause) => ReporterFailed.make({
452
+ reporterName: "clear-text",
453
+ event: "mutationTestReportReady",
454
+ cause: errorToString(cause)
455
+ });
456
+ const writeRendered = (services, rendered, options) => {
457
+ const stdoutEffect = write(services.stdio, "stdout", rendered.stdout.map((line) => `${line}\n`)).pipe(Effect.mapError(failAsClearText), Effect.asVoid);
458
+ return Match.value(options.logLevel === "debug").pipe(Match.when(true, () => Effect.andThen(stdoutEffect, write(services.stdio, "stderr", rendered.debug.map((line) => `${line}\n`)).pipe(Effect.mapError(failAsClearText)))), Match.orElse(() => stdoutEffect));
459
+ };
460
+ const makeClearTextReporter = (services) => (options) => (events) => {
461
+ const seen = {};
462
+ return Stream.runForEach(Stream.fromAsyncIterable(events, streamErrorOf$1), (event) => Effect.sync(() => rememberTerminalReport(seen, event))).pipe(Effect.andThen(Effect.sync(() => seen.terminal)), Effect.flatMap((terminal) => Option.match(Option.fromUndefinedOr(terminal), {
463
+ onNone: () => Effect.void,
464
+ onSome: (ready) => decodeClearTextReport(ready).pipe(Effect.flatMap((command) => writeRendered(services, renderClearText(command.report, command.metrics, options), options)))
465
+ })));
466
+ };
467
+ //#endregion
468
+ //#region src/json-reporter.ts
469
+ const failAsJsonReporter = (cause) => ReporterFailed.make({
470
+ reporterName: "json",
471
+ event: "mutationTestReportReady",
472
+ cause: errorToString(cause)
473
+ });
474
+ const writeReport = (services, options, report) => Effect.gen(function* () {
475
+ const json = yield* S.encodeEffect(S.fromJsonString(S.Unknown, { space: 0 }))(report).pipe(Effect.orDie);
476
+ const fs = yield* FileSystem.FileSystem;
477
+ const path = yield* Path.Path;
478
+ const fileName = path.resolve(options.jsonReporter.fileName);
479
+ if (options.logLevel === "debug") yield* Effect.ignore(write(services.stdio, "stderr", [`Using relative path ${path.normalize(options.jsonReporter.fileName)}\n`]));
480
+ yield* fs.makeDirectory(path.dirname(fileName), { recursive: true }).pipe(Effect.mapError(failAsJsonReporter));
481
+ yield* fs.writeFileString(fileName, json).pipe(Effect.mapError(failAsJsonReporter));
482
+ const url = yield* path.toFileUrl(fileName).pipe(Effect.mapError(failAsJsonReporter));
483
+ yield* Effect.ignore(write(services.stdio, "stdout", [`Your report can be found at: ${url.href}\n`]));
484
+ }).pipe(Effect.provideService(FileSystem.FileSystem, services.fileSystem), Effect.provideService(Path.Path, services.path));
485
+ const rememberReport = (seen, event) => {
486
+ Match.value(event).pipe(Match.tag("mutationTestReportReady", (ready) => {
487
+ seen.report = ready.report;
488
+ }), Match.orElse(() => void 0));
489
+ };
490
+ const streamErrorOf = (cause) => ReporterFailed.make({
491
+ reporterName: "json",
492
+ event: "mutationTestReportReady",
493
+ cause: errorToString(cause)
494
+ });
495
+ const makeJsonReporter = (services) => (options) => (events) => {
496
+ const seen = {};
497
+ return Stream.runForEach(Stream.fromAsyncIterable(events, streamErrorOf), (event) => Effect.sync(() => rememberReport(seen, event))).pipe(Effect.andThen(Effect.sync(() => seen.report)), Effect.flatMap((report) => Option.match(Option.fromUndefinedOr(report), {
498
+ onNone: () => Effect.void,
499
+ onSome: (ready) => writeReport(services, options, ready)
500
+ })));
501
+ };
502
+ //#endregion
503
+ //#region src/progress-reporter.ts
504
+ const makeProgressBarState = (format, options) => ({
505
+ format,
506
+ total: options.total,
507
+ curr: 0,
508
+ width: options.width,
509
+ complete: options.complete,
510
+ incomplete: options.incomplete
511
+ });
512
+ const tickProgressBar = (state, ticks) => ({
513
+ ...state,
514
+ curr: state.curr + ticks
515
+ });
516
+ const renderProgressBar = (state, data) => formatBar(state.format, state.curr, state.total, data, {
517
+ width: state.width,
518
+ complete: state.complete,
519
+ incomplete: state.incomplete
520
+ });
521
+ const isComplete = (state) => state.curr >= state.total;
522
+ function formatBar(format, curr, total, data, options) {
523
+ const ratio = Match.value(total === 0).pipe(Match.when(true, () => 0), Match.when(false, () => Math.min(curr / total, 1)), Match.exhaustive);
524
+ const filled = Math.floor(ratio * options.width);
525
+ const bar = options.complete.repeat(filled) + options.incomplete.repeat(options.width - filled);
526
+ const percent = `${Math.floor(ratio * 100).toString().padStart(3, " ")}%`;
527
+ const printed = format.replace(":bar", bar).replace(":percent", percent);
528
+ return Object.entries(data).reduce((out, [key, value]) => out.replaceAll(`:${key}`, String(value)), printed);
529
+ }
530
+ const emptyTally = (startedAt) => ({
531
+ survived: 0,
532
+ timedOut: 0,
533
+ tested: 0,
534
+ mutants: 0,
535
+ total: 0,
536
+ ticks: 0,
537
+ ticksByMutantId: {},
538
+ timing: {
539
+ net: 0,
540
+ overhead: 0
541
+ },
542
+ capabilities: { reloadEnvironment: false },
543
+ startedAt
544
+ });
545
+ const getElapsedTime = (tally, now) => {
546
+ return formatTime(Math.floor((now - tally.startedAt) / 1e3));
547
+ };
548
+ const getEtc = (tally, now) => {
549
+ const elapsed = Math.floor((now - tally.startedAt) / 1e3);
550
+ const remaining = Math.floor(elapsed / tally.ticks * (tally.total - tally.ticks));
551
+ return Match.value(Number.isFinite(remaining) && remaining > 0).pipe(Match.when(true, () => formatTime(remaining)), Match.when(false, () => "n/a"), Match.exhaustive);
552
+ };
553
+ function formatTime(timeInSeconds) {
554
+ const hours = Math.floor(timeInSeconds / 3600);
555
+ const minutes = Math.floor(timeInSeconds % 3600 / 60);
556
+ return Match.value(hours > 0).pipe(Match.when(true, () => `~${hours}h ${minutes}m`), Match.when(false, () => Match.value(minutes > 0).pipe(Match.when(true, () => `~${minutes}m`), Match.when(false, () => "<1m"), Match.exhaustive)), Match.exhaustive);
557
+ }
558
+ const ticksFor = (plan, tally) => Match.value(tally.capabilities.reloadEnvironment === false && plan.reloadEnvironment).pipe(Match.when(true, () => plan.netTime + tally.timing.overhead), Match.when(false, () => plan.netTime), Match.exhaustive);
559
+ const progressData = (tally, now) => ({
560
+ survived: tally.survived,
561
+ timedOut: tally.timedOut,
562
+ tested: tally.tested,
563
+ mutants: tally.mutants,
564
+ total: tally.total,
565
+ ticks: tally.ticks,
566
+ et: getElapsedTime(tally, now),
567
+ etc: getEtc(tally, now)
568
+ });
569
+ const tallyAfterTest = (tally, tested, ticks) => {
570
+ const counted = Match.value(tested.status).pipe(Match.when("Survived", () => ({
571
+ survived: 1,
572
+ timedOut: 0
573
+ })), Match.when("Timeout", () => ({
574
+ survived: 0,
575
+ timedOut: 1
576
+ })), Match.orElse(() => ({
577
+ survived: 0,
578
+ timedOut: 0
579
+ })));
580
+ return {
581
+ ...tally,
582
+ tested: tested.completed,
583
+ ticks: tally.ticks + ticks,
584
+ survived: tally.survived + counted.survived,
585
+ timedOut: tally.timedOut + counted.timedOut
586
+ };
587
+ };
588
+ const PROGRESS_BAR_FORMAT = "Mutation testing [:bar] :percent (elapsed: :et, remaining: :etc) :tested/:mutants Mutants tested (:survived survived, :timedOut timed out)";
589
+ const PROGRESS_BAR_OPTIONS = {
590
+ complete: "=",
591
+ incomplete: " ",
592
+ width: 50
593
+ };
594
+ const progressStreamErrorOf = (cause) => ReporterFailed.make({
595
+ reporterName: "progress",
596
+ event: "mutationTestReportReady",
597
+ cause: errorToString(cause)
598
+ });
599
+ const makeProgressBarReporter = (services) => () => (events) => {
600
+ const progress = {
601
+ tally: emptyTally(0),
602
+ bar: void 0
603
+ };
604
+ let pending = Promise.resolve();
605
+ const enqueue = (text) => {
606
+ pending = pending.then(() => writeAsync(services.stdio, "stdout", [text])).catch(() => void 0);
607
+ };
608
+ const render = (now) => Option.match(Option.fromUndefinedOr(progress.bar), {
609
+ onNone: () => void 0,
610
+ onSome: (bar) => {
611
+ const line = renderProgressBar(bar, progressData(progress.tally, now));
612
+ const newline = Match.value(isComplete(bar)).pipe(Match.when(true, () => "\n"), Match.when(false, () => ""), Match.exhaustive);
613
+ enqueue(`\r${line}${newline}`);
614
+ }
615
+ });
616
+ return Stream.runForEach(Stream.fromAsyncIterable(events, progressStreamErrorOf), (event) => Effect.sync(() => applyProgressEvent(progress, render, event))).pipe(Effect.ensuring(Effect.ignore(Effect.promise(() => {
617
+ finishProgressBar(progress, enqueue);
618
+ return pending;
619
+ }))));
620
+ };
621
+ const makeProgressStreamReporter = () => (events) => Stream.runDrain(Stream.fromAsyncIterable(events, progressStreamErrorOf));
622
+ const applyProgressEvent = (progress, render, event) => {
623
+ Match.value(event).pipe(Match.tag("dryRunCompleted", (dryRun) => {
624
+ progress.tally = {
625
+ ...progress.tally,
626
+ timing: dryRun.timing,
627
+ capabilities: { reloadEnvironment: dryRun.capabilities.reloadEnvironment }
628
+ };
629
+ }), Match.tag("mutationTestingPlanReady", (planReady) => {
630
+ const ticksByMutantId = Object.fromEntries(planReady.plans.flatMap((plan) => {
631
+ if (plan.plan !== "Run") return [];
632
+ return [[plan.mutantId, ticksFor(plan, progress.tally)]];
633
+ }));
634
+ const total = Object.values(ticksByMutantId).reduce((sum, ticks) => sum + ticks, 0);
635
+ progress.tally = {
636
+ ...progress.tally,
637
+ startedAt: performance.now(),
638
+ ticksByMutantId,
639
+ mutants: Object.keys(ticksByMutantId).length,
640
+ total
641
+ };
642
+ progress.bar = makeProgressBarState(PROGRESS_BAR_FORMAT, {
643
+ ...PROGRESS_BAR_OPTIONS,
644
+ total
645
+ });
646
+ }), Match.tag("mutantTested", (tested) => {
647
+ Option.match(Option.fromUndefinedOr(progress.tally.ticksByMutantId[tested.id]), {
648
+ onNone: () => void 0,
649
+ onSome: (ticks) => {
650
+ progress.tally = tallyAfterTest(progress.tally, tested, ticks);
651
+ progress.bar = Option.getOrUndefined(Option.map(Option.fromUndefinedOr(progress.bar), (bar) => tickProgressBar(bar, ticks)));
652
+ render(performance.now());
653
+ }
654
+ });
655
+ }), Match.orElse(() => void 0));
656
+ };
657
+ const finishProgressBar = (progress, enqueue) => {
658
+ Option.match(Option.fromUndefinedOr(progress.bar), {
659
+ onNone: () => void 0,
660
+ onSome: (bar) => Match.value(isComplete(bar)).pipe(Match.when(true, () => void 0), Match.when(false, () => enqueue("\n")), Match.exhaustive)
661
+ });
662
+ };
663
+ //#endregion
664
+ //#region src/builtin-reporters.ts
665
+ const makeBuiltinReporterFactories = (services) => ({
666
+ "json": makeJsonReporter(services),
667
+ "clear-text": makeClearTextReporter(services),
668
+ "progress": makeProgressBarReporter(services),
669
+ "progress-stream": makeProgressStreamReporter
670
+ });
671
+ //#endregion
672
+ export { ansi as n, makeBuiltinReporterFactories as t };
@@ -0,0 +1,14 @@
1
+ import * as FileSystem from 'effect/FileSystem';
2
+ import * as Path from 'effect/Path';
3
+ import { ReporterFactory } from '@systemfsoftware/stryker-js-plugin-interface';
4
+ import * as Stdio from 'effect/Stdio';
5
+
6
+ export declare interface BuiltinReporterServices {
7
+ readonly fileSystem: FileSystem.FileSystem;
8
+ readonly path: Path.Path;
9
+ readonly stdio: Stdio.Stdio;
10
+ }
11
+
12
+ export declare const makeBuiltinReporterFactories: (services: BuiltinReporterServices) => Record<string, ReporterFactory>;
13
+
14
+ export { }
@@ -0,0 +1,2 @@
1
+ import { t as makeBuiltinReporterFactories } from "./builtin-reporters-wAuKFSsd.mjs";
2
+ export { makeBuiltinReporterFactories };