@systemfsoftware/stryker-js-instrumenter 12.1.0 → 15.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
@@ -36,8 +36,8 @@ function readFieldOf(record, key) {
36
36
  return record[key];
37
37
  }
38
38
  const fieldOf = (value, key) => Option.getOrUndefined(Option.filter(Option.some(value), (candidate) => Predicate.hasProperty(key)(candidate)).pipe(Option.map((record) => readFieldOf(record, key))));
39
- const hasStringCode = (error) => Match.value(fieldOf(error, "code")).pipe(Match.when(Match.string, () => true), Match.orElse(() => false));
40
- const isErrnoException = (error) => Match.value(error).pipe(Match.when(Match.instanceOf(Error), hasStringCode), Match.orElse(() => false));
39
+ const isStringField = (error, key) => Match.value(fieldOf(error, key)).pipe(Match.when(Match.string, () => true), Match.orElse(() => false));
40
+ const isErrnoException = (error) => Match.value(error).pipe(Match.when(Match.instanceOf(Error), (candidate) => isStringField(candidate, "code") && isStringField(candidate, "syscall")), Match.orElse(() => false));
41
41
  const isEmptyNumber = (value) => Match.value(value).pipe(Match.when(0, () => true), Match.orElse(Number.isNaN));
42
42
  const isEmptyError = (error) => Match.value(error).pipe(Match.when(Match.undefined, () => true), Match.when(Match.null, () => true), Match.when(Match.string, (text) => text.length === 0), Match.when(Match.number, isEmptyNumber), Match.when(Match.boolean, (value) => !value), Match.orElse(() => false));
43
43
  const formatErrnoException = (error) => Match.value(error.stack).pipe(Match.when(hasText, (stack) => `${error.name}: ${error.code} (${error.syscall}) ${stack}`), Match.orElse(() => `${error.name}: ${error.code} (${error.syscall})`));
@@ -211,6 +211,180 @@ var MutantsUnplaced = class extends S.TaggedError()("MutantsUnplaced", {
211
211
  };
212
212
  var PlacementRefused = class extends S.TaggedError()("PlacementRefused", { message: S.String }) {};
213
213
  //#endregion
214
+ //#region src/assess-probe-eligibility.workflow.ts
215
+ const OperandKind = S.Literals(["literal", "other"]);
216
+ const BinaryOperator = S.Literals([
217
+ "==",
218
+ "!=",
219
+ "===",
220
+ "!==",
221
+ "<",
222
+ "<=",
223
+ ">",
224
+ ">=",
225
+ "<<",
226
+ ">>",
227
+ ">>>",
228
+ "+",
229
+ "-",
230
+ "*",
231
+ "/",
232
+ "%",
233
+ "**",
234
+ "|",
235
+ "^",
236
+ "&",
237
+ "in",
238
+ "instanceof"
239
+ ]);
240
+ const UnaryOperator$1 = S.Literals([
241
+ "-",
242
+ "+",
243
+ "!",
244
+ "~",
245
+ "typeof",
246
+ "void",
247
+ "delete"
248
+ ]);
249
+ const BinarySwap = S.TaggedStruct("BinarySwap", {
250
+ originalOperator: BinaryOperator,
251
+ replacementOperator: BinaryOperator
252
+ });
253
+ const UnarySwap = S.TaggedStruct("UnarySwap", {
254
+ originalOperator: UnaryOperator$1,
255
+ replacementOperator: S.NullOr(UnaryOperator$1)
256
+ });
257
+ const ConstantReplacement = S.TaggedStruct("ConstantReplacement", {
258
+ comparedOperator: S.NullOr(BinaryOperator),
259
+ left: OperandKind,
260
+ right: OperandKind
261
+ });
262
+ const UnrecognizedShape = S.TaggedStruct("UnrecognizedShape", {});
263
+ const ProbeSite = S.Union([
264
+ BinarySwap,
265
+ UnarySwap,
266
+ ConstantReplacement,
267
+ UnrecognizedShape
268
+ ]);
269
+ const ProbedMutatorName = S.Literals([
270
+ "ArithmeticOperator",
271
+ "EqualityOperator",
272
+ "UnaryOperator",
273
+ "ConditionalExpression"
274
+ ]);
275
+ var AssessProbeEligibilityCommand = class extends S.TaggedClass()("AssessProbeEligibilityCommand", {
276
+ mutatorName: S.Union([ProbedMutatorName, S.String]),
277
+ site: ProbeSite
278
+ }) {
279
+ static [Workflow.InstrumentationBrand] = {};
280
+ };
281
+ const ProbeDecisionTypeId = Symbol.for("@systemfsoftware/stryker-js-instrumenter/ProbeDecision");
282
+ var MutantProbed = class extends S.TaggedClass()("MutantProbed", { kind: Mutant.ProbeKind }) {
283
+ [ProbeDecisionTypeId] = ProbeDecisionTypeId;
284
+ };
285
+ var MutantUnprobed = class extends S.TaggedClass()("MutantUnprobed", { reason: Mutant.UnprobedReason }) {
286
+ [ProbeDecisionTypeId] = ProbeDecisionTypeId;
287
+ };
288
+ const PROBED_ARITHMETIC_ORIGINALS = {
289
+ "-": true,
290
+ "*": true,
291
+ "/": true,
292
+ "%": true
293
+ };
294
+ const ARITHMETIC_OPERATORS = {
295
+ "+": true,
296
+ ...PROBED_ARITHMETIC_ORIGINALS
297
+ };
298
+ const RELATIONAL_OPERATORS$1 = {
299
+ "<": true,
300
+ "<=": true,
301
+ ">": true,
302
+ ">=": true
303
+ };
304
+ const EQUALITY_OPERATORS = {
305
+ "==": true,
306
+ "!=": true,
307
+ "===": true,
308
+ "!==": true
309
+ };
310
+ const COMPARISON_OPERATORS = {
311
+ ...RELATIONAL_OPERATORS$1,
312
+ ...EQUALITY_OPERATORS
313
+ };
314
+ const SIGN_OPERATORS = {
315
+ "+": true,
316
+ "-": true
317
+ };
318
+ const isIn = (table, operator) => table[operator] === true;
319
+ const probed = (kind) => MutantProbed.make({ kind });
320
+ const unprobed = (reason) => MutantUnprobed.make({ reason });
321
+ const SHAPE_NOT_RECOGNIZED = () => unprobed("shape-not-recognized");
322
+ const arithmeticSwap = (swap) => Boolean$1.match(isIn(PROBED_ARITHMETIC_ORIGINALS, swap.originalOperator), {
323
+ onFalse: () => unprobed("operator-not-probed"),
324
+ onTrue: () => Boolean$1.match(isIn(ARITHMETIC_OPERATORS, swap.replacementOperator), {
325
+ onFalse: SHAPE_NOT_RECOGNIZED,
326
+ onTrue: () => probed("arithmetic")
327
+ })
328
+ });
329
+ const nonRelationalEqualitySwap = (swap) => Boolean$1.match(isIn(EQUALITY_OPERATORS, swap.originalOperator), {
330
+ onFalse: () => unprobed("operator-not-probed"),
331
+ onTrue: () => unprobed("every-reach-infects")
332
+ });
333
+ const equalitySwap = (swap) => Boolean$1.match(isIn(RELATIONAL_OPERATORS$1, swap.originalOperator), {
334
+ onFalse: () => nonRelationalEqualitySwap(swap),
335
+ onTrue: () => Boolean$1.match(isIn(COMPARISON_OPERATORS, swap.replacementOperator), {
336
+ onFalse: SHAPE_NOT_RECOGNIZED,
337
+ onTrue: () => probed("relational")
338
+ })
339
+ });
340
+ const binarySwap = (mutator, swap) => Match.value(mutator).pipe(Match.when("ArithmeticOperator", () => arithmeticSwap(swap)), Match.when("EqualityOperator", () => equalitySwap(swap)), Match.when("UnaryOperator", SHAPE_NOT_RECOGNIZED), Match.when("ConditionalExpression", SHAPE_NOT_RECOGNIZED), Match.exhaustive);
341
+ const FLIPPED_SIGN = {
342
+ "+": "-",
343
+ "-": "+"
344
+ };
345
+ const flipsSign = (swap) => Option.exists(Option.fromNullOr(swap.replacementOperator), (replacement) => replacement === FLIPPED_SIGN[swap.originalOperator]);
346
+ const signSwap = (swap) => Boolean$1.match(isIn(SIGN_OPERATORS, swap.originalOperator), {
347
+ onFalse: () => unprobed("operator-not-probed"),
348
+ onTrue: () => Boolean$1.match(flipsSign(swap), {
349
+ onFalse: SHAPE_NOT_RECOGNIZED,
350
+ onTrue: () => probed("unary")
351
+ })
352
+ });
353
+ const unarySwap = (mutator, swap) => Match.value(mutator).pipe(Match.when("UnaryOperator", () => signSwap(swap)), Match.when("ArithmeticOperator", SHAPE_NOT_RECOGNIZED), Match.when("EqualityOperator", SHAPE_NOT_RECOGNIZED), Match.when("ConditionalExpression", SHAPE_NOT_RECOGNIZED), Match.exhaustive);
354
+ const isComparison = (replacement) => Option.exists(Option.fromNullOr(replacement.comparedOperator), (operator) => isIn(COMPARISON_OPERATORS, operator));
355
+ const isLiteral$1 = (operand) => operand === "literal";
356
+ const comparisonConstant = (replacement) => Boolean$1.match(isComparison(replacement), {
357
+ onFalse: () => unprobed("not-a-comparison"),
358
+ onTrue: () => Boolean$1.match([replacement.left, replacement.right].every(isLiteral$1), {
359
+ onFalse: () => unprobed("operand-not-proven-local"),
360
+ onTrue: () => probed("constant")
361
+ })
362
+ });
363
+ const constantReplacement = (mutator, replacement) => Match.value(mutator).pipe(Match.when("ConditionalExpression", () => comparisonConstant(replacement)), Match.when("EqualityOperator", () => comparisonConstant(replacement)), Match.when("ArithmeticOperator", SHAPE_NOT_RECOGNIZED), Match.when("UnaryOperator", SHAPE_NOT_RECOGNIZED), Match.exhaustive);
364
+ const siteDecision = (mutator, site) => Match.value(site).pipe(Match.tagsExhaustive({
365
+ BinarySwap: (swap) => binarySwap(mutator, swap),
366
+ UnarySwap: (swap) => unarySwap(mutator, swap),
367
+ ConstantReplacement: (replacement) => constantReplacement(mutator, replacement),
368
+ UnrecognizedShape: SHAPE_NOT_RECOGNIZED
369
+ }));
370
+ const assessProbeEligibility = Workflow.make({
371
+ command: AssessProbeEligibilityCommand,
372
+ decision: S.Union([MutantProbed, MutantUnprobed]),
373
+ error: S.Never,
374
+ decide: (command) => Result.succeed(Option.match(Option.liftPredicate(command.mutatorName, S.is(ProbedMutatorName)), {
375
+ onNone: () => unprobed("mutator-not-probed"),
376
+ onSome: (mutator) => siteDecision(mutator, command.site)
377
+ }))
378
+ });
379
+ //#endregion
380
+ //#region src/relational-operator.schema.ts
381
+ const RelationalOperator = S.Literals([
382
+ "<",
383
+ "<=",
384
+ ">",
385
+ ">="
386
+ ]);
387
+ //#endregion
214
388
  //#region src/relational-sufficient-sets.ts
215
389
  /**
216
390
  * The sufficient sets of non-redundant relational-operator mutations.
@@ -228,12 +402,7 @@ var PlacementRefused = class extends S.TaggedError()("PlacementRefused", { messa
228
402
  * sets assume numeric operands without `NaN`; `mutantSetPolicy: 'full'`
229
403
  * restores every variant.
230
404
  */
231
- const RELATIONAL_OPERATORS = [
232
- "<",
233
- "<=",
234
- ">",
235
- ">="
236
- ];
405
+ const RELATIONAL_OPERATORS = RelationalOperator.literals;
237
406
  const isRelationalOperator = (operator) => RELATIONAL_OPERATORS.some((relational) => relational === operator);
238
407
  const SUFFICIENT_RELATIONAL_SETS = {
239
408
  "<": {
@@ -751,106 +920,25 @@ const writeElement = (container, index, replacement) => Option.match(Option.filt
751
920
  }
752
921
  });
753
922
  //#endregion
754
- //#region src/EffectCall.ts
755
- const MODULE_NAMES = [
756
- "Effect",
757
- "Ref",
758
- "Semaphore",
759
- "SynchronizedRef"
760
- ];
923
+ //#region src/effect-imports.ts
761
924
  const EFFECT_SOURCE = "effect";
762
925
  const FUNCTION_SOURCE = "effect/Function";
763
- const DATA_FIRST = "data-first";
764
- const DATA_LAST_PIPE_ARG = "data-last-pipe-arg";
765
- const DATA_LAST_PIPE_METHOD = "data-last-pipe-method";
766
- const DATA_LAST_IMMEDIATE = "data-last-immediate";
767
- const PIPED_REFERENCE = "piped-reference";
768
- const NO_ARGUMENTS = Object.freeze([]);
769
- const ARITIES = Object.freeze({
770
- "Ref.modify": {
771
- dataFirst: [2],
772
- dataLast: 1
773
- },
774
- "Ref.modifySome": {
775
- dataFirst: [2],
776
- dataLast: 1
777
- },
778
- "Ref.update": {
779
- dataFirst: [2],
780
- dataLast: 1
781
- },
782
- "Ref.updateSome": {
783
- dataFirst: [2],
784
- dataLast: 1
785
- },
786
- "Ref.updateAndGet": {
787
- dataFirst: [2],
788
- dataLast: 1
789
- },
790
- "Ref.getAndUpdate": {
791
- dataFirst: [2],
792
- dataLast: 1
793
- },
794
- "SynchronizedRef.modify": {
795
- dataFirst: [2],
796
- dataLast: 1
797
- },
798
- "SynchronizedRef.modifySome": {
799
- dataFirst: [2],
800
- dataLast: 1
801
- },
802
- "SynchronizedRef.update": {
803
- dataFirst: [2],
804
- dataLast: 1
805
- },
806
- "SynchronizedRef.updateSome": {
807
- dataFirst: [2],
808
- dataLast: 1
809
- },
810
- "SynchronizedRef.updateAndGet": {
811
- dataFirst: [2],
812
- dataLast: 1
813
- },
814
- "SynchronizedRef.getAndUpdate": {
815
- dataFirst: [2],
816
- dataLast: 1
817
- },
818
- "Semaphore.withPermits": {
819
- dataFirst: [3],
820
- dataLast: 2
821
- },
822
- "Semaphore.withPermit": {
823
- dataFirst: [2],
824
- dataLast: 1
825
- },
826
- "Effect.ensuring": {
827
- dataFirst: [2],
828
- dataLast: 1
829
- },
830
- "Effect.onExit": {
831
- dataFirst: [2],
832
- dataLast: 1
833
- },
834
- "Effect.onError": {
835
- dataFirst: [2],
836
- dataLast: 1
837
- },
838
- "Effect.onInterrupt": {
839
- dataFirst: [2],
840
- dataLast: 1
841
- },
842
- "Effect.uninterruptible": { dataFirst: [1] },
843
- "Effect.uninterruptibleMask": { dataFirst: [1] },
844
- "Effect.acquireRelease": { dataFirst: [2, 3] },
845
- "Effect.acquireUseRelease": { dataFirst: [3] }
846
- });
847
- const EMPTY_IMPORT_TABLE = Object.freeze({
926
+ const emptyImportTable = (modules) => ({
927
+ modules,
848
928
  moduleBindings: /* @__PURE__ */ new Map(),
849
929
  namespaces: /* @__PURE__ */ new Map(),
850
930
  bareFunctions: /* @__PURE__ */ new Map(),
851
- pipeBindings: /* @__PURE__ */ new Set()
852
- });
853
- const buildImportTable = (program) => Arr.reduce(Arr.flatMap(Arr.filter(program.body, isImportDeclaration$1), importContributions), EMPTY_IMPORT_TABLE, applyContribution);
931
+ pipeBindings: /* @__PURE__ */ new Set(),
932
+ valueLocals: /* @__PURE__ */ new Set()
933
+ });
934
+ const buildImportTableDataFirst = (program, modules) => Arr.reduce(Arr.flatMap(Arr.filter(program.body, isImportDeclaration$1), (declaration) => importContributions(declaration, modules)), {
935
+ ...emptyImportTable(modules),
936
+ valueLocals: valueImportLocals(program)
937
+ }, applyContribution);
938
+ const buildImportTable = dual((args) => args.length >= 2, buildImportTableDataFirst);
939
+ const valueImportLocals = (program) => new Set(Arr.flatMap(Arr.filter(program.body, isImportDeclaration$1), valueSpecifierLocals));
940
+ const valueSpecifierLocals = (declaration) => Option.toArray(onlyWhen(declaration.importKind !== "type", declaration)).flatMap((imported) => imported.specifiers.flatMap(valueSpecifierLocal));
941
+ const valueSpecifierLocal = (specifier) => Match.value(specifier).pipe(Match.when(isNamedImportSpecifier, (named) => Option.toArray(onlyWhen(named.importKind !== "type", named.local.name))), Match.orElse((other) => [other.local.name]));
854
942
  const withEntry = (entries, key, value) => new Map([...entries, [key, value]]);
855
943
  const withMember = (members, value) => /* @__PURE__ */ new Set([...members, value]);
856
944
  const applyContribution = (table, contribution) => Match.value(contribution).pipe(Match.when((candidate) => candidate.kind === "module", (moduleBinding) => ({
@@ -869,13 +957,13 @@ const applyContribution = (table, contribution) => Match.value(contribution).pip
869
957
  ...table,
870
958
  pipeBindings: withMember(table.pipeBindings, pipeBinding.local)
871
959
  })));
872
- const importContributions = (declaration) => Option.toArray(onlyWhen(declaration.importKind !== "type", declaration)).flatMap((imported) => Arr.flatMap(imported.specifiers, (specifier) => Option.toArray(specifierContribution(imported.source.value, specifier))));
873
- const specifierContribution = (source, specifier) => Match.value(specifier).pipe(Match.when(isNamedImportSpecifier, (named) => namedSpecifierContribution(source, named)), Match.when(isNamespaceImportSpecifier, (namespace) => namespaceSpecifierContribution(source, namespace)), Match.orElse(() => Option.none()));
874
- const namedSpecifierContribution = (source, specifier) => onlyWhen(specifier.importKind !== "type", specifier).pipe(Option.flatMap((imported) => namedContribution(source, moduleExportName(imported.imported), imported.local.name)));
875
- const namedContribution = (source, importedName, localName) => Match.value(source).pipe(Match.when((candidate) => candidate === EFFECT_SOURCE, () => effectRootContribution(importedName, localName)), Match.when((candidate) => candidate === FUNCTION_SOURCE, () => pipeContribution(importedName, localName)), Match.orElse(() => Option.map(moduleNameFromSource(source), (module) => bareFunctionContribution(localName, module, importedName))));
876
- const effectRootContribution = (importedName, localName) => Option.orElse(Option.map(moduleNameFromName(importedName), (module) => moduleContribution(localName, module)), () => pipeContribution(importedName, localName));
960
+ const importContributions = (declaration, modules) => Option.toArray(onlyWhen(declaration.importKind !== "type", declaration)).flatMap((imported) => Arr.flatMap(imported.specifiers, (specifier) => Option.toArray(specifierContribution(imported.source.value, specifier, modules))));
961
+ const specifierContribution = (source, specifier, modules) => Match.value(specifier).pipe(Match.when(isNamedImportSpecifier, (named) => namedSpecifierContribution(source, named, modules)), Match.when(isNamespaceImportSpecifier, (namespace) => namespaceSpecifierContribution(source, namespace, modules)), Match.orElse(() => Option.none()));
962
+ const namedSpecifierContribution = (source, specifier, modules) => onlyWhen(specifier.importKind !== "type", specifier).pipe(Option.flatMap((imported) => namedContribution(source, moduleExportName(imported.imported), imported.local.name, modules)));
963
+ const namedContribution = (source, importedName, localName, modules) => Match.value(source).pipe(Match.when((candidate) => candidate === EFFECT_SOURCE, () => effectRootContribution(importedName, localName, modules)), Match.when((candidate) => candidate === FUNCTION_SOURCE, () => pipeContribution(importedName, localName)), Match.orElse(() => Option.map(moduleNameFromSource(source, modules), (module) => bareFunctionContribution(localName, module, importedName))));
964
+ const effectRootContribution = (importedName, localName, modules) => Option.orElse(Option.map(moduleNameFromName(importedName, modules), (module) => moduleContribution(localName, module)), () => pipeContribution(importedName, localName));
877
965
  const pipeContribution = (importedName, localName) => onlyWhen(importedName === "pipe", pipeContributionOf(localName));
878
- const namespaceSpecifierContribution = (source, specifier) => Option.map(namespaceBindingFor(source), (binding) => ({
966
+ const namespaceSpecifierContribution = (source, specifier, modules) => Option.map(namespaceBindingFor(source, modules), (binding) => ({
879
967
  kind: "namespace",
880
968
  local: specifier.local.name,
881
969
  binding
@@ -895,33 +983,14 @@ const pipeContributionOf = (local) => ({
895
983
  kind: "pipe",
896
984
  local
897
985
  });
898
- const namespaceBindingFor = (source) => Match.value(source).pipe(Match.when((candidate) => candidate === EFFECT_SOURCE, () => Option.some({ kind: "root" })), Match.when((candidate) => candidate === FUNCTION_SOURCE, () => Option.some({ kind: "functionModule" })), Match.orElse(() => Option.map(moduleNameFromSource(source), (module) => ({
986
+ const namespaceBindingFor = (source, modules) => Match.value(source).pipe(Match.when((candidate) => candidate === EFFECT_SOURCE, () => Option.some({ kind: "root" })), Match.when((candidate) => candidate === FUNCTION_SOURCE, () => Option.some({ kind: "functionModule" })), Match.orElse(() => Option.map(moduleNameFromSource(source, modules), (module) => ({
899
987
  kind: "module",
900
988
  module
901
989
  }))));
902
- const moduleNameFromName = (name) => Arr.findFirst(MODULE_NAMES, (module) => module === name);
903
- const moduleNameFromSource = (source) => Arr.findFirst(MODULE_NAMES, (module) => source === `${EFFECT_SOURCE}/${module}`);
904
- const resolveEffectCallDataFirst = (node, context) => Option.flatMap(programOf(context), (program) => resolveIn(node, context, buildImportTable(program)));
905
- const resolveEffectCall = dual((args) => args.length >= 2, resolveEffectCallDataFirst);
906
- const programOf = (context) => Arr.findLast(context.ancestors, isProgram);
907
- 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()));
908
- const resolveCall = (call, context, table) => Option.flatMap(resolveCallee(call.callee, context, table), (callee) => Option.flatMap(spreadFreeArguments$1(call.arguments), (args) => Option.flatMap(callForm(callee, args, call, context, table), (form) => Option.some({
909
- module: callee.module,
910
- exportName: callee.exportName,
911
- form,
912
- args,
913
- moduleAccess: moduleAccessFunction(callee, context, table)
914
- }))));
915
- const resolveReference = (reference, context, table) => Option.flatMap(resolveCallee(reference, context, table), (callee) => Option.flatMap(referenceForm(reference, context, table), (form) => Option.some({
916
- module: callee.module,
917
- exportName: callee.exportName,
918
- form,
919
- args: NO_ARGUMENTS,
920
- moduleAccess: moduleAccessFunction(callee, context, table)
921
- })));
922
- const referenceForm = (reference, context, table) => Option.flatMap(parentCall(context), (parent) => Option.map(pipePosition(parent, reference, context, table), () => PIPED_REFERENCE));
923
- const spreadFreeArguments$1 = (args) => Match.value(args.some(isSpreadElement)).pipe(Match.when(true, () => Option.none()), Match.orElse(() => Option.some(args.filter(isSpreadFree))));
924
- const resolveCallee = (callee, context, table) => Match.value(callee).pipe(Match.when(isIdentifier$1, (reference) => resolveBareCallee(reference, context, table)), Match.when(isMemberExpression, (member) => resolveMemberCallee(member, context, table)), Match.orElse(() => Option.none()));
990
+ const moduleNameFromName = (name, modules) => Arr.findFirst(modules, (module) => module === name);
991
+ const moduleNameFromSource = (source, modules) => Arr.findFirst(modules, (module) => source === `${EFFECT_SOURCE}/${module}`);
992
+ const resolveCalleeDataFirst = (callee, context, table) => Match.value(callee).pipe(Match.when(isIdentifier$1, (reference) => resolveBareCallee(reference, context, table)), Match.when(isMemberExpression, (member) => resolveMemberCallee(member, context, table)), Match.orElse(() => Option.none()));
993
+ const resolveCallee = dual((args) => args.length >= 3, resolveCalleeDataFirst);
925
994
  const resolveBareCallee = (reference, context, table) => Option.flatMap(Option.fromNullishOr(table.bareFunctions.get(reference.name)), (binding) => Option.map(unshadowedName(reference.name, context), () => ({
926
995
  module: binding.module,
927
996
  exportName: binding.exportName,
@@ -940,61 +1009,41 @@ const identifierModuleObject = (reference, context, table) => Option.orElse(Opti
940
1009
  module,
941
1010
  access: reference
942
1011
  }))));
943
- const namespacedModuleObject = (member, context, table) => Option.flatMap(staticMemberName(member), (moduleName) => Option.flatMap(namespaceOf(member.object, context, table), (binding) => Match.value(binding).pipe(Match.when(isRootNamespace, () => Option.map(moduleNameFromName(moduleName), (module) => ({
1012
+ const namespacedModuleObject = (member, context, table) => Option.flatMap(staticMemberName(member), (moduleName) => Option.flatMap(namespaceOf(member.object, context, table), (binding) => moduleObjectOfNamespace(binding, moduleName, member, table)));
1013
+ const moduleObjectOfNamespace = (binding, moduleName, member, table) => binding.kind === "root" ? rootModuleObject(moduleName, member, table) : namedModuleObject(binding, moduleName, member);
1014
+ const rootModuleObject = (moduleName, member, table) => Option.map(moduleNameFromName(moduleName, table.modules), (module) => ({
944
1015
  module,
945
1016
  access: member
946
- }))), Match.when(isModuleNamespace, (moduleNamespace) => Option.map(onlyWhen(moduleNamespace.module === moduleName, member), (access) => ({
947
- module: moduleNamespace.module,
1017
+ }));
1018
+ const namedModuleObject = (binding, moduleName, member) => binding.kind === "module" ? Option.map(onlyWhen(binding.module === moduleName, member), (access) => ({
1019
+ module: binding.module,
948
1020
  access
949
- }))), Match.orElse(() => Option.none()))));
950
- const isRootNamespace = (binding) => binding.kind === "root";
951
- const moduleOf = (binding) => Match.value(binding).pipe(Match.when(isModuleNamespace, (moduleNamespace) => Option.some(moduleNamespace.module)), Match.orElse(() => Option.none()));
952
- const isModuleNamespace = (binding) => binding.kind === "module";
1021
+ })) : Option.none();
1022
+ const moduleOf = (binding) => binding.kind === "module" ? Option.some(binding.module) : Option.none();
953
1023
  const namespaceOf = (expression, context, table) => Match.value(expression).pipe(Match.when(isIdentifier$1, (reference) => Option.flatMap(Option.fromNullishOr(table.namespaces.get(reference.name)), (binding) => Option.map(unshadowedName(reference.name, context), () => binding))), Match.orElse(() => Option.none()));
954
- const callForm = (callee, args, call, context, table) => Option.flatMap(arityOf(callee), (arity) => Match.value(args.length).pipe(Match.when((count) => arity.dataFirst.includes(count), () => Option.some(DATA_FIRST)), Match.when((count) => arity.dataLast === count, () => dataLastForm(call, context, table)), Match.orElse(() => Option.none())));
955
- const arityOf = (callee) => Option.fromNullishOr(ARITIES[`${callee.module}.${callee.exportName}`]);
956
- const dataLastForm = (call, context, table) => Option.flatMap(parentCall(context), (parent) => Match.value(parent.callee === call).pipe(Match.when(true, () => Option.some(DATA_LAST_IMMEDIATE)), Match.orElse(() => Option.flatMap(pipePosition(parent, call, context, table), formForPosition))));
957
- const pipePosition = (parent, argument, context, table) => Option.flatMap(argumentIndex(parent.arguments, argument), (index) => Option.orElse(onlyWhen(isPipeMethodCall(parent), {
958
- index,
959
- callee: "method"
960
- }), () => onlyWhen(isEffectPipeCall(parent, context, table), {
961
- index,
962
- callee: "effect"
963
- })));
964
- const formForPosition = (position) => Match.value(position.callee).pipe(Match.when((callee) => callee === "method", () => Option.some(DATA_LAST_PIPE_METHOD)), Match.orElse(() => onlyWhen(position.index >= 1, DATA_LAST_PIPE_ARG)));
965
- const argumentIndex = (args, node) => Option.flatMap(Option.fromNullishOr(args.findIndex((argument) => sameNode(argument, node))), (index) => onlyWhen(index >= 0, index));
966
- const parentCall = (context) => Option.flatMap(Option.fromNullishOr(context.parent), (parent) => Match.value(parent).pipe(Match.when(isCallExpression$1, (call) => Option.some(call)), Match.orElse(() => Option.none())));
967
- const isPipeMethodCall = (parent) => Match.value(parent.callee).pipe(Match.when(isMemberExpression, (member) => Option.exists(staticMemberName(member), (name) => name === "pipe")), Match.orElse(() => false));
968
- const isEffectPipeCall = (parent, context, table) => Match.value(parent.callee).pipe(Match.when(isIdentifier$1, (reference) => Boolean$1.and(isVisible(reference.name, context), table.pipeBindings.has(reference.name))), Match.when(isMemberExpression, (member) => Option.exists(staticMemberName(member), (name) => Boolean$1.and(name === "pipe", isPipeNamespace(member.object, context, table)))), Match.orElse(() => false));
969
- const isPipeNamespace = (expression, context, table) => Match.value(expression).pipe(Match.when(isIdentifier$1, (reference) => Option.match(namespaceOf(expression, context, table), {
970
- onNone: () => false,
971
- onSome: (binding) => Boolean$1.and(isPipeNamespaceBinding(binding), isVisible(reference.name, context))
972
- })), Match.orElse(() => false));
973
- const isPipeNamespaceBinding = (binding) => Match.value(binding.kind).pipe(Match.when((kind) => kind === "root", () => true), Match.when((kind) => kind === "functionModule", () => true), Match.orElse(() => false));
974
- const moduleAccessFunction = (callee, context, table) => (module) => Arr.findFirst([
1024
+ const moduleAccessFunctionDataFirst = (callee, context, table) => (module) => Arr.findFirst([
975
1025
  ownerAccessFor(callee, module),
976
1026
  namedModuleAccess(module, context, table),
977
1027
  rootNamespaceAccess(module, context, table),
978
1028
  moduleNamespaceAccess(module, context, table)
979
1029
  ], Option.isSome).pipe(Option.flatten);
1030
+ const moduleAccessFunction = dual((args) => args.length >= 3, moduleAccessFunctionDataFirst);
980
1031
  const ownerAccessFor = (callee, module) => Option.flatMap(callee.owner, (owner) => onlyWhen(owner.module === module, owner.access));
981
1032
  const namedModuleAccess = (module, context, table) => Option.map(Option.fromNullishOr(table.moduleBindings.entries().find(([local, bound]) => Boolean$1.and(bound === module, isVisible(local, context)))), ([local]) => identifier(local));
982
1033
  const rootNamespaceAccess = (module, context, table) => Option.map(Option.fromNullishOr(table.namespaces.entries().find(([local, binding]) => Boolean$1.and(binding.kind === "root", isVisible(local, context)))), ([local]) => memberExpression(identifier(local), identifier(module), false));
983
1034
  const moduleNamespaceAccess = (module, context, table) => Option.map(Option.fromNullishOr(table.namespaces.entries().find(([local, binding]) => Option.exists(moduleOf(binding), (bound) => bound === module) && isVisible(local, context))), ([local]) => identifier(local));
984
- const isMovableArgument = (node) => nodesOutsideFunctions(node).every((candidate) => YIELD_OR_AWAIT_KINDS[nodeType(candidate) ?? ""] !== true);
985
- const YIELD_OR_AWAIT_KINDS = {
986
- AwaitExpression: true,
987
- YieldExpression: true
988
- };
989
- const isDroppableArgument = (node) => Match.value(node).pipe(Match.when(isIdentifier$1, () => true), Match.when(isLiteral, () => true), Match.when(isThisExpression, () => true), Match.when(isFunctionLike, () => true), Match.when(isMemberExpression, (member) => isCallFreeMemberChain(member)), Match.orElse(() => false));
990
- const isCallFreeMemberChain = (member) => Match.value(member.computed).pipe(Match.when(true, () => false), Match.orElse(() => Match.value(member.object).pipe(Match.when(isIdentifier$1, () => true), Match.when(isThisExpression, () => true), Match.when(isMemberExpression, (inner) => isCallFreeMemberChain(inner)), Match.orElse(() => false))));
991
- const identifiersIn = (node) => allNodesUnder(node).flatMap((candidate) => Option.toArray(identifierText(candidate)));
992
- const freshIdentifierDataFirst = (base, taken) => freeIdentifier(base, taken, 0);
993
- const freshIdentifier = dual((args) => args.length >= 2, freshIdentifierDataFirst);
994
- const freeIdentifier = (base, taken, suffix) => Match.value(taken.has(suffixedName(base, suffix))).pipe(Match.when(false, () => suffixedName(base, suffix)), Match.orElse(() => freeIdentifier(base, taken, suffix + 1)));
995
- const suffixedName = (base, suffix) => Match.value(suffix).pipe(Match.when(0, () => base), Match.orElse((count) => `${base}${count}`));
996
- const isShadowed = (name, context) => context.ancestors.some((ancestor) => scopeDeclares(ancestor, name));
997
- const isVisible = (name, context) => isShadowed(name, context) === false;
1035
+ const isEffectPipeCallDataFirst = (parent, context, table) => Match.value(parent.callee).pipe(Match.when(isIdentifier$1, (reference) => Boolean$1.and(isVisible(reference.name, context), table.pipeBindings.has(reference.name))), Match.when(isMemberExpression, (member) => Option.exists(staticMemberName(member), (name) => Boolean$1.and(name === "pipe", isPipeNamespace(member.object, context, table)))), Match.orElse(() => false));
1036
+ const isEffectPipeCall = dual((args) => args.length >= 3, isEffectPipeCallDataFirst);
1037
+ const isPipeNamespace = (expression, context, table) => Match.value(expression).pipe(Match.when(isIdentifier$1, (reference) => Option.match(namespaceOf(expression, context, table), {
1038
+ onNone: () => false,
1039
+ onSome: (binding) => Boolean$1.and(isPipeNamespaceBinding(binding), isVisible(reference.name, context))
1040
+ })), Match.orElse(() => false));
1041
+ const isPipeNamespaceBinding = (binding) => Match.value(binding.kind).pipe(Match.when((kind) => kind === "root", () => true), Match.when((kind) => kind === "functionModule", () => true), Match.orElse(() => false));
1042
+ const isImportedLocalDataFirst = (name, table) => table.valueLocals.has(name);
1043
+ const isImportedLocal = dual((args) => args.length >= 2, isImportedLocalDataFirst);
1044
+ const isShadowedDataFirst = (name, context) => context.ancestors.some((ancestor) => scopeDeclares(ancestor, name));
1045
+ const isShadowed = dual((args) => args.length >= 2, isShadowedDataFirst);
1046
+ const isVisible = (name, context) => isShadowedDataFirst(name, context) === false;
998
1047
  const unshadowedName = (name, context) => onlyWhen(isVisible(name, context), name);
999
1048
  const scopeDeclares = (ancestor, name) => Match.value(ancestor).pipe(Match.when(isProgram, (program) => statementsDeclare(program.body, name)), Match.when(isBlockStatement, (block) => statementsDeclare(block.body, name)), Match.when(isStaticBlock, (block) => statementsDeclare(block.body, name)), Match.when(isTSModuleBlock, (block) => statementsDeclare(block.body, name)), Match.when(isSwitchStatement, (statement) => statement.cases.some((caseClause) => statementsDeclare(caseClause.consequent, name))), Match.when(isFunctionLike, (fn) => functionDeclares(fn, name)), Match.when(isCatchClause, (clause) => patternDeclaresOr(clause.param, name)), Match.when(isForStatement, (statement) => variableDeclarationDeclaresOr(statement.init, name)), Match.when(isForInStatement, (statement) => variableDeclarationDeclaresOr(statement.left, name)), Match.when(isForOfStatement, (statement) => variableDeclarationDeclaresOr(statement.left, name)), Match.when(isClassLike, (cls) => declaresIdentifier(cls.id, name)), Match.orElse(() => false));
1000
1049
  const functionDeclares = (fn, name) => holdsAny([
@@ -1044,23 +1093,13 @@ const nodesOutsideFunctions = (root) => {
1044
1093
  } });
1045
1094
  return collected;
1046
1095
  };
1047
- const allNodesUnder = (root) => {
1048
- const collected = [];
1049
- traverse(make(root), { enter(path) {
1050
- collected.push(path.node);
1051
- } });
1052
- return collected;
1053
- };
1054
1096
  const pruneAtFunction = (path) => Option.match(onlyWhen(isFunctionLike(path.node), path), {
1055
1097
  onNone: () => void 0,
1056
1098
  onSome: (atFunction) => atFunction.skip()
1057
1099
  });
1058
- const moduleCallDataFirst = (module, property, args) => callExpression(memberExpression(module, identifier(property), false), args);
1059
- const moduleCall = dual((args) => args.length >= 3, moduleCallDataFirst);
1060
1100
  const onlyWhenDataFirst = (holds, value) => Match.value(holds).pipe(Match.when(true, () => Option.some(value)), Match.orElse(() => Option.none()));
1061
1101
  const onlyWhen = dual((args) => args.length >= 2, onlyWhenDataFirst);
1062
1102
  const holdsAny = (conditions) => conditions.some((condition) => condition);
1063
- const sameNode = (first, second) => first === second;
1064
1103
  const FUNCTION_KINDS$1 = {
1065
1104
  ArrowFunctionExpression: true,
1066
1105
  FunctionDeclaration: true,
@@ -1097,6 +1136,154 @@ const isNamedIdentifier = (candidate) => nodeType(candidate) === "Identifier";
1097
1136
  const isThisExpression = (candidate) => nodeType(candidate) === "ThisExpression";
1098
1137
  const isLiteral = (candidate) => nodeType(candidate) === "Literal";
1099
1138
  const isStringLiteral$1 = (candidate) => nodeType(candidate) === "Literal";
1139
+ //#endregion
1140
+ //#region src/EffectCall.ts
1141
+ const MODULE_NAMES = [
1142
+ "Effect",
1143
+ "Ref",
1144
+ "Semaphore",
1145
+ "SynchronizedRef"
1146
+ ];
1147
+ const DATA_FIRST = "data-first";
1148
+ const DATA_LAST_PIPE_ARG = "data-last-pipe-arg";
1149
+ const DATA_LAST_PIPE_METHOD = "data-last-pipe-method";
1150
+ const DATA_LAST_IMMEDIATE = "data-last-immediate";
1151
+ const PIPED_REFERENCE = "piped-reference";
1152
+ const NO_ARGUMENTS = Object.freeze([]);
1153
+ const ARITIES = Object.freeze({
1154
+ "Ref.modify": {
1155
+ dataFirst: [2],
1156
+ dataLast: 1
1157
+ },
1158
+ "Ref.modifySome": {
1159
+ dataFirst: [2],
1160
+ dataLast: 1
1161
+ },
1162
+ "Ref.update": {
1163
+ dataFirst: [2],
1164
+ dataLast: 1
1165
+ },
1166
+ "Ref.updateSome": {
1167
+ dataFirst: [2],
1168
+ dataLast: 1
1169
+ },
1170
+ "Ref.updateAndGet": {
1171
+ dataFirst: [2],
1172
+ dataLast: 1
1173
+ },
1174
+ "Ref.getAndUpdate": {
1175
+ dataFirst: [2],
1176
+ dataLast: 1
1177
+ },
1178
+ "SynchronizedRef.modify": {
1179
+ dataFirst: [2],
1180
+ dataLast: 1
1181
+ },
1182
+ "SynchronizedRef.modifySome": {
1183
+ dataFirst: [2],
1184
+ dataLast: 1
1185
+ },
1186
+ "SynchronizedRef.update": {
1187
+ dataFirst: [2],
1188
+ dataLast: 1
1189
+ },
1190
+ "SynchronizedRef.updateSome": {
1191
+ dataFirst: [2],
1192
+ dataLast: 1
1193
+ },
1194
+ "SynchronizedRef.updateAndGet": {
1195
+ dataFirst: [2],
1196
+ dataLast: 1
1197
+ },
1198
+ "SynchronizedRef.getAndUpdate": {
1199
+ dataFirst: [2],
1200
+ dataLast: 1
1201
+ },
1202
+ "Semaphore.withPermits": {
1203
+ dataFirst: [3],
1204
+ dataLast: 2
1205
+ },
1206
+ "Semaphore.withPermit": {
1207
+ dataFirst: [2],
1208
+ dataLast: 1
1209
+ },
1210
+ "Effect.ensuring": {
1211
+ dataFirst: [2],
1212
+ dataLast: 1
1213
+ },
1214
+ "Effect.onExit": {
1215
+ dataFirst: [2],
1216
+ dataLast: 1
1217
+ },
1218
+ "Effect.onError": {
1219
+ dataFirst: [2],
1220
+ dataLast: 1
1221
+ },
1222
+ "Effect.onInterrupt": {
1223
+ dataFirst: [2],
1224
+ dataLast: 1
1225
+ },
1226
+ "Effect.uninterruptible": { dataFirst: [1] },
1227
+ "Effect.uninterruptibleMask": { dataFirst: [1] },
1228
+ "Effect.acquireRelease": { dataFirst: [2, 3] },
1229
+ "Effect.acquireUseRelease": { dataFirst: [3] }
1230
+ });
1231
+ const resolveEffectCallDataFirst = (node, context) => Option.flatMap(programOf(context), (program) => resolveIn(node, context, buildImportTable(program, MODULE_NAMES)));
1232
+ const resolveEffectCall = dual((args) => args.length >= 2, resolveEffectCallDataFirst);
1233
+ const programOf = (context) => Arr.findLast(context.ancestors, isProgram);
1234
+ 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()));
1235
+ const resolveCall = (call, context, table) => Option.flatMap(resolveCallee(call.callee, context, table), (callee) => Option.flatMap(spreadFreeArguments$1(call.arguments), (args) => Option.flatMap(callForm(callee, args, call, context, table), (form) => Option.some({
1236
+ module: callee.module,
1237
+ exportName: callee.exportName,
1238
+ form,
1239
+ args,
1240
+ moduleAccess: moduleAccessFunction(callee, context, table)
1241
+ }))));
1242
+ const resolveReference = (reference, context, table) => Option.flatMap(resolveCallee(reference, context, table), (callee) => Option.flatMap(referenceForm(reference, context, table), (form) => Option.some({
1243
+ module: callee.module,
1244
+ exportName: callee.exportName,
1245
+ form,
1246
+ args: NO_ARGUMENTS,
1247
+ moduleAccess: moduleAccessFunction(callee, context, table)
1248
+ })));
1249
+ const referenceForm = (reference, context, table) => Option.flatMap(parentCall(context), (parent) => Option.map(pipePosition(parent, reference, context, table), () => PIPED_REFERENCE));
1250
+ const spreadFreeArguments$1 = (args) => Match.value(args.some(isSpreadElement)).pipe(Match.when(true, () => Option.none()), Match.orElse(() => Option.some(args.filter(isSpreadFree))));
1251
+ const callForm = (callee, args, call, context, table) => Option.flatMap(arityOf(callee), (arity) => Match.value(args.length).pipe(Match.when((count) => arity.dataFirst.includes(count), () => Option.some(DATA_FIRST)), Match.when((count) => arity.dataLast === count, () => dataLastForm(call, context, table)), Match.orElse(() => Option.none())));
1252
+ const arityOf = (callee) => Option.fromNullishOr(ARITIES[`${callee.module}.${callee.exportName}`]);
1253
+ const dataLastForm = (call, context, table) => Option.flatMap(parentCall(context), (parent) => Match.value(parent.callee === call).pipe(Match.when(true, () => Option.some(DATA_LAST_IMMEDIATE)), Match.orElse(() => Option.flatMap(pipePosition(parent, call, context, table), formForPosition))));
1254
+ const pipePosition = (parent, argument, context, table) => Option.flatMap(argumentIndex(parent.arguments, argument), (index) => Option.orElse(onlyWhen(isPipeMethodCall(parent), {
1255
+ index,
1256
+ callee: "method"
1257
+ }), () => onlyWhen(isEffectPipeCall(parent, context, table), {
1258
+ index,
1259
+ callee: "effect"
1260
+ })));
1261
+ const formForPosition = (position) => Match.value(position.callee).pipe(Match.when((callee) => callee === "method", () => Option.some(DATA_LAST_PIPE_METHOD)), Match.orElse(() => onlyWhen(position.index >= 1, DATA_LAST_PIPE_ARG)));
1262
+ const argumentIndex = (args, node) => Option.flatMap(Option.fromNullishOr(args.findIndex((argument) => sameNode(argument, node))), (index) => onlyWhen(index >= 0, index));
1263
+ const parentCall = (context) => Option.flatMap(Option.fromNullishOr(context.parent), (parent) => Match.value(parent).pipe(Match.when(isCallExpression$1, (call) => Option.some(call)), Match.orElse(() => Option.none())));
1264
+ const isPipeMethodCall = (parent) => Match.value(parent.callee).pipe(Match.when(isMemberExpression, (member) => Option.exists(staticMemberName(member), (name) => name === "pipe")), Match.orElse(() => false));
1265
+ const isMovableArgument = (node) => nodesOutsideFunctions(node).every((candidate) => YIELD_OR_AWAIT_KINDS[nodeType(candidate) ?? ""] !== true);
1266
+ const YIELD_OR_AWAIT_KINDS = {
1267
+ AwaitExpression: true,
1268
+ YieldExpression: true
1269
+ };
1270
+ const isDroppableArgument = (node) => Match.value(node).pipe(Match.when(isIdentifier$1, () => true), Match.when(isLiteral, () => true), Match.when(isThisExpression, () => true), Match.when(isFunctionLike, () => true), Match.when(isMemberExpression, (member) => isCallFreeMemberChain(member)), Match.orElse(() => false));
1271
+ const isCallFreeMemberChain = (member) => Match.value(member.computed).pipe(Match.when(true, () => false), Match.orElse(() => Match.value(member.object).pipe(Match.when(isIdentifier$1, () => true), Match.when(isThisExpression, () => true), Match.when(isMemberExpression, (inner) => isCallFreeMemberChain(inner)), Match.orElse(() => false))));
1272
+ const identifiersIn = (node) => allNodesUnder(node).flatMap((candidate) => Option.toArray(identifierText(candidate)));
1273
+ const freshIdentifierDataFirst = (base, taken) => freeIdentifier(base, taken, 0);
1274
+ const freshIdentifier = dual((args) => args.length >= 2, freshIdentifierDataFirst);
1275
+ const freeIdentifier = (base, taken, suffix) => Match.value(taken.has(suffixedName(base, suffix))).pipe(Match.when(false, () => suffixedName(base, suffix)), Match.orElse(() => freeIdentifier(base, taken, suffix + 1)));
1276
+ const suffixedName = (base, suffix) => Match.value(suffix).pipe(Match.when(0, () => base), Match.orElse((count) => `${base}${count}`));
1277
+ const allNodesUnder = (root) => {
1278
+ const collected = [];
1279
+ traverse(make(root), { enter(path) {
1280
+ collected.push(path.node);
1281
+ } });
1282
+ return collected;
1283
+ };
1284
+ const moduleCallDataFirst = (module, property, args) => callExpression(memberExpression(module, identifier(property), false), args);
1285
+ const moduleCall = dual((args) => args.length >= 3, moduleCallDataFirst);
1286
+ const sameNode = (first, second) => first === second;
1100
1287
  const isSpreadElement = (argument) => nodeType(argument) === "SpreadElement";
1101
1288
  const isSpreadFree = (argument) => nodeType(argument) !== "SpreadElement";
1102
1289
  //#endregion
@@ -1324,10 +1511,55 @@ function createMutantDataFirst(planned, fileName, original, replacement) {
1324
1511
  replacement,
1325
1512
  mutatorName: planned.mutatorName,
1326
1513
  ignoreReason: planned.ignoreReason,
1327
- replacementCode: planned.replacementCode
1514
+ replacementCode: planned.replacementCode,
1515
+ ...planned.subsumption === void 0 ? {} : { subsumption: planned.subsumption }
1328
1516
  };
1329
1517
  }
1330
1518
  const createMutant = dual((args) => args.length >= 4, createMutantDataFirst);
1519
+ const subsumptionFieldOf = (mutant) => Option.match(Option.fromUndefinedOr(mutant.subsumption), {
1520
+ onNone: () => ({}),
1521
+ onSome: (subsumption) => ({ subsumption })
1522
+ });
1523
+ const statusReasonOf = (mutant) => Option.orElse(Option.fromUndefinedOr(mutant.ignoreReason), () => Option.map(Option.fromUndefinedOr(mutant.subsumption), Mutant.subsumedStatusReason));
1524
+ const isPrimitiveLiteral = (node) => node.type === "Literal" && !Predicate.hasProperty(node, "regex");
1525
+ const operandKindOf = (node) => isPrimitiveLiteral(node) ? "literal" : "other";
1526
+ const constantReplacementOf = (original) => isBinaryExpression(original) ? {
1527
+ _tag: "ConstantReplacement",
1528
+ comparedOperator: original.operator,
1529
+ left: operandKindOf(original.left),
1530
+ right: operandKindOf(original.right)
1531
+ } : {
1532
+ _tag: "ConstantReplacement",
1533
+ comparedOperator: null,
1534
+ left: "other",
1535
+ right: "other"
1536
+ };
1537
+ const unarySwapOf = (original, replacement) => ({
1538
+ _tag: "UnarySwap",
1539
+ originalOperator: original.operator,
1540
+ replacementOperator: isPrefixUnaryExpression(replacement) ? replacement.operator : null
1541
+ });
1542
+ const probeSiteOf = (original, replacement) => Match.value({
1543
+ original,
1544
+ replacement
1545
+ }).pipe(Match.when({ replacement: isBooleanLiteral }, () => constantReplacementOf(original)), Match.when({
1546
+ original: isBinaryExpression,
1547
+ replacement: isBinaryExpression
1548
+ }, (pair) => ({
1549
+ _tag: "BinarySwap",
1550
+ originalOperator: pair.original.operator,
1551
+ replacementOperator: pair.replacement.operator
1552
+ })), Match.when({ original: isPrefixUnaryExpression }, (pair) => unarySwapOf(pair.original, pair.replacement)), Match.orElse(() => ({ _tag: "UnrecognizedShape" })));
1553
+ const probeEligibilityOf = (mutant) => Result.match(assessProbeEligibility(AssessProbeEligibilityCommand.make({
1554
+ mutatorName: mutant.mutatorName,
1555
+ site: probeSiteOf(mutant.original, mutant.replacement)
1556
+ })), {
1557
+ onFailure: (never) => never,
1558
+ onSuccess: (decision) => Match.value(decision).pipe(Match.tagsExhaustive({
1559
+ MutantProbed: (decided) => Mutant.Probed.make({ kind: decided.kind }),
1560
+ MutantUnprobed: (decided) => Mutant.Unprobed.make({ reason: decided.reason })
1561
+ }))
1562
+ });
1331
1563
  function toApiMutant(mutant) {
1332
1564
  const baseFields = {
1333
1565
  _tag: "Mutant",
@@ -1335,13 +1567,18 @@ function toApiMutant(mutant) {
1335
1567
  id: mutant.id,
1336
1568
  location: mutant.location,
1337
1569
  mutatorName: mutant.mutatorName,
1338
- replacement: mutant.replacementCode
1570
+ replacement: mutant.replacementCode,
1571
+ probeEligibility: probeEligibilityOf(mutant)
1339
1572
  };
1340
- return S.decodeResult(Mutant.Mutant)(mutant.ignoreReason === void 0 ? baseFields : {
1341
- ...baseFields,
1342
- statusReason: mutant.ignoreReason,
1343
- status: "Ignored"
1344
- });
1573
+ return S.decodeResult(Mutant.Mutant)(Option.match(statusReasonOf(mutant), {
1574
+ onNone: () => baseFields,
1575
+ onSome: (statusReason) => ({
1576
+ ...baseFields,
1577
+ statusReason,
1578
+ status: "Ignored",
1579
+ ...subsumptionFieldOf(mutant)
1580
+ })
1581
+ }));
1345
1582
  }
1346
1583
  function applyMutantDataFirst(mutant, originalTree) {
1347
1584
  return Match.value(originalTree === mutant.original).pipe(Match.when(true, () => Result.succeed(mutant.replacement)), Match.when(false, () => cloneWithReplacement(mutant, originalTree)), Match.exhaustive);
@@ -1847,26 +2084,12 @@ function isConditionPosition(node, context) {
1847
2084
  function binaryOperatorOf(node) {
1848
2085
  return node.type === "BinaryExpression" ? node.operator : void 0;
1849
2086
  }
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) => ({
2087
+ const relationalOperatorOf = (node) => Option.filter(Option.fromNullishOr(binaryOperatorOf(node)), isRelationalOperator);
2088
+ const isRelationalComparison = (node) => Option.isSome(relationalOperatorOf(node));
2089
+ const relationalSiteFacts = (node, context) => Option.getOrUndefined(Option.map(relationalOperatorOf(node), (operator) => ({
1852
2090
  operator,
1853
2091
  inConditionPosition: isConditionPosition(node, context)
1854
2092
  })));
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
- }
1870
2093
  function isBooleanExpression(node) {
1871
2094
  return isOperatorExpression(node) && booleanOperators.includes(node.operator);
1872
2095
  }
@@ -2157,8 +2380,12 @@ function isUpdateExpression(node) {
2157
2380
  * Naming each mutator here costs one line when a mutator is added and makes
2158
2381
  * that line a compile-checked import instead of a runtime effect.
2159
2382
  *
2160
- * `optInMutators` below follows the same hand-written rule, for the same
2161
- * reason: a mutator exists for a run only when a human named it here.
2383
+ * Every stock mutator is default-tier: the stock registry declares no opt-in
2384
+ * entries, so `stockRegistry.optIn` is empty. Opt-in is a tier only a plugin
2385
+ * catalog declares for its own entries; a stock name placed in a run's
2386
+ * `mutator.optInMutations` is refused before instrumentation as a config
2387
+ * failure (`NotOptInTier`), because naming a default-tier entry as opt-in
2388
+ * would silently select nothing.
2162
2389
  */
2163
2390
  const defaultMutators = Object.freeze({
2164
2391
  ArithmeticOperator: arithmeticOperatorMutator,
@@ -2176,23 +2403,17 @@ const defaultMutators = Object.freeze({
2176
2403
  Regex: regexMutator,
2177
2404
  StringLiteral: stringLiteralMutator,
2178
2405
  UnaryOperator: unaryOperatorMutator,
2179
- UpdateOperator: updateOperatorMutator
2180
- });
2181
- const optInMutators = Object.freeze({
2406
+ UpdateOperator: updateOperatorMutator,
2182
2407
  AtomicUpdateSplit: atomicUpdateSplitMutator,
2183
2408
  SynchronizationRemoval: synchronizationRemovalMutator,
2184
2409
  FinalizerEscape: finalizerEscapeMutator
2185
2410
  });
2186
2411
  const stockRegistry = {
2187
2412
  defaults: defaultMutators,
2188
- optIn: optInMutators
2413
+ optIn: {}
2189
2414
  };
2190
2415
  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
- ]);
2416
+ const implementations = new Map([...Object.entries(defaultMutators), ...contributions.flatMap(({ entries }) => entries.map((entry) => [entry.name, entry.implementation]))]);
2196
2417
  const entries = catalogs.flatMap((catalog) => catalog.entries);
2197
2418
  const entriesOfTier = (tier) => entries.filter((entry) => entry.tier === tier).flatMap((entry) => Option.match(Option.fromNullishOr(implementations.get(entry.name)), {
2198
2419
  onNone: () => [],
@@ -2211,9 +2432,12 @@ const registryOf = dual((args) => args.length >= 2, registryOfDataFirst);
2211
2432
  * of the defaults and removes none. Selection walks the registry's declared
2212
2433
  * order, not the order the run listed its names in, so a mutant's identity
2213
2434
  * never depends on how a config happened to spell the list; a name listed
2214
- * twice selects its entry once. An unknown name selects nothing here —
2215
- * `instrument` refuses it before any file is parsed, because a typo that
2216
- * silently enables nothing removes mutants and raises the score.
2435
+ * twice selects its entry once. A name the loaded catalogs do not declare
2436
+ * opt-in selects nothing here; the refusal lives in `decodeMutatorSelection`
2437
+ * (packages/stryker-js/src/decode-mutator-selection.workflow.ts), which runs
2438
+ * before instrumentation and fails the run for an unknown or default-tier
2439
+ * name, because a typo that silently enables nothing removes mutants and
2440
+ * raises the score.
2217
2441
  *
2218
2442
  * Pure: a registry and a run's names in, entries and names out.
2219
2443
  */
@@ -2235,9 +2459,9 @@ var ParseFailed = class extends S.TaggedError("@systemfsoftware/stryker-js-instr
2235
2459
  }
2236
2460
  };
2237
2461
  //#endregion
2238
- //#region src/Parser.service.ts
2462
+ //#region src/Parser.ts
2239
2463
  const oxcModule = Effect.cached(Effect.promise(() => import("oxc-parser")));
2240
- const loadOxc = Effect.flatMap(oxcModule, (load) => load);
2464
+ const loadOxc = Effect.flatten(oxcModule);
2241
2465
  const parseWithOxcDataFirst = Effect.fn(SpanTaxonomy.Spans.instrumentParserParseWithOxc.name)(function* (text, fileName, lang) {
2242
2466
  const result = (yield* loadOxc).parseSync(fileName, text, {
2243
2467
  lang,
@@ -3398,75 +3622,120 @@ const AridRuleId = S.Literals([
3398
3622
  "arid-config-default",
3399
3623
  "arid-memoization"
3400
3624
  ]);
3401
- const AridCalleeSchema = S.Struct({
3402
- object: S.String,
3625
+ const AridEffectModule = S.Literals([
3626
+ "Effect",
3627
+ "Logger",
3628
+ "Metric",
3629
+ "Schedule",
3630
+ "Duration",
3631
+ "Config"
3632
+ ]);
3633
+ const AridGlobalObject = S.Literals(["console"]);
3634
+ const AridEffectExportCallee = S.TaggedStruct("EffectExport", {
3635
+ module: AridEffectModule,
3636
+ exportName: S.String
3637
+ });
3638
+ const AridGlobalCallee = S.TaggedStruct("Global", {
3639
+ name: AridGlobalObject,
3403
3640
  member: S.String
3404
3641
  });
3405
- const AridFrameSchema = S.Struct({
3406
- callee: S.Option(AridCalleeSchema),
3407
- childIsArgument: S.Boolean
3642
+ const AridCalleeSchema = S.Union([AridEffectExportCallee, AridGlobalCallee]);
3643
+ const AridArgumentFrame = S.TaggedStruct("ArgumentFrame", { callee: S.Option(AridCalleeSchema) });
3644
+ const AridThroughFrame = S.TaggedStruct("ThroughFrame", {});
3645
+ const AridFunctionBoundary = S.TaggedStruct("FunctionBoundary", {});
3646
+ const AridFrameSchema = S.Union([
3647
+ AridArgumentFrame,
3648
+ AridThroughFrame,
3649
+ AridFunctionBoundary
3650
+ ]);
3651
+ const isCallFrame = (frame) => Match.value(frame).pipe(Match.tagsExhaustive({
3652
+ ArgumentFrame: () => true,
3653
+ ThroughFrame: () => true,
3654
+ FunctionBoundary: () => false
3655
+ }));
3656
+ const matchCallee = (callee, arms) => Match.value(callee).pipe(Match.tagsExhaustive({
3657
+ EffectExport: arms.effectExport,
3658
+ Global: arms.global
3659
+ }));
3660
+ const readsModule = (callee, module) => matchCallee(callee, {
3661
+ effectExport: (effectExport) => effectExport.module === module,
3662
+ global: () => false
3663
+ });
3664
+ const readsExport = (callee, module, exportName) => matchCallee(callee, {
3665
+ effectExport: (effectExport) => [effectExport.module === module, effectExport.exportName === exportName].every(Boolean),
3666
+ global: () => false
3667
+ });
3668
+ const readsExportPrefixed = (callee, module, prefix) => matchCallee(callee, {
3669
+ effectExport: (effectExport) => [effectExport.module === module, effectExport.exportName.startsWith(prefix)].every(Boolean),
3670
+ global: () => false
3671
+ });
3672
+ const readsGlobal = (callee) => matchCallee(callee, {
3673
+ effectExport: () => false,
3674
+ global: () => true
3408
3675
  });
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
3676
  const ARID_RULES_IN_PRECEDENCE_ORDER = [
3415
3677
  {
3416
3678
  ruleId: "arid-logging",
3417
- matches: (callee) => readsMemberPrefixed(callee, "Effect", "log")
3679
+ matches: (callee) => readsExportPrefixed(callee, "Effect", "log")
3418
3680
  },
3419
3681
  {
3420
3682
  ruleId: "arid-logging",
3421
- matches: (callee) => NAMED_LOGGERS.includes(callee.object)
3683
+ matches: (callee) => readsModule(callee, "Logger")
3684
+ },
3685
+ {
3686
+ ruleId: "arid-logging",
3687
+ matches: readsGlobal
3422
3688
  },
3423
3689
  {
3424
3690
  ruleId: "arid-telemetry",
3425
- matches: (callee) => readsMember(callee, "Effect", "withSpan")
3691
+ matches: (callee) => readsExport(callee, "Effect", "withSpan")
3426
3692
  },
3427
3693
  {
3428
3694
  ruleId: "arid-telemetry",
3429
- matches: (callee) => readsMemberPrefixed(callee, "Effect", "annotate")
3695
+ matches: (callee) => readsExportPrefixed(callee, "Effect", "annotate")
3430
3696
  },
3431
3697
  {
3432
3698
  ruleId: "arid-telemetry",
3433
- matches: (callee) => readsMember(callee, "Effect", "withLogSpan")
3699
+ matches: (callee) => readsExport(callee, "Effect", "withLogSpan")
3434
3700
  },
3435
3701
  {
3436
3702
  ruleId: "arid-telemetry",
3437
- matches: (callee) => readsNamespace(callee, "Metric")
3703
+ matches: (callee) => readsModule(callee, "Metric")
3438
3704
  },
3439
3705
  {
3440
- ruleId: "arid-time",
3441
- matches: (callee) => readsMember(callee, "Effect", "sleep")
3706
+ ruleId: "arid-telemetry",
3707
+ matches: (callee) => readsExport(callee, "Effect", "fn")
3442
3708
  },
3443
3709
  {
3444
3710
  ruleId: "arid-time",
3445
- matches: (callee) => readsNamespace(callee, "Schedule")
3711
+ matches: (callee) => readsExport(callee, "Effect", "sleep")
3446
3712
  },
3447
3713
  {
3448
3714
  ruleId: "arid-time",
3449
- matches: (callee) => readsNamespace(callee, "Duration")
3715
+ matches: (callee) => readsModule(callee, "Schedule")
3450
3716
  },
3451
3717
  {
3452
3718
  ruleId: "arid-time",
3453
- matches: (callee) => readsMember(callee, "Date", "now")
3719
+ matches: (callee) => readsModule(callee, "Duration")
3454
3720
  },
3455
3721
  {
3456
3722
  ruleId: "arid-config-default",
3457
- matches: (callee) => readsMember(callee, "Config", "withDefault")
3723
+ matches: (callee) => readsExport(callee, "Config", "withDefault")
3458
3724
  },
3459
3725
  {
3460
3726
  ruleId: "arid-memoization",
3461
- matches: (callee) => readsMember(callee, "Effect", "cached")
3727
+ matches: (callee) => readsExport(callee, "Effect", "cached")
3462
3728
  },
3463
3729
  {
3464
3730
  ruleId: "arid-memoization",
3465
- matches: (callee) => readsMember(callee, "Effect", "cachedWithTTL")
3731
+ matches: (callee) => readsExport(callee, "Effect", "cachedWithTTL")
3466
3732
  }
3467
3733
  ];
3468
3734
  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}`;
3735
+ const calleeDetail = (callee) => Match.value(callee).pipe(Match.tagsExhaustive({
3736
+ EffectExport: (effectExport) => `${effectExport.module}.${effectExport.exportName}`,
3737
+ Global: (global) => `${global.name}.${global.member}`
3738
+ }));
3470
3739
  var AridCodeCommand = class extends S.TaggedClass()("AridCodeCommand", {
3471
3740
  policy: Options.MutantSetPolicy,
3472
3741
  frames: S.Array(AridFrameSchema)
@@ -3483,11 +3752,15 @@ var AridSuppressed = class extends S.TaggedClass()("AridSuppressed", {
3483
3752
  var AridKept = class extends S.TaggedClass()("AridKept", {}) {
3484
3753
  [AridCodeDecisionTypeId] = AridCodeDecisionTypeId;
3485
3754
  };
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));
3755
+ const matchOfFrame = (frame) => Match.value(frame).pipe(Match.tagsExhaustive({
3756
+ ArgumentFrame: (argument) => Option.flatMap(argument.callee, (callee) => Option.map(ruleFor(callee), (ruleId) => ({
3757
+ ruleId,
3758
+ callee
3759
+ }))),
3760
+ ThroughFrame: () => Option.none(),
3761
+ FunctionBoundary: () => Option.none()
3762
+ }));
3763
+ const firstMatch = (frames) => Option.firstSomeOf(Arr.takeWhile(frames, isCallFrame).map(matchOfFrame));
3491
3764
  const defaultPolicyDecision = (frames) => Match.value(firstMatch(frames)).pipe(Match.when(Option.isSome, (found) => AridSuppressed.make({
3492
3765
  ruleId: found.value.ruleId,
3493
3766
  detail: calleeDetail(found.value.callee)
@@ -3785,14 +4058,9 @@ const placeMutants = Workflow.make({
3785
4058
  //#region src/mutant-set-policy.workflow.ts
3786
4059
  const MutantSetFactsSchema = S.Struct({
3787
4060
  originalCode: S.String,
3788
- replacementCode: S.String,
3789
- relationalSufficient: S.Boolean
4061
+ replacementCode: S.String
3790
4062
  });
3791
- const MutantSetRuleId = S.Literals([
3792
- "redundant-relational",
3793
- "equivalent-to-original",
3794
- "duplicate-at-site"
3795
- ]);
4063
+ const MutantSetRuleId = S.Literals(["equivalent-to-original", "duplicate-at-site"]);
3796
4064
  var MutantSetPolicyCommand = class extends S.TaggedClass()("MutantSetPolicyCommand", {
3797
4065
  policy: Options.MutantSetPolicy,
3798
4066
  candidates: S.Array(MutantSetFactsSchema)
@@ -3830,10 +4098,6 @@ const printedAs = (code) => canonicalForm(code);
3830
4098
  const printsSomething = (code) => printedAs(code).length > 0;
3831
4099
  const samePrintedCode = (left, right) => [printsSomething(left), printedAs(left) === printedAs(right)].every(Boolean);
3832
4100
  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
4101
  const equivalentSuppression = (facts, policy) => defaultOnly(policy, () => Option.map(Option.filter(Option.some(facts), (candidate) => samePrintedCode(candidate.replacementCode, candidate.originalCode)), () => MutantSuppressed.make({
3838
4102
  ruleId: "equivalent-to-original",
3839
4103
  detail: `${printedAs(facts.replacementCode)} is the original code`
@@ -3842,7 +4106,7 @@ const duplicateSuppression = (facts, earlier, policy) => defaultOnly(policy, ()
3842
4106
  ruleId: "duplicate-at-site",
3843
4107
  detail: `${printedAs(facts.replacementCode)} is already planted at this site`
3844
4108
  })));
3845
- const outcomeOf = (facts, earlier, policy) => Option.getOrElse(Option.orElse(relationalSuppression(facts, policy), () => Option.orElse(equivalentSuppression(facts, policy), () => duplicateSuppression(facts, earlier, policy))), () => MutantKept.make({}));
4109
+ const outcomeOf = (facts, earlier, policy) => Option.getOrElse(Option.orElse(equivalentSuppression(facts, policy), () => duplicateSuppression(facts, earlier, policy)), () => MutantKept.make({}));
3846
4110
  const foldCandidate = (state, facts, policy) => ({
3847
4111
  outcomes: [...state.outcomes, outcomeOf(facts, state.earlier, policy)],
3848
4112
  earlier: [...state.earlier, canonicalForm(facts.replacementCode)]
@@ -3858,6 +4122,75 @@ const mutantSetPolicy = Workflow.make({
3858
4122
  decide: (command) => Result.succeed([...outcomesFor(command)])
3859
4123
  });
3860
4124
  //#endregion
4125
+ //#region src/subsume-mutants.workflow.ts
4126
+ const COMPLEMENT_ROWS = {
4127
+ "<": {
4128
+ complement: ">=",
4129
+ dominator: "<="
4130
+ },
4131
+ "<=": {
4132
+ complement: ">",
4133
+ dominator: "<"
4134
+ },
4135
+ ">": {
4136
+ complement: "<=",
4137
+ dominator: ">="
4138
+ },
4139
+ ">=": {
4140
+ complement: "<",
4141
+ dominator: ">"
4142
+ }
4143
+ };
4144
+ var RelationalSite = class extends S.TaggedClass()("RelationalSite", { operator: RelationalOperator }) {};
4145
+ var OtherSite = class extends S.TaggedClass()("OtherSite", {}) {};
4146
+ const SubsumptionSite = S.Union([RelationalSite, OtherSite]);
4147
+ var OrderingOperator = class extends S.TaggedClass()("OrderingOperator", { operator: RelationalOperator }) {};
4148
+ var OtherReplacement = class extends S.TaggedClass()("OtherReplacement", {}) {};
4149
+ const SubsumptionReplacement = S.Union([OrderingOperator, OtherReplacement]);
4150
+ const StaticStatus = S.Literals(["StaticallyKept", "StaticallyIgnored"]);
4151
+ const SubsumptionCandidateSchema = S.Struct({
4152
+ id: Mutant.MutantId,
4153
+ replacement: SubsumptionReplacement,
4154
+ status: StaticStatus
4155
+ });
4156
+ var SubsumeMutantsCommand = class extends S.TaggedClass()("SubsumeMutantsCommand", {
4157
+ policy: Options.MutantSetPolicy,
4158
+ site: SubsumptionSite,
4159
+ candidates: S.Array(SubsumptionCandidateSchema)
4160
+ }) {
4161
+ static [Workflow.InstrumentationBrand] = {};
4162
+ };
4163
+ const SubsumptionDecisionTypeId = Symbol.for("@systemfsoftware/stryker-js-instrumenter/SubsumptionDecision");
4164
+ var Unaffected = class extends S.TaggedClass()("Unaffected", {}) {
4165
+ [SubsumptionDecisionTypeId] = SubsumptionDecisionTypeId;
4166
+ };
4167
+ var Subsumed = class extends S.TaggedClass()("Subsumed", {
4168
+ rule: S.Literal("complement"),
4169
+ dominators: S.NonEmptyArray(Mutant.MutantId)
4170
+ }) {
4171
+ [SubsumptionDecisionTypeId] = SubsumptionDecisionTypeId;
4172
+ };
4173
+ const SubsumptionDecision = S.Union([Unaffected, Subsumed]);
4174
+ const SubsumptionDecisions = S.Array(SubsumptionDecision);
4175
+ const replacesWith = (candidate, operator) => Match.value(candidate.replacement).pipe(Match.tag("OrderingOperator", (replacement) => replacement.operator === operator), Match.tag("OtherReplacement", () => false), Match.exhaustive);
4176
+ const isKeptAs = (candidate, operator) => [candidate.status === "StaticallyKept", replacesWith(candidate, operator)].every(Boolean);
4177
+ const dominatorsOf = (candidates, operator) => Option.liftPredicate(candidates.filter((candidate) => isKeptAs(candidate, COMPLEMENT_ROWS[operator].dominator)).map((candidate) => candidate.id), Arr.isArrayNonEmpty);
4178
+ const decideAt = (candidates, operator) => (candidate) => Option.match(Option.flatMap(Option.liftPredicate(candidate, (kept) => isKeptAs(kept, COMPLEMENT_ROWS[operator].complement)), () => dominatorsOf(candidates, operator)), {
4179
+ onNone: () => Unaffected.make({}),
4180
+ onSome: (dominators) => Subsumed.make({
4181
+ rule: "complement",
4182
+ dominators
4183
+ })
4184
+ });
4185
+ const allUnaffected = (command) => command.candidates.map(() => Unaffected.make({}));
4186
+ const decisionsAtSite = (command) => Match.value(command.site).pipe(Match.tag("RelationalSite", (site) => command.candidates.map(decideAt(command.candidates, site.operator))), Match.tag("OtherSite", () => allUnaffected(command)), Match.exhaustive);
4187
+ const subsumeMutants = Workflow.make({
4188
+ command: SubsumeMutantsCommand,
4189
+ decision: SubsumptionDecisions,
4190
+ error: S.Never,
4191
+ decide: (command) => Result.succeed(Match.value(command.policy).pipe(Match.when("full", () => allUnaffected(command)), Match.when("default", () => decisionsAtSite(command)), Match.exhaustive))
4192
+ });
4193
+ //#endregion
3861
4194
  //#region src/plan-mutants.workflow.ts
3862
4195
  const WILDCARD = "all";
3863
4196
  const NEXT_LINE = "next-line";
@@ -3865,21 +4198,31 @@ const RULE_SEPARATOR = ": ";
3865
4198
  const policyWorkflow = mutantSetPolicy;
3866
4199
  const policyCommand = MutantSetPolicyCommand;
3867
4200
  const keptOutcome = MutantKept;
4201
+ const subsumptionWorkflow = subsumeMutants;
4202
+ const subsumptionCommand = SubsumeMutantsCommand;
4203
+ const unaffectedOutcome = Unaffected;
3868
4204
  const ignoreReasonFor = (ruleId, detail) => `${ruleId}${RULE_SEPARATOR}${detail}`;
4205
+ const IgnorerAnswerSchema = S.Struct({
4206
+ ignorerName: S.String,
4207
+ answer: S.Unknown
4208
+ });
3869
4209
  const MutantCandidateSchema = S.Struct({
3870
4210
  id: Mutant.MutantId,
3871
4211
  mutatorName: MutatorNameSchema,
3872
4212
  replacementCode: S.String,
3873
4213
  location: S.optional(Mutant.Location),
3874
- ignorerReason: S.optional(S.String),
3875
- mutantSet: MutantSetFactsSchema
4214
+ ignorerAnswer: S.optional(IgnorerAnswerSchema),
4215
+ aridReason: S.optional(Mutant.IgnoreStatusReasonText),
4216
+ mutantSet: MutantSetFactsSchema,
4217
+ subsumption: SubsumptionReplacement
3876
4218
  });
3877
4219
  const PlannedMutantSchema = S.Struct({
3878
4220
  id: Mutant.MutantId,
3879
4221
  mutatorName: MutatorNameSchema,
3880
4222
  replacementCode: S.String,
3881
4223
  location: Mutant.Location,
3882
- ignoreReason: S.optional(S.String)
4224
+ ignoreReason: S.optional(S.String),
4225
+ subsumption: S.optional(Mutant.Subsumed)
3883
4226
  });
3884
4227
  var PlanMutantsCommand = class extends S.TaggedClass()("PlanMutantsCommand", {
3885
4228
  fileName: S.String,
@@ -3890,6 +4233,7 @@ var PlanMutantsCommand = class extends S.TaggedClass()("PlanMutantsCommand", {
3890
4233
  rule: S.Array(LocatedDirectiveSchema),
3891
4234
  directives: S.Array(LocatedDirectiveSchema),
3892
4235
  candidates: S.Array(MutantCandidateSchema),
4236
+ site: SubsumptionSite,
3893
4237
  mutantSetPolicy: Options.MutantSetPolicy
3894
4238
  }) {
3895
4239
  static [Workflow.InstrumentationBrand] = {};
@@ -3916,13 +4260,31 @@ var MutantWithoutLocation = class extends S.TaggedError()("MutantWithoutLocation
3916
4260
  return `Mutant without a source location: ${this.mutatorName} in ${this.fileName}`;
3917
4261
  }
3918
4262
  };
4263
+ var IgnorerAnsweredWithoutReason = class extends S.TaggedError()("IgnorerAnsweredWithoutReason", {
4264
+ fileName: S.String,
4265
+ mutantId: Mutant.MutantId,
4266
+ ignorerName: S.String
4267
+ }) {
4268
+ get message() {
4269
+ return `Ignorer "${this.ignorerName}" ignored mutant ${this.mutantId} in ${this.fileName} without a reason. Make its \`shouldIgnore\` return a non-empty reason string, or \`undefined\` to keep the mutant.`;
4270
+ }
4271
+ };
3919
4272
  const reachedLine = (located, line) => Match.value(located.directive.scope).pipe(Match.when(NEXT_LINE, () => located.governedLine === line), Match.when("block", () => true), Match.exhaustive);
3920
4273
  const namesMutator = (located, lowerMutatorName) => located.directive.mutatorNames.some((name) => [name === WILDCARD, name.toLowerCase() === lowerMutatorName].some(Boolean));
3921
4274
  const lastReachingDirective = (rule, lowerMutatorName, line) => Option.fromNullishOr(rule.findLast((located) => [reachedLine(located, line), namesMutator(located, lowerMutatorName)].every(Boolean)));
3922
4275
  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
4276
  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)));
4277
+ const ignorerReasonText = (answered) => Option.filter(S.decodeUnknownOption(S.String)(answered.answer), (reason) => reason.trim().length > 0);
4278
+ const ignorerRefusalOf = (command, candidate) => Option.flatMap(Option.fromNullishOr(candidate.ignorerAnswer), (answered) => Option.match(ignorerReasonText(answered), {
4279
+ onNone: () => Option.some(IgnorerAnsweredWithoutReason.make({
4280
+ fileName: command.fileName,
4281
+ mutantId: candidate.id,
4282
+ ignorerName: answered.ignorerName
4283
+ })),
4284
+ onSome: () => Option.none()
4285
+ }));
4286
+ const ignorerReason = (candidate) => Option.map(Option.flatMap(Option.fromNullishOr(candidate.ignorerAnswer), ignorerReasonText), (reason) => ignoreReasonFor("ignorer", reason));
4287
+ const ignoreReasonOf = (command, candidate, policyReason) => Option.getOrUndefined(Option.orElse(Option.orElse(Option.orElse(Option.orElse(directiveReason(command.rule, candidate.mutatorName, command.line), () => exclusionReason(command.excludedMutations, candidate.mutatorName)), () => ignorerReason(candidate)), () => Option.fromNullishOr(policyReason)), () => Option.fromNullishOr(candidate.aridReason)));
3926
4288
  const policyOutcomes = (command) => Result.match(policyWorkflow(policyCommand.make({
3927
4289
  policy: command.mutantSetPolicy,
3928
4290
  candidates: command.candidates.map((candidate) => candidate.mutantSet)
@@ -3932,6 +4294,38 @@ const policyOutcomes = (command) => Result.match(policyWorkflow(policyCommand.ma
3932
4294
  });
3933
4295
  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
4296
  const policyReasonsAt = (command) => policyOutcomes(command).map(policyReasonOf);
4297
+ const staticReasonsAt = (command) => {
4298
+ const policyReasons = policyReasonsAt(command);
4299
+ return command.candidates.map((candidate, index) => ignoreReasonOf(command, candidate, policyReasons[index]));
4300
+ };
4301
+ const staticStatusOf = (reason) => Option.match(Option.fromNullishOr(reason), {
4302
+ onNone: () => "StaticallyKept",
4303
+ onSome: () => "StaticallyIgnored"
4304
+ });
4305
+ const subsumptionsAt = (command, staticReasons) => Result.match(subsumptionWorkflow(subsumptionCommand.make({
4306
+ policy: command.mutantSetPolicy,
4307
+ site: command.site,
4308
+ candidates: command.candidates.map((candidate, index) => ({
4309
+ id: candidate.id,
4310
+ replacement: candidate.subsumption,
4311
+ status: staticStatusOf(staticReasons[index])
4312
+ }))
4313
+ })), {
4314
+ onFailure: () => command.candidates.map(() => unaffectedOutcome.make({})),
4315
+ onSuccess: (decisions) => decisions
4316
+ });
4317
+ const subsumedOf = (decision) => Match.value(decision).pipe(Match.tag("Subsumed", (subsumed) => Option.some({
4318
+ _tag: "Subsumed",
4319
+ rule: subsumed.rule,
4320
+ dominators: subsumed.dominators
4321
+ })), Match.tag("Unaffected", () => Option.none()), Match.exhaustive);
4322
+ const candidateReasonsAt = (command) => {
4323
+ const staticReasons = staticReasonsAt(command);
4324
+ return Arr.zip(staticReasons, subsumptionsAt(command, staticReasons)).map(([ignoreReason, decision]) => ({
4325
+ ignoreReason,
4326
+ subsumption: subsumedOf(decision)
4327
+ }));
4328
+ };
3935
4329
  const unusedDirectives = (directives, mutatorNames) => directives.flatMap((located) => located.directive.mutatorNames.filter((mutatorName) => mutatorName !== WILDCARD).filter((mutatorName) => !mutatorNames.includes(mutatorName.toLowerCase())).map((mutatorName) => ({
3936
4330
  directive: located.directive,
3937
4331
  at: located.at,
@@ -3957,23 +4351,27 @@ const shiftedLocation = (location, offset) => ({
3957
4351
  start: shiftedPosition(location.start, offset),
3958
4352
  end: shiftedPosition(location.end, offset)
3959
4353
  });
3960
- const plannedMutant = (command, candidate, policyReason) => Option.match(Option.fromNullishOr(candidate.location), {
3961
- onNone: () => Result.fail(MutantWithoutLocation.make({
3962
- fileName: command.fileName,
3963
- mutatorName: candidate.mutatorName
3964
- })),
3965
- onSome: (location) => Result.succeed({
3966
- id: candidate.id,
3967
- mutatorName: candidate.mutatorName,
3968
- replacementCode: candidate.replacementCode,
3969
- location: shiftedLocation(location, command.offset),
3970
- ignoreReason: ignoreReasonOf(command, candidate, policyReason)
4354
+ const plannedMutant = (command, candidate, reasons) => Option.match(ignorerRefusalOf(command, candidate), {
4355
+ onSome: (refusal) => Result.fail(refusal),
4356
+ onNone: () => Option.match(Option.fromNullishOr(candidate.location), {
4357
+ onNone: () => Result.fail(MutantWithoutLocation.make({
4358
+ fileName: command.fileName,
4359
+ mutatorName: candidate.mutatorName
4360
+ })),
4361
+ onSome: (location) => Result.succeed({
4362
+ id: candidate.id,
4363
+ mutatorName: candidate.mutatorName,
4364
+ replacementCode: candidate.replacementCode,
4365
+ location: shiftedLocation(location, command.offset),
4366
+ ignoreReason: reasons.ignoreReason,
4367
+ ...Option.match(reasons.subsumption, {
4368
+ onNone: () => ({}),
4369
+ onSome: (subsumption) => ({ subsumption })
4370
+ })
4371
+ })
3971
4372
  })
3972
4373
  });
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
- };
4374
+ const plannedMutants = (command, reasons) => Arr.zip(command.candidates, reasons).reduce((accumulated, [candidate, reason]) => Result.flatMap(accumulated, (mutants) => Result.map(plannedMutant(command, candidate, reason), (mutant) => [...mutants, mutant])), Result.succeed([]));
3977
4375
  const withoutReason = (mutant) => mutant.ignoreReason === void 0;
3978
4376
  const planOf = (command, mutants) => Match.value(mutants.some(withoutReason)).pipe(Match.when(true, () => MutantsPlanned.make({
3979
4377
  mutants: [...mutants],
@@ -3986,9 +4384,10 @@ const planOf = (command, mutants) => Match.value(mutants.some(withoutReason)).pi
3986
4384
  const planMutants = Workflow.make({
3987
4385
  command: PlanMutantsCommand,
3988
4386
  decision: S.Union([MutantsPlanned, MutantsFullyIgnored]),
3989
- error: MutantWithoutLocation,
4387
+ error: S.Union([MutantWithoutLocation, IgnorerAnsweredWithoutReason]),
3990
4388
  decide: (command) => Result.gen(function* () {
3991
- const mutants = yield* plannedMutants(command);
4389
+ const reasons = candidateReasonsAt(command);
4390
+ const mutants = yield* plannedMutants(command, reasons);
3992
4391
  return planOf(command, mutants);
3993
4392
  })
3994
4393
  });
@@ -4357,7 +4756,10 @@ const plannedWithNodes = (candidates, planned, fileName, includes) => planned.fl
4357
4756
  onNone: () => [],
4358
4757
  onSome: (candidate) => includes(mutant) ? [createMutant(mutant, fileName, candidate.node, candidate.replacement)] : []
4359
4758
  }));
4360
- const ignorersReasonFor = (node, ancestors, ignorers) => ignorers.reduce((reason, ignorer) => Option.orElse(reason, () => Option.fromNullishOr(ignorer.shouldIgnore(node, ancestors))), Option.none());
4759
+ const ignorerAnswerFor = (node, ancestors, ignorers) => ignorers.reduce((answered, ignorer) => Option.orElse(answered, () => Option.map(Option.fromNullishOr(ignorer.shouldIgnore(node, ancestors)), (answer) => ({
4760
+ ignorerName: ignorer.name,
4761
+ answer
4762
+ }))), Option.none());
4361
4763
  const hasKey = (node, key) => key in node;
4362
4764
  const readKey = (node, key) => hasKey(node, key) ? node[key] : void 0;
4363
4765
  const isMemberExpressionValue = (node) => nodeType(node) === "MemberExpression";
@@ -4367,23 +4769,47 @@ const isMemberOfIdentifierParts = (node) => isMemberExpressionValue(node) && has
4367
4769
  function isNamedCallee(node) {
4368
4770
  return isMemberOfIdentifierParts(node) && readKey(node, "computed") === false;
4369
4771
  }
4370
- const aridCalleeOf = (callee) => Match.value(callee).pipe(Match.when(isNamedCallee, (named) => Option.some({
4371
- object: named.object.name,
4772
+ const unshadowedGlobalOf = (name, context, table) => Option.filter(Option.filter(Option.some(name), S.is(AridGlobalObject)), (global) => isShadowed(global, context) === false && isImportedLocal(global, table) === false);
4773
+ const aridGlobalCalleeOf = (callee, context, table) => Match.value(callee).pipe(Match.when(isNamedCallee, (named) => Option.map(unshadowedGlobalOf(named.object.name, context, table), (name) => ({
4774
+ _tag: "Global",
4775
+ name,
4372
4776
  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), {
4777
+ }))), Match.orElse(() => Option.none()));
4778
+ const aridCalleeOf = (call, callFrame, policy, table) => {
4779
+ const context = toMutatorContext([call, ...ancestorsOfFrame(callFrame)], policy);
4780
+ return Option.orElse(Option.map(resolveCallee(call.callee, context, table), (callee) => ({
4781
+ _tag: "EffectExport",
4782
+ module: callee.module,
4783
+ exportName: callee.exportName
4784
+ })), () => aridGlobalCalleeOf(call.callee, context, table));
4785
+ };
4786
+ const aridCallFrame = (current, ancestor, ancestorFrame, policy, table) => ancestor.arguments.some((argument) => argument === current.node) ? {
4787
+ _tag: "ArgumentFrame",
4788
+ callee: aridCalleeOf(ancestor, ancestorFrame, policy, table)
4789
+ } : { _tag: "ThroughFrame" };
4790
+ const aridCallFrameFor = (current, ancestorFrame, policy, table) => {
4791
+ const ancestor = ancestorFrame.node;
4792
+ if (ancestor.type !== "CallExpression") return [];
4793
+ return [aridCallFrame(current, ancestor, ancestorFrame, policy, table)];
4794
+ };
4795
+ const functionBoundaryOf = (current) => isFunctionLike(current.node) ? [{ _tag: "FunctionBoundary" }] : [];
4796
+ const aridFramesOf = (frame, policy, table) => framesUpward(frame).flatMap((current) => [...functionBoundaryOf(current), ...Option.match(Option.fromNullishOr(current.parent), {
4379
4797
  onNone: () => [],
4380
- onSome: (parent) => Option.toArray(aridFrameFor(current.node, parent.node))
4381
- }));
4798
+ onSome: (parent) => aridCallFrameFor(current, parent, policy, table)
4799
+ })]);
4382
4800
  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({
4801
+ const aridReasonOf = (frame, policy, table) => Match.value(aridCode(AridCodeCommand.make({
4384
4802
  policy,
4385
- frames: [...aridFramesOf(frame)]
4803
+ frames: [...aridFramesOf(frame, policy, table)]
4386
4804
  }))).pipe(Match.when(Result.isSuccess, (decided) => aridStatusReason(decided.success)), Match.orElse(() => Option.none()));
4805
+ const subsumptionSiteOf = (node) => Option.match(relationalOperatorOf(node), {
4806
+ onNone: () => OtherSite.make({}),
4807
+ onSome: (operator) => RelationalSite.make({ operator })
4808
+ });
4809
+ const subsumptionReplacementOf = (mutatorName, replacement) => Option.match(Option.filter(relationalOperatorOf(replacement), () => mutatorName === "EqualityOperator"), {
4810
+ onNone: () => OtherReplacement.make({}),
4811
+ onSome: (operator) => OrderingOperator.make({ operator })
4812
+ });
4387
4813
  const mutablesFor = (frame, location, context) => {
4388
4814
  const ancestors = ancestorsOfFrame(frame);
4389
4815
  const mutatorContext = toMutatorContext(ancestors, context.mutantSetPolicy);
@@ -4391,10 +4817,10 @@ const mutablesFor = (frame, location, context) => {
4391
4817
  mutatorName,
4392
4818
  replacement
4393
4819
  })));
4394
- const ignorerReason = replacements.length === 0 ? void 0 : Option.getOrUndefined(ignorersReasonFor(frame.node, ancestors, context.ignorers));
4395
- const aridReason = aridReasonOf(frame, context.mutantSetPolicy);
4820
+ if (replacements.length === 0) return [];
4821
+ const ignorerAnswer = Option.getOrUndefined(ignorerAnswerFor(frame.node, ancestors, context.ignorers));
4822
+ const aridReason = Option.getOrUndefined(aridReasonOf(frame, context.mutantSetPolicy, context.aridImportTable));
4396
4823
  const originalCode = printNode(frame.node);
4397
- const relationalSite = relationalSiteFacts(frame.node, mutatorContext);
4398
4824
  return replacements.map(({ mutatorName, replacement }) => {
4399
4825
  const replacementCode = printNode(replacement);
4400
4826
  const tuple = {
@@ -4406,7 +4832,6 @@ const mutablesFor = (frame, location, context) => {
4406
4832
  return {
4407
4833
  node: frame.node,
4408
4834
  replacement,
4409
- aridReason,
4410
4835
  data: {
4411
4836
  id: mutantIdOf({
4412
4837
  ...tuple,
@@ -4415,12 +4840,13 @@ const mutablesFor = (frame, location, context) => {
4415
4840
  mutatorName,
4416
4841
  replacementCode,
4417
4842
  location,
4418
- ignorerReason,
4843
+ ignorerAnswer,
4844
+ ...aridReason === void 0 ? {} : { aridReason },
4419
4845
  mutantSet: {
4420
4846
  originalCode,
4421
- replacementCode,
4422
- relationalSufficient: relationalSufficientReplacement(relationalSite, replacement)
4423
- }
4847
+ replacementCode
4848
+ },
4849
+ subsumption: subsumptionReplacementOf(mutatorName, replacement)
4424
4850
  }
4425
4851
  };
4426
4852
  });
@@ -4461,18 +4887,8 @@ const attachPlaceable = (mutants, frame, state, context) => Match.value(mutants.
4461
4887
  })),
4462
4888
  onSome: (target) => applyToClaim(mutants, target, state, context)
4463
4889
  })), 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)));
4473
4890
  const collectPlan = (frame, candidates, plan, state, context) => {
4474
- const planned = withAridReasons(candidates, plan.mutants);
4475
- const collected = plannedWithNodes(candidates, planned, context.fileName, () => true);
4891
+ const collected = plannedWithNodes(candidates, plan.mutants, context.fileName, () => true);
4476
4892
  const nextState = {
4477
4893
  ...state,
4478
4894
  mutants: [...state.mutants, ...collected],
@@ -4480,7 +4896,7 @@ const collectPlan = (frame, candidates, plan, state, context) => {
4480
4896
  };
4481
4897
  return Match.value(plan).pipe(Match.tag("MutantsPlanned", (plannedPlan) => {
4482
4898
  const placeable = new Set(plannedPlan.placeableIds);
4483
- const live = plannedWithNodes(candidates, planned, context.fileName, (mutant) => placeable.has(mutant.id) && mutant.ignoreReason === void 0);
4899
+ const live = plannedWithNodes(candidates, plan.mutants, context.fileName, (mutant) => placeable.has(mutant.id));
4484
4900
  return attachPlaceable(live, frame, {
4485
4901
  ...nextState,
4486
4902
  hasLiveMutants: nextState.hasLiveMutants || live.length > 0
@@ -4497,6 +4913,7 @@ const planMutantsAt = (frame, candidates, directives, location, state, context)
4497
4913
  rule: [...state.directiveRule],
4498
4914
  directives: [...directives],
4499
4915
  candidates: candidates.map((candidate) => candidate.data),
4916
+ site: subsumptionSiteOf(frame.node),
4500
4917
  mutantSetPolicy: context.mutantSetPolicy
4501
4918
  }));
4502
4919
  return Match.value(plan).pipe(Match.when(Result.isFailure, (failed) => Result.fail(failed.failure)), Match.orElse((succeeded) => collectPlan(frame, candidates, succeeded.success, state, context)));
@@ -4615,6 +5032,9 @@ const refusalError = (refusal) => Match.value(refusal).pipe(Match.tag("NodeWitho
4615
5032
  })), Match.tag("MutantWithoutLocation", (failed) => InstrumentError.make({
4616
5033
  message: `Mutant without a source location: ${failed.mutatorName} in ${failed.fileName}`,
4617
5034
  cause: void 0
5035
+ })), Match.tag("IgnorerAnsweredWithoutReason", (failed) => InstrumentError.make({
5036
+ message: failed.message,
5037
+ cause: failed
4618
5038
  })), Match.exhaustive);
4619
5039
  const transformScriptDataFirst = Effect.fn(SpanTaxonomy.Spans.instrumentTransformScript.name)(function* ({ root, originFileName, rawContent, offset, comments }, mutantCollector, { options, mutateDescription, basePath }) {
4620
5040
  const lineStarts = lineStartsOf(rawContent);
@@ -4629,6 +5049,7 @@ const transformScriptDataFirst = Effect.fn(SpanTaxonomy.Spans.instrumentTransfor
4629
5049
  allMutatorNames: options.mutators.known.map((name) => name.toLowerCase()),
4630
5050
  excludedMutations: options.excludedMutations,
4631
5051
  mutantSetPolicy: options.mutantSetPolicy,
5052
+ aridImportTable: buildImportTable(root, AridEffectModule.literals),
4632
5053
  ignorers: options.ignorers,
4633
5054
  ordinalOf: mutantCollector.ordinalOf
4634
5055
  };
@@ -4875,7 +5296,7 @@ const spliceForAssigned = (request) => Option.match(request.registry.entryForFor
4875
5296
  onSome: (entry) => spliceFileFor(request.file, entry)
4876
5297
  });
4877
5298
  //#endregion
4878
- //#region src/drivers/oxc-program.ts
5299
+ //#region src/oxc-program.ts
4879
5300
  const EMBEDDED_SCRIPT_FILE = "embedded-script.js";
4880
5301
  const makeScriptParser = (oxc) => (source, scriptFormat) => {
4881
5302
  const result = oxc.parseSync(EMBEDDED_SCRIPT_FILE, source, {
@@ -5044,6 +5465,11 @@ const admitInstrumentFiles = Workflow.make({
5044
5465
  });
5045
5466
  //#endregion
5046
5467
  //#region src/instrument-files.cell.ts
5468
+ const skippedOf = (raw) => raw.skipped.map(({ extension, file, reason }) => InstrumentFileSkip.make({
5469
+ extension,
5470
+ file,
5471
+ reason
5472
+ }));
5047
5473
  const printedFile = (raw, { file, ast }) => Result.map(print(ast, raw.registry.entryForFormat), (content) => ({
5048
5474
  name: file.name,
5049
5475
  mutate: file.mutate,
@@ -5056,12 +5482,12 @@ const printedFiles = (raw) => Effect.forEach(raw.parsed, (parsed) => Effect.from
5056
5482
  const instrumentedResult = (raw) => Effect.map(printedFiles(raw), (files) => InstrumentResult.make({
5057
5483
  files: [...files],
5058
5484
  mutants: [...raw.mutants],
5059
- skipped: [...raw.skipped]
5485
+ skipped: skippedOf(raw)
5060
5486
  }));
5061
5487
  const skippedOnlyResult = (raw) => InstrumentResult.make({
5062
5488
  files: [],
5063
5489
  mutants: [],
5064
- skipped: [...raw.skipped]
5490
+ skipped: skippedOf(raw)
5065
5491
  });
5066
5492
  const readInstrumentFiles = Effect.fn(SpanTaxonomy.Spans.instrumentReadFiles.name)(function* (input) {
5067
5493
  const outcomes = yield* Effect.forEach(input.files, (file) => fileOutcome({
@@ -5128,6 +5554,7 @@ const disableTypeChecks = dual((args) => Predicate.hasProperty(args[0], "content
5128
5554
  var Instrument_exports = /* @__PURE__ */ __exportAll({
5129
5555
  InstrumentFileSkip: () => InstrumentFileSkip,
5130
5556
  InstrumenterContext: () => InstrumenterContext,
5557
+ MutateDescriptionSchema: () => MutateDescriptionSchema,
5131
5558
  disableTypeChecks: () => disableTypeChecks,
5132
5559
  instrument: () => instrument
5133
5560
  });
@@ -5135,7 +5562,6 @@ var Instrument_exports = /* @__PURE__ */ __exportAll({
5135
5562
  //#region src/Mutator/mod.ts
5136
5563
  var Mutator_exports = /* @__PURE__ */ __exportAll({
5137
5564
  defaultMutators: () => defaultMutators,
5138
- optInMutators: () => optInMutators,
5139
5565
  registryOf: () => registryOf,
5140
5566
  selectMutators: () => selectMutators,
5141
5567
  stockRegistry: () => stockRegistry