@systemfsoftware/stryker-js-instrumenter 9.0.1 → 10.0.1

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
@@ -8,18 +8,19 @@ import * as S from "effect/Schema";
8
8
  import * as Effect from "effect/Effect";
9
9
  import { dual } from "effect/Function";
10
10
  import { Handle, Sandwich, Workflow } from "@systemfsoftware/effect-cell-types";
11
- import * as Arr from "effect/Array";
12
- import * as SGetter from "effect/SchemaGetter";
11
+ import * as Order from "effect/Order";
12
+ import * as SchemaGetter from "effect/SchemaGetter";
13
13
  import { RegExpParser, visitRegExpAST } from "@eslint-community/regexpp";
14
+ import * as Arr from "effect/Array";
14
15
  //#region src/ErrorText.schema.ts
15
- var ErrorText = class ErrorText extends S.Class("ErrorText")({ text: S.NonEmptyString }) {
16
- static fromCause = (cause) => Option.flatMap(nonEmptyText(errorTextOf$2(cause)), (text) => ErrorText.makeOption({ text }));
17
- };
18
- var CauseText = class CauseText extends S.Class("CauseText")({ text: S.NonEmptyString }) {
19
- static fromCause = (cause) => Option.flatMap(nonEmptyText(causeChainTextOf(cause)), (text) => CauseText.makeOption({ text }));
20
- };
16
+ var ErrorText = class extends S.Class("ErrorText")({ text: S.NonEmptyString }) {};
17
+ var CauseText = class extends S.Class("CauseText")({ text: S.NonEmptyString }) {};
18
+ //#endregion
19
+ //#region src/error-text.ts
21
20
  const nonEmptyText = Option.liftPredicate(S.is(S.NonEmptyString));
22
- function errorTextOf$2(error) {
21
+ const errorTextOf$2 = (cause) => Option.flatMap(nonEmptyText(errorTextValueOf(cause)), (text) => ErrorText.makeOption({ text }));
22
+ const causeTextOf = (cause) => Option.flatMap(nonEmptyText(causeChainTextOf(cause)), (text) => CauseText.makeOption({ text }));
23
+ function errorTextValueOf(error) {
23
24
  return Match.value(error).pipe(Match.when(isEmptyError, () => ""), Match.when(Match.instanceOf(Error), errorText), Match.orElse(() => stringifyNonError(error)));
24
25
  }
25
26
  function causeChainTextOf(cause) {
@@ -87,10 +88,10 @@ function causeText(cause, depth) {
87
88
  function missingCauseTextOf(cause, depth) {
88
89
  return Boolean$1.match(isMissingCause(cause), {
89
90
  onTrue: () => void 0,
90
- onFalse: () => causeTextOfValue(cause, depth)
91
+ onFalse: () => causeNodeTextOf(cause, depth)
91
92
  });
92
93
  }
93
- function causeTextOfValue(cause, depth) {
94
+ function causeNodeTextOf(cause, depth) {
94
95
  return Match.value(cause).pipe(Match.when(Match.string, (text) => textIfNonEmpty(text)), Match.orElse((value) => objectCauseTextOf(value, depth)));
95
96
  }
96
97
  function objectCauseTextOf(cause, depth) {
@@ -100,93 +101,92 @@ function objectCauseTextOf(cause, depth) {
100
101
  //#region src/ErrorText/mod.ts
101
102
  var ErrorText_exports = /* @__PURE__ */ __exportAll({
102
103
  CauseText: () => CauseText,
103
- ErrorText: () => ErrorText
104
+ ErrorText: () => ErrorText,
105
+ causeTextOf: () => causeTextOf,
106
+ errorTextOf: () => errorTextOf$2
104
107
  });
105
108
  //#endregion
106
109
  //#region src/Location.schema.ts
107
- /**
108
- * File coordinates in the mutation-testing-report-schema contract: both line
109
- * and column are 1-based. The first line of a file is line 1, and the first
110
- * character of a line is column 1. Slicing a source line by one of these
111
- * positions uses `line - 1` for the line index and `column - 1` for the
112
- * character offset.
113
- */
114
- const PositionSchema = S.Struct({
115
- line: S.Int.pipe(S.check(S.isGreaterThanOrEqualTo(0))),
116
- column: S.Int.pipe(S.check(S.isGreaterThanOrEqualTo(0)))
117
- });
118
- const LocationSchema$1 = S.Struct({
119
- start: PositionSchema,
120
- end: PositionSchema
110
+ const Offset = S.Int.check(S.isGreaterThanOrEqualTo(0));
111
+ /** A 1-based line: the first line of a file is line 1. */
112
+ const Line = S.Int.check(S.isGreaterThanOrEqualTo(1));
113
+ /** A 1-based column: the first character of a line is column 1. */
114
+ const Column = S.Int.check(S.isGreaterThanOrEqualTo(1));
115
+ const Position = S.Struct({
116
+ line: Line,
117
+ column: Column
118
+ });
119
+ const positionOrder = Order.combine(Order.mapInput(Order.Number, (position) => position.line), Order.mapInput(Order.Number, (position) => position.column));
120
+ const inOrder = (order) => (ends) => ({
121
+ start: Order.min(order)(ends.start, ends.end),
122
+ end: Order.max(order)(ends.start, ends.end)
123
+ });
124
+ const notReversed = (order) => (ends) => Order.isLessThanOrEqualTo(order)(ends.start, ends.end);
125
+ const PositionEnds = S.Struct({
126
+ start: Position,
127
+ end: Position
128
+ });
129
+ const Location = S.declare((value) => S.is(PositionEnds)(value) && notReversed(positionOrder)(value), {
130
+ expected: "a location whose end is not before its start",
131
+ toCodecArbitrary: () => S.link()(PositionEnds, {
132
+ decode: SchemaGetter.transform(inOrder(positionOrder)),
133
+ encode: SchemaGetter.transform((ends) => ends)
134
+ })
121
135
  });
122
- const OpenEndLocationSchema = S.Struct({
123
- start: PositionSchema,
124
- end: S.optional(PositionSchema)
136
+ const OpenEnds = S.Struct({
137
+ start: Position,
138
+ end: S.optional(Position)
125
139
  });
126
- /**
127
- * A mutant's `location` already speaks the report contract (1-based line and
128
- * column), so the report's location is that location unchanged.
129
- */
130
- const ReportLocationFromMutant = LocationSchema$1;
131
- const NonNegativeInt = S.Int.pipe(S.check(S.isGreaterThanOrEqualTo(0)));
132
- var LineTable = class extends S.Class("LineTable")({ lineStarts: S.NonEmptyArray(NonNegativeInt) }) {
133
- positionAt(offset) {
134
- return positionOf(this.lineStarts, offset);
135
- }
136
- locationAt(span) {
137
- return {
138
- start: this.positionAt(span.start),
139
- end: this.positionAt(span.end)
140
- };
141
- }
142
- zeroBasedPositionAt(offset) {
143
- return zeroBasedPositionOf(this.lineStarts, offset);
144
- }
145
- };
146
- const LINE_TERMINATOR = /\r\n|[\n\r\u2028\u2029]/g;
147
- const lineStartsOf = (text) => ({ lineStarts: [0, ...[...text.matchAll(LINE_TERMINATOR)].map((match) => endOfMatch(match))] });
148
- const endOfMatch = (match) => (match.index ?? 0) + match[0].length;
149
- const canonicalTextOf = (table) => Boolean$1.match(table.lineStarts.length === 1, {
150
- onTrue: () => "",
151
- onFalse: () => `${Arr.zip(table.lineStarts.slice(0, -1), table.lineStarts.slice(1)).map(([start, next]) => " ".repeat(next - start - 1)).join("\n")}\n`
152
- });
153
- const LineTableFromText = S.String.pipe(S.decodeTo(LineTable, {
154
- decode: SGetter.transform(lineStartsOf),
155
- encode: SGetter.transform(canonicalTextOf)
140
+ const closedEnds = (ends) => Option.map(Option.fromUndefinedOr(ends.end), (end) => ({
141
+ start: ends.start,
142
+ end
156
143
  }));
157
- const middleIndex = (low, high) => low + (high - low >> 1);
158
- const zeroBasedPositionOf = (lineStarts, offset) => {
159
- const search = (low, high, start) => Boolean$1.match(low > high, {
160
- onTrue: () => ({
161
- line: low - 1,
162
- column: offset - start
163
- }),
164
- onFalse: () => Option.match(Arr.get(lineStarts, middleIndex(low, high)), {
165
- onNone: () => ({
166
- line: low - 1,
167
- column: offset - start
168
- }),
169
- onSome: (found) => Boolean$1.match(found === offset, {
170
- onTrue: () => ({
171
- line: middleIndex(low, high),
172
- column: offset - found
173
- }),
174
- onFalse: () => Boolean$1.match(found < offset, {
175
- onTrue: () => search(middleIndex(low, high) + 1, high, found),
176
- onFalse: () => search(low, middleIndex(low, high) - 1, start)
177
- })
178
- })
179
- })
180
- });
181
- return search(0, lineStarts.length - 1, 0);
182
- };
183
- const positionOf = (lineStarts, offset) => {
184
- const zeroBased = zeroBasedPositionOf(lineStarts, offset);
185
- return {
186
- line: zeroBased.line + 1,
187
- column: zeroBased.column + 1
188
- };
189
- };
144
+ const OpenEndLocation = S.declare((value) => S.is(OpenEnds)(value) && Option.match(closedEnds(value), {
145
+ onNone: () => true,
146
+ onSome: notReversed(positionOrder)
147
+ }), {
148
+ expected: "a location whose end, when present, is not before its start",
149
+ toCodecArbitrary: () => S.link()(OpenEnds, {
150
+ decode: SchemaGetter.transform((ends) => Option.getOrElse(Option.map(closedEnds(ends), inOrder(positionOrder)), () => ends)),
151
+ encode: SchemaGetter.transform((ends) => ends)
152
+ })
153
+ });
154
+ const OffsetEnds = S.Struct({
155
+ start: Offset,
156
+ end: Offset
157
+ });
158
+ const Span = S.declare((value) => S.is(OffsetEnds)(value) && notReversed(Order.Number)(value), {
159
+ expected: "a span whose end is not before its start",
160
+ toCodecArbitrary: () => S.link()(OffsetEnds, {
161
+ decode: SchemaGetter.transform(inOrder(Order.Number)),
162
+ encode: SchemaGetter.transform((ends) => ends)
163
+ })
164
+ });
165
+ const ScriptOrigin = S.Struct({
166
+ line: Line,
167
+ columnShift: Offset
168
+ });
169
+ S.is(Offset), S.is(Line), S.is(Column), S.is(Location), S.is(Span);
170
+ //#endregion
171
+ //#region src/directives/directive.schema.ts
172
+ const MutatorNameSchema = S.String.pipe(S.check(S.isPattern(/^[a-zA-Z]+(?: [a-zA-Z]+)*$/)));
173
+ const DirectiveReasonSchema = S.String.pipe(S.check(S.isPattern(/^\S(?:[^\r\n\u2028\u2029]*\S)?$/)));
174
+ const DirectiveSchema = S.Struct({
175
+ action: S.Literals(["disable", "restore"]),
176
+ scope: S.Literals(["next-line", "block"]),
177
+ mutatorNames: S.Array(MutatorNameSchema).check(S.isMinLength(1)),
178
+ reason: DirectiveReasonSchema
179
+ });
180
+ const LocatedDirectiveSchema = S.Struct({
181
+ directive: DirectiveSchema,
182
+ at: Position,
183
+ governedLine: Line
184
+ });
185
+ S.Struct({
186
+ directive: DirectiveSchema,
187
+ at: Position,
188
+ mutatorName: MutatorNameSchema
189
+ });
190
190
  //#endregion
191
191
  //#region src/Mutant.schema.ts
192
192
  const MutantStatusSchema = S.Literals([
@@ -199,11 +199,30 @@ const MutantStatusSchema = S.Literals([
199
199
  "Ignored",
200
200
  "Pending"
201
201
  ]);
202
- const MutantId = S.NonEmptyString.pipe(S.brand("MutantId"));
202
+ const SurvivorStatusSchema = S.Literals(["Survived", "NoCoverage"]);
203
+ const RememberedStatusSchema = S.Literals([
204
+ "Survived",
205
+ "Killed",
206
+ "Timeout",
207
+ "NoCoverage",
208
+ "Ignored"
209
+ ]);
210
+ const EphemeralStatusSchema = S.Literals([
211
+ "CompileError",
212
+ "RuntimeError",
213
+ "Pending"
214
+ ]);
215
+ const ActionableStatusSchema = S.Literals([
216
+ "Survived",
217
+ "NoCoverage",
218
+ "Timeout",
219
+ "RuntimeError"
220
+ ]);
221
+ const MutantId = S.String.check(S.isPattern(/^(0|[1-9][0-9]*)$/)).pipe(S.brand("MutantId"));
203
222
  const MutatorName = S.NonEmptyString.pipe(S.brand("MutatorName"));
204
223
  const CanonicalFileName = S.String.pipe(S.decodeTo(S.String.pipe(S.check(S.isPattern(/^[^\\]+$/)), S.brand("CanonicalFileName")), {
205
- decode: SGetter.transform((fileName) => fileName.replace(/\\/g, "/")),
206
- encode: SGetter.transform((canonical) => canonical)
224
+ decode: SchemaGetter.transform((fileName) => fileName.replace(/\\/g, "/")),
225
+ encode: SchemaGetter.transform((canonical) => canonical)
207
226
  }));
208
227
  /**
209
228
  * A mutant's file location in the mutation-testing-report-schema contract:
@@ -211,19 +230,19 @@ const CanonicalFileName = S.String.pipe(S.decodeTo(S.String.pipe(S.check(S.isPat
211
230
  * machine stream emit. Every producer on the instrument path (node spans,
212
231
  * embedded-region shifts) targets this base; no downstream layer converts.
213
232
  */
214
- var Mutant = class extends S.TaggedClass()("Mutant", {
233
+ const Mutant = S.TaggedStruct("Mutant", {
215
234
  id: MutantId,
216
235
  fileName: CanonicalFileName,
217
236
  mutatorName: MutatorName,
218
237
  replacement: S.String,
219
- location: LocationSchema$1,
238
+ location: Location,
220
239
  status: S.optional(MutantStatusSchema),
221
240
  statusReason: S.optional(S.String),
222
241
  coveredBy: S.String.pipe(S.Array, S.optional),
223
242
  static: S.optional(S.Boolean),
224
243
  testsCompleted: S.optional(S.Finite),
225
244
  description: S.optional(S.String)
226
- }) {};
245
+ }).check(S.makeFilter((mutant) => mutant.statusReason === void 0 || mutant.status !== void 0, { message: "a mutant carries a status reason only together with a status" }));
227
246
  const MutantFromUnknown = S.Unknown.pipe(S.decodeTo(Mutant));
228
247
  const RunOptionsFields = {
229
248
  timeout: S.Finite.pipe(S.check(S.isGreaterThanOrEqualTo(0))),
@@ -241,15 +260,15 @@ const MutantRunOptionsSchema = S.Struct({
241
260
  hitLimit: S.optionalKey(HitCount)
242
261
  });
243
262
  const MutantCoverageSchema = S.Struct({
244
- perTest: S.Record(S.String, S.Record(S.String, HitCount)),
245
- static: S.Record(S.String, HitCount)
263
+ perTest: S.Record(S.String, S.Record(MutantId, HitCount)),
264
+ static: S.Record(MutantId, HitCount)
246
265
  });
247
266
  var InstrumenterContext = class extends S.Class("InstrumenterContext")({
248
267
  activeMutant: S.optional(S.String),
249
268
  currentTestId: S.optional(S.String),
250
269
  mutantCoverage: S.optional(MutantCoverageSchema),
251
- hitCount: S.optional(S.Finite),
252
- hitLimit: S.optional(S.Finite)
270
+ hitCount: S.optional(HitCount),
271
+ hitLimit: S.optional(HitCount)
253
272
  }) {
254
273
  static NAMESPACE = "__stryker__";
255
274
  static MUTATION_COVERAGE_OBJECT = "mutantCoverage";
@@ -272,21 +291,15 @@ var InstrumentError = class extends S.TaggedError("@systemfsoftware/stryker-js-i
272
291
  });
273
292
  }
274
293
  };
275
- const MutateDescriptionSchema = S.Union([S.Boolean, S.Array(LocationSchema$1)]);
276
- const SourceLineSchema = S.Int.pipe(S.check(S.isGreaterThanOrEqualTo(1)));
277
- const SourceColumnSchema = S.Int.pipe(S.check(S.isGreaterThanOrEqualTo(0)));
278
- const NodePositionSchema = S.Struct({
279
- line: S.Int,
280
- column: S.Int
281
- });
294
+ const MutateDescriptionSchema = S.Union([S.Boolean, S.Array(Location)]);
282
295
  const FileSchema = S.Struct({
283
- name: S.String,
296
+ name: CanonicalFileName,
284
297
  content: S.String,
285
298
  mutate: MutateDescriptionSchema
286
299
  });
287
300
  const IgnorerSchema = S.Unknown;
288
301
  S.Struct({
289
- excludedMutations: S.Array(S.String),
302
+ excludedMutations: S.Array(MutatorNameSchema),
290
303
  ignorers: S.Array(IgnorerSchema),
291
304
  noHeader: S.optional(S.Boolean),
292
305
  optInMutations: S.String.pipe(S.Array, S.optional)
@@ -316,13 +329,81 @@ const PlacerNameSchema = S.Literals([
316
329
  var MutantsUnapplied = class extends S.TaggedError()("MutantsUnapplied", {
317
330
  fileName: S.String,
318
331
  placer: PlacerNameSchema,
319
- mutatorNames: S.Array(S.String),
332
+ mutatorNames: S.Array(MutatorNameSchema),
320
333
  cause: S.Defect()
321
- }) {};
334
+ }) {
335
+ get message() {
336
+ return `Failed to apply ${this.mutatorNames.join(", ")} in ${this.fileName}`;
337
+ }
338
+ };
322
339
  var MutantNotApplied = class extends S.TaggedError()("MutantNotApplied", {
323
340
  fileName: S.String,
324
- mutatorName: S.String
325
- }) {};
341
+ mutatorName: MutatorNameSchema
342
+ }) {
343
+ get message() {
344
+ return `Could not apply the ${this.mutatorName} mutant in ${this.fileName}`;
345
+ }
346
+ };
347
+ var NodeWithoutSpan = class extends S.TaggedError()("NodeWithoutSpan", { fileName: S.String }) {
348
+ get message() {
349
+ return `Node without a span in ${this.fileName}`;
350
+ }
351
+ };
352
+ var MutantsUnplaced = class extends S.TaggedError()("MutantsUnplaced", {
353
+ fileName: S.String,
354
+ detail: S.String
355
+ }) {
356
+ get message() {
357
+ return `Mutants cannot be placed. This shouldn't happen! Unplaced mutants: ${this.detail}`;
358
+ }
359
+ };
360
+ var PlacementRefused = class extends S.TaggedError()("PlacementRefused", { message: S.String }) {};
361
+ //#endregion
362
+ //#region src/Location.ts
363
+ const LINE_TERMINATOR = /\r\n|[\n\r\u2028\u2029]/g;
364
+ const lineStartsOf = (text) => [0, ...[...text.matchAll(LINE_TERMINATOR)].map((match) => match.index + match[0].length)];
365
+ const middleIndex = (low, high) => low + (high - low >> 1);
366
+ const positionAtOffset = (lineStarts, offset, low, high, start) => Boolean$1.match(low > high, {
367
+ onTrue: () => ({
368
+ line: low,
369
+ column: offset - start + 1
370
+ }),
371
+ onFalse: () => Option.match(Arr.get(lineStarts, middleIndex(low, high)), {
372
+ onNone: () => ({
373
+ line: low,
374
+ column: offset - start + 1
375
+ }),
376
+ onSome: (found) => Boolean$1.match(found === offset, {
377
+ onTrue: () => ({
378
+ line: middleIndex(low, high) + 1,
379
+ column: 1
380
+ }),
381
+ onFalse: () => Boolean$1.match(found < offset, {
382
+ onTrue: () => positionAtOffset(lineStarts, offset, middleIndex(low, high) + 1, high, found),
383
+ onFalse: () => positionAtOffset(lineStarts, offset, low, middleIndex(low, high) - 1, start)
384
+ })
385
+ })
386
+ })
387
+ });
388
+ const positionAt = dual(2, (lineStarts, offset) => positionAtOffset(lineStarts, offset, 0, lineStarts.length - 1, 0));
389
+ const locationOf = dual(2, (lineStarts, span) => Location.make({
390
+ start: positionAt(lineStarts, span.start),
391
+ end: positionAt(lineStarts, span.end)
392
+ }));
393
+ const originAt = dual(2, (lineStarts, offset) => {
394
+ const position = positionAt(lineStarts, offset);
395
+ return ScriptOrigin.make({
396
+ line: position.line,
397
+ columnShift: position.column - 1
398
+ });
399
+ });
400
+ const offsetAt = dual(2, (lineStarts, position) => Option.flatMap(Arr.get(lineStarts, position.line - 1), (start) => {
401
+ const offset = start + position.column - 1;
402
+ return Option.match(Arr.get(lineStarts, position.line), {
403
+ onNone: () => Option.some(offset),
404
+ onSome: (next) => Option.liftPredicate(offset, (candidate) => candidate < next)
405
+ });
406
+ }));
326
407
  //#endregion
327
408
  //#region src/Ast.handle.ts
328
409
  const formatKeyOf = (ast) => ast.format === "embedded" ? ast.formatId : ast.format;
@@ -560,14 +641,14 @@ const switchCase = dual((args) => isDataFirstArity(args, 2), (test, consequent,
560
641
  consequent: [...consequent]
561
642
  }, loc));
562
643
  const cloneNode = (node) => structuredClone(node);
563
- const attachComments = dual((args) => args.length >= 3, (self, comments, lineTable) => Boolean$1.match(comments.length === 0, {
644
+ const attachComments = dual((args) => args.length >= 3, (self, comments, lineStarts) => Boolean$1.match(comments.length === 0, {
564
645
  onTrue: () => void 0,
565
646
  onFalse: () => {
566
647
  const nodes = collectNodes(self.root).filter((entry) => entry.node !== self.root);
567
648
  nodes.sort((a, b) => a.start - b.start);
568
649
  const groups = groupComments(nodes, comments);
569
- assignComments(groups.leading, lineTable, "leadingComments");
570
- assignComments(groups.trailing, lineTable, "trailingComments");
650
+ assignComments(groups.leading, lineStarts, "leadingComments");
651
+ assignComments(groups.trailing, lineStarts, "trailingComments");
571
652
  }
572
653
  }));
573
654
  const groupComments = (nodes, comments) => {
@@ -593,12 +674,12 @@ const commentHost = (nodes, comment) => {
593
674
  };
594
675
  const followingNode = (nodes, comment) => Option.getOrUndefined(Option.map(Option.fromNullishOr(nodes.find((candidate) => candidate.start >= comment.end)), (entry) => entry.node));
595
676
  const precedingStatement = (nodes, comment) => Option.getOrUndefined(Option.map(Option.fromNullishOr(nodes.findLast((candidate) => candidate.end <= comment.start && isStatementKind(candidate.node))), (entry) => entry.node));
596
- const assignComments = (map, lineTable, field) => map.forEach((list, node) => Object.assign(node, { [field]: list.map((comment) => ({
677
+ const assignComments = (map, lineStarts, field) => map.forEach((list, node) => Object.assign(node, { [field]: list.map((comment) => ({
597
678
  ...comment,
598
- loc: {
599
- start: lineTable.positionAt(comment.start),
600
- end: lineTable.positionAt(comment.end)
601
- }
679
+ loc: locationOf(lineStarts, {
680
+ start: comment.start,
681
+ end: comment.end
682
+ })
602
683
  })) }));
603
684
  const pushComment = (map, node, comment) => {
604
685
  Option.match(Option.fromNullishOr(map.get(node)), {
@@ -615,72 +696,41 @@ const WALK_SKIPPED_KEYS = {
615
696
  start: true,
616
697
  end: true
617
698
  };
618
- const walkNodes = (root, visitors) => visitIfNode(root, {
619
- key: null,
620
- index: null
621
- }, visitors);
622
- const visitNode = (node, context, visitors) => {
623
- const state = { skipped: false };
624
- visitors.enter(node, context, { skip: () => {
625
- state.skipped = true;
626
- } });
627
- Boolean$1.match(state.skipped, {
628
- onTrue: () => void 0,
629
- onFalse: () => visitChildren(node, visitors)
630
- });
631
- visitors.leave(node, context);
632
- };
633
- const visitChildren = (node, visitors) => Object.keys(node).forEach((key) => Boolean$1.match(WALK_SKIPPED_KEYS[key] === true, {
634
- onTrue: () => void 0,
635
- onFalse: () => visitSlot(node[key], key, visitors)
636
- }));
637
699
  const isChildList = (value) => Array.isArray(value);
638
- const visitSlot = (value, key, visitors) => Option.match(Option.filter(Option.some(value), isChildList), {
639
- onSome: (items) => items.forEach((item, index) => visitIfNode(item, {
640
- key,
641
- index
642
- }, visitors)),
643
- onNone: () => visitIfNode(value, {
700
+ const childSlotsOf = (node) => Object.keys(node).flatMap((key) => WALK_SKIPPED_KEYS[key] === true ? [] : childSlotsAt(key, node[key]));
701
+ const childSlotsAt = (key, value) => isChildList(value) ? listSlotsAt(key, value) : singleSlotAt(key, value);
702
+ const listSlotsAt = (key, value) => value.flatMap((item, index) => isAstNode(item) ? [{
703
+ key,
704
+ index,
705
+ node: item
706
+ }] : []);
707
+ const singleSlotAt = (key, value) => Option.match(Option.filter(Option.some(value), isAstNode), {
708
+ onNone: () => [],
709
+ onSome: (node) => [{
644
710
  key,
645
- index: null
646
- }, visitors)
711
+ index: null,
712
+ node
713
+ }]
714
+ });
715
+ const asWalkedNode = (value) => Option.filter(Option.some(value), isAstNode);
716
+ const childNodes = (node) => Option.match(asWalkedNode(node), {
717
+ onNone: () => [],
718
+ onSome: (walked) => childSlotsOf(walked).filter((slot) => !isCommentKey(slot.key)).map((slot) => ({
719
+ key: slot.key,
720
+ index: slot.index,
721
+ node: slot.node
722
+ }))
647
723
  });
648
- const visitIfNode = (value, context, visitors) => Option.match(Option.filter(Option.some(value), isAstNode), {
649
- onNone: () => void 0,
650
- onSome: (child) => visitNode(child, context, visitors)
651
- });
652
- const walker = (root, visitors) => {
653
- const ancestors = [];
654
- walkNodes(root, {
655
- enter(node) {
656
- visitors.enter?.(node, [...ancestors]);
657
- ancestors.push(node);
658
- },
659
- leave(node) {
660
- ancestors.pop();
661
- visitors.leave?.(node, [...ancestors]);
662
- }
663
- });
664
- };
665
- const collectNodes = (root) => {
666
- const out = [];
667
- walker(root, { enter(node) {
668
- appendEntry(node, out);
669
- } });
670
- return out;
671
- };
672
- const appendEntry = (node, out) => {
673
- Option.match(Option.fromNullishOr(spanOf(node)), {
674
- onNone: () => void 0,
675
- onSome: (span) => {
676
- out.push({
677
- node,
678
- start: span.start,
679
- end: span.end
680
- });
681
- }
682
- });
683
- };
724
+ const collectNodes = (root) => Option.toArray(nodeEntryOf(root)).concat(childEntriesOf(root));
725
+ const childEntriesOf = (root) => Option.match(asWalkedNode(root), {
726
+ onNone: () => [],
727
+ onSome: (node) => childSlotsOf(node).flatMap((slot) => collectNodes(slot.node))
728
+ });
729
+ const nodeEntryOf = (node) => Option.map(Option.fromNullishOr(spanOf(node)), (span) => ({
730
+ node,
731
+ start: span.start,
732
+ end: span.end
733
+ }));
684
734
  function isAstNode(value) {
685
735
  return Predicate.isObject(value) && typeof value["type"] === "string";
686
736
  }
@@ -689,32 +739,41 @@ const COMMENT_KEYS = {
689
739
  trailingComments: true
690
740
  };
691
741
  const isCommentKey = (key) => typeof key === "string" && COMMENT_KEYS[key] === true;
692
- const walkTraverse = (root, stack, visitors) => {
693
- walkNodes(root, {
694
- enter(node, context, controls) {
695
- readPath(stack, node, controls, context, visitors);
696
- },
697
- leave(_node, context) {
698
- closePath(stack, context, visitors);
742
+ const walkTraverse = (root, visitors) => Option.match(asWalkedNode(root), {
743
+ onNone: () => void 0,
744
+ onSome: (node) => traverseNode(node, null, {
745
+ key: null,
746
+ index: null
747
+ }, visitors)
748
+ });
749
+ const skipControls = () => {
750
+ let skipped = false;
751
+ return {
752
+ skipped: () => skipped,
753
+ skip: () => {
754
+ skipped = true;
699
755
  }
700
- });
756
+ };
701
757
  };
702
- const traverse = dual(2, (self, visitors) => walkTraverse(self.root, [], visitors));
703
- const readPath = (stack, node, controls, context, visitors) => {
758
+ const traverseNode = (node, parentPath, context, visitors) => {
704
759
  Boolean$1.match(isCommentKey(context.key), {
705
760
  onTrue: () => void 0,
706
761
  onFalse: () => {
707
- const current = createPath(node, parentOf(stack), controls, context);
708
- stack.push(current);
709
- notify(visitors.enter, current);
762
+ const controls = skipControls();
763
+ const path = createPath(node, parentPath, controls, context);
764
+ notify(visitors.enter, path);
765
+ Boolean$1.match(controls.skipped(), {
766
+ onTrue: () => void 0,
767
+ onFalse: () => childSlotsOf(node).forEach((slot) => traverseNode(slot.node, path, {
768
+ key: slot.key,
769
+ index: slot.index
770
+ }, visitors))
771
+ });
772
+ notify(visitors.exit, path);
710
773
  }
711
774
  });
712
775
  };
713
- const closePath = (stack, context, visitors) => Boolean$1.match(isCommentKey(context.key), {
714
- onTrue: () => void 0,
715
- onFalse: () => notify(visitors.exit, stack.pop())
716
- });
717
- const parentOf = (stack) => Option.getOrNull(Arr.last(stack));
776
+ const traverse = dual(2, (self, visitors) => walkTraverse(self.root, visitors));
718
777
  const notify = (visitor, path) => Option.match(Option.all({
719
778
  visitor: Option.fromNullishOr(visitor),
720
779
  path: Option.fromNullishOr(path)
@@ -877,13 +936,6 @@ const EMPTY_IMPORT_TABLE = Object.freeze({
877
936
  bareFunctions: /* @__PURE__ */ new Map(),
878
937
  pipeBindings: /* @__PURE__ */ new Set()
879
938
  });
880
- const importTables = /* @__PURE__ */ new WeakMap();
881
- const importTableFor = (program) => Option.getOrElse(Option.fromNullishOr(importTables.get(program)), () => rememberImportTable(program));
882
- function rememberImportTable(program) {
883
- const table = buildImportTable(program);
884
- importTables.set(program, table);
885
- return table;
886
- }
887
939
  const buildImportTable = (program) => Arr.reduce(Arr.flatMap(Arr.filter(program.body, isImportDeclaration$1), importContributions), EMPTY_IMPORT_TABLE, applyContribution);
888
940
  const withEntry = (entries, key, value) => new Map([...entries, [key, value]]);
889
941
  const withMember = (members, value) => /* @__PURE__ */ new Set([...members, value]);
@@ -935,7 +987,7 @@ const namespaceBindingFor = (source) => Match.value(source).pipe(Match.when((can
935
987
  }))));
936
988
  const moduleNameFromName = (name) => Arr.findFirst(MODULE_NAMES, (module) => module === name);
937
989
  const moduleNameFromSource = (source) => Arr.findFirst(MODULE_NAMES, (module) => source === `${EFFECT_SOURCE}/${module}`);
938
- const resolveEffectCallDataFirst = (node, context) => Option.flatMap(programOf(context), (program) => resolveIn(node, context, importTableFor(program)));
990
+ const resolveEffectCallDataFirst = (node, context) => Option.flatMap(programOf(context), (program) => resolveIn(node, context, buildImportTable(program)));
939
991
  const resolveEffectCall = dual((args) => args.length >= 2, resolveEffectCallDataFirst);
940
992
  const programOf = (context) => Arr.findLast(context.ancestors, isProgram);
941
993
  const resolveIn = (node, context, table) => Match.value(node).pipe(Match.when(isCallExpression$1, (call) => resolveCall(call, context, table)), Match.when(isMemberExpression, (member) => resolveReference(member, context, table)), Match.when(isIdentifier$1, (reference) => resolveReference(reference, context, table)), Match.orElse(() => Option.none()));
@@ -1341,9 +1393,10 @@ function eqNode(a, b) {
1341
1393
  return identity !== void 0 && identity === nodeIdentity(b);
1342
1394
  }
1343
1395
  function nodeIdentity(node) {
1344
- const span = spanOf(node);
1345
- if (span === void 0) return;
1346
- return `${node.type}:${span.start}:${span.end}`;
1396
+ return Option.match(Option.fromNullishOr(spanOf(node)), {
1397
+ onNone: () => void 0,
1398
+ onSome: (span) => `${node.type}:${span.start}:${span.end}`
1399
+ });
1347
1400
  }
1348
1401
  function orDefault(value, fallback) {
1349
1402
  return value ?? fallback;
@@ -1391,14 +1444,15 @@ function cloneWithReplacement(mutant, originalTree) {
1391
1444
  function hasReplaced(root, original, replacement) {
1392
1445
  let applied = false;
1393
1446
  traverse(make(root), { enter(path) {
1394
- if (!applied) applied = replaceFirstMatch(path, original, replacement);
1447
+ applied = applied || replaceFirstMatch(path, original, replacement);
1395
1448
  } });
1396
1449
  return applied;
1397
1450
  }
1398
1451
  function replaceFirstMatch(path, original, replacement) {
1399
- if (eqNode(path.node, original) === false) return false;
1400
- path.replaceWith(replacement);
1401
- return true;
1452
+ return Match.value(eqNode(path.node, original)).pipe(Match.when(false, () => false), Match.orElse(() => {
1453
+ path.replaceWith(replacement);
1454
+ return true;
1455
+ }));
1402
1456
  }
1403
1457
  /**
1404
1458
  * The mutations of a regular expression pattern.
@@ -1427,17 +1481,16 @@ function replaceFirstMatch(path, original, replacement) {
1427
1481
  * quantifier removal ahead of class negation.
1428
1482
  */
1429
1483
  function mutateRegexPattern(pattern, flags) {
1430
- if (pattern.length === 0) return [];
1431
- return parseRegexMutants(pattern, orDefault(flags, ""));
1484
+ return pattern.length === 0 ? [] : parseRegexMutants(pattern, orDefault(flags, ""));
1432
1485
  }
1433
1486
  function parseRegexMutants(pattern, flags) {
1434
1487
  try {
1435
1488
  const groups = collectSplices(pattern, flags);
1436
- groups.rest.sort((a, b) => a.start - b.start || a.priority - b.priority);
1489
+ const rest = groups.rest.toSorted((a, b) => a.start - b.start || a.priority - b.priority);
1437
1490
  return [
1438
1491
  ...groups.bol,
1439
1492
  ...groups.eol,
1440
- ...groups.rest
1493
+ ...rest
1441
1494
  ].map((splice) => spliceText(pattern, splice));
1442
1495
  } catch {
1443
1496
  return [];
@@ -1500,18 +1553,17 @@ function negationMarker(negate) {
1500
1553
  }
1501
1554
  function collectCharacterClass(characterClass, groups) {
1502
1555
  const pos = characterClass.start + 1;
1503
- if (characterClass.negate) groups.rest.push({
1556
+ groups.rest.push(Match.value(characterClass.negate).pipe(Match.when(true, () => ({
1504
1557
  start: pos,
1505
1558
  end: pos + 1,
1506
1559
  text: "",
1507
1560
  priority: 1
1508
- });
1509
- else groups.rest.push({
1561
+ })), Match.orElse(() => ({
1510
1562
  start: pos,
1511
1563
  end: pos,
1512
1564
  text: "^",
1513
1565
  priority: 1
1514
- });
1566
+ }))));
1515
1567
  }
1516
1568
  function collectCharacterSet(characterSet, groups) {
1517
1569
  pushWhen(groups.rest, characterSetSplice(characterSet));
@@ -1541,12 +1593,10 @@ function classSplice(characterSet) {
1541
1593
  /** `\d`-style sets invert by letter case, `\p{}` by `p`/`P`. */
1542
1594
  function invertedMarker(marker) {
1543
1595
  const propertyMarker = PROPERTY_MARKERS[marker];
1544
- if (propertyMarker !== void 0) return propertyMarker;
1545
- return invertLetterCase(marker);
1596
+ return propertyMarker === void 0 ? invertLetterCase(marker) : propertyMarker;
1546
1597
  }
1547
1598
  function invertLetterCase(letter) {
1548
- if (letter === letter.toUpperCase()) return letter.toLowerCase();
1549
- return letter.toUpperCase();
1599
+ return letter === letter.toUpperCase() ? letter.toLowerCase() : letter.toUpperCase();
1550
1600
  }
1551
1601
  function collectQuantifier(quantifier, groups) {
1552
1602
  groups.rest.push({
@@ -1562,11 +1612,15 @@ function anchorRemoval(assertion, pattern) {
1562
1612
  end: assertion.end,
1563
1613
  text: ""
1564
1614
  };
1565
- if (spliceText(pattern, splice).length === 0) return;
1566
- return splice;
1615
+ return spliceText(pattern, splice).length === 0 ? void 0 : splice;
1567
1616
  }
1568
1617
  function pushWhen(list, splice) {
1569
- if (splice !== void 0) list.push(splice);
1618
+ Option.match(Option.fromNullishOr(splice), {
1619
+ onNone: () => void 0,
1620
+ onSome: (present) => {
1621
+ list.push(present);
1622
+ }
1623
+ });
1570
1624
  }
1571
1625
  function spliceText(pattern, splice) {
1572
1626
  return pattern.slice(0, splice.start) + splice.text + pattern.slice(splice.end);
@@ -1620,16 +1674,14 @@ function isStringConcatenation(node) {
1620
1674
  }
1621
1675
  /** A chained `a + b + c` carries its value on the innermost left operand's right side. */
1622
1676
  function outerLeftOperand(node) {
1623
- if (node.left.type === "BinaryExpression") return node.left.right;
1624
- return node.left;
1677
+ return node.left.type === "BinaryExpression" ? node.left.right : node.left;
1625
1678
  }
1626
1679
  const arrayDeclarationMutator = (node) => Match.value(node).pipe(Match.when(isArrayExpression, (array) => [arrayDeclarationReplacement(array)]), Match.when(isArrayConstructorCall, (construct) => [arrayConstructorReplacement(construct)]), Match.orElse(() => NO_MUTANTS));
1627
1680
  function isArrayExpression(node) {
1628
1681
  return node.type === "ArrayExpression";
1629
1682
  }
1630
1683
  function arrayDeclarationReplacement(array) {
1631
- if (array.elements.length > 0) return arrayExpression([]);
1632
- return arrayExpression([stringLiteral("Stryker was here")]);
1684
+ return array.elements.length > 0 ? arrayExpression([]) : arrayExpression([stringLiteral("Stryker was here")]);
1633
1685
  }
1634
1686
  function isArrayConstructorCall(node) {
1635
1687
  return isCallOrNewExpression(node) && isArrayIdentifier(node.callee);
@@ -1642,12 +1694,11 @@ function isArrayIdentifier(node) {
1642
1694
  }
1643
1695
  function arrayConstructorReplacement(construct) {
1644
1696
  const mutatedCallArgs = constructorArguments(construct.arguments);
1645
- if (construct.type === "NewExpression") return newExpression(cloneNode(construct.callee), mutatedCallArgs);
1646
- return callExpression(cloneNode(construct.callee), mutatedCallArgs);
1697
+ const callee = cloneNode(construct.callee);
1698
+ return construct.type === "NewExpression" ? newExpression(callee, mutatedCallArgs) : callExpression(callee, mutatedCallArgs);
1647
1699
  }
1648
1700
  function constructorArguments(args) {
1649
- if (args.length > 0) return [];
1650
- return [arrayExpression([])];
1701
+ return args.length > 0 ? [] : [arrayExpression([])];
1651
1702
  }
1652
1703
  const arrowFunctionMutator = (node) => Match.value(node).pipe(Match.when(isExpressionBodiedArrow, () => [arrowFunctionExpression([], identifier("undefined"))]), Match.orElse(() => NO_MUTANTS));
1653
1704
  function isExpressionBodiedArrow(node) {
@@ -1683,8 +1734,7 @@ function isStringLiteral(value) {
1683
1734
  return nodeType(value) === "Literal" && hasStringValue(value);
1684
1735
  }
1685
1736
  function hasStringValue(value) {
1686
- if (!Predicate.hasProperty(value, "value")) return false;
1687
- return typeof value["value"] === "string";
1737
+ return Predicate.hasProperty(value, "value") && typeof value["value"] === "string";
1688
1738
  }
1689
1739
  const stringAssignmentTypes = Object.freeze([
1690
1740
  "&&=",
@@ -1767,8 +1817,7 @@ function hasSuperInChildren(node) {
1767
1817
  return Object.keys(node).some((key) => containsSuperInValue(propertyOf(node, key)));
1768
1818
  }
1769
1819
  function containsSuperInValue(value) {
1770
- if (Array.isArray(value)) return value.some(containsSuperCall);
1771
- return containsSuperCall(value);
1820
+ return Array.isArray(value) ? value.some(containsSuperCall) : containsSuperCall(value);
1772
1821
  }
1773
1822
  const booleanLiteralMutator = (node) => Match.value(node).pipe(Match.when(isBooleanLiteral, (literal) => [booleanLiteral(!literal.value)]), Match.when(isNegatedPrefix, (unary) => [cloneNode(unary.argument)]), Match.orElse(() => NO_MUTANTS));
1774
1823
  function isBooleanLiteral(node) {
@@ -1919,8 +1968,12 @@ const baseReplacements = {
1919
1968
  setUTCSeconds: "setUTCMilliseconds"
1920
1969
  };
1921
1970
  const noReverseReplacements = ["getUTCDate", "setUTCDate"];
1922
- const replacements = new Map(Object.entries(baseReplacements));
1923
- for (const [key, value] of Object.entries(baseReplacements)) if (value !== null && !noReverseReplacements.includes(key)) replacements.set(value, key);
1971
+ const isReversible = (key, value) => value !== null && !noReverseReplacements.includes(key);
1972
+ const reverseReplacements = Object.fromEntries(Object.entries(baseReplacements).flatMap(([key, value]) => isReversible(key, value) ? [[value, key]] : []));
1973
+ const replacements = {
1974
+ ...baseReplacements,
1975
+ ...reverseReplacements
1976
+ };
1924
1977
  const methodExpressionMutator = (node) => Match.value(node).pipe(Match.when(isCallExpression, (call) => methodCallMutants(call)), Match.orElse(() => NO_MUTANTS));
1925
1978
  function methodCallMutants(call) {
1926
1979
  return Match.value(methodMutation(call)).pipe(Match.when(isMethodMutation, (mutation) => [methodExpressionReplacement(mutation)]), Match.orElse(() => NO_MUTANTS));
@@ -1934,7 +1987,9 @@ function methodMutation(call) {
1934
1987
  return Match.value(callee).pipe(Match.when(void 0, () => void 0), Match.orElse((member) => mutationFor(call, member)));
1935
1988
  }
1936
1989
  function mutationFor(call, callee) {
1937
- return Match.value(replacements.get(callee.property.name)).pipe(Match.when(void 0, () => void 0), Match.orElse((newName) => ({
1990
+ const methodName = callee.property.name;
1991
+ const replacement = Object.hasOwn(replacements, methodName) ? replacements[methodName] : void 0;
1992
+ return Match.value(replacement).pipe(Match.when(void 0, () => void 0), Match.orElse((newName) => ({
1938
1993
  call,
1939
1994
  callee,
1940
1995
  newName
@@ -2017,16 +2072,17 @@ const PLACEHOLDER = "Stryker was here!";
2017
2072
  const stringLiteralMutator = (node, context) => Match.value(node).pipe(Match.when(isTemplateLiteral, (template) => templateMutants(template)), Match.when(isStringLiteral, (literal) => mutantsWhen(isValidParent(literal, context), () => [stringLiteral(replacementText(literal.value.length === 0))])), Match.orElse(() => NO_MUTANTS));
2018
2073
  function templateMutants(template) {
2019
2074
  const first = template.quasis[0];
2020
- if (first === void 0) return NO_MUTANTS;
2021
- return [emptyOrPlaceholderTemplate(template, first)];
2075
+ return Option.match(Option.fromNullishOr(first), {
2076
+ onNone: () => NO_MUTANTS,
2077
+ onSome: (present) => [emptyOrPlaceholderTemplate(template, present)]
2078
+ });
2022
2079
  }
2023
2080
  function emptyOrPlaceholderTemplate(template, first) {
2024
2081
  const isEmptyTemplate = [template.quasis.length === 1, first.value.raw.length === 0].every(Boolean);
2025
2082
  return templateLiteral([templateElement(replacementText(isEmptyTemplate))], []);
2026
2083
  }
2027
2084
  function replacementText(isEmpty) {
2028
- if (isEmpty) return PLACEHOLDER;
2029
- return "";
2085
+ return isEmpty ? PLACEHOLDER : "";
2030
2086
  }
2031
2087
  function isValidParent(child, context) {
2032
2088
  const parent = context.parent;
@@ -2181,10 +2237,7 @@ const selectMutators = dual((args) => args.length >= 2, selectMutatorsDataFirst)
2181
2237
  var ParseFailed = class extends S.TaggedError("@systemfsoftware/stryker-js-instrumenter/Parser.schema/ParseFailed")("ParseFailed", {
2182
2238
  fileName: S.String,
2183
2239
  message: S.String,
2184
- location: S.Struct({
2185
- line: S.Finite,
2186
- column: S.Finite
2187
- }),
2240
+ location: Position,
2188
2241
  cause: S.Defect()
2189
2242
  }) {
2190
2243
  get message() {
@@ -2195,61 +2248,70 @@ var ParseFailed = class extends S.TaggedError("@systemfsoftware/stryker-js-instr
2195
2248
  //#region src/Parser.service.ts
2196
2249
  const oxcModule = Effect.cached(Effect.promise(() => import("oxc-parser")));
2197
2250
  const loadOxc = Effect.flatMap(oxcModule, (load) => load);
2198
- const parseWithOxcDataFirst = (text, fileName, lang) => Effect.gen(function* () {
2251
+ const parseWithOxcDataFirst = Effect.fn("stryker.instrument.parser.parseWithOxc")(function* (text, fileName, lang) {
2199
2252
  const result = (yield* loadOxc).parseSync(fileName, text, {
2200
2253
  lang,
2201
2254
  range: true
2202
2255
  });
2203
- const lineTable = yield* Effect.orDie(S.decodeEffect(LineTableFromText)(text));
2204
- const failure = oxcParseFailure(result.errors, fileName, lineTable);
2205
- if (failure !== void 0) return yield* failure;
2206
- return {
2207
- root: result.program,
2208
- comments: result.comments
2209
- };
2256
+ const lineStarts = lineStartsOf(text);
2257
+ const failure = oxcParseFailure(result.errors, fileName, lineStarts);
2258
+ return yield* Option.match(Option.fromNullishOr(failure), {
2259
+ onNone: () => Effect.succeed({
2260
+ root: result.program,
2261
+ comments: result.comments
2262
+ }),
2263
+ onSome: (failed) => Effect.fail(failed)
2264
+ });
2210
2265
  });
2211
2266
  const parseWithOxc = dual((args) => args.length >= 3, parseWithOxcDataFirst);
2212
- const oxcParseFailure = (errors, fileName, lineTable) => {
2213
- const first = errors.at(0);
2214
- if (first === void 0) return;
2215
- return ParseFailed.make({
2267
+ const oxcParseFailure = (errors, fileName, lineStarts) => Option.match(Option.fromNullishOr(errors.at(0)), {
2268
+ onNone: () => void 0,
2269
+ onSome: (first) => ParseFailed.make({
2216
2270
  fileName,
2217
2271
  message: first.message,
2218
- location: lineTable.positionAt(oxcErrorLabelStart(first)),
2272
+ location: positionAt(lineStarts, oxcErrorLabelStart(first)),
2219
2273
  cause: errors.map((reported) => reported.message)
2220
- });
2221
- };
2222
- const oxcErrorLabelStart = (error) => {
2223
- const label = error.labels.at(0);
2224
- if (label === void 0) return 0;
2225
- return label.start;
2226
- };
2274
+ })
2275
+ });
2276
+ const oxcErrorLabelStart = (error) => Option.getOrElse(Option.map(Option.fromNullishOr(error.labels.at(0)), (label) => label.start), () => 0);
2227
2277
  const createParser = () => ({ parse: (code, fileName, scriptFormat) => Match.value(scriptFormat).pipe(Match.when("js", () => parseJS(code, fileName)), Match.when("ts", () => parseTS(code, fileName)), Match.when("tsx", () => parseTsx(code, fileName)), Match.exhaustive) });
2228
- const parseJSDataFirst = (text, fileName) => Effect.map(parseWithOxc(text, fileName, "js"), ({ root, comments }) => ({
2229
- originFileName: fileName,
2230
- rawContent: text,
2231
- format: "js",
2232
- root,
2233
- comments
2234
- }));
2278
+ const parseJSDataFirst = Effect.fn("stryker.instrument.parser.parseJS")(function* (text, fileName) {
2279
+ const parsed = yield* parseWithOxc(text, fileName, "js");
2280
+ return {
2281
+ originFileName: fileName,
2282
+ rawContent: text,
2283
+ format: "js",
2284
+ root: parsed.root,
2285
+ comments: parsed.comments
2286
+ };
2287
+ });
2235
2288
  const parseJS = dual((args) => args.length >= 2, parseJSDataFirst);
2236
- const parseTSDataFirst = (text, fileName) => Effect.map(parseWithOxc(text, fileName, "ts"), ({ root, comments }) => ({
2237
- originFileName: fileName,
2238
- rawContent: text,
2239
- format: "ts",
2240
- root,
2241
- comments
2242
- }));
2289
+ const parseTSDataFirst = Effect.fn("stryker.instrument.parser.parseTS")(function* (text, fileName) {
2290
+ const parsed = yield* parseWithOxc(text, fileName, "ts");
2291
+ return {
2292
+ originFileName: fileName,
2293
+ rawContent: text,
2294
+ format: "ts",
2295
+ root: parsed.root,
2296
+ comments: parsed.comments
2297
+ };
2298
+ });
2243
2299
  const parseTS = dual((args) => args.length >= 2, parseTSDataFirst);
2244
- const parseTsxDataFirst = (text, fileName) => Effect.map(parseWithOxc(text, fileName, "tsx"), ({ root, comments }) => ({
2245
- root,
2246
- comments,
2247
- format: "tsx",
2248
- originFileName: fileName,
2249
- rawContent: text
2250
- }));
2300
+ const parseTsxDataFirst = Effect.fn("stryker.instrument.parser.parseTsx")(function* (text, fileName) {
2301
+ const parsed = yield* parseWithOxc(text, fileName, "tsx");
2302
+ return {
2303
+ root: parsed.root,
2304
+ comments: parsed.comments,
2305
+ format: "tsx",
2306
+ originFileName: fileName,
2307
+ rawContent: text
2308
+ };
2309
+ });
2251
2310
  const parseTsx = dual((args) => args.length >= 2, parseTsxDataFirst);
2252
2311
  //#endregion
2312
+ //#region src/print/PrintFailed.schema.ts
2313
+ var PrintFailed = class extends S.TaggedError("@systemfsoftware/stryker-js-instrumenter/print/PrintFailed.schema/PrintFailed")("PrintFailed", { message: S.String }) {};
2314
+ //#endregion
2253
2315
  //#region src/print/SourceText.ts
2254
2316
  const printProgramDataFirst = (program, opts = {}) => programText(opts, program);
2255
2317
  const printProgram = dual((args) => Predicate.hasProperty(args[0], "type"), printProgramDataFirst);
@@ -3239,28 +3301,19 @@ const externalModuleArgumentText = (value) => Boolean$1.match(Predicate.isTruthy
3239
3301
  function printDataFirst(file, entryForFormat) {
3240
3302
  const context = { print: (inner) => print(inner, entryForFormat) };
3241
3303
  return Option.match(entryForFormat(formatKeyOf(file)), {
3242
- onNone: () => missingFormatPrint(file),
3243
- onSome: (entry) => entry.print(file, context)
3304
+ onNone: () => Result.fail(PrintFailed.make({ message: `No registered format renders the "${formatKeyOf(file)}" AST` })),
3305
+ onSome: (entry) => Result.succeed(entry.print(file, context))
3244
3306
  });
3245
3307
  }
3246
3308
  const print = dual((args) => args.length >= 2, printDataFirst);
3247
- function missingFormatPrint(file) {
3248
- throw new Error(`No registered format renders the "${formatKeyOf(file)}" AST`);
3249
- }
3250
- function hashbangOf(root) {
3251
- if (!hasHashbang(root)) return null;
3252
- return printHashbang(root.hashbang);
3253
- }
3309
+ const hashbangOf = (root) => hasHashbang(root) ? printHashbang(root.hashbang) : null;
3254
3310
  const hasHashbang = (root) => Predicate.hasProperty(root, "hashbang") && Predicate.isObject(root.hashbang);
3255
3311
  const printHashbang = (field) => ({
3256
3312
  type: "Hashbang",
3257
3313
  value: hashbangValue(field),
3258
3314
  start: field.start
3259
3315
  });
3260
- function hashbangValue(field) {
3261
- if (typeof field.value !== "string") return "";
3262
- return field.value;
3263
- }
3316
+ const hashbangValue = (field) => typeof field.value === "string" ? field.value : "";
3264
3317
  const printScriptAst$1 = (root) => printProgram(root, { hashbang: hashbangOf(root) });
3265
3318
  const jsPrintDataFirst = (file) => printScriptAst$1(file.root);
3266
3319
  const jsPrint = dual((args) => args.length >= 2, jsPrintDataFirst);
@@ -3302,7 +3355,11 @@ var FormatOverrideUnclaimed = class extends S.TaggedError()("FormatOverrideUncla
3302
3355
  fileName: S.String,
3303
3356
  formatId: S.String,
3304
3357
  reason: S.String
3305
- }) {};
3358
+ }) {
3359
+ get message() {
3360
+ return `Cannot instrument ${this.fileName}: ${this.reason}`;
3361
+ }
3362
+ };
3306
3363
  const pinOf = (command) => Option.fromUndefinedOr(command.formatId);
3307
3364
  const matchesCommand = (claim, command) => Match.value(pinOf(command)).pipe(Match.when(Option.isSome, (pin) => claim.formatId === pin.value), Match.orElse(() => claim.extensions.includes(command.extension)));
3308
3365
  const claimFor = (command) => command.claims.find((claim) => matchesCommand(claim, command));
@@ -3344,51 +3401,10 @@ const resolveFormat = Workflow.make({
3344
3401
  decide: (command) => Match.value(selectionFor(command)).pipe(Match.tag("ClaimSelected", ({ claim }) => Result.succeed(assignedFrom(claim, command))), Match.tag("PinUnclaimed", ({ formatId }) => Result.fail(overrideUnclaimedFrom(command, formatId))), Match.tag("ExtensionUnclaimed", () => Result.succeed(skippedFrom(command))), Match.exhaustive)
3345
3402
  });
3346
3403
  //#endregion
3347
- //#region src/directives/directive.schema.ts
3348
- const MutatorNameSchema = S.String.pipe(S.check(S.isPattern(/^[a-zA-Z]+(?: [a-zA-Z]+)*$/)));
3349
- const DirectiveReasonSchema = S.String.pipe(S.check(S.isPattern(/^\S(?:[\s\S]*\S)?$/)));
3350
- const DirectiveSchema = S.Struct({
3351
- action: S.Literals(["disable", "restore"]),
3352
- scope: S.Literals(["next-line", "block"]),
3353
- mutatorNames: S.Array(MutatorNameSchema).check(S.isMinLength(1)),
3354
- reason: DirectiveReasonSchema
3355
- });
3356
- const LocatedPositionSchema = S.Struct({
3357
- line: SourceLineSchema,
3358
- column: SourceColumnSchema
3359
- });
3360
- const LocatedDirectiveSchema = S.Struct({
3361
- directive: DirectiveSchema,
3362
- at: LocatedPositionSchema,
3363
- governedLine: SourceLineSchema
3364
- });
3365
- S.Struct({
3366
- directive: DirectiveSchema,
3367
- at: LocatedPositionSchema,
3368
- mutatorName: S.String
3369
- });
3370
- //#endregion
3371
3404
  //#region src/directives/decode-directive.workflow.ts
3372
3405
  const DIRECTIVE_PATTERN = /^\s?Stryker (disable|restore)(?: (next-line))? ([a-zA-Z, ]+)(?::(.+)?)?/;
3373
3406
  const DEFAULT_REASON = "Ignored using a comment";
3374
3407
  const NEXT_LINE$1 = "next-line";
3375
- S.Struct({
3376
- clause: S.Literals([
3377
- " Stryker disable",
3378
- " Stryker restore",
3379
- " Stryker disable next-line",
3380
- " Stryker restore next-line",
3381
- " Stryker enable",
3382
- " nothing"
3383
- ]),
3384
- nameLetters: S.Array(S.Literals([
3385
- "a",
3386
- "b",
3387
- "Z",
3388
- ",",
3389
- " "
3390
- ])).check(S.isMinLength(1))
3391
- });
3392
3408
  var DecodeDirectiveCommand = class extends S.TaggedClass()("DecodeDirectiveCommand", { commentText: S.String }) {
3393
3409
  static [Workflow.InstrumentationBrand] = {};
3394
3410
  };
@@ -3496,27 +3512,34 @@ const loadHeader = Effect.map(parseWithOxc(INSTRUMENTATION_HEADER_SOURCE, "instr
3496
3512
  instrumentationHeaderValue = parsed.root.body;
3497
3513
  return parsed.root.body;
3498
3514
  });
3499
- const instrumentationHeader = instrumentationHeaderValue !== void 0 ? Effect.succeed(instrumentationHeaderValue) : loadHeader;
3515
+ const instrumentationHeader = Option.match(Option.fromNullishOr(instrumentationHeaderValue), {
3516
+ onNone: () => loadHeader,
3517
+ onSome: (header) => Effect.succeed(header)
3518
+ });
3500
3519
  const placeHeader = (root) => Effect.map(headerFor(root), (header) => {
3501
3520
  root.body.unshift(...header);
3502
3521
  });
3503
- const headerFor = (root) => Effect.map(instrumentationHeader, (header) => Option.match(leadingCommentsOf(root), {
3522
+ const headerFor = (root) => Effect.map(instrumentationHeader, (header) => Option.match(Option.all([Option.fromNullishOr(header[0]), leadingCommentsOf(root)]), {
3504
3523
  onNone: () => header,
3505
- onSome: (leadingComments) => [commentedHeader(leadingComments, header), ...header.slice(1)]
3524
+ onSome: ([firstHeader, leadingComments]) => commentedHeader(firstHeader, leadingComments, header.slice(1))
3506
3525
  }));
3507
- const placeHeaderIfNeededDataFirst = (placed, options, root) => placed ? placeHeaderWhenWanted(options, root) : Effect.void;
3526
+ const placeHeaderIfNeededDataFirst = (placed, options, root) => Boolean$1.match(placed, {
3527
+ onTrue: () => placeHeaderWhenWanted(options, root),
3528
+ onFalse: () => Effect.void
3529
+ });
3508
3530
  const placeHeaderIfNeeded = dual((args) => args.length >= 3, placeHeaderIfNeededDataFirst);
3509
- const placeHeaderWhenWanted = (options, root) => options.noHeader === true ? Effect.void : placeHeader(root);
3531
+ const placeHeaderWhenWanted = (options, root) => Boolean$1.match(options.noHeader === true, {
3532
+ onTrue: () => Effect.void,
3533
+ onFalse: () => placeHeader(root)
3534
+ });
3510
3535
  function leadingCommentsOf(root) {
3511
3536
  const first = root.body[0];
3512
- if (!hasLeadingComments$1(first)) return Option.none();
3513
- return Option.some(first.leadingComments);
3537
+ return Option.map(Option.liftPredicate(first, hasLeadingComments$1), (value) => value.leadingComments);
3514
3538
  }
3515
- function commentedHeader(leadingComments, header) {
3516
- const firstHeader = Option.getOrThrowWith(Option.fromNullishOr(header[0]), () => /* @__PURE__ */ new Error("Instrumentation header is empty"));
3539
+ function commentedHeader(firstHeader, leadingComments, rest) {
3517
3540
  const cloned = cloneNode(firstHeader);
3518
3541
  Object.assign(cloned, { leadingComments });
3519
- return cloned;
3542
+ return [cloned, ...rest];
3520
3543
  }
3521
3544
  //#endregion
3522
3545
  //#region src/place-mutants.workflow.ts
@@ -3532,8 +3555,8 @@ const ReplacementFactsSchema = S.Struct({
3532
3555
  isSwitchCase: S.Boolean
3533
3556
  });
3534
3557
  const PlacedMutantSchema = S.Struct({
3535
- id: S.String,
3536
- mutatorName: S.String,
3558
+ id: MutantId,
3559
+ mutatorName: MutatorNameSchema,
3537
3560
  replacement: ReplacementFactsSchema
3538
3561
  });
3539
3562
  var PlaceMutantsCommand = class extends S.TaggedClass()("PlaceMutantsCommand", {
@@ -3546,19 +3569,19 @@ var PlaceMutantsCommand = class extends S.TaggedClass()("PlaceMutantsCommand", {
3546
3569
  const PlacementDecisionTypeId = Symbol.for("@systemfsoftware/stryker-js-instrumenter/PlacementDecision");
3547
3570
  var ExpressionSite = class extends S.TaggedClass()("ExpressionSite", {
3548
3571
  fileName: S.String,
3549
- mutantIds: S.Array(S.String)
3572
+ mutantIds: S.Array(MutantId)
3550
3573
  }) {
3551
3574
  [PlacementDecisionTypeId] = PlacementDecisionTypeId;
3552
3575
  };
3553
3576
  var StatementSite = class extends S.TaggedClass()("StatementSite", {
3554
3577
  fileName: S.String,
3555
- mutantIds: S.Array(S.String)
3578
+ mutantIds: S.Array(MutantId)
3556
3579
  }) {
3557
3580
  [PlacementDecisionTypeId] = PlacementDecisionTypeId;
3558
3581
  };
3559
3582
  var SwitchCaseSite = class extends S.TaggedClass()("SwitchCaseSite", {
3560
3583
  fileName: S.String,
3561
- mutantIds: S.Array(S.String)
3584
+ mutantIds: S.Array(MutantId)
3562
3585
  }) {
3563
3586
  [PlacementDecisionTypeId] = PlacementDecisionTypeId;
3564
3587
  };
@@ -3570,11 +3593,19 @@ const ExpectedKindSchema = S.Literals([
3570
3593
  var MutantKindMismatch = class extends S.TaggedError()("MutantKindMismatch", {
3571
3594
  fileName: S.String,
3572
3595
  placer: PlacerNameSchema,
3573
- mutantId: S.String,
3574
- mutatorName: S.String,
3596
+ mutantId: MutantId,
3597
+ mutatorName: MutatorNameSchema,
3575
3598
  expected: ExpectedKindSchema
3576
- }) {};
3577
- var NoPlacerClaimsNode = class extends S.TaggedError()("NoPlacerClaimsNode", { fileName: S.String }) {};
3599
+ }) {
3600
+ get message() {
3601
+ return `Expected ${this.expected} for mutant ${this.mutantId} (${this.mutatorName}) in ${this.fileName}`;
3602
+ }
3603
+ };
3604
+ var NoPlacerClaimsNode = class extends S.TaggedError()("NoPlacerClaimsNode", { fileName: S.String }) {
3605
+ get message() {
3606
+ return `No placer claims the node in ${this.fileName}`;
3607
+ }
3608
+ };
3578
3609
  const claimingPlacer = (facts) => Match.value(facts).pipe(Match.when({
3579
3610
  isExpression: true,
3580
3611
  expressionIsValid: true
@@ -3624,23 +3655,19 @@ const placeMutants = Workflow.make({
3624
3655
  });
3625
3656
  //#endregion
3626
3657
  //#region src/plan-mutants.workflow.ts
3627
- const LocationSchema = S.Struct({
3628
- start: NodePositionSchema,
3629
- end: NodePositionSchema
3630
- });
3631
3658
  const WILDCARD = "all";
3632
3659
  const NEXT_LINE = "next-line";
3633
3660
  const MutantCandidateSchema = S.Struct({
3634
- mutatorName: S.String,
3661
+ mutatorName: MutatorNameSchema,
3635
3662
  replacementCode: S.String,
3636
- location: S.optional(LocationSchema),
3663
+ location: S.optional(Location),
3637
3664
  ignorerReason: S.optional(S.String)
3638
3665
  });
3639
3666
  const PlannedMutantSchema = S.Struct({
3640
- id: S.String,
3641
- mutatorName: S.String,
3667
+ id: MutantId,
3668
+ mutatorName: MutatorNameSchema,
3642
3669
  replacementCode: S.String,
3643
- location: LocationSchema,
3670
+ location: Location,
3644
3671
  ignoreReason: S.optional(S.String)
3645
3672
  });
3646
3673
  const MutantCounterSchema = S.Int.pipe(S.check(S.isBetween({
@@ -3651,10 +3678,10 @@ const MutantCountSchema = S.Int.pipe(S.check(S.isGreaterThanOrEqualTo(0)));
3651
3678
  var PlanMutantsCommand = class extends S.TaggedClass()("PlanMutantsCommand", {
3652
3679
  fileName: S.String,
3653
3680
  firstIndex: MutantCounterSchema,
3654
- offset: NodePositionSchema,
3655
- line: SourceLineSchema,
3656
- mutatorNames: S.Array(S.String),
3657
- excludedMutations: S.Array(S.String),
3681
+ offset: ScriptOrigin,
3682
+ line: Line,
3683
+ mutatorNames: S.Array(MutatorNameSchema),
3684
+ excludedMutations: S.Array(MutatorNameSchema),
3658
3685
  rule: S.Array(LocatedDirectiveSchema),
3659
3686
  directives: S.Array(LocatedDirectiveSchema),
3660
3687
  candidates: S.Array(MutantCandidateSchema)
@@ -3679,8 +3706,12 @@ var MutantsFullyIgnored = class extends S.TaggedClass()("MutantsFullyIgnored", {
3679
3706
  };
3680
3707
  var MutantWithoutLocation = class extends S.TaggedError()("MutantWithoutLocation", {
3681
3708
  fileName: S.String,
3682
- mutatorName: S.String
3683
- }) {};
3709
+ mutatorName: MutatorNameSchema
3710
+ }) {
3711
+ get message() {
3712
+ return `Mutant without a source location: ${this.mutatorName} in ${this.fileName}`;
3713
+ }
3714
+ };
3684
3715
  const reachedLine = (located, line) => Match.value(located.directive.scope).pipe(Match.when(NEXT_LINE, () => located.governedLine === line), Match.when("block", () => true), Match.exhaustive);
3685
3716
  const namesMutator = (located, lowerMutatorName) => located.directive.mutatorNames.some((name) => [name === WILDCARD, name.toLowerCase() === lowerMutatorName].some(Boolean));
3686
3717
  const lastReachingDirective = (rule, lowerMutatorName, line) => Option.fromNullishOr(rule.findLast((located) => [reachedLine(located, line), namesMutator(located, lowerMutatorName)].every(Boolean)));
@@ -3697,12 +3728,13 @@ const unusedDirectiveWarning = (unused, fileName) => {
3697
3728
  };
3698
3729
  const warningsOf = (command) => unusedDirectives(command.directives, command.mutatorNames).map((unused) => unusedDirectiveWarning(unused, command.fileName));
3699
3730
  /**
3700
- * Both the node span (`source`) and the region origin (`offset`) are 1-based
3701
- * file coordinates: the region's first line carries its column origin, later
3702
- * lines start at column 1, so the shift adds `offset.line - 1` lines and —
3703
- * only when the node sits on the region's first line — `offset.column`.
3731
+ * The node span (`source`) is a 1-based position inside the embedded script;
3732
+ * the region origin (`offset`) is where that script begins in its host file.
3733
+ * The region's first line carries its column shift, later lines start at
3734
+ * column 1, so the shift adds `offset.line - 1` lines and — only when the node
3735
+ * sits on the region's first line — `offset.columnShift`.
3704
3736
  */
3705
- const columnOffsetOf = (source, offset) => Match.value(source.line === 1).pipe(Match.when(true, () => offset.column), Match.when(false, () => 0), Match.exhaustive);
3737
+ const columnOffsetOf = (source, offset) => Match.value(source.line === 1).pipe(Match.when(true, () => offset.columnShift), Match.when(false, () => 0), Match.exhaustive);
3706
3738
  const shiftedPosition = (source, offset) => ({
3707
3739
  column: source.column + columnOffsetOf(source, offset),
3708
3740
  line: source.line + offset.line - 1
@@ -3717,7 +3749,7 @@ const plannedMutant = (command, candidate, index) => Option.match(Option.fromNul
3717
3749
  mutatorName: candidate.mutatorName
3718
3750
  })),
3719
3751
  onSome: (location) => Result.succeed({
3720
- id: `${command.firstIndex + index}`,
3752
+ id: PlannedMutantSchema.fields.id.make(`${command.firstIndex + index}`),
3721
3753
  mutatorName: candidate.mutatorName,
3722
3754
  replacementCode: candidate.replacementCode,
3723
3755
  location: shiftedLocation(location, command.offset),
@@ -3753,7 +3785,11 @@ const comparePositions = (a, b) => {
3753
3785
  };
3754
3786
  const locationIncluded = (haystack, needle) => comparePositions(haystack.start, needle.start) <= 0 && comparePositions(haystack.end, needle.end) >= 0;
3755
3787
  const locationOverlaps = (a, b) => comparePositions(a.start, b.end) <= 0 && comparePositions(a.end, b.start) >= 0;
3756
- const errorTextOf$1 = (cause) => Option.getOrElse(Option.map(ErrorText.fromCause(cause), (rendered) => rendered.text), () => "");
3788
+ const errorTextOf$1 = (cause) => Option.getOrElse(Option.map(errorTextOf$2(cause), (rendered) => rendered.text), () => "");
3789
+ const traversalFailure = (cause) => InstrumentError.make({
3790
+ message: cause instanceof Error ? cause.message : errorTextOf$1(cause),
3791
+ cause
3792
+ });
3757
3793
  const DEFAULT_MUTATOR_REGISTRY = {
3758
3794
  defaults: defaultMutators,
3759
3795
  optIn: optInMutators
@@ -3776,28 +3812,20 @@ const locatedDirective = (comment, governedLine) => Option.flatMap(Option.fromNu
3776
3812
  at: loc.start,
3777
3813
  governedLine
3778
3814
  })));
3779
- const directivesOf = (node, governedLineOf) => attachedComments(node).flatMap((comment) => Option.toArray(locatedDirective(comment, governedLineOf(node))));
3815
+ const directivesOf = (node, governedLine) => attachedComments(node).flatMap((comment) => Option.toArray(locatedDirective(comment, governedLine)));
3780
3816
  const foldInto = (rule, directive) => Match.value(foldRule(FoldRuleCommand.make({
3781
3817
  rule,
3782
3818
  directive
3783
3819
  }))).pipe(Match.when(Result.isSuccess, (folded) => folded.success.rule), Match.orElse(() => rule));
3784
3820
  const NO_COMMENTS = [];
3785
- function attachedComments(node) {
3786
- if (!hasLeadingComments(node)) return NO_COMMENTS;
3787
- return node.leadingComments;
3788
- }
3821
+ const attachedComments = (node) => hasLeadingComments(node) ? node.leadingComments : NO_COMMENTS;
3789
3822
  const hasLeadingComments = (value) => Predicate.hasProperty(value, "leadingComments") && Array.isArray(value.leadingComments);
3790
- function ancestorsOf(path) {
3791
- const ancestors = [];
3792
- for (let current = path.parentPath; current !== null; current = current.parentPath) ancestors.push(current.node);
3793
- return ancestors;
3794
- }
3795
- function isTypeNode(path) {
3823
+ function isTypeNode(node) {
3796
3824
  return [
3797
- tsTypeAnnotationNodeTypes.includes(path.node.type),
3798
- flowTypeAnnotationNodeTypes.includes(path.node.type),
3799
- isDeclareVariableStatement(path.node),
3800
- isDeclareModule(path.node)
3825
+ tsTypeAnnotationNodeTypes.includes(node.type),
3826
+ flowTypeAnnotationNodeTypes.includes(node.type),
3827
+ isDeclareVariableStatement(node),
3828
+ isDeclareModule(node)
3801
3829
  ].some((isType) => isType);
3802
3830
  }
3803
3831
  function isDeclareVariableStatement(node) {
@@ -3833,8 +3861,8 @@ const flowTypeAnnotationNodeTypes = Object.freeze([
3833
3861
  "OpaqueType",
3834
3862
  "TypeAlias"
3835
3863
  ]);
3836
- function isImportDeclaration(path) {
3837
- return nodeType(path.node) === "TSImportEqualsDeclaration" || path.node.type === "ImportDeclaration";
3864
+ function isImportDeclaration(node) {
3865
+ return nodeType(node) === "TSImportEqualsDeclaration" || node.type === "ImportDeclaration";
3838
3866
  }
3839
3867
  function mutantTestExpression(mutantId) {
3840
3868
  return callExpression(identifier(IS_MUTANT_ACTIVE_HELPER), [stringLiteral(mutantId)]);
@@ -3842,41 +3870,33 @@ function mutantTestExpression(mutantId) {
3842
3870
  function mutationCoverageSequenceExpression(mutants, targetExpression) {
3843
3871
  const mutantIds = [...mutants].map((mutant) => stringLiteral(mutant.id));
3844
3872
  const sequence = [callExpression(identifier(COVER_MUTANT_HELPER), mutantIds)];
3845
- if (targetExpression !== void 0) sequence.push(targetExpression);
3846
- return sequenceExpression(sequence);
3873
+ return sequenceExpression(targetExpression === void 0 ? sequence : [...sequence, targetExpression]);
3847
3874
  }
3848
3875
  const refusalPlacer = (refusal) => Match.value(refusal).pipe(Match.tag("MutantKindMismatch", (mismatch) => mismatch.placer), Match.tag("MutantsUnapplied", (unapplied) => unapplied.placer), Match.tag("NoPlacerClaimsNode", () => "no placer"), Match.tag("MutantNotApplied", () => "no placer"), Match.exhaustive);
3849
3876
  const refusalDetail = (refusal) => Match.value(refusal).pipe(Match.tag("MutantKindMismatch", (mismatch) => `Expected ${mismatch.expected} for mutant ${mismatch.mutantId}`), Match.tag("MutantsUnapplied", (unapplied) => `Failed to apply ${unapplied.mutatorNames.join(", ")}`), Match.tag("NoPlacerClaimsNode", () => "No placer claims the node"), Match.tag("MutantNotApplied", (unapplied) => `Could not apply the ${unapplied.mutatorName} mutant`), Match.exhaustive);
3850
- const placementFailure = (refusal, nodePath, mutants, fileName, lineTable, basePath) => {
3877
+ const placementFailureMessage = (refusal, node, mutants, fileName, lineStarts, basePath) => {
3851
3878
  const message = `${refusalPlacer(refusal)} could not place mutants with type(s): "${placementListFormat.format(mutants.map((mutant) => mutant.mutatorName))}"`;
3852
- return /* @__PURE__ */ new Error(`${placementLocation(nodePath.node, fileName, lineTable, basePath)} ${message}. Either remove this file from the list of files to be mutated, or exclude the mutator (using \`mutator.excludedMutations\`). Original error: ${refusalDetail(refusal)}`);
3879
+ return `${placementLocation(node, fileName, lineStarts, basePath)} ${message}. Either remove this file from the list of files to be mutated, or exclude the mutator (using \`mutator.excludedMutations\`). Original error: ${refusalDetail(refusal)}`;
3853
3880
  };
3854
3881
  function nodeOfKind(mutant, node, isKind, kind) {
3855
- return narrowNode(node, isKind, `Cannot place mutant ${mutant.id}: expected ${kind}, got ${node.type}`);
3856
- }
3857
- function narrowNode(node, isKind, message) {
3858
- if (isKind(node)) return node;
3859
- throw new Error(message);
3882
+ return isKind(node) ? Result.succeed(node) : Result.fail(/* @__PURE__ */ new Error(`Cannot place mutant ${mutant.id}: expected ${kind}, got ${node.type}`));
3860
3883
  }
3861
3884
  function expressionOf(node) {
3862
- return narrowNode(node, isExpressionKind, `Expected an expression, got ${node.type}`);
3885
+ return isExpressionKind(node) ? Result.succeed(node) : Result.fail(/* @__PURE__ */ new Error(`Expected an expression, got ${node.type}`));
3863
3886
  }
3864
3887
  function statementOf(node) {
3865
- return narrowNode(node, isStatementKind, `Expected a statement, got ${node.type}`);
3888
+ return isStatementKind(node) ? Result.succeed(node) : Result.fail(/* @__PURE__ */ new Error(`Expected a statement, got ${node.type}`));
3866
3889
  }
3867
3890
  function isSwitchCase(node) {
3868
3891
  return nodeType(node) === "SwitchCase";
3869
3892
  }
3870
3893
  function switchCaseOf(node) {
3871
- return narrowNode(node, isSwitchCase, `Expected a switch case, got ${node.type}`);
3894
+ return isSwitchCase(node) ? Result.succeed(node) : Result.fail(/* @__PURE__ */ new Error(`Expected a switch case, got ${node.type}`));
3872
3895
  }
3873
- const fileNameWithin = (basePath, fileName) => {
3874
- if (basePath === void 0) return fileName;
3875
- return relativeTo(basePath, fileName);
3876
- };
3877
- function placementLocation(node, fileName, lineTable, basePath) {
3896
+ const fileNameWithin = (basePath, fileName) => basePath === void 0 ? fileName : relativeTo(basePath, fileName);
3897
+ function placementLocation(node, fileName, lineStarts, basePath) {
3878
3898
  const relativeFile = fileNameWithin(basePath, fileName);
3879
- const position = Option.map(Option.fromNullishOr(spanOf(node)), (span) => lineTable.positionAt(span.start));
3899
+ const position = Option.map(Option.fromNullishOr(spanOf(node)), (span) => positionAt(lineStarts, span.start));
3880
3900
  return Option.match(position, {
3881
3901
  onNone: () => `${relativeFile}:undefined:undefined`,
3882
3902
  onSome: (at) => `${relativeFile}:${at.line}:${at.column}`
@@ -3886,27 +3906,25 @@ const placementListFormat = new Intl.ListFormat("en");
3886
3906
  const normalizeSeparators = (value) => value.replace(/\\/g, "/");
3887
3907
  const withTrailingSlash = (basePath) => {
3888
3908
  const normalized = normalizeSeparators(basePath);
3889
- if (normalized.endsWith("/")) return normalized;
3890
- return `${normalized}/`;
3909
+ return normalized.endsWith("/") ? normalized : `${normalized}/`;
3891
3910
  };
3892
3911
  const relativeTo = (basePath, fileName) => {
3893
3912
  const prefix = withTrailingSlash(basePath);
3894
3913
  const normalizedFile = normalizeSeparators(fileName);
3895
- if (!normalizedFile.startsWith(prefix)) return fileName;
3896
- return normalizedFile.slice(prefix.length);
3914
+ return normalizedFile.startsWith(prefix) ? normalizedFile.slice(prefix.length) : fileName;
3897
3915
  };
3898
3916
  function classOrFunctionExpressionNamedIfNeeded(path) {
3899
- return Match.value(path.node).pipe(Match.when(isAnonymousFunctionOrClass, (node) => nameFromParent(path, node)), Match.orElse(() => void 0));
3917
+ return Option.flatMap(Option.filter(Option.some(path.node), isAnonymousFunctionOrClass), (node) => nameFromParent(path, node));
3900
3918
  }
3901
3919
  function nameFromParent(path, node) {
3902
3920
  return Match.value(path.parentPath?.node).pipe(Match.when(isVariableDeclarator, (declarator) => adoptDeclaredName(node, declarator)), Match.when({
3903
3921
  type: "Property",
3904
3922
  key: { type: "Identifier" }
3905
- }, () => namedPropertyValue(path, node)), Match.orElse(() => void 0));
3923
+ }, () => namedPropertyValue(path, node)), Match.orElse(() => Option.none()));
3906
3924
  }
3907
3925
  /** A property value only survives by name when the declaration above it carries one. */
3908
3926
  function namedPropertyValue(path, node) {
3909
- return Match.value(path.getStatementParent()?.node.type).pipe(Match.when("VariableDeclaration", () => node), Match.orElse(() => void 0));
3927
+ return Match.value(path.getStatementParent()?.node.type).pipe(Match.when("VariableDeclaration", () => Option.some(node)), Match.orElse(() => Option.none()));
3910
3928
  }
3911
3929
  function adoptIdentifier(node, identifier) {
3912
3930
  node.id = identifier;
@@ -3919,14 +3937,11 @@ function isFunctionOrClassExpression(node) {
3919
3937
  return node.type === "FunctionExpression" || node.type === "ClassExpression";
3920
3938
  }
3921
3939
  function arrowFunctionExpressionNamedIfNeeded(path) {
3922
- return Match.value(path.node).pipe(Match.when({ type: "ArrowFunctionExpression" }, (node) => arrowNamedByDeclarator(node, path.parentPath)), Match.orElse(() => void 0));
3940
+ return Match.value(path.node).pipe(Match.when({ type: "ArrowFunctionExpression" }, (node) => arrowNamedByDeclarator(node, path.parentPath)), Match.orElse(() => Option.none()));
3923
3941
  }
3924
3942
  /** An arrow bound to a named declaration is re-emitted as a named function. */
3925
3943
  function arrowNamedByDeclarator(node, parentPath) {
3926
- return Option.match(declaratorIdentifier(parentPath), {
3927
- onNone: () => void 0,
3928
- onSome: (identifier) => namedArrowExpression(node, identifier)
3929
- });
3944
+ return Option.map(declaratorIdentifier(parentPath), (identifier) => namedArrowExpression(node, identifier));
3930
3945
  }
3931
3946
  function namedArrowExpression(node, identifier) {
3932
3947
  const declaration = variableDeclaration("const", [variableDeclarator(identifier, node)]);
@@ -3939,10 +3954,7 @@ function isVariableDeclarator(node) {
3939
3954
  return nodeType(node) === "VariableDeclarator";
3940
3955
  }
3941
3956
  function adoptDeclaredName(node, declarator) {
3942
- return Option.match(declaredName(declarator), {
3943
- onNone: () => void 0,
3944
- onSome: (identifier) => adoptIdentifier(node, identifier)
3945
- });
3957
+ return Option.map(declaredName(declarator), (identifier) => adoptIdentifier(node, identifier));
3946
3958
  }
3947
3959
  function declaredName(declarator) {
3948
3960
  return Match.value(declarator.id).pipe(Match.when(isIdentifierReference, (identifier) => Option.some(identifier)), Match.orElse(() => Option.none()));
@@ -3951,10 +3963,10 @@ function isIdentifierReference(node) {
3951
3963
  return nodeType(node) === "Identifier";
3952
3964
  }
3953
3965
  function nameIfAnonymous(path) {
3954
- return classOrFunctionExpressionNamedIfNeeded(path) ?? arrowNameOrNode(path);
3955
- }
3956
- function arrowNameOrNode(path) {
3957
- return arrowFunctionExpressionNamedIfNeeded(path) ?? expressionOf(path.node);
3966
+ return Option.match(Option.orElse(classOrFunctionExpressionNamedIfNeeded(path), () => arrowFunctionExpressionNamedIfNeeded(path)), {
3967
+ onNone: () => expressionOf(path.node),
3968
+ onSome: (named) => Result.succeed(named)
3969
+ });
3958
3970
  }
3959
3971
  function isChainLink(node) {
3960
3972
  return [
@@ -3972,51 +3984,45 @@ function isCallExpressionNode(node) {
3972
3984
  function isNonNullExpression(node) {
3973
3985
  return nodeType(node) === "TSNonNullExpression";
3974
3986
  }
3975
- function isValidExpression(path) {
3976
- const parent = path.parentPath;
3977
- return parent === null || !isUnmutatableContext(path, parent);
3987
+ function isValidExpression(node, parent) {
3988
+ return parent === null || !isUnmutatableContext(node, parent);
3978
3989
  }
3979
- function isUnmutatableContext(path, parent) {
3990
+ function isUnmutatableContext(node, parent) {
3980
3991
  return [
3981
- isObjectPropertyKey(path, parent),
3982
- isPartOfChain(path, parent),
3992
+ isObjectPropertyKey(node, parent),
3993
+ isPartOfChain(node, parent),
3983
3994
  isTaggedTemplateTag(parent),
3984
- isDeletedOperand(path, parent),
3985
- isAssignedTarget(path, parent)
3995
+ isDeletedOperand(node, parent),
3996
+ isAssignedTarget(node, parent)
3986
3997
  ].some((invalid) => invalid);
3987
3998
  }
3988
- function isObjectPropertyKey(path, parent) {
3989
- const parentNode = parent.node;
3990
- return parentNode.type === "Property" && parentNode.key === path.node;
3999
+ function isObjectPropertyKey(node, parent) {
4000
+ return parent.type === "Property" && parent.key === node;
3991
4001
  }
3992
4002
  function isTaggedTemplateTag(parent) {
3993
- return parent.node.type === "TaggedTemplateExpression";
4003
+ return parent.type === "TaggedTemplateExpression";
3994
4004
  }
3995
- function isDeletedOperand(path, parent) {
3996
- const parentNode = parent.node;
3997
- return parentNode.type === "UnaryExpression" && parentNode.operator === "delete";
4005
+ function isDeletedOperand(node, parent) {
4006
+ return parent.type === "UnaryExpression" && parent.operator === "delete";
3998
4007
  }
3999
- function isAssignedTarget(path, parent) {
4000
- const parentNode = parent.node;
4001
- return parentNode.type === "AssignmentExpression" && parentNode.left === path.node;
4008
+ function isAssignedTarget(node, parent) {
4009
+ return parent.type === "AssignmentExpression" && parent.left === node;
4002
4010
  }
4003
- function isPartOfChain(path, parent) {
4004
- return isChainLink(path.node) && chainContinuesIn(path, parent);
4011
+ function isPartOfChain(node, parent) {
4012
+ return isChainLink(node) && chainContinuesIn(node, parent);
4005
4013
  }
4006
- function chainContinuesIn(path, parent) {
4014
+ function chainContinuesIn(node, parent) {
4007
4015
  return [
4008
- isMemberAccessParent(path, parent),
4009
- isNonNullExpression(parent.node),
4010
- isCalleeParent(path, parent)
4016
+ isMemberAccessParent(node, parent),
4017
+ isNonNullExpression(parent),
4018
+ isCalleeParent(node, parent)
4011
4019
  ].some((continues) => continues);
4012
4020
  }
4013
- function isMemberAccessParent(path, parent) {
4014
- const parentNode = parent.node;
4015
- return parentNode.type === "MemberExpression" && isNotACallOnTheNode(parentNode, path.node);
4021
+ function isMemberAccessParent(node, parent) {
4022
+ return parent.type === "MemberExpression" && isNotACallOnTheNode(parent, node);
4016
4023
  }
4017
- function isCalleeParent(path, parent) {
4018
- const parentNode = parent.node;
4019
- return parentNode.type === "CallExpression" && parentNode.callee === path.node;
4024
+ function isCalleeParent(node, parent) {
4025
+ return parent.type === "CallExpression" && parent.callee === node;
4020
4026
  }
4021
4027
  function isNotACallOnTheNode(member, node) {
4022
4028
  return !(member.computed && member.property === node);
@@ -4033,27 +4039,31 @@ function isParenthesizedWrapper(value) {
4033
4039
  const expressionMutantPlacer = {
4034
4040
  name: "expression",
4035
4041
  place(path, appliedMutants) {
4036
- let expression = nameIfAnonymous(path);
4037
- expression = mutationCoverageSequenceExpression(appliedMutants.keys(), expression);
4038
- for (const [mutant, appliedMutant] of appliedMutants) expression = conditionalExpression(mutantTestExpression(mutant.id), nodeOfKind(mutant, unwrapParenthesizedExpression(appliedMutant), isExpressionKind, "an expression"), expression);
4039
- path.replaceWith(expression);
4042
+ return Result.gen(function* () {
4043
+ const base = yield* nameIfAnonymous(path);
4044
+ const sequenced = mutationCoverageSequenceExpression(appliedMutants.keys(), base);
4045
+ const expression = yield* [...appliedMutants].reduce((accumulated, [mutant, appliedMutant]) => Result.flatMap(accumulated, (current) => Result.map(nodeOfKind(mutant, unwrapParenthesizedExpression(appliedMutant), isExpressionKind, "an expression"), (replacement) => conditionalExpression(mutantTestExpression(mutant.id), replacement, current))), Result.succeed(sequenced));
4046
+ path.replaceWith(expression);
4047
+ });
4040
4048
  }
4041
4049
  };
4042
4050
  const statementMutantPlacer = {
4043
4051
  name: "statement",
4044
4052
  place(path, appliedMutants) {
4045
- const body = [expressionStatement(mutationCoverageSequenceExpression(appliedMutants.keys())), ...statementsOf(path)];
4046
- const statement = [...appliedMutants].reduce(guardedStatement, blockStatement(body));
4047
- path.replaceWith(wrappedStatement(path, statement));
4053
+ return Result.gen(function* () {
4054
+ const statements = yield* statementsOf(path);
4055
+ const body = [expressionStatement(mutationCoverageSequenceExpression(appliedMutants.keys())), ...statements];
4056
+ const statement = yield* [...appliedMutants].reduce((accumulated, entry) => Result.flatMap(accumulated, (current) => guardedStatement(current, entry)), Result.succeed(blockStatement(body)));
4057
+ path.replaceWith(wrappedStatement(path, statement));
4058
+ });
4048
4059
  }
4049
4060
  };
4050
- function statementsOf(path) {
4061
+ const statementsOf = (path) => {
4051
4062
  const node = path.node;
4052
- if (node.type === "BlockStatement") return node.body;
4053
- return [statementOf(node)];
4054
- }
4063
+ return node.type === "BlockStatement" ? Result.succeed(node.body) : Result.map(statementOf(node), (statement) => [statement]);
4064
+ };
4055
4065
  function guardedStatement(statement, entry) {
4056
- return ifStatement(mutantTestExpression(entry[0].id), blockStatement([nodeOfKind(entry[0], entry[1], isStatementKind, "a statement")]), statement);
4066
+ return Result.map(nodeOfKind(entry[0], entry[1], isStatementKind, "a statement"), (narrowed) => ifStatement(mutantTestExpression(entry[0].id), blockStatement([narrowed]), statement));
4057
4067
  }
4058
4068
  function wrappedStatement(path, statement) {
4059
4069
  return Match.value(nodeType(path.node)).pipe(Match.when("BlockStatement", () => blockStatement([statement])), Match.orElse(() => statement));
@@ -4064,13 +4074,11 @@ const placerBuilders = Object.freeze({
4064
4074
  "switch-case": {
4065
4075
  name: "switch-case",
4066
4076
  place(path, appliedMutants) {
4067
- const currentCase = switchCaseOf(path.node);
4068
- let consequence = blockStatement([expressionStatement(mutationCoverageSequenceExpression(appliedMutants.keys())), ...currentCase.consequent]);
4069
- for (const [mutant, appliedMutant] of appliedMutants) {
4070
- const appliedCase = nodeOfKind(mutant, appliedMutant, isSwitchCase, "a switch case");
4071
- consequence = ifStatement(mutantTestExpression(mutant.id), blockStatement(appliedCase.consequent), consequence);
4072
- }
4073
- path.replaceWith(switchCase(currentCase.test, [consequence]));
4077
+ return Result.gen(function* () {
4078
+ const currentCase = yield* switchCaseOf(path.node);
4079
+ const consequence = yield* [...appliedMutants].reduce((accumulated, [mutant, appliedMutant]) => Result.flatMap(accumulated, (current) => Result.map(nodeOfKind(mutant, appliedMutant, isSwitchCase, "a switch case"), (appliedCase) => ifStatement(mutantTestExpression(mutant.id), blockStatement(appliedCase.consequent), current))), Result.succeed(blockStatement([expressionStatement(mutationCoverageSequenceExpression(appliedMutants.keys())), ...currentCase.consequent])));
4080
+ path.replaceWith(switchCase(currentCase.test, [consequence]));
4081
+ });
4074
4082
  }
4075
4083
  }
4076
4084
  });
@@ -4086,231 +4094,281 @@ const transformDataFirst = (ast, mutantCollector, transformerContext) => {
4086
4094
  }))));
4087
4095
  };
4088
4096
  const transform = dual((args) => args.length >= 3, transformDataFirst);
4089
- const emptyAppliedMutants = () => /* @__PURE__ */ new Map();
4090
- const traversalFailure = (cause) => InstrumentError.make({
4091
- message: cause instanceof Error ? cause.message : errorTextOf$1(cause),
4092
- cause
4093
- });
4094
4097
  function isMutateRangeList(value) {
4095
4098
  return Array.isArray(value);
4096
4099
  }
4097
- const transformScriptDataFirst = ({ root, originFileName, rawContent, offset, comments }, mutantCollector, { options, mutateDescription, basePath }) => {
4098
- const placementMap = /* @__PURE__ */ new Map();
4099
- return Effect.gen(function* () {
4100
- const lineTable = yield* Effect.orDie(S.decodeEffect(LineTableFromText)(rawContent));
4101
- attachComments(make(root), comments, lineTable);
4102
- let directiveRule = [];
4103
- let hasLiveMutants = false;
4104
- const selection = selectMutators(DEFAULT_MUTATOR_REGISTRY, options.optInMutations);
4105
- const mutatorEntries = selection.active;
4106
- const allMutatorNames = selection.known.map((name) => name.toLowerCase());
4107
- const warnings = [];
4108
- yield* Effect.try({
4109
- try: () => traverse(make(root), {
4110
- enter(path) {
4111
- const directives = directivesOf(path.node, (node) => getNodeLocation(node).start.line);
4112
- directiveRule = directives.reduce(foldInto, directiveRule);
4113
- visitNode(path, directives);
4114
- },
4115
- exit(path) {
4116
- const placement = placementMap.get(path.node);
4117
- if (hasAppliedMutants(placement)) applyPlacement(path, placement);
4118
- }
4119
- }),
4120
- catch: traversalFailure
4121
- });
4122
- yield* placeHeaderIfNeeded(hasLiveMutants, options, root);
4123
- return warnings;
4124
- function visitNode(path, directives) {
4125
- if (shouldSkip(path)) {
4126
- path.skip();
4127
- return;
4128
- }
4129
- addToPlacementMapIfPossible(path);
4130
- placeCollectedMutants(path, directives);
4131
- }
4132
- function placementFacts(path) {
4133
- return {
4134
- isExpression: path.isExpression(),
4135
- isStatement: path.isStatement(),
4136
- isSwitchCase: nodeType(path.node) === "SwitchCase",
4137
- expressionIsValid: isValidExpression(path)
4138
- };
4139
- }
4140
- function placerNameOf(site) {
4141
- return Match.value(site).pipe(Match.tag("ExpressionSite", () => "expression"), Match.tag("StatementSite", () => "statement"), Match.tag("SwitchCaseSite", () => "switch-case"), Match.exhaustive);
4142
- }
4143
- function addToPlacementMapIfPossible(path) {
4144
- const facts = placementFacts(path);
4145
- const claimed = placeMutants(PlaceMutantsCommand.make({
4146
- fileName: originFileName,
4147
- facts,
4148
- mutants: []
4149
- }));
4150
- Match.value(claimed).pipe(Match.when(Result.isSuccess, () => placementMap.set(path.node, {
4151
- appliedMutants: emptyAppliedMutants(),
4152
- facts
4153
- })), Match.orElse(() => void 0));
4154
- }
4155
- function hasAppliedMutants(placement) {
4156
- return placement !== void 0 && placement.appliedMutants.size > 0;
4157
- }
4158
- function replacementRecord(mutant, applied) {
4159
- const replacement = unwrapParenthesizedExpression(applied);
4160
- return {
4161
- id: mutant.id,
4162
- mutatorName: mutant.mutatorName,
4163
- replacement: {
4164
- isExpression: isExpressionKind(replacement),
4165
- isStatement: isStatementKind(replacement),
4166
- isSwitchCase: nodeType(replacement) === "SwitchCase"
4167
- }
4168
- };
4100
+ const MUTATION_OFFSET = {
4101
+ line: 1,
4102
+ columnShift: 0
4103
+ };
4104
+ const initialFoldState = (firstIndex) => ({
4105
+ directiveRule: [],
4106
+ nextIndex: firstIndex,
4107
+ mutants: [],
4108
+ warnings: [],
4109
+ claims: [],
4110
+ placements: [],
4111
+ hasLiveMutants: false
4112
+ });
4113
+ const framesUpward = (frame) => [frame, ...framesAbove(frame)];
4114
+ const framesAbove = (frame) => Option.match(Option.fromNullishOr(frame.parent), {
4115
+ onNone: () => [],
4116
+ onSome: (parent) => framesUpward(parent)
4117
+ });
4118
+ const parentNodeOf = (frame) => Option.getOrNull(Option.map(Option.fromNullishOr(frame.parent), (parent) => parent.node));
4119
+ const ancestorsOfFrame = (frame) => Option.match(Option.fromNullishOr(frame.parent), {
4120
+ onNone: () => [],
4121
+ onSome: (parent) => [parent.node, ...ancestorsOfFrame(parent)]
4122
+ });
4123
+ const placementFactsOf = (frame) => ({
4124
+ isExpression: isExpressionKind(frame.node),
4125
+ isStatement: isStatementKind(frame.node),
4126
+ isSwitchCase: nodeType(frame.node) === "SwitchCase",
4127
+ expressionIsValid: isValidExpression(frame.node, parentNodeOf(frame))
4128
+ });
4129
+ const placerNameOf = (site) => Match.value(site).pipe(Match.tag("ExpressionSite", () => "expression"), Match.tag("StatementSite", () => "statement"), Match.tag("SwitchCaseSite", () => "switch-case"), Match.exhaustive);
4130
+ const replacementRecord = (mutant, applied) => {
4131
+ const replacement = unwrapParenthesizedExpression(applied);
4132
+ return {
4133
+ id: mutant.id,
4134
+ mutatorName: mutant.mutatorName,
4135
+ replacement: {
4136
+ isExpression: isExpressionKind(replacement),
4137
+ isStatement: isStatementKind(replacement),
4138
+ isSwitchCase: nodeType(replacement) === "SwitchCase"
4169
4139
  }
4170
- function raisePlacementRefusal(refusal, placement, path) {
4171
- throw placementFailure(refusal, path, [...placement.appliedMutants.keys()], originFileName, lineTable, basePath);
4140
+ };
4141
+ };
4142
+ const plannedWithNodes = (candidates, planned, fileName) => candidates.flatMap((candidate, index) => Option.match(Option.fromNullishOr(planned[index]), {
4143
+ onNone: () => [],
4144
+ onSome: (mutant) => [createMutant(mutant, fileName, candidate.node, candidate.replacement)]
4145
+ }));
4146
+ const ignorersReasonFor = (node, ancestors, ignorers) => ignorers.reduce((reason, ignorer) => Option.orElse(reason, () => Option.fromNullishOr(ignorer.shouldIgnore(node, ancestors))), Option.none());
4147
+ const mutablesFor = (frame, location, context) => {
4148
+ const ancestors = ancestorsOfFrame(frame);
4149
+ const mutatorContext = toMutatorContext(ancestors);
4150
+ const replacements = context.mutatorEntries.flatMap(([mutatorName, mutate]) => [...mutate(frame.node, mutatorContext)].map((replacement) => ({
4151
+ mutatorName,
4152
+ replacement
4153
+ })));
4154
+ const ignorerReason = replacements.length === 0 ? void 0 : Option.getOrUndefined(ignorersReasonFor(frame.node, ancestors, context.ignorers));
4155
+ return replacements.map(({ mutatorName, replacement }) => ({
4156
+ node: frame.node,
4157
+ replacement,
4158
+ data: {
4159
+ mutatorName,
4160
+ replacementCode: printNode(replacement),
4161
+ location,
4162
+ ignorerReason
4172
4163
  }
4173
- function placeSite(site, placement, path) {
4174
- try {
4175
- placerBuilders[placerNameOf(site)].place(path, placement.appliedMutants);
4176
- path.skip();
4177
- } catch (error) {
4178
- raisePlacementRefusal(MutantsUnapplied.make({
4179
- fileName: originFileName,
4180
- placer: placerNameOf(site),
4181
- mutatorNames: [...placement.appliedMutants.keys()].map((mutant) => mutant.mutatorName),
4182
- cause: toError(error)
4183
- }), placement, path);
4164
+ }));
4165
+ };
4166
+ const mutateRangesOf = (mutateDescription) => Option.filter(Option.some(mutateDescription), isMutateRangeList);
4167
+ const isOutsideMutateRanges = (location, mutateDescription) => Option.exists(mutateRangesOf(mutateDescription), (ranges) => ranges.every((range) => !locationOverlaps(range, location)));
4168
+ const isInsideMutateRanges = (location, mutateDescription) => Option.exists(mutateRangesOf(mutateDescription), (ranges) => ranges.some((range) => locationIncluded(range, location)));
4169
+ const shouldMutateAt = (location, mutateDescription) => mutateDescription === true || isInsideMutateRanges(location, mutateDescription);
4170
+ const shouldSkipNode = (frame, location, mutateDescription) => [
4171
+ isTypeNode(frame.node),
4172
+ isImportDeclaration(frame.node),
4173
+ nodeType(frame.node) === "Decorator",
4174
+ mutateDescription === false,
4175
+ isOutsideMutateRanges(location, mutateDescription)
4176
+ ].some((skip) => skip);
4177
+ const locationOfNode = (frame, context) => Option.match(Option.map(Option.fromNullishOr(spanOf(frame.node)), (span) => locationOf(context.lineStarts, span)), {
4178
+ onNone: () => Result.fail(NodeWithoutSpan.make({ fileName: context.fileName })),
4179
+ onSome: Result.succeed
4180
+ });
4181
+ const refusedPlacement = (refusal, node, mutants, context) => PlacementRefused.make({ message: placementFailureMessage(refusal, node, mutants, context.fileName, context.lineStarts, context.basePath) });
4182
+ const applyMutantToClaim = (mutant, target, appliedSoFar, context) => Result.mapError(applyMutant(mutant, target), (failure) => refusedPlacement(failure, target, appliedSoFar, context));
4183
+ const applyToClaim = (mutants, target, state, context) => {
4184
+ const existing = state.claims.find((claim) => claim.node === target.node);
4185
+ const priorMutants = existing === void 0 ? [] : existing.applied.map(([mutant]) => mutant);
4186
+ const applied = mutants.reduce((accumulated, mutant) => Result.flatMap(accumulated, (entries) => Result.map(applyMutantToClaim(mutant, target.node, [...priorMutants, ...entries.map(([entry]) => entry)], context), (tree) => [...entries, [mutant, tree]])), Result.succeed([]));
4187
+ return Result.map(applied, (entries) => ({
4188
+ ...state,
4189
+ claims: state.claims.map((claim) => claim.node === target.node ? {
4190
+ ...claim,
4191
+ applied: [...claim.applied, ...entries]
4192
+ } : claim)
4193
+ }));
4194
+ };
4195
+ const attachPlaceable = (mutants, frame, state, context) => Match.value(mutants.length > 0).pipe(Match.when(false, () => Result.succeed(state)), Match.when(true, () => Option.match(Option.fromNullishOr(framesUpward(frame).find((candidate) => state.claims.some((claim) => claim.node === candidate.node))), {
4196
+ onNone: () => Result.fail(MutantsUnplaced.make({
4197
+ fileName: context.fileName,
4198
+ detail: JSON.stringify(mutants, null, 2)
4199
+ })),
4200
+ onSome: (target) => applyToClaim(mutants, target, state, context)
4201
+ })), Match.exhaustive);
4202
+ const collectPlan = (frame, candidates, plan, state, context) => {
4203
+ const collected = plannedWithNodes(candidates, plan.mutants, context.fileName);
4204
+ const nextState = {
4205
+ ...state,
4206
+ mutants: [...state.mutants, ...collected],
4207
+ warnings: [...state.warnings, ...plan.warnings],
4208
+ nextIndex: plan.nextIndex
4209
+ };
4210
+ return Match.value(plan).pipe(Match.tag("MutantsPlanned", (planned) => attachPlaceable(plannedWithNodes(candidates, planned.placeable, context.fileName), frame, {
4211
+ ...nextState,
4212
+ hasLiveMutants: true
4213
+ }, context)), Match.orElse(() => Result.succeed(nextState)));
4214
+ };
4215
+ const planMutantsAt = (frame, candidates, directives, location, state, context) => {
4216
+ const plan = planMutants(PlanMutantsCommand.make({
4217
+ fileName: context.fileName,
4218
+ firstIndex: state.nextIndex,
4219
+ offset: context.offset,
4220
+ line: location.start.line,
4221
+ mutatorNames: [...context.allMutatorNames],
4222
+ excludedMutations: [...context.excludedMutations],
4223
+ rule: [...state.directiveRule],
4224
+ directives: [...directives],
4225
+ candidates: candidates.map((candidate) => candidate.data)
4226
+ }));
4227
+ return Match.value(plan).pipe(Match.when(Result.isFailure, (failed) => Result.fail(failed.failure)), Match.orElse((succeeded) => collectPlan(frame, candidates, succeeded.success, state, context)));
4228
+ };
4229
+ const candidatesFor = (frame, location, context) => Match.value(shouldMutateAt(location, context.mutateDescription)).pipe(Match.when(true, () => mutablesFor(frame, location, context)), Match.orElse(() => []));
4230
+ const needsPlan = (candidates, directives) => [candidates.length > 0, directives.length > 0].some(Boolean);
4231
+ const planAtNode = (frame, directives, location, state, context) => {
4232
+ const candidates = candidatesFor(frame, location, context);
4233
+ return Match.value(needsPlan(candidates, directives)).pipe(Match.when(false, () => Result.succeed(state)), Match.when(true, () => planMutantsAt(frame, candidates, directives, location, state, context)), Match.exhaustive);
4234
+ };
4235
+ const claimSelf = (frame, facts, claims, context) => {
4236
+ const claimed = placeMutants(PlaceMutantsCommand.make({
4237
+ fileName: context.fileName,
4238
+ facts,
4239
+ mutants: []
4240
+ }));
4241
+ return Match.value(claimed.pipe(Result.isSuccess)).pipe(Match.when(true, () => [...claims, {
4242
+ node: frame.node,
4243
+ facts,
4244
+ applied: []
4245
+ }]), Match.orElse(() => claims));
4246
+ };
4247
+ const siteOfClaim = (claim, context) => {
4248
+ const mutants = claim.applied.map(([mutant]) => mutant);
4249
+ const decision = placeMutants(PlaceMutantsCommand.make({
4250
+ fileName: context.fileName,
4251
+ facts: claim.facts,
4252
+ mutants: claim.applied.map(([mutant, applied]) => replacementRecord(mutant, applied))
4253
+ }));
4254
+ return Match.value(decision).pipe(Match.when(Result.isFailure, (refused) => Result.fail(refusedPlacement(refused.failure, claim.node, mutants, context))), Match.orElse((decided) => Result.succeed(decided.success)));
4255
+ };
4256
+ const emitClaim = (claim, state, context) => Match.value(claim.applied.length > 0).pipe(Match.when(false, () => Result.succeed(state)), Match.when(true, () => Result.map(siteOfClaim(claim, context), (site) => ({
4257
+ ...state,
4258
+ placements: [...state.placements, {
4259
+ node: claim.node,
4260
+ placer: placerNameOf(site),
4261
+ applied: claim.applied
4262
+ }]
4263
+ }))), Match.exhaustive);
4264
+ const emitPlacement = (frame, state, context) => Option.match(Option.fromNullishOr(state.claims.find((claim) => claim.node === frame.node)), {
4265
+ onNone: () => Result.succeed(state),
4266
+ onSome: (claim) => emitClaim(claim, state, context)
4267
+ });
4268
+ const foldChildren = (frame, state, context) => childNodes(frame.node).reduce((accumulated, child) => Result.flatMap(accumulated, (current) => foldPlacements({
4269
+ node: child.node,
4270
+ parent: frame
4271
+ }, current, context)), Result.succeed(state));
4272
+ const visitFrame = (frame, directives, location, state, context) => {
4273
+ const facts = placementFactsOf(frame);
4274
+ const claimed = claimSelf(frame, facts, state.claims, context);
4275
+ return Result.flatMap(planAtNode(frame, directives, location, {
4276
+ ...state,
4277
+ claims: claimed
4278
+ }, context), (afterPlan) => Result.flatMap(foldChildren(frame, afterPlan, context), (afterChildren) => emitPlacement(frame, afterChildren, context)));
4279
+ };
4280
+ const foldPlacements = (frame, state, context) => Result.flatMap(locationOfNode(frame, context), (location) => {
4281
+ const directives = directivesOf(frame.node, location.start.line);
4282
+ const ruled = {
4283
+ ...state,
4284
+ directiveRule: directives.reduce(foldInto, state.directiveRule)
4285
+ };
4286
+ return Match.value(shouldSkipNode(frame, location, context.mutateDescription)).pipe(Match.when(true, () => Result.succeed(ruled)), Match.when(false, () => visitFrame(frame, directives, location, ruled, context)), Match.exhaustive);
4287
+ });
4288
+ const planInstrumentation = (root, firstIndex, context) => Result.map(foldPlacements({
4289
+ node: root,
4290
+ parent: null
4291
+ }, initialFoldState(firstIndex), context), (state) => ({
4292
+ mutants: state.mutants,
4293
+ placements: state.placements,
4294
+ warnings: state.warnings,
4295
+ hasLiveMutants: state.hasLiveMutants
4296
+ }));
4297
+ const applyOnePlacement = (placement, path, previous, context) => {
4298
+ const result = placerBuilders[placement.placer].place(path, new Map(placement.applied));
4299
+ return Result.match(result, {
4300
+ onFailure: (cause) => new Error(placementFailureMessage(MutantsUnapplied.make({
4301
+ fileName: context.fileName,
4302
+ placer: placement.placer,
4303
+ mutatorNames: placement.applied.map(([mutant]) => mutant.mutatorName),
4304
+ cause: toError(cause)
4305
+ }), placement.node, placement.applied.map(([mutant]) => mutant), context.fileName, context.lineStarts, context.basePath)),
4306
+ onSuccess: () => previous
4307
+ });
4308
+ };
4309
+ const applyPlacementsToAst = (root, places, context) => {
4310
+ let failure;
4311
+ traverse(make(root), { exit(path) {
4312
+ Option.match(Option.fromNullishOr(places.get(path.node)), {
4313
+ onNone: () => void 0,
4314
+ onSome: (placement) => {
4315
+ failure = applyOnePlacement(placement, path, failure, context);
4184
4316
  }
4185
- }
4186
- function applyPlacement(path, placement) {
4187
- const decision = placeMutants(PlaceMutantsCommand.make({
4188
- fileName: originFileName,
4189
- facts: placement.facts,
4190
- mutants: [...placement.appliedMutants].map(([mutant, applied]) => replacementRecord(mutant, applied))
4191
- }));
4192
- Match.value(decision).pipe(Match.when(Result.isFailure, (refused) => raisePlacementRefusal(refused.failure, placement, path)), Match.orElse((decided) => placeSite(decided.success, placement, path)));
4193
- }
4194
- function placeCollectedMutants(path, directives) {
4195
- const candidates = candidateSteps(path);
4196
- Match.value(needsPlan(candidates, directives)).pipe(Match.when(true, () => planAndPlace(path, candidates, directives)), Match.when(false, () => void 0), Match.exhaustive);
4197
- }
4198
- function candidateSteps(path) {
4199
- return Match.value(shouldMutate(path)).pipe(Match.when(true, () => mutablesFor(path)), Match.when(false, () => []), Match.exhaustive);
4200
- }
4201
- function needsPlan(candidates, directives) {
4202
- return [candidates.length > 0, directives.length > 0].some(Boolean);
4203
- }
4204
- function planAndPlace(path, candidates, directives) {
4205
- Match.value(planFor(path, candidates, directives)).pipe(Match.when(Option.isSome, (toPlace) => placeOnPath(path, toPlace.value)), Match.orElse(() => void 0));
4206
- }
4207
- function planFor(path, candidates, directives) {
4208
- const plan = planMutants(PlanMutantsCommand.make({
4209
- fileName: originFileName,
4210
- firstIndex: mutantCollector.nextIndex,
4211
- offset: offset ?? {
4212
- line: 1,
4213
- column: 0
4214
- },
4215
- line: getNodeLocation(path.node).start.line,
4216
- mutatorNames: allMutatorNames,
4217
- excludedMutations: options.excludedMutations,
4218
- rule: directiveRule,
4219
- directives: [...directives],
4220
- candidates: candidates.map((candidate) => candidate.data)
4221
- }));
4222
- return Match.value(plan).pipe(Match.when(Result.isFailure, (failed) => raisePlanFailure(failed.failure)), Match.orElse((succeeded) => collectPlanned(candidates, succeeded.success)));
4223
- }
4224
- function raisePlanFailure(failure) {
4225
- throw new Error(`Mutant without a source location: ${failure.mutatorName} in ${failure.fileName}`);
4226
- }
4227
- function collectPlanned(candidates, plan) {
4228
- mutantCollector.append(plannedWithNodes(candidates, plan.mutants));
4229
- warnings.push(...plan.warnings);
4230
- return Match.value(plan).pipe(Match.tag("MutantsPlanned", (planned) => {
4231
- hasLiveMutants = true;
4232
- return Option.some(plannedWithNodes(candidates, planned.placeable));
4233
- }), Match.orElse(() => Option.none()));
4234
- }
4235
- function plannedWithNodes(candidates, planned) {
4236
- return candidates.flatMap((candidate, index) => Option.match(Option.fromNullishOr(planned[index]), {
4237
- onNone: () => [],
4238
- onSome: (mutant) => [createMutant(mutant, originFileName, candidate.node, candidate.replacement)]
4239
- }));
4240
- }
4241
- function placeOnPath(path, mutantsToPlace) {
4242
- const placementPath = requiredPlacementPath(path, mutantsToPlace);
4243
- const placement = requiredPlacement(placementPath.node);
4244
- mutantsToPlace.forEach((mutant) => {
4245
- Match.value(applyMutant(mutant, placementPath.node)).pipe(Match.when(Result.isFailure, (failed) => raisePlacementRefusal(failed.failure, placement, path)), Match.orElse((applied) => placement.appliedMutants.set(mutant, applied.success)));
4246
- });
4247
- }
4248
- function requiredPlacementPath(path, mutantsToPlace) {
4249
- return Option.getOrThrowWith(Option.fromNullishOr(path.find((ancestor) => placementMap.has(ancestor.node))), () => unplacedMutantsError(mutantsToPlace));
4250
- }
4251
- function unplacedMutantsError(mutantsToPlace) {
4252
- return /* @__PURE__ */ new Error(`Mutants cannot be placed. This shouldn't happen! Unplaced mutants: ${JSON.stringify(mutantsToPlace, null, 2)}`);
4253
- }
4254
- function requiredPlacement(node) {
4255
- return Option.getOrThrowWith(Option.fromNullishOr(placementMap.get(node)), () => /* @__PURE__ */ new Error("Placement not found for node"));
4256
- }
4257
- function shouldSkip(path) {
4258
- return [
4259
- isTypeNode(path),
4260
- isImportDeclaration(path),
4261
- nodeType(path.node) === "Decorator",
4262
- mutateDescription === false,
4263
- isOutsideMutateRanges(path)
4264
- ].some((skip) => skip);
4265
- }
4266
- function mutateRanges() {
4267
- return Option.filter(Option.some(mutateDescription), isMutateRangeList);
4268
- }
4269
- function isOutsideMutateRanges(path) {
4270
- return Option.exists(mutateRanges(), (ranges) => ranges.every((range) => !locationOverlaps(range, getNodeLocation(path.node))));
4271
- }
4272
- function shouldMutate(path) {
4273
- return mutateDescription === true || isInsideMutateRanges(path);
4274
- }
4275
- function isInsideMutateRanges(path) {
4276
- return Option.exists(mutateRanges(), (ranges) => ranges.some((range) => locationIncluded(range, getNodeLocation(path.node))));
4277
- }
4278
- function getNodeLocation(node) {
4279
- const span = spanOf(node);
4280
- if (span === void 0) throw new Error("Node without a span");
4281
- return {
4282
- start: lineTable.positionAt(span.start),
4283
- end: lineTable.positionAt(span.end)
4284
- };
4285
- }
4286
- function mutablesFor(path) {
4287
- const ancestors = ancestorsOf(path);
4288
- const context = toMutatorContext(ancestors);
4289
- const location = Option.map(Option.fromNullishOr(spanOf(path.node)), (span) => ({
4290
- start: lineTable.positionAt(span.start),
4291
- end: lineTable.positionAt(span.end)
4292
- }));
4293
- const replacements = mutatorEntries.flatMap(([mutatorName, mutate]) => [...mutate(path.node, context)].map((replacement) => ({
4294
- mutatorName,
4295
- replacement
4296
- })));
4297
- const ignorerReason = replacements.length === 0 ? void 0 : Option.getOrUndefined(ignorersReason(path.node, ancestors));
4298
- return replacements.map(({ mutatorName, replacement }) => ({
4299
- node: path.node,
4300
- replacement,
4301
- data: {
4302
- mutatorName,
4303
- replacementCode: printNode(replacement),
4304
- location: Option.getOrUndefined(location),
4305
- ignorerReason
4306
- }
4307
- }));
4308
- }
4309
- function ignorersReason(node, ancestors) {
4310
- return options.ignorers.reduce((reason, ignorer) => Option.orElse(reason, () => Option.fromNullishOr(ignorer.shouldIgnore(node, ancestors))), Option.none());
4311
- }
4317
+ });
4318
+ } });
4319
+ return failure;
4320
+ };
4321
+ const applyPlan = (root, plan, context) => {
4322
+ const places = new Map(plan.placements.map((placement) => [placement.node, placement]));
4323
+ return Option.match(Option.fromNullishOr(applyPlacementsToAst(root, places, context)), {
4324
+ onNone: () => Effect.void,
4325
+ onSome: (error) => Effect.fail(InstrumentError.make({
4326
+ message: error.message,
4327
+ cause: error
4328
+ }))
4312
4329
  });
4313
4330
  };
4331
+ const refusalError = (refusal) => Match.value(refusal).pipe(Match.tag("NodeWithoutSpan", () => InstrumentError.make({
4332
+ message: "Node without a span",
4333
+ cause: void 0
4334
+ })), Match.tag("MutantsUnplaced", (unplaced) => InstrumentError.make({
4335
+ message: `Mutants cannot be placed. This shouldn't happen! Unplaced mutants: ${unplaced.detail}`,
4336
+ cause: void 0
4337
+ })), Match.tag("PlacementRefused", (refused) => InstrumentError.make({
4338
+ message: refused.message,
4339
+ cause: new Error(refused.message)
4340
+ })), Match.tag("MutantWithoutLocation", (failed) => InstrumentError.make({
4341
+ message: `Mutant without a source location: ${failed.mutatorName} in ${failed.fileName}`,
4342
+ cause: void 0
4343
+ })), Match.exhaustive);
4344
+ const transformScriptDataFirst = Effect.fn("stryker.instrument.transform.script")(function* ({ root, originFileName, rawContent, offset, comments }, mutantCollector, { options, mutateDescription, basePath }) {
4345
+ const lineStarts = lineStartsOf(rawContent);
4346
+ attachComments(make(root), comments, lineStarts);
4347
+ const selection = selectMutators(DEFAULT_MUTATOR_REGISTRY, options.optInMutations);
4348
+ const context = {
4349
+ fileName: originFileName,
4350
+ lineStarts,
4351
+ mutateDescription,
4352
+ offset: offset ?? MUTATION_OFFSET,
4353
+ basePath,
4354
+ mutatorEntries: selection.active,
4355
+ allMutatorNames: selection.known.map((name) => name.toLowerCase()),
4356
+ excludedMutations: options.excludedMutations,
4357
+ ignorers: options.ignorers
4358
+ };
4359
+ const planned = yield* Effect.try({
4360
+ try: () => planInstrumentation(root, mutantCollector.nextIndex, context),
4361
+ catch: traversalFailure
4362
+ });
4363
+ const plan = yield* Match.value(planned).pipe(Match.when(Result.isFailure, (refused) => {
4364
+ const error = refusalError(refused.failure);
4365
+ return Effect.fail(error);
4366
+ }), Match.orElse((succeeded) => Effect.succeed(succeeded.success)));
4367
+ mutantCollector.append(plan.mutants);
4368
+ yield* applyPlan(root, plan, context);
4369
+ yield* placeHeaderIfNeeded(plan.hasLiveMutants, options, root);
4370
+ return plan.warnings;
4371
+ });
4314
4372
  const transformScript = dual((args) => args.length >= 3, transformScriptDataFirst);
4315
4373
  function toMutatorContext(ancestors) {
4316
4374
  return {
@@ -4319,10 +4377,7 @@ function toMutatorContext(ancestors) {
4319
4377
  ancestors: [...ancestors]
4320
4378
  };
4321
4379
  }
4322
- function toError(value) {
4323
- if (value instanceof Error) return value;
4324
- return new Error("Unexpected error", { cause: value });
4325
- }
4380
+ const toError = (value) => value instanceof Error ? value : new Error("Unexpected error", { cause: value });
4326
4381
  //#endregion
4327
4382
  //#region src/TypeCheckDisablers.ts
4328
4383
  const tsDirectiveLikeRegEx = /@(ts-[a-z-]+)/;
@@ -4332,18 +4387,15 @@ function disableTypeCheckingInScript(ast) {
4332
4387
  return prefixWithNoCheck(removeTSDirectives(ast.rawContent, ast.comments));
4333
4388
  }
4334
4389
  function prefixWithNoCheck(code) {
4335
- if (code.startsWith("#")) return afterHashbang(code);
4336
- return afterLeadingComment(code);
4390
+ return code.startsWith("#") ? afterHashbang(code) : afterLeadingComment(code);
4337
4391
  }
4338
4392
  function afterHashbang(code) {
4339
4393
  const newLineIndex = code.indexOf("\n");
4340
- if (newLineIndex <= 0) return code;
4341
- return `${code.substring(0, newLineIndex)}\n// @ts-nocheck\n${code.substring(newLineIndex + 1)}`;
4394
+ return newLineIndex <= 0 ? code : `${code.substring(0, newLineIndex)}\n// @ts-nocheck\n${code.substring(newLineIndex + 1)}`;
4342
4395
  }
4343
4396
  function afterLeadingComment(code) {
4344
4397
  const leadingComment = leadingCommentOf(code);
4345
- if (leadingComment === void 0) return `// @ts-nocheck\n${code}`;
4346
- return `${leadingComment.concat("\n")}// @ts-nocheck\n${code.substring(leadingComment.length)}`;
4398
+ return leadingComment === void 0 ? `// @ts-nocheck\n${code}` : `${leadingComment.concat("\n")}// @ts-nocheck\n${code.substring(leadingComment.length)}`;
4347
4399
  }
4348
4400
  function leadingCommentOf(code) {
4349
4401
  return STARTING_COMMENT.exec(code)?.[0];
@@ -4365,31 +4417,26 @@ function removeRanges(text, ranges) {
4365
4417
  return remaining.pruned + text.substring(remaining.cursor);
4366
4418
  }
4367
4419
  function tryParseTSDirective(comment) {
4368
- const match = commentDirectiveRegEx.exec(comment.value);
4369
- if (match === null) return void 0;
4370
- const directivePrefix = requirePart(match[1], "TS directive match without prefix");
4371
- const directiveName = requirePart(match[2], "TS directive match without directive name");
4372
- const startPos = comment.start + directivePrefix.length + 2;
4420
+ return Option.match(Option.fromNullishOr(commentDirectiveRegEx.exec(comment.value)), {
4421
+ onNone: () => void 0,
4422
+ onSome: (match) => directiveRangeAt(comment.start, lengthOf(match[1]), lengthOf(match[2]))
4423
+ });
4424
+ }
4425
+ const lengthOf = (text) => Option.getOrElse(Option.map(Option.fromNullishOr(text), (value) => value.length), () => 0);
4426
+ const directiveRangeAt = (commentStart, prefixLength, nameLength) => {
4427
+ const startPos = commentStart + prefixLength + 2;
4373
4428
  return {
4374
4429
  startPos,
4375
- endPos: startPos + directiveName.length + 1
4430
+ endPos: startPos + nameLength + 1
4376
4431
  };
4377
- }
4378
- function requirePart(part, message) {
4379
- if (part === void 0) throw new Error(message);
4380
- return part;
4381
- }
4432
+ };
4382
4433
  //#endregion
4383
4434
  //#region src/Format.ts
4384
- const requireScriptAst = (ast) => {
4385
- if (ast.format === "embedded") return rejectAst(ast, "a script");
4386
- return ast;
4387
- };
4388
- const requireJsAst = (ast) => ast.format === "js" ? ast : rejectAst(ast, "a js script");
4389
- const requireTsFamilyAst = (ast) => Match.value(ast).pipe(Match.when({ format: "ts" }, (ts) => ts), Match.when({ format: "tsx" }, (tsx) => tsx), Match.orElse((other) => rejectAst(other, "a ts script")));
4390
- function rejectAst(ast, expected) {
4391
- throw new Error(`Expected ${expected} AST, received the "${ast.format}" format`);
4392
- }
4435
+ const scriptAstError = (ast) => InstrumentError.make({
4436
+ message: `Expected a script AST, received the "${ast.format}" format`,
4437
+ cause: void 0
4438
+ });
4439
+ const scriptAstOf = (ast) => Match.value(ast).pipe(Match.when({ format: "embedded" }, () => Option.none()), Match.orElse((script) => Option.some(script)));
4393
4440
  const extensionOf = (fileName) => {
4394
4441
  const dot = fileName.lastIndexOf(".");
4395
4442
  return dot > Math.max(fileName.lastIndexOf("/"), fileName.lastIndexOf("\\")) ? fileName.slice(dot).toLowerCase() : "";
@@ -4421,12 +4468,19 @@ const resolutionCommandOfDataFirst = (registry, fileName, formatIdOverride) => F
4421
4468
  const resolutionCommandOf = dual((args) => typeof args[0] !== "string", resolutionCommandOfDataFirst);
4422
4469
  const scriptHooks = (scriptFormat) => ({
4423
4470
  parse: (text, fileName, _context) => parseScriptFormat(scriptFormat, text, fileName),
4424
- transform: (ast, mutantCollector, context) => transformScript(requireScriptAst(ast), mutantCollector, context),
4471
+ transform: (ast, mutantCollector, context) => Option.match(scriptAstOf(ast), {
4472
+ onNone: () => Effect.fail(scriptAstError(ast)),
4473
+ onSome: (script) => transformScript(script, mutantCollector, context)
4474
+ }),
4425
4475
  print: (ast, context) => printScriptAst(ast, context),
4426
- disableTypeChecks: (ast) => Effect.succeed(disableTypeCheckingInScript(requireScriptAst(ast)))
4476
+ disableTypeChecks: (ast) => Option.match(scriptAstOf(ast), {
4477
+ onNone: () => Effect.fail(scriptAstError(ast)),
4478
+ onSome: (script) => Effect.succeed(disableTypeCheckingInScript(script))
4479
+ })
4427
4480
  });
4428
4481
  const parseScriptFormat = (scriptFormat, text, fileName) => Match.value(scriptFormat).pipe(Match.when("js", () => parseJS(text, fileName)), Match.when("ts", () => parseTS(text, fileName)), Match.when("tsx", () => parseTsx(text, fileName)), Match.exhaustive);
4429
- const printScriptAst = (ast, context) => ast.format === "js" ? jsPrint(requireJsAst(ast), context) : tsPrint(requireTsFamilyAst(ast), context);
4482
+ const printScriptAst = (ast, context) => Match.value(ast).pipe(Match.when({ format: "js" }, (js) => jsPrint(js, context)), Match.when({ format: "ts" }, (ts) => tsPrint(ts, context)), Match.when({ format: "tsx" }, (tsx) => tsPrint(tsx, context)), Match.orElse(printNothingForEmbeddedAst));
4483
+ const printNothingForEmbeddedAst = () => "";
4430
4484
  const coreFormatRegistry = formatRegistry([
4431
4485
  {
4432
4486
  claim: {
@@ -4545,20 +4599,9 @@ const spliceForAssigned = (request) => Option.match(request.registry.entryForFor
4545
4599
  onSome: (entry) => spliceFileFor(request.file, entry)
4546
4600
  });
4547
4601
  //#endregion
4548
- //#region src/FrameworkEntry.ts
4549
- const toOneBasedOrigin = (origin) => ({
4550
- line: origin.line + 1,
4551
- column: origin.column
4552
- });
4602
+ //#region src/drivers/oxc-program.ts
4553
4603
  const EMBEDDED_SCRIPT_FILE = "embedded-script.js";
4554
- const loadFrameworkToolkit = Effect.gen(function* () {
4555
- const oxc = yield* loadOxc;
4556
- return {
4557
- parseScript: (source, scriptFormat) => parseScriptProgram(oxc, source, scriptFormat),
4558
- header: yield* instrumentationHeader
4559
- };
4560
- });
4561
- const parseScriptProgram = (oxc, source, scriptFormat) => {
4604
+ const makeScriptParser = (oxc) => (source, scriptFormat) => {
4562
4605
  const result = oxc.parseSync(EMBEDDED_SCRIPT_FILE, source, {
4563
4606
  lang: scriptFormat,
4564
4607
  range: true
@@ -4568,19 +4611,29 @@ const parseScriptProgram = (oxc, source, scriptFormat) => {
4568
4611
  fileName: EMBEDDED_SCRIPT_FILE,
4569
4612
  message: first.message,
4570
4613
  location: {
4571
- line: 0,
4572
- column: 0
4614
+ line: 1,
4615
+ column: 1
4573
4616
  },
4574
4617
  cause: first
4575
4618
  });
4576
4619
  return result.program;
4577
4620
  };
4621
+ //#endregion
4622
+ //#region src/FrameworkEntry.ts
4623
+ const loadFrameworkToolkit = Effect.gen(function* () {
4624
+ const oxc = yield* loadOxc;
4625
+ const header = yield* instrumentationHeader;
4626
+ return {
4627
+ parseScript: makeScriptParser(oxc),
4628
+ header
4629
+ };
4630
+ });
4578
4631
  const frameworkContextOf = (toolkit) => ({
4579
4632
  parseScript: toolkit.parseScript,
4580
4633
  printScript: (script) => printProgram(script, { hashbang: null }),
4581
4634
  instrumentationHeader: () => toolkit.header
4582
4635
  });
4583
- const embeddedScriptsOf = (document, rawContent, originFileName, lineTable) => document.regions.flatMap((region, index) => {
4636
+ const embeddedScriptsOf = (document, rawContent, originFileName, lineStarts) => document.regions.flatMap((region, index) => {
4584
4637
  return [{
4585
4638
  region: index,
4586
4639
  ast: {
@@ -4589,14 +4642,16 @@ const embeddedScriptsOf = (document, rawContent, originFileName, lineTable) => d
4589
4642
  comments: [],
4590
4643
  rawContent: rawContent.slice(region.start, region.end),
4591
4644
  originFileName,
4592
- offset: toOneBasedOrigin(lineTable.zeroBasedPositionAt(region.start))
4645
+ offset: originAt(lineStarts, region.start)
4593
4646
  }
4594
4647
  }];
4595
4648
  });
4596
- const embeddedOf = (ast) => {
4597
- if (ast.format !== "embedded") throw new Error(`Expected an embedded document AST, received the "${ast.format}" format`);
4598
- return ast;
4599
- };
4649
+ const embeddedAstError = (ast) => InstrumentError.make({
4650
+ message: `Expected an embedded document AST, received the "${ast.format}" format`,
4651
+ cause: void 0
4652
+ });
4653
+ const embeddedOf = (ast) => Match.value(ast).pipe(Match.when({ format: "embedded" }, (embedded) => Option.some(embedded)), Match.orElse(() => Option.none()));
4654
+ const printNothingForNonEmbeddedAst = () => "";
4600
4655
  const frameworkFailure = (moduleName, fileName, detail) => InstrumentError.make({
4601
4656
  message: `The framework "${moduleName}" could not parse ${fileName}: ${detail}`,
4602
4657
  cause: new Error(detail)
@@ -4605,7 +4660,7 @@ const hookFailure = (moduleName, hook, fileName, cause) => InstrumentError.make(
4605
4660
  message: `The framework "${moduleName}" threw while running ${hook} for ${fileName}: ${errorTextOf(cause)}`,
4606
4661
  cause
4607
4662
  });
4608
- const errorTextOf = (cause) => Option.getOrElse(Option.map(ErrorText.fromCause(cause), (text) => text.text), () => "");
4663
+ const errorTextOf = (cause) => Option.getOrElse(Option.map(errorTextOf$2(cause), (rendered) => rendered.text), () => "");
4609
4664
  const runHook = (moduleName, hook, fileName, run) => Effect.try({
4610
4665
  try: run,
4611
4666
  catch: (cause) => hookFailure(moduleName, hook, fileName, cause)
@@ -4620,9 +4675,9 @@ const frameworkEntryOfDataFirst = (moduleName, framework) => ({
4620
4675
  },
4621
4676
  owner: moduleName,
4622
4677
  ownerVersion: framework.claim.ownerVersion,
4623
- parse: (text, fileName) => Effect.gen(function* () {
4678
+ parse: Effect.fn("stryker.instrument.framework_entry.parse")(function* (text, fileName) {
4624
4679
  const context = frameworkContextOf(yield* loadFrameworkToolkit);
4625
- const lineTable = yield* Effect.orDie(S.decodeEffect(LineTableFromText)(text));
4680
+ const lineStarts = lineStartsOf(text);
4626
4681
  const result = yield* runHook(moduleName, "parse", fileName, () => framework.parse(text, context));
4627
4682
  const document = yield* settled(moduleName, fileName, result);
4628
4683
  return {
@@ -4632,23 +4687,25 @@ const frameworkEntryOfDataFirst = (moduleName, framework) => ({
4632
4687
  rawContent: text,
4633
4688
  document,
4634
4689
  context,
4635
- scripts: embeddedScriptsOf(document, text, fileName, lineTable)
4690
+ scripts: embeddedScriptsOf(document, text, fileName, lineStarts)
4636
4691
  };
4637
4692
  }),
4638
- transform: (ast, mutantCollector, context) => Effect.gen(function* () {
4639
- const embedded = embeddedOf(ast);
4640
- const warnings = yield* Effect.forEach(embedded.scripts, (script) => context.transform(script.ast, mutantCollector, context));
4641
- embedded.document = yield* runHook(moduleName, "transform", embedded.originFileName, () => framework.transform(embedded.document, embedded.context));
4642
- return warnings.flat();
4693
+ transform: (ast, mutantCollector, context) => Option.match(embeddedOf(ast), {
4694
+ onNone: () => Effect.fail(embeddedAstError(ast)),
4695
+ onSome: Effect.fn("stryker.instrument.framework_entry.transform")(function* (embedded) {
4696
+ const warnings = yield* Effect.forEach(embedded.scripts, (script) => context.transform(script.ast, mutantCollector, context));
4697
+ embedded.document = yield* runHook(moduleName, "transform", embedded.originFileName, () => framework.transform(embedded.document, embedded.context));
4698
+ return warnings.flat();
4699
+ })
4643
4700
  }),
4644
- print: (ast) => {
4645
- const embedded = embeddedOf(ast);
4646
- return framework.print(embedded.document, embedded.context);
4647
- },
4648
- disableTypeChecks: (ast) => {
4649
- const embedded = embeddedOf(ast);
4650
- return runHook(moduleName, "disableTypeChecks", embedded.originFileName, () => framework.disableTypeChecks(embedded.rawContent)).pipe(Effect.flatMap((result) => settled(moduleName, embedded.originFileName, result)));
4651
- }
4701
+ print: (ast) => Option.match(embeddedOf(ast), {
4702
+ onNone: printNothingForNonEmbeddedAst,
4703
+ onSome: (embedded) => framework.print(embedded.document, embedded.context)
4704
+ }),
4705
+ disableTypeChecks: (ast) => Option.match(embeddedOf(ast), {
4706
+ onNone: () => Effect.fail(embeddedAstError(ast)),
4707
+ onSome: (embedded) => runHook(moduleName, "disableTypeChecks", embedded.originFileName, () => framework.disableTypeChecks(embedded.rawContent)).pipe(Effect.flatMap((result) => settled(moduleName, embedded.originFileName, result)))
4708
+ })
4652
4709
  });
4653
4710
  const frameworkEntryOf = dual((args) => args.length >= 2, frameworkEntryOfDataFirst);
4654
4711
  //#endregion
@@ -4711,22 +4768,26 @@ const admitInstrumentFiles = Workflow.make({
4711
4768
  });
4712
4769
  //#endregion
4713
4770
  //#region src/instrument-files.cell.ts
4714
- const printedFile = (raw, { file, ast }) => ({
4771
+ const printedFile = (raw, { file, ast }) => Result.map(print(ast, raw.registry.entryForFormat), (content) => ({
4715
4772
  name: file.name,
4716
4773
  mutate: file.mutate,
4717
- content: print(ast, raw.registry.entryForFormat)
4718
- });
4719
- const instrumentedResult = (raw) => InstrumentResult.make({
4720
- files: raw.parsed.map((parsed) => printedFile(raw, parsed)),
4774
+ content
4775
+ }));
4776
+ const printedFiles = (raw) => Effect.forEach(raw.parsed, (parsed) => Effect.fromResult(printedFile(raw, parsed)), { concurrency: 1 }).pipe(Effect.mapError((failure) => InstrumentError.make({
4777
+ message: failure.message,
4778
+ cause: failure
4779
+ })));
4780
+ const instrumentedResult = (raw) => Effect.map(printedFiles(raw), (files) => InstrumentResult.make({
4781
+ files: [...files],
4721
4782
  mutants: [...raw.mutants],
4722
4783
  skipped: [...raw.skipped]
4723
- });
4784
+ }));
4724
4785
  const skippedOnlyResult = (raw) => InstrumentResult.make({
4725
4786
  files: [],
4726
4787
  mutants: [],
4727
4788
  skipped: [...raw.skipped]
4728
4789
  });
4729
- const readInstrumentFiles = (input) => Effect.gen(function* () {
4790
+ const readInstrumentFiles = Effect.fn("stryker.instrument.read_files")(function* (input) {
4730
4791
  const outcomes = yield* Effect.forEach(input.files, (file) => fileOutcome({
4731
4792
  registry: input.registry,
4732
4793
  file
@@ -4755,7 +4816,7 @@ const readInstrumentFiles = (input) => Effect.gen(function* () {
4755
4816
  };
4756
4817
  });
4757
4818
  const instrumentFilesCell = Sandwich.named("stryker.instrument.files")((input) => readInstrumentFiles(input)).decide(admitInstrumentFiles).write({
4758
- InstrumentFilesAdmitted: (_admitted, raw) => Effect.succeed(instrumentedResult(raw)),
4819
+ InstrumentFilesAdmitted: (_admitted, raw) => instrumentedResult(raw),
4759
4820
  InstrumentFilesSkippedOnly: (_skipped, raw) => Effect.succeed(skippedOnlyResult(raw)),
4760
4821
  CommandRejected: ({ issue }) => Effect.fail(InstrumentError.make({
4761
4822
  message: issue,
@@ -4765,7 +4826,7 @@ const instrumentFilesCell = Sandwich.named("stryker.instrument.files")((input) =
4765
4826
  //#endregion
4766
4827
  //#region src/Instrument.service.ts
4767
4828
  const toSchemaFile = (file) => ({
4768
- name: file.name,
4829
+ name: CanonicalFileName.make(file.name),
4769
4830
  content: file.content,
4770
4831
  mutate: file.mutate
4771
4832
  });
@@ -4810,9 +4871,14 @@ var Instrument_exports = /* @__PURE__ */ __exportAll({
4810
4871
  //#endregion
4811
4872
  //#region src/Mutant/mod.ts
4812
4873
  var Mutant_exports = /* @__PURE__ */ __exportAll({
4874
+ ActionableStatusSchema: () => ActionableStatusSchema,
4813
4875
  CanonicalFileName: () => CanonicalFileName,
4876
+ Column: () => Column,
4877
+ EphemeralStatusSchema: () => EphemeralStatusSchema,
4878
+ HitCount: () => HitCount,
4814
4879
  InstrumenterContext: () => InstrumenterContext,
4815
- LocationSchema: () => LocationSchema$1,
4880
+ Line: () => Line,
4881
+ Location: () => Location,
4816
4882
  Mutant: () => Mutant,
4817
4883
  MutantActivationSchema: () => MutantActivationSchema,
4818
4884
  MutantCoverageSchema: () => MutantCoverageSchema,
@@ -4821,10 +4887,14 @@ var Mutant_exports = /* @__PURE__ */ __exportAll({
4821
4887
  MutantRunOptionsSchema: () => MutantRunOptionsSchema,
4822
4888
  MutantStatusSchema: () => MutantStatusSchema,
4823
4889
  MutatorName: () => MutatorName,
4824
- OpenEndLocationSchema: () => OpenEndLocationSchema,
4825
- PositionSchema: () => PositionSchema,
4826
- ReportLocationFromMutant: () => ReportLocationFromMutant,
4827
- RunOptionsFields: () => RunOptionsFields
4890
+ Offset: () => Offset,
4891
+ OpenEndLocation: () => OpenEndLocation,
4892
+ Position: () => Position,
4893
+ RememberedStatusSchema: () => RememberedStatusSchema,
4894
+ RunOptionsFields: () => RunOptionsFields,
4895
+ ScriptOrigin: () => ScriptOrigin,
4896
+ Span: () => Span,
4897
+ SurvivorStatusSchema: () => SurvivorStatusSchema
4828
4898
  });
4829
4899
  //#endregion
4830
- export { ErrorText_exports as ErrorText, Format_exports as Format, Instrument_exports as Instrument, Mutant_exports as Mutant };
4900
+ export { ErrorText_exports as ErrorText, Format_exports as Format, Instrument_exports as Instrument, Mutant_exports as Mutant, lineStartsOf, locationOf, offsetAt, originAt, positionAt };