@systemfsoftware/stryker-js-instrumenter 10.0.0 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -8,10 +8,14 @@ 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 Order from "effect/Order";
12
- import * as SchemaGetter from "effect/SchemaGetter";
11
+ import { Mutant, Options } from "@systemfsoftware/stryker-js-plugin-interface";
13
12
  import { RegExpParser, visitRegExpAST } from "@eslint-community/regexpp";
14
13
  import * as Arr from "effect/Array";
14
+ import * as Order from "effect/Order";
15
+ import * as SchemaGetter from "effect/SchemaGetter";
16
+ import { SpanTaxonomy } from "@systemfsoftware/stryker-js-cli-contract";
17
+ import { sha256 } from "@noble/hashes/sha2.js";
18
+ import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils.js";
15
19
  //#region src/ErrorText.schema.ts
16
20
  var ErrorText = class extends S.Class("ErrorText")({ text: S.NonEmptyString }) {};
17
21
  var CauseText = class extends S.Class("CauseText")({ text: S.NonEmptyString }) {};
@@ -106,70 +110,8 @@ var ErrorText_exports = /* @__PURE__ */ __exportAll({
106
110
  errorTextOf: () => errorTextOf$2
107
111
  });
108
112
  //#endregion
109
- //#region src/Location.schema.ts
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
- })
135
- });
136
- const OpenEnds = S.Struct({
137
- start: Position,
138
- end: S.optional(Position)
139
- });
140
- const closedEnds = (ends) => Option.map(Option.fromUndefinedOr(ends.end), (end) => ({
141
- start: ends.start,
142
- end
143
- }));
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
113
  //#region src/directives/directive.schema.ts
172
- const MutatorNameSchema = S.String.pipe(S.check(S.isPattern(/^[a-zA-Z]+(?: [a-zA-Z]+)*$/)));
114
+ const MutatorNameSchema = S.String.pipe(S.check(S.isPattern(/^(?:[a-z][a-z0-9]*(?:-[a-z0-9]+)*\/)?[a-zA-Z][a-zA-Z0-9]*(?: [a-zA-Z][a-zA-Z0-9]*)*$/)));
173
115
  const DirectiveReasonSchema = S.String.pipe(S.check(S.isPattern(/^\S(?:[^\r\n\u2028\u2029]*\S)?$/)));
174
116
  const DirectiveSchema = S.Struct({
175
117
  action: S.Literals(["disable", "restore"]),
@@ -179,106 +121,15 @@ const DirectiveSchema = S.Struct({
179
121
  });
180
122
  const LocatedDirectiveSchema = S.Struct({
181
123
  directive: DirectiveSchema,
182
- at: Position,
183
- governedLine: Line
124
+ at: Mutant.Position,
125
+ governedLine: Mutant.Line
184
126
  });
185
127
  S.Struct({
186
128
  directive: DirectiveSchema,
187
- at: Position,
129
+ at: Mutant.Position,
188
130
  mutatorName: MutatorNameSchema
189
131
  });
190
132
  //#endregion
191
- //#region src/Mutant.schema.ts
192
- const MutantStatusSchema = S.Literals([
193
- "Killed",
194
- "Survived",
195
- "NoCoverage",
196
- "CompileError",
197
- "RuntimeError",
198
- "Timeout",
199
- "Ignored",
200
- "Pending"
201
- ]);
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"));
222
- const MutatorName = S.NonEmptyString.pipe(S.brand("MutatorName"));
223
- const CanonicalFileName = S.String.pipe(S.decodeTo(S.String.pipe(S.check(S.isPattern(/^[^\\]+$/)), S.brand("CanonicalFileName")), {
224
- decode: SchemaGetter.transform((fileName) => fileName.replace(/\\/g, "/")),
225
- encode: SchemaGetter.transform((canonical) => canonical)
226
- }));
227
- /**
228
- * A mutant's file location in the mutation-testing-report-schema contract:
229
- * 1-based line and 1-based column, the same base the JSON report and the
230
- * machine stream emit. Every producer on the instrument path (node spans,
231
- * embedded-region shifts) targets this base; no downstream layer converts.
232
- */
233
- const Mutant = S.TaggedStruct("Mutant", {
234
- id: MutantId,
235
- fileName: CanonicalFileName,
236
- mutatorName: MutatorName,
237
- replacement: S.String,
238
- location: Location,
239
- status: S.optional(MutantStatusSchema),
240
- statusReason: S.optional(S.String),
241
- coveredBy: S.String.pipe(S.Array, S.optional),
242
- static: S.optional(S.Boolean),
243
- testsCompleted: S.optional(S.Finite),
244
- description: S.optional(S.String)
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" }));
246
- const MutantFromUnknown = S.Unknown.pipe(S.decodeTo(Mutant));
247
- const RunOptionsFields = {
248
- timeout: S.Finite.pipe(S.check(S.isGreaterThanOrEqualTo(0))),
249
- disableBail: S.Boolean
250
- };
251
- const MutantActivationSchema = S.Literals(["runtime", "static"]);
252
- const HitCount = S.Int.pipe(S.check(S.isGreaterThanOrEqualTo(0)));
253
- const MutantRunOptionsSchema = S.Struct({
254
- ...RunOptionsFields,
255
- activeMutant: Mutant,
256
- sandboxFileName: S.String,
257
- mutantActivation: MutantActivationSchema,
258
- reloadEnvironment: S.Boolean,
259
- testFilter: S.String.pipe(S.Array, S.optionalKey),
260
- hitLimit: S.optionalKey(HitCount)
261
- });
262
- const MutantCoverageSchema = S.Struct({
263
- perTest: S.Record(S.String, S.Record(MutantId, HitCount)),
264
- static: S.Record(MutantId, HitCount)
265
- });
266
- var InstrumenterContext = class extends S.Class("InstrumenterContext")({
267
- activeMutant: S.optional(S.String),
268
- currentTestId: S.optional(S.String),
269
- mutantCoverage: S.optional(MutantCoverageSchema),
270
- hitCount: S.optional(HitCount),
271
- hitLimit: S.optional(HitCount)
272
- }) {
273
- static NAMESPACE = "__stryker__";
274
- static MUTATION_COVERAGE_OBJECT = "mutantCoverage";
275
- static ACTIVE_MUTANT = "activeMutant";
276
- static CURRENT_TEST_ID = "currentTestId";
277
- static HIT_COUNT = "hitCount";
278
- static HIT_LIMIT = "hitLimit";
279
- static ACTIVE_MUTANT_ENV_VARIABLE = "__STRYKER_ACTIVE_MUTANT__";
280
- };
281
- //#endregion
282
133
  //#region src/Instrument.schema.ts
283
134
  var InstrumentError = class extends S.TaggedError("@systemfsoftware/stryker-js-instrumenter/Instrument.schema/InstrumentError")("InstrumentError", {
284
135
  message: S.String,
@@ -291,9 +142,9 @@ var InstrumentError = class extends S.TaggedError("@systemfsoftware/stryker-js-i
291
142
  });
292
143
  }
293
144
  };
294
- const MutateDescriptionSchema = S.Union([S.Boolean, S.Array(Location)]);
145
+ const MutateDescriptionSchema = S.Union([S.Boolean, S.Array(Mutant.Location)]);
295
146
  const FileSchema = S.Struct({
296
- name: CanonicalFileName,
147
+ name: Mutant.CanonicalFileName,
297
148
  content: S.String,
298
149
  mutate: MutateDescriptionSchema
299
150
  });
@@ -301,8 +152,9 @@ const IgnorerSchema = S.Unknown;
301
152
  S.Struct({
302
153
  excludedMutations: S.Array(MutatorNameSchema),
303
154
  ignorers: S.Array(IgnorerSchema),
304
- noHeader: S.optional(S.Boolean),
305
- optInMutations: S.String.pipe(S.Array, S.optional)
155
+ mutators: S.Unknown,
156
+ mutantSetPolicy: Options.MutantSetPolicy,
157
+ noHeader: S.optional(S.Boolean)
306
158
  });
307
159
  var InstrumentFileSkip = class extends S.TaggedClass()("InstrumentFileSkip", {
308
160
  file: S.String,
@@ -318,7 +170,7 @@ var InstrumentFilesCommand = class extends S.TaggedClass()("InstrumentFilesComma
318
170
  };
319
171
  var InstrumentResult = class extends S.TaggedClass()("InstrumentResult", {
320
172
  files: S.Array(FileSchema),
321
- mutants: S.Array(Mutant),
173
+ mutants: S.Array(Mutant.Mutant),
322
174
  skipped: S.Array(InstrumentFileSkip)
323
175
  }) {};
324
176
  const PlacerNameSchema = S.Literals([
@@ -331,18 +183,96 @@ var MutantsUnapplied = class extends S.TaggedError()("MutantsUnapplied", {
331
183
  placer: PlacerNameSchema,
332
184
  mutatorNames: S.Array(MutatorNameSchema),
333
185
  cause: S.Defect()
334
- }) {};
186
+ }) {
187
+ get message() {
188
+ return `Failed to apply ${this.mutatorNames.join(", ")} in ${this.fileName}`;
189
+ }
190
+ };
335
191
  var MutantNotApplied = class extends S.TaggedError()("MutantNotApplied", {
336
192
  fileName: S.String,
337
193
  mutatorName: MutatorNameSchema
338
- }) {};
339
- var NodeWithoutSpan = class extends S.TaggedError()("NodeWithoutSpan", { fileName: S.String }) {};
194
+ }) {
195
+ get message() {
196
+ return `Could not apply the ${this.mutatorName} mutant in ${this.fileName}`;
197
+ }
198
+ };
199
+ var NodeWithoutSpan = class extends S.TaggedError()("NodeWithoutSpan", { fileName: S.String }) {
200
+ get message() {
201
+ return `Node without a span in ${this.fileName}`;
202
+ }
203
+ };
340
204
  var MutantsUnplaced = class extends S.TaggedError()("MutantsUnplaced", {
341
205
  fileName: S.String,
342
206
  detail: S.String
343
- }) {};
207
+ }) {
208
+ get message() {
209
+ return `Mutants cannot be placed. This shouldn't happen! Unplaced mutants: ${this.detail}`;
210
+ }
211
+ };
344
212
  var PlacementRefused = class extends S.TaggedError()("PlacementRefused", { message: S.String }) {};
345
213
  //#endregion
214
+ //#region src/relational-sufficient-sets.ts
215
+ /**
216
+ * The sufficient sets of non-redundant relational-operator mutations.
217
+ *
218
+ * Source: René Just and Franz Schweiggert, "Higher Accuracy and Lower Run Time:
219
+ * Efficient Mutation Analysis Using Non-Redundant Mutation Operators", Journal
220
+ * of Software Testing, Verification and Reliability 24(5), 2014, Table III
221
+ * (https://homes.cs.washington.edu/~rjust/publ/non_redundant_mutants_jstvr_2014.pdf).
222
+ *
223
+ * The paper groups every input tuple into the three intervals `a < b`,
224
+ * `a == b`, and `a > b`. For each relational operator exactly one
225
+ * minimal-distance mutation covers each interval, and those three mutations
226
+ * subsume every other relational replacement. Each row below lists the two
227
+ * replacement operators and the boolean the whole comparison collapses to. The
228
+ * sets assume numeric operands without `NaN`; `mutantSetPolicy: 'full'`
229
+ * restores every variant.
230
+ */
231
+ const RELATIONAL_OPERATORS = [
232
+ "<",
233
+ "<=",
234
+ ">",
235
+ ">="
236
+ ];
237
+ const isRelationalOperator = (operator) => RELATIONAL_OPERATORS.some((relational) => relational === operator);
238
+ const SUFFICIENT_RELATIONAL_SETS = {
239
+ "<": {
240
+ replacements: ["<=", "!="],
241
+ literal: false
242
+ },
243
+ "<=": {
244
+ replacements: ["<", "=="],
245
+ literal: true
246
+ },
247
+ ">": {
248
+ replacements: [">=", "!="],
249
+ literal: false
250
+ },
251
+ ">=": {
252
+ replacements: [">", "=="],
253
+ literal: true
254
+ }
255
+ };
256
+ //#endregion
257
+ //#region src/Location.schema.ts
258
+ const Offset = S.Int.check(S.isGreaterThanOrEqualTo(0));
259
+ const OffsetEnds = S.Struct({
260
+ start: Offset,
261
+ end: Offset
262
+ });
263
+ const Span = S.declare((value) => S.is(OffsetEnds)(value) && Mutant.notReversed(Order.Number)(value), {
264
+ expected: "a span whose end is not before its start",
265
+ toCodecArbitrary: () => S.link()(OffsetEnds, {
266
+ decode: SchemaGetter.transform(Mutant.inOrder(Order.Number)),
267
+ encode: SchemaGetter.transform((ends) => ends)
268
+ })
269
+ });
270
+ const ScriptOrigin = S.Struct({
271
+ line: Mutant.Line,
272
+ columnShift: Offset
273
+ });
274
+ S.is(Offset), S.is(Span);
275
+ //#endregion
346
276
  //#region src/Location.ts
347
277
  const LINE_TERMINATOR = /\r\n|[\n\r\u2028\u2029]/g;
348
278
  const lineStartsOf = (text) => [0, ...[...text.matchAll(LINE_TERMINATOR)].map((match) => match.index + match[0].length)];
@@ -370,7 +300,7 @@ const positionAtOffset = (lineStarts, offset, low, high, start) => Boolean$1.mat
370
300
  })
371
301
  });
372
302
  const positionAt = dual(2, (lineStarts, offset) => positionAtOffset(lineStarts, offset, 0, lineStarts.length - 1, 0));
373
- const locationOf = dual(2, (lineStarts, span) => Location.make({
303
+ const locationOf = dual(2, (lineStarts, span) => Mutant.Location.make({
374
304
  start: positionAt(lineStarts, span.start),
375
305
  end: positionAt(lineStarts, span.end)
376
306
  }));
@@ -1407,7 +1337,7 @@ function toApiMutant(mutant) {
1407
1337
  mutatorName: mutant.mutatorName,
1408
1338
  replacement: mutant.replacementCode
1409
1339
  };
1410
- return S.decodeResult(Mutant)(mutant.ignoreReason === void 0 ? baseFields : {
1340
+ return S.decodeResult(Mutant.Mutant)(mutant.ignoreReason === void 0 ? baseFields : {
1411
1341
  ...baseFields,
1412
1342
  statusReason: mutant.ignoreReason,
1413
1343
  status: "Ignored"
@@ -1828,9 +1758,19 @@ const booleanOperators = Object.freeze([
1828
1758
  ">=",
1829
1759
  "||"
1830
1760
  ]);
1831
- const conditionalExpressionMutator = (node, context) => Match.value(isTestOfLoop(node, context)).pipe(Match.when(true, () => [booleanLiteral(false)]), Match.orElse(() => conditionTestMutants(node, context)));
1761
+ const conditionalExpressionMutator = (node, context) => Match.value(isTestOfLoop(node, context)).pipe(Match.when(true, () => withSufficientConditional(node, context, [booleanLiteral(false)])), Match.orElse(() => conditionTestMutants(node, context)));
1832
1762
  function conditionTestMutants(node, context) {
1833
- return Match.value(isTestOfCondition(node, context)).pipe(Match.when(true, () => [booleanLiteral(true), booleanLiteral(false)]), Match.orElse(() => booleanExpressionMutants(node, context)));
1763
+ return Match.value(isTestOfCondition(node, context)).pipe(Match.when(true, () => withSufficientConditional(node, context, [booleanLiteral(true), booleanLiteral(false)])), Match.orElse(() => booleanExpressionMutants(node, context)));
1764
+ }
1765
+ function withSufficientConditional(node, context, replacements) {
1766
+ const literal = sufficientRelationalLiteral(node, context);
1767
+ return literal === void 0 ? replacements : appendBooleanLiteral(replacements, literal);
1768
+ }
1769
+ function sufficientRelationalLiteral(node, context) {
1770
+ return Option.getOrUndefined(Option.map(Option.filter(Option.fromNullishOr(relationalSiteFacts(node, context)), (facts) => [facts.inConditionPosition, context.mutantSetPolicy === "default"].every(Boolean)), (facts) => SUFFICIENT_RELATIONAL_SETS[facts.operator].literal));
1771
+ }
1772
+ function appendBooleanLiteral(replacements, literal) {
1773
+ return replacements.some((replacement) => isBooleanLiteral(replacement) && replacement.value === literal) ? replacements : [...replacements, booleanLiteral(literal)];
1834
1774
  }
1835
1775
  function booleanExpressionMutants(node, context) {
1836
1776
  return Match.value(isBooleanExpression(node)).pipe(Match.when(true, () => booleanExpressionReplacements(context)), Match.orElse(() => statementMutants(node)));
@@ -1893,6 +1833,40 @@ function isTestBearingStatement(node) {
1893
1833
  function testOfStatement(node) {
1894
1834
  return Match.value(node).pipe(Match.when(isTestBearingStatement, (statement) => statement.test ?? void 0), Match.orElse(() => void 0));
1895
1835
  }
1836
+ const isConditionalExpression = (node) => node.type === "ConditionalExpression";
1837
+ function isTestOfConditionalExpression(node, context) {
1838
+ return Option.exists(Option.filter(Option.fromNullishOr(context.parent), isConditionalExpression), (parent) => parent.test === node);
1839
+ }
1840
+ function isConditionPosition(node, context) {
1841
+ return [
1842
+ isTestOfLoop(node, context),
1843
+ isTestOfCondition(node, context),
1844
+ isTestOfConditionalExpression(node, context)
1845
+ ].some(Boolean);
1846
+ }
1847
+ function binaryOperatorOf(node) {
1848
+ return node.type === "BinaryExpression" ? node.operator : void 0;
1849
+ }
1850
+ const isRelationalComparison = (node) => Option.exists(Option.fromNullishOr(binaryOperatorOf(node)), isRelationalOperator);
1851
+ const relationalSiteFactsDataFirst = (node, context) => Option.getOrUndefined(Option.map(Option.filter(Option.fromNullishOr(binaryOperatorOf(node)), isRelationalOperator), (operator) => ({
1852
+ operator,
1853
+ inConditionPosition: isConditionPosition(node, context)
1854
+ })));
1855
+ const relationalSiteFacts = dual((args) => args.length >= 2, relationalSiteFactsDataFirst);
1856
+ const relationalSufficientDataFirst = (facts, replacement) => facts === void 0 ? true : sufficientInCondition(facts, replacement);
1857
+ function sufficientInCondition(facts, replacement) {
1858
+ return facts.inConditionPosition ? sufficientReplacementOf(facts, replacement) : true;
1859
+ }
1860
+ function sufficientReplacementOf(facts, replacement) {
1861
+ return Match.value(replacement).pipe(Match.when(isBooleanLiteral, (literal) => literal.value === SUFFICIENT_RELATIONAL_SETS[facts.operator].literal), Match.when(isBinaryExpressionNode, (binary) => isSufficientOperator(facts, binary.operator)), Match.orElse(() => true));
1862
+ }
1863
+ const relationalSufficientReplacement = dual((args) => args.length >= 2, relationalSufficientDataFirst);
1864
+ function isSufficientOperator(facts, operator) {
1865
+ return SUFFICIENT_RELATIONAL_SETS[facts.operator].replacements.some((sufficient) => sufficient === operator);
1866
+ }
1867
+ function isBinaryExpressionNode(node) {
1868
+ return node.type === "BinaryExpression";
1869
+ }
1896
1870
  function isBooleanExpression(node) {
1897
1871
  return isOperatorExpression(node) && booleanOperators.includes(node.operator);
1898
1872
  }
@@ -1910,12 +1884,23 @@ const operators = {
1910
1884
  "!==": ["==="]
1911
1885
  };
1912
1886
  const EQUALITY_OPERATOR_KEYS = Object.keys(operators);
1913
- const equalityOperatorMutator = (node) => Match.value(node).pipe(Match.when(isEqualityBinary, (binary) => mutatedEqualityOperators(binary)), Match.orElse(() => NO_MUTANTS));
1887
+ const equalityOperatorMutator = (node, context) => Match.value(node).pipe(Match.when(isEqualityBinary, (binary) => mutatedEqualityOperators(binary, context)), Match.orElse(() => NO_MUTANTS));
1914
1888
  function isEqualityBinary(node) {
1915
1889
  return node.type === "BinaryExpression" && EQUALITY_OPERATOR_KEYS.includes(node.operator);
1916
1890
  }
1917
- function mutatedEqualityOperators(binary) {
1918
- return operators[binary.operator].map((operator) => withOperator(binary, operator));
1891
+ function mutatedEqualityOperators(binary, context) {
1892
+ const base = operators[binary.operator].map((operator) => withOperator(binary, operator));
1893
+ return isRelationalConditionSite(binary, context) ? [...base, ...sufficientAdditions(binary)] : base;
1894
+ }
1895
+ function isRelationalConditionSite(node, context) {
1896
+ return [
1897
+ context.mutantSetPolicy === "default",
1898
+ isRelationalComparison(node),
1899
+ isConditionPosition(node, context)
1900
+ ].every(Boolean);
1901
+ }
1902
+ function sufficientAdditions(binary) {
1903
+ return isRelationalOperator(binary.operator) ? SUFFICIENT_RELATIONAL_SETS[binary.operator].replacements.filter((operator) => !operators[binary.operator].some((replacement) => replacement === operator)).map((operator) => withOperator(binary, operator)) : NO_MUTANTS;
1919
1904
  }
1920
1905
  const logicalOperatorReplacements = Object.freeze({
1921
1906
  "&&": "||",
@@ -2198,6 +2183,27 @@ const optInMutators = Object.freeze({
2198
2183
  SynchronizationRemoval: synchronizationRemovalMutator,
2199
2184
  FinalizerEscape: finalizerEscapeMutator
2200
2185
  });
2186
+ const stockRegistry = {
2187
+ defaults: defaultMutators,
2188
+ optIn: optInMutators
2189
+ };
2190
+ const registryOfDataFirst = (catalogs, contributions) => {
2191
+ const implementations = new Map([
2192
+ ...Object.entries(defaultMutators),
2193
+ ...Object.entries(optInMutators),
2194
+ ...contributions.flatMap(({ entries }) => entries.map((entry) => [entry.name, entry.implementation]))
2195
+ ]);
2196
+ const entries = catalogs.flatMap((catalog) => catalog.entries);
2197
+ const entriesOfTier = (tier) => entries.filter((entry) => entry.tier === tier).flatMap((entry) => Option.match(Option.fromNullishOr(implementations.get(entry.name)), {
2198
+ onNone: () => [],
2199
+ onSome: (implementation) => [[entry.name, implementation]]
2200
+ }));
2201
+ return {
2202
+ defaults: Object.fromEntries(entriesOfTier("default")),
2203
+ optIn: Object.fromEntries(entriesOfTier("optIn"))
2204
+ };
2205
+ };
2206
+ const registryOf = dual((args) => args.length >= 2, registryOfDataFirst);
2201
2207
  /**
2202
2208
  * The entries a run applies, and the names its directives may reference.
2203
2209
  *
@@ -2221,7 +2227,7 @@ const selectMutators = dual((args) => args.length >= 2, selectMutatorsDataFirst)
2221
2227
  var ParseFailed = class extends S.TaggedError("@systemfsoftware/stryker-js-instrumenter/Parser.schema/ParseFailed")("ParseFailed", {
2222
2228
  fileName: S.String,
2223
2229
  message: S.String,
2224
- location: Position,
2230
+ location: Mutant.Position,
2225
2231
  cause: S.Defect()
2226
2232
  }) {
2227
2233
  get message() {
@@ -2232,7 +2238,7 @@ var ParseFailed = class extends S.TaggedError("@systemfsoftware/stryker-js-instr
2232
2238
  //#region src/Parser.service.ts
2233
2239
  const oxcModule = Effect.cached(Effect.promise(() => import("oxc-parser")));
2234
2240
  const loadOxc = Effect.flatMap(oxcModule, (load) => load);
2235
- const parseWithOxcDataFirst = Effect.fn("stryker.instrument.parser.parseWithOxc")(function* (text, fileName, lang) {
2241
+ const parseWithOxcDataFirst = Effect.fn(SpanTaxonomy.Spans.instrumentParserParseWithOxc.name)(function* (text, fileName, lang) {
2236
2242
  const result = (yield* loadOxc).parseSync(fileName, text, {
2237
2243
  lang,
2238
2244
  range: true
@@ -2259,7 +2265,7 @@ const oxcParseFailure = (errors, fileName, lineStarts) => Option.match(Option.fr
2259
2265
  });
2260
2266
  const oxcErrorLabelStart = (error) => Option.getOrElse(Option.map(Option.fromNullishOr(error.labels.at(0)), (label) => label.start), () => 0);
2261
2267
  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) });
2262
- const parseJSDataFirst = Effect.fn("stryker.instrument.parser.parseJS")(function* (text, fileName) {
2268
+ const parseJSDataFirst = Effect.fn(SpanTaxonomy.Spans.instrumentParserParseJS.name)(function* (text, fileName) {
2263
2269
  const parsed = yield* parseWithOxc(text, fileName, "js");
2264
2270
  return {
2265
2271
  originFileName: fileName,
@@ -2270,7 +2276,7 @@ const parseJSDataFirst = Effect.fn("stryker.instrument.parser.parseJS")(function
2270
2276
  };
2271
2277
  });
2272
2278
  const parseJS = dual((args) => args.length >= 2, parseJSDataFirst);
2273
- const parseTSDataFirst = Effect.fn("stryker.instrument.parser.parseTS")(function* (text, fileName) {
2279
+ const parseTSDataFirst = Effect.fn(SpanTaxonomy.Spans.instrumentParserParseTS.name)(function* (text, fileName) {
2274
2280
  const parsed = yield* parseWithOxc(text, fileName, "ts");
2275
2281
  return {
2276
2282
  originFileName: fileName,
@@ -2281,7 +2287,7 @@ const parseTSDataFirst = Effect.fn("stryker.instrument.parser.parseTS")(function
2281
2287
  };
2282
2288
  });
2283
2289
  const parseTS = dual((args) => args.length >= 2, parseTSDataFirst);
2284
- const parseTsxDataFirst = Effect.fn("stryker.instrument.parser.parseTsx")(function* (text, fileName) {
2290
+ const parseTsxDataFirst = Effect.fn(SpanTaxonomy.Spans.instrumentParserParseTsx.name)(function* (text, fileName) {
2285
2291
  const parsed = yield* parseWithOxc(text, fileName, "tsx");
2286
2292
  return {
2287
2293
  root: parsed.root,
@@ -2766,7 +2772,7 @@ const logicalExpressionText = (ctx, node, prec) => {
2766
2772
  };
2767
2773
  const conditionalExpressionText = (ctx, node, prec) => {
2768
2774
  const myPrec = PREC.Conditional;
2769
- const testStr = wrapIfNeeded(ctx, node.test, precOf(node.test), myPrec, false);
2775
+ const testStr = wrapIfNeeded(ctx, node.test, precOf(node.test), myPrec, CONDITIONAL_TEST_WRAPPED_KINDS[node.test.type] === true);
2770
2776
  const consStr = dispatchNode(ctx, node.consequent, PREC.Assignment);
2771
2777
  const altStr = dispatchNode(ctx, node.alternate, PREC.Assignment);
2772
2778
  return parenthesizedIf(myPrec < prec, `${testStr} ? ${consStr} : ${altStr}`);
@@ -3164,6 +3170,7 @@ const MEMBER_OBJECT_WRAPPED_KINDS = {
3164
3170
  AwaitExpression: true,
3165
3171
  YieldExpression: true
3166
3172
  };
3173
+ const CONDITIONAL_TEST_WRAPPED_KINDS = { ConditionalExpression: true };
3167
3174
  const CALLEE_WRAPPED_KINDS = {
3168
3175
  SequenceExpression: true,
3169
3176
  ConditionalExpression: true
@@ -3339,7 +3346,11 @@ var FormatOverrideUnclaimed = class extends S.TaggedError()("FormatOverrideUncla
3339
3346
  fileName: S.String,
3340
3347
  formatId: S.String,
3341
3348
  reason: S.String
3342
- }) {};
3349
+ }) {
3350
+ get message() {
3351
+ return `Cannot instrument ${this.fileName}: ${this.reason}`;
3352
+ }
3353
+ };
3343
3354
  const pinOf = (command) => Option.fromUndefinedOr(command.formatId);
3344
3355
  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)));
3345
3356
  const claimFor = (command) => command.claims.find((claim) => matchesCommand(claim, command));
@@ -3380,9 +3391,117 @@ const resolveFormat = Workflow.make({
3380
3391
  error: FormatOverrideUnclaimed,
3381
3392
  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)
3382
3393
  });
3394
+ const AridRuleId = S.Literals([
3395
+ "arid-logging",
3396
+ "arid-telemetry",
3397
+ "arid-time",
3398
+ "arid-config-default",
3399
+ "arid-memoization"
3400
+ ]);
3401
+ const AridCalleeSchema = S.Struct({
3402
+ object: S.String,
3403
+ member: S.String
3404
+ });
3405
+ const AridFrameSchema = S.Struct({
3406
+ callee: S.Option(AridCalleeSchema),
3407
+ childIsArgument: S.Boolean
3408
+ });
3409
+ const NAMED_LOGGERS = ["Logger", "console"];
3410
+ const readsNamespace = (callee, object) => callee.object === object;
3411
+ const readsName = (callee, name) => callee.member === name;
3412
+ const readsMember = (callee, object, member) => [readsNamespace(callee, object), readsName(callee, member)].every(Boolean);
3413
+ const readsMemberPrefixed = (callee, object, prefix) => [readsNamespace(callee, object), callee.member.startsWith(prefix)].every(Boolean);
3414
+ const ARID_RULES_IN_PRECEDENCE_ORDER = [
3415
+ {
3416
+ ruleId: "arid-logging",
3417
+ matches: (callee) => readsMemberPrefixed(callee, "Effect", "log")
3418
+ },
3419
+ {
3420
+ ruleId: "arid-logging",
3421
+ matches: (callee) => NAMED_LOGGERS.includes(callee.object)
3422
+ },
3423
+ {
3424
+ ruleId: "arid-telemetry",
3425
+ matches: (callee) => readsMember(callee, "Effect", "withSpan")
3426
+ },
3427
+ {
3428
+ ruleId: "arid-telemetry",
3429
+ matches: (callee) => readsMemberPrefixed(callee, "Effect", "annotate")
3430
+ },
3431
+ {
3432
+ ruleId: "arid-telemetry",
3433
+ matches: (callee) => readsMember(callee, "Effect", "withLogSpan")
3434
+ },
3435
+ {
3436
+ ruleId: "arid-telemetry",
3437
+ matches: (callee) => readsNamespace(callee, "Metric")
3438
+ },
3439
+ {
3440
+ ruleId: "arid-time",
3441
+ matches: (callee) => readsMember(callee, "Effect", "sleep")
3442
+ },
3443
+ {
3444
+ ruleId: "arid-time",
3445
+ matches: (callee) => readsNamespace(callee, "Schedule")
3446
+ },
3447
+ {
3448
+ ruleId: "arid-time",
3449
+ matches: (callee) => readsNamespace(callee, "Duration")
3450
+ },
3451
+ {
3452
+ ruleId: "arid-time",
3453
+ matches: (callee) => readsMember(callee, "Date", "now")
3454
+ },
3455
+ {
3456
+ ruleId: "arid-config-default",
3457
+ matches: (callee) => readsMember(callee, "Config", "withDefault")
3458
+ },
3459
+ {
3460
+ ruleId: "arid-memoization",
3461
+ matches: (callee) => readsMember(callee, "Effect", "cached")
3462
+ },
3463
+ {
3464
+ ruleId: "arid-memoization",
3465
+ matches: (callee) => readsMember(callee, "Effect", "cachedWithTTL")
3466
+ }
3467
+ ];
3468
+ const ruleFor = (callee) => Option.map(Option.fromNullishOr(ARID_RULES_IN_PRECEDENCE_ORDER.find((rule) => rule.matches(callee))), (rule) => rule.ruleId);
3469
+ const calleeDetail = (callee) => `${callee.object}.${callee.member}`;
3470
+ var AridCodeCommand = class extends S.TaggedClass()("AridCodeCommand", {
3471
+ policy: Options.MutantSetPolicy,
3472
+ frames: S.Array(AridFrameSchema)
3473
+ }) {
3474
+ static [Workflow.InstrumentationBrand] = {};
3475
+ };
3476
+ const AridCodeDecisionTypeId = Symbol.for("@systemfsoftware/stryker-js-instrumenter/AridCodeDecision");
3477
+ var AridSuppressed = class extends S.TaggedClass()("AridSuppressed", {
3478
+ ruleId: AridRuleId,
3479
+ detail: S.String
3480
+ }) {
3481
+ [AridCodeDecisionTypeId] = AridCodeDecisionTypeId;
3482
+ };
3483
+ var AridKept = class extends S.TaggedClass()("AridKept", {}) {
3484
+ [AridCodeDecisionTypeId] = AridCodeDecisionTypeId;
3485
+ };
3486
+ const matchOfFrame = (frame) => Option.flatMap(Option.filter(frame.callee, () => frame.childIsArgument), (callee) => Option.map(ruleFor(callee), (ruleId) => ({
3487
+ ruleId,
3488
+ callee
3489
+ })));
3490
+ const firstMatch = (frames) => Option.firstSomeOf(frames.map(matchOfFrame));
3491
+ const defaultPolicyDecision = (frames) => Match.value(firstMatch(frames)).pipe(Match.when(Option.isSome, (found) => AridSuppressed.make({
3492
+ ruleId: found.value.ruleId,
3493
+ detail: calleeDetail(found.value.callee)
3494
+ })), Match.when(Option.isNone, () => AridKept.make({})), Match.exhaustive);
3495
+ const decisionFor = (command) => Match.value(command.policy).pipe(Match.when("default", () => defaultPolicyDecision(command.frames)), Match.when("full", () => AridKept.make({})), Match.exhaustive);
3496
+ const aridCode = Workflow.make({
3497
+ command: AridCodeCommand,
3498
+ decision: S.Union([AridSuppressed, AridKept]),
3499
+ error: S.Never,
3500
+ decide: (command) => Result.succeed(decisionFor(command))
3501
+ });
3383
3502
  //#endregion
3384
3503
  //#region src/directives/decode-directive.workflow.ts
3385
- const DIRECTIVE_PATTERN = /^\s?Stryker (disable|restore)(?: (next-line))? ([a-zA-Z, ]+)(?::(.+)?)?/;
3504
+ const DIRECTIVE_PATTERN = /^\s?Stryker (disable|restore)(?: (next-line))? ([a-zA-Z0-9,/ -]+)(?::(.+)?)?/;
3386
3505
  const DEFAULT_REASON = "Ignored using a comment";
3387
3506
  const NEXT_LINE$1 = "next-line";
3388
3507
  var DecodeDirectiveCommand = class extends S.TaggedClass()("DecodeDirectiveCommand", { commentText: S.String }) {
@@ -3436,6 +3555,23 @@ const foldRule = Workflow.make({
3436
3555
  decide: (command) => Match.value(command.directive.directive.action).pipe(Match.when("restore", () => Result.succeed(RestoreFolded.make({ rule: [...command.rule, command.directive] }))), Match.when("disable", () => Result.succeed(DisableFolded.make({ rule: [...command.rule, command.directive] }))), Match.exhaustive)
3437
3556
  });
3438
3557
  //#endregion
3558
+ //#region src/InstrumentContext.schema.ts
3559
+ var InstrumenterContext = class extends S.Class("InstrumenterContext")({
3560
+ activeMutant: S.optional(S.String),
3561
+ currentTestId: S.optional(S.String),
3562
+ mutantCoverage: S.optional(Mutant.MutantCoverageSchema),
3563
+ hitCount: S.optional(Mutant.HitCount),
3564
+ hitLimit: S.optional(Mutant.HitCount)
3565
+ }) {
3566
+ static NAMESPACE = "__stryker__";
3567
+ static MUTATION_COVERAGE_OBJECT = "mutantCoverage";
3568
+ static ACTIVE_MUTANT = "activeMutant";
3569
+ static CURRENT_TEST_ID = "currentTestId";
3570
+ static HIT_COUNT = "hitCount";
3571
+ static HIT_LIMIT = "hitLimit";
3572
+ static ACTIVE_MUTANT_ENV_VARIABLE = "__STRYKER_ACTIVE_MUTANT__";
3573
+ };
3574
+ //#endregion
3439
3575
  //#region src/InstrumentHeader.ts
3440
3576
  const hasLeadingComments$1 = (value) => Predicate.hasProperty(value, "leadingComments") && Array.isArray(value.leadingComments);
3441
3577
  const STRYKER_NAMESPACE_HELPER = "stryNS_9fa48";
@@ -3522,6 +3658,18 @@ function commentedHeader(firstHeader, leadingComments, rest) {
3522
3658
  return [cloned, ...rest];
3523
3659
  }
3524
3660
  //#endregion
3661
+ //#region src/MutantIdentity.ts
3662
+ const MUTANT_ID_HEX_LENGTH = 16;
3663
+ const lengthPrefixed = (value) => `${value.length}:${value}`;
3664
+ const tupleFields = (tuple) => [
3665
+ tuple.fileName,
3666
+ tuple.mutatorName,
3667
+ tuple.originalCode,
3668
+ tuple.replacementCode
3669
+ ];
3670
+ const mutantTupleKey = (tuple) => tupleFields(tuple).map(lengthPrefixed).join("");
3671
+ const mutantIdOf = (identity) => Mutant.MutantId.make(bytesToHex(sha256(utf8ToBytes([...tupleFields(identity), `${identity.ordinal}`].map(lengthPrefixed).join("")))).slice(0, MUTANT_ID_HEX_LENGTH));
3672
+ //#endregion
3525
3673
  //#region src/place-mutants.workflow.ts
3526
3674
  const PlacementFactsSchema = S.Struct({
3527
3675
  isExpression: S.Boolean,
@@ -3535,7 +3683,7 @@ const ReplacementFactsSchema = S.Struct({
3535
3683
  isSwitchCase: S.Boolean
3536
3684
  });
3537
3685
  const PlacedMutantSchema = S.Struct({
3538
- id: MutantId,
3686
+ id: Mutant.MutantId,
3539
3687
  mutatorName: MutatorNameSchema,
3540
3688
  replacement: ReplacementFactsSchema
3541
3689
  });
@@ -3549,19 +3697,19 @@ var PlaceMutantsCommand = class extends S.TaggedClass()("PlaceMutantsCommand", {
3549
3697
  const PlacementDecisionTypeId = Symbol.for("@systemfsoftware/stryker-js-instrumenter/PlacementDecision");
3550
3698
  var ExpressionSite = class extends S.TaggedClass()("ExpressionSite", {
3551
3699
  fileName: S.String,
3552
- mutantIds: S.Array(MutantId)
3700
+ mutantIds: S.Array(Mutant.MutantId)
3553
3701
  }) {
3554
3702
  [PlacementDecisionTypeId] = PlacementDecisionTypeId;
3555
3703
  };
3556
3704
  var StatementSite = class extends S.TaggedClass()("StatementSite", {
3557
3705
  fileName: S.String,
3558
- mutantIds: S.Array(MutantId)
3706
+ mutantIds: S.Array(Mutant.MutantId)
3559
3707
  }) {
3560
3708
  [PlacementDecisionTypeId] = PlacementDecisionTypeId;
3561
3709
  };
3562
3710
  var SwitchCaseSite = class extends S.TaggedClass()("SwitchCaseSite", {
3563
3711
  fileName: S.String,
3564
- mutantIds: S.Array(MutantId)
3712
+ mutantIds: S.Array(Mutant.MutantId)
3565
3713
  }) {
3566
3714
  [PlacementDecisionTypeId] = PlacementDecisionTypeId;
3567
3715
  };
@@ -3573,11 +3721,19 @@ const ExpectedKindSchema = S.Literals([
3573
3721
  var MutantKindMismatch = class extends S.TaggedError()("MutantKindMismatch", {
3574
3722
  fileName: S.String,
3575
3723
  placer: PlacerNameSchema,
3576
- mutantId: MutantId,
3724
+ mutantId: Mutant.MutantId,
3577
3725
  mutatorName: MutatorNameSchema,
3578
3726
  expected: ExpectedKindSchema
3579
- }) {};
3580
- var NoPlacerClaimsNode = class extends S.TaggedError()("NoPlacerClaimsNode", { fileName: S.String }) {};
3727
+ }) {
3728
+ get message() {
3729
+ return `Expected ${this.expected} for mutant ${this.mutantId} (${this.mutatorName}) in ${this.fileName}`;
3730
+ }
3731
+ };
3732
+ var NoPlacerClaimsNode = class extends S.TaggedError()("NoPlacerClaimsNode", { fileName: S.String }) {
3733
+ get message() {
3734
+ return `No placer claims the node in ${this.fileName}`;
3735
+ }
3736
+ };
3581
3737
  const claimingPlacer = (facts) => Match.value(facts).pipe(Match.when({
3582
3738
  isExpression: true,
3583
3739
  expressionIsValid: true
@@ -3626,66 +3782,156 @@ const placeMutants = Workflow.make({
3626
3782
  })
3627
3783
  });
3628
3784
  //#endregion
3785
+ //#region src/mutant-set-policy.workflow.ts
3786
+ const MutantSetFactsSchema = S.Struct({
3787
+ originalCode: S.String,
3788
+ replacementCode: S.String,
3789
+ relationalSufficient: S.Boolean
3790
+ });
3791
+ const MutantSetRuleId = S.Literals([
3792
+ "redundant-relational",
3793
+ "equivalent-to-original",
3794
+ "duplicate-at-site"
3795
+ ]);
3796
+ var MutantSetPolicyCommand = class extends S.TaggedClass()("MutantSetPolicyCommand", {
3797
+ policy: Options.MutantSetPolicy,
3798
+ candidates: S.Array(MutantSetFactsSchema)
3799
+ }) {
3800
+ static [Workflow.InstrumentationBrand] = {};
3801
+ };
3802
+ const MutantSetOutcomeTypeId = Symbol.for("@systemfsoftware/stryker-js-instrumenter/MutantSetOutcome");
3803
+ var MutantSuppressed = class extends S.TaggedClass()("MutantSuppressed", {
3804
+ ruleId: MutantSetRuleId,
3805
+ detail: S.String
3806
+ }) {
3807
+ [MutantSetOutcomeTypeId] = MutantSetOutcomeTypeId;
3808
+ };
3809
+ var MutantKept = class extends S.TaggedClass()("MutantKept", {}) {
3810
+ [MutantSetOutcomeTypeId] = MutantSetOutcomeTypeId;
3811
+ };
3812
+ const MutantSetOutcome = S.Union([MutantSuppressed, MutantKept]);
3813
+ const MutantSetDecision = S.Array(MutantSetOutcome);
3814
+ const canonicalForm = (text) => stripOuterParentheses(text.trim());
3815
+ const stripOuterParentheses = (text) => Match.value(wrapsWhole(text)).pipe(Match.when(true, () => stripOuterParentheses(text.slice(1, -1).trim())), Match.orElse(() => text));
3816
+ const parenDepthDelta = (character) => Match.value(character).pipe(Match.when("(", () => 1), Match.when(")", () => -1), Match.orElse(() => 0));
3817
+ const depthsAfter = (text) => text.split("").reduce((depths, character) => [...depths, Option.getOrElse(Arr.last(depths), () => 0) + parenDepthDelta(character)], []);
3818
+ const wrapsWhole = (text) => {
3819
+ const depths = depthsAfter(text);
3820
+ return [
3821
+ text.startsWith("("),
3822
+ text.endsWith(")"),
3823
+ depths.length > 0,
3824
+ depths.slice(0, -1).every((depth) => depth >= 1),
3825
+ Option.getOrElse(Arr.last(depths), () => 0) === 0
3826
+ ].every(Boolean);
3827
+ };
3828
+ const defaultOnly = (policy, value) => Match.value(policy).pipe(Match.when("full", () => Option.none()), Match.when("default", value), Match.exhaustive);
3829
+ const printedAs = (code) => canonicalForm(code);
3830
+ const printsSomething = (code) => printedAs(code).length > 0;
3831
+ const samePrintedCode = (left, right) => [printsSomething(left), printedAs(left) === printedAs(right)].every(Boolean);
3832
+ const alreadyPrinted = (code, earlier) => [printsSomething(code), earlier.includes(printedAs(code))].every(Boolean);
3833
+ const relationalSuppression = (facts, policy) => defaultOnly(policy, () => Option.map(Option.filter(Option.some(facts), (candidate) => candidate.relationalSufficient === false), () => MutantSuppressed.make({
3834
+ ruleId: "redundant-relational",
3835
+ detail: `${printedAs(facts.replacementCode)} is not in the sufficient set for ${printedAs(facts.originalCode)}`
3836
+ })));
3837
+ const equivalentSuppression = (facts, policy) => defaultOnly(policy, () => Option.map(Option.filter(Option.some(facts), (candidate) => samePrintedCode(candidate.replacementCode, candidate.originalCode)), () => MutantSuppressed.make({
3838
+ ruleId: "equivalent-to-original",
3839
+ detail: `${printedAs(facts.replacementCode)} is the original code`
3840
+ })));
3841
+ const duplicateSuppression = (facts, earlier, policy) => defaultOnly(policy, () => Option.map(Option.filter(Option.some(facts), (candidate) => alreadyPrinted(candidate.replacementCode, earlier)), () => MutantSuppressed.make({
3842
+ ruleId: "duplicate-at-site",
3843
+ detail: `${printedAs(facts.replacementCode)} is already planted at this site`
3844
+ })));
3845
+ const outcomeOf = (facts, earlier, policy) => Option.getOrElse(Option.orElse(relationalSuppression(facts, policy), () => Option.orElse(equivalentSuppression(facts, policy), () => duplicateSuppression(facts, earlier, policy))), () => MutantKept.make({}));
3846
+ const foldCandidate = (state, facts, policy) => ({
3847
+ outcomes: [...state.outcomes, outcomeOf(facts, state.earlier, policy)],
3848
+ earlier: [...state.earlier, canonicalForm(facts.replacementCode)]
3849
+ });
3850
+ const outcomesFor = (command) => command.candidates.reduce((state, facts) => foldCandidate(state, facts, command.policy), {
3851
+ outcomes: [],
3852
+ earlier: []
3853
+ }).outcomes;
3854
+ const mutantSetPolicy = Workflow.make({
3855
+ command: MutantSetPolicyCommand,
3856
+ decision: MutantSetDecision,
3857
+ error: S.Never,
3858
+ decide: (command) => Result.succeed([...outcomesFor(command)])
3859
+ });
3860
+ //#endregion
3629
3861
  //#region src/plan-mutants.workflow.ts
3630
3862
  const WILDCARD = "all";
3631
3863
  const NEXT_LINE = "next-line";
3864
+ const RULE_SEPARATOR = ": ";
3865
+ const policyWorkflow = mutantSetPolicy;
3866
+ const policyCommand = MutantSetPolicyCommand;
3867
+ const keptOutcome = MutantKept;
3868
+ const ignoreReasonFor = (ruleId, detail) => `${ruleId}${RULE_SEPARATOR}${detail}`;
3632
3869
  const MutantCandidateSchema = S.Struct({
3870
+ id: Mutant.MutantId,
3633
3871
  mutatorName: MutatorNameSchema,
3634
3872
  replacementCode: S.String,
3635
- location: S.optional(Location),
3636
- ignorerReason: S.optional(S.String)
3873
+ location: S.optional(Mutant.Location),
3874
+ ignorerReason: S.optional(S.String),
3875
+ mutantSet: MutantSetFactsSchema
3637
3876
  });
3638
3877
  const PlannedMutantSchema = S.Struct({
3639
- id: MutantId,
3878
+ id: Mutant.MutantId,
3640
3879
  mutatorName: MutatorNameSchema,
3641
3880
  replacementCode: S.String,
3642
- location: Location,
3881
+ location: Mutant.Location,
3643
3882
  ignoreReason: S.optional(S.String)
3644
3883
  });
3645
- const MutantCounterSchema = S.Int.pipe(S.check(S.isBetween({
3646
- minimum: 0,
3647
- maximum: 1e6
3648
- })));
3649
- const MutantCountSchema = S.Int.pipe(S.check(S.isGreaterThanOrEqualTo(0)));
3650
3884
  var PlanMutantsCommand = class extends S.TaggedClass()("PlanMutantsCommand", {
3651
3885
  fileName: S.String,
3652
- firstIndex: MutantCounterSchema,
3653
3886
  offset: ScriptOrigin,
3654
- line: Line,
3887
+ line: Mutant.Line,
3655
3888
  mutatorNames: S.Array(MutatorNameSchema),
3656
3889
  excludedMutations: S.Array(MutatorNameSchema),
3657
3890
  rule: S.Array(LocatedDirectiveSchema),
3658
3891
  directives: S.Array(LocatedDirectiveSchema),
3659
- candidates: S.Array(MutantCandidateSchema)
3892
+ candidates: S.Array(MutantCandidateSchema),
3893
+ mutantSetPolicy: Options.MutantSetPolicy
3660
3894
  }) {
3661
3895
  static [Workflow.InstrumentationBrand] = {};
3662
3896
  };
3663
3897
  const MutantPlanTypeId = Symbol.for("@systemfsoftware/stryker-js-instrumenter/MutantPlan");
3664
3898
  var MutantsPlanned = class extends S.TaggedClass()("MutantsPlanned", {
3665
3899
  mutants: S.Array(PlannedMutantSchema),
3666
- placeable: S.Array(PlannedMutantSchema),
3667
- warnings: S.Array(S.String),
3668
- nextIndex: MutantCountSchema
3900
+ placeableIds: S.Array(Mutant.MutantId),
3901
+ warnings: S.Array(S.String)
3669
3902
  }) {
3670
3903
  [MutantPlanTypeId] = MutantPlanTypeId;
3671
3904
  };
3672
3905
  var MutantsFullyIgnored = class extends S.TaggedClass()("MutantsFullyIgnored", {
3673
3906
  mutants: S.Array(PlannedMutantSchema),
3674
- warnings: S.Array(S.String),
3675
- nextIndex: MutantCountSchema
3907
+ warnings: S.Array(S.String)
3676
3908
  }) {
3677
3909
  [MutantPlanTypeId] = MutantPlanTypeId;
3678
3910
  };
3679
3911
  var MutantWithoutLocation = class extends S.TaggedError()("MutantWithoutLocation", {
3680
3912
  fileName: S.String,
3681
3913
  mutatorName: MutatorNameSchema
3682
- }) {};
3914
+ }) {
3915
+ get message() {
3916
+ return `Mutant without a source location: ${this.mutatorName} in ${this.fileName}`;
3917
+ }
3918
+ };
3683
3919
  const reachedLine = (located, line) => Match.value(located.directive.scope).pipe(Match.when(NEXT_LINE, () => located.governedLine === line), Match.when("block", () => true), Match.exhaustive);
3684
3920
  const namesMutator = (located, lowerMutatorName) => located.directive.mutatorNames.some((name) => [name === WILDCARD, name.toLowerCase() === lowerMutatorName].some(Boolean));
3685
3921
  const lastReachingDirective = (rule, lowerMutatorName, line) => Option.fromNullishOr(rule.findLast((located) => [reachedLine(located, line), namesMutator(located, lowerMutatorName)].every(Boolean)));
3686
- const directiveReason = (rule, mutatorName, line) => Option.flatMap(lastReachingDirective(rule, mutatorName.toLowerCase(), line), (located) => Match.value(located.directive.action).pipe(Match.when("disable", () => Option.some(located.directive.reason)), Match.when("restore", () => Option.none()), Match.exhaustive));
3687
- const exclusionReason = (excludedMutations, mutatorName) => Match.value(excludedMutations.includes(mutatorName)).pipe(Match.when(true, () => Option.some(`Ignored because of excluded mutation "${mutatorName}"`)), Match.when(false, () => Option.none()), Match.exhaustive);
3688
- const ignoreReasonOf = (command, candidate) => Option.getOrUndefined(Option.orElse(Option.orElse(directiveReason(command.rule, candidate.mutatorName, command.line), () => exclusionReason(command.excludedMutations, candidate.mutatorName)), () => Option.fromNullishOr(candidate.ignorerReason)));
3922
+ const directiveReason = (rule, mutatorName, line) => Option.flatMap(lastReachingDirective(rule, mutatorName.toLowerCase(), line), (located) => Match.value(located.directive.action).pipe(Match.when("disable", () => Option.some(ignoreReasonFor("directive", located.directive.reason))), Match.when("restore", () => Option.none()), Match.exhaustive));
3923
+ const exclusionReason = (excludedMutations, mutatorName) => Match.value(excludedMutations.includes(mutatorName)).pipe(Match.when(true, () => Option.some(ignoreReasonFor("excluded-mutator", `Ignored because of excluded mutation "${mutatorName}"`))), Match.when(false, () => Option.none()), Match.exhaustive);
3924
+ const ignorerReason = (candidate) => Option.map(Option.fromNullishOr(candidate.ignorerReason), (reason) => ignoreReasonFor("ignorer", reason));
3925
+ const ignoreReasonOf = (command, candidate, policyReason) => Option.getOrUndefined(Option.orElse(Option.orElse(Option.orElse(directiveReason(command.rule, candidate.mutatorName, command.line), () => exclusionReason(command.excludedMutations, candidate.mutatorName)), () => ignorerReason(candidate)), () => Option.fromNullishOr(policyReason)));
3926
+ const policyOutcomes = (command) => Result.match(policyWorkflow(policyCommand.make({
3927
+ policy: command.mutantSetPolicy,
3928
+ candidates: command.candidates.map((candidate) => candidate.mutantSet)
3929
+ })), {
3930
+ onFailure: () => command.candidates.map(() => keptOutcome.make({})),
3931
+ onSuccess: (outcomes) => outcomes
3932
+ });
3933
+ const policyReasonOf = (outcome) => Match.value(outcome).pipe(Match.withReturnType(), Match.tag("MutantSuppressed", (suppressed) => ignoreReasonFor(suppressed.ruleId, suppressed.detail)), Match.tag("MutantKept", () => void 0), Match.exhaustive);
3934
+ const policyReasonsAt = (command) => policyOutcomes(command).map(policyReasonOf);
3689
3935
  const unusedDirectives = (directives, mutatorNames) => directives.flatMap((located) => located.directive.mutatorNames.filter((mutatorName) => mutatorName !== WILDCARD).filter((mutatorName) => !mutatorNames.includes(mutatorName.toLowerCase())).map((mutatorName) => ({
3690
3936
  directive: located.directive,
3691
3937
  at: located.at,
@@ -3711,30 +3957,31 @@ const shiftedLocation = (location, offset) => ({
3711
3957
  start: shiftedPosition(location.start, offset),
3712
3958
  end: shiftedPosition(location.end, offset)
3713
3959
  });
3714
- const plannedMutant = (command, candidate, index) => Option.match(Option.fromNullishOr(candidate.location), {
3960
+ const plannedMutant = (command, candidate, policyReason) => Option.match(Option.fromNullishOr(candidate.location), {
3715
3961
  onNone: () => Result.fail(MutantWithoutLocation.make({
3716
3962
  fileName: command.fileName,
3717
3963
  mutatorName: candidate.mutatorName
3718
3964
  })),
3719
3965
  onSome: (location) => Result.succeed({
3720
- id: PlannedMutantSchema.fields.id.make(`${command.firstIndex + index}`),
3966
+ id: candidate.id,
3721
3967
  mutatorName: candidate.mutatorName,
3722
3968
  replacementCode: candidate.replacementCode,
3723
3969
  location: shiftedLocation(location, command.offset),
3724
- ignoreReason: ignoreReasonOf(command, candidate)
3970
+ ignoreReason: ignoreReasonOf(command, candidate, policyReason)
3725
3971
  })
3726
3972
  });
3727
- const plannedMutants = (command) => command.candidates.reduce((accumulated, candidate, index) => Result.flatMap(accumulated, (mutants) => Result.map(plannedMutant(command, candidate, index), (mutant) => [...mutants, mutant])), Result.succeed([]));
3973
+ const plannedMutants = (command) => {
3974
+ const policyReasons = policyReasonsAt(command);
3975
+ return command.candidates.reduce((accumulated, candidate, index) => Result.flatMap(accumulated, (mutants) => Result.map(plannedMutant(command, candidate, policyReasons[index]), (mutant) => [...mutants, mutant])), Result.succeed([]));
3976
+ };
3728
3977
  const withoutReason = (mutant) => mutant.ignoreReason === void 0;
3729
3978
  const planOf = (command, mutants) => Match.value(mutants.some(withoutReason)).pipe(Match.when(true, () => MutantsPlanned.make({
3730
3979
  mutants: [...mutants],
3731
- placeable: mutants.filter(withoutReason),
3732
- warnings: warningsOf(command),
3733
- nextIndex: command.firstIndex + mutants.length
3980
+ placeableIds: mutants.filter(withoutReason).map((mutant) => mutant.id),
3981
+ warnings: warningsOf(command)
3734
3982
  })), Match.when(false, () => MutantsFullyIgnored.make({
3735
3983
  mutants: [...mutants],
3736
- warnings: warningsOf(command),
3737
- nextIndex: command.firstIndex + mutants.length
3984
+ warnings: warningsOf(command)
3738
3985
  })), Match.exhaustive);
3739
3986
  const planMutants = Workflow.make({
3740
3987
  command: PlanMutantsCommand,
@@ -3758,15 +4005,15 @@ const traversalFailure = (cause) => InstrumentError.make({
3758
4005
  message: cause instanceof Error ? cause.message : errorTextOf$1(cause),
3759
4006
  cause
3760
4007
  });
3761
- const DEFAULT_MUTATOR_REGISTRY = {
3762
- defaults: defaultMutators,
3763
- optIn: optInMutators
3764
- };
3765
4008
  const createMutantCollector = () => {
3766
4009
  const mutants = [];
4010
+ const occurrences = /* @__PURE__ */ new Map();
3767
4011
  return {
3768
- get nextIndex() {
3769
- return mutants.length;
4012
+ ordinalOf: (tuple) => {
4013
+ const key = mutantTupleKey(tuple);
4014
+ const ordinal = occurrences.get(key) ?? 0;
4015
+ occurrences.set(key, ordinal + 1);
4016
+ return ordinal;
3770
4017
  },
3771
4018
  append: (added) => {
3772
4019
  mutants.push(...added);
@@ -4069,9 +4316,8 @@ const MUTATION_OFFSET = {
4069
4316
  line: 1,
4070
4317
  columnShift: 0
4071
4318
  };
4072
- const initialFoldState = (firstIndex) => ({
4319
+ const initialFoldState = () => ({
4073
4320
  directiveRule: [],
4074
- nextIndex: firstIndex,
4075
4321
  mutants: [],
4076
4322
  warnings: [],
4077
4323
  claims: [],
@@ -4107,29 +4353,77 @@ const replacementRecord = (mutant, applied) => {
4107
4353
  }
4108
4354
  };
4109
4355
  };
4110
- const plannedWithNodes = (candidates, planned, fileName) => candidates.flatMap((candidate, index) => Option.match(Option.fromNullishOr(planned[index]), {
4356
+ const plannedWithNodes = (candidates, planned, fileName, includes) => planned.flatMap((mutant, index) => Option.match(Option.fromNullishOr(candidates[index]), {
4111
4357
  onNone: () => [],
4112
- onSome: (mutant) => [createMutant(mutant, fileName, candidate.node, candidate.replacement)]
4358
+ onSome: (candidate) => includes(mutant) ? [createMutant(mutant, fileName, candidate.node, candidate.replacement)] : []
4113
4359
  }));
4114
4360
  const ignorersReasonFor = (node, ancestors, ignorers) => ignorers.reduce((reason, ignorer) => Option.orElse(reason, () => Option.fromNullishOr(ignorer.shouldIgnore(node, ancestors))), Option.none());
4361
+ const hasKey = (node, key) => key in node;
4362
+ const readKey = (node, key) => hasKey(node, key) ? node[key] : void 0;
4363
+ const isMemberExpressionValue = (node) => nodeType(node) === "MemberExpression";
4364
+ const isIdentifierValue = (value) => value !== void 0 && readKey(value, "type") === "Identifier";
4365
+ const hasIdentifierParts = (member) => [isIdentifierValue(readKey(member, "property")), isIdentifierValue(readKey(member, "object"))].every(Boolean);
4366
+ const isMemberOfIdentifierParts = (node) => isMemberExpressionValue(node) && hasIdentifierParts(node);
4367
+ function isNamedCallee(node) {
4368
+ return isMemberOfIdentifierParts(node) && readKey(node, "computed") === false;
4369
+ }
4370
+ const aridCalleeOf = (callee) => Match.value(callee).pipe(Match.when(isNamedCallee, (named) => Option.some({
4371
+ object: named.object.name,
4372
+ member: named.property.name
4373
+ })), Match.orElse(() => Option.none()));
4374
+ const aridFrameFor = (child, ancestor) => ancestor.type === "CallExpression" ? Option.some({
4375
+ callee: aridCalleeOf(ancestor.callee),
4376
+ childIsArgument: ancestor.arguments.some((argument) => argument === child)
4377
+ }) : Option.none();
4378
+ const aridFramesOf = (frame) => framesUpward(frame).flatMap((current) => Option.match(Option.fromNullishOr(current.parent), {
4379
+ onNone: () => [],
4380
+ onSome: (parent) => Option.toArray(aridFrameFor(current.node, parent.node))
4381
+ }));
4382
+ const aridStatusReason = (decision) => Match.value(decision).pipe(Match.tag("AridSuppressed", (suppressed) => Option.some(`${suppressed.ruleId}: ${suppressed.detail}`)), Match.tag("AridKept", () => Option.none()), Match.exhaustive);
4383
+ const aridReasonOf = (frame, policy) => Match.value(aridCode(AridCodeCommand.make({
4384
+ policy,
4385
+ frames: [...aridFramesOf(frame)]
4386
+ }))).pipe(Match.when(Result.isSuccess, (decided) => aridStatusReason(decided.success)), Match.orElse(() => Option.none()));
4115
4387
  const mutablesFor = (frame, location, context) => {
4116
4388
  const ancestors = ancestorsOfFrame(frame);
4117
- const mutatorContext = toMutatorContext(ancestors);
4389
+ const mutatorContext = toMutatorContext(ancestors, context.mutantSetPolicy);
4118
4390
  const replacements = context.mutatorEntries.flatMap(([mutatorName, mutate]) => [...mutate(frame.node, mutatorContext)].map((replacement) => ({
4119
4391
  mutatorName,
4120
4392
  replacement
4121
4393
  })));
4122
4394
  const ignorerReason = replacements.length === 0 ? void 0 : Option.getOrUndefined(ignorersReasonFor(frame.node, ancestors, context.ignorers));
4123
- return replacements.map(({ mutatorName, replacement }) => ({
4124
- node: frame.node,
4125
- replacement,
4126
- data: {
4395
+ const aridReason = aridReasonOf(frame, context.mutantSetPolicy);
4396
+ const originalCode = printNode(frame.node);
4397
+ const relationalSite = relationalSiteFacts(frame.node, mutatorContext);
4398
+ return replacements.map(({ mutatorName, replacement }) => {
4399
+ const replacementCode = printNode(replacement);
4400
+ const tuple = {
4401
+ fileName: context.fileName,
4127
4402
  mutatorName,
4128
- replacementCode: printNode(replacement),
4129
- location,
4130
- ignorerReason
4131
- }
4132
- }));
4403
+ originalCode,
4404
+ replacementCode
4405
+ };
4406
+ return {
4407
+ node: frame.node,
4408
+ replacement,
4409
+ aridReason,
4410
+ data: {
4411
+ id: mutantIdOf({
4412
+ ...tuple,
4413
+ ordinal: context.ordinalOf(tuple)
4414
+ }),
4415
+ mutatorName,
4416
+ replacementCode,
4417
+ location,
4418
+ ignorerReason,
4419
+ mutantSet: {
4420
+ originalCode,
4421
+ replacementCode,
4422
+ relationalSufficient: relationalSufficientReplacement(relationalSite, replacement)
4423
+ }
4424
+ }
4425
+ };
4426
+ });
4133
4427
  };
4134
4428
  const mutateRangesOf = (mutateDescription) => Option.filter(Option.some(mutateDescription), isMutateRangeList);
4135
4429
  const isOutsideMutateRanges = (location, mutateDescription) => Option.exists(mutateRangesOf(mutateDescription), (ranges) => ranges.every((range) => !locationOverlaps(range, location)));
@@ -4167,30 +4461,43 @@ const attachPlaceable = (mutants, frame, state, context) => Match.value(mutants.
4167
4461
  })),
4168
4462
  onSome: (target) => applyToClaim(mutants, target, state, context)
4169
4463
  })), Match.exhaustive);
4464
+ const aridReasonAt = (candidates, index) => Option.flatMap(Option.fromNullishOr(candidates[index]), (candidate) => candidate.aridReason);
4465
+ const withAridReason = (mutant, reason) => Option.match(reason, {
4466
+ onNone: () => mutant,
4467
+ onSome: (text) => mutant.ignoreReason === void 0 ? {
4468
+ ...mutant,
4469
+ ignoreReason: text
4470
+ } : mutant
4471
+ });
4472
+ const withAridReasons = (candidates, planned) => planned.map((mutant, index) => withAridReason(mutant, aridReasonAt(candidates, index)));
4170
4473
  const collectPlan = (frame, candidates, plan, state, context) => {
4171
- const collected = plannedWithNodes(candidates, plan.mutants, context.fileName);
4474
+ const planned = withAridReasons(candidates, plan.mutants);
4475
+ const collected = plannedWithNodes(candidates, planned, context.fileName, () => true);
4172
4476
  const nextState = {
4173
4477
  ...state,
4174
4478
  mutants: [...state.mutants, ...collected],
4175
- warnings: [...state.warnings, ...plan.warnings],
4176
- nextIndex: plan.nextIndex
4479
+ warnings: [...state.warnings, ...plan.warnings]
4177
4480
  };
4178
- return Match.value(plan).pipe(Match.tag("MutantsPlanned", (planned) => attachPlaceable(plannedWithNodes(candidates, planned.placeable, context.fileName), frame, {
4179
- ...nextState,
4180
- hasLiveMutants: true
4181
- }, context)), Match.orElse(() => Result.succeed(nextState)));
4481
+ return Match.value(plan).pipe(Match.tag("MutantsPlanned", (plannedPlan) => {
4482
+ const placeable = new Set(plannedPlan.placeableIds);
4483
+ const live = plannedWithNodes(candidates, planned, context.fileName, (mutant) => placeable.has(mutant.id) && mutant.ignoreReason === void 0);
4484
+ return attachPlaceable(live, frame, {
4485
+ ...nextState,
4486
+ hasLiveMutants: nextState.hasLiveMutants || live.length > 0
4487
+ }, context);
4488
+ }), Match.tag("MutantsFullyIgnored", () => Result.succeed(nextState)), Match.exhaustive);
4182
4489
  };
4183
4490
  const planMutantsAt = (frame, candidates, directives, location, state, context) => {
4184
4491
  const plan = planMutants(PlanMutantsCommand.make({
4185
4492
  fileName: context.fileName,
4186
- firstIndex: state.nextIndex,
4187
4493
  offset: context.offset,
4188
4494
  line: location.start.line,
4189
4495
  mutatorNames: [...context.allMutatorNames],
4190
4496
  excludedMutations: [...context.excludedMutations],
4191
4497
  rule: [...state.directiveRule],
4192
4498
  directives: [...directives],
4193
- candidates: candidates.map((candidate) => candidate.data)
4499
+ candidates: candidates.map((candidate) => candidate.data),
4500
+ mutantSetPolicy: context.mutantSetPolicy
4194
4501
  }));
4195
4502
  return Match.value(plan).pipe(Match.when(Result.isFailure, (failed) => Result.fail(failed.failure)), Match.orElse((succeeded) => collectPlan(frame, candidates, succeeded.success, state, context)));
4196
4503
  };
@@ -4253,10 +4560,10 @@ const foldPlacements = (frame, state, context) => Result.flatMap(locationOfNode(
4253
4560
  };
4254
4561
  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);
4255
4562
  });
4256
- const planInstrumentation = (root, firstIndex, context) => Result.map(foldPlacements({
4563
+ const planInstrumentation = (root, context) => Result.map(foldPlacements({
4257
4564
  node: root,
4258
4565
  parent: null
4259
- }, initialFoldState(firstIndex), context), (state) => ({
4566
+ }, initialFoldState(), context), (state) => ({
4260
4567
  mutants: state.mutants,
4261
4568
  placements: state.placements,
4262
4569
  warnings: state.warnings,
@@ -4309,23 +4616,24 @@ const refusalError = (refusal) => Match.value(refusal).pipe(Match.tag("NodeWitho
4309
4616
  message: `Mutant without a source location: ${failed.mutatorName} in ${failed.fileName}`,
4310
4617
  cause: void 0
4311
4618
  })), Match.exhaustive);
4312
- const transformScriptDataFirst = Effect.fn("stryker.instrument.transform.script")(function* ({ root, originFileName, rawContent, offset, comments }, mutantCollector, { options, mutateDescription, basePath }) {
4619
+ const transformScriptDataFirst = Effect.fn(SpanTaxonomy.Spans.instrumentTransformScript.name)(function* ({ root, originFileName, rawContent, offset, comments }, mutantCollector, { options, mutateDescription, basePath }) {
4313
4620
  const lineStarts = lineStartsOf(rawContent);
4314
4621
  attachComments(make(root), comments, lineStarts);
4315
- const selection = selectMutators(DEFAULT_MUTATOR_REGISTRY, options.optInMutations);
4316
4622
  const context = {
4317
4623
  fileName: originFileName,
4318
4624
  lineStarts,
4319
4625
  mutateDescription,
4320
4626
  offset: offset ?? MUTATION_OFFSET,
4321
4627
  basePath,
4322
- mutatorEntries: selection.active,
4323
- allMutatorNames: selection.known.map((name) => name.toLowerCase()),
4628
+ mutatorEntries: options.mutators.active,
4629
+ allMutatorNames: options.mutators.known.map((name) => name.toLowerCase()),
4324
4630
  excludedMutations: options.excludedMutations,
4325
- ignorers: options.ignorers
4631
+ mutantSetPolicy: options.mutantSetPolicy,
4632
+ ignorers: options.ignorers,
4633
+ ordinalOf: mutantCollector.ordinalOf
4326
4634
  };
4327
4635
  const planned = yield* Effect.try({
4328
- try: () => planInstrumentation(root, mutantCollector.nextIndex, context),
4636
+ try: () => planInstrumentation(root, context),
4329
4637
  catch: traversalFailure
4330
4638
  });
4331
4639
  const plan = yield* Match.value(planned).pipe(Match.when(Result.isFailure, (refused) => {
@@ -4338,11 +4646,12 @@ const transformScriptDataFirst = Effect.fn("stryker.instrument.transform.script"
4338
4646
  return plan.warnings;
4339
4647
  });
4340
4648
  const transformScript = dual((args) => args.length >= 3, transformScriptDataFirst);
4341
- function toMutatorContext(ancestors) {
4649
+ function toMutatorContext(ancestors, mutantSetPolicy) {
4342
4650
  return {
4343
4651
  parent: ancestors[0],
4344
4652
  grandParent: ancestors[1],
4345
- ancestors: [...ancestors]
4653
+ ancestors: [...ancestors],
4654
+ mutantSetPolicy
4346
4655
  };
4347
4656
  }
4348
4657
  const toError = (value) => value instanceof Error ? value : new Error("Unexpected error", { cause: value });
@@ -4498,12 +4807,11 @@ const coreFormatRegistry = formatRegistry([
4498
4807
  ]);
4499
4808
  const resolveFileDataFirst = (registry, fileName, formatIdOverride) => resolveFormat(resolutionCommandOf(registry, fileName, formatIdOverride));
4500
4809
  const resolveFile = dual((args) => typeof args[0] !== "string", resolveFileDataFirst);
4501
- const NO_OPT_IN_MUTATIONS = [];
4502
- const optInMutationsOf = (options) => options.optInMutations ?? NO_OPT_IN_MUTATIONS;
4503
4810
  const isIgnorer = (value) => Predicate.isObject(value) && typeof value["shouldIgnore"] === "function";
4504
4811
  const toTransformerOptions = (options) => ({
4505
4812
  excludedMutations: [...options.excludedMutations],
4506
- optInMutations: [...optInMutationsOf(options)],
4813
+ mutators: options.mutators,
4814
+ mutantSetPolicy: options.mutantSetPolicy,
4507
4815
  ignorers: options.ignorers.filter(isIgnorer),
4508
4816
  ...options.noHeader !== void 0 ? { noHeader: options.noHeader } : {}
4509
4817
  });
@@ -4643,7 +4951,7 @@ const frameworkEntryOfDataFirst = (moduleName, framework) => ({
4643
4951
  },
4644
4952
  owner: moduleName,
4645
4953
  ownerVersion: framework.claim.ownerVersion,
4646
- parse: Effect.fn("stryker.instrument.framework_entry.parse")(function* (text, fileName) {
4954
+ parse: Effect.fn(SpanTaxonomy.Spans.instrumentFrameworkEntryParse.name)(function* (text, fileName) {
4647
4955
  const context = frameworkContextOf(yield* loadFrameworkToolkit);
4648
4956
  const lineStarts = lineStartsOf(text);
4649
4957
  const result = yield* runHook(moduleName, "parse", fileName, () => framework.parse(text, context));
@@ -4660,7 +4968,7 @@ const frameworkEntryOfDataFirst = (moduleName, framework) => ({
4660
4968
  }),
4661
4969
  transform: (ast, mutantCollector, context) => Option.match(embeddedOf(ast), {
4662
4970
  onNone: () => Effect.fail(embeddedAstError(ast)),
4663
- onSome: Effect.fn("stryker.instrument.framework_entry.transform")(function* (embedded) {
4971
+ onSome: Effect.fn(SpanTaxonomy.Spans.instrumentFrameworkEntryTransform.name)(function* (embedded) {
4664
4972
  const warnings = yield* Effect.forEach(embedded.scripts, (script) => context.transform(script.ast, mutantCollector, context));
4665
4973
  embedded.document = yield* runHook(moduleName, "transform", embedded.originFileName, () => framework.transform(embedded.document, embedded.context));
4666
4974
  return warnings.flat();
@@ -4699,7 +5007,7 @@ const readDisable = (input) => Effect.sync(() => {
4699
5007
  registry: input.registry
4700
5008
  };
4701
5009
  });
4702
- const disableTypeChecksCell = Sandwich.named("stryker.instrument.disableTypeChecks")((input) => readDisable(input)).decide(resolveFormat).write({
5010
+ const disableTypeChecksCell = Sandwich.named(SpanTaxonomy.Spans.instrumentDisableTypeChecks.name)((input) => readDisable(input)).decide(resolveFormat).write({
4703
5011
  FormatAssigned: (assigned, raw) => spliceForAssigned({
4704
5012
  file: raw.file,
4705
5013
  registry: raw.registry,
@@ -4755,7 +5063,7 @@ const skippedOnlyResult = (raw) => InstrumentResult.make({
4755
5063
  mutants: [],
4756
5064
  skipped: [...raw.skipped]
4757
5065
  });
4758
- const readInstrumentFiles = Effect.fn("stryker.instrument.read_files")(function* (input) {
5066
+ const readInstrumentFiles = Effect.fn(SpanTaxonomy.Spans.instrumentReadFiles.name)(function* (input) {
4759
5067
  const outcomes = yield* Effect.forEach(input.files, (file) => fileOutcome({
4760
5068
  registry: input.registry,
4761
5069
  file
@@ -4794,29 +5102,15 @@ const instrumentFilesCell = Sandwich.named("stryker.instrument.files")((input) =
4794
5102
  //#endregion
4795
5103
  //#region src/Instrument.service.ts
4796
5104
  const toSchemaFile = (file) => ({
4797
- name: CanonicalFileName.make(file.name),
5105
+ name: Mutant.CanonicalFileName.make(file.name),
4798
5106
  content: file.content,
4799
5107
  mutate: file.mutate
4800
5108
  });
4801
- const KNOWN_OPT_IN_MUTATIONS = Object.keys(optInMutators);
4802
- const unknownOptInMutations = (requested) => requested.filter((name) => !KNOWN_OPT_IN_MUTATIONS.includes(name));
4803
- const listNames = (names) => Option.match(Option.fromNullishOr(names.at(0)), {
4804
- onNone: () => "none",
4805
- onSome: () => names.map((name) => `'${name}'`).join(", ")
4806
- });
4807
- const unknownOptInMutationsError = (requested) => Option.map(Option.fromNullishOr(unknownOptInMutations(requested).at(0)), () => InstrumentError.make({
4808
- message: `Unknown opt-in mutations: ${listNames(unknownOptInMutations(requested))}. Known opt-in mutations: ${listNames(KNOWN_OPT_IN_MUTATIONS)}.`,
4809
- cause: void 0
4810
- }));
4811
- const refuseUnknownOptInMutations = (options) => Option.match(unknownOptInMutationsError(optInMutationsOf(options)), {
4812
- onNone: () => Effect.void,
4813
- onSome: (error) => Effect.fail(error)
4814
- });
4815
- const instrumentDataFirst = (files, options, registry = coreFormatRegistry) => refuseUnknownOptInMutations(options).pipe(Effect.andThen(instrumentFilesCell.run({
5109
+ const instrumentDataFirst = (files, options, registry = coreFormatRegistry) => instrumentFilesCell.run({
4816
5110
  files: files.map(toSchemaFile),
4817
5111
  options,
4818
5112
  registry
4819
- })));
5113
+ });
4820
5114
  const instrument = dual((args) => Array.isArray(args[0]), instrumentDataFirst);
4821
5115
  const disableTypeChecksDataFirst = (file, registry = coreFormatRegistry) => disableTypeChecksCell.run({
4822
5116
  file: toSchemaFile(file),
@@ -4833,36 +5127,25 @@ const disableTypeChecks = dual((args) => Predicate.hasProperty(args[0], "content
4833
5127
  //#region src/Instrument/mod.ts
4834
5128
  var Instrument_exports = /* @__PURE__ */ __exportAll({
4835
5129
  InstrumentFileSkip: () => InstrumentFileSkip,
5130
+ InstrumenterContext: () => InstrumenterContext,
4836
5131
  disableTypeChecks: () => disableTypeChecks,
4837
5132
  instrument: () => instrument
4838
5133
  });
4839
5134
  //#endregion
4840
- //#region src/Mutant/mod.ts
4841
- var Mutant_exports = /* @__PURE__ */ __exportAll({
4842
- ActionableStatusSchema: () => ActionableStatusSchema,
4843
- CanonicalFileName: () => CanonicalFileName,
4844
- Column: () => Column,
4845
- EphemeralStatusSchema: () => EphemeralStatusSchema,
4846
- HitCount: () => HitCount,
4847
- InstrumenterContext: () => InstrumenterContext,
4848
- Line: () => Line,
4849
- Location: () => Location,
4850
- Mutant: () => Mutant,
4851
- MutantActivationSchema: () => MutantActivationSchema,
4852
- MutantCoverageSchema: () => MutantCoverageSchema,
4853
- MutantFromUnknown: () => MutantFromUnknown,
4854
- MutantId: () => MutantId,
4855
- MutantRunOptionsSchema: () => MutantRunOptionsSchema,
4856
- MutantStatusSchema: () => MutantStatusSchema,
4857
- MutatorName: () => MutatorName,
5135
+ //#region src/Mutator/mod.ts
5136
+ var Mutator_exports = /* @__PURE__ */ __exportAll({
5137
+ defaultMutators: () => defaultMutators,
5138
+ optInMutators: () => optInMutators,
5139
+ registryOf: () => registryOf,
5140
+ selectMutators: () => selectMutators,
5141
+ stockRegistry: () => stockRegistry
5142
+ });
5143
+ //#endregion
5144
+ //#region src/Source/mod.ts
5145
+ var Source_exports = /* @__PURE__ */ __exportAll({
4858
5146
  Offset: () => Offset,
4859
- OpenEndLocation: () => OpenEndLocation,
4860
- Position: () => Position,
4861
- RememberedStatusSchema: () => RememberedStatusSchema,
4862
- RunOptionsFields: () => RunOptionsFields,
4863
5147
  ScriptOrigin: () => ScriptOrigin,
4864
- Span: () => Span,
4865
- SurvivorStatusSchema: () => SurvivorStatusSchema
5148
+ Span: () => Span
4866
5149
  });
4867
5150
  //#endregion
4868
- export { ErrorText_exports as ErrorText, Format_exports as Format, Instrument_exports as Instrument, Mutant_exports as Mutant, lineStartsOf, locationOf, offsetAt, originAt, positionAt };
5151
+ export { ErrorText_exports as ErrorText, Format_exports as Format, Instrument_exports as Instrument, Mutator_exports as Mutator, Source_exports as Source, lineStartsOf, locationOf, offsetAt, originAt, positionAt };