@systemfsoftware/stryker-js-instrumenter 0.2.0 → 2.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.
package/dist/index.mjs CHANGED
@@ -1,29 +1,365 @@
1
- import { PluginKind, commonTokens, declareClassPlugin, tokens } from "@systemfsoftware/stryker-js-plugin-api/plugin";
2
- import path from "path";
3
- import { INSTRUMENTER_CONSTANTS as ID } from "@systemfsoftware/stryker-js-plugin-api/core";
4
- import babel, { File, types } from "@babel/core";
5
- import { deepFreeze, notEmpty, propertyPath } from "@systemfsoftware/stryker-js-util";
6
- import { satisfies } from "semver";
1
+ import babel, { File } from "@babel/core";
7
2
  import generator from "@babel/generator";
8
- import * as weaponRegex from "weapon-regex";
9
- import "@systemfsoftware/stryker-js-plugin-api/ignore";
10
- import "@systemfsoftware/stryker-js-plugin-api/logging";
3
+ import { Cell, Wire, Workflow } from "@systemfsoftware/effect-cell-types";
4
+ import { INSTRUMENTER_CONSTANTS, Mutant } from "@systemfsoftware/stryker-js/Mutant";
5
+ import * as Effect from "effect/Effect";
6
+ import { pipe } from "effect/Function";
7
+ import * as Predicate from "effect/Predicate";
8
+ import * as Result from "effect/Result";
9
+ import * as S from "effect/Schema";
11
10
  import { createRequire } from "module";
12
- //#region src/instrumenter-tokens.ts
13
- const instrumenterTokens = Object.freeze({
14
- createParser: "instrumenterCreateParser",
15
- print: "instrumenterPrint",
16
- transform: "instrumenterTransform"
11
+ import path from "path";
12
+ import "@systemfsoftware/stryker-js/Ignorer";
13
+ import { propertyPath, strykerReportBugUrl } from "@systemfsoftware/stryker-js/Schema";
14
+ import * as Option from "effect/Option";
15
+ import path$1 from "node:path";
16
+ import { RegExpParser, visitRegExpAST } from "@eslint-community/regexpp";
17
+ //#region src/Instrument.workflow.ts
18
+ /**
19
+ * Instrument.workflow — pure decision for the instrument capability.
20
+ *
21
+ * The heavy lifting (Babel traversal, mutant collection, code generation)
22
+ * happens in `Instrument.ts`'s read phase, which is allowed to be stateful.
23
+ * This decision receives the already-collected mutants as data and wraps them
24
+ * into the decision type. All helpers and schemas live in this file so the
25
+ * decision body only reaches parameters, const locals, same-file declarations,
26
+ * and the sealed pure effect surface.
27
+ */
28
+ var InstrumentError = class extends S.TaggedError("@systemfsoftware/stryker-js-instrumenter/InstrumentError")("InstrumentError", {
29
+ message: S.String,
30
+ cause: S.Defect()
31
+ }) {
32
+ get message() {
33
+ if (this.message.length === 0) return "Instrumenter failure";
34
+ return this.message;
35
+ }
36
+ };
37
+ const PositionSchema = S.Struct({
38
+ line: S.Finite,
39
+ column: S.Finite
40
+ });
41
+ const RangeSchema = S.Struct({
42
+ start: PositionSchema,
43
+ end: PositionSchema
44
+ });
45
+ const MutateDescriptionSchema = S.Union([S.Boolean, S.Array(RangeSchema)]);
46
+ const FileSchema = S.Struct({
47
+ name: S.String,
48
+ content: S.String,
49
+ mutate: MutateDescriptionSchema
50
+ });
51
+ const PluginSchema = Wire.mint(S.Unknown);
52
+ const IgnorerSchema = Wire.mint(S.Unknown);
53
+ const AstSchema = Wire.mint(S.Unknown);
54
+ const InstrumenterOptionsSchema = Wire.wire({
55
+ plugins: Wire.mint(S.NullOr(Wire.mint(S.Array(PluginSchema)))),
56
+ excludedMutations: Wire.mint(S.Array(Wire.mint(S.String))),
57
+ ignorers: Wire.mint(S.Array(IgnorerSchema)),
58
+ noHeader: Wire.mint(S.optional(Wire.mint(S.Boolean)))
59
+ });
60
+ var InstrumentCommand = class extends S.TaggedClass()("InstrumentCommand", {
61
+ files: S.Array(FileSchema),
62
+ options: InstrumenterOptionsSchema
63
+ }) {};
64
+ var InstrumentDecoded = class extends S.TaggedClass()("InstrumentDecoded", {
65
+ files: S.Array(FileSchema),
66
+ options: InstrumenterOptionsSchema,
67
+ asts: S.Array(AstSchema),
68
+ mutants: S.Array(Mutant)
69
+ }) {};
70
+ var InstrumentDecision = class extends S.TaggedClass()("InstrumentDecision", {
71
+ files: S.Array(FileSchema),
72
+ mutants: S.Array(Mutant),
73
+ asts: S.Array(AstSchema)
74
+ }) {};
75
+ var InstrumentResult = class extends S.TaggedClass()("InstrumentResult", {
76
+ files: S.Array(FileSchema),
77
+ mutants: S.Array(Mutant)
78
+ }) {};
79
+ const instrumentWorkflow = Workflow.make(InstrumentDecoded, (decoded) => Result.succeed(InstrumentDecision.make({
80
+ files: decoded.files,
81
+ mutants: decoded.mutants,
82
+ asts: decoded.asts
83
+ })));
84
+ //#endregion
85
+ //#region src/Parser.schema.ts
86
+ var ParseFailed = class extends S.TaggedError("@systemfsoftware/stryker-js-instrumenter/ParseFailed")("ParseFailed", {
87
+ fileName: S.String,
88
+ message: S.String,
89
+ location: S.Struct({
90
+ line: S.Finite,
91
+ column: S.Finite
92
+ }),
93
+ cause: S.Defect()
94
+ }) {
95
+ get message() {
96
+ return `Failed to parse ${this.fileName} at ${this.location.line}:${this.location.column}: ${this.message}`;
97
+ }
98
+ };
99
+ S.TaggedError("@systemfsoftware/stryker-js-instrumenter/ParserNotFound")("ParserNotFound", {
100
+ fileName: S.String,
101
+ extension: S.String,
102
+ cause: S.Defect()
103
+ });
104
+ var SvelteVersionNotSupported = class extends S.TaggedError("@systemfsoftware/stryker-js-instrumenter/SvelteVersionNotSupported")("SvelteVersionNotSupported", {
105
+ version: S.String,
106
+ fileName: S.String,
107
+ cause: S.Defect()
108
+ }) {
109
+ get message() {
110
+ return `Svelte version ${this.version} is not supported for ${this.fileName} (expected >=3.30)`;
111
+ }
112
+ };
113
+ var SvelteWalkerNotFound = class extends S.TaggedError("@systemfsoftware/stryker-js-instrumenter/SvelteWalkerNotFound")("SvelteWalkerNotFound", {
114
+ fileName: S.String,
115
+ cause: S.Defect()
116
+ }) {
117
+ get message() {
118
+ return `Svelte walker not found for ${this.fileName}`;
119
+ }
120
+ };
121
+ S.TaggedError("@systemfsoftware/stryker-js-instrumenter/SvelteParseFailed")("SvelteParseFailed", {
122
+ fileName: S.String,
123
+ cause: S.Defect()
17
124
  });
125
+ S.Literals([
126
+ "html",
127
+ "js",
128
+ "ts",
129
+ "tsx",
130
+ "svelte"
131
+ ]);
18
132
  //#endregion
19
- //#region src/parsers/parse-error.ts
20
- var ParseError = class extends Error {
21
- constructor(message, fileName, location) {
22
- super(`Parse error in ${fileName} (${location.line}:${location.column}) ${message}`);
133
+ //#region src/Syntax.ts
134
+ /**
135
+ * Determines if a location (needle) is included in an other location (haystack)
136
+ * @param haystack The range to look in
137
+ * @param needle the range to search for
138
+ */
139
+ function locationIncluded(haystack, needle) {
140
+ const startIncluded = haystack.start.line < needle.start.line || haystack.start.line === needle.start.line && haystack.start.column <= needle.start.column;
141
+ const endIncluded = haystack.end.line > needle.end.line || haystack.end.line === needle.end.line && haystack.end.column >= needle.end.column;
142
+ return startIncluded && endIncluded;
143
+ }
144
+ /**
145
+ * Determines if two locations overlap with each other
146
+ */
147
+ function locationOverlaps(a, b) {
148
+ const startIncluded = a.start.line < b.end.line || a.start.line === b.end.line && a.start.column <= b.end.column;
149
+ const endIncluded = a.end.line > b.start.line || a.end.line === b.start.line && a.end.column >= b.start.column;
150
+ return startIncluded && endIncluded;
151
+ }
152
+ function computeLineStarts(text) {
153
+ const result = [];
154
+ let pos = 0;
155
+ let lineStart = 0;
156
+ while (pos < text.length) {
157
+ const ch = text.charCodeAt(pos);
158
+ pos++;
159
+ switch (ch) {
160
+ case CharacterCodes.carriageReturn:
161
+ if (text.charCodeAt(pos) === CharacterCodes.lineFeed) pos++;
162
+ result.push(lineStart);
163
+ lineStart = pos;
164
+ break;
165
+ case CharacterCodes.lineFeed:
166
+ result.push(lineStart);
167
+ lineStart = pos;
168
+ break;
169
+ default: if (ch > CharacterCodes.maxAsciiCharacter && isLineBreak(ch)) {
170
+ result.push(lineStart);
171
+ lineStart = pos;
172
+ }
173
+ }
174
+ }
175
+ result.push(lineStart);
176
+ return result;
177
+ }
178
+ function positionFromOffset(lineStarts, offset) {
179
+ const lineNumber = computeLineOfPosition(lineStarts, offset);
180
+ const lineStart = lineStarts[lineNumber];
181
+ if (lineStart === void 0) throw new Error("Line start not found for computed line number");
182
+ return {
183
+ line: lineNumber,
184
+ column: offset - lineStart
185
+ };
186
+ }
187
+ function computeLineOfPosition(lineStarts, offset) {
188
+ let lineNumber = binarySearch(lineStarts, offset);
189
+ if (lineNumber < 0) {
190
+ lineNumber = ~lineNumber - 1;
191
+ if (lineNumber === -1) throw new Error("position cannot precede the beginning of the file");
192
+ }
193
+ return lineNumber;
194
+ }
195
+ function binarySearch(array, value) {
196
+ if (!array.length) return -1;
197
+ let low = 0;
198
+ let high = array.length - 1;
199
+ while (low <= high) {
200
+ const middle = low + (high - low >> 1);
201
+ const midValue = array[middle];
202
+ if (midValue === void 0) throw new Error("Binary search middle value is missing");
203
+ switch (compare(midValue, value)) {
204
+ case -1:
205
+ low = middle + 1;
206
+ break;
207
+ case 0: return middle;
208
+ case 1: high = middle - 1;
209
+ }
23
210
  }
211
+ return ~low;
212
+ }
213
+ function compare(a, b) {
214
+ if (a < b) return -1;
215
+ if (a > b) return 1;
216
+ return 0;
217
+ }
218
+ const CharacterCodes = {
219
+ lineFeed: 10,
220
+ carriageReturn: 13,
221
+ maxAsciiCharacter: 127,
222
+ lineSeparator: 8232,
223
+ paragraphSeparator: 8233
24
224
  };
225
+ function isLineBreak(ch) {
226
+ return ch === CharacterCodes.lineFeed || ch === CharacterCodes.carriageReturn || ch === CharacterCodes.lineSeparator || ch === CharacterCodes.paragraphSeparator;
227
+ }
25
228
  //#endregion
26
- //#region src/parsers/html-parser.ts
229
+ //#region src/Parser.ts
230
+ /**
231
+ * Parser — all parsers that turn source text into the instrumenter's ASTs.
232
+ */
233
+ const { types: types$1, parseAsync } = babel;
234
+ const require = createRequire(import.meta.url);
235
+ function createParser(parserOptions) {
236
+ const jsParse = createJSParser(parserOptions);
237
+ async function parse(code, fileName, formatOverride) {
238
+ const format = getFormat(fileName, formatOverride);
239
+ if (!format) {
240
+ const ext = path.extname(fileName).toLowerCase();
241
+ throw new Error(`Unable to parse ${fileName}. No parser registered for ${ext}!`);
242
+ }
243
+ switch (format) {
244
+ case "js": return jsParse(code, fileName);
245
+ case "tsx": return parseTsx(code, fileName);
246
+ case "ts": return parseTS(code, fileName);
247
+ case "html": return parseHtml(code, fileName, { parse });
248
+ case "svelte": return parseSvelte(code, fileName, { parse });
249
+ }
250
+ throw new Error(`Unsupported format: ${String(format)}`);
251
+ }
252
+ return parse;
253
+ }
254
+ function getFormat(fileName, override) {
255
+ if (override) return override;
256
+ else switch (path.extname(fileName).toLowerCase()) {
257
+ case ".js":
258
+ case ".jsx":
259
+ case ".mjs":
260
+ case ".cjs": return "js";
261
+ case ".mts":
262
+ case ".cts":
263
+ case ".ts": return "ts";
264
+ case ".tsx": return "tsx";
265
+ case ".vue":
266
+ case ".html":
267
+ case ".htm": return "html";
268
+ case ".svelte": return "svelte";
269
+ default: return;
270
+ }
271
+ }
272
+ function isParserPlugin(value) {
273
+ return typeof value === "string" || Array.isArray(value);
274
+ }
275
+ function isParserPluginArray(value) {
276
+ return value.every(isParserPlugin);
277
+ }
278
+ function getParserPlugins(override) {
279
+ if (override === null) return defaultPlugins;
280
+ if (isParserPluginArray(override)) return override;
281
+ throw new Error("Invalid parser plugins: expected ParserPlugin[]");
282
+ }
283
+ const defaultPlugins = [
284
+ "doExpressions",
285
+ "objectRestSpread",
286
+ "classProperties",
287
+ "exportDefaultFrom",
288
+ "exportNamespaceFrom",
289
+ "asyncGenerators",
290
+ "functionBind",
291
+ "functionSent",
292
+ "dynamicImport",
293
+ "numericSeparator",
294
+ "importMeta",
295
+ "optionalCatchBinding",
296
+ "optionalChaining",
297
+ "classPrivateProperties",
298
+ ["pipelineOperator", { proposal: "minimal" }],
299
+ "nullishCoalescingOperator",
300
+ "bigInt",
301
+ "throwExpressions",
302
+ "logicalAssignment",
303
+ "classPrivateMethods",
304
+ "v8intrinsic",
305
+ "partialApplication",
306
+ ["decorators", { decoratorsBeforeExport: false }],
307
+ "jsx"
308
+ ];
309
+ function createJSParser({ plugins: pluginsOverride }) {
310
+ return async function parse(text, fileName) {
311
+ const plugins = getParserPlugins(pluginsOverride);
312
+ const ast = await parseAsync(text, {
313
+ parserOpts: { plugins: [...plugins] },
314
+ filename: fileName,
315
+ sourceType: "module"
316
+ });
317
+ if (ast === null) throw new Error(`Expected ${fileName} to contain a babel.types.file, but it yielded null`);
318
+ return {
319
+ originFileName: fileName,
320
+ rawContent: text,
321
+ format: "js",
322
+ root: ast
323
+ };
324
+ };
325
+ }
326
+ /**
327
+ * See https://babeljs.io/docs/en/babel-preset-typescript
328
+ * @param text The text to parse
329
+ * @param fileName The name of the file
330
+ */
331
+ async function parseTS(text, fileName) {
332
+ return {
333
+ originFileName: fileName,
334
+ rawContent: text,
335
+ format: "ts",
336
+ root: await parseTSInternal(text, fileName, false)
337
+ };
338
+ }
339
+ async function parseTsx(text, fileName) {
340
+ return {
341
+ root: await parseTSInternal(text, fileName, true),
342
+ format: "tsx",
343
+ originFileName: fileName,
344
+ rawContent: text
345
+ };
346
+ }
347
+ async function parseTSInternal(text, fileName, isTSX) {
348
+ const ast = await parseAsync(text, {
349
+ filename: fileName,
350
+ parserOpts: { ranges: true },
351
+ configFile: false,
352
+ babelrc: false,
353
+ presets: [[require.resolve("@babel/preset-typescript"), {
354
+ isTSX,
355
+ allExtensions: true
356
+ }]],
357
+ plugins: [[require.resolve("@babel/plugin-proposal-decorators"), { legacy: true }], [require.resolve("@babel/plugin-transform-explicit-resource-management")]]
358
+ });
359
+ if (ast === null) throw new Error(`Expected ${fileName} to contain a babel.types.file, but it yielded null`);
360
+ if (types$1.isProgram(ast)) throw new Error(`Expected ${fileName} to contain a babel.types.file, but was a program`);
361
+ return ast;
362
+ }
27
363
  const TSX_SCRIPT_TYPES = Object.freeze(["tsx", "text/tsx"]);
28
364
  const TS_SCRIPT_TYPES = Object.freeze([
29
365
  "ts",
@@ -36,7 +372,7 @@ const JS_SCRIPT_TYPES = Object.freeze([
36
372
  "javascript",
37
373
  "module"
38
374
  ]);
39
- async function parse$2(text, originFileName, context) {
375
+ async function parseHtml(text, originFileName, context) {
40
376
  return {
41
377
  originFileName,
42
378
  rawContent: text,
@@ -53,29 +389,55 @@ async function ngHtmlParser(text, fileName, parserContext) {
53
389
  });
54
390
  if (errors.length !== 0) {
55
391
  const firstError = errors[0];
56
- if (firstError === void 0) throw new Error("HTML parser reported errors but first error is missing");
57
- throw new ParseError(firstError.msg, fileName, toSourceLocation(firstError.span.start));
392
+ if (firstError === void 0) throw new ParseFailed({
393
+ fileName,
394
+ message: "HTML parser reported errors but first error is missing",
395
+ location: {
396
+ line: 0,
397
+ column: 0
398
+ },
399
+ cause: errors
400
+ });
401
+ throw new ParseFailed({
402
+ fileName,
403
+ message: firstError.msg,
404
+ location: toSourceLocation(firstError.span.start),
405
+ cause: firstError
406
+ });
58
407
  }
59
408
  const scriptsAsPromised = [];
60
- ngParser.visitAll(new class extends ngParser.RecursiveVisitor {
61
- visitElement(el, context) {
409
+ const scriptCollector = {
410
+ visitElement: (el, context) => {
62
411
  const scriptFormat = getScriptType(el);
63
412
  if (scriptFormat) scriptsAsPromised.push(parseScript(el, scriptFormat));
64
- super.visitElement(el, context);
65
- }
66
- }(), rootNodes);
413
+ ngParser.visitAll(scriptCollector, el.children, context);
414
+ },
415
+ visitAttribute: () => void 0,
416
+ visitText: () => void 0,
417
+ visitComment: () => void 0,
418
+ visitDocType: () => void 0,
419
+ visitExpansion: () => void 0,
420
+ visitExpansionCase: () => void 0,
421
+ visitBlock: () => void 0,
422
+ visitBlockParameter: () => void 0,
423
+ visitLetDeclaration: () => void 0,
424
+ visitCdata: () => void 0,
425
+ visitComponent: () => void 0,
426
+ visitDirective: () => void 0
427
+ };
428
+ ngParser.visitAll(scriptCollector, rootNodes);
67
429
  return { scripts: await Promise.all(scriptsAsPromised) };
68
430
  async function parseScript(el, scriptFormat) {
69
431
  const endSourceSpan = el.endSourceSpan;
70
- if (endSourceSpan === null || endSourceSpan === void 0) throw new Error("HTML element without an end source span");
432
+ if (endSourceSpan == null) throw new Error("HTML element without an end source span");
71
433
  const content = text.substring(el.startSourceSpan.end.offset, endSourceSpan.start.offset);
72
434
  const ast = await parserContext.parse(content, fileName, scriptFormat);
73
- if (ast) {
435
+ if (ast !== null && ast !== void 0) {
74
436
  const offset = el.startSourceSpan.end;
75
437
  const rootStart = ast.root.start;
76
- if (rootStart === null || rootStart === void 0) throw new Error("Babel File node without a start offset");
438
+ if (rootStart == null) throw new Error("Babel File node without a start offset");
77
439
  const rootEnd = ast.root.end;
78
- if (rootEnd === null || rootEnd === void 0) throw new Error("Babel File node without an end offset");
440
+ if (rootEnd == null) throw new Error("Babel File node without an end offset");
79
441
  ast.root.start = rootStart + offset.offset;
80
442
  ast.root.end = rootEnd + offset.offset;
81
443
  return {
@@ -108,151 +470,221 @@ function getScriptType(element) {
108
470
  }
109
471
  }
110
472
  }
111
- //#endregion
112
- //#region src/parsers/js-parser.ts
113
- const { types: types$15, parseAsync: parseAsync$1 } = babel;
114
- function isParserPlugin(value) {
115
- return typeof value === "string" || Array.isArray(value);
473
+ function parseVersion(version) {
474
+ const match = /^(\d+)\.(\d+)(?:\.\d+)?/.exec(version);
475
+ if (!match) return;
476
+ const major = Number.parseInt(match[1] ?? "", 10);
477
+ const minor = Number.parseInt(match[2] ?? "", 10);
478
+ if (Number.isNaN(major) || Number.isNaN(minor)) return;
479
+ return {
480
+ major,
481
+ minor
482
+ };
116
483
  }
117
- function isParserPluginArray(value) {
118
- return value.every(isParserPlugin);
484
+ function isSupportedSvelteVersion(version) {
485
+ const parsed = parseVersion(version);
486
+ if (parsed === void 0) return false;
487
+ return parsed.major > 3 || parsed.major === 3 && parsed.minor >= 30;
119
488
  }
120
- function getParserPlugins(override) {
121
- if (override === null || override === void 0) return defaultPlugins;
122
- if (isParserPluginArray(override)) return override;
123
- throw new Error("Invalid parser plugins: expected ParserPlugin[]");
489
+ function isSvelteV5OrLater(version) {
490
+ const parsed = parseVersion(version);
491
+ if (parsed === void 0) return false;
492
+ return parsed.major >= 5;
124
493
  }
125
- const defaultPlugins = [
126
- "doExpressions",
127
- "objectRestSpread",
128
- "classProperties",
129
- "exportDefaultFrom",
130
- "exportNamespaceFrom",
131
- "asyncGenerators",
132
- "functionBind",
133
- "functionSent",
134
- "dynamicImport",
135
- "numericSeparator",
136
- "importMeta",
137
- "optionalCatchBinding",
138
- "optionalChaining",
139
- "classPrivateProperties",
140
- ["pipelineOperator", { proposal: "minimal" }],
141
- "nullishCoalescingOperator",
142
- "bigInt",
143
- "throwExpressions",
144
- "logicalAssignment",
145
- "classPrivateMethods",
146
- "v8intrinsic",
147
- "partialApplication",
148
- ["decorators", { decoratorsBeforeExport: false }],
149
- "jsx"
150
- ];
151
- function createParser$1({ plugins: pluginsOverride }) {
152
- return async function parse(text, fileName) {
153
- const plugins = getParserPlugins(pluginsOverride);
154
- const ast = await parseAsync$1(text, {
155
- parserOpts: { plugins: [...plugins] },
156
- filename: fileName,
157
- sourceType: "module"
158
- });
159
- if (ast === null || ast === void 0) throw new Error(`Expected ${fileName} to contain a babel.types.file, but it yielded null`);
160
- if (types$15.isProgram(ast)) throw new Error(`Expected ${fileName} to contain a babel.types.file, but was a program`);
161
- return {
162
- originFileName: fileName,
163
- rawContent: text,
164
- format: "js",
165
- root: ast
166
- };
167
- };
494
+ function isPlainRecord(value) {
495
+ return typeof value === "object" && value !== null;
168
496
  }
169
- //#endregion
170
- //#region src/util/position-converter.ts
171
- /**
172
- * A class that can convert a string offset back to line / column.
173
- * Grabbed from TypeScript code base
174
- * @see https://github.com/microsoft/TypeScript/blob/aa9b6953441b53f8b14072c047f0519b611150c4/src/compiler/scanner.ts#L503
175
- */
176
- var PositionConverter = class {
177
- text;
178
- _lineStarts;
179
- get lineStarts() {
180
- if (!this._lineStarts) this._lineStarts = this.computeLineStarts(this.text);
181
- return this._lineStarts;
182
- }
183
- constructor(text) {
184
- this.text = text;
185
- }
186
- positionFromOffset(offset) {
187
- const lineNumber = this.computeLineOfPosition(offset);
188
- const lineStart = this.lineStarts[lineNumber];
189
- if (lineStart === void 0) throw new Error("Line start not found for computed line number");
190
- return {
191
- line: lineNumber,
192
- column: offset - lineStart
193
- };
194
- }
195
- computeLineOfPosition(offset) {
196
- let lineNumber = binarySearch(this.lineStarts, offset);
197
- if (lineNumber < 0) {
198
- lineNumber = ~lineNumber - 1;
199
- if (lineNumber === -1) throw new Error("position cannot precede the beginning of the file");
200
- }
201
- return lineNumber;
202
- }
203
- computeLineStarts(text) {
204
- const result = [];
205
- let pos = 0;
206
- let lineStart = 0;
207
- while (pos < text.length) {
208
- const ch = text.charCodeAt(pos);
209
- pos++;
210
- switch (ch) {
211
- case 13:
212
- if (text.charCodeAt(pos) === 10) pos++;
213
- result.push(lineStart);
214
- lineStart = pos;
215
- break;
216
- case 10:
217
- result.push(lineStart);
218
- lineStart = pos;
219
- break;
220
- default: if (ch > 127 && isLineBreak(ch)) {
221
- result.push(lineStart);
222
- lineStart = pos;
223
- }
497
+ function isUnknownArray(value) {
498
+ return Array.isArray(value);
499
+ }
500
+ function isRangedProgram(value) {
501
+ return isPlainRecord(value) && typeof value["start"] === "number" && typeof value["end"] === "number";
502
+ }
503
+ function isWalkFunction(value) {
504
+ return typeof value === "function";
505
+ }
506
+ function isRangedBaseNode(value) {
507
+ return isPlainRecord(value) && typeof value["type"] === "string" && typeof value["start"] === "number" && typeof value["end"] === "number";
508
+ }
509
+ function isTemplateExpressionType(type) {
510
+ return type === "MustacheTag" || type === "RawMustacheTag" || type === "IfBlock" || type === "ConstTag" || type === "EachBlock" || type === "AwaitBlock" || type === "KeyBlock" || type === "EventHandler";
511
+ }
512
+ function tryGetScriptRangeFromElement(node) {
513
+ if (!isPlainRecord(node) || node["type"] !== "Element" || node["name"] !== "script") return;
514
+ const children = node["children"];
515
+ if (!isUnknownArray(children) || children.length === 0) return;
516
+ const firstChild = children[0];
517
+ if (!isPlainRecord(firstChild) || firstChild["type"] !== "Text" || typeof firstChild["start"] !== "number" || typeof firstChild["end"] !== "number") return;
518
+ return {
519
+ start: firstChild["start"],
520
+ end: firstChild["end"],
521
+ isExpression: false
522
+ };
523
+ }
524
+ async function parseSvelte(text, fileName, context) {
525
+ const { parse: svelteParse, preprocess, VERSION } = await import("svelte/compiler");
526
+ let walk;
527
+ if (!isSupportedSvelteVersion(VERSION)) throw new SvelteVersionNotSupported({
528
+ version: VERSION,
529
+ fileName,
530
+ cause: `Expected >=3.30`
531
+ });
532
+ if (isSvelteV5OrLater(VERSION)) {
533
+ const walkerModule = await import(import.meta.resolve("estree-walker"));
534
+ if (!isPlainRecord(walkerModule) || !isWalkFunction(walkerModule["walk"])) throw new SvelteWalkerNotFound({
535
+ fileName,
536
+ cause: "estree-walker module without walk export"
537
+ });
538
+ walk = walkerModule["walk"];
539
+ } else {
540
+ const svelteCompilerModule = await import("svelte/compiler");
541
+ if (!isPlainRecord(svelteCompilerModule) || !isWalkFunction(svelteCompilerModule["walk"])) throw new SvelteWalkerNotFound({
542
+ fileName,
543
+ cause: "svelte/compiler module without walk export"
544
+ });
545
+ walk = svelteCompilerModule["walk"];
546
+ }
547
+ const lineStarts = computeLineStarts(text);
548
+ const { replacedCode, scriptMap } = await replaceScripts(text);
549
+ const svelteAst = svelteParse(replacedCode, { filename: fileName });
550
+ const { remappedModuleScriptRange, remappedScriptRanges } = remapScriptLocations(replacedCode, scriptMap, getModuleScriptRange(svelteAst), getTemplateScriptRanges(svelteAst, walk));
551
+ const [moduleScript, ...additionalScripts] = await Promise.all([parseTemplateScriptIfDefined(remappedModuleScriptRange), ...remappedScriptRanges.map(parseTemplateScript)]);
552
+ let root;
553
+ if (moduleScript === void 0) root = { additionalScripts };
554
+ else root = {
555
+ moduleScript,
556
+ additionalScripts
557
+ };
558
+ return {
559
+ originFileName: fileName,
560
+ rawContent: text,
561
+ format: "svelte",
562
+ root
563
+ };
564
+ /**
565
+ * Replaces script tags with placeholders.
566
+ * This is needed, because svelte's `parse` doesn't support `lang="ts"`.
567
+ */
568
+ async function replaceScripts(code) {
569
+ const map = /* @__PURE__ */ new Map();
570
+ let scriptIndex = 0;
571
+ return {
572
+ replacedCode: (await preprocess(code, { script(script) {
573
+ const scriptName = `script${scriptIndex++}`;
574
+ map.set(scriptName, script);
575
+ return { code: scriptName };
576
+ } })).code,
577
+ scriptMap: map
578
+ };
579
+ }
580
+ function getTemplateScriptRanges(ast, walker) {
581
+ const ranges = [];
582
+ if (isPlainRecord(ast) && ast["instance"] !== null && ast["instance"] !== void 0) {
583
+ const instance = ast["instance"];
584
+ if (isPlainRecord(instance) && "content" in instance) {
585
+ const content = instance["content"];
586
+ if (isRangedProgram(content)) ranges.push({
587
+ start: content.start,
588
+ end: content.end,
589
+ isExpression: false
590
+ });
591
+ else throw new Error("Svelte instance script without a source range");
224
592
  }
225
593
  }
226
- result.push(lineStart);
227
- return result;
594
+ if (!isPlainRecord(ast) || !("html" in ast)) throw new Error("Svelte AST without html");
595
+ const html = ast["html"];
596
+ walker(html, { enter(node) {
597
+ const scriptRange = tryGetScriptRangeFromElement(node);
598
+ if (scriptRange) ranges.push(scriptRange);
599
+ const templateExpression = collectTemplateExpression(node);
600
+ if (templateExpression) {
601
+ const { start, end } = templateExpression;
602
+ ranges.push({
603
+ start,
604
+ end,
605
+ isExpression: true
606
+ });
607
+ }
608
+ } });
609
+ return ranges;
228
610
  }
229
- };
230
- function binarySearch(array, value) {
231
- if (!array.length) return -1;
232
- let low = 0;
233
- let high = array.length - 1;
234
- while (low <= high) {
235
- const middle = low + (high - low >> 1);
236
- const midValue = array[middle];
237
- if (midValue === void 0) throw new Error("Binary search middle value is missing");
238
- switch (compare(midValue, value)) {
239
- case -1:
240
- low = middle + 1;
241
- break;
242
- case 0: return middle;
243
- case 1: high = middle - 1;
244
- }
611
+ async function parseTemplateScriptIfDefined(range) {
612
+ if (range) return parseTemplateScript(range);
613
+ }
614
+ async function parseTemplateScript({ start, end, isExpression, format }) {
615
+ const scriptText = text.slice(start, end);
616
+ return {
617
+ ast: {
618
+ ...await context.parse(scriptText, fileName, format),
619
+ offset: positionFromOffset(lineStarts, start)
620
+ },
621
+ range: {
622
+ start,
623
+ end
624
+ },
625
+ isExpression
626
+ };
245
627
  }
246
- return ~low;
247
628
  }
248
- function compare(a, b) {
249
- return a < b ? -1 : a > b ? 1 : 0;
629
+ function getModuleScriptRange(svelteAst) {
630
+ if (!isPlainRecord(svelteAst) || !("module" in svelteAst) || svelteAst["module"] === null || svelteAst["module"] === void 0) return;
631
+ const mod = svelteAst["module"];
632
+ if (!isPlainRecord(mod) || !("content" in mod)) throw new Error("Svelte module script without a source range");
633
+ const content = mod["content"];
634
+ if (!isRangedProgram(content)) throw new Error("Svelte module script without a source range");
635
+ return {
636
+ start: content.start,
637
+ end: content.end,
638
+ isExpression: false
639
+ };
250
640
  }
251
- function isLineBreak(ch) {
252
- return ch === 10 || ch === 13 || ch === 8232 || ch === 8233;
641
+ /**
642
+ * Remaps script locations back to the original places using the script map
643
+ */
644
+ function remapScriptLocations(code, scriptMap, moduleScriptRange, templateRanges) {
645
+ const scriptRanges = [moduleScriptRange, ...templateRanges].filter(Predicate.isNotNullish).sort((a, b) => a.start - b.start);
646
+ let offset = 0;
647
+ let newModuleScriptRange;
648
+ const newScriptRanges = scriptRanges.map((range) => {
649
+ const script = code.substring(range.start, range.end);
650
+ const actualScript = scriptMap.get(script);
651
+ const start = range.start + offset;
652
+ if (actualScript) {
653
+ let langFormat = "js";
654
+ if (actualScript.attributes["lang"] === "ts") langFormat = "ts";
655
+ const scriptRange = {
656
+ start,
657
+ end: start + actualScript.content.length,
658
+ isExpression: range.isExpression,
659
+ format: langFormat
660
+ };
661
+ offset += actualScript.content.length - script.length;
662
+ if (range === moduleScriptRange) newModuleScriptRange = scriptRange;
663
+ return scriptRange;
664
+ } else return {
665
+ start,
666
+ end: start + script.length,
667
+ isExpression: range.isExpression,
668
+ format: "js"
669
+ };
670
+ });
671
+ return {
672
+ remappedModuleScriptRange: newModuleScriptRange,
673
+ remappedScriptRanges: newScriptRanges.filter((range) => range !== newModuleScriptRange)
674
+ };
675
+ }
676
+ function collectTemplateExpression(node) {
677
+ if (!isPlainRecord(node) || typeof node["type"] !== "string") return;
678
+ if (!isTemplateExpressionType(node["type"])) return;
679
+ if (!("expression" in node)) return;
680
+ const expression = node["expression"];
681
+ if (isRangedBaseNode(expression)) return expression;
253
682
  }
254
683
  //#endregion
255
- //#region src/util/babel-generator.ts
684
+ //#region src/Printer.ts
685
+ /**
686
+ * Printer — turns the instrumenter's ASTs back into source text.
687
+ */
256
688
  /**
257
689
  * `@babel/generator` is CommonJS. Under Node's own ESM interop a default import
258
690
  * of it is the module's `exports` object, so the code generator sits behind
@@ -263,142 +695,319 @@ function isLineBreak(ch) {
263
695
  * time. Resolving both shapes once keeps the printers and the mutant's
264
696
  * replacement code identical under either layout.
265
697
  */
266
- const generate = typeof generator === "function" ? generator : generator.default;
267
- //#endregion
268
- //#region src/mutant.ts
269
- const { traverse: traverse$2 } = babel;
270
- var Mutant$1 = class {
271
- id;
272
- fileName;
273
- original;
274
- offset;
275
- replacementCode;
276
- replacement;
277
- mutatorName;
278
- ignoreReason;
279
- constructor(id, fileName, original, specs, offset = {
280
- column: 0,
281
- line: 0
282
- }) {
283
- this.id = id;
284
- this.fileName = fileName;
285
- this.original = original;
286
- this.offset = offset;
287
- this.replacement = specs.replacement;
288
- this.mutatorName = specs.mutatorName;
289
- this.ignoreReason = specs.ignoreReason;
290
- this.replacementCode = generate(this.replacement).code;
291
- }
292
- toApiMutant() {
293
- const loc = this.original.loc;
294
- if (loc === void 0 || loc === null) throw new Error("Babel node without a source location");
295
- return {
296
- fileName: this.fileName,
297
- id: this.id,
298
- location: toApiLocation(loc, this.offset),
299
- mutatorName: this.mutatorName,
300
- replacement: this.replacementCode,
301
- ...this.ignoreReason === void 0 ? {} : { statusReason: this.ignoreReason },
302
- ...this.ignoreReason === void 0 ? {} : { status: "Ignored" }
303
- };
304
- }
305
- /**
306
- * Applies the mutant in (a copy of) the AST, without changing provided AST.
307
- * Can the tree itself (in which case the replacement is returned),
308
- * or can be nested in the given tree.
309
- *
310
- * Returns a plain node rather than the argument's own type: whether the
311
- * replacement fits a given position is the placer's claim, and the placer
312
- * checks it with a Babel predicate. A generic return would have to assert it
313
- * here, where nothing can check it.
314
- * @param originalTree The original node, which will be treated as readonly
315
- */
316
- applied(originalTree) {
317
- if (originalTree === this.original) return this.replacement;
318
- else {
319
- const mutatedAst = deepCloneNode(originalTree);
320
- let applied = false;
321
- const { original, replacement } = this;
322
- traverse$2(mutatedAst, {
323
- noScope: true,
324
- enter(path) {
325
- if (eqNode(path.node, original)) {
326
- path.replaceWith(replacement);
327
- path.stop();
328
- applied = true;
329
- }
330
- }
331
- });
332
- if (!applied) throw new Error(`Could not apply mutant ${JSON.stringify(this.replacement)}.`);
333
- return mutatedAst;
334
- }
335
- }
336
- };
337
- function toApiLocation(source, offset) {
338
- return {
339
- start: toPosition(source.start, offset),
340
- end: toPosition(source.end, offset)
341
- };
342
- }
343
- function toPosition(source, offset) {
344
- return {
345
- column: source.column + (source.line === 1 ? offset.column : 0),
346
- line: source.line + offset.line - 1
347
- };
698
+ function resolveGenerate$1() {
699
+ if (typeof generator === "function") return generator;
700
+ return generator.default;
348
701
  }
349
- //#endregion
350
- //#region src/mutators/arithmetic-operator-mutator.ts
351
- const arithmeticOperatorReplacements = Object.freeze({
352
- "+": "-",
353
- "-": "+",
354
- "*": "/",
355
- "/": "*",
356
- "%": "*"
357
- });
358
- const arithmeticOperatorMutator = {
359
- name: "ArithmeticOperator",
360
- *mutate(path) {
361
- if (path.isBinaryExpression() && isSupported$3(path.node.operator, path.node)) {
362
- const mutatedOperator = arithmeticOperatorReplacements[path.node.operator];
363
- const replacement = deepCloneNode(path.node);
364
- replacement.operator = mutatedOperator;
365
- yield replacement;
366
- }
702
+ const generate$2 = resolveGenerate$1();
703
+ function print(file) {
704
+ const context = { print };
705
+ switch (file.format) {
706
+ case "js": return jsPrint(file, context);
707
+ case "ts": return tsPrint(file, context);
708
+ case "tsx": return tsPrint(file, context);
709
+ case "html": return htmlPrint(file, context);
710
+ case "svelte": return sveltePrint(file, context);
367
711
  }
712
+ }
713
+ const jsPrint = (file) => {
714
+ return generate$2(file.root, { sourceMaps: false }).code;
368
715
  };
369
- function isSupported$3(operator, node) {
370
- if (!Object.keys(arithmeticOperatorReplacements).includes(operator)) return false;
371
- const stringTypes = ["StringLiteral", "TemplateLiteral"];
372
- const leftType = node.left.type === "BinaryExpression" ? node.left.right.type : node.left.type;
373
- if (stringTypes.includes(node.right.type) || stringTypes.includes(leftType)) return false;
374
- return true;
716
+ const tsPrint = (file) => {
717
+ return generate$2(file.root, {
718
+ decoratorsBeforeExport: true,
719
+ sourceMaps: false
720
+ }).code;
721
+ };
722
+ function getScriptStart$1(script) {
723
+ const start = script.root.start;
724
+ if (start === void 0 || start === null) throw new Error("Script AST root without start");
725
+ return start;
375
726
  }
376
- //#endregion
377
- //#region src/mutators/array-declaration-mutator.ts
378
- const { types: types$14 } = babel;
379
- const arrayDeclarationMutator = {
380
- name: "ArrayDeclaration",
381
- *mutate(path) {
382
- if (path.isArrayExpression()) yield path.node.elements.length ? types$14.arrayExpression() : types$14.arrayExpression([types$14.stringLiteral("Stryker was here")]);
383
- if ((path.isCallExpression() || path.isNewExpression()) && types$14.isIdentifier(path.node.callee) && path.node.callee.name === "Array") {
384
- const mutatedCallArgs = path.node.arguments.length ? [] : [types$14.arrayExpression()];
385
- yield types$14.isNewExpression(path.node) ? types$14.newExpression(deepCloneNode(path.node.callee), mutatedCallArgs) : types$14.callExpression(deepCloneNode(path.node.callee), mutatedCallArgs);
386
- }
727
+ function getScriptEnd$1(script) {
728
+ const end = script.root.end;
729
+ if (end === void 0 || end === null) throw new Error("Script AST root without end");
730
+ return end;
731
+ }
732
+ const htmlPrint = (ast, context) => {
733
+ const sortedScripts = [...ast.root.scripts].sort((a, b) => getScriptStart$1(a) - getScriptStart$1(b));
734
+ let currentIndex = 0;
735
+ let html = "";
736
+ for (const script of sortedScripts) {
737
+ html += ast.rawContent.substring(currentIndex, getScriptStart$1(script));
738
+ html += "\n";
739
+ html += context.print(script, context);
740
+ html += "\n";
741
+ currentIndex = getScriptEnd$1(script);
387
742
  }
743
+ html += ast.rawContent.substr(currentIndex);
744
+ return html;
388
745
  };
389
- //#endregion
390
- //#region src/mutators/arrow-function-mutator.ts
391
- const { types: types$13 } = babel;
392
- const arrowFunctionMutator = {
393
- name: "ArrowFunction",
394
- *mutate(path) {
395
- if (path.isArrowFunctionExpression() && !types$13.isBlockStatement(path.node.body) && !(types$13.isIdentifier(path.node.body) && path.node.body.name === "undefined")) yield types$13.arrowFunctionExpression([], types$13.identifier("undefined"));
746
+ const sveltePrint = ({ root, rawContent }, context) => {
747
+ let currentIndex = 0;
748
+ let outputText = "";
749
+ const sortedScripts = [root.moduleScript, ...root.additionalScripts].filter(Predicate.isNotNullish).sort((a, b) => a.range.start - b.range.start);
750
+ for (const script of sortedScripts) if (script.isExpression) {
751
+ const codeWithoutSemicolon = context.print(script.ast, context).slice(0, -1);
752
+ outputText += rawContent.substring(currentIndex, script.range.start) + codeWithoutSemicolon;
753
+ currentIndex = script.range.end;
754
+ } else {
755
+ outputText += rawContent.substring(currentIndex, script.range.start);
756
+ outputText += "\n";
757
+ outputText += context.print(script.ast, context);
758
+ outputText += "\n";
759
+ currentIndex = script.range.end;
396
760
  }
761
+ outputText += rawContent.substring(currentIndex);
762
+ return outputText;
397
763
  };
398
764
  //#endregion
399
- //#region src/mutators/assignment-operator-mutator.ts
400
- const assignmentOperatorReplacements = Object.freeze({
401
- "+=": "-=",
765
+ //#region src/Mutator.ts
766
+ /**
767
+ * Mutator — every mutation operator and its registry.
768
+ */
769
+ const { traverse: babelTraverse, types: babelTypes$2 } = babel;
770
+ const t$1 = babelTypes$2;
771
+ function isGeneratorFn(value) {
772
+ return typeof value === "function";
773
+ }
774
+ function hasDefault(value) {
775
+ if (typeof value !== "object" || value === null) return false;
776
+ if (!("default" in value)) return false;
777
+ return typeof Reflect.get(value, "default") === "function";
778
+ }
779
+ let generateExport;
780
+ const candidate = generator;
781
+ if (isGeneratorFn(candidate)) generateExport = candidate;
782
+ else if (hasDefault(candidate)) generateExport = candidate.default;
783
+ else throw new Error("Missing generator");
784
+ const generate$1 = generateExport;
785
+ function deepCloneNode(node) {
786
+ return babelTypes$2.cloneNode(node, true, false);
787
+ }
788
+ function eqPosition(a, b) {
789
+ return a.line === b.line && a.column === b.column;
790
+ }
791
+ function eqLocation(a, b) {
792
+ return eqPosition(a.start, b.start) && eqPosition(a.end, b.end);
793
+ }
794
+ function eqNode(a, b) {
795
+ return a.type === b.type && !!a.loc && !!b.loc && eqLocation(a.loc, b.loc);
796
+ }
797
+ function createMutant(id, fileName, original, specs, offset = {
798
+ column: 0,
799
+ line: 0
800
+ }) {
801
+ return {
802
+ id,
803
+ fileName,
804
+ original,
805
+ offset,
806
+ replacement: specs.replacement,
807
+ mutatorName: specs.mutatorName,
808
+ ignoreReason: specs.ignoreReason,
809
+ replacementCode: generate$1(specs.replacement, { sourceMaps: false }).code
810
+ };
811
+ }
812
+ function applyMutant(mutant, originalTree) {
813
+ if (originalTree === mutant.original) return mutant.replacement;
814
+ const mutatedAst = deepCloneNode(originalTree);
815
+ const { original, replacement } = mutant;
816
+ if (hasReplaced(mutatedAst, original, replacement) === false) throw new Error(`Could not apply mutant ${JSON.stringify(replacement)}.`);
817
+ return mutatedAst;
818
+ }
819
+ function hasReplaced(root, original, replacement) {
820
+ let applied = false;
821
+ babelTraverse(root, {
822
+ noScope: true,
823
+ enter(path) {
824
+ if (eqNode(path.node, original)) {
825
+ path.replaceWith(replacement);
826
+ path.stop();
827
+ applied = true;
828
+ }
829
+ }
830
+ });
831
+ return applied;
832
+ }
833
+ /**
834
+ * The mutations of a regular expression pattern.
835
+ *
836
+ * Pure: a pattern and its flags in, replacement patterns out. No I/O, no clock,
837
+ * no throwing — a pattern this cannot parse yields no mutants, which is the
838
+ * honest answer for a literal whose syntax the engine does not model.
839
+ *
840
+ * The transformation set is fixed and small, and each member changes exactly
841
+ * one thing about the pattern:
842
+ *
843
+ * | family | example |
844
+ * | ----------------------- | ------------------------ |
845
+ * | anchor removal | `^abc$` -> `abc$`, `^abc` |
846
+ * | character class negation| `[abc]` <-> `[^abc]` |
847
+ * | predefined class negation| `\d` <-> `\D`, `\p{L}` <-> `\P{L}` |
848
+ * | quantifier removal | `a+`, `a*`, `a{2,3}` -> `a` |
849
+ * | lookaround negation | `(?=a)` <-> `(?!a)`, `(?<=a)` <-> `(?<!a)` |
850
+ *
851
+ * Alternation and grouping are deliberately untouched: swapping a branch or
852
+ * dropping a group produces mutants that survive for reasons unrelated to the
853
+ * test suite's strength, which inflates a score rather than measuring one.
854
+ *
855
+ * The order is part of the contract, because a mutant's identity in a report is
856
+ * its position: anchors first, then each remaining position left to right with
857
+ * quantifier removal ahead of class negation.
858
+ */
859
+ function mutateRegexPattern(pattern, flags) {
860
+ if (pattern.length === 0) return [];
861
+ try {
862
+ const f = flags ?? "";
863
+ const ast = new RegExpParser().parsePattern(pattern, void 0, void 0, {
864
+ unicode: f.includes("u"),
865
+ unicodeSets: f.includes("v")
866
+ });
867
+ const bol = [];
868
+ const eol = [];
869
+ const rest = [];
870
+ visitRegExpAST(ast, {
871
+ onAssertionEnter(node) {
872
+ if (node.kind === "start") {
873
+ const splice = {
874
+ start: node.start,
875
+ end: node.end,
876
+ text: ""
877
+ };
878
+ if ((pattern.slice(0, splice.start) + splice.text + pattern.slice(splice.end)).length === 0) return;
879
+ bol.push(splice);
880
+ } else if (node.kind === "end") {
881
+ const splice = {
882
+ start: node.start,
883
+ end: node.end,
884
+ text: ""
885
+ };
886
+ if ((pattern.slice(0, splice.start) + splice.text + pattern.slice(splice.end)).length === 0) return;
887
+ eol.push(splice);
888
+ } else if (node.kind === "lookahead" || node.kind === "lookbehind") {
889
+ let offset;
890
+ if (node.kind === "lookahead") offset = 2;
891
+ else offset = 3;
892
+ const pos = node.start + offset;
893
+ let text;
894
+ if (node.negate) text = "=";
895
+ else text = "!";
896
+ rest.push({
897
+ start: pos,
898
+ end: pos + 1,
899
+ text,
900
+ priority: 1
901
+ });
902
+ }
903
+ },
904
+ onCharacterClassEnter(node) {
905
+ const pos = node.start + 1;
906
+ if (node.negate) rest.push({
907
+ start: pos,
908
+ end: pos + 1,
909
+ text: "",
910
+ priority: 1
911
+ });
912
+ else rest.push({
913
+ start: pos,
914
+ end: pos,
915
+ text: "^",
916
+ priority: 1
917
+ });
918
+ },
919
+ onCharacterSetEnter(node) {
920
+ if (node.kind === "digit" || node.kind === "space" || node.kind === "word") {
921
+ const pos = node.start + 1;
922
+ const cur = node.raw[1] ?? "";
923
+ let toggled;
924
+ if (cur === cur.toUpperCase()) toggled = cur.toLowerCase();
925
+ else toggled = cur.toUpperCase();
926
+ rest.push({
927
+ start: pos,
928
+ end: pos + 1,
929
+ text: toggled,
930
+ priority: 2
931
+ });
932
+ } else if (node.kind === "property") {
933
+ const pos = node.start + 1;
934
+ const cur = node.raw[1] ?? "";
935
+ let toggled;
936
+ if (cur === "p") toggled = "P";
937
+ else toggled = "p";
938
+ rest.push({
939
+ start: pos,
940
+ end: pos + 1,
941
+ text: toggled,
942
+ priority: 2
943
+ });
944
+ }
945
+ },
946
+ onQuantifierEnter(node) {
947
+ rest.push({
948
+ start: node.start,
949
+ end: node.end,
950
+ text: node.element.raw,
951
+ priority: 0
952
+ });
953
+ }
954
+ });
955
+ rest.sort((a, b) => a.start - b.start || a.priority - b.priority);
956
+ return [
957
+ ...bol,
958
+ ...eol,
959
+ ...rest
960
+ ].map((s) => pattern.slice(0, s.start) + s.text + pattern.slice(s.end));
961
+ } catch {
962
+ return [];
963
+ }
964
+ }
965
+ const arithmeticOperatorReplacements = Object.freeze({
966
+ "+": "-",
967
+ "-": "+",
968
+ "*": "/",
969
+ "/": "*",
970
+ "%": "*"
971
+ });
972
+ const arithmeticOperatorMutator = function* (node, _context) {
973
+ if (t$1.isBinaryExpression(node) && isSupportedArithmeticOperator(node.operator, node)) {
974
+ const mutatedOperator = arithmeticOperatorReplacements[node.operator];
975
+ const replacement = deepCloneNode(node);
976
+ replacement.operator = mutatedOperator;
977
+ yield replacement;
978
+ }
979
+ };
980
+ function isSupportedArithmeticOperator(operator, node) {
981
+ if (!Object.keys(arithmeticOperatorReplacements).includes(operator)) return false;
982
+ const stringTypes = ["StringLiteral", "TemplateLiteral"];
983
+ let leftType;
984
+ if (t$1.isBinaryExpression(node.left)) leftType = node.left.right.type;
985
+ else leftType = node.left.type;
986
+ if (stringTypes.includes(node.right.type) || stringTypes.includes(leftType)) return false;
987
+ return true;
988
+ }
989
+ const arrayDeclarationMutator = function* (node, _context) {
990
+ if (babelTypes$2.isArrayExpression(node)) {
991
+ let replacement;
992
+ if (node.elements.length > 0) replacement = babelTypes$2.arrayExpression();
993
+ else replacement = babelTypes$2.arrayExpression([babelTypes$2.stringLiteral("Stryker was here")]);
994
+ yield replacement;
995
+ }
996
+ if ((babelTypes$2.isCallExpression(node) || babelTypes$2.isNewExpression(node)) && babelTypes$2.isIdentifier(node.callee) && node.callee.name === "Array") {
997
+ let mutatedCallArgs;
998
+ if (node.arguments.length > 0) mutatedCallArgs = [];
999
+ else mutatedCallArgs = [babelTypes$2.arrayExpression()];
1000
+ let replacement;
1001
+ if (babelTypes$2.isNewExpression(node)) replacement = babelTypes$2.newExpression(deepCloneNode(node.callee), mutatedCallArgs);
1002
+ else replacement = babelTypes$2.callExpression(deepCloneNode(node.callee), mutatedCallArgs);
1003
+ yield replacement;
1004
+ }
1005
+ };
1006
+ const arrowFunctionMutator = function* (node, _context) {
1007
+ if (babelTypes$2.isArrowFunctionExpression(node) && !babelTypes$2.isBlockStatement(node.body) && !(babelTypes$2.isIdentifier(node.body) && node.body.name === "undefined")) yield babelTypes$2.arrowFunctionExpression([], babelTypes$2.identifier("undefined"));
1008
+ };
1009
+ const assignmentOperatorReplacements = Object.freeze({
1010
+ "+=": "-=",
402
1011
  "-=": "+=",
403
1012
  "*=": "/=",
404
1013
  "/=": "*=",
@@ -417,90 +1026,71 @@ const stringAssignmentTypes = Object.freeze([
417
1026
  "||=",
418
1027
  "??="
419
1028
  ]);
420
- const assignmentOperatorMutator = {
421
- name: "AssignmentOperator",
422
- *mutate(path) {
423
- if (path.isAssignmentExpression() && isSupportedAssignmentOperator(path.node.operator) && isSupported$2(path.node)) {
424
- const mutatedOperator = assignmentOperatorReplacements[path.node.operator];
425
- const replacement = deepCloneNode(path.node);
426
- replacement.operator = mutatedOperator;
427
- yield replacement;
428
- }
1029
+ const assignmentOperatorMutator = function* (node, _context) {
1030
+ if (babelTypes$2.isAssignmentExpression(node) && isSupportedAssignmentOperator(node.operator) && isSupportedAssignmentExpression(node)) {
1031
+ const mutatedOperator = assignmentOperatorReplacements[node.operator];
1032
+ const replacement = deepCloneNode(node);
1033
+ replacement.operator = mutatedOperator;
1034
+ yield replacement;
429
1035
  }
430
1036
  };
431
1037
  function isSupportedAssignmentOperator(operator) {
432
1038
  return Object.keys(assignmentOperatorReplacements).includes(operator);
433
1039
  }
434
- function isSupported$2(node) {
1040
+ function isSupportedAssignmentExpression(node) {
435
1041
  if (stringTypes.includes(node.right.type) && !stringAssignmentTypes.includes(node.operator)) return false;
436
1042
  return true;
437
1043
  }
438
- //#endregion
439
- //#region src/mutators/block-statement-mutator.ts
440
- const { types: types$12 } = babel;
441
- const blockStatementMutator = {
442
- name: "BlockStatement",
443
- *mutate(path) {
444
- if (path.isBlockStatement() && isValid(path)) yield types$12.blockStatement([]);
445
- }
1044
+ const blockStatementMutator = function* (node, context) {
1045
+ if (babelTypes$2.isBlockStatement(node) && isValid(node, context)) yield babelTypes$2.blockStatement([]);
446
1046
  };
447
- function isValid(path) {
448
- return !isEmpty(path) && !isInvalidConstructorBody(path);
449
- }
450
- function isEmpty(path) {
451
- return !path.node.body.length;
452
- }
453
- /**
454
- * Checks to see if a statement is an invalid constructor body
455
- * @example
456
- * // Invalid!
457
- * class Foo extends Bar {
458
- * constructor(public baz: string) {
459
- * super(42);
460
- * }
461
- * }
462
- * @example
463
- * // Invalid!
464
- * class Foo extends Bar {
465
- * public baz = 'string';
466
- * constructor() {
467
- * super(42);
468
- * }
469
- * }
470
- * @see https://github.com/stryker-mutator/stryker-js/issues/2314
471
- * @see https://github.com/stryker-mutator/stryker-js/issues/2474
472
- */
473
- function isInvalidConstructorBody(blockStatement) {
474
- return Boolean(blockStatement.parentPath.isClassMethod() && blockStatement.parentPath.node.kind === "constructor" && (containsTSParameterProperties(blockStatement.parentPath) || containsInitializedClassProperties(blockStatement.parentPath)) && hasSuperExpression(blockStatement));
475
- }
476
- function containsTSParameterProperties(constructor) {
477
- return constructor.node.params.some((param) => types$12.isTSParameterProperty(param));
478
- }
479
- function containsInitializedClassProperties(constructor) {
480
- return constructor.parentPath.isClassBody() && constructor.parentPath.node.body.some((classMember) => types$12.isClassProperty(classMember) && classMember.value);
481
- }
482
- function hasSuperExpression(constructor) {
483
- let hasSuper = false;
484
- constructor.traverse({ Super(path) {
485
- if (path.parentPath.isCallExpression()) {
486
- path.stop();
487
- hasSuper = true;
488
- }
489
- } });
490
- return hasSuper;
491
- }
492
- //#endregion
493
- //#region src/mutators/boolean-literal-mutator.ts
494
- const { types: types$11 } = babel;
495
- const booleanLiteralMutator = {
496
- name: "BooleanLiteral",
497
- *mutate(path) {
498
- if (path.isBooleanLiteral()) yield types$11.booleanLiteral(!path.node.value);
499
- if (path.isUnaryExpression() && path.node.operator === "!" && path.node.prefix) yield deepCloneNode(path.node.argument);
500
- }
1047
+ function isValid(node, context) {
1048
+ return !isEmpty(node) && !isInvalidConstructorBody(node, context);
1049
+ }
1050
+ function isEmpty(node) {
1051
+ return node.body.length === 0;
1052
+ }
1053
+ function isInvalidConstructorBody(node, context) {
1054
+ const parent = context.parent;
1055
+ if (parent === void 0 || !babelTypes$2.isClassMethod(parent) || parent.kind !== "constructor") return false;
1056
+ const hasParamProps = parent.params.some((param) => babelTypes$2.isTSParameterProperty(param));
1057
+ const hasInitProps = containsInitializedClassProperties(parent, context);
1058
+ const hasSuper = hasSuperExpression(node);
1059
+ return (hasParamProps || hasInitProps) && hasSuper;
1060
+ }
1061
+ function containsInitializedClassProperties(constructor, context) {
1062
+ const grandParent = context.grandParent;
1063
+ if (grandParent === void 0 || !babelTypes$2.isClassBody(grandParent)) return false;
1064
+ return grandParent.body.some((classMember) => babelTypes$2.isClassProperty(classMember) && classMember.value !== null && classMember.value !== void 0);
1065
+ }
1066
+ function hasSuperExpression(block) {
1067
+ return containsSuperCall(block);
1068
+ }
1069
+ function isSuperType(node) {
1070
+ return Predicate.hasProperty(node, "type") && node["type"] === "Super";
1071
+ }
1072
+ function isSuperCallExpression(node) {
1073
+ return Predicate.hasProperty(node, "type") && node["type"] === "CallExpression" && Predicate.hasProperty(node, "callee") && isSuperType(node["callee"]);
1074
+ }
1075
+ function containsSuperCall(node) {
1076
+ if (typeof node !== "object" || node === null) return false;
1077
+ if (isSuperType(node) || isSuperCallExpression(node)) return true;
1078
+ return hasSuperInChildren(node);
1079
+ }
1080
+ function hasSuperInChildren(node) {
1081
+ for (const key of Object.keys(node)) {
1082
+ if (!Predicate.hasProperty(node, key)) continue;
1083
+ const value = node[key];
1084
+ if (Array.isArray(value)) {
1085
+ for (const element of value) if (containsSuperCall(element)) return true;
1086
+ } else if (typeof value === "object" && value !== null && containsSuperCall(value)) return true;
1087
+ }
1088
+ return false;
1089
+ }
1090
+ const booleanLiteralMutator = function* (node, _context) {
1091
+ if (babelTypes$2.isBooleanLiteral(node)) yield babelTypes$2.booleanLiteral(!node.value);
1092
+ if (babelTypes$2.isUnaryExpression(node) && node.operator === "!" && node.prefix) yield deepCloneNode(node.argument);
501
1093
  };
502
- //#endregion
503
- //#region src/mutators/conditional-expression-mutator.ts
504
1094
  const booleanOperators = Object.freeze([
505
1095
  "!=",
506
1096
  "!==",
@@ -513,54 +1103,53 @@ const booleanOperators = Object.freeze([
513
1103
  ">=",
514
1104
  "||"
515
1105
  ]);
516
- const { types: types$10 } = babel;
517
- const conditionalExpressionMutator = {
518
- name: "ConditionalExpression",
519
- *mutate(path) {
520
- if (isTestOfLoop(path)) yield types$10.booleanLiteral(false);
521
- else if (isTestOfCondition(path)) {
522
- yield types$10.booleanLiteral(true);
523
- yield types$10.booleanLiteral(false);
524
- } else if (isBooleanExpression(path)) {
525
- if (path.parent?.type === "LogicalExpression") {
526
- if (path.parent.operator === "||") {
527
- yield types$10.booleanLiteral(false);
528
- return;
529
- }
530
- if (path.parent.operator === "&&") {
531
- yield types$10.booleanLiteral(true);
532
- return;
533
- }
1106
+ const conditionalExpressionMutator = function* (node, context) {
1107
+ if (isTestOfLoop(node, context)) yield babelTypes$2.booleanLiteral(false);
1108
+ else if (isTestOfCondition(node, context)) {
1109
+ yield babelTypes$2.booleanLiteral(true);
1110
+ yield babelTypes$2.booleanLiteral(false);
1111
+ } else if (isBooleanExpression(node)) {
1112
+ const parent = context.parent;
1113
+ if (parent !== void 0 && babelTypes$2.isLogicalExpression(parent)) {
1114
+ if (parent.operator === "||") {
1115
+ yield babelTypes$2.booleanLiteral(false);
1116
+ return;
1117
+ }
1118
+ if (parent.operator === "&&") {
1119
+ yield babelTypes$2.booleanLiteral(true);
1120
+ return;
534
1121
  }
535
- yield types$10.booleanLiteral(true);
536
- yield types$10.booleanLiteral(false);
537
- } else if (path.isForStatement() && !path.node.test) {
538
- const replacement = deepCloneNode(path.node);
539
- replacement.test = types$10.booleanLiteral(false);
540
- yield replacement;
541
- } else if (path.isSwitchCase() && path.node.consequent.length > 0) {
542
- const replacement = deepCloneNode(path.node);
543
- replacement.consequent = [];
544
- yield replacement;
545
1122
  }
1123
+ yield babelTypes$2.booleanLiteral(true);
1124
+ yield babelTypes$2.booleanLiteral(false);
1125
+ } else if (babelTypes$2.isForStatement(node) && node.test === null) {
1126
+ const replacement = deepCloneNode(node);
1127
+ replacement.test = babelTypes$2.booleanLiteral(false);
1128
+ yield replacement;
1129
+ } else if (babelTypes$2.isSwitchCase(node) && node.consequent.length > 0) {
1130
+ const replacement = deepCloneNode(node);
1131
+ replacement.consequent = [];
1132
+ yield replacement;
546
1133
  }
547
1134
  };
548
- function isTestOfLoop(path) {
549
- const { parentPath } = path;
550
- if (!parentPath) return false;
551
- return (parentPath.isForStatement() || parentPath.isWhileStatement() || parentPath.isDoWhileStatement()) && parentPath.node.test === path.node;
552
- }
553
- function isTestOfCondition(path) {
554
- const { parentPath } = path;
555
- if (!parentPath) return false;
556
- return parentPath.isIfStatement() && parentPath.node.test === path.node;
1135
+ function isTestOfLoop(node, context) {
1136
+ const parent = context.parent;
1137
+ if (parent === void 0) return false;
1138
+ if (babelTypes$2.isForStatement(parent) && parent.test === node) return true;
1139
+ if (babelTypes$2.isWhileStatement(parent) && parent.test === node) return true;
1140
+ if (babelTypes$2.isDoWhileStatement(parent) && parent.test === node) return true;
1141
+ return false;
1142
+ }
1143
+ function isTestOfCondition(node, context) {
1144
+ const parent = context.parent;
1145
+ if (parent === void 0) return false;
1146
+ return babelTypes$2.isIfStatement(parent) && parent.test === node;
1147
+ }
1148
+ function isBooleanExpression(node) {
1149
+ if (babelTypes$2.isBinaryExpression(node)) return booleanOperators.includes(node.operator);
1150
+ if (babelTypes$2.isLogicalExpression(node)) return booleanOperators.includes(node.operator);
1151
+ return false;
557
1152
  }
558
- function isBooleanExpression(path) {
559
- return (path.isBinaryExpression() || path.isLogicalExpression()) && booleanOperators.includes(path.node.operator);
560
- }
561
- //#endregion
562
- //#region src/mutators/equality-operator-mutator.ts
563
- const { types: t$2 } = babel;
564
1153
  const operators = {
565
1154
  "<": ["<=", ">="],
566
1155
  "<=": ["<", ">"],
@@ -574,329 +1163,502 @@ const operators = {
574
1163
  function isEqualityOperator(operator) {
575
1164
  return Object.keys(operators).includes(operator);
576
1165
  }
577
- const equalityOperatorMutator = {
578
- name: "EqualityOperator",
579
- *mutate(path) {
580
- if (path.isBinaryExpression() && isEqualityOperator(path.node.operator)) for (const mutableOperator of operators[path.node.operator]) {
581
- const replacement = t$2.cloneNode(path.node, true);
582
- replacement.operator = mutableOperator;
583
- yield replacement;
584
- }
1166
+ const equalityOperatorMutator = function* (node, _context) {
1167
+ if (t$1.isBinaryExpression(node) && isEqualityOperator(node.operator)) for (const mutableOperator of operators[node.operator]) {
1168
+ const replacement = t$1.cloneNode(node, true);
1169
+ replacement.operator = mutableOperator;
1170
+ yield replacement;
585
1171
  }
586
1172
  };
587
- //#endregion
588
- //#region src/mutators/logical-operator-mutator.ts
589
1173
  const logicalOperatorReplacements = Object.freeze({
590
1174
  "&&": "||",
591
1175
  "||": "&&",
592
1176
  "??": "&&"
593
1177
  });
594
- const logicalOperatorMutator = {
595
- name: "LogicalOperator",
596
- *mutate(path) {
597
- if (path.isLogicalExpression() && isSupported$1(path.node.operator)) {
598
- const mutatedOperator = logicalOperatorReplacements[path.node.operator];
599
- const replacement = deepCloneNode(path.node);
600
- replacement.operator = mutatedOperator;
601
- yield replacement;
602
- }
1178
+ const logicalOperatorMutator = function* (node, _context) {
1179
+ if (babelTypes$2.isLogicalExpression(node) && isSupportedLogicalOperator(node.operator)) {
1180
+ const mutatedOperator = logicalOperatorReplacements[node.operator];
1181
+ const replacement = deepCloneNode(node);
1182
+ replacement.operator = mutatedOperator;
1183
+ yield replacement;
603
1184
  }
604
1185
  };
605
- function isSupported$1(operator) {
1186
+ function isSupportedLogicalOperator(operator) {
606
1187
  return Object.keys(logicalOperatorReplacements).includes(operator);
607
1188
  }
608
- //#endregion
609
- //#region src/mutators/method-expression-mutator.ts
610
- const { types: types$9 } = babel;
611
- const replacements = /* @__PURE__ */ new Map([
612
- ["charAt", null],
613
- ["endsWith", "startsWith"],
614
- ["every", "some"],
615
- ["filter", null],
616
- ["reverse", null],
617
- ["slice", null],
618
- ["sort", null],
619
- ["substr", null],
620
- ["substring", null],
621
- ["toLocaleLowerCase", "toLocaleUpperCase"],
622
- ["toLowerCase", "toUpperCase"],
623
- ["trim", null],
624
- ["trimEnd", "trimStart"],
625
- ["min", "max"],
626
- ["setDate", "setTime"],
627
- ["setFullYear", "setMonth"],
628
- ["setHours", "setMinutes"],
629
- ["setSeconds", "setMilliseconds"],
630
- ["setUTCDate", "setTime"],
631
- ["setUTCFullYear", "setUTCMonth"],
632
- ["setUTCHours", "setUTCMinutes"],
633
- ["setUTCSeconds", "setUTCMilliseconds"]
634
- ]);
635
- const noReverseRemplacements = ["getUTCDate", "setUTCDate"];
636
- for (const [key, value] of Array.from(replacements)) if (value && !noReverseRemplacements.includes(key)) replacements.set(value, key);
637
- const methodExpressionMutator = {
638
- name: "MethodExpression",
639
- *mutate(path) {
640
- if (!(path.isCallExpression() || path.isOptionalCallExpression())) return;
641
- const { callee } = path.node;
642
- if (!(types$9.isMemberExpression(callee) || types$9.isOptionalMemberExpression(callee)) || !types$9.isIdentifier(callee.property)) return;
643
- const newName = replacements.get(callee.property.name);
644
- if (newName === void 0) return;
645
- if (newName === null) {
646
- yield deepCloneNode(callee.object);
647
- return;
648
- }
649
- const nodeArguments = path.node.arguments.map((argumentNode) => deepCloneNode(argumentNode));
650
- const mutatedCallee = types$9.isMemberExpression(callee) ? types$9.memberExpression(deepCloneNode(callee.object), types$9.identifier(newName), false, callee.optional) : types$9.optionalMemberExpression(deepCloneNode(callee.object), types$9.identifier(newName), false, callee.optional);
651
- yield types$9.isCallExpression(path.node) ? types$9.callExpression(mutatedCallee, nodeArguments) : types$9.optionalCallExpression(mutatedCallee, nodeArguments, path.node.optional);
652
- }
1189
+ const baseReplacements = {
1190
+ charAt: null,
1191
+ endsWith: "startsWith",
1192
+ every: "some",
1193
+ filter: null,
1194
+ reverse: null,
1195
+ slice: null,
1196
+ sort: null,
1197
+ substr: null,
1198
+ substring: null,
1199
+ toLocaleLowerCase: "toLocaleUpperCase",
1200
+ toLowerCase: "toUpperCase",
1201
+ trim: null,
1202
+ trimEnd: "trimStart",
1203
+ min: "max",
1204
+ setDate: "setTime",
1205
+ setFullYear: "setMonth",
1206
+ setHours: "setMinutes",
1207
+ setSeconds: "setMilliseconds",
1208
+ setUTCDate: "setTime",
1209
+ setUTCFullYear: "setUTCMonth",
1210
+ setUTCHours: "setUTCMinutes",
1211
+ setUTCSeconds: "setUTCMilliseconds"
653
1212
  };
654
- //#endregion
655
- //#region src/mutators/object-literal-mutator.ts
656
- const { types: types$8 } = babel;
657
- const objectLiteralMutator = {
658
- name: "ObjectLiteral",
659
- *mutate(path) {
660
- if (path.isObjectExpression() && path.node.properties.length > 0) yield types$8.objectExpression([]);
661
- }
1213
+ const noReverseReplacements = ["getUTCDate", "setUTCDate"];
1214
+ const replacements = new Map(Object.entries(baseReplacements));
1215
+ for (const [key, value] of Object.entries(baseReplacements)) if (value !== null && !noReverseReplacements.includes(key)) replacements.set(value, key);
1216
+ const methodExpressionMutator = function* (node, _context) {
1217
+ if (!(babelTypes$2.isCallExpression(node) || babelTypes$2.isOptionalCallExpression(node))) return;
1218
+ const { callee } = node;
1219
+ if (!(babelTypes$2.isMemberExpression(callee) || babelTypes$2.isOptionalMemberExpression(callee)) || !babelTypes$2.isIdentifier(callee.property)) return;
1220
+ const newName = replacements.get(callee.property.name);
1221
+ if (newName === void 0) return;
1222
+ if (newName === null) {
1223
+ yield deepCloneNode(callee.object);
1224
+ return;
1225
+ }
1226
+ const nodeArguments = node.arguments.map((argumentNode) => deepCloneNode(argumentNode));
1227
+ let mutatedCallee;
1228
+ if (babelTypes$2.isMemberExpression(callee)) mutatedCallee = babelTypes$2.memberExpression(deepCloneNode(callee.object), babelTypes$2.identifier(newName), false, callee.optional);
1229
+ else mutatedCallee = babelTypes$2.optionalMemberExpression(deepCloneNode(callee.object), babelTypes$2.identifier(newName), false, callee.optional);
1230
+ if (babelTypes$2.isCallExpression(node)) yield babelTypes$2.callExpression(mutatedCallee, nodeArguments);
1231
+ else yield babelTypes$2.optionalCallExpression(mutatedCallee, nodeArguments, node.optional);
662
1232
  };
663
- //#endregion
664
- //#region src/mutators/optional-chaining-mutator.ts
665
- const { types: t$1 } = babel;
666
- /**
667
- * Mutates optional chaining operators
668
- * Note that the AST for optional chaining might not be what you expect. Nodes of type `OptionalMemberExpression` can be either optional or not-optional
669
- *
670
- * For example: In this expression: `foo?.bar.baz` the `.baz` member expression is of type `OptionalMemberExpression`, because it is part of an optional chain, but is is _not_ optional.
671
- * Only the `.bar` optional member expression is optional.
672
- *
673
- * @example
674
- * foo?.bar -> foo.bar
675
- * foo?.[1] -> foo[1]
676
- * foo?.() -> foo()
677
- */
678
- const optionalChainingMutator = {
679
- name: "OptionalChaining",
680
- *mutate(path) {
681
- if (path.isOptionalMemberExpression() && path.node.optional) yield t$1.optionalMemberExpression(t$1.cloneNode(path.node.object, true), t$1.cloneNode(path.node.property, true), path.node.computed, false);
682
- if (path.isOptionalCallExpression() && path.node.optional) yield t$1.optionalCallExpression(t$1.cloneNode(path.node.callee, true), path.node.arguments.map((arg) => t$1.cloneNode(arg, true)), false);
683
- }
1233
+ const objectLiteralMutator = function* (node, _context) {
1234
+ if (babelTypes$2.isObjectExpression(node) && node.properties.length > 0) yield babelTypes$2.objectExpression([]);
684
1235
  };
685
- //#endregion
686
- //#region src/mutators/regex-mutator.ts
687
- const { types: types$7 } = babel;
688
- /**
689
- * Checks that a string literal is an obvious regex string literal
690
- * @param path The string literal to checks
691
- * @example
692
- * new RegExp("\\d{4}");
693
- */
694
- function isObviousRegexString(path) {
695
- return path.parentPath.isNewExpression() && types$7.isIdentifier(path.parentPath.node.callee) && path.parentPath.node.callee.name === RegExp.name && path.parentPath.node.arguments[0] === path.node;
696
- }
697
- function getFlags(path) {
698
- if (types$7.isStringLiteral(path.node.arguments[1])) return path.node.arguments[1].value;
699
- }
700
- const weaponRegexOptions = { mutationLevels: [1] };
701
- const regexMutator = {
702
- name: "Regex",
703
- *mutate(path) {
704
- if (path.isRegExpLiteral()) for (const replacementPattern of mutatePattern(path.node.pattern, path.node.flags)) yield types$7.regExpLiteral(replacementPattern, path.node.flags);
705
- else if (path.isStringLiteral() && isObviousRegexString(path)) {
706
- const parentPath = path.parentPath;
707
- if (parentPath.isNewExpression()) {
708
- const flags = getFlags(parentPath);
709
- for (const replacementPattern of mutatePattern(path.node.value, flags)) yield types$7.stringLiteral(replacementPattern);
710
- }
1236
+ const optionalChainingMutator = function* (node, _context) {
1237
+ if (t$1.isOptionalMemberExpression(node) && node.optional) yield t$1.optionalMemberExpression(t$1.cloneNode(node.object, true), t$1.cloneNode(node.property, true), node.computed, false);
1238
+ if (t$1.isOptionalCallExpression(node) && node.optional) yield t$1.optionalCallExpression(t$1.cloneNode(node.callee, true), node.arguments.map((arg) => t$1.cloneNode(arg, true)), false);
1239
+ };
1240
+ const regexMutator = function* (node, context) {
1241
+ if (babelTypes$2.isRegExpLiteral(node)) for (const replacementPattern of mutateRegexPattern(node.pattern, node.flags)) yield babelTypes$2.regExpLiteral(replacementPattern, node.flags);
1242
+ else if (babelTypes$2.isStringLiteral(node) && isObviousRegexString(node, context)) {
1243
+ const parent = context.parent;
1244
+ if (parent !== void 0 && babelTypes$2.isNewExpression(parent)) {
1245
+ const flags = getFlags(parent);
1246
+ for (const replacementPattern of mutateRegexPattern(node.value, flags)) yield babelTypes$2.stringLiteral(replacementPattern);
711
1247
  }
712
1248
  }
713
1249
  };
714
- function mutatePattern(pattern, flags) {
715
- if (pattern.length) try {
716
- return weaponRegex.mutate(pattern, flags, weaponRegexOptions).map((mutant) => mutant.pattern);
717
- } catch (err) {
718
- const message = err instanceof Error ? err.message : typeof err === "string" ? err : JSON.stringify(err) ?? "Unknown error";
719
- console.error(`[RegexMutator]: The Regex parser of weapon-regex couldn't parse this regex pattern: "${pattern}". Please report this issue at https://github.com/stryker-mutator/weapon-regex/issues. Inner error: ${message}`);
720
- }
721
- return [];
722
- }
723
- //#endregion
724
- //#region src/mutators/string-literal-mutator.ts
725
- const { types: types$6 } = babel;
726
- const stringLiteralMutator = {
727
- name: "StringLiteral",
728
- *mutate(path) {
729
- if (path.isTemplateLiteral()) {
730
- const firstQuasi = path.node.quasis[0];
731
- if (firstQuasi === void 0) throw new Error("Template literal without quasis");
732
- const replacement = path.node.quasis.length === 1 && firstQuasi.value.raw.length === 0 ? "Stryker was here!" : "";
733
- yield types$6.templateLiteral([types$6.templateElement({ raw: replacement })], []);
734
- }
735
- if (path.isStringLiteral() && isValidParent(path)) yield types$6.stringLiteral(path.node.value.length === 0 ? "Stryker was here!" : "");
1250
+ function isObviousRegexString(node, context) {
1251
+ const parent = context.parent;
1252
+ if (parent === void 0 || !babelTypes$2.isNewExpression(parent) || !babelTypes$2.isIdentifier(parent.callee) || parent.callee.name !== RegExp.name) return false;
1253
+ return parent.arguments[0] === node;
1254
+ }
1255
+ function getFlags(node) {
1256
+ const secondArg = node.arguments[1];
1257
+ if (secondArg !== void 0 && babelTypes$2.isStringLiteral(secondArg)) return secondArg.value;
1258
+ }
1259
+ const stringLiteralMutator = function* (node, context) {
1260
+ if (babelTypes$2.isTemplateLiteral(node)) {
1261
+ const firstQuasi = node.quasis[0];
1262
+ if (firstQuasi === void 0) return;
1263
+ let replacement;
1264
+ if (node.quasis.length === 1 && firstQuasi.value.raw.length === 0) replacement = "Stryker was here!";
1265
+ else replacement = "";
1266
+ yield babelTypes$2.templateLiteral([babelTypes$2.templateElement({ raw: replacement })], []);
1267
+ }
1268
+ if (babelTypes$2.isStringLiteral(node) && isValidParent(node, context)) {
1269
+ let replacement;
1270
+ if (node.value.length === 0) replacement = "Stryker was here!";
1271
+ else replacement = "";
1272
+ yield babelTypes$2.stringLiteral(replacement);
736
1273
  }
737
1274
  };
738
- function isValidParent(child) {
739
- const { parent } = child;
1275
+ function isValidParent(child, context) {
1276
+ const parent = context.parent;
1277
+ if (parent === void 0) return true;
740
1278
  return !isImportExportRelated(parent) && !isJsxOrExpressionRelated(parent) && !isObjectOrClassPropertyKey(parent, child) && !isDisallowedCallExpression(parent);
741
1279
  }
742
1280
  function isImportExportRelated(parent) {
743
- return types$6.isImportDeclaration(parent) || types$6.isExportDeclaration(parent) || types$6.isImportOrExportDeclaration(parent) || types$6.isTSExternalModuleReference(parent);
1281
+ return babelTypes$2.isImportDeclaration(parent) || babelTypes$2.isExportDeclaration(parent) || babelTypes$2.isImportOrExportDeclaration(parent) || babelTypes$2.isTSExternalModuleReference(parent);
744
1282
  }
745
1283
  function isJsxOrExpressionRelated(parent) {
746
- return types$6.isJSXAttribute(parent) || types$6.isExpressionStatement(parent) || types$6.isTSLiteralType(parent) || types$6.isObjectMethod(parent);
1284
+ return babelTypes$2.isJSXAttribute(parent) || babelTypes$2.isExpressionStatement(parent) || babelTypes$2.isTSLiteralType(parent) || babelTypes$2.isObjectMethod(parent);
747
1285
  }
748
1286
  function isObjectOrClassPropertyKey(parent, child) {
749
- return types$6.isObjectProperty(parent) && parent.key === child.node || types$6.isClassProperty(parent) && parent.key === child.node;
1287
+ return babelTypes$2.isObjectProperty(parent) && parent.key === child || babelTypes$2.isClassProperty(parent) && parent.key === child;
750
1288
  }
751
1289
  function isDisallowedCallExpression(parent) {
752
1290
  return isRequireCall(parent) || isSymbolCall(parent) || isImportCall(parent);
753
1291
  }
754
1292
  function isRequireCall(parent) {
755
- return types$6.isCallExpression(parent) && types$6.isIdentifier(parent.callee, { name: "require" });
1293
+ return babelTypes$2.isCallExpression(parent) && babelTypes$2.isIdentifier(parent.callee, { name: "require" });
756
1294
  }
757
1295
  function isSymbolCall(parent) {
758
- return types$6.isCallExpression(parent) && types$6.isIdentifier(parent.callee, { name: "Symbol" });
1296
+ return babelTypes$2.isCallExpression(parent) && babelTypes$2.isIdentifier(parent.callee, { name: "Symbol" });
759
1297
  }
760
1298
  function isImportCall(parent) {
761
- return types$6.isCallExpression(parent) && types$6.isImport(parent.callee);
1299
+ return babelTypes$2.isCallExpression(parent) && babelTypes$2.isImport(parent.callee);
762
1300
  }
763
- //#endregion
764
- //#region src/mutators/unary-operator-mutator.ts
765
- const { types: types$5 } = babel;
766
- var UnaryOperator = /* @__PURE__ */ function(UnaryOperator) {
767
- UnaryOperator["+"] = "-";
768
- UnaryOperator["-"] = "+";
769
- UnaryOperator["~"] = "";
770
- return UnaryOperator;
771
- }(UnaryOperator || {});
772
- const unaryOperatorMutator = {
773
- name: "UnaryOperator",
774
- *mutate(path) {
775
- if (path.isUnaryExpression() && isSupported(path.node.operator) && path.node.prefix) {
776
- const mutatedOperator = UnaryOperator[path.node.operator];
777
- yield isPlusOrMinus(mutatedOperator) ? types$5.unaryExpression(mutatedOperator, deepCloneNode(path.node.argument)) : deepCloneNode(path.node.argument);
778
- }
1301
+ const UnaryOperator = {
1302
+ "+": "-",
1303
+ "-": "+",
1304
+ "~": ""
1305
+ };
1306
+ const unaryOperatorMutator = function* (node, _context) {
1307
+ if (babelTypes$2.isUnaryExpression(node) && isSupportedUnaryOperator(node.operator) && node.prefix) {
1308
+ const mutatedOperator = UnaryOperator[node.operator];
1309
+ let replacement;
1310
+ if (isPlusOrMinus(mutatedOperator)) replacement = babelTypes$2.unaryExpression(mutatedOperator, deepCloneNode(node.argument));
1311
+ else replacement = deepCloneNode(node.argument);
1312
+ yield replacement;
779
1313
  }
780
1314
  };
781
- function isSupported(operator) {
1315
+ function isSupportedUnaryOperator(operator) {
782
1316
  return Object.keys(UnaryOperator).includes(operator);
783
1317
  }
784
1318
  function isPlusOrMinus(operator) {
785
1319
  return operator === "-" || operator === "+";
786
1320
  }
1321
+ const UpdateOperators = {
1322
+ "++": "--",
1323
+ "--": "++"
1324
+ };
1325
+ const updateOperatorMutator = function* (node, _context) {
1326
+ if (babelTypes$2.isUpdateExpression(node)) yield babelTypes$2.updateExpression(UpdateOperators[node.operator], deepCloneNode(node.argument), node.prefix);
1327
+ };
1328
+ /**
1329
+ * Every mutator this instrumenter can apply, named explicitly.
1330
+ *
1331
+ * This list is deliberately hand-written rather than self-registering. A
1332
+ * registry populated by import side effects — each mutator module calling
1333
+ * `registerMutator(self)` at module scope — makes the mutant population depend
1334
+ * on which imports were evaluated: import order decides the order, a bundler
1335
+ * that judges a side-effect-only import unused drops a mutator entirely, and
1336
+ * anything reading the array before the last import finished sees a short list.
1337
+ * Every one of those failures REMOVES mutants, which RAISES the mutation score,
1338
+ * so the tool reports a better number for doing less work and nothing anywhere
1339
+ * says so.
1340
+ *
1341
+ * Naming each mutator here costs one line when a mutator is added and makes
1342
+ * that line a compile-checked import instead of a runtime effect.
1343
+ */
1344
+ const allMutators = Object.freeze({
1345
+ ArithmeticOperator: arithmeticOperatorMutator,
1346
+ ArrayDeclaration: arrayDeclarationMutator,
1347
+ ArrowFunction: arrowFunctionMutator,
1348
+ AssignmentOperator: assignmentOperatorMutator,
1349
+ BlockStatement: blockStatementMutator,
1350
+ BooleanLiteral: booleanLiteralMutator,
1351
+ ConditionalExpression: conditionalExpressionMutator,
1352
+ EqualityOperator: equalityOperatorMutator,
1353
+ LogicalOperator: logicalOperatorMutator,
1354
+ MethodExpression: methodExpressionMutator,
1355
+ ObjectLiteral: objectLiteralMutator,
1356
+ OptionalChaining: optionalChainingMutator,
1357
+ Regex: regexMutator,
1358
+ StringLiteral: stringLiteralMutator,
1359
+ UnaryOperator: unaryOperatorMutator,
1360
+ UpdateOperator: updateOperatorMutator
1361
+ });
1362
+ S.TaggedError("@systemfsoftware/stryker-js-instrumenter/PlacementFailed")("PlacementFailed", {
1363
+ fileName: S.String,
1364
+ placerName: S.String,
1365
+ mutantIds: S.Array(S.String),
1366
+ cause: S.Defect()
1367
+ });
1368
+ S.TaggedError("@systemfsoftware/stryker-js-instrumenter/TransformFailed")("TransformFailed", {
1369
+ fileName: S.String,
1370
+ cause: S.Defect()
1371
+ });
787
1372
  //#endregion
788
- //#region src/mutators/update-operator-mutator.ts
789
- const { types: types$4 } = babel;
790
- var UpdateOperators = /* @__PURE__ */ function(UpdateOperators) {
791
- UpdateOperators["++"] = "--";
792
- UpdateOperators["--"] = "++";
793
- return UpdateOperators;
794
- }(UpdateOperators || {});
795
- //#endregion
796
- //#region src/mutators/mutate.ts
797
- const allMutators = [
798
- arithmeticOperatorMutator,
799
- arrayDeclarationMutator,
800
- arrowFunctionMutator,
801
- blockStatementMutator,
802
- booleanLiteralMutator,
803
- conditionalExpressionMutator,
804
- equalityOperatorMutator,
805
- logicalOperatorMutator,
806
- methodExpressionMutator,
807
- objectLiteralMutator,
808
- stringLiteralMutator,
809
- unaryOperatorMutator,
810
- {
811
- name: "UpdateOperator",
812
- *mutate(path) {
813
- if (path.isUpdateExpression()) yield types$4.updateExpression(UpdateOperators[path.node.operator], deepCloneNode(path.node.argument), path.node.prefix);
1373
+ //#region src/Transformer.ts
1374
+ /**
1375
+ * Transformer — traverses ASTs, asks mutators for mutants and placers to instrument them.
1376
+ */
1377
+ const { types: babelTypes$1 } = babel;
1378
+ const t = babelTypes$1;
1379
+ const STRYKER_NAMESPACE_HELPER = "stryNS_9fa48";
1380
+ const COVER_MUTANT_HELPER = "stryCov_9fa48";
1381
+ const IS_MUTANT_ACTIVE_HELPER = "stryMutAct_9fa48";
1382
+ function createMutantCollector() {
1383
+ return [];
1384
+ }
1385
+ function collect(collector, fileName, original, mutable, offset = {
1386
+ line: 0,
1387
+ column: 0
1388
+ }) {
1389
+ const mutant = createMutant(collector.length.toString(), fileName, original, mutable, offset);
1390
+ collector.push(mutant);
1391
+ return mutant;
1392
+ }
1393
+ function hasPlacedMutants(collector, fileName) {
1394
+ return collector.some((mutant) => mutant.fileName === fileName && mutant.ignoreReason === void 0);
1395
+ }
1396
+ const WILDCARD = "all";
1397
+ const DEFAULT_REASON = "Ignored using a comment";
1398
+ const strykerCommentDirectiveRegex = /^\s?Stryker (disable|restore)(?: (next-line))? ([a-zA-Z, ]+)(?::(.+)?)?/;
1399
+ const rootRule = { kind: "Root" };
1400
+ function findIgnoreReason(rule, mutatorName, line) {
1401
+ const lower = mutatorName.toLowerCase();
1402
+ let current = rule;
1403
+ for (;;) switch (current.kind) {
1404
+ case "Root": return;
1405
+ case "Ignore": {
1406
+ const lineMatches = current.line === void 0 || current.line === line;
1407
+ const mutatorMatches = current.mutatorNames.includes(lower) || current.mutatorNames.includes(WILDCARD);
1408
+ if (lineMatches && mutatorMatches) return current.ignoreReason;
1409
+ current = current.previous;
1410
+ break;
1411
+ }
1412
+ case "Restore": {
1413
+ const lineMatches = current.line === void 0 || current.line === line;
1414
+ const mutatorMatches = current.mutatorNames.includes(lower) || current.mutatorNames.includes(WILDCARD);
1415
+ if (lineMatches && mutatorMatches) return;
1416
+ current = current.previous;
1417
+ break;
814
1418
  }
815
- },
816
- regexMutator,
817
- optionalChainingMutator,
818
- assignmentOperatorMutator
819
- ];
820
- //#endregion
821
- //#region src/transformers/mutant-collector.ts
822
- var MutantCollector = class {
823
- _mutants = [];
824
- get mutants() {
825
- return this._mutants;
826
1419
  }
827
- /**
828
- * Adds mutants to the internal mutant list.
829
- * @param fileName file name that houses the mutant
830
- * @param original The node to mutate
831
- * @param mutables the named node mutation to be added
832
- * @param contextPath the context where these mutants are found and should be placed as close by as possible
833
- * @param offset offset of mutant nodes
834
- * @returns The mutant (for testability)
835
- */
836
- collect(fileName, original, mutable, offset = {
837
- line: 0,
838
- column: 0
839
- }) {
840
- const mutant = new Mutant$1(this._mutants.length.toString(), fileName, original, mutable, offset);
841
- this._mutants.push(mutant);
842
- return mutant;
1420
+ }
1421
+ function processStrykerDirectives(rule, node, allMutatorNames, originFileName) {
1422
+ const leadingComments = node.leadingComments;
1423
+ if (!leadingComments) return {
1424
+ rule,
1425
+ warnings: []
1426
+ };
1427
+ let current = rule;
1428
+ const warnings = [];
1429
+ for (const comment of leadingComments) {
1430
+ const matchResult = strykerCommentDirectiveRegex.exec(comment.value);
1431
+ if (!matchResult) continue;
1432
+ const directiveType = matchResult[1];
1433
+ const scope = matchResult[2];
1434
+ const mutators = matchResult[3];
1435
+ const optionalReason = matchResult[4];
1436
+ if (directiveType === void 0 || mutators === void 0) throw new Error("Stryker directive without directive type or mutators");
1437
+ let mutatorNames = mutators.split(",").map((mutator) => mutator.trim());
1438
+ for (const mutator of mutatorNames) {
1439
+ if (mutator === WILDCARD) continue;
1440
+ if (!allMutatorNames.includes(mutator.toLowerCase())) {
1441
+ const commentLoc = comment.loc;
1442
+ if (commentLoc == null) throw new Error("Comment without location");
1443
+ let directiveLabel;
1444
+ if (scope !== void 0 && scope !== "") directiveLabel = `${directiveType} ${scope}`;
1445
+ else directiveLabel = directiveType;
1446
+ warnings.push(`Unused 'Stryker ${directiveLabel}' directive. Mutator with name '${mutator}' not found. Directive found at: ${originFileName}:${commentLoc.start.line}:${commentLoc.start.column}.`);
1447
+ }
1448
+ }
1449
+ mutatorNames = mutatorNames.map((mutator) => mutator.toLowerCase());
1450
+ const reason = (optionalReason ?? DEFAULT_REASON).trim();
1451
+ current = applyDirective(current, directiveType, scope, mutatorNames, reason, node.loc);
843
1452
  }
844
- hasPlacedMutants(fileName) {
845
- return this.mutants.some((mutant) => mutant.fileName === fileName && !mutant.ignoreReason);
1453
+ return {
1454
+ rule: current,
1455
+ warnings
1456
+ };
1457
+ }
1458
+ function applyDirective(rule, directiveType, scope, mutatorNames, reason, loc) {
1459
+ switch (directiveType) {
1460
+ case "disable": return applyDisable(rule, scope, mutatorNames, reason, loc);
1461
+ case "restore": return applyRestore(rule, scope, mutatorNames, loc);
1462
+ default: return rule;
1463
+ }
1464
+ }
1465
+ function applyDisable(rule, scope, mutatorNames, reason, loc) {
1466
+ switch (scope) {
1467
+ case "next-line": return {
1468
+ kind: "Ignore",
1469
+ mutatorNames,
1470
+ line: getLine(loc),
1471
+ ignoreReason: reason,
1472
+ previous: rule
1473
+ };
1474
+ case void 0:
1475
+ default: return {
1476
+ kind: "Ignore",
1477
+ mutatorNames,
1478
+ line: void 0,
1479
+ ignoreReason: reason,
1480
+ previous: rule
1481
+ };
846
1482
  }
847
- };
848
- //#endregion
849
- //#region src/mutant-placers/mutant-placer.ts
1483
+ }
1484
+ function applyRestore(rule, scope, mutatorNames, loc) {
1485
+ switch (scope) {
1486
+ case "next-line": return {
1487
+ kind: "Restore",
1488
+ mutatorNames,
1489
+ line: getLine(loc),
1490
+ previous: rule
1491
+ };
1492
+ case void 0:
1493
+ default: return {
1494
+ kind: "Restore",
1495
+ mutatorNames,
1496
+ line: void 0,
1497
+ previous: rule
1498
+ };
1499
+ }
1500
+ }
1501
+ function getLine(loc) {
1502
+ if (loc == null) throw new Error("Babel node without location");
1503
+ return loc.start.line;
1504
+ }
1505
+ function toIgnorerPath(path) {
1506
+ let parentResult = null;
1507
+ if (path.parentPath !== null) parentResult = toIgnorerPath(path.parentPath);
1508
+ return {
1509
+ node: path.node,
1510
+ parentPath: parentResult,
1511
+ isObjectExpression() {
1512
+ return path.isObjectExpression();
1513
+ },
1514
+ isCallExpression() {
1515
+ return path.isCallExpression() || path.isOptionalCallExpression();
1516
+ },
1517
+ isClassProperty() {
1518
+ return path.isClassProperty();
1519
+ },
1520
+ isClassPrivateProperty() {
1521
+ return path.isClassPrivateProperty();
1522
+ },
1523
+ isClassAccessorProperty() {
1524
+ return path.isClassAccessorProperty();
1525
+ }
1526
+ };
1527
+ }
850
1528
  /**
851
- * Narrows an applied mutant to the node kind a placer emits. `applied()` hands
852
- * back a plain node — whether it fits this position is the placer's claim, and
853
- * `canPlace` is what established it, so a mismatch here means the placer was
854
- * handed a mutant it never accepted.
1529
+ * Wraps a parsed AST the way Babel's own pipeline does, so
1530
+ * `NodePath#buildCodeFrameError` can render a code frame
1531
+ * (https://github.com/babel/babel/issues/11889). Without the wrapper a
1532
+ * placement failure reports no source context.
855
1533
  */
856
- function nodeOfKind(mutant, node, isKind, kind) {
857
- if (!isKind(node)) throw new Error(`Cannot place mutant ${mutant.id}: expected ${kind}, got ${node.type}`);
858
- return node;
1534
+ function createBabelFile(filename, code, ast) {
1535
+ return new File({ filename }, {
1536
+ code,
1537
+ ast
1538
+ });
1539
+ }
1540
+ function isTypeNode(path) {
1541
+ return path.isTypeAnnotation() || flowTypeAnnotationNodeTypes.includes(path.node.type) || tsTypeAnnotationNodeTypes.includes(path.node.type) || isDeclareVariableStatement(path) || isDeclareModule(path);
859
1542
  }
860
- //#endregion
861
- //#region src/mutant-placers/expression-mutant-placer.ts
862
- const { types: types$3 } = babel;
863
1543
  /**
864
- * Will set the identifier of anonymous function expressions if is located in a variable declaration.
865
- * Will treat input as readonly. Returns undefined if not needed.
1544
+ * Determines whether or not it is a declare variable statement node.
866
1545
  * @example
867
- * const a = function() { }
868
- * becomes
869
- * const a = function a() {}
1546
+ * declare const foo: 'foo';
870
1547
  */
871
- function classOrFunctionExpressionNamedIfNeeded(path) {
872
- if ((path.isFunctionExpression() || path.isClassExpression()) && !path.node.id) {
873
- if (path.parentPath.isVariableDeclarator() && types$3.isIdentifier(path.parentPath.node.id)) {
874
- path.node.id = path.parentPath.node.id;
875
- return path.node;
876
- } else if (path.parentPath.isObjectProperty() && types$3.isIdentifier(path.parentPath.node.key) && path.getStatementParent()?.isVariableDeclaration()) {
877
- path.node.id = path.parentPath.node.key;
878
- return path.node;
879
- }
880
- }
1548
+ function isDeclareVariableStatement(path) {
1549
+ return path.isVariableDeclaration() && path.node.declare === true;
881
1550
  }
882
1551
  /**
883
- * Will set the identifier of anonymous arrow function expressions if is located in a variable declaration.
884
- * Will treat input as readonly. Returns undefined if not needed.
1552
+ * Determines whether or not a node is a string literal that is the name of a module.
885
1553
  * @example
886
- * const a = () => { }
887
- * becomes
888
- * const a = (() => { const a = () => {}; return a; })()
1554
+ * declare module "express" {};
889
1555
  */
890
- function arrowFunctionExpressionNamedIfNeeded(path) {
891
- if (path.isArrowFunctionExpression() && path.parentPath.isVariableDeclarator() && types$3.isIdentifier(path.parentPath.node.id)) return types$3.callExpression(types$3.arrowFunctionExpression([], types$3.blockStatement([types$3.variableDeclaration("const", [types$3.variableDeclarator(path.parentPath.node.id, path.node)]), types$3.returnStatement(path.parentPath.node.id)])), []);
1556
+ function isDeclareModule(path) {
1557
+ return path.isTSModuleDeclaration() && (path.node.declare ?? false);
892
1558
  }
893
- function nameIfAnonymous(path) {
894
- return classOrFunctionExpressionNamedIfNeeded(path) ?? arrowFunctionExpressionNamedIfNeeded(path) ?? path.node;
1559
+ const tsTypeAnnotationNodeTypes = Object.freeze([
1560
+ "TSAsExpression",
1561
+ "TSInterfaceDeclaration",
1562
+ "TSTypeAnnotation",
1563
+ "TSTypeAliasDeclaration",
1564
+ "TSEnumDeclaration",
1565
+ "TSDeclareFunction",
1566
+ "TSTypeParameterInstantiation",
1567
+ "TSTypeParameterDeclaration"
1568
+ ]);
1569
+ const flowTypeAnnotationNodeTypes = Object.freeze([
1570
+ "DeclareClass",
1571
+ "DeclareFunction",
1572
+ "DeclareInterface",
1573
+ "DeclareModule",
1574
+ "DeclareModuleExports",
1575
+ "DeclareTypeAlias",
1576
+ "DeclareOpaqueType",
1577
+ "DeclareVariable",
1578
+ "DeclareExportDeclaration",
1579
+ "DeclareExportAllDeclaration",
1580
+ "InterfaceDeclaration",
1581
+ "OpaqueType",
1582
+ "TypeAlias",
1583
+ "InterfaceDeclaration"
1584
+ ]);
1585
+ function isImportDeclaration(path) {
1586
+ return babelTypes$1.isTSImportEqualsDeclaration(path.node) || path.isImportDeclaration();
895
1587
  }
896
- function isMemberOrCallOrNonNullExpression(path) {
897
- return isCallExpression(path) || isMemberOrNonNullExpression(path);
1588
+ /**
1589
+ * returns syntax for `global.activeMutant === $mutantId`
1590
+ * @param mutantId The id of the mutant to switch
1591
+ */
1592
+ function mutantTestExpression(mutantId) {
1593
+ return babelTypes$1.callExpression(babelTypes$1.identifier(IS_MUTANT_ACTIVE_HELPER), [babelTypes$1.stringLiteral(mutantId)]);
898
1594
  }
899
- function isMemberOrNonNullExpression(path) {
1595
+ /**
1596
+ * Returns a sequence of mutation coverage counters with an optional last expression.
1597
+ *
1598
+ * @example (global.__coverMutant__(0, 1), 40 + 2)
1599
+ * @param mutants The mutants for which covering syntax needs to be generated
1600
+ * @param targetExpression The original expression
1601
+ */
1602
+ function mutationCoverageSequenceExpression(mutants, targetExpression) {
1603
+ const mutantIds = [...mutants].map((mutant) => babelTypes$1.stringLiteral(mutant.id));
1604
+ const sequence = [babelTypes$1.callExpression(babelTypes$1.identifier(COVER_MUTANT_HELPER), mutantIds)];
1605
+ if (targetExpression) sequence.push(targetExpression);
1606
+ return babelTypes$1.sequenceExpression(sequence);
1607
+ }
1608
+ /**
1609
+ * Narrows an applied mutant to the node kind a placer emits. `applied()` hands
1610
+ * back a plain node — whether it fits this position is the placer's claim, and
1611
+ * `canPlace` is what established it, so a mismatch here means the placer was
1612
+ * handed a mutant it never accepted.
1613
+ */
1614
+ function nodeOfKind(mutant, node, isKind, kind) {
1615
+ if (!isKind(node)) throw new Error(`Cannot place mutant ${mutant.id}: expected ${kind}, got ${node.type}`);
1616
+ return node;
1617
+ }
1618
+ function throwPlacementError(error, nodePath, placer, mutants, fileName) {
1619
+ const location = `${path$1.relative(process.cwd(), fileName)}:${nodePath.node.loc?.start.line}:${nodePath.node.loc?.start.column}`;
1620
+ const message = `${placer.name} could not place mutants with type(s): "${new Intl.ListFormat("en").format(mutants.map((mutant) => mutant.mutatorName))}"`;
1621
+ const errorMessage = `${location} ${message}. Either remove this file from the list of files to be mutated, or exclude the mutator (using ${propertyPath()("mutator", "excludedMutations")}). Please report this issue at ${strykerReportBugUrl(message)}. Original error: ${error.stack}`;
1622
+ let builtError = new Error(errorMessage);
1623
+ try {
1624
+ builtError = nodePath.buildCodeFrameError(errorMessage);
1625
+ } catch {}
1626
+ throw builtError;
1627
+ }
1628
+ /**
1629
+ * Will set the identifier of anonymous function expressions if is located in a variable declaration.
1630
+ * Will treat input as readonly. Returns undefined if not needed.
1631
+ * @example
1632
+ * const a = function() { }
1633
+ * becomes
1634
+ * const a = function a() {}
1635
+ */
1636
+ function classOrFunctionExpressionNamedIfNeeded(path) {
1637
+ if ((path.isFunctionExpression() || path.isClassExpression()) && !path.node.id) {
1638
+ if (path.parentPath.isVariableDeclarator() && babelTypes$1.isIdentifier(path.parentPath.node.id)) {
1639
+ path.node.id = path.parentPath.node.id;
1640
+ return path.node;
1641
+ } else if (path.parentPath.isObjectProperty() && babelTypes$1.isIdentifier(path.parentPath.node.key) && path.getStatementParent()?.isVariableDeclaration() === true) return path.node;
1642
+ }
1643
+ }
1644
+ /**
1645
+ * Will set the identifier of anonymous arrow function expressions if is located in a variable declaration.
1646
+ * Will treat input as readonly. Returns undefined if not needed.
1647
+ * @example
1648
+ * const a = () => { }
1649
+ * becomes
1650
+ * const a = (() => { const a = () => {}; return a; })()
1651
+ */
1652
+ function arrowFunctionExpressionNamedIfNeeded(path) {
1653
+ if (path.isArrowFunctionExpression() && path.parentPath.isVariableDeclarator() && babelTypes$1.isIdentifier(path.parentPath.node.id)) return babelTypes$1.callExpression(babelTypes$1.arrowFunctionExpression([], babelTypes$1.blockStatement([babelTypes$1.variableDeclaration("const", [babelTypes$1.variableDeclarator(path.parentPath.node.id, path.node)]), babelTypes$1.returnStatement(path.parentPath.node.id)])), []);
1654
+ }
1655
+ function nameIfAnonymous(path) {
1656
+ return classOrFunctionExpressionNamedIfNeeded(path) ?? arrowFunctionExpressionNamedIfNeeded(path) ?? path.node;
1657
+ }
1658
+ function isMemberOrCallOrNonNullExpression(path) {
1659
+ return isCallExpression(path) || isMemberOrNonNullExpression(path);
1660
+ }
1661
+ function isMemberOrNonNullExpression(path) {
900
1662
  return isMemberExpression(path) || path.isTSNonNullExpression();
901
1663
  }
902
1664
  function isMemberExpression(path) {
@@ -954,258 +1716,295 @@ function isValidExpression(path) {
954
1716
  return parent.isAssignmentExpression() && parent.node.left === path.node;
955
1717
  }
956
1718
  }
957
- /**
958
- * Places the mutants with a conditional expression: `global.activeMutant === 1? mutatedCode : originalCode`;
959
- */
960
- const expressionMutantPlacer = {
961
- name: "expressionMutantPlacer",
962
- canPlace(path) {
963
- return path.isExpression() && isValidExpression(path);
964
- },
965
- place(path, appliedMutants) {
966
- let expression = nameIfAnonymous(path);
967
- expression = mutationCoverageSequenceExpression(appliedMutants.keys(), expression);
968
- for (const [mutant, appliedMutant] of appliedMutants) expression = types$3.conditionalExpression(mutantTestExpression(mutant.id), nodeOfKind(mutant, appliedMutant, types$3.isExpression, "an expression"), expression);
969
- path.replaceWith(expression);
970
- }
971
- };
972
- //#endregion
973
- //#region src/mutant-placers/statement-mutant-placer.ts
974
- const { types: t } = babel;
975
- /**
976
- * Mutant placer that places mutants in statements that allow it.
977
- * It uses an `if` statement to do so
978
- */
979
- const statementMutantPlacer = {
980
- name: "statementMutantPlacer",
981
- canPlace(path) {
982
- return path.isStatement();
1719
+ const allMutantPlacers = Object.freeze([
1720
+ {
1721
+ name: "expressionMutantPlacer",
1722
+ canPlace(path) {
1723
+ return path.isExpression() && isValidExpression(path);
1724
+ },
1725
+ place(path, appliedMutants) {
1726
+ let expression = nameIfAnonymous(path);
1727
+ expression = mutationCoverageSequenceExpression(appliedMutants.keys(), expression);
1728
+ for (const [mutant, appliedMutant] of appliedMutants) expression = babelTypes$1.conditionalExpression(mutantTestExpression(mutant.id), nodeOfKind(mutant, appliedMutant, babelTypes$1.isExpression, "an expression"), expression);
1729
+ path.replaceWith(expression);
1730
+ }
983
1731
  },
984
- place(path, appliedMutants) {
985
- let statement = t.blockStatement([t.expressionStatement(mutationCoverageSequenceExpression(appliedMutants.keys())), ...path.isBlockStatement() ? path.node.body : [path.node]]);
986
- for (const [mutant, appliedMutant] of appliedMutants) statement = t.ifStatement(mutantTestExpression(mutant.id), t.blockStatement([nodeOfKind(mutant, appliedMutant, t.isStatement, "a statement")]), statement);
987
- path.replaceWith(path.isBlockStatement() ? t.blockStatement([statement]) : statement);
988
- }
989
- };
990
- //#endregion
991
- //#region src/mutant-placers/switch-case-mutant-placer.ts
992
- /**
993
- * Places the mutants with consequent of a SwitchCase node. Uses an if-statement to do so.
994
- * @example
995
- * case 'foo':
996
- * if (stryMutAct_9fa48(0)) {} else {
997
- * stryCov_9fa48(0);
998
- * console.log('bar');
999
- * break;
1000
- * }
1001
- */
1002
- const switchCaseMutantPlacer = {
1003
- name: "switchCaseMutantPlacer",
1004
- canPlace(path) {
1005
- return path.isSwitchCase();
1732
+ {
1733
+ name: "statementMutantPlacer",
1734
+ canPlace(path) {
1735
+ return path.isStatement();
1736
+ },
1737
+ place(path, appliedMutants) {
1738
+ const bodyStatements = [t.expressionStatement(mutationCoverageSequenceExpression(appliedMutants.keys()))];
1739
+ if (path.isBlockStatement()) bodyStatements.push(...path.node.body);
1740
+ else bodyStatements.push(path.node);
1741
+ let statement = t.blockStatement(bodyStatements);
1742
+ for (const [mutant, appliedMutant] of appliedMutants) statement = t.ifStatement(mutantTestExpression(mutant.id), t.blockStatement([nodeOfKind(mutant, appliedMutant, t.isStatement, "a statement")]), statement);
1743
+ if (path.isBlockStatement()) path.replaceWith(t.blockStatement([statement]));
1744
+ else path.replaceWith(statement);
1745
+ }
1006
1746
  },
1007
- place(path, appliedMutants) {
1008
- let consequence = babel.types.blockStatement([babel.types.expressionStatement(mutationCoverageSequenceExpression(appliedMutants.keys())), ...path.node.consequent]);
1009
- for (const [mutant, appliedMutant] of appliedMutants) {
1010
- const switchCase = nodeOfKind(mutant, appliedMutant, babel.types.isSwitchCase, "a switch case");
1011
- consequence = babel.types.ifStatement(mutantTestExpression(mutant.id), babel.types.blockStatement(switchCase.consequent), consequence);
1747
+ {
1748
+ name: "switchCaseMutantPlacer",
1749
+ canPlace(path) {
1750
+ return path.isSwitchCase();
1751
+ },
1752
+ place(path, appliedMutants) {
1753
+ let consequence = babel.types.blockStatement([babel.types.expressionStatement(mutationCoverageSequenceExpression(appliedMutants.keys())), ...path.node.consequent]);
1754
+ for (const [mutant, appliedMutant] of appliedMutants) {
1755
+ const switchCase = nodeOfKind(mutant, appliedMutant, babel.types.isSwitchCase, "a switch case");
1756
+ consequence = babel.types.ifStatement(mutantTestExpression(mutant.id), babel.types.blockStatement(switchCase.consequent), consequence);
1757
+ }
1758
+ path.replaceWith(babel.types.switchCase(path.node.test, [consequence]));
1012
1759
  }
1013
- path.replaceWith(babel.types.switchCase(path.node.test, [consequence]));
1014
1760
  }
1015
- };
1016
- //#endregion
1017
- //#region src/mutant-placers/throw-placement-error.ts
1018
- function throwPlacementError(error, nodePath, placer, mutants, fileName) {
1019
- const location = `${path.relative(process.cwd(), fileName)}:${nodePath.node.loc?.start.line}:${nodePath.node.loc?.start.column}`;
1020
- const message = `${placer.name} could not place mutants with type(s): "${new Intl.ListFormat("en").format(mutants.map((mutant) => mutant.mutatorName))}"`;
1021
- const errorMessage = `${location} ${message}. Either remove this file from the list of files to be mutated, or exclude the mutator (using ${propertyPath()("mutator", "excludedMutations")}). Please report this issue at https://github.com/stryker-mutator/stryker-js/issues/new?assignees=&labels=%F0%9F%90%9B+Bug&template=bug_report.md&title=${encodeURIComponent(message)}. Original error: ${error.stack}`;
1022
- let builtError = new Error(errorMessage);
1023
- try {
1024
- builtError = nodePath.buildCodeFrameError(errorMessage);
1025
- } catch {}
1026
- throw builtError;
1027
- }
1028
- //#endregion
1029
- //#region src/mutant-placers/index.ts
1030
- const allMutantPlacers = Object.freeze([
1031
- expressionMutantPlacer,
1032
- statementMutantPlacer,
1033
- switchCaseMutantPlacer
1034
1761
  ]);
1035
- //#endregion
1036
- //#region src/util/babel-file.ts
1762
+ const ANGULAR_SIGNAL_IO_FUNCTIONS = Object.freeze([
1763
+ "input",
1764
+ "model",
1765
+ "output"
1766
+ ]);
1767
+ const ANGULAR_SIGNAL_QUERY_FUNCTIONS = Object.freeze([
1768
+ "contentChild",
1769
+ "contentChildren",
1770
+ "viewChild",
1771
+ "viewChildren"
1772
+ ]);
1773
+ const INPUT_MODEL_OUTPUT_CONFIG_MSG = "Angular signal based input, model and output functions configuration object cannot be mutated as that causes issues with the Angular compiler.";
1774
+ const SIGNAL_QUERY_OPTIONS_MSG = "Angular signal query options object cannot be mutated as that causes issues with the Angular compiler.";
1775
+ function shouldIgnore(path) {
1776
+ for (let current = path; current; current = current.parentPath) {
1777
+ if (isInputModelOrOutputConfigurationObject(current)) return Option.some(INPUT_MODEL_OUTPUT_CONFIG_MSG);
1778
+ if (isSignalQueryOptionsObject(current)) return Option.some(SIGNAL_QUERY_OPTIONS_MSG);
1779
+ }
1780
+ return Option.none();
1781
+ }
1782
+ const angularIgnorer = { shouldIgnore };
1783
+ function isClassFieldLike(path) {
1784
+ return path.isClassProperty() || path.isClassPrivateProperty() || path.isClassAccessorProperty();
1785
+ }
1786
+ function isInputModelOrOutputConfigurationObject(path) {
1787
+ const parent = path.parentPath;
1788
+ const grandParent = parent?.parentPath;
1789
+ if (!path.isObjectExpression() || parent === null || parent === void 0 || !parent.isCallExpression() || grandParent === null || grandParent === void 0 || !grandParent.isClassProperty()) return false;
1790
+ const callExpression = parent;
1791
+ const objectExpression = path;
1792
+ const callNode = callExpression.node;
1793
+ if (!Predicate.hasProperty(callNode, "callee") || !Predicate.hasProperty(callNode, "arguments")) return false;
1794
+ const callee = callNode["callee"];
1795
+ const args = callNode["arguments"];
1796
+ if (!Array.isArray(args)) return false;
1797
+ const isRequiredSignalIOFunction = isMemberExpressionWithIdentifier(callee, ANGULAR_SIGNAL_IO_FUNCTIONS, "required");
1798
+ const isSignalIOFunction = isIdentifierIn(callee, ANGULAR_SIGNAL_IO_FUNCTIONS);
1799
+ const isOutput = isIdentifierWithName(callee, "output");
1800
+ if (isRequiredSignalIOFunction || isOutput) return args.length >= 1 && args[0] === objectExpression.node;
1801
+ if (isSignalIOFunction) return args.length >= 2 && args[1] === objectExpression.node;
1802
+ return false;
1803
+ }
1804
+ function isSignalQueryOptionsObject(path) {
1805
+ const parent = path.parentPath;
1806
+ const grandParent = parent?.parentPath;
1807
+ if (!path.isObjectExpression() || parent === null || parent === void 0 || !parent.isCallExpression() || grandParent === null || grandParent === void 0 || !isClassFieldLike(grandParent)) return false;
1808
+ const callExpression = parent;
1809
+ const objectExpression = path;
1810
+ const callNode = callExpression.node;
1811
+ if (!Predicate.hasProperty(callNode, "callee") || !Predicate.hasProperty(callNode, "arguments")) return false;
1812
+ const callee = callNode["callee"];
1813
+ const args = callNode["arguments"];
1814
+ if (!Array.isArray(args)) return false;
1815
+ const isQueryFn = isIdentifierIn(callee, ANGULAR_SIGNAL_QUERY_FUNCTIONS);
1816
+ const isRequiredQueryFn = isMemberExpressionWithIdentifier(callee, ANGULAR_SIGNAL_QUERY_FUNCTIONS, "required");
1817
+ if (!isQueryFn && !isRequiredQueryFn) return false;
1818
+ return args.length >= 2 && args[1] === objectExpression.node;
1819
+ }
1820
+ function isIdentifierWithName(node, name) {
1821
+ return Predicate.hasProperty(node, "type") && node["type"] === "Identifier" && Predicate.hasProperty(node, "name") && node["name"] === name;
1822
+ }
1823
+ function isIdentifierIn(node, names) {
1824
+ return Predicate.hasProperty(node, "type") && node["type"] === "Identifier" && Predicate.hasProperty(node, "name") && typeof node["name"] === "string" && names.includes(node["name"]);
1825
+ }
1826
+ function isMemberExpressionWithIdentifier(node, objectNames, propertyName) {
1827
+ return Predicate.hasProperty(node, "type") && node["type"] === "MemberExpression" && Predicate.hasProperty(node, "object") && Predicate.hasProperty(node, "property") && isIdentifierIn(node["object"], objectNames) && isIdentifierWithName(node["property"], propertyName);
1828
+ }
1829
+ const strykerPlugins = [];
1830
+ const frameworkPluginsFileUrl = import.meta.url;
1037
1831
  /**
1038
- * Wraps a parsed AST the way Babel's own pipeline does, so
1039
- * `NodePath#buildCodeFrameError` can render a code frame
1040
- * (https://github.com/babel/babel/issues/11889). Without the wrapper a
1041
- * placement failure reports no source context.
1832
+ * Returns syntax for the header if JS/TS files
1042
1833
  */
1043
- function createBabelFile(filename, code, ast) {
1044
- return new File({ filename }, {
1045
- code,
1046
- ast
1047
- });
1048
- }
1049
- //#endregion
1050
- //#region src/transformers/directive-bookkeeper.ts
1051
- const WILDCARD = "all";
1052
- const DEFAULT_REASON = "Ignored using a comment";
1053
- var IgnoreRule = class {
1054
- mutatorNames;
1055
- line;
1056
- ignoreReason;
1057
- previousRule;
1058
- constructor(mutatorNames, line, ignoreReason, previousRule) {
1059
- this.mutatorNames = mutatorNames;
1060
- this.line = line;
1061
- this.ignoreReason = ignoreReason;
1062
- this.previousRule = previousRule;
1063
- }
1064
- matches(mutatorName, line) {
1065
- const lineMatches = () => this.line === void 0 || this.line === line;
1066
- const mutatorMatches = () => this.mutatorNames.includes(mutatorName) || this.mutatorNames.includes(WILDCARD);
1067
- return lineMatches() && mutatorMatches();
1068
- }
1069
- findIgnoreReason(mutatorName, line) {
1070
- if (this.matches(mutatorName, line)) return this.ignoreReason;
1071
- return this.previousRule.findIgnoreReason(mutatorName, line);
1072
- }
1834
+ const parsedInstrumentationHeader = babel.parse(`// @ts-nocheck
1835
+ var ${STRYKER_NAMESPACE_HELPER} = function(){
1836
+ var g = typeof globalThis === 'object' && globalThis && globalThis.Math === Math && globalThis || new Function("return this")();
1837
+ var ns = g.${INSTRUMENTER_CONSTANTS.NAMESPACE} || (g.${INSTRUMENTER_CONSTANTS.NAMESPACE} = {});
1838
+ if (ns.${INSTRUMENTER_CONSTANTS.ACTIVE_MUTANT} === undefined && g.process && g.process.env && g.process.env.${INSTRUMENTER_CONSTANTS.ACTIVE_MUTANT_ENV_VARIABLE}) {
1839
+ ns.${INSTRUMENTER_CONSTANTS.ACTIVE_MUTANT} = g.process.env.${INSTRUMENTER_CONSTANTS.ACTIVE_MUTANT_ENV_VARIABLE};
1840
+ }
1841
+ function retrieveNS(){
1842
+ return ns;
1843
+ }
1844
+ ${STRYKER_NAMESPACE_HELPER} = retrieveNS;
1845
+ return retrieveNS();
1073
1846
  };
1074
- var RestoreRule = class extends IgnoreRule {
1075
- constructor(mutatorNames, line, previousRule) {
1076
- super(mutatorNames, line, void 0, previousRule);
1077
- }
1847
+ ${STRYKER_NAMESPACE_HELPER}();
1848
+
1849
+ var ${COVER_MUTANT_HELPER} = function() {
1850
+ var ns = ${STRYKER_NAMESPACE_HELPER}();
1851
+ var cov = ns.${INSTRUMENTER_CONSTANTS.MUTATION_COVERAGE_OBJECT} || (ns.${INSTRUMENTER_CONSTANTS.MUTATION_COVERAGE_OBJECT} = { static: {}, perTest: {} });
1852
+ function cover() {
1853
+ var c = cov.static;
1854
+ if (ns.${INSTRUMENTER_CONSTANTS.CURRENT_TEST_ID}) {
1855
+ c = cov.perTest[ns.${INSTRUMENTER_CONSTANTS.CURRENT_TEST_ID}] = cov.perTest[ns.${INSTRUMENTER_CONSTANTS.CURRENT_TEST_ID}] || {};
1856
+ }
1857
+ var a = arguments;
1858
+ for(var i=0; i < a.length; i++){
1859
+ c[a[i]] = (c[a[i]] || 0) + 1;
1860
+ }
1861
+ }
1862
+ ${COVER_MUTANT_HELPER} = cover;
1863
+ cover.apply(null, arguments);
1078
1864
  };
1079
- const rootRule = { findIgnoreReason() {} };
1080
- /**
1081
- * Responsible for the bookkeeping of "// Stryker" directives like "disable" and "restore".
1082
- */
1083
- var DirectiveBookkeeper = class {
1084
- logger;
1085
- allMutators;
1086
- originFileName;
1087
- strykerCommentDirectiveRegex = /^\s?Stryker (disable|restore)(?: (next-line))? ([a-zA-Z, ]+)(?::(.+)?)?/;
1088
- currentIgnoreRule = rootRule;
1089
- allMutatorNames;
1090
- constructor(logger, allMutators, originFileName) {
1091
- this.logger = logger;
1092
- this.allMutators = allMutators;
1093
- this.originFileName = originFileName;
1094
- this.allMutatorNames = this.allMutators.map((x) => x.name.toLowerCase());
1095
- }
1096
- processStrykerDirectives({ loc, leadingComments }) {
1097
- if (!leadingComments) return;
1098
- for (const comment of leadingComments) {
1099
- const matchResult = this.strykerCommentDirectiveRegex.exec(comment.value);
1100
- if (!matchResult) continue;
1101
- const directiveType = matchResult[1];
1102
- const scope = matchResult[2];
1103
- const mutators = matchResult[3];
1104
- const optionalReason = matchResult[4];
1105
- if (directiveType === void 0 || mutators === void 0) throw new Error("Stryker directive without directive type or mutators");
1106
- let mutatorNames = mutators.split(",").map((mutator) => mutator.trim());
1107
- this.warnAboutUnusedDirective(mutatorNames, directiveType, scope, comment);
1108
- mutatorNames = mutatorNames.map((mutator) => mutator.toLowerCase());
1109
- const reason = (optionalReason ?? DEFAULT_REASON).trim();
1110
- this.applyDirective(directiveType, scope, mutatorNames, reason, loc);
1111
- }
1865
+ var ${IS_MUTANT_ACTIVE_HELPER} = function(id) {
1866
+ var ns = ${STRYKER_NAMESPACE_HELPER}();
1867
+ function isActive(id) {
1868
+ if (ns.${INSTRUMENTER_CONSTANTS.ACTIVE_MUTANT} === id) {
1869
+ if (ns.${INSTRUMENTER_CONSTANTS.HIT_COUNT} !== void 0 && ++ns.${INSTRUMENTER_CONSTANTS.HIT_COUNT} > ns.${INSTRUMENTER_CONSTANTS.HIT_LIMIT}) {
1870
+ throw new Error('Stryker: Hit count limit reached (' + ns.${INSTRUMENTER_CONSTANTS.HIT_COUNT} + ')');
1871
+ }
1872
+ return true;
1873
+ }
1874
+ return false;
1875
+ }
1876
+ ${IS_MUTANT_ACTIVE_HELPER} = isActive;
1877
+ return isActive(id);
1878
+ }`, {
1879
+ configFile: false,
1880
+ browserslistConfigFile: false,
1881
+ env: { targets: {} }
1882
+ });
1883
+ if (!babelTypes$1.isFile(parsedInstrumentationHeader)) throw new Error("Instrumentation header parsed as non-File");
1884
+ const instrumentationBabelHeader = parsedInstrumentationHeader.program.body;
1885
+ deepFreeze(instrumentationBabelHeader);
1886
+ function placeHeaderIfNeeded(mutantCollector, originFileName, options, root) {
1887
+ if (hasPlacedMutants(mutantCollector, originFileName) && options.noHeader !== true) placeHeader(root);
1888
+ }
1889
+ function placeHeader(root) {
1890
+ let header = instrumentationBabelHeader;
1891
+ const leadingComments = root.program.body[0]?.leadingComments;
1892
+ if (Array.isArray(leadingComments)) {
1893
+ const firstHeader = instrumentationBabelHeader[0];
1894
+ if (firstHeader === void 0) throw new Error("Instrumentation header is empty");
1895
+ const cloned = babelTypes$1.cloneNode(firstHeader, true, false);
1896
+ cloned.leadingComments = leadingComments;
1897
+ header = [cloned, ...instrumentationBabelHeader.slice(1)];
1112
1898
  }
1113
- applyDirective(directiveType, scope, mutatorNames, reason, loc) {
1114
- switch (directiveType) {
1115
- case "disable":
1116
- this.applyDisable(scope, mutatorNames, reason, loc);
1117
- break;
1118
- case "restore": this.applyRestore(scope, mutatorNames, loc);
1899
+ root.program.body.unshift(...header);
1900
+ }
1901
+ function deepFreeze(value) {
1902
+ if (value !== null && typeof value === "object") {
1903
+ if (Array.isArray(value)) {
1904
+ for (const item of value) deepFreeze(item);
1905
+ return Object.freeze(value);
1119
1906
  }
1120
- }
1121
- applyDisable(scope, mutatorNames, reason, loc) {
1122
- switch (scope) {
1123
- case "next-line":
1124
- this.currentIgnoreRule = new IgnoreRule(mutatorNames, this.getLine(loc), reason, this.currentIgnoreRule);
1125
- break;
1126
- case void 0:
1127
- default: this.currentIgnoreRule = new IgnoreRule(mutatorNames, void 0, reason, this.currentIgnoreRule);
1907
+ if (value instanceof RegExp) return Object.freeze(value);
1908
+ if (value instanceof Map) {
1909
+ for (const [k, v] of value.entries()) {
1910
+ deepFreeze(k);
1911
+ deepFreeze(v);
1912
+ }
1913
+ return Object.freeze(value);
1128
1914
  }
1129
- }
1130
- applyRestore(scope, mutatorNames, loc) {
1131
- switch (scope) {
1132
- case "next-line":
1133
- this.currentIgnoreRule = new RestoreRule(mutatorNames, this.getLine(loc), this.currentIgnoreRule);
1134
- break;
1135
- case void 0:
1136
- default: this.currentIgnoreRule = new RestoreRule(mutatorNames, void 0, this.currentIgnoreRule);
1915
+ if (value instanceof Set) {
1916
+ for (const v of value.values()) deepFreeze(v);
1917
+ return Object.freeze(value);
1137
1918
  }
1919
+ for (const v of Object.values(value)) deepFreeze(v);
1920
+ return Object.freeze(value);
1138
1921
  }
1139
- getLine(loc) {
1140
- if (loc === void 0 || loc === null || loc.start === null || loc.start === void 0) throw new Error("Babel node without location");
1141
- return loc.start.line;
1142
- }
1143
- findIgnoreReason(line, mutatorName) {
1144
- mutatorName = mutatorName.toLowerCase();
1145
- return this.currentIgnoreRule.findIgnoreReason(mutatorName, line);
1146
- }
1147
- warnAboutUnusedDirective(mutators, directiveType, scope, comment) {
1148
- for (const mutator of mutators) {
1149
- if (mutator === WILDCARD) continue;
1150
- if (!this.allMutatorNames.includes(mutator.toLowerCase())) {
1151
- const commentLoc = comment.loc;
1152
- if (commentLoc === void 0 || commentLoc === null || commentLoc.start === null || commentLoc.start === void 0) throw new Error("Comment without location");
1153
- this.logger.warn(`Unused 'Stryker ${scope ? directiveType + " " + scope : directiveType}' directive. Mutator with name '${mutator}' not found. Directive found at: ${this.originFileName}:${commentLoc.start.line}:${commentLoc.start.column}.`);
1154
- }
1155
- }
1922
+ return value;
1923
+ }
1924
+ function transform(ast, mutantCollector, transformerContext) {
1925
+ const context = {
1926
+ ...transformerContext,
1927
+ transform
1928
+ };
1929
+ switch (ast.format) {
1930
+ case "html": return transformHtml(ast, mutantCollector, context);
1931
+ case "js":
1932
+ case "ts":
1933
+ case "tsx": return transformBabel(ast, mutantCollector, context);
1934
+ case "svelte": return transformSvelte(ast, mutantCollector, context);
1156
1935
  }
1936
+ }
1937
+ const transformHtml = ({ root }, mutantCollector, context) => {
1938
+ const warnings = [];
1939
+ root.scripts.forEach((ast) => {
1940
+ warnings.push(...context.transform(ast, mutantCollector, context));
1941
+ });
1942
+ return warnings;
1157
1943
  };
1158
- //#endregion
1159
- //#region src/transformers/ignorer-bookkeeper.ts
1160
- /**
1161
- * Responsible for keeping track of the active ignore message and node using the configured ignore-plugins.
1162
- */
1163
- var IgnorerBookkeeper = class {
1164
- ignorers;
1165
- activeIgnored;
1166
- get currentIgnoreMessage() {
1167
- return this.activeIgnored?.message;
1168
- }
1169
- constructor(ignorers) {
1170
- this.ignorers = ignorers;
1171
- }
1172
- enterNode(path) {
1173
- if (!this.activeIgnored) this.ignorers.forEach((ignorer) => {
1174
- const message = ignorer.shouldIgnore(path);
1175
- if (message) this.activeIgnored = {
1176
- node: path.node,
1177
- message
1944
+ const moduleScript = `<script context="module">
1945
+ \n<\/script>\n`;
1946
+ const transformSvelte = (svelte, mutantCollector, context) => {
1947
+ const warnings = [];
1948
+ const { root, originFileName } = svelte;
1949
+ [root.moduleScript, ...root.additionalScripts].filter(Predicate.isNotNullish).forEach((script) => {
1950
+ warnings.push(...context.transform(script.ast, mutantCollector, {
1951
+ ...context,
1952
+ options: {
1953
+ ...context.options,
1954
+ noHeader: true
1955
+ }
1956
+ }));
1957
+ });
1958
+ if (hasPlacedMutants(mutantCollector, originFileName)) {
1959
+ if (!root.moduleScript) {
1960
+ root.moduleScript = {
1961
+ ast: {
1962
+ format: "js",
1963
+ root: babelTypes$1.file(babelTypes$1.program([])),
1964
+ rawContent: "",
1965
+ originFileName
1966
+ },
1967
+ range: {
1968
+ start: 26,
1969
+ end: 26
1970
+ },
1971
+ isExpression: false
1178
1972
  };
1179
- });
1180
- }
1181
- leaveNode(path) {
1182
- if (this.activeIgnored?.node === path.node) this.activeIgnored = void 0;
1973
+ svelte.rawContent = `${moduleScript}${svelte.rawContent}`;
1974
+ svelte.root.additionalScripts.forEach((script) => {
1975
+ script.range.start += moduleScript.length;
1976
+ script.range.end += moduleScript.length;
1977
+ });
1978
+ }
1979
+ placeHeader(root.moduleScript.ast.root);
1183
1980
  }
1981
+ return warnings;
1184
1982
  };
1185
- //#endregion
1186
- //#region src/transformers/babel-transformer.ts
1187
1983
  const { traverse: traverse$1 } = babel;
1188
- const transformBabel = ({ root, originFileName, rawContent, offset }, mutantCollector, { options, mutateDescription, logger }, mutators = allMutators, mutantPlacers = allMutantPlacers) => {
1984
+ const transformBabel = ({ root, originFileName, rawContent, offset }, mutantCollector, { options, mutateDescription }, mutators = allMutators, mutantPlacers = allMutantPlacers) => {
1189
1985
  const file = createBabelFile(originFileName, rawContent, root);
1190
1986
  const placementMap = /* @__PURE__ */ new Map();
1191
- const directiveBookkeeper = new DirectiveBookkeeper(logger, mutators, originFileName);
1192
- const ignorerBookkeeper = new IgnorerBookkeeper(options.ignorers);
1987
+ let directiveRule = rootRule;
1988
+ const mutatorEntries = Object.entries(mutators);
1989
+ const allMutatorNames = mutatorEntries.map(([name]) => name.toLowerCase());
1990
+ const warnings = [];
1193
1991
  traverse$1(file.ast, {
1194
1992
  enter(path) {
1195
- directiveBookkeeper.processStrykerDirectives(path.node);
1993
+ const result = processStrykerDirectives(directiveRule, path.node, allMutatorNames, originFileName);
1994
+ directiveRule = result.rule;
1995
+ warnings.push(...result.warnings);
1196
1996
  if (shouldSkip(path)) path.skip();
1197
1997
  else {
1198
- ignorerBookkeeper.enterNode(path);
1199
1998
  addToPlacementMapIfPossible(path);
1200
1999
  if (shouldMutate(path)) {
1201
2000
  const mutantsToPlace = collectMutants(path);
1202
- if (mutantsToPlace.length) {
2001
+ if (mutantsToPlace.length > 0) {
1203
2002
  const placementPath = path.find((ancestor) => placementMap.has(ancestor.node));
1204
2003
  if (placementPath) {
1205
2004
  const placement = placementMap.get(placementPath.node);
1206
2005
  if (placement === void 0) throw new Error("Placement not found for node");
1207
2006
  const { appliedMutants } = placement;
1208
- mutantsToPlace.forEach((mutant) => appliedMutants.set(mutant, mutant.applied(placementPath.node)));
2007
+ mutantsToPlace.forEach((mutant) => appliedMutants.set(mutant, applyMutant(mutant, placementPath.node)));
1209
2008
  } else throw new Error(`Mutants cannot be placed. This shouldn't happen! Unplaced mutants: ${JSON.stringify(mutantsToPlace, null, 2)}`);
1210
2009
  }
1211
2010
  }
@@ -1213,669 +2012,139 @@ const transformBabel = ({ root, originFileName, rawContent, offset }, mutantColl
1213
2012
  },
1214
2013
  exit(path) {
1215
2014
  placeMutantsIfNeeded(path);
1216
- ignorerBookkeeper.leaveNode(path);
1217
2015
  }
1218
2016
  });
1219
2017
  placeHeaderIfNeeded(mutantCollector, originFileName, options, root);
1220
- /**
1221
- * If this node can be used to place mutants on, add to the placement map
1222
- */
2018
+ return warnings;
1223
2019
  function addToPlacementMapIfPossible(path) {
1224
2020
  const placer = mutantPlacers.find((p) => p.canPlace(path));
1225
- if (placer) placementMap.set(path.node, {
2021
+ if (placer !== void 0) placementMap.set(path.node, {
1226
2022
  appliedMutants: /* @__PURE__ */ new Map(),
1227
2023
  placer
1228
2024
  });
1229
2025
  }
1230
- /**
1231
- * Don't traverse import declarations, decorators and nodes that don't have overlap with the selected mutation ranges
1232
- */
1233
2026
  function shouldSkip(path) {
1234
- return isTypeNode(path) || isImportDeclaration(path) || path.isDecorator() || !mutateDescription || Array.isArray(mutateDescription) && mutateDescription.every((range) => !locationOverlaps(range, getNodeLocation(path.node)));
2027
+ return isTypeNode(path) || isImportDeclaration(path) || path.isDecorator() || mutateDescription === false || Array.isArray(mutateDescription) && mutateDescription.every((range) => {
2028
+ return !locationOverlaps(range, getNodeLocation(path.node));
2029
+ });
1235
2030
  }
1236
2031
  function shouldMutate(path) {
1237
- return mutateDescription === true || Array.isArray(mutateDescription) && mutateDescription.some((range) => locationIncluded(range, getNodeLocation(path.node)));
2032
+ return mutateDescription === true || Array.isArray(mutateDescription) && mutateDescription.some((range) => {
2033
+ return locationIncluded(range, getNodeLocation(path.node));
2034
+ });
1238
2035
  }
1239
- /**
1240
- * Place mutants that are assigned to the current node path (on exit)
1241
- */
1242
2036
  function placeMutantsIfNeeded(path) {
1243
2037
  const mutantsPlacement = placementMap.get(path.node);
1244
- if (mutantsPlacement?.appliedMutants.size) try {
2038
+ if (mutantsPlacement !== void 0 && mutantsPlacement.appliedMutants.size > 0) try {
1245
2039
  mutantsPlacement.placer.place(path, mutantsPlacement.appliedMutants);
1246
2040
  path.skip();
1247
2041
  } catch (error) {
1248
2042
  throwPlacementError(toError(error), path, mutantsPlacement.placer, [...mutantsPlacement.appliedMutants.keys()], originFileName);
1249
2043
  }
1250
2044
  }
1251
- /**
1252
- * Collect the mutants for the current node and return the non-ignored.
1253
- */
2045
+ function ignoreMessageFor(path) {
2046
+ const view = toIgnorerPath(path);
2047
+ for (const ignorer of options.ignorers) {
2048
+ const result = ignorer.shouldIgnore(view);
2049
+ if (Option.isSome(result)) return result.value;
2050
+ }
2051
+ }
1254
2052
  function collectMutants(path) {
1255
- return [...mutate(path)].map((mutable) => mutantCollector.collect(originFileName, path.node, mutable, offset)).filter((mutant) => !mutant.ignoreReason);
2053
+ return [...mutate(path)].map((mutable) => collect(mutantCollector, originFileName, path.node, mutable, offset)).filter((mutant) => mutant.ignoreReason === void 0);
1256
2054
  }
1257
- /**
1258
- * Generate mutants for the current node.
1259
- * @yields {Mutable} A mutable describing the mutant to be placed
1260
- */
1261
- function* mutate(node) {
1262
- for (const mutator of mutators) for (const replacement of mutator.mutate(node)) {
1263
- const ignoreReason = directiveBookkeeper.findIgnoreReason(getNodeLocation(node.node).start.line, mutator.name) ?? findExcludedMutatorIgnoreReason(mutator.name) ?? ignorerBookkeeper.currentIgnoreMessage;
1264
- yield {
2055
+ function* mutate(path) {
2056
+ const context = toMutatorContext(path);
2057
+ for (const [mutatorName, mutate] of mutatorEntries) for (const replacement of mutate(path.node, context)) {
2058
+ const ignoreReason = findIgnoreReason(directiveRule, mutatorName, getNodeLocation(path.node).start.line) ?? findExcludedMutatorIgnoreReason(mutatorName) ?? ignoreMessageFor(path);
2059
+ const mutableEntry = {
1265
2060
  replacement,
1266
- mutatorName: mutator.name,
1267
- ...ignoreReason === void 0 ? {} : { ignoreReason }
1268
- };
1269
- }
1270
- function findExcludedMutatorIgnoreReason(mutatorName) {
1271
- if (options.excludedMutations.includes(mutatorName)) return `Ignored because of excluded mutation "${mutatorName}"`;
1272
- else return;
1273
- }
1274
- }
1275
- };
1276
- function getNodeLocation(node) {
1277
- const loc = node.loc;
1278
- if (loc === void 0 || loc === null || loc.start === null || loc.start === void 0 || loc.end === null || loc.end === void 0) throw new Error("Babel node without location");
1279
- return loc;
1280
- }
1281
- function toError(value) {
1282
- if (value instanceof Error) return value;
1283
- return new Error("Unexpected error", { cause: value });
1284
- }
1285
- //#endregion
1286
- //#region src/transformers/html-transformer.ts
1287
- const transformHtml = ({ root }, mutantCollector, context) => {
1288
- root.scripts.forEach((ast) => {
1289
- context.transform(ast, mutantCollector, context);
1290
- });
1291
- };
1292
- //#endregion
1293
- //#region src/transformers/svelte-transformer.ts
1294
- const moduleScript = `<script context="module">
1295
- \n<\/script>\n`;
1296
- const transformSvelte = (svelte, mutantCollector, context) => {
1297
- const { root, originFileName } = svelte;
1298
- [root.moduleScript, ...root.additionalScripts].filter(notEmpty).forEach((script) => {
1299
- context.transform(script.ast, mutantCollector, {
1300
- ...context,
1301
- options: {
1302
- ...context.options,
1303
- noHeader: true
1304
- }
1305
- });
1306
- });
1307
- if (mutantCollector.hasPlacedMutants(originFileName)) {
1308
- if (!root.moduleScript) {
1309
- root.moduleScript = {
1310
- ast: {
1311
- format: "js",
1312
- root: types.file(types.program([])),
1313
- rawContent: "",
1314
- originFileName
1315
- },
1316
- range: {
1317
- start: 26,
1318
- end: 26
1319
- },
1320
- isExpression: false
1321
- };
1322
- svelte.rawContent = `${moduleScript}${svelte.rawContent}`;
1323
- svelte.root.additionalScripts.forEach((script) => {
1324
- script.range.start += moduleScript.length;
1325
- script.range.end += moduleScript.length;
1326
- });
1327
- }
1328
- placeHeader(root.moduleScript.ast.root);
1329
- }
1330
- };
1331
- //#endregion
1332
- //#region src/transformers/transformer.ts
1333
- /**
1334
- * Transform the AST by generating mutants and placing them in the AST.
1335
- * Supports all AST formats supported by Stryker.
1336
- * @param ast The Abstract Syntax Tree
1337
- * @param mutantCollector the mutant collector that will be used to register and administer mutants
1338
- * @param transformerContext the options used during transforming
1339
- */
1340
- function transform(ast, mutantCollector, transformerContext) {
1341
- const context = {
1342
- ...transformerContext,
1343
- transform
1344
- };
1345
- switch (ast.format) {
1346
- case "html":
1347
- transformHtml(ast, mutantCollector, context);
1348
- break;
1349
- case "js":
1350
- case "ts":
1351
- case "tsx":
1352
- transformBabel(ast, mutantCollector, context);
1353
- break;
1354
- case "svelte": transformSvelte(ast, mutantCollector, context);
1355
- }
1356
- }
1357
- //#endregion
1358
- //#region src/util/syntax-helpers.ts
1359
- const STRYKER_NAMESPACE_HELPER = "stryNS_9fa48";
1360
- const COVER_MUTANT_HELPER = "stryCov_9fa48";
1361
- const IS_MUTANT_ACTIVE_HELPER = "stryMutAct_9fa48";
1362
- const { types: types$2, traverse } = babel;
1363
- /**
1364
- * Returns syntax for the header if JS/TS files
1365
- */
1366
- const parsedInstrumentationHeader = babel.parse(`function ${STRYKER_NAMESPACE_HELPER}(){
1367
- var g = typeof globalThis === 'object' && globalThis && globalThis.Math === Math && globalThis || new Function("return this")();
1368
- var ns = g.${ID.NAMESPACE} || (g.${ID.NAMESPACE} = {});
1369
- if (ns.${ID.ACTIVE_MUTANT} === undefined && g.process && g.process.env && g.process.env.${ID.ACTIVE_MUTANT_ENV_VARIABLE}) {
1370
- ns.${ID.ACTIVE_MUTANT} = g.process.env.${ID.ACTIVE_MUTANT_ENV_VARIABLE};
1371
- }
1372
- function retrieveNS(){
1373
- return ns;
1374
- }
1375
- ${STRYKER_NAMESPACE_HELPER} = retrieveNS;
1376
- return retrieveNS();
1377
- }
1378
- ${STRYKER_NAMESPACE_HELPER}();
1379
-
1380
- function ${COVER_MUTANT_HELPER}() {
1381
- var ns = ${STRYKER_NAMESPACE_HELPER}();
1382
- var cov = ns.${ID.MUTATION_COVERAGE_OBJECT} || (ns.${ID.MUTATION_COVERAGE_OBJECT} = { static: {}, perTest: {} });
1383
- function cover() {
1384
- var c = cov.static;
1385
- if (ns.${ID.CURRENT_TEST_ID}) {
1386
- c = cov.perTest[ns.${ID.CURRENT_TEST_ID}] = cov.perTest[ns.${ID.CURRENT_TEST_ID}] || {};
1387
- }
1388
- var a = arguments;
1389
- for(var i=0; i < a.length; i++){
1390
- c[a[i]] = (c[a[i]] || 0) + 1;
1391
- }
1392
- }
1393
- ${COVER_MUTANT_HELPER} = cover;
1394
- cover.apply(null, arguments);
1395
- }
1396
- function ${IS_MUTANT_ACTIVE_HELPER}(id) {
1397
- var ns = ${STRYKER_NAMESPACE_HELPER}();
1398
- function isActive(id) {
1399
- if (ns.${ID.ACTIVE_MUTANT} === id) {
1400
- if (ns.${ID.HIT_COUNT} !== void 0 && ++ns.${ID.HIT_COUNT} > ns.${ID.HIT_LIMIT}) {
1401
- throw new Error('Stryker: Hit count limit reached (' + ns.${ID.HIT_COUNT} + ')');
1402
- }
1403
- return true;
1404
- }
1405
- return false;
1406
- }
1407
- ${IS_MUTANT_ACTIVE_HELPER} = isActive;
1408
- return isActive(id);
1409
- }`, {
1410
- configFile: false,
1411
- browserslistConfigFile: false,
1412
- env: { targets: {} }
1413
- });
1414
- if (!types$2.isFile(parsedInstrumentationHeader)) throw new Error("Instrumentation header parsed as non-File");
1415
- const instrumentationBabelHeader = parsedInstrumentationHeader.program.body;
1416
- deepFreeze(instrumentationBabelHeader);
1417
- /**
1418
- * returns syntax for `global.activeMutant === $mutantId`
1419
- * @param mutantId The id of the mutant to switch
1420
- */
1421
- function mutantTestExpression(mutantId) {
1422
- return types$2.callExpression(types$2.identifier(IS_MUTANT_ACTIVE_HELPER), [types$2.stringLiteral(mutantId)]);
1423
- }
1424
- function eqLocation(a, b) {
1425
- function eqPosition(start, end) {
1426
- return start.column === end.column && start.line === end.line;
1427
- }
1428
- return eqPosition(a.start, b.start) && eqPosition(a.end, b.end);
1429
- }
1430
- function eqNode(a, b) {
1431
- return a.type === b.type && !!a.loc && !!b.loc && eqLocation(a.loc, b.loc);
1432
- }
1433
- /**
1434
- * Returns a sequence of mutation coverage counters with an optional last expression.
1435
- *
1436
- * @example (global.__coverMutant__(0, 1), 40 + 2)
1437
- * @param mutants The mutants for which covering syntax needs to be generated
1438
- * @param targetExpression The original expression
1439
- */
1440
- function mutationCoverageSequenceExpression(mutants, targetExpression) {
1441
- const mutantIds = [...mutants].map((mutant) => types$2.stringLiteral(mutant.id));
1442
- const sequence = [types$2.callExpression(types$2.identifier(COVER_MUTANT_HELPER), mutantIds)];
1443
- if (targetExpression) sequence.push(targetExpression);
1444
- return types$2.sequenceExpression(sequence);
1445
- }
1446
- function isTypeNode(path) {
1447
- return path.isTypeAnnotation() || flowTypeAnnotationNodeTypes.includes(path.node.type) || tsTypeAnnotationNodeTypes.includes(path.node.type) || isDeclareVariableStatement(path) || isDeclareModule(path);
1448
- }
1449
- /**
1450
- * Determines whether or not it is a declare variable statement node.
1451
- * @example
1452
- * declare const foo: 'foo';
1453
- */
1454
- function isDeclareVariableStatement(path) {
1455
- return path.isVariableDeclaration() && path.node.declare === true;
1456
- }
1457
- /**
1458
- * Determines whether or not a node is a string literal that is the name of a module.
1459
- * @example
1460
- * declare module "express" {};
1461
- */
1462
- function isDeclareModule(path) {
1463
- return path.isTSModuleDeclaration() && (path.node.declare ?? false);
1464
- }
1465
- const tsTypeAnnotationNodeTypes = Object.freeze([
1466
- "TSAsExpression",
1467
- "TSInterfaceDeclaration",
1468
- "TSTypeAnnotation",
1469
- "TSTypeAliasDeclaration",
1470
- "TSEnumDeclaration",
1471
- "TSDeclareFunction",
1472
- "TSTypeParameterInstantiation",
1473
- "TSTypeParameterDeclaration"
1474
- ]);
1475
- const flowTypeAnnotationNodeTypes = Object.freeze([
1476
- "DeclareClass",
1477
- "DeclareFunction",
1478
- "DeclareInterface",
1479
- "DeclareModule",
1480
- "DeclareModuleExports",
1481
- "DeclareTypeAlias",
1482
- "DeclareOpaqueType",
1483
- "DeclareVariable",
1484
- "DeclareExportDeclaration",
1485
- "DeclareExportAllDeclaration",
1486
- "InterfaceDeclaration",
1487
- "OpaqueType",
1488
- "TypeAlias",
1489
- "InterfaceDeclaration"
1490
- ]);
1491
- function isImportDeclaration(path) {
1492
- return types$2.isTSImportEqualsDeclaration(path.node) || path.isImportDeclaration();
1493
- }
1494
- /**
1495
- * Determines if a location (needle) is included in an other location (haystack)
1496
- * @param haystack The range to look in
1497
- * @param needle the range to search for
1498
- */
1499
- function locationIncluded(haystack, needle) {
1500
- const startIncluded = haystack.start.line < needle.start.line || haystack.start.line === needle.start.line && haystack.start.column <= needle.start.column;
1501
- const endIncluded = haystack.end.line > needle.end.line || haystack.end.line === needle.end.line && haystack.end.column >= needle.end.column;
1502
- return startIncluded && endIncluded;
1503
- }
1504
- /**
1505
- * Determines if two locations overlap with each other
1506
- */
1507
- function locationOverlaps(a, b) {
1508
- const startIncluded = a.start.line < b.end.line || a.start.line === b.end.line && a.start.column <= b.end.column;
1509
- const endIncluded = a.end.line > b.start.line || a.end.line === b.start.line && a.end.column >= b.start.column;
1510
- return startIncluded && endIncluded;
1511
- }
1512
- /**
1513
- * Helper for `types.cloneNode(node, deep: true, withoutLocations: false);`
1514
- */
1515
- function deepCloneNode(node) {
1516
- return types$2.cloneNode(node, true, false);
1517
- }
1518
- function placeHeaderIfNeeded(mutantCollector, originFileName, options, root) {
1519
- if (mutantCollector.hasPlacedMutants(originFileName) && !options.noHeader) placeHeader(root);
1520
- }
1521
- function placeHeader(root) {
1522
- let header = instrumentationBabelHeader;
1523
- const leadingComments = root.program.body[0]?.leadingComments;
1524
- if (Array.isArray(leadingComments)) {
1525
- const firstHeader = instrumentationBabelHeader[0];
1526
- if (firstHeader === void 0) throw new Error("Instrumentation header is empty");
1527
- const cloned = types$2.cloneNode(firstHeader, true, false);
1528
- cloned.leadingComments = leadingComments;
1529
- header = [cloned, ...instrumentationBabelHeader.slice(1)];
1530
- }
1531
- root.program.body.unshift(...header);
1532
- }
1533
- //#endregion
1534
- //#region src/parsers/svelte-parser.ts
1535
- const MIN_SVELTE_VERSION = ">=3.30";
1536
- function isPlainRecord(value) {
1537
- return typeof value === "object" && value !== null;
1538
- }
1539
- function isUnknownArray(value) {
1540
- return Array.isArray(value);
1541
- }
1542
- function isRangedProgram(value) {
1543
- return isPlainRecord(value) && typeof value["start"] === "number" && typeof value["end"] === "number";
1544
- }
1545
- function isWalkFunction(value) {
1546
- return typeof value === "function";
1547
- }
1548
- function isRangedBaseNode(value) {
1549
- return isPlainRecord(value) && typeof value["type"] === "string" && typeof value["start"] === "number" && typeof value["end"] === "number";
1550
- }
1551
- function isTemplateExpressionType(type) {
1552
- return type === "MustacheTag" || type === "RawMustacheTag" || type === "IfBlock" || type === "ConstTag" || type === "EachBlock" || type === "AwaitBlock" || type === "KeyBlock" || type === "EventHandler";
1553
- }
1554
- function tryGetScriptRangeFromElement(node) {
1555
- if (!isPlainRecord(node) || node["type"] !== "Element" || node["name"] !== "script") return;
1556
- const children = node["children"];
1557
- if (!isUnknownArray(children) || children.length === 0) return;
1558
- const firstChild = children[0];
1559
- if (!isPlainRecord(firstChild) || firstChild["type"] !== "Text" || typeof firstChild["start"] !== "number" || typeof firstChild["end"] !== "number") return;
1560
- return {
1561
- start: firstChild["start"],
1562
- end: firstChild["end"],
1563
- isExpression: false
1564
- };
1565
- }
1566
- async function parse$1(text, fileName, context) {
1567
- const { parse: svelteParse, preprocess, VERSION } = await import("./compiler-Ck-td5Ds.mjs");
1568
- let walk;
1569
- if (!satisfies(VERSION, MIN_SVELTE_VERSION)) throw new Error(`Svelte version ${VERSION} not supported. Expected: ${MIN_SVELTE_VERSION} (processing file ${fileName})`);
1570
- if (satisfies(VERSION, ">=5")) {
1571
- const walkerModule = await import(import.meta.resolve("estree-walker", import.meta.resolve("svelte")));
1572
- if (!isPlainRecord(walkerModule) || !isWalkFunction(walkerModule["walk"])) throw new Error("estree-walker module without walk export");
1573
- walk = walkerModule["walk"];
1574
- } else {
1575
- const svelteCompilerModule = await import("./compiler-Ck-td5Ds.mjs");
1576
- if (!isPlainRecord(svelteCompilerModule) || !isWalkFunction(svelteCompilerModule["walk"])) throw new Error("svelte/compiler module without walk export");
1577
- walk = svelteCompilerModule["walk"];
1578
- }
1579
- const positionConverter = new PositionConverter(text);
1580
- const { replacedCode, scriptMap } = await replaceScripts(text);
1581
- const svelteAst = svelteParse(replacedCode, { filename: fileName });
1582
- const { remappedModuleScriptRange, remappedScriptRanges } = remapScriptLocations(replacedCode, scriptMap, getModuleScriptRange(svelteAst), getTemplateScriptRanges(svelteAst, walk));
1583
- const [moduleScript, ...additionalScripts] = await Promise.all([parseTemplateScriptIfDefined(remappedModuleScriptRange), ...remappedScriptRanges.map(parseTemplateScript)]);
1584
- return {
1585
- originFileName: fileName,
1586
- rawContent: text,
1587
- format: "svelte",
1588
- root: {
1589
- ...moduleScript === void 0 ? {} : { moduleScript },
1590
- additionalScripts
1591
- }
1592
- };
1593
- /**
1594
- * Replaces script tags with placeholders.
1595
- * This is needed, because svelte's `parse` doesn't support `lang="ts"`.
1596
- */
1597
- async function replaceScripts(code) {
1598
- const map = /* @__PURE__ */ new Map();
1599
- let scriptIndex = 0;
1600
- return {
1601
- replacedCode: (await preprocess(code, { script(script) {
1602
- const scriptName = `script${scriptIndex++}`;
1603
- map.set(scriptName, script);
1604
- return { code: scriptName };
1605
- } })).code,
1606
- scriptMap: map
1607
- };
1608
- }
1609
- function getTemplateScriptRanges(ast, walker) {
1610
- const ranges = [];
1611
- if (isPlainRecord(ast) && ast["instance"] !== null && ast["instance"] !== void 0) {
1612
- const instance = ast["instance"];
1613
- if (isPlainRecord(instance) && "content" in instance) {
1614
- const content = instance["content"];
1615
- if (isRangedProgram(content)) ranges.push({
1616
- start: content.start,
1617
- end: content.end,
1618
- isExpression: false
1619
- });
1620
- else throw new Error("Svelte instance script without a source range");
1621
- }
1622
- }
1623
- if (!isPlainRecord(ast) || !("html" in ast)) throw new Error("Svelte AST without html");
1624
- const html = ast["html"];
1625
- walker(html, { enter(node) {
1626
- const scriptRange = tryGetScriptRangeFromElement(node);
1627
- if (scriptRange) ranges.push(scriptRange);
1628
- const templateExpression = collectTemplateExpression(node);
1629
- if (templateExpression) {
1630
- const { start, end } = templateExpression;
1631
- ranges.push({
1632
- start,
1633
- end,
1634
- isExpression: true
1635
- });
1636
- }
1637
- } });
1638
- return ranges;
1639
- }
1640
- async function parseTemplateScriptIfDefined(range) {
1641
- if (range) return parseTemplateScript(range);
2061
+ mutatorName
2062
+ };
2063
+ if (ignoreReason !== void 0) mutableEntry.ignoreReason = ignoreReason;
2064
+ yield mutableEntry;
2065
+ }
2066
+ function findExcludedMutatorIgnoreReason(mutatorName) {
2067
+ if (options.excludedMutations.includes(mutatorName)) return `Ignored because of excluded mutation "${mutatorName}"`;
2068
+ else return;
2069
+ }
1642
2070
  }
1643
- async function parseTemplateScript({ start, end, isExpression, format }) {
1644
- const scriptText = text.slice(start, end);
1645
- return {
1646
- ast: {
1647
- ...await context.parse(scriptText, fileName, format),
1648
- offset: positionConverter.positionFromOffset(start)
1649
- },
1650
- range: {
1651
- start,
1652
- end
1653
- },
1654
- isExpression
1655
- };
2071
+ };
2072
+ function toMutatorContext(path) {
2073
+ const ancestors = [];
2074
+ let current = path.parentPath;
2075
+ while (current !== null) {
2076
+ ancestors.push(current.node);
2077
+ current = current.parentPath;
1656
2078
  }
1657
- }
1658
- function getModuleScriptRange(svelteAst) {
1659
- if (!isPlainRecord(svelteAst) || !("module" in svelteAst) || svelteAst["module"] === null || svelteAst["module"] === void 0) return;
1660
- const mod = svelteAst["module"];
1661
- if (!isPlainRecord(mod) || !("content" in mod)) throw new Error("Svelte module script without a source range");
1662
- const content = mod["content"];
1663
- if (!isRangedProgram(content)) throw new Error("Svelte module script without a source range");
1664
2079
  return {
1665
- start: content.start,
1666
- end: content.end,
1667
- isExpression: false
2080
+ parent: ancestors[0],
2081
+ grandParent: ancestors[1],
2082
+ ancestors
1668
2083
  };
1669
2084
  }
1670
- /**
1671
- * Remaps script locations back to the original places using the script map
1672
- */
1673
- function remapScriptLocations(code, scriptMap, moduleScriptRange, templateRanges) {
1674
- const scriptRanges = [moduleScriptRange, ...templateRanges].filter(notEmpty).sort((a, b) => a.start - b.start);
1675
- let offset = 0;
1676
- let newModuleScriptRange;
1677
- const newScriptRanges = scriptRanges.map((range) => {
1678
- const script = code.substring(range.start, range.end);
1679
- const actualScript = scriptMap.get(script);
1680
- const start = range.start + offset;
1681
- if (actualScript) {
1682
- const scriptRange = {
1683
- start,
1684
- end: start + actualScript.content.length,
1685
- isExpression: range.isExpression,
1686
- format: actualScript.attributes["lang"] === "ts" ? "ts" : "js"
1687
- };
1688
- offset += actualScript.content.length - script.length;
1689
- if (range === moduleScriptRange) newModuleScriptRange = scriptRange;
1690
- return scriptRange;
1691
- } else return {
1692
- start,
1693
- end: start + script.length,
1694
- isExpression: range.isExpression,
1695
- format: "js"
1696
- };
1697
- });
2085
+ function getNodeLocation(node) {
2086
+ const loc = node.loc;
2087
+ if (loc == null) throw new Error("Babel node without location");
1698
2088
  return {
1699
- remappedModuleScriptRange: newModuleScriptRange,
1700
- remappedScriptRanges: newScriptRanges.filter((range) => range !== newModuleScriptRange)
2089
+ start: {
2090
+ line: loc.start.line,
2091
+ column: loc.start.column
2092
+ },
2093
+ end: {
2094
+ line: loc.end.line,
2095
+ column: loc.end.column
2096
+ }
1701
2097
  };
1702
2098
  }
1703
- function collectTemplateExpression(node) {
1704
- if (!isPlainRecord(node) || typeof node["type"] !== "string") return;
1705
- if (!isTemplateExpressionType(node["type"])) return;
1706
- if (!("expression" in node)) return;
1707
- const expression = node["expression"];
1708
- if (isRangedBaseNode(expression)) return expression;
2099
+ function toError(value) {
2100
+ if (value instanceof Error) return value;
2101
+ return new Error("Unexpected error", { cause: value });
1709
2102
  }
1710
2103
  //#endregion
1711
- //#region src/parsers/ts-parser.ts
1712
- const { types: types$1, parseAsync } = babel;
1713
- const require = createRequire(import.meta.url);
2104
+ //#region src/Instrument.ts
1714
2105
  /**
1715
- * See https://babeljs.io/docs/en/babel-preset-typescript
1716
- * @param text The text to parse
1717
- * @param fileName The name of the file
2106
+ * Instrument — the instrument capability: Cell description, file/mutant types and the instrument entry point.
1718
2107
  */
1719
- async function parseTS(text, fileName) {
1720
- return {
1721
- originFileName: fileName,
1722
- rawContent: text,
1723
- format: "ts",
1724
- root: await parse(text, fileName, false)
2108
+ const { traverse, types: babelTypes } = babel;
2109
+ function resolveGenerate() {
2110
+ if (typeof generator === "function") return generator;
2111
+ return generator.default;
2112
+ }
2113
+ resolveGenerate();
2114
+ function toApiMutant(mutant) {
2115
+ const loc = mutant.original.loc;
2116
+ if (loc === void 0 || loc === null) throw new Error("Babel node without a source location");
2117
+ const base = {
2118
+ fileName: mutant.fileName,
2119
+ id: mutant.id,
2120
+ location: toApiLocation(loc, mutant.offset),
2121
+ mutatorName: mutant.mutatorName,
2122
+ replacement: mutant.replacementCode
1725
2123
  };
2124
+ if (mutant.ignoreReason === void 0) return Mutant.make(base);
2125
+ return Mutant.make({
2126
+ ...base,
2127
+ statusReason: mutant.ignoreReason,
2128
+ status: "Ignored"
2129
+ });
1726
2130
  }
1727
- async function parseTsx(text, fileName) {
2131
+ function toApiLocation(source, offset) {
1728
2132
  return {
1729
- root: await parse(text, fileName, true),
1730
- format: "tsx",
1731
- originFileName: fileName,
1732
- rawContent: text
2133
+ start: toPosition(source.start, offset),
2134
+ end: toPosition(source.end, offset)
1733
2135
  };
1734
2136
  }
1735
- async function parse(text, fileName, isTSX) {
1736
- const ast = await parseAsync(text, {
1737
- filename: fileName,
1738
- parserOpts: { ranges: true },
1739
- configFile: false,
1740
- babelrc: false,
1741
- presets: [[require.resolve("@babel/preset-typescript"), {
1742
- isTSX,
1743
- allExtensions: true
1744
- }]],
1745
- plugins: [[require.resolve("@babel/plugin-proposal-decorators"), { legacy: true }], [require.resolve("@babel/plugin-transform-explicit-resource-management")]]
1746
- });
1747
- if (ast === null) throw new Error(`Expected ${fileName} to contain a babel.types.file, but it yielded null`);
1748
- if (types$1.isProgram(ast)) throw new Error(`Expected ${fileName} to contain a babel.types.file, but was a program`);
1749
- return ast;
1750
- }
1751
- //#endregion
1752
- //#region src/parsers/create-parser.ts
1753
- function createParser(parserOptions) {
1754
- const jsParse = createParser$1(parserOptions);
1755
- async function parse(code, fileName, formatOverride) {
1756
- const format = getFormat(fileName, formatOverride);
1757
- if (!format) {
1758
- const ext = path.extname(fileName).toLowerCase();
1759
- throw new Error(`Unable to parse ${fileName}. No parser registered for ${ext}!`);
1760
- }
1761
- switch (format) {
1762
- case "js": return jsParse(code, fileName);
1763
- case "tsx": return parseTsx(code, fileName);
1764
- case "ts": return parseTS(code, fileName);
1765
- case "html": return parse$2(code, fileName, { parse });
1766
- case "svelte": return parse$1(code, fileName, { parse });
1767
- default: throw new Error(`Unsupported format: ${String(format)}`);
1768
- }
1769
- }
1770
- return parse;
1771
- }
1772
- function getFormat(fileName, override) {
1773
- if (override) return override;
1774
- else switch (path.extname(fileName).toLowerCase()) {
1775
- case ".js":
1776
- case ".jsx":
1777
- case ".mjs":
1778
- case ".cjs": return "js";
1779
- case ".mts":
1780
- case ".cts":
1781
- case ".ts": return "ts";
1782
- case ".tsx": return "tsx";
1783
- case ".vue":
1784
- case ".html":
1785
- case ".htm": return "html";
1786
- case ".svelte": return "svelte";
1787
- default: return;
1788
- }
1789
- }
1790
- //#endregion
1791
- //#region src/printers/html-printer.ts
1792
- function getScriptStart$1(script) {
1793
- const start = script.root.start;
1794
- if (start === void 0 || start === null) throw new Error("Script AST root without start");
1795
- return start;
1796
- }
1797
- function getScriptEnd$1(script) {
1798
- const end = script.root.end;
1799
- if (end === void 0 || end === null) throw new Error("Script AST root without end");
1800
- return end;
1801
- }
1802
- const print$4 = (ast, context) => {
1803
- const sortedScripts = [...ast.root.scripts].sort((a, b) => getScriptStart$1(a) - getScriptStart$1(b));
1804
- let currentIndex = 0;
1805
- let html = "";
1806
- for (const script of sortedScripts) {
1807
- html += ast.rawContent.substring(currentIndex, getScriptStart$1(script));
1808
- html += "\n";
1809
- html += context.print(script, context);
1810
- html += "\n";
1811
- currentIndex = getScriptEnd$1(script);
1812
- }
1813
- html += ast.rawContent.substr(currentIndex);
1814
- return html;
1815
- };
1816
- //#endregion
1817
- //#region src/printers/js-printer.ts
1818
- const print$3 = (file) => {
1819
- return generate(file.root, { sourceMaps: false }).code;
1820
- };
1821
- //#endregion
1822
- //#region src/printers/svelte-printer.ts
1823
- const print$2 = ({ root, rawContent }, context) => {
1824
- let currentIndex = 0;
1825
- let outputText = "";
1826
- const sortedScripts = [root.moduleScript, ...root.additionalScripts].filter(notEmpty).sort((a, b) => a.range.start - b.range.start);
1827
- for (const script of sortedScripts) if (script.isExpression) {
1828
- const codeWithoutSemicolon = context.print(script.ast, context).slice(0, -1);
1829
- outputText += rawContent.substring(currentIndex, script.range.start) + codeWithoutSemicolon;
1830
- currentIndex = script.range.end;
1831
- } else {
1832
- outputText += rawContent.substring(currentIndex, script.range.start);
1833
- outputText += "\n";
1834
- outputText += context.print(script.ast, context);
1835
- outputText += "\n";
1836
- currentIndex = script.range.end;
1837
- }
1838
- outputText += rawContent.substring(currentIndex);
1839
- return outputText;
1840
- };
1841
- //#endregion
1842
- //#region src/printers/ts-printer.ts
1843
- const print$1 = (file) => {
1844
- return generate(file.root, {
1845
- decoratorsBeforeExport: true,
1846
- sourceMaps: false
1847
- }).code;
1848
- };
1849
- //#endregion
1850
- //#region src/printers/index.ts
1851
- function print(file) {
1852
- const context = { print };
1853
- switch (file.format) {
1854
- case "js": return print$3(file, context);
1855
- case "ts": return print$1(file, context);
1856
- case "tsx": return print$1(file, context);
1857
- case "html": return print$4(file, context);
1858
- case "svelte": return print$2(file, context);
1859
- }
1860
- }
1861
- //#endregion
1862
- //#region src/create-instrumenter.ts
1863
- createInstrumenter.inject = tokens(commonTokens.injector);
1864
- function createInstrumenter(injector) {
1865
- return injector.provideValue(instrumenterTokens.print, print).provideValue(instrumenterTokens.createParser, createParser).provideValue(instrumenterTokens.transform, transform).injectClass(Instrumenter);
2137
+ function toPosition(source, offset) {
2138
+ let columnOffset = 0;
2139
+ if (source.line === 1) columnOffset = offset.column;
2140
+ return {
2141
+ column: source.column + columnOffset,
2142
+ line: source.line + offset.line - 1
2143
+ };
1866
2144
  }
1867
- //#endregion
1868
- //#region src/disable-type-checks.ts
1869
2145
  const commentDirectiveRegEx = /^(\s*)@(ts-[a-z-]+).*$/;
1870
2146
  const tsDirectiveLikeRegEx = /@(ts-[a-z-]+)/;
1871
2147
  const startingCommentRegex = /(^\s*\/\*.*?\*\/)/gs;
1872
- /**
1873
- * Disables TypeScript type checking for a single file by inserting `// @ts-nocheck` commands.
1874
- * It also does this for *.js files, as they can be type checked by typescript as well.
1875
- * Other file types are silently ignored
1876
- *
1877
- * @see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#-ts-nocheck-in-typescript-files
1878
- */
1879
2148
  async function disableTypeChecks(file, options) {
1880
2149
  const format = getFormat(file.name);
1881
2150
  if (!format) return file;
@@ -1944,7 +2213,7 @@ function disableTypeCheckingInHtml(ast) {
1944
2213
  return html;
1945
2214
  }
1946
2215
  function disableTypeCheckingInSvelte(ast) {
1947
- const sortedScripts = [ast.root.moduleScript, ...ast.root.additionalScripts].filter(notEmpty).sort((a, b) => a.range.start - b.range.start);
2216
+ const sortedScripts = [ast.root.moduleScript, ...ast.root.additionalScripts].filter(Predicate.isNotNullish).sort((a, b) => a.range.start - b.range.start);
1948
2217
  let currentIndex = 0;
1949
2218
  let html = "";
1950
2219
  for (const script of sortedScripts) {
@@ -1958,23 +2227,23 @@ function disableTypeCheckingInSvelte(ast) {
1958
2227
  return html;
1959
2228
  }
1960
2229
  function removeTSDirectives(text, comments) {
1961
- const directiveRanges = comments?.map(tryParseTSDirective).filter(notEmpty).sort((a, b) => a.startPos - b.startPos);
1962
- if (directiveRanges) {
1963
- let currentIndex = 0;
1964
- let pruned = "";
1965
- for (const directiveRange of directiveRanges) {
1966
- pruned += text.substring(currentIndex, directiveRange.startPos);
1967
- currentIndex = directiveRange.endPos;
1968
- }
1969
- pruned += text.substring(currentIndex);
1970
- return pruned;
1971
- } else return text;
2230
+ if (comments === null || comments === void 0) return text;
2231
+ const directiveRanges = comments.map(tryParseTSDirective).filter(Predicate.isNotNullish).sort((a, b) => a.startPos - b.startPos);
2232
+ if (directiveRanges.length === 0) return text;
2233
+ let currentIndex = 0;
2234
+ let pruned = "";
2235
+ for (const directiveRange of directiveRanges) {
2236
+ pruned += text.substring(currentIndex, directiveRange.startPos);
2237
+ currentIndex = directiveRange.endPos;
2238
+ }
2239
+ pruned += text.substring(currentIndex);
2240
+ return pruned;
1972
2241
  }
1973
2242
  function tryParseTSDirective(comment) {
1974
2243
  const match = commentDirectiveRegEx.exec(comment.value);
1975
- if (match) {
2244
+ if (match !== null) {
1976
2245
  const start = comment.start;
1977
- if (start === void 0 || start === null) throw new Error("Comment without start");
2246
+ if (start == null) throw new Error("Comment without start");
1978
2247
  const directivePrefix = match[1];
1979
2248
  if (directivePrefix === void 0) throw new Error("TS directive match without prefix");
1980
2249
  const directiveName = match[2];
@@ -1986,120 +2255,9 @@ function tryParseTSDirective(comment) {
1986
2255
  };
1987
2256
  }
1988
2257
  }
1989
- //#endregion
1990
- //#region src/frameworks/angular-ignorer.ts
1991
- const ANGULAR_SIGNAL_IO_FUNCTIONS = Object.freeze([
1992
- "input",
1993
- "model",
1994
- "output"
1995
- ]);
1996
- const ANGULAR_SIGNAL_QUERY_FUNCTIONS = Object.freeze([
1997
- "contentChild",
1998
- "contentChildren",
1999
- "viewChild",
2000
- "viewChildren"
2001
- ]);
2002
- const INPUT_MODEL_OUTPUT_CONFIG_MSG = "Angular signal based input, model and output functions configuration object cannot be mutated as that causes issues with the Angular compiler.";
2003
- const SIGNAL_QUERY_OPTIONS_MSG = "Angular signal query options object cannot be mutated as that causes issues with the Angular compiler.";
2004
- var AngularIgnorer = class {
2005
- shouldIgnore(path) {
2006
- if (this.isInputModelOrOutputConfigurationObject(path)) return INPUT_MODEL_OUTPUT_CONFIG_MSG;
2007
- if (this.isSignalQueryOptionsObject(path)) return SIGNAL_QUERY_OPTIONS_MSG;
2008
- }
2009
- #isClassFieldLike(path) {
2010
- return path.isClassProperty() || path.isClassPrivateProperty() || path.isClassAccessorProperty();
2011
- }
2012
- /**
2013
- * Determines if the given path is a configuration object for an Angular input, model or output function.
2014
- * This solves the "Argument needs to be statically analyzable." error
2015
- */
2016
- isInputModelOrOutputConfigurationObject(path) {
2017
- if (!path.isObjectExpression() || !path.parentPath.isCallExpression() || !path.parentPath.parentPath.isClassProperty()) return false;
2018
- const callExpression = path.parentPath;
2019
- const objectExpression = path;
2020
- const isRequiredSignalIOFunction = callExpression.node.callee.type === "MemberExpression" && callExpression.node.callee.object.type === "Identifier" && ANGULAR_SIGNAL_IO_FUNCTIONS.includes(callExpression.node.callee.object.name) && callExpression.node.callee.property.type === "Identifier" && callExpression.node.callee.property.name === "required";
2021
- const isSignalIOFunction = callExpression.node.callee.type === "Identifier" && ANGULAR_SIGNAL_IO_FUNCTIONS.includes(callExpression.node.callee.name);
2022
- const isOutput = callExpression.node.callee.type === "Identifier" && callExpression.node.callee.name === "output";
2023
- if (isRequiredSignalIOFunction || isOutput) return callExpression.node.arguments.length >= 1 && callExpression.node.arguments[0] === objectExpression.node;
2024
- if (isSignalIOFunction) return callExpression.node.arguments.length >= 2 && callExpression.node.arguments[1] === objectExpression.node;
2025
- return false;
2026
- }
2027
- /**
2028
- * Determines if the given path is a configuration object for an Angular signal query function.
2029
- * This solves the "Argument needs to be statically analyzable." error
2030
- */
2031
- isSignalQueryOptionsObject(path) {
2032
- if (!path.isObjectExpression() || !path.parentPath.isCallExpression() || !this.#isClassFieldLike(path.parentPath.parentPath)) return false;
2033
- const callExpression = path.parentPath;
2034
- const objectExpression = path;
2035
- const callee = callExpression.node.callee;
2036
- const isQueryFn = callee.type === "Identifier" && ANGULAR_SIGNAL_QUERY_FUNCTIONS.includes(callee.name);
2037
- const isRequiredQueryFn = callee.type === "MemberExpression" && callee.object.type === "Identifier" && ANGULAR_SIGNAL_QUERY_FUNCTIONS.includes(callee.object.name) && callee.property.type === "Identifier" && callee.property.name === "required";
2038
- if (!isQueryFn && !isRequiredQueryFn) return false;
2039
- return callExpression.node.arguments.length >= 2 && callExpression.node.arguments[1] === objectExpression.node;
2040
- }
2041
- };
2042
- //#endregion
2043
- //#region src/frameworks/index.ts
2044
- const strykerPlugins = [declareClassPlugin(PluginKind.Ignore, "angular", AngularIgnorer)];
2045
- const frameworkPluginsFileUrl = import.meta.url;
2046
- //#endregion
2047
- //#region src/instrumenter.ts
2048
- /**
2049
- * The instrumenter is responsible for
2050
- * * Generating mutants based on source files
2051
- * * Instrumenting the source code with the mutants placed in `mutant switches`.
2052
- * * Adding mutant coverage expressions in the source code.
2053
- * @see https://github.com/stryker-mutator/stryker-js/issues/1514
2054
- */
2055
- var Instrumenter = class {
2056
- logger;
2057
- _createParser;
2058
- _print;
2059
- _transform;
2060
- static inject = tokens(commonTokens.logger, instrumenterTokens.createParser, instrumenterTokens.print, instrumenterTokens.transform);
2061
- constructor(logger, _createParser = createParser, _print = print, _transform = transform) {
2062
- this.logger = logger;
2063
- this._createParser = _createParser;
2064
- this._print = _print;
2065
- this._transform = _transform;
2066
- }
2067
- async instrument(files, options) {
2068
- this.logger.debug("Instrumenting %d source files with mutants", files.length);
2069
- const mutantCollector = new MutantCollector();
2070
- const outFiles = [];
2071
- let mutantCount = 0;
2072
- const parse = this._createParser(options);
2073
- for (const { name, mutate, content } of files) {
2074
- const ast = await parse(content, name);
2075
- this._transform(ast, mutantCollector, {
2076
- options,
2077
- mutateDescription: toBabelLineNumber(mutate),
2078
- logger: this.logger
2079
- });
2080
- const mutatedContent = this._print(ast);
2081
- outFiles.push({
2082
- name,
2083
- mutate,
2084
- content: mutatedContent
2085
- });
2086
- if (this.logger.isDebugEnabled()) {
2087
- const nrOfMutantsInFile = mutantCollector.mutants.length - mutantCount;
2088
- mutantCount = mutantCollector.mutants.length;
2089
- this.logger.debug(`Instrumented ${path.relative(process.cwd(), name)} (${nrOfMutantsInFile} mutant(s))`);
2090
- }
2091
- }
2092
- const mutants = mutantCollector.mutants.map((mutant) => mutant.toApiMutant());
2093
- this.logger.info("Instrumented %d source file(s) with %d mutant(s)", files.length, mutants.length);
2094
- return {
2095
- files: outFiles,
2096
- mutants
2097
- };
2098
- }
2099
- };
2100
2258
  function toBabelLineNumber(range) {
2101
2259
  if (typeof range === "boolean") return range;
2102
- else return range.map(({ start, end }) => ({
2260
+ return range.map(({ start, end }) => ({
2103
2261
  start: {
2104
2262
  column: start.column,
2105
2263
  line: start.line + 1
@@ -2110,5 +2268,115 @@ function toBabelLineNumber(range) {
2110
2268
  }
2111
2269
  }));
2112
2270
  }
2271
+ function isIgnorerService(value) {
2272
+ return typeof value === "object" && value !== null && "shouldIgnore" in value && typeof value.shouldIgnore === "function";
2273
+ }
2274
+ function toTransformerOptions(options) {
2275
+ const base = {
2276
+ excludedMutations: [...options.excludedMutations],
2277
+ ignorers: options.ignorers.filter(isIgnorerService)
2278
+ };
2279
+ if (options.noHeader !== void 0) return {
2280
+ ...base,
2281
+ noHeader: options.noHeader
2282
+ };
2283
+ return base;
2284
+ }
2285
+ function isAst(value) {
2286
+ return typeof value === "object" && value !== null && "format" in value && "root" in value;
2287
+ }
2288
+ const instrumentDescription = pipe(Cell.read((command) => Effect.gen(function* () {
2289
+ const files = command.files;
2290
+ const options = command.options;
2291
+ const parse = createParser(options);
2292
+ const asts = [];
2293
+ for (const { name, content } of files) {
2294
+ const ast = yield* Effect.tryPromise({
2295
+ try: () => parse(content, name),
2296
+ catch: (cause) => new InstrumentError({
2297
+ message: `Failed to parse ${name}`,
2298
+ cause
2299
+ })
2300
+ });
2301
+ asts.push(ast);
2302
+ }
2303
+ const collector = createMutantCollector();
2304
+ const workAsts = asts.filter(isAst);
2305
+ for (let i = 0; i < workAsts.length; i++) {
2306
+ const ast = workAsts[i];
2307
+ const file = files[i];
2308
+ if (ast === void 0 || file === void 0) continue;
2309
+ yield* Effect.try({
2310
+ try: () => transform(ast, collector, {
2311
+ options: toTransformerOptions(options),
2312
+ mutateDescription: toBabelLineNumber(file.mutate)
2313
+ }),
2314
+ catch: (cause) => new InstrumentError({
2315
+ message: `Failed to transform ${file.name}`,
2316
+ cause
2317
+ })
2318
+ });
2319
+ }
2320
+ return {
2321
+ files,
2322
+ options,
2323
+ asts,
2324
+ mutants: yield* Effect.try({
2325
+ try: () => collector.map(toApiMutant),
2326
+ catch: (cause) => new InstrumentError({
2327
+ message: "Failed to instrument",
2328
+ cause
2329
+ })
2330
+ })
2331
+ };
2332
+ })), Cell.decode((raw) => Result.succeed(InstrumentDecoded.make({
2333
+ files: raw.files,
2334
+ options: raw.options,
2335
+ asts: raw.asts,
2336
+ mutants: raw.mutants
2337
+ }))), Cell.decide(instrumentWorkflow), Cell.encode((outcome) => outcome), Cell.write((outcome) => Result.match(outcome, {
2338
+ onFailure: (error) => Effect.fail(error),
2339
+ onSuccess: (decision) => Effect.try({
2340
+ try: () => {
2341
+ const files = decision.files;
2342
+ const asts = decision.asts;
2343
+ const outFiles = [];
2344
+ const isAstValue = (value) => typeof value === "object" && value !== null && "format" in value && "root" in value;
2345
+ for (let i = 0; i < asts.length; i++) {
2346
+ const maybeAst = asts[i];
2347
+ const maybeFile = files[i];
2348
+ if (maybeAst === void 0 || maybeFile === void 0) continue;
2349
+ if (!isAstValue(maybeAst)) continue;
2350
+ const ast = maybeAst;
2351
+ const file = maybeFile;
2352
+ const mutatedContent = print(ast);
2353
+ outFiles.push({
2354
+ name: file.name,
2355
+ mutate: file.mutate,
2356
+ content: mutatedContent
2357
+ });
2358
+ }
2359
+ return InstrumentResult.make({
2360
+ files: outFiles,
2361
+ mutants: decision.mutants
2362
+ });
2363
+ },
2364
+ catch: (cause) => new InstrumentError({
2365
+ message: "Failed to print",
2366
+ cause
2367
+ })
2368
+ })
2369
+ })));
2370
+ const instrument = (files, options) => {
2371
+ const schemaFiles = files.map((file) => ({
2372
+ name: file.name,
2373
+ content: file.content,
2374
+ mutate: file.mutate
2375
+ }));
2376
+ return Cell.apply(instrumentDescription, InstrumentCommand.make({
2377
+ files: schemaFiles,
2378
+ options
2379
+ }));
2380
+ };
2113
2381
  //#endregion
2114
- export { Instrumenter, createInstrumenter, disableTypeChecks, frameworkPluginsFileUrl, strykerPlugins };
2382
+ export { angularIgnorer, disableTypeChecks, frameworkPluginsFileUrl, instrument, strykerPlugins };