@systemfsoftware/stryker-js-instrumenter 12.1.1 → 16.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)
@@ -3811,29 +4079,16 @@ var MutantKept = class extends S.TaggedClass()("MutantKept", {}) {
3811
4079
  };
3812
4080
  const MutantSetOutcome = S.Union([MutantSuppressed, MutantKept]);
3813
4081
  const MutantSetDecision = S.Array(MutantSetOutcome);
3814
- const canonicalForm = (text) => stripOuterParentheses(text.trim());
3815
- const stripOuterParentheses = (text) => Match.value(wrapsWhole(text)).pipe(Match.when(true, () => stripOuterParentheses(text.slice(1, -1).trim())), Match.orElse(() => text));
3816
- const parenDepthDelta = (character) => Match.value(character).pipe(Match.when("(", () => 1), Match.when(")", () => -1), Match.orElse(() => 0));
3817
- const depthsAfter = (text) => text.split("").reduce((depths, character) => [...depths, Option.getOrElse(Arr.last(depths), () => 0) + parenDepthDelta(character)], []);
3818
- const wrapsWhole = (text) => {
3819
- const depths = depthsAfter(text);
3820
- return [
3821
- text.startsWith("("),
3822
- text.endsWith(")"),
3823
- depths.length > 0,
3824
- depths.slice(0, -1).every((depth) => depth >= 1),
3825
- Option.getOrElse(Arr.last(depths), () => 0) === 0
3826
- ].every(Boolean);
3827
- };
4082
+ /**
4083
+ * The code a candidate plants, as the instrumenter's printer wrote it from the node the placers plant. Two candidates
4084
+ * plant the same code exactly when these texts are equal, so the policy compares text and reparses nothing.
4085
+ */
4086
+ const canonicalForm = (text) => text.trim();
3828
4087
  const defaultOnly = (policy, value) => Match.value(policy).pipe(Match.when("full", () => Option.none()), Match.when("default", value), Match.exhaustive);
3829
4088
  const printedAs = (code) => canonicalForm(code);
3830
4089
  const printsSomething = (code) => printedAs(code).length > 0;
3831
4090
  const samePrintedCode = (left, right) => [printsSomething(left), printedAs(left) === printedAs(right)].every(Boolean);
3832
4091
  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
4092
  const equivalentSuppression = (facts, policy) => defaultOnly(policy, () => Option.map(Option.filter(Option.some(facts), (candidate) => samePrintedCode(candidate.replacementCode, candidate.originalCode)), () => MutantSuppressed.make({
3838
4093
  ruleId: "equivalent-to-original",
3839
4094
  detail: `${printedAs(facts.replacementCode)} is the original code`
@@ -3842,7 +4097,7 @@ const duplicateSuppression = (facts, earlier, policy) => defaultOnly(policy, ()
3842
4097
  ruleId: "duplicate-at-site",
3843
4098
  detail: `${printedAs(facts.replacementCode)} is already planted at this site`
3844
4099
  })));
3845
- const outcomeOf = (facts, earlier, policy) => Option.getOrElse(Option.orElse(relationalSuppression(facts, policy), () => Option.orElse(equivalentSuppression(facts, policy), () => duplicateSuppression(facts, earlier, policy))), () => MutantKept.make({}));
4100
+ const outcomeOf = (facts, earlier, policy) => Option.getOrElse(Option.orElse(equivalentSuppression(facts, policy), () => duplicateSuppression(facts, earlier, policy)), () => MutantKept.make({}));
3846
4101
  const foldCandidate = (state, facts, policy) => ({
3847
4102
  outcomes: [...state.outcomes, outcomeOf(facts, state.earlier, policy)],
3848
4103
  earlier: [...state.earlier, canonicalForm(facts.replacementCode)]
@@ -3858,6 +4113,75 @@ const mutantSetPolicy = Workflow.make({
3858
4113
  decide: (command) => Result.succeed([...outcomesFor(command)])
3859
4114
  });
3860
4115
  //#endregion
4116
+ //#region src/subsume-mutants.workflow.ts
4117
+ const COMPLEMENT_ROWS = {
4118
+ "<": {
4119
+ complement: ">=",
4120
+ dominator: "<="
4121
+ },
4122
+ "<=": {
4123
+ complement: ">",
4124
+ dominator: "<"
4125
+ },
4126
+ ">": {
4127
+ complement: "<=",
4128
+ dominator: ">="
4129
+ },
4130
+ ">=": {
4131
+ complement: "<",
4132
+ dominator: ">"
4133
+ }
4134
+ };
4135
+ var RelationalSite = class extends S.TaggedClass()("RelationalSite", { operator: RelationalOperator }) {};
4136
+ var OtherSite = class extends S.TaggedClass()("OtherSite", {}) {};
4137
+ const SubsumptionSite = S.Union([RelationalSite, OtherSite]);
4138
+ var OrderingOperator = class extends S.TaggedClass()("OrderingOperator", { operator: RelationalOperator }) {};
4139
+ var OtherReplacement = class extends S.TaggedClass()("OtherReplacement", {}) {};
4140
+ const SubsumptionReplacement = S.Union([OrderingOperator, OtherReplacement]);
4141
+ const StaticStatus = S.Literals(["StaticallyKept", "StaticallyIgnored"]);
4142
+ const SubsumptionCandidateSchema = S.Struct({
4143
+ id: Mutant.MutantId,
4144
+ replacement: SubsumptionReplacement,
4145
+ status: StaticStatus
4146
+ });
4147
+ var SubsumeMutantsCommand = class extends S.TaggedClass()("SubsumeMutantsCommand", {
4148
+ policy: Options.MutantSetPolicy,
4149
+ site: SubsumptionSite,
4150
+ candidates: S.Array(SubsumptionCandidateSchema)
4151
+ }) {
4152
+ static [Workflow.InstrumentationBrand] = {};
4153
+ };
4154
+ const SubsumptionDecisionTypeId = Symbol.for("@systemfsoftware/stryker-js-instrumenter/SubsumptionDecision");
4155
+ var Unaffected = class extends S.TaggedClass()("Unaffected", {}) {
4156
+ [SubsumptionDecisionTypeId] = SubsumptionDecisionTypeId;
4157
+ };
4158
+ var Subsumed = class extends S.TaggedClass()("Subsumed", {
4159
+ rule: S.Literal("complement"),
4160
+ dominators: S.NonEmptyArray(Mutant.MutantId)
4161
+ }) {
4162
+ [SubsumptionDecisionTypeId] = SubsumptionDecisionTypeId;
4163
+ };
4164
+ const SubsumptionDecision = S.Union([Unaffected, Subsumed]);
4165
+ const SubsumptionDecisions = S.Array(SubsumptionDecision);
4166
+ const replacesWith = (candidate, operator) => Match.value(candidate.replacement).pipe(Match.tag("OrderingOperator", (replacement) => replacement.operator === operator), Match.tag("OtherReplacement", () => false), Match.exhaustive);
4167
+ const isKeptAs = (candidate, operator) => [candidate.status === "StaticallyKept", replacesWith(candidate, operator)].every(Boolean);
4168
+ const dominatorsOf = (candidates, operator) => Option.liftPredicate(candidates.filter((candidate) => isKeptAs(candidate, COMPLEMENT_ROWS[operator].dominator)).map((candidate) => candidate.id), Arr.isArrayNonEmpty);
4169
+ const decideAt = (candidates, operator) => (candidate) => Option.match(Option.flatMap(Option.liftPredicate(candidate, (kept) => isKeptAs(kept, COMPLEMENT_ROWS[operator].complement)), () => dominatorsOf(candidates, operator)), {
4170
+ onNone: () => Unaffected.make({}),
4171
+ onSome: (dominators) => Subsumed.make({
4172
+ rule: "complement",
4173
+ dominators
4174
+ })
4175
+ });
4176
+ const allUnaffected = (command) => command.candidates.map(() => Unaffected.make({}));
4177
+ 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);
4178
+ const subsumeMutants = Workflow.make({
4179
+ command: SubsumeMutantsCommand,
4180
+ decision: SubsumptionDecisions,
4181
+ error: S.Never,
4182
+ decide: (command) => Result.succeed(Match.value(command.policy).pipe(Match.when("full", () => allUnaffected(command)), Match.when("default", () => decisionsAtSite(command)), Match.exhaustive))
4183
+ });
4184
+ //#endregion
3861
4185
  //#region src/plan-mutants.workflow.ts
3862
4186
  const WILDCARD = "all";
3863
4187
  const NEXT_LINE = "next-line";
@@ -3865,21 +4189,31 @@ const RULE_SEPARATOR = ": ";
3865
4189
  const policyWorkflow = mutantSetPolicy;
3866
4190
  const policyCommand = MutantSetPolicyCommand;
3867
4191
  const keptOutcome = MutantKept;
4192
+ const subsumptionWorkflow = subsumeMutants;
4193
+ const subsumptionCommand = SubsumeMutantsCommand;
4194
+ const unaffectedOutcome = Unaffected;
3868
4195
  const ignoreReasonFor = (ruleId, detail) => `${ruleId}${RULE_SEPARATOR}${detail}`;
4196
+ const IgnorerAnswerSchema = S.Struct({
4197
+ ignorerName: S.String,
4198
+ answer: S.Unknown
4199
+ });
3869
4200
  const MutantCandidateSchema = S.Struct({
3870
4201
  id: Mutant.MutantId,
3871
4202
  mutatorName: MutatorNameSchema,
3872
4203
  replacementCode: S.String,
3873
4204
  location: S.optional(Mutant.Location),
3874
- ignorerReason: S.optional(S.String),
3875
- mutantSet: MutantSetFactsSchema
4205
+ ignorerAnswer: S.optional(IgnorerAnswerSchema),
4206
+ aridReason: S.optional(Mutant.IgnoreStatusReasonText),
4207
+ mutantSet: MutantSetFactsSchema,
4208
+ subsumption: SubsumptionReplacement
3876
4209
  });
3877
4210
  const PlannedMutantSchema = S.Struct({
3878
4211
  id: Mutant.MutantId,
3879
4212
  mutatorName: MutatorNameSchema,
3880
4213
  replacementCode: S.String,
3881
4214
  location: Mutant.Location,
3882
- ignoreReason: S.optional(S.String)
4215
+ ignoreReason: S.optional(S.String),
4216
+ subsumption: S.optional(Mutant.Subsumed)
3883
4217
  });
3884
4218
  var PlanMutantsCommand = class extends S.TaggedClass()("PlanMutantsCommand", {
3885
4219
  fileName: S.String,
@@ -3890,6 +4224,7 @@ var PlanMutantsCommand = class extends S.TaggedClass()("PlanMutantsCommand", {
3890
4224
  rule: S.Array(LocatedDirectiveSchema),
3891
4225
  directives: S.Array(LocatedDirectiveSchema),
3892
4226
  candidates: S.Array(MutantCandidateSchema),
4227
+ site: SubsumptionSite,
3893
4228
  mutantSetPolicy: Options.MutantSetPolicy
3894
4229
  }) {
3895
4230
  static [Workflow.InstrumentationBrand] = {};
@@ -3916,13 +4251,31 @@ var MutantWithoutLocation = class extends S.TaggedError()("MutantWithoutLocation
3916
4251
  return `Mutant without a source location: ${this.mutatorName} in ${this.fileName}`;
3917
4252
  }
3918
4253
  };
4254
+ var IgnorerAnsweredWithoutReason = class extends S.TaggedError()("IgnorerAnsweredWithoutReason", {
4255
+ fileName: S.String,
4256
+ mutantId: Mutant.MutantId,
4257
+ ignorerName: S.String
4258
+ }) {
4259
+ get message() {
4260
+ 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.`;
4261
+ }
4262
+ };
3919
4263
  const reachedLine = (located, line) => Match.value(located.directive.scope).pipe(Match.when(NEXT_LINE, () => located.governedLine === line), Match.when("block", () => true), Match.exhaustive);
3920
4264
  const namesMutator = (located, lowerMutatorName) => located.directive.mutatorNames.some((name) => [name === WILDCARD, name.toLowerCase() === lowerMutatorName].some(Boolean));
3921
4265
  const lastReachingDirective = (rule, lowerMutatorName, line) => Option.fromNullishOr(rule.findLast((located) => [reachedLine(located, line), namesMutator(located, lowerMutatorName)].every(Boolean)));
3922
4266
  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
4267
  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)));
4268
+ const ignorerReasonText = (answered) => Option.filter(S.decodeUnknownOption(S.String)(answered.answer), (reason) => reason.trim().length > 0);
4269
+ const ignorerRefusalOf = (command, candidate) => Option.flatMap(Option.fromNullishOr(candidate.ignorerAnswer), (answered) => Option.match(ignorerReasonText(answered), {
4270
+ onNone: () => Option.some(IgnorerAnsweredWithoutReason.make({
4271
+ fileName: command.fileName,
4272
+ mutantId: candidate.id,
4273
+ ignorerName: answered.ignorerName
4274
+ })),
4275
+ onSome: () => Option.none()
4276
+ }));
4277
+ const ignorerReason = (candidate) => Option.map(Option.flatMap(Option.fromNullishOr(candidate.ignorerAnswer), ignorerReasonText), (reason) => ignoreReasonFor("ignorer", reason));
4278
+ 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
4279
  const policyOutcomes = (command) => Result.match(policyWorkflow(policyCommand.make({
3927
4280
  policy: command.mutantSetPolicy,
3928
4281
  candidates: command.candidates.map((candidate) => candidate.mutantSet)
@@ -3932,6 +4285,38 @@ const policyOutcomes = (command) => Result.match(policyWorkflow(policyCommand.ma
3932
4285
  });
3933
4286
  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
4287
  const policyReasonsAt = (command) => policyOutcomes(command).map(policyReasonOf);
4288
+ const staticReasonsAt = (command) => {
4289
+ const policyReasons = policyReasonsAt(command);
4290
+ return command.candidates.map((candidate, index) => ignoreReasonOf(command, candidate, policyReasons[index]));
4291
+ };
4292
+ const staticStatusOf = (reason) => Option.match(Option.fromNullishOr(reason), {
4293
+ onNone: () => "StaticallyKept",
4294
+ onSome: () => "StaticallyIgnored"
4295
+ });
4296
+ const subsumptionsAt = (command, staticReasons) => Result.match(subsumptionWorkflow(subsumptionCommand.make({
4297
+ policy: command.mutantSetPolicy,
4298
+ site: command.site,
4299
+ candidates: command.candidates.map((candidate, index) => ({
4300
+ id: candidate.id,
4301
+ replacement: candidate.subsumption,
4302
+ status: staticStatusOf(staticReasons[index])
4303
+ }))
4304
+ })), {
4305
+ onFailure: () => command.candidates.map(() => unaffectedOutcome.make({})),
4306
+ onSuccess: (decisions) => decisions
4307
+ });
4308
+ const subsumedOf = (decision) => Match.value(decision).pipe(Match.tag("Subsumed", (subsumed) => Option.some({
4309
+ _tag: "Subsumed",
4310
+ rule: subsumed.rule,
4311
+ dominators: subsumed.dominators
4312
+ })), Match.tag("Unaffected", () => Option.none()), Match.exhaustive);
4313
+ const candidateReasonsAt = (command) => {
4314
+ const staticReasons = staticReasonsAt(command);
4315
+ return Arr.zip(staticReasons, subsumptionsAt(command, staticReasons)).map(([ignoreReason, decision]) => ({
4316
+ ignoreReason,
4317
+ subsumption: subsumedOf(decision)
4318
+ }));
4319
+ };
3935
4320
  const unusedDirectives = (directives, mutatorNames) => directives.flatMap((located) => located.directive.mutatorNames.filter((mutatorName) => mutatorName !== WILDCARD).filter((mutatorName) => !mutatorNames.includes(mutatorName.toLowerCase())).map((mutatorName) => ({
3936
4321
  directive: located.directive,
3937
4322
  at: located.at,
@@ -3957,23 +4342,27 @@ const shiftedLocation = (location, offset) => ({
3957
4342
  start: shiftedPosition(location.start, offset),
3958
4343
  end: shiftedPosition(location.end, offset)
3959
4344
  });
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)
4345
+ const plannedMutant = (command, candidate, reasons) => Option.match(ignorerRefusalOf(command, candidate), {
4346
+ onSome: (refusal) => Result.fail(refusal),
4347
+ onNone: () => Option.match(Option.fromNullishOr(candidate.location), {
4348
+ onNone: () => Result.fail(MutantWithoutLocation.make({
4349
+ fileName: command.fileName,
4350
+ mutatorName: candidate.mutatorName
4351
+ })),
4352
+ onSome: (location) => Result.succeed({
4353
+ id: candidate.id,
4354
+ mutatorName: candidate.mutatorName,
4355
+ replacementCode: candidate.replacementCode,
4356
+ location: shiftedLocation(location, command.offset),
4357
+ ignoreReason: reasons.ignoreReason,
4358
+ ...Option.match(reasons.subsumption, {
4359
+ onNone: () => ({}),
4360
+ onSome: (subsumption) => ({ subsumption })
4361
+ })
4362
+ })
3971
4363
  })
3972
4364
  });
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
- };
4365
+ 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
4366
  const withoutReason = (mutant) => mutant.ignoreReason === void 0;
3978
4367
  const planOf = (command, mutants) => Match.value(mutants.some(withoutReason)).pipe(Match.when(true, () => MutantsPlanned.make({
3979
4368
  mutants: [...mutants],
@@ -3986,9 +4375,10 @@ const planOf = (command, mutants) => Match.value(mutants.some(withoutReason)).pi
3986
4375
  const planMutants = Workflow.make({
3987
4376
  command: PlanMutantsCommand,
3988
4377
  decision: S.Union([MutantsPlanned, MutantsFullyIgnored]),
3989
- error: MutantWithoutLocation,
4378
+ error: S.Union([MutantWithoutLocation, IgnorerAnsweredWithoutReason]),
3990
4379
  decide: (command) => Result.gen(function* () {
3991
- const mutants = yield* plannedMutants(command);
4380
+ const reasons = candidateReasonsAt(command);
4381
+ const mutants = yield* plannedMutants(command, reasons);
3992
4382
  return planOf(command, mutants);
3993
4383
  })
3994
4384
  });
@@ -4357,7 +4747,10 @@ const plannedWithNodes = (candidates, planned, fileName, includes) => planned.fl
4357
4747
  onNone: () => [],
4358
4748
  onSome: (candidate) => includes(mutant) ? [createMutant(mutant, fileName, candidate.node, candidate.replacement)] : []
4359
4749
  }));
4360
- const ignorersReasonFor = (node, ancestors, ignorers) => ignorers.reduce((reason, ignorer) => Option.orElse(reason, () => Option.fromNullishOr(ignorer.shouldIgnore(node, ancestors))), Option.none());
4750
+ const ignorerAnswerFor = (node, ancestors, ignorers) => ignorers.reduce((answered, ignorer) => Option.orElse(answered, () => Option.map(Option.fromNullishOr(ignorer.shouldIgnore(node, ancestors)), (answer) => ({
4751
+ ignorerName: ignorer.name,
4752
+ answer
4753
+ }))), Option.none());
4361
4754
  const hasKey = (node, key) => key in node;
4362
4755
  const readKey = (node, key) => hasKey(node, key) ? node[key] : void 0;
4363
4756
  const isMemberExpressionValue = (node) => nodeType(node) === "MemberExpression";
@@ -4367,23 +4760,47 @@ const isMemberOfIdentifierParts = (node) => isMemberExpressionValue(node) && has
4367
4760
  function isNamedCallee(node) {
4368
4761
  return isMemberOfIdentifierParts(node) && readKey(node, "computed") === false;
4369
4762
  }
4370
- const aridCalleeOf = (callee) => Match.value(callee).pipe(Match.when(isNamedCallee, (named) => Option.some({
4371
- object: named.object.name,
4763
+ const unshadowedGlobalOf = (name, context, table) => Option.filter(Option.filter(Option.some(name), S.is(AridGlobalObject)), (global) => isShadowed(global, context) === false && isImportedLocal(global, table) === false);
4764
+ const aridGlobalCalleeOf = (callee, context, table) => Match.value(callee).pipe(Match.when(isNamedCallee, (named) => Option.map(unshadowedGlobalOf(named.object.name, context, table), (name) => ({
4765
+ _tag: "Global",
4766
+ name,
4372
4767
  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), {
4768
+ }))), Match.orElse(() => Option.none()));
4769
+ const aridCalleeOf = (call, callFrame, policy, table) => {
4770
+ const context = toMutatorContext([call, ...ancestorsOfFrame(callFrame)], policy);
4771
+ return Option.orElse(Option.map(resolveCallee(call.callee, context, table), (callee) => ({
4772
+ _tag: "EffectExport",
4773
+ module: callee.module,
4774
+ exportName: callee.exportName
4775
+ })), () => aridGlobalCalleeOf(call.callee, context, table));
4776
+ };
4777
+ const aridCallFrame = (current, ancestor, ancestorFrame, policy, table) => ancestor.arguments.some((argument) => argument === current.node) ? {
4778
+ _tag: "ArgumentFrame",
4779
+ callee: aridCalleeOf(ancestor, ancestorFrame, policy, table)
4780
+ } : { _tag: "ThroughFrame" };
4781
+ const aridCallFrameFor = (current, ancestorFrame, policy, table) => {
4782
+ const ancestor = ancestorFrame.node;
4783
+ if (ancestor.type !== "CallExpression") return [];
4784
+ return [aridCallFrame(current, ancestor, ancestorFrame, policy, table)];
4785
+ };
4786
+ const functionBoundaryOf = (current) => isFunctionLike(current.node) ? [{ _tag: "FunctionBoundary" }] : [];
4787
+ const aridFramesOf = (frame, policy, table) => framesUpward(frame).flatMap((current) => [...functionBoundaryOf(current), ...Option.match(Option.fromNullishOr(current.parent), {
4379
4788
  onNone: () => [],
4380
- onSome: (parent) => Option.toArray(aridFrameFor(current.node, parent.node))
4381
- }));
4789
+ onSome: (parent) => aridCallFrameFor(current, parent, policy, table)
4790
+ })]);
4382
4791
  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({
4792
+ const aridReasonOf = (frame, policy, table) => Match.value(aridCode(AridCodeCommand.make({
4384
4793
  policy,
4385
- frames: [...aridFramesOf(frame)]
4794
+ frames: [...aridFramesOf(frame, policy, table)]
4386
4795
  }))).pipe(Match.when(Result.isSuccess, (decided) => aridStatusReason(decided.success)), Match.orElse(() => Option.none()));
4796
+ const subsumptionSiteOf = (node) => Option.match(relationalOperatorOf(node), {
4797
+ onNone: () => OtherSite.make({}),
4798
+ onSome: (operator) => RelationalSite.make({ operator })
4799
+ });
4800
+ const subsumptionReplacementOf = (mutatorName, replacement) => Option.match(Option.filter(relationalOperatorOf(replacement), () => mutatorName === "EqualityOperator"), {
4801
+ onNone: () => OtherReplacement.make({}),
4802
+ onSome: (operator) => OrderingOperator.make({ operator })
4803
+ });
4387
4804
  const mutablesFor = (frame, location, context) => {
4388
4805
  const ancestors = ancestorsOfFrame(frame);
4389
4806
  const mutatorContext = toMutatorContext(ancestors, context.mutantSetPolicy);
@@ -4391,10 +4808,11 @@ const mutablesFor = (frame, location, context) => {
4391
4808
  mutatorName,
4392
4809
  replacement
4393
4810
  })));
4394
- const ignorerReason = replacements.length === 0 ? void 0 : Option.getOrUndefined(ignorersReasonFor(frame.node, ancestors, context.ignorers));
4395
- const aridReason = aridReasonOf(frame, context.mutantSetPolicy);
4811
+ if (replacements.length === 0) return [];
4812
+ const ignorerAnswer = Option.getOrUndefined(ignorerAnswerFor(frame.node, ancestors, context.ignorers));
4813
+ const aridReason = Option.getOrUndefined(aridReasonOf(frame, context.mutantSetPolicy, context.aridImportTable));
4396
4814
  const originalCode = printNode(frame.node);
4397
- const relationalSite = relationalSiteFacts(frame.node, mutatorContext);
4815
+ const plantedOriginal = printNode(unwrapParenthesizedExpression(frame.node));
4398
4816
  return replacements.map(({ mutatorName, replacement }) => {
4399
4817
  const replacementCode = printNode(replacement);
4400
4818
  const tuple = {
@@ -4406,7 +4824,6 @@ const mutablesFor = (frame, location, context) => {
4406
4824
  return {
4407
4825
  node: frame.node,
4408
4826
  replacement,
4409
- aridReason,
4410
4827
  data: {
4411
4828
  id: mutantIdOf({
4412
4829
  ...tuple,
@@ -4415,12 +4832,13 @@ const mutablesFor = (frame, location, context) => {
4415
4832
  mutatorName,
4416
4833
  replacementCode,
4417
4834
  location,
4418
- ignorerReason,
4835
+ ignorerAnswer,
4836
+ ...aridReason === void 0 ? {} : { aridReason },
4419
4837
  mutantSet: {
4420
- originalCode,
4421
- replacementCode,
4422
- relationalSufficient: relationalSufficientReplacement(relationalSite, replacement)
4423
- }
4838
+ originalCode: plantedOriginal,
4839
+ replacementCode: printNode(unwrapParenthesizedExpression(replacement))
4840
+ },
4841
+ subsumption: subsumptionReplacementOf(mutatorName, replacement)
4424
4842
  }
4425
4843
  };
4426
4844
  });
@@ -4461,18 +4879,8 @@ const attachPlaceable = (mutants, frame, state, context) => Match.value(mutants.
4461
4879
  })),
4462
4880
  onSome: (target) => applyToClaim(mutants, target, state, context)
4463
4881
  })), 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
4882
  const collectPlan = (frame, candidates, plan, state, context) => {
4474
- const planned = withAridReasons(candidates, plan.mutants);
4475
- const collected = plannedWithNodes(candidates, planned, context.fileName, () => true);
4883
+ const collected = plannedWithNodes(candidates, plan.mutants, context.fileName, () => true);
4476
4884
  const nextState = {
4477
4885
  ...state,
4478
4886
  mutants: [...state.mutants, ...collected],
@@ -4480,7 +4888,7 @@ const collectPlan = (frame, candidates, plan, state, context) => {
4480
4888
  };
4481
4889
  return Match.value(plan).pipe(Match.tag("MutantsPlanned", (plannedPlan) => {
4482
4890
  const placeable = new Set(plannedPlan.placeableIds);
4483
- const live = plannedWithNodes(candidates, planned, context.fileName, (mutant) => placeable.has(mutant.id) && mutant.ignoreReason === void 0);
4891
+ const live = plannedWithNodes(candidates, plan.mutants, context.fileName, (mutant) => placeable.has(mutant.id));
4484
4892
  return attachPlaceable(live, frame, {
4485
4893
  ...nextState,
4486
4894
  hasLiveMutants: nextState.hasLiveMutants || live.length > 0
@@ -4497,6 +4905,7 @@ const planMutantsAt = (frame, candidates, directives, location, state, context)
4497
4905
  rule: [...state.directiveRule],
4498
4906
  directives: [...directives],
4499
4907
  candidates: candidates.map((candidate) => candidate.data),
4908
+ site: subsumptionSiteOf(frame.node),
4500
4909
  mutantSetPolicy: context.mutantSetPolicy
4501
4910
  }));
4502
4911
  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 +5024,9 @@ const refusalError = (refusal) => Match.value(refusal).pipe(Match.tag("NodeWitho
4615
5024
  })), Match.tag("MutantWithoutLocation", (failed) => InstrumentError.make({
4616
5025
  message: `Mutant without a source location: ${failed.mutatorName} in ${failed.fileName}`,
4617
5026
  cause: void 0
5027
+ })), Match.tag("IgnorerAnsweredWithoutReason", (failed) => InstrumentError.make({
5028
+ message: failed.message,
5029
+ cause: failed
4618
5030
  })), Match.exhaustive);
4619
5031
  const transformScriptDataFirst = Effect.fn(SpanTaxonomy.Spans.instrumentTransformScript.name)(function* ({ root, originFileName, rawContent, offset, comments }, mutantCollector, { options, mutateDescription, basePath }) {
4620
5032
  const lineStarts = lineStartsOf(rawContent);
@@ -4629,6 +5041,7 @@ const transformScriptDataFirst = Effect.fn(SpanTaxonomy.Spans.instrumentTransfor
4629
5041
  allMutatorNames: options.mutators.known.map((name) => name.toLowerCase()),
4630
5042
  excludedMutations: options.excludedMutations,
4631
5043
  mutantSetPolicy: options.mutantSetPolicy,
5044
+ aridImportTable: buildImportTable(root, AridEffectModule.literals),
4632
5045
  ignorers: options.ignorers,
4633
5046
  ordinalOf: mutantCollector.ordinalOf
4634
5047
  };
@@ -4875,7 +5288,7 @@ const spliceForAssigned = (request) => Option.match(request.registry.entryForFor
4875
5288
  onSome: (entry) => spliceFileFor(request.file, entry)
4876
5289
  });
4877
5290
  //#endregion
4878
- //#region src/drivers/oxc-program.ts
5291
+ //#region src/oxc-program.ts
4879
5292
  const EMBEDDED_SCRIPT_FILE = "embedded-script.js";
4880
5293
  const makeScriptParser = (oxc) => (source, scriptFormat) => {
4881
5294
  const result = oxc.parseSync(EMBEDDED_SCRIPT_FILE, source, {
@@ -5044,6 +5457,11 @@ const admitInstrumentFiles = Workflow.make({
5044
5457
  });
5045
5458
  //#endregion
5046
5459
  //#region src/instrument-files.cell.ts
5460
+ const skippedOf = (raw) => raw.skipped.map(({ extension, file, reason }) => InstrumentFileSkip.make({
5461
+ extension,
5462
+ file,
5463
+ reason
5464
+ }));
5047
5465
  const printedFile = (raw, { file, ast }) => Result.map(print(ast, raw.registry.entryForFormat), (content) => ({
5048
5466
  name: file.name,
5049
5467
  mutate: file.mutate,
@@ -5056,12 +5474,12 @@ const printedFiles = (raw) => Effect.forEach(raw.parsed, (parsed) => Effect.from
5056
5474
  const instrumentedResult = (raw) => Effect.map(printedFiles(raw), (files) => InstrumentResult.make({
5057
5475
  files: [...files],
5058
5476
  mutants: [...raw.mutants],
5059
- skipped: [...raw.skipped]
5477
+ skipped: skippedOf(raw)
5060
5478
  }));
5061
5479
  const skippedOnlyResult = (raw) => InstrumentResult.make({
5062
5480
  files: [],
5063
5481
  mutants: [],
5064
- skipped: [...raw.skipped]
5482
+ skipped: skippedOf(raw)
5065
5483
  });
5066
5484
  const readInstrumentFiles = Effect.fn(SpanTaxonomy.Spans.instrumentReadFiles.name)(function* (input) {
5067
5485
  const outcomes = yield* Effect.forEach(input.files, (file) => fileOutcome({
@@ -5128,6 +5546,7 @@ const disableTypeChecks = dual((args) => Predicate.hasProperty(args[0], "content
5128
5546
  var Instrument_exports = /* @__PURE__ */ __exportAll({
5129
5547
  InstrumentFileSkip: () => InstrumentFileSkip,
5130
5548
  InstrumenterContext: () => InstrumenterContext,
5549
+ MutateDescriptionSchema: () => MutateDescriptionSchema,
5131
5550
  disableTypeChecks: () => disableTypeChecks,
5132
5551
  instrument: () => instrument
5133
5552
  });
@@ -5135,7 +5554,6 @@ var Instrument_exports = /* @__PURE__ */ __exportAll({
5135
5554
  //#region src/Mutator/mod.ts
5136
5555
  var Mutator_exports = /* @__PURE__ */ __exportAll({
5137
5556
  defaultMutators: () => defaultMutators,
5138
- optInMutators: () => optInMutators,
5139
5557
  registryOf: () => registryOf,
5140
5558
  selectMutators: () => selectMutators,
5141
5559
  stockRegistry: () => stockRegistry