@systemfsoftware/stryker-js-instrumenter 8.0.1 → 8.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @systemfsoftware/stryker-js-instrumenter
2
2
 
3
+ ## 8.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Three opt-in mutators plant Effect concurrency faults on top of the default set, so a mutation run can check that your concurrency tests catch races. `AtomicUpdateSplit` splits read-modify-write calls on `Ref` and `SynchronizedRef` into a separate read, a yield, and a separate write. `SynchronizationRemoval` removes semaphore permits, `Effect.uninterruptible`, and `Effect.uninterruptibleMask`. `FinalizerEscape` stops `ensuring`, `onExit`, `onError`, `onInterrupt`, `acquireRelease`, and `acquireUseRelease` cleanup from running on interruption. All three are off by default. Enable any subset by listing them under `mutator: { optInMutations: [...] }` in your configuration. A name that is not an opt-in mutator fails the run, and the error lists the known names. The replacements use Effect 4 APIs, so a repository on Effect 3.x must not opt in.
8
+
9
+ ### Patch Changes
10
+
11
+ - Disable comments with the `next-line` scope now ignore the mutants on the line directly below the comment and record the given reason. Before this fix they ignored nothing.
12
+
13
+ ## 8.0.2
14
+
15
+ ### Patch Changes
16
+
17
+ - Exported declarations that previously took `unknown` now take a defaulted type parameter: calls that omit the type argument are unchanged, and calls that were previously rejected for passing an unconstrained value are accepted. No runtime behaviour changes.
18
+
3
19
  ## 8.0.1
4
20
 
5
21
  ### Patch Changes
package/README.md CHANGED
@@ -16,6 +16,24 @@ pnpm add @systemfsoftware/stryker-js-instrumenter
16
16
 
17
17
  The instrumenter is used internally by the Stryker mutation testing framework to instrument source files with mutant coverage and switching logic.
18
18
 
19
+ ## Opt-in mutators
20
+
21
+ Three mutators that plant Effect concurrency faults are available on request and stay out of the default set. Enable any subset in your Stryker config:
22
+
23
+ ```json
24
+ {
25
+ "mutator": {
26
+ "optInMutations": ["AtomicUpdateSplit", "SynchronizationRemoval", "FinalizerEscape"]
27
+ }
28
+ }
29
+ ```
30
+
31
+ - **AtomicUpdateSplit** turns read-modify-write calls such as `Ref.update` or `SynchronizedRef.modify` into a separate read, a yield, and a separate write, so an interleaved mutation can lose an update.
32
+ - **SynchronizationRemoval** drops the guarding effect of `Semaphore.withPermits`/`withPermit` and `Effect.uninterruptible`, letting code that assumed exclusivity or uninterruptibility run without it.
33
+ - **FinalizerEscape** weakens `Effect.ensuring`, `Effect.onExit`, `Effect.onError`, and `Effect.onInterrupt` so their cleanup no longer runs on interruption.
34
+
35
+ Naming a mutator that does not exist fails the run and lists the names that do. The replacements these mutators plant use Effect 4 APIs, so a repository on Effect 3.x must not opt in.
36
+
19
37
  ## License
20
38
 
21
39
  Apache-2.0. Part of [systemfsoftware](https://github.com/systemfsoftware/stryker-js-effect/tree/main/packages/stryker-js-instrumenter#readme).
package/dist/index.d.mts CHANGED
@@ -2,7 +2,7 @@ import * as Effect from 'effect/Effect';
2
2
  import * as S from 'effect/Schema';
3
3
  import { YieldableError } from 'effect/Cause';
4
4
 
5
- export declare const causeText: (cause: unknown, depth: number) => string | undefined;
5
+ export declare const causeText: <A = unknown>(cause: A, depth: number) => string | undefined;
6
6
 
7
7
  export declare interface Coverage {
8
8
  readonly static: CoverageData;
@@ -31,7 +31,7 @@ export declare const ERROR_CODES: Readonly<{
31
31
  NoSuchFileOrDirectory: 'ENOENT';
32
32
  }>;
33
33
 
34
- export declare function errorToString(error: unknown): string;
34
+ export declare function errorToString<A = unknown>(error: A): string;
35
35
 
36
36
  declare interface File_2 extends FileDescription {
37
37
  name: string;
@@ -71,6 +71,7 @@ export declare const InstrumenterOptionsSchema: S.Struct<{
71
71
  readonly excludedMutations: S.$Array<S.String>;
72
72
  readonly ignorers: S.$Array<S.Unknown>;
73
73
  readonly noHeader: S.optional<S.Boolean>;
74
+ readonly optInMutations: S.optional<S.$Array<S.String>>;
74
75
  }>;
75
76
 
76
77
  export declare class InstrumentError extends InstrumentError_base {
package/dist/index.mjs CHANGED
@@ -1,12 +1,13 @@
1
1
  import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
2
3
  import * as Predicate from "effect/Predicate";
3
4
  import * as S from "effect/Schema";
4
5
  import * as Arr from "effect/Array";
5
6
  import * as Match from "effect/Match";
6
- import * as Option from "effect/Option";
7
7
  import { walk } from "oxc-walker";
8
8
  import { Schema } from "effect";
9
9
  import { RegExpParser, visitRegExpAST } from "@eslint-community/regexpp";
10
+ import * as Bool from "effect/Boolean";
10
11
  //#region src/Location.schema.ts
11
12
  const PositionSchema = S.Struct({
12
13
  line: S.Finite,
@@ -88,7 +89,8 @@ const IgnorerSchema = S.Unknown;
88
89
  const InstrumenterOptionsSchema = S.Struct({
89
90
  excludedMutations: S.Array(S.String),
90
91
  ignorers: S.Array(IgnorerSchema),
91
- noHeader: S.optional(S.Boolean)
92
+ noHeader: S.optional(S.Boolean),
93
+ optInMutations: S.optional(S.Array(S.String))
92
94
  });
93
95
  var InstrumentResult = class extends S.TaggedClass()("InstrumentResult", {
94
96
  files: S.Array(FileSchema),
@@ -403,6 +405,22 @@ function unaryExpression(operator, argument, loc) {
403
405
  prefix: true
404
406
  }, loc);
405
407
  }
408
+ function binaryExpression(operator, left, right, loc) {
409
+ return mark({
410
+ type: "BinaryExpression",
411
+ operator,
412
+ left,
413
+ right
414
+ }, loc);
415
+ }
416
+ function logicalExpression(operator, left, right, loc) {
417
+ return mark({
418
+ type: "LogicalExpression",
419
+ operator,
420
+ left,
421
+ right
422
+ }, loc);
423
+ }
406
424
  function updateExpression(operator, argument, prefix, loc) {
407
425
  return mark({
408
426
  type: "UpdateExpression",
@@ -542,24 +560,29 @@ const COMMENT_KEYS = /* @__PURE__ */ new Set(["leadingComments", "trailingCommen
542
560
  function isCommentKey(key) {
543
561
  return typeof key === "string" && COMMENT_KEYS.has(key);
544
562
  }
563
+ function walkTraverse(root, stack, visitors) {
564
+ walk(walkableNode(root), {
565
+ enter(node, _parent, context) {
566
+ readPath(stack, node, this, context, visitors);
567
+ },
568
+ leave(_node, _parent, context) {
569
+ closePath(stack, context, visitors);
570
+ }
571
+ });
572
+ }
573
+ const toTraverseError = (error) => error instanceof Error ? error : new Error("Traversal failed", { cause: error });
574
+ const handleTraverseError = (error) => {
575
+ const err = toTraverseError(error);
576
+ if (!S.is(TraversalStopped)(err)) throw err;
577
+ };
545
578
  function traverse(root, visitors) {
546
579
  const stack = [];
547
580
  try {
548
- walk(walkableNode(root), {
549
- enter(node, _parent, context) {
550
- readPath(stack, node, this, context, visitors);
551
- },
552
- leave(_node, _parent, context) {
553
- closePath(stack, context, visitors);
554
- }
555
- });
581
+ walkTraverse(root, stack, visitors);
556
582
  } catch (error) {
557
- rethrowUnlessStopped(error);
583
+ handleTraverseError(error);
558
584
  }
559
585
  }
560
- const rethrowUnlessStopped = (error) => {
561
- if (!S.is(TraversalStopped)(error)) throw error;
562
- };
563
586
  const readPath = (stack, node, controls, context, visitors) => {
564
587
  if (isCommentKey(context.key)) return;
565
588
  const path = createPath(node, parentOf(stack), controls, context);
@@ -627,7 +650,7 @@ function writeInto(parent, key, index, replacement) {
627
650
  writeAtKey(parent, key, index, replacement);
628
651
  }
629
652
  function writeAtKey(parent, key, index, replacement) {
630
- Match.value(key).pipe(Match.when(Predicate.isString, (slot) => writeAtSlot(parent, slot, index, replacement)), Match.orElse(() => void 0));
653
+ if (Predicate.isString(key)) writeAtSlot(parent, key, index, replacement);
631
654
  }
632
655
  function writeAtSlot(parent, key, index, replacement) {
633
656
  Match.value(index).pipe(Match.when(Match.null, () => overwrite(parent, key, replacement)), Match.orElse((position) => writeElement(parent[key], position, replacement)));
@@ -713,17 +736,16 @@ function isRangedBaseNode(value) {
713
736
  function isNonEmptyArray(value) {
714
737
  return Array.isArray(value) && value.length > 0;
715
738
  }
716
- function hasField(value, key) {
717
- return isPlainRecord(value) && key in value;
718
- }
719
- function hasContent(value) {
720
- return hasField(value, "content");
739
+ function hasContentField(value) {
740
+ return isPlainRecord(value) && "content" in value;
721
741
  }
722
- function hasHtml(value) {
723
- return hasField(value, "html");
742
+ function hasHtmlField(value) {
743
+ return isPlainRecord(value) && "html" in value;
724
744
  }
745
+ const readRecordField = (record, key) => record[key];
725
746
  function fieldOf$1(value, key) {
726
- return Match.value(value).pipe(Match.when(isPlainRecord, (record) => record[key]), Match.orElse(() => void 0));
747
+ if (!isPlainRecord(value)) return void 0;
748
+ return readRecordField(value, key);
727
749
  }
728
750
  function appendIfDefined(values, value) {
729
751
  if (value !== void 0) values.push(value);
@@ -998,12 +1020,12 @@ function isTextRange(value) {
998
1020
  function isTemplateExpressionTag(value) {
999
1021
  return isTypedRecord(value) && TEMPLATE_EXPRESSION_TYPES[value["type"]] === true;
1000
1022
  }
1001
- /**
1002
- * The first child of a `<script>` element node. A template `<script>` tag
1003
- * carries its code as that single `Text` child.
1004
- */
1023
+ const firstChild = (element) => {
1024
+ const children = element["children"];
1025
+ return isNonEmptyArray(children) ? Option.getOrUndefined(Arr.head(children)) : void 0;
1026
+ };
1005
1027
  function scriptChild(node) {
1006
- return Match.value(node).pipe(Match.when(isScriptElement, (element) => Match.value(element["children"]).pipe(Match.when(isNonEmptyArray, (values) => Option.getOrUndefined(Arr.head(values))), Match.orElse(() => void 0))), Match.orElse(() => void 0));
1028
+ return isScriptElement(node) ? firstChild(node) : void 0;
1007
1029
  }
1008
1030
  function tryGetScriptRangeFromElement(node) {
1009
1031
  return Match.value(scriptChild(node)).pipe(Match.when(isTextRange, (range) => ({
@@ -1013,14 +1035,14 @@ function tryGetScriptRangeFromElement(node) {
1013
1035
  })), Match.orElse(() => void 0));
1014
1036
  }
1015
1037
  function templateExpressionRange(node) {
1016
- return Match.value(node).pipe(Match.when(isTemplateExpressionTag, (tag) => rangedExpressionOf(tag["expression"])), Match.orElse(() => void 0));
1038
+ return isTemplateExpressionTag(node) ? rangedExpressionOf(node["expression"]) : void 0;
1017
1039
  }
1018
1040
  function rangedExpressionOf(payload) {
1019
- return Match.value(payload).pipe(Match.when(isRangedBaseNode, (expression) => ({
1020
- start: expression.start,
1021
- end: expression.end,
1041
+ if (isRangedBaseNode(payload)) return {
1042
+ start: payload.start,
1043
+ end: payload.end,
1022
1044
  isExpression: true
1023
- })), Match.orElse(() => void 0));
1045
+ };
1024
1046
  }
1025
1047
  /**
1026
1048
  * Allow instrumentation of Svelte 5 projects without dropping support for Svelte 4.
@@ -1032,9 +1054,10 @@ const loadWalker = (version, fileName) => Match.value(isAtLeast(version, SVELTE_
1032
1054
  * The specifier is chosen at run time and both modules are optional peers of
1033
1055
  * this package, so neither can be a static import.
1034
1056
  */
1057
+ const findWalker = (module) => isRecordWithWalk(module) ? module.walk : void 0;
1035
1058
  const loadWalkerModule = (specifier, fileName, cause) => Effect.gen(function* () {
1036
1059
  const module = yield* Effect.promise(() => import(specifier));
1037
- const walk = Match.value(module).pipe(Match.when(isRecordWithWalk, (record) => record.walk), Match.orElse(() => void 0));
1060
+ const walk = findWalker(module);
1038
1061
  if (walk === void 0) return yield* SvelteWalkerNotFound.make({
1039
1062
  fileName,
1040
1063
  cause
@@ -1109,29 +1132,31 @@ function getTemplateScriptRanges(ast, walker) {
1109
1132
  return ranges;
1110
1133
  }
1111
1134
  function htmlRootOf(ast) {
1112
- return Match.value(ast).pipe(Match.when(hasHtml, (record) => record["html"]), Match.orElse(() => {
1113
- throw new Error("Svelte AST without html");
1114
- }));
1135
+ if (!hasHtmlField(ast)) throw new Error("Svelte AST without html");
1136
+ return ast["html"];
1115
1137
  }
1116
1138
  function instanceScriptRange(ast) {
1117
- return Match.value(fieldOf$1(ast, "instance")).pipe(Match.when(hasContent, (instance) => scriptContentRange(instance["content"], INSTANCE_RANGE_MISSING)), Match.orElse(() => void 0));
1139
+ const record = contentFieldOf(fieldOf$1(ast, "instance"));
1140
+ return record === void 0 ? void 0 : scriptContentRange(record["content"], INSTANCE_RANGE_MISSING);
1118
1141
  }
1119
1142
  function scriptContentRange(content, missingRange) {
1120
- return Match.value(content).pipe(Match.when(isRange, (range) => ({
1121
- start: range.start,
1122
- end: range.end,
1143
+ if (isRange(content)) return {
1144
+ start: content.start,
1145
+ end: content.end,
1123
1146
  isExpression: false
1124
- })), Match.orElse(() => {
1125
- throw new Error(missingRange);
1126
- }));
1147
+ };
1148
+ throw new Error(missingRange);
1127
1149
  }
1128
1150
  function getModuleScriptRange(svelteAst) {
1129
1151
  return Match.value(fieldOf$1(svelteAst, "module")).pipe(Match.when(void 0, () => void 0), Match.when(null, () => void 0), Match.orElse((block) => moduleBlockRange(block)));
1130
1152
  }
1153
+ function contentFieldOf(value) {
1154
+ return Option.getOrUndefined(Option.filter(Option.fromNullishOr(value), hasContentField));
1155
+ }
1131
1156
  function moduleBlockRange(block) {
1132
- return Match.value(block).pipe(Match.when(hasContent, (record) => scriptContentRange(record["content"], MODULE_RANGE_MISSING)), Match.orElse(() => {
1133
- throw new Error(MODULE_RANGE_MISSING);
1134
- }));
1157
+ const record = contentFieldOf(block);
1158
+ if (record === void 0) throw new Error(MODULE_RANGE_MISSING);
1159
+ return scriptContentRange(record["content"], MODULE_RANGE_MISSING);
1135
1160
  }
1136
1161
  /**
1137
1162
  * Remaps script locations back to the original places using the script map
@@ -3021,14 +3046,14 @@ function isTSType(node) {
3021
3046
  function isNodeOfKind(node, kind) {
3022
3047
  return node.type === kind;
3023
3048
  }
3024
- const FUNCTION_KINDS = {
3049
+ const FUNCTION_KINDS$1 = {
3025
3050
  FunctionDeclaration: true,
3026
3051
  FunctionExpression: true,
3027
3052
  TSDeclareFunction: true,
3028
3053
  TSEmptyBodyFunctionExpression: true
3029
3054
  };
3030
3055
  function isFunctionNode(node) {
3031
- return FUNCTION_KINDS[node.type] === true;
3056
+ return FUNCTION_KINDS$1[node.type] === true;
3032
3057
  }
3033
3058
  function isAssignmentPattern(node) {
3034
3059
  return node.type === "AssignmentPattern";
@@ -3331,16 +3356,19 @@ function literalWithoutRegex(node) {
3331
3356
  return valueLiteralText(node.value);
3332
3357
  }
3333
3358
  function valueLiteralText(value) {
3334
- switch (typeof value) {
3335
- case "string": return JSON.stringify(value);
3336
- case "number": return String(value);
3337
- case "boolean": return String(value);
3338
- case "bigint": return `${value}n`;
3339
- case "symbol":
3340
- case "undefined":
3341
- case "object":
3342
- case "function": return "null";
3343
- }
3359
+ if (typeof value === "string") return JSON.stringify(value);
3360
+ return nonStringLiteralText(value);
3361
+ }
3362
+ function nonStringLiteralText(value) {
3363
+ if (typeof value === "number") return String(value);
3364
+ return booleanOrBigintText(value);
3365
+ }
3366
+ function booleanOrBigintText(value) {
3367
+ if (typeof value === "boolean") return String(value);
3368
+ return bigintText(value);
3369
+ }
3370
+ function bigintText(value) {
3371
+ return typeof value === "bigint" ? `${value}n` : "null";
3344
3372
  }
3345
3373
  function flagText(present, text) {
3346
3374
  switch (Boolean(present)) {
@@ -3394,13 +3422,12 @@ const HASHBANG_FIELDS = {
3394
3422
  function isHashbang(value) {
3395
3423
  return Predicate.isObject(value) && Object.entries(HASHBANG_FIELDS).every(([key, accepts]) => accepts(value[key]));
3396
3424
  }
3397
- const hashbangOf = (root) => {
3398
- const hashbang = Reflect.get(root, "hashbang");
3399
- if (!isHashbang(hashbang)) return null;
3400
- return hashbang;
3401
- };
3402
- const jsPrint = (file) => printProgram(file.root, { hashbang: hashbangOf(file.root) });
3403
- const tsPrint = (file) => printProgram(file.root, { hashbang: hashbangOf(file.root) });
3425
+ const toHashbang = (hashbang) => isHashbang(hashbang) ? hashbang : null;
3426
+ function getHashbang(root) {
3427
+ return toHashbang("hashbang" in root ? root.hashbang : null);
3428
+ }
3429
+ const jsPrint = (file) => printProgram(file.root, { hashbang: getHashbang(file.root) });
3430
+ const tsPrint = (file) => printProgram(file.root, { hashbang: getHashbang(file.root) });
3404
3431
  function getScriptStart$1(script) {
3405
3432
  const span = spanOf(script.root);
3406
3433
  if (span === void 0) throw new Error("Script AST root without start");
@@ -3467,9 +3494,14 @@ function normalizeFileName(fileName) {
3467
3494
  }
3468
3495
  const hasText = (value) => Predicate.isString(value) && value.length > 0;
3469
3496
  const textIfNonEmpty = (value) => Option.getOrUndefined(Option.filter(Option.fromUndefinedOr(value), hasText));
3470
- const fieldOf = (value, key) => Match.value(key in value).pipe(Match.when(true, () => {
3471
- return Reflect.get(value, key);
3472
- }), Match.orElse(() => void 0));
3497
+ function readFieldOf(record, key) {
3498
+ return record[key];
3499
+ }
3500
+ const hasFieldIn = (value, key) => key in value;
3501
+ const fieldOf = (value, key) => {
3502
+ if (!hasFieldIn(value, key)) return;
3503
+ return readFieldOf(value, key);
3504
+ };
3473
3505
  const hasStringCode = (error) => Match.value(fieldOf(error, "code")).pipe(Match.when(Match.string, () => true), Match.orElse(() => false));
3474
3506
  function isErrnoException(error) {
3475
3507
  return Match.value(error).pipe(Match.when(Match.instanceOf(Error), hasStringCode), Match.orElse(() => false));
@@ -3494,7 +3526,7 @@ const isNonPlaceholderText = (text) => Match.value({
3494
3526
  isPlaceholder: false
3495
3527
  }, () => true), Match.orElse(() => false));
3496
3528
  const isUsableText = (value) => Match.value(value).pipe(Match.when(Match.string, isNonPlaceholderText), Match.orElse(() => false));
3497
- const usableText = (value) => Match.value(value).pipe(Match.when(isUsableText, (text) => text), Match.orElse(() => ""));
3529
+ const usableText = (value) => isUsableText(value) ? value : "";
3498
3530
  const objectToStringText = (value) => Match.value(fieldOf(value, "toString")).pipe(Match.when(Match.instanceOf(Function), (callable) => {
3499
3531
  try {
3500
3532
  return usableText(Reflect.apply(callable, value, []));
@@ -3503,9 +3535,19 @@ const objectToStringText = (value) => Match.value(fieldOf(value, "toString")).pi
3503
3535
  }
3504
3536
  }), Match.orElse(() => ""));
3505
3537
  const isObjectType = (cause) => typeof cause === "object";
3506
- const toStringText = (error) => Match.value(error).pipe(Match.when(Match.null, () => ""), Match.when(isObjectType, (value) => objectToStringText(value)), Match.orElse(() => ""));
3507
- const stringifyRest = (error) => Match.value(jsonText(error)).pipe(Match.when(hasText, (json) => json), Match.orElse(() => toStringText(error)));
3508
- const stringifyNonError = (error) => Match.value(error).pipe(Match.when(Match.string, (text) => text), Match.when(isJsonPrimitive, (primitive) => JSON.stringify(primitive)), Match.orElse(() => stringifyRest(error)));
3538
+ const isNonNullObjectType = (error) => error !== null && isObjectType(error);
3539
+ const toStringText = (error) => isNonNullObjectType(error) ? objectToStringText(error) : "";
3540
+ const stringifyRest = (error) => {
3541
+ const json = jsonText(error);
3542
+ return hasText(json) ? json : toStringText(error);
3543
+ };
3544
+ function primitiveJsonOf(error) {
3545
+ return isJsonPrimitive(error) ? JSON.stringify(error) : void 0;
3546
+ }
3547
+ const stringifyNonError = (error) => typeof error === "string" ? error : nonStringTextOf(error);
3548
+ function nonStringTextOf(error) {
3549
+ return primitiveJsonOf(error) ?? stringifyRest(error);
3550
+ }
3509
3551
  const errorText = (error) => Match.value(error).pipe(Match.when(isErrnoException, formatErrnoException), Match.orElse(() => formatError(error)));
3510
3552
  function errorToString(error) {
3511
3553
  return Match.value(error).pipe(Match.when(isEmptyError, () => ""), Match.when(Match.instanceOf(Error), errorText), Match.orElse(() => stringifyNonError(error)));
@@ -3519,7 +3561,603 @@ const errorMessage = (error) => Match.value(error.message.length > 0).pipe(Match
3519
3561
  const errorMessageOrTag = (value) => Match.value(value).pipe(Match.when(Match.instanceOf(Error), errorMessage), Match.orElse(() => tagOf(value)));
3520
3562
  const messageText = (value) => Match.value(textIfNonEmpty(fieldOf(value, "message"))).pipe(Match.when(hasText, (message) => message), Match.orElse(() => errorMessageOrTag(value)));
3521
3563
  const ownCauseText = (value) => Match.value(textIfNonEmpty(fieldOf(value, "reason"))).pipe(Match.when(hasText, (reason) => reason), Match.orElse(() => messageText(value)));
3522
- const causeText = (cause, depth) => Match.value(cause).pipe(Match.when(() => depth > 4, () => void 0), Match.when(Match.undefined, () => void 0), Match.when(Match.null, () => void 0), Match.when(Match.string, textIfNonEmpty), Match.when(isObjectType, (value) => textWithNested(ownCauseText(value), causeText(fieldOf(value, "cause"), depth + 1))), Match.orElse(() => void 0));
3564
+ const isPastDepth = (depth) => depth > 4;
3565
+ const isMissingCause = (cause) => cause === void 0 || cause === null;
3566
+ const causeText = (cause, depth) => isPastDepth(depth) ? void 0 : missingCauseTextOf(cause, depth);
3567
+ function missingCauseTextOf(cause, depth) {
3568
+ return isMissingCause(cause) ? void 0 : causeTextOfValue(cause, depth);
3569
+ }
3570
+ function causeTextOfValue(cause, depth) {
3571
+ return typeof cause === "string" ? textIfNonEmpty(cause) : objectCauseTextOf(cause, depth);
3572
+ }
3573
+ function objectCauseTextOf(cause, depth) {
3574
+ return isObjectType(cause) ? textWithNested(ownCauseText(cause), causeText(fieldOf(cause, "cause"), depth + 1)) : void 0;
3575
+ }
3576
+ //#endregion
3577
+ //#region src/EffectCall.ts
3578
+ /**
3579
+ * EffectCall — the resolution front half the opt-in concurrency mutators share.
3580
+ *
3581
+ * Given a visited node it answers: is this a covered Effect call (R1); through
3582
+ * which module and export did its callee resolve; which call form does its
3583
+ * argument count select (KTD10); does a data-last form sit where the replacement
3584
+ * function is contextually typed (R4); and how can each `effect` module the
3585
+ * replacement needs be reached from the file's own imports (KTD3). It decides
3586
+ * nothing about a replacement: a mutator asks for module access through the
3587
+ * description's `moduleAccess`, and gets none when the file has no unshadowed
3588
+ * binding for that module (R2).
3589
+ *
3590
+ * Pure. One expression per decision, no I/O, no throwing. The import table is
3591
+ * built once per Program and memoised in a WeakMap, because every visited node
3592
+ * asks for it and a file's imports never change mid-parse (KTD4).
3593
+ */
3594
+ const MODULE_NAMES = [
3595
+ "Effect",
3596
+ "Ref",
3597
+ "Semaphore",
3598
+ "SynchronizedRef"
3599
+ ];
3600
+ const EFFECT_SOURCE = "effect";
3601
+ const FUNCTION_SOURCE = "effect/Function";
3602
+ const DATA_FIRST = "data-first";
3603
+ const DATA_LAST_PIPE_ARG = "data-last-pipe-arg";
3604
+ const DATA_LAST_PIPE_METHOD = "data-last-pipe-method";
3605
+ const DATA_LAST_IMMEDIATE = "data-last-immediate";
3606
+ const PIPED_REFERENCE = "piped-reference";
3607
+ const NO_ARGUMENTS = Object.freeze([]);
3608
+ const ARITIES = Object.freeze({
3609
+ "Ref.modify": {
3610
+ dataFirst: [2],
3611
+ dataLast: 1
3612
+ },
3613
+ "Ref.modifySome": {
3614
+ dataFirst: [2],
3615
+ dataLast: 1
3616
+ },
3617
+ "Ref.update": {
3618
+ dataFirst: [2],
3619
+ dataLast: 1
3620
+ },
3621
+ "Ref.updateSome": {
3622
+ dataFirst: [2],
3623
+ dataLast: 1
3624
+ },
3625
+ "Ref.updateAndGet": {
3626
+ dataFirst: [2],
3627
+ dataLast: 1
3628
+ },
3629
+ "Ref.getAndUpdate": {
3630
+ dataFirst: [2],
3631
+ dataLast: 1
3632
+ },
3633
+ "SynchronizedRef.modify": {
3634
+ dataFirst: [2],
3635
+ dataLast: 1
3636
+ },
3637
+ "SynchronizedRef.modifySome": {
3638
+ dataFirst: [2],
3639
+ dataLast: 1
3640
+ },
3641
+ "SynchronizedRef.update": {
3642
+ dataFirst: [2],
3643
+ dataLast: 1
3644
+ },
3645
+ "SynchronizedRef.updateSome": {
3646
+ dataFirst: [2],
3647
+ dataLast: 1
3648
+ },
3649
+ "SynchronizedRef.updateAndGet": {
3650
+ dataFirst: [2],
3651
+ dataLast: 1
3652
+ },
3653
+ "SynchronizedRef.getAndUpdate": {
3654
+ dataFirst: [2],
3655
+ dataLast: 1
3656
+ },
3657
+ "Semaphore.withPermits": {
3658
+ dataFirst: [3],
3659
+ dataLast: 2
3660
+ },
3661
+ "Semaphore.withPermit": {
3662
+ dataFirst: [2],
3663
+ dataLast: 1
3664
+ },
3665
+ "Effect.ensuring": {
3666
+ dataFirst: [2],
3667
+ dataLast: 1
3668
+ },
3669
+ "Effect.onExit": {
3670
+ dataFirst: [2],
3671
+ dataLast: 1
3672
+ },
3673
+ "Effect.onError": {
3674
+ dataFirst: [2],
3675
+ dataLast: 1
3676
+ },
3677
+ "Effect.onInterrupt": {
3678
+ dataFirst: [2],
3679
+ dataLast: 1
3680
+ },
3681
+ "Effect.uninterruptible": { dataFirst: [1] },
3682
+ "Effect.uninterruptibleMask": { dataFirst: [1] },
3683
+ "Effect.acquireRelease": { dataFirst: [2, 3] },
3684
+ "Effect.acquireUseRelease": { dataFirst: [3] }
3685
+ });
3686
+ const EMPTY_IMPORT_TABLE = Object.freeze({
3687
+ moduleBindings: /* @__PURE__ */ new Map(),
3688
+ namespaces: /* @__PURE__ */ new Map(),
3689
+ bareFunctions: /* @__PURE__ */ new Map(),
3690
+ pipeBindings: /* @__PURE__ */ new Set()
3691
+ });
3692
+ const importTables = /* @__PURE__ */ new WeakMap();
3693
+ const importTableFor = (program) => Option.getOrElse(Option.fromNullishOr(importTables.get(program)), () => rememberImportTable(program));
3694
+ function rememberImportTable(program) {
3695
+ const table = buildImportTable(program);
3696
+ importTables.set(program, table);
3697
+ return table;
3698
+ }
3699
+ const buildImportTable = (program) => Arr.reduce(Arr.flatMap(Arr.filter(program.body, isImportDeclaration$1), importContributions), EMPTY_IMPORT_TABLE, applyContribution);
3700
+ const withEntry = (entries, key, value) => new Map([...entries, [key, value]]);
3701
+ const withMember = (members, value) => /* @__PURE__ */ new Set([...members, value]);
3702
+ const applyContribution = (table, contribution) => Match.value(contribution).pipe(Match.when((candidate) => candidate.kind === "module", (moduleBinding) => ({
3703
+ ...table,
3704
+ moduleBindings: withEntry(table.moduleBindings, moduleBinding.local, moduleBinding.module)
3705
+ })), Match.when((candidate) => candidate.kind === "namespace", (namespaceBinding) => ({
3706
+ ...table,
3707
+ namespaces: withEntry(table.namespaces, namespaceBinding.local, namespaceBinding.binding)
3708
+ })), Match.when((candidate) => candidate.kind === "bareFunction", (bareFunction) => ({
3709
+ ...table,
3710
+ bareFunctions: withEntry(table.bareFunctions, bareFunction.local, {
3711
+ module: bareFunction.module,
3712
+ exportName: bareFunction.exportName
3713
+ })
3714
+ })), Match.orElse((pipeBinding) => ({
3715
+ ...table,
3716
+ pipeBindings: withMember(table.pipeBindings, pipeBinding.local)
3717
+ })));
3718
+ const importContributions = (declaration) => Option.toArray(onlyWhen(declaration.importKind !== "type", declaration)).flatMap((imported) => Arr.flatMap(imported.specifiers, (specifier) => Option.toArray(specifierContribution(imported.source.value, specifier))));
3719
+ 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()));
3720
+ const namedSpecifierContribution = (source, specifier) => onlyWhen(specifier.importKind !== "type", specifier).pipe(Option.flatMap((imported) => namedContribution(source, moduleExportName(imported.imported), imported.local.name)));
3721
+ 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))));
3722
+ const effectRootContribution = (importedName, localName) => Option.orElse(Option.map(moduleNameFromName(importedName), (module) => moduleContribution(localName, module)), () => pipeContribution(importedName, localName));
3723
+ const pipeContribution = (importedName, localName) => onlyWhen(importedName === "pipe", pipeContributionOf(localName));
3724
+ const namespaceSpecifierContribution = (source, specifier) => Option.map(namespaceBindingFor(source), (binding) => ({
3725
+ kind: "namespace",
3726
+ local: specifier.local.name,
3727
+ binding
3728
+ }));
3729
+ const moduleContribution = (local, module) => ({
3730
+ kind: "module",
3731
+ local,
3732
+ module
3733
+ });
3734
+ const bareFunctionContribution = (local, module, exportName) => ({
3735
+ kind: "bareFunction",
3736
+ local,
3737
+ module,
3738
+ exportName
3739
+ });
3740
+ const pipeContributionOf = (local) => ({
3741
+ kind: "pipe",
3742
+ local
3743
+ });
3744
+ 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) => ({
3745
+ kind: "module",
3746
+ module
3747
+ }))));
3748
+ const moduleNameFromName = (name) => Arr.findFirst(MODULE_NAMES, (module) => module === name);
3749
+ const moduleNameFromSource = (source) => Arr.findFirst(MODULE_NAMES, (module) => source === `${EFFECT_SOURCE}/${module}`);
3750
+ const resolveEffectCall = (node, context) => Option.flatMap(programOf(context), (program) => resolveIn(node, context, importTableFor(program)));
3751
+ const programOf = (context) => Arr.findLast(context.ancestors, isProgram);
3752
+ 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()));
3753
+ 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({
3754
+ module: callee.module,
3755
+ exportName: callee.exportName,
3756
+ form,
3757
+ args,
3758
+ moduleAccess: moduleAccessFunction(callee, context, table)
3759
+ }))));
3760
+ const resolveReference = (reference, context, table) => Option.flatMap(resolveCallee(reference, context, table), (callee) => Option.flatMap(referenceForm(reference, context, table), (form) => Option.some({
3761
+ module: callee.module,
3762
+ exportName: callee.exportName,
3763
+ form,
3764
+ args: NO_ARGUMENTS,
3765
+ moduleAccess: moduleAccessFunction(callee, context, table)
3766
+ })));
3767
+ const referenceForm = (reference, context, table) => Option.flatMap(parentCall(context), (parent) => Option.map(pipePosition(parent, reference, context, table), () => PIPED_REFERENCE));
3768
+ const spreadFreeArguments$1 = (args) => Match.value(args.some(isSpreadElement)).pipe(Match.when(true, () => Option.none()), Match.orElse(() => Option.some(args.filter(isSpreadFree))));
3769
+ 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()));
3770
+ const resolveBareCallee = (reference, context, table) => Option.flatMap(Option.fromNullishOr(table.bareFunctions.get(reference.name)), (binding) => Option.map(unshadowedName(reference.name, context), () => ({
3771
+ module: binding.module,
3772
+ exportName: binding.exportName,
3773
+ owner: Option.none()
3774
+ })));
3775
+ const resolveMemberCallee = (member, context, table) => Option.flatMap(staticMemberName(member), (exportName) => Option.map(resolveModuleObject(member.object, context, table), (owner) => ({
3776
+ module: owner.module,
3777
+ exportName,
3778
+ owner: Option.some(owner)
3779
+ })));
3780
+ const resolveModuleObject = (expression, context, table) => Match.value(expression).pipe(Match.when(isIdentifier$1, (reference) => identifierModuleObject(reference, context, table)), Match.when(isMemberExpression, (member) => namespacedModuleObject(member, context, table)), Match.orElse(() => Option.none()));
3781
+ const identifierModuleObject = (reference, context, table) => Option.orElse(Option.flatMap(Option.fromNullishOr(table.moduleBindings.get(reference.name)), (module) => Option.map(unshadowedName(reference.name, context), () => ({
3782
+ module,
3783
+ access: reference
3784
+ }))), () => Option.flatMap(namespaceOf(reference, context, table), (binding) => Option.map(moduleOf(binding), (module) => ({
3785
+ module,
3786
+ access: reference
3787
+ }))));
3788
+ 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) => ({
3789
+ module,
3790
+ access: member
3791
+ }))), Match.when(isModuleNamespace, (moduleNamespace) => Option.map(onlyWhen(moduleNamespace.module === moduleName, member), (access) => ({
3792
+ module: moduleNamespace.module,
3793
+ access
3794
+ }))), Match.orElse(() => Option.none()))));
3795
+ const isRootNamespace = (binding) => binding.kind === "root";
3796
+ const moduleOf = (binding) => Match.value(binding).pipe(Match.when(isModuleNamespace, (moduleNamespace) => Option.some(moduleNamespace.module)), Match.orElse(() => Option.none()));
3797
+ const isModuleNamespace = (binding) => binding.kind === "module";
3798
+ 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()));
3799
+ 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())));
3800
+ const arityOf = (callee) => Option.fromNullishOr(ARITIES[`${callee.module}.${callee.exportName}`]);
3801
+ 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))));
3802
+ const pipePosition = (parent, argument, context, table) => Option.flatMap(argumentIndex(parent.arguments, argument), (index) => Option.orElse(onlyWhen(isPipeMethodCall(parent), {
3803
+ index,
3804
+ callee: "method"
3805
+ }), () => onlyWhen(isEffectPipeCall(parent, context, table), {
3806
+ index,
3807
+ callee: "effect"
3808
+ })));
3809
+ 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)));
3810
+ const argumentIndex = (args, node) => Option.flatMap(Option.fromNullishOr(args.findIndex((argument) => sameNode(argument, node))), (index) => onlyWhen(index >= 0, index));
3811
+ 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())));
3812
+ const isPipeMethodCall = (parent) => Match.value(parent.callee).pipe(Match.when(isMemberExpression, (member) => Option.exists(staticMemberName(member), (name) => name === "pipe")), Match.orElse(() => false));
3813
+ const isEffectPipeCall = (parent, context, table) => Match.value(parent.callee).pipe(Match.when(isIdentifier$1, (reference) => Bool.and(isVisible(reference.name, context), table.pipeBindings.has(reference.name))), Match.when(isMemberExpression, (member) => Option.exists(staticMemberName(member), (name) => Bool.and(name === "pipe", isPipeNamespace(member.object, context, table)))), Match.orElse(() => false));
3814
+ const isPipeNamespace = (expression, context, table) => Match.value(expression).pipe(Match.when(isIdentifier$1, (reference) => Option.match(namespaceOf(expression, context, table), {
3815
+ onNone: () => false,
3816
+ onSome: (binding) => Bool.and(isPipeNamespaceBinding(binding), isVisible(reference.name, context))
3817
+ })), Match.orElse(() => false));
3818
+ const isPipeNamespaceBinding = (binding) => Match.value(binding.kind).pipe(Match.when((kind) => kind === "root", () => true), Match.when((kind) => kind === "functionModule", () => true), Match.orElse(() => false));
3819
+ const moduleAccessFunction = (callee, context, table) => (module) => Arr.findFirst([
3820
+ ownerAccessFor(callee, module),
3821
+ namedModuleAccess(module, context, table),
3822
+ rootNamespaceAccess(module, context, table),
3823
+ moduleNamespaceAccess(module, context, table)
3824
+ ], Option.isSome).pipe(Option.flatten);
3825
+ const ownerAccessFor = (callee, module) => Option.flatMap(callee.owner, (owner) => onlyWhen(owner.module === module, owner.access));
3826
+ const namedModuleAccess = (module, context, table) => Option.map(Option.fromNullishOr([...table.moduleBindings].find(([local, bound]) => Bool.and(bound === module, isVisible(local, context)))), ([local]) => identifier(local));
3827
+ const rootNamespaceAccess = (module, context, table) => Option.map(Option.fromNullishOr([...table.namespaces].find(([local, binding]) => Bool.and(binding.kind === "root", isVisible(local, context)))), ([local]) => memberExpression(identifier(local), identifier(module), false));
3828
+ const moduleNamespaceAccess = (module, context, table) => Option.map(Option.fromNullishOr([...table.namespaces].find(([local, binding]) => Bool.and(Option.exists(moduleOf(binding), (bound) => bound === module), isVisible(local, context)))), ([local]) => identifier(local));
3829
+ const isMovableArgument = (node) => nodesOutsideFunctions(node).every((candidate) => YIELD_OR_AWAIT_KINDS[nodeType(candidate) ?? ""] !== true);
3830
+ const YIELD_OR_AWAIT_KINDS = {
3831
+ AwaitExpression: true,
3832
+ YieldExpression: true
3833
+ };
3834
+ 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));
3835
+ 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))));
3836
+ const identifiersIn = (node) => allNodesUnder(node).flatMap((candidate) => Option.toArray(identifierText(candidate)));
3837
+ const freshIdentifier = (base, taken) => freeIdentifier(base, taken, 0);
3838
+ 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)));
3839
+ const suffixedName = (base, suffix) => Match.value(suffix).pipe(Match.when(0, () => base), Match.orElse((count) => `${base}${count}`));
3840
+ const isShadowed = (name, context) => context.ancestors.some((ancestor) => scopeDeclares(ancestor, name));
3841
+ const isVisible = (name, context) => isShadowed(name, context) === false;
3842
+ const unshadowedName = (name, context) => onlyWhen(isVisible(name, context), name);
3843
+ 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));
3844
+ const functionDeclares = (fn, name) => holdsAny([
3845
+ fn.params.some((param) => patternDeclaresOr(param, name)),
3846
+ declaresIdentifier(fn.id, name),
3847
+ blockBodyDeclaresHoistedVar(fn.body, name)
3848
+ ]);
3849
+ const blockBodyDeclaresHoistedVar = (body, name) => Option.match(Option.fromNullishOr(body), {
3850
+ onNone: () => false,
3851
+ onSome: (present) => Match.value(present).pipe(Match.when(isBlockStatement, (block) => hoistedVarsDeclare(block, name)), Match.orElse(() => false))
3852
+ });
3853
+ const hoistedVarsDeclare = (block, name) => nodesOutsideFunctions(block).some((candidate) => hoistedCandidateDeclares(candidate, name));
3854
+ const hoistedCandidateDeclares = (candidate, name) => Match.value(candidate).pipe(Match.when(isVariableDeclaration, (declaration) => Bool.and(declaration.kind === "var", statementBindingsDeclare(declaration, name))), Match.orElse(() => false));
3855
+ const statementsDeclare = (statements, name) => statements.some((statement) => statementDeclares(statement, name));
3856
+ const statementDeclares = (statement, name) => Match.value(statement).pipe(Match.when(isVariableDeclaration, (declaration) => statementBindingsDeclare(declaration, name)), Match.when(isFunctionDeclaration, (declaration) => declaresIdentifier(declaration.id, name)), Match.when(isClassLike, (declaration) => declaresIdentifier(declaration.id, name)), Match.when(isTSEnumDeclaration, (declaration) => declaration.id.name === name), Match.when(isTSModuleDeclaration, (declaration) => moduleDeclarationDeclares(declaration, name)), Match.when(isTSImportEqualsDeclaration, (declaration) => importEqualsDeclares(declaration, name)), Match.when(isExportNamedDeclaration, (declaration) => Option.exists(Option.fromNullishOr(declaration.declaration), (inner) => statementDeclares(inner, name))), Match.orElse(() => false));
3857
+ const statementBindingsDeclare = (declaration, name) => declaration.declarations.some((declarator) => patternDeclaresOr(declarator.id, name));
3858
+ const variableDeclarationDeclaresOr = (node, name) => Option.match(Option.fromNullishOr(node), {
3859
+ onNone: () => false,
3860
+ onSome: (present) => Match.value(present).pipe(Match.when(isVariableDeclaration, (declaration) => statementBindingsDeclare(declaration, name)), Match.orElse(() => false))
3861
+ });
3862
+ const moduleDeclarationDeclares = (declaration, name) => Bool.and(declaration.body !== null, Option.exists(identifierText(declaration.id), (text) => text === name));
3863
+ const importEqualsDeclares = (declaration, name) => Bool.and(declaration.importKind !== "type", declaration.id.name === name);
3864
+ const declaresIdentifier = (id, name) => Option.exists(identifierText(id), (text) => text === name);
3865
+ const patternDeclaresOr = (pattern, name) => Option.match(Option.fromNullishOr(pattern), {
3866
+ onNone: () => false,
3867
+ onSome: (present) => Match.value(present).pipe(Match.when(isBindingIdentifierPattern, (binding) => binding.name === name), Match.when(isObjectBindingPattern, (object) => object.properties.some((property) => propertyDeclares(property, name))), Match.when(isArrayBindingPattern, (array) => array.elements.some((element) => patternDeclaresOr(element, name))), Match.when(isAssignmentBindingPattern, (assignment) => patternDeclaresOr(assignment.left, name)), Match.when(isBindingRestElement, (rest) => patternDeclaresOr(rest.argument, name)), Match.orElse(() => false))
3868
+ });
3869
+ const propertyDeclares = (property, name) => Match.value(property).pipe(Match.when(isBindingProperty, (binding) => patternDeclaresOr(binding.value, name)), Match.when(isBindingRestElement, (rest) => patternDeclaresOr(rest.argument, name)), Match.orElse(() => false));
3870
+ const isBindingIdentifierPattern = (candidate) => nodeType(candidate) === "Identifier";
3871
+ const isObjectBindingPattern = (candidate) => nodeType(candidate) === "ObjectPattern";
3872
+ const isArrayBindingPattern = (candidate) => nodeType(candidate) === "ArrayPattern";
3873
+ const isAssignmentBindingPattern = (candidate) => nodeType(candidate) === "AssignmentPattern";
3874
+ const isBindingRestElement = (candidate) => nodeType(candidate) === "RestElement";
3875
+ const isBindingProperty = (candidate) => nodeType(candidate) === "Property";
3876
+ const identifierText = (node) => Option.match(Option.fromNullishOr(node), {
3877
+ onNone: () => Option.none(),
3878
+ onSome: (present) => Match.value(present).pipe(Match.when(isNamedIdentifier, (name) => Option.some(name.name)), Match.when(isStringLiteral$1, (literal) => literalValueText(literal.value)), Match.orElse(() => Option.none()))
3879
+ });
3880
+ const literalValueText = (value) => Match.value(value).pipe(Match.when(Predicate.isString, (text) => Option.some(text)), Match.orElse(() => Option.none()));
3881
+ const moduleExportName = (name) => Option.getOrElse(identifierText(name), () => "");
3882
+ const staticMemberName = (member) => Match.value(member.computed).pipe(Match.when(false, () => identifierText(member.property)), Match.orElse(() => Option.none()));
3883
+ const nodesOutsideFunctions = (root) => {
3884
+ const collected = [];
3885
+ traverse(root, { enter(path) {
3886
+ collected.push(path.node);
3887
+ pruneAtFunction(path);
3888
+ } });
3889
+ return collected;
3890
+ };
3891
+ const allNodesUnder = (root) => {
3892
+ const collected = [];
3893
+ traverse(root, { enter(path) {
3894
+ collected.push(path.node);
3895
+ } });
3896
+ return collected;
3897
+ };
3898
+ const pruneAtFunction = (path) => Option.match(onlyWhen(isFunctionLike(path.node), path), {
3899
+ onNone: () => void 0,
3900
+ onSome: (atFunction) => atFunction.skip()
3901
+ });
3902
+ const moduleCall = (module, property, args) => callExpression(memberExpression(module, identifier(property), false), args);
3903
+ const onlyWhen = (holds, value) => Match.value(holds).pipe(Match.when(true, () => Option.some(value)), Match.orElse(() => Option.none()));
3904
+ const holdsAny = (conditions) => conditions.some((condition) => condition);
3905
+ const sameNode = (first, second) => first === second;
3906
+ const FUNCTION_KINDS = {
3907
+ ArrowFunctionExpression: true,
3908
+ FunctionDeclaration: true,
3909
+ FunctionExpression: true
3910
+ };
3911
+ const CLASS_KINDS = {
3912
+ ClassDeclaration: true,
3913
+ ClassExpression: true
3914
+ };
3915
+ const isFunctionLike = (candidate) => FUNCTION_KINDS[nodeType(candidate) ?? ""] === true;
3916
+ const isClassLike = (candidate) => CLASS_KINDS[nodeType(candidate) ?? ""] === true;
3917
+ const isProgram = (candidate) => nodeType(candidate) === "Program";
3918
+ const isBlockStatement = (candidate) => nodeType(candidate) === "BlockStatement";
3919
+ const isStaticBlock = (candidate) => nodeType(candidate) === "StaticBlock";
3920
+ const isTSModuleBlock = (candidate) => nodeType(candidate) === "TSModuleBlock";
3921
+ const isSwitchStatement = (candidate) => nodeType(candidate) === "SwitchStatement";
3922
+ const isCatchClause = (candidate) => nodeType(candidate) === "CatchClause";
3923
+ const isForStatement = (candidate) => nodeType(candidate) === "ForStatement";
3924
+ const isForInStatement = (candidate) => nodeType(candidate) === "ForInStatement";
3925
+ const isForOfStatement = (candidate) => nodeType(candidate) === "ForOfStatement";
3926
+ const isVariableDeclaration = (candidate) => nodeType(candidate) === "VariableDeclaration";
3927
+ const isFunctionDeclaration = (candidate) => nodeType(candidate) === "FunctionDeclaration";
3928
+ const isTSEnumDeclaration = (candidate) => nodeType(candidate) === "TSEnumDeclaration";
3929
+ const isTSModuleDeclaration = (candidate) => nodeType(candidate) === "TSModuleDeclaration";
3930
+ const isTSImportEqualsDeclaration = (candidate) => nodeType(candidate) === "TSImportEqualsDeclaration";
3931
+ const isExportNamedDeclaration = (candidate) => nodeType(candidate) === "ExportNamedDeclaration";
3932
+ const isImportDeclaration$1 = (candidate) => nodeType(candidate) === "ImportDeclaration";
3933
+ const isNamedImportSpecifier = (candidate) => nodeType(candidate) === "ImportSpecifier";
3934
+ const isNamespaceImportSpecifier = (candidate) => nodeType(candidate) === "ImportNamespaceSpecifier";
3935
+ const isCallExpression$1 = (candidate) => nodeType(candidate) === "CallExpression";
3936
+ const isMemberExpression = (candidate) => nodeType(candidate) === "MemberExpression";
3937
+ const isIdentifier$1 = (candidate) => nodeType(candidate) === "Identifier";
3938
+ const isNamedIdentifier = (candidate) => nodeType(candidate) === "Identifier";
3939
+ const isThisExpression = (candidate) => nodeType(candidate) === "ThisExpression";
3940
+ const isLiteral = (candidate) => nodeType(candidate) === "Literal";
3941
+ const isStringLiteral$1 = (candidate) => nodeType(candidate) === "Literal";
3942
+ const isSpreadElement = (argument) => nodeType(argument) === "SpreadElement";
3943
+ const isSpreadFree = (argument) => nodeType(argument) !== "SpreadElement";
3944
+ //#endregion
3945
+ //#region src/AtomicUpdateSplit.ts
3946
+ const NO_MUTANTS$3 = [];
3947
+ const ATOMIC_UPDATE_SPLIT_MODULES = ["Ref", "SynchronizedRef"];
3948
+ const ATOMIC_UPDATE_SPLIT_OPERATIONS = [
3949
+ "modify",
3950
+ "modifySome",
3951
+ "update",
3952
+ "updateSome",
3953
+ "updateAndGet",
3954
+ "getAndUpdate"
3955
+ ];
3956
+ const atomicUpdateSplitMutator = (node, context) => Option.match(resolveEffectCall(node, context), {
3957
+ onNone: () => NO_MUTANTS$3,
3958
+ onSome: (call) => Option.toArray(atomicUpdateSplitReplacement(call))
3959
+ });
3960
+ const atomicUpdateSplitReplacement = (call) => Option.flatMap(splitOperation(call), (operation) => Option.some(replacementExpression$1(operation)));
3961
+ const splitOperation = (call) => Match.value(call).pipe(Match.when(isAtomicUpdateSplitOperation, (covered) => splitOperationOf(covered)), Match.orElse(() => Option.none()));
3962
+ const isAtomicUpdateSplitOperation = (call) => Bool.and(included(ATOMIC_UPDATE_SPLIT_MODULES, call.module), included(ATOMIC_UPDATE_SPLIT_OPERATIONS, call.exportName));
3963
+ const splitOperationOf = (call) => Option.flatMap(moduleBindings(call), (bindings) => Option.flatMap(splitArguments$1(call), (args) => Option.some({
3964
+ effectModule: bindings.effectModule,
3965
+ operationModule: bindings.operationModule,
3966
+ exportName: call.exportName,
3967
+ ref: args.ref,
3968
+ f: args.f,
3969
+ binders: freshBinders$1(takenNames$2(call, bindings, args.f))
3970
+ })));
3971
+ const splitArguments$1 = (call) => Match.value(call.form).pipe(Match.when((form) => form === "data-first", () => dataFirstArguments$1(call)), Match.when((form) => form === "piped-reference", () => Option.none()), Match.orElse(() => dataLastArguments(call)));
3972
+ const dataFirstArguments$1 = (call) => Option.flatMap(Arr.get(call.args, 0), (ref) => Option.flatMap(Arr.get(call.args, 1), (f) => onlyWhen(Bool.and(isMovableArgument(ref), isMovableArgument(f)), {
3973
+ ref: Option.some(cloneNode(ref)),
3974
+ f: cloneNode(f)
3975
+ })));
3976
+ const dataLastArguments = (call) => Option.flatMap(Arr.get(call.args, 0), (f) => onlyWhen(isMovableArgument(f), {
3977
+ ref: Option.none(),
3978
+ f: cloneNode(f)
3979
+ }));
3980
+ const moduleBindings = (call) => Option.flatMap(call.moduleAccess("Effect"), (effectModule) => Option.map(call.moduleAccess(call.module), (operationModule) => ({
3981
+ effectModule,
3982
+ operationModule
3983
+ })));
3984
+ const takenNames$2 = (call, bindings, f) => /* @__PURE__ */ new Set([
3985
+ ...call.args.flatMap((argument) => identifiersIn(argument)),
3986
+ ...identifiersIn(f),
3987
+ ...identifiersIn(bindings.effectModule),
3988
+ ...identifiersIn(bindings.operationModule)
3989
+ ]);
3990
+ const freshBinders$1 = (taken) => ({
3991
+ self: freshIdentifier("self", taken),
3992
+ s: freshIdentifier("s", taken),
3993
+ snap: freshIdentifier("snap", taken),
3994
+ b: freshIdentifier("b", taken),
3995
+ a: freshIdentifier("a", taken)
3996
+ });
3997
+ const replacementExpression$1 = (operation) => Option.match(operation.ref, {
3998
+ onNone: () => arrowFunctionExpression([identifier(operation.binders.self)], splitBody(operation)),
3999
+ onSome: (ref) => moduleCall(operation.effectModule, "flatMap", [moduleCall(operation.effectModule, "succeed", [ref]), arrowFunctionExpression([identifier(operation.binders.self)], splitBody(operation))])
4000
+ });
4001
+ const splitBody = (operation) => moduleCall(operation.effectModule, "flatMap", [moduleCall(operation.operationModule, "get", [identifier(operation.binders.self)]), arrowFunctionExpression([identifier(operation.binders.s)], moduleCall(operation.effectModule, "flatMap", [moduleCall(operation.operationModule, "make", [identifier(operation.binders.s)]), arrowFunctionExpression([identifier(operation.binders.snap)], moduleCall(operation.effectModule, "flatMap", [moduleCall(operation.operationModule, operation.exportName, [identifier(operation.binders.snap), operation.f]), arrowFunctionExpression([identifier(operation.binders.b)], moduleCall(operation.effectModule, "flatMap", [memberExpression(operation.effectModule, identifier("yieldNow"), false), arrowFunctionExpression([], moduleCall(operation.effectModule, "flatMap", [moduleCall(operation.operationModule, "get", [identifier(operation.binders.snap)]), arrowFunctionExpression([identifier(operation.binders.a)], moduleCall(operation.effectModule, "as", [moduleCall(operation.operationModule, "set", [identifier(operation.binders.self), identifier(operation.binders.a)]), identifier(operation.binders.b)]))]))]))]))]))]);
4002
+ const included = (values, value) => values.includes(value);
4003
+ //#endregion
4004
+ //#region src/FinalizerEscape.ts
4005
+ const NO_MUTANTS$2 = [];
4006
+ const COVERED_MODULE = "Effect";
4007
+ const TAG = "_tag";
4008
+ const SUCCESS_TAG = "Success";
4009
+ const INTERRUPT_TAG = "Interrupt";
4010
+ const finalizerEscapeMutator = (node, context) => Option.match(resolveEffectCall(node, context), {
4011
+ onNone: () => NO_MUTANTS$2,
4012
+ onSome: (call) => Option.toArray(replacementOf(call))
4013
+ });
4014
+ const replacementOf = (call) => Option.flatMap(onlyWhen(call.module === COVERED_MODULE, call), (covered) => Option.flatMap(covered.moduleAccess(COVERED_MODULE), (effect) => Option.flatMap(coveredForm(covered), (form) => Option.flatMap(splitArguments(covered, form), (split) => Option.flatMap(shapeOf(covered.exportName, split.rest), (shape) => Option.some(replacementExpression(call, effect, form, split, shape)))))));
4015
+ const replacementExpression = (call, effect, form, split, shape) => {
4016
+ const binders = freshBinders(takenNames$1(call, effect));
4017
+ const plan = {
4018
+ effect,
4019
+ self: selfExpression(split, binders),
4020
+ shape,
4021
+ binders
4022
+ };
4023
+ return placedBody(form, bodyExpression(plan), binders);
4024
+ };
4025
+ const coveredForm = (call) => Match.value(call.form).pipe(Match.when("data-first", () => Option.some("data-first")), Match.when("data-last-pipe-arg", () => Option.some("data-last")), Match.when("data-last-pipe-method", () => Option.some("data-last")), Match.when("data-last-immediate", () => Option.some("data-last")), Match.orElse(() => Option.none()));
4026
+ const splitArguments = (call, form) => Match.value(form).pipe(Match.when("data-first", () => dataFirstArguments(call)), Match.orElse(() => Option.some({
4027
+ self: Option.none(),
4028
+ rest: [...call.args]
4029
+ })));
4030
+ const dataFirstArguments = (call) => Option.map(Arr.get(call.args, 0), (self) => ({
4031
+ self: Option.some(cloneNode(self)),
4032
+ rest: call.args.slice(1)
4033
+ }));
4034
+ const selfExpression = (split, binders) => Option.getOrElse(split.self, () => identifier(binders.self));
4035
+ const shapeOf = (exportName, rest) => Match.value(exportName).pipe(Match.when("ensuring", () => finalizerShape(rest)), Match.when("onExit", () => exitShape(rest)), Match.when("onError", () => handlerShape(rest)), Match.when("onInterrupt", () => droppedHandlerShape(rest)), Match.when("acquireRelease", () => releaseShape(rest)), Match.when("acquireUseRelease", () => useReleaseShape(rest)), Match.orElse(() => Option.none()));
4036
+ const finalizerShape = (rest) => Option.flatMap(Arr.get(rest, 0), (fin) => onlyWhen(isMovableArgument(fin), {
4037
+ kind: "ensuring",
4038
+ fin
4039
+ }));
4040
+ const exitShape = (rest) => Option.map(Arr.get(rest, 0), (f) => ({
4041
+ kind: "onExit",
4042
+ f
4043
+ }));
4044
+ const handlerShape = (rest) => Option.map(Arr.get(rest, 0), (h) => ({
4045
+ kind: "onError",
4046
+ h
4047
+ }));
4048
+ const droppedHandlerShape = (rest) => Option.flatMap(Arr.get(rest, 0), (h) => onlyWhen(isDroppableArgument(h), {
4049
+ kind: "onInterrupt",
4050
+ h
4051
+ }));
4052
+ const releaseShape = (rest) => Option.flatMap(Arr.get(rest, 0), (rel) => Option.flatMap(droppableOptionAt(rest, 1), (optionsDroppable) => onlyWhen(Bool.and(isMovableArgument(rel), optionsDroppable), {
4053
+ kind: "acquireRelease",
4054
+ rel
4055
+ })));
4056
+ const useReleaseShape = (rest) => Option.flatMap(Arr.get(rest, 1), (rel) => Option.flatMap(Arr.get(rest, 0), (use) => Option.flatMap(releaseArityOf(rel), (releaseArity) => onlyWhen(isMovableArgument(rel), {
4057
+ kind: "acquireUseRelease",
4058
+ use,
4059
+ rel,
4060
+ releaseArity
4061
+ }))));
4062
+ const droppableOptionAt = (rest, index) => Option.match(Arr.get(rest, index), {
4063
+ onNone: () => Option.some(true),
4064
+ onSome: (argument) => onlyWhen(isDroppableArgument(argument), true)
4065
+ });
4066
+ const bodyExpression = (plan) => Match.value(plan.shape).pipe(Match.when({ kind: "ensuring" }, (shape) => moduleCall(plan.effect, "onExitIf", [
4067
+ plan.self,
4068
+ notInterruptedPredicate(plan.binders),
4069
+ arrowFunctionExpression([], shape.fin)
4070
+ ])), Match.when({ kind: "onExit" }, (shape) => moduleCall(plan.effect, "onExitIf", [
4071
+ plan.self,
4072
+ notInterruptedPredicate(plan.binders),
4073
+ shape.f
4074
+ ])), Match.when({ kind: "onError" }, (shape) => moduleCall(plan.effect, "onErrorIf", [
4075
+ plan.self,
4076
+ noInterruptPredicate(plan.binders),
4077
+ shape.h
4078
+ ])), Match.when({ kind: "onInterrupt" }, () => plan.self), Match.when({ kind: "acquireRelease" }, (shape) => releaseBody(plan, shape)), Match.when({ kind: "acquireUseRelease" }, (shape) => useReleaseBody(plan, shape)), Match.exhaustive);
4079
+ const releaseBody = (plan, shape) => moduleCall(plan.effect, "flatMap", [moduleCall(plan.effect, "interruptible", [plan.self]), arrowFunctionExpression([identifier(plan.binders.a)], moduleCall(plan.effect, "acquireRelease", [moduleCall(plan.effect, "succeed", [identifier(plan.binders.a)]), shape.rel]))]);
4080
+ const useReleaseBody = (plan, shape) => moduleCall(plan.effect, "acquireUseRelease", [
4081
+ plan.self,
4082
+ shape.use,
4083
+ arrowFunctionExpression([identifier(plan.binders.a), identifier(plan.binders.exit)], conditionalExpression(notInterruptedTest(plan.binders), appliedRelease(shape.rel, plan.binders, shape.releaseArity), memberExpression(plan.effect, identifier("void"), false)))
4084
+ ]);
4085
+ const appliedRelease = (rel, binders, releaseArity) => callExpression(rel, Arr.take([identifier(binders.a), identifier(binders.exit)], releaseArity));
4086
+ const releaseArityOf = (rel) => Match.value(rel).pipe(Match.when(isInlineFunction, plainParameterArity), Match.orElse(() => Option.none()));
4087
+ const isInlineFunction = (candidate) => nodeType(candidate) === "ArrowFunctionExpression" || nodeType(candidate) === "FunctionExpression";
4088
+ const plainParameterArity = (fn) => onlyWhen(Bool.and(fn.params.every(isPlainParameter), fn.params.length <= 2), fn.params.length);
4089
+ const isPlainParameter = (param) => nodeType(param) === "Identifier";
4090
+ const placedBody = (form, body, binders) => Match.value(form).pipe(Match.when("data-first", () => body), Match.orElse(() => arrowFunctionExpression([identifier(binders.self)], body)));
4091
+ const notInterruptedPredicate = (binders) => arrowFunctionExpression([identifier(binders.exit)], notInterruptedTest(binders));
4092
+ const notInterruptedTest = (binders) => logicalExpression("||", tagCheck(binders.exit, SUCCESS_TAG), unaryExpression("!", interruptionSome(failureReasons(binders.exit), binders.reason)));
4093
+ const noInterruptPredicate = (binders) => arrowFunctionExpression([identifier(binders.cause)], unaryExpression("!", interruptionSome(causeReasons(binders.cause), binders.reason)));
4094
+ const tagCheck = (name, tag) => binaryExpression("===", memberExpression(identifier(name), identifier(TAG), false), stringLiteral(tag));
4095
+ const failureReasons = (exitName) => memberExpression(memberExpression(identifier(exitName), identifier("cause"), false), identifier("reasons"), false);
4096
+ const causeReasons = (causeName) => memberExpression(identifier(causeName), identifier("reasons"), false);
4097
+ const interruptionSome = (reasons, reasonName) => callExpression(memberExpression(reasons, identifier("some"), false), [arrowFunctionExpression([identifier(reasonName)], tagCheck(reasonName, INTERRUPT_TAG))]);
4098
+ const takenNames$1 = (call, effect) => /* @__PURE__ */ new Set([...call.args.flatMap((argument) => identifiersIn(argument)), ...identifiersIn(effect)]);
4099
+ const freshBinders = (taken) => ({
4100
+ self: freshIdentifier("self", taken),
4101
+ exit: freshIdentifier("exit", taken),
4102
+ cause: freshIdentifier("cause", taken),
4103
+ reason: freshIdentifier("reason", taken),
4104
+ a: freshIdentifier("a", taken)
4105
+ });
4106
+ //#endregion
4107
+ //#region src/SynchronizationRemoval.ts
4108
+ /**
4109
+ * SynchronizationRemoval — the R7 mutants: a removed lock and a removed mask.
4110
+ *
4111
+ * Four call shapes, each producing the wrapped effect alone:
4112
+ *
4113
+ * - `Semaphore.withPermits(sem, n, e)` and `Semaphore.withPermit(sem, e)` become
4114
+ * the guarded effect. The guard arguments are dropped, so they must be
4115
+ * droppable (KTD5); the effect itself is cloned so the mutant's tree shares
4116
+ * nothing with the original's.
4117
+ * - Their data-last forms become `(self) => self`, which the resolver only
4118
+ * reports where that function is contextually typed (R4, KTD9) — a piped
4119
+ * argument, a piped method argument, or the callee of an immediate call.
4120
+ * - `Effect.uninterruptible(e)` becomes `e`, and a bare `Effect.uninterruptible`
4121
+ * used as a pipe argument becomes `(self) => self` (KTD9).
4122
+ * - `Effect.uninterruptibleMask(f)` becomes `E.suspend(() => f(E.interruptible))`
4123
+ * (KTD7): `f` is called at run time, as the original calls it, and the restore
4124
+ * it receives is the identity wherever the surrounding region is interruptible.
4125
+ * This needs the Effect module object in scope (R2, KTD3).
4126
+ *
4127
+ * Resolution lives in `EffectCall.ts`; this module never inspects imports. It is
4128
+ * a pure function from a resolved call to the one mutant it implies, and yields
4129
+ * no mutant whenever the replacement would evaluate an argument twice or lean on
4130
+ * a moved argument that can yield.
4131
+ */
4132
+ const NO_MUTANTS$1 = [];
4133
+ const synchronizationRemovalMutator = (node, context) => Option.match(resolveEffectCall(node, context), {
4134
+ onNone: () => NO_MUTANTS$1,
4135
+ onSome: (call) => Option.toArray(synchronizationRemovalReplacement(call))
4136
+ });
4137
+ const synchronizationRemovalReplacement = (call) => Match.value(call).pipe(Match.when(isWithPermits, withPermitsReplacement), Match.when(isWithPermit, withPermitReplacement), Match.when(isUninterruptible, uninterruptibleReplacement), Match.when(isUninterruptibleMask, uninterruptibleMaskReplacement), Match.orElse(() => Option.none()));
4138
+ const isWithPermits = (call) => Bool.and(call.module === "Semaphore", call.exportName === "withPermits");
4139
+ const isWithPermit = (call) => Bool.and(call.module === "Semaphore", call.exportName === "withPermit");
4140
+ const isUninterruptible = (call) => Bool.and(call.module === "Effect", call.exportName === "uninterruptible");
4141
+ const isUninterruptibleMask = (call) => Bool.and(call.module === "Effect", call.exportName === "uninterruptibleMask");
4142
+ const isDataFirst = (form) => form === "data-first";
4143
+ const isPipedReference = (form) => form === "piped-reference";
4144
+ const withPermitsReplacement = (call) => Match.value(call.form).pipe(Match.when(isDataFirst, () => whenDroppable(call, [0, 1], (guarded) => argumentReplacement(guarded, 2))), Match.orElse(() => whenDroppable(call, [0, 1], identityFunctionOf)));
4145
+ const withPermitReplacement = (call) => Match.value(call.form).pipe(Match.when(isDataFirst, () => whenDroppable(call, [0], (guarded) => argumentReplacement(guarded, 1))), Match.orElse(() => whenDroppable(call, [0], identityFunctionOf)));
4146
+ const uninterruptibleReplacement = (call) => Match.value(call.form).pipe(Match.when(isPipedReference, () => Option.some(identityFunction(call))), Match.orElse(() => argumentReplacement(call, 0)));
4147
+ const uninterruptibleMaskReplacement = (call) => whenMovable(call, [0], (masked) => Option.flatMap(masked.moduleAccess("Effect"), (effectModule) => Option.map(Arr.get(masked.args, 0), (f) => maskedReplacement(effectModule, f))));
4148
+ const maskedReplacement = (effectModule, f) => moduleCall(effectModule, "suspend", [arrowFunctionExpression([], callExpression(cloneNode(f), [memberExpression(effectModule, identifier("interruptible"), false)]))]);
4149
+ const argumentReplacement = (call, index) => Option.map(Arr.get(call.args, index), (argument) => cloneNode(argument));
4150
+ const identityFunctionOf = (call) => Option.some(identityFunction(call));
4151
+ const identityFunction = (call) => {
4152
+ const name = freshIdentifier("self", takenNames(call));
4153
+ return arrowFunctionExpression([identifier(name)], identifier(name));
4154
+ };
4155
+ const takenNames = (call) => new Set(call.args.flatMap((argument) => identifiersIn(argument)));
4156
+ const whenDroppable = (call, indices, build) => Option.flatMap(onlyWhen(argumentsDroppable(call, indices), call), build);
4157
+ const whenMovable = (call, indices, build) => Option.flatMap(onlyWhen(argumentsMovable(call, indices), call), build);
4158
+ const argumentsDroppable = (call, indices) => indices.every((index) => argumentSatisfies(call, index, isDroppableArgument));
4159
+ const argumentsMovable = (call, indices) => indices.every((index) => argumentSatisfies(call, index, isMovableArgument));
4160
+ const argumentSatisfies = (call, index, predicate) => Option.exists(Arr.get(call.args, index), predicate);
3523
4161
  //#endregion
3524
4162
  //#region src/Mutator.ts
3525
4163
  /**
@@ -3809,9 +4447,10 @@ function withOperator(node, operator) {
3809
4447
  function mutantsWhen(holds, build) {
3810
4448
  return Match.value(holds).pipe(Match.when(true, build), Match.orElse(() => NO_MUTANTS));
3811
4449
  }
3812
- /** A named property of a node that may or may not carry it. */
4450
+ const hasPropertyIn = (node, key) => key in node;
4451
+ const readPropertyOf = (node, key) => hasPropertyIn(node, key) ? node[key] : void 0;
3813
4452
  function propertyOf(node, key) {
3814
- return Match.value(node).pipe(Match.when((candidate) => Predicate.hasProperty(candidate, key), (host) => host[key]), Match.orElse(() => void 0));
4453
+ return Predicate.isObject(node) ? readPropertyOf(node, key) : void 0;
3815
4454
  }
3816
4455
  function isIdentifier(node) {
3817
4456
  return nodeType(node) === "Identifier";
@@ -4355,8 +4994,11 @@ function isUpdateExpression(node) {
4355
4994
  *
4356
4995
  * Naming each mutator here costs one line when a mutator is added and makes
4357
4996
  * that line a compile-checked import instead of a runtime effect.
4997
+ *
4998
+ * `optInMutators` below follows the same hand-written rule, for the same
4999
+ * reason: a mutator exists for a run only when a human named it here.
4358
5000
  */
4359
- const allMutators = Object.freeze({
5001
+ const defaultMutators = Object.freeze({
4360
5002
  ArithmeticOperator: arithmeticOperatorMutator,
4361
5003
  ArrayDeclaration: arrayDeclarationMutator,
4362
5004
  ArrowFunction: arrowFunctionMutator,
@@ -4374,6 +5016,28 @@ const allMutators = Object.freeze({
4374
5016
  UnaryOperator: unaryOperatorMutator,
4375
5017
  UpdateOperator: updateOperatorMutator
4376
5018
  });
5019
+ const optInMutators = Object.freeze({
5020
+ AtomicUpdateSplit: atomicUpdateSplitMutator,
5021
+ SynchronizationRemoval: synchronizationRemovalMutator,
5022
+ FinalizerEscape: finalizerEscapeMutator
5023
+ });
5024
+ /**
5025
+ * The entries a run applies, and the names its directives may reference.
5026
+ *
5027
+ * Naming is additive, never a whitelist: `optInMutations` adds entries on top
5028
+ * of the defaults and removes none. Selection walks the registry's declared
5029
+ * order, not the order the run listed its names in, so a mutant's identity
5030
+ * never depends on how a config happened to spell the list; a name listed
5031
+ * twice selects its entry once. An unknown name selects nothing here —
5032
+ * `instrument` refuses it before any file is parsed, because a typo that
5033
+ * silently enables nothing removes mutants and raises the score.
5034
+ *
5035
+ * Pure: a registry and a run's names in, entries and names out.
5036
+ */
5037
+ const selectMutators = (registry, optInMutations) => ({
5038
+ active: [...Object.entries(registry.defaults), ...Object.entries(registry.optIn).filter(([name]) => optInMutations.includes(name))],
5039
+ known: [...Object.keys(registry.defaults), ...Object.keys(registry.optIn)]
5040
+ });
4377
5041
  S.TaggedError("@systemfsoftware/stryker-js-instrumenter/Transformer.schema/PlacementFailed")("PlacementFailed", {
4378
5042
  fileName: S.String,
4379
5043
  placerName: S.String,
@@ -4389,6 +5053,10 @@ S.TaggedError("@systemfsoftware/stryker-js-instrumenter/Transformer.schema/Trans
4389
5053
  const STRYKER_NAMESPACE_HELPER = "stryNS_9fa48";
4390
5054
  const COVER_MUTANT_HELPER = "stryCov_9fa48";
4391
5055
  const IS_MUTANT_ACTIVE_HELPER = "stryMutAct_9fa48";
5056
+ const DEFAULT_MUTATOR_REGISTRY = {
5057
+ defaults: defaultMutators,
5058
+ optIn: optInMutators
5059
+ };
4392
5060
  function createMutantCollector() {
4393
5061
  return [];
4394
5062
  }
@@ -4424,11 +5092,11 @@ function directiveApplies(directive, lowerMutatorName, line) {
4424
5092
  const NO_COMMENTS = [];
4425
5093
  const PARSE_FAILURE = "Stryker directive without directive type or mutators";
4426
5094
  const MISSING_LOCATION = "Comment without location";
4427
- function processStrykerDirectives(rule, node, allMutatorNames, originFileName) {
5095
+ function processStrykerDirectives(rule, node, knownMutatorNames, originFileName, nodeLine) {
4428
5096
  const directives = attachedComments(node).map(parseStrykerDirective).flatMap(Option.toArray);
4429
- const warnings = directives.flatMap((directive) => mutatorWarnings(directive, allMutatorNames, originFileName));
5097
+ const warnings = directives.flatMap((directive) => mutatorWarnings(directive, knownMutatorNames, originFileName));
4430
5098
  return {
4431
- rule: directives.reduce(applyStrykerDirective, rule),
5099
+ rule: directives.map((directive) => nextLineOn(directive, nodeLine)).reduce(applyStrykerDirective, rule),
4432
5100
  warnings
4433
5101
  };
4434
5102
  }
@@ -4457,36 +5125,39 @@ function strykerDirective(match, loc) {
4457
5125
  function matchGroup(match, group) {
4458
5126
  return Option.getOrThrowWith(Option.fromNullishOr(match[group]), () => /* @__PURE__ */ new Error(PARSE_FAILURE));
4459
5127
  }
4460
- function applyStrykerDirective(rule, directive) {
4461
- return Match.value(directive.type).pipe(Match.when("disable", () => ignoreRuleFor(rule, directive)), Match.when("restore", () => restoreRuleFor(rule, directive)), Match.orElse(() => rule));
5128
+ const nextLineOn = (directive, nodeLine) => ({
5129
+ directive,
5130
+ nodeLine
5131
+ });
5132
+ function applyStrykerDirective(rule, directed) {
5133
+ return Match.value(directed.directive.type).pipe(Match.when("disable", () => ignoreRuleFor(rule, directed)), Match.when("restore", () => restoreRuleFor(rule, directed)), Match.orElse(() => rule));
4462
5134
  }
4463
- function ignoreRuleFor(rule, directive) {
5135
+ function ignoreRuleFor(rule, directed) {
4464
5136
  return {
4465
5137
  kind: "Ignore",
4466
- mutatorNames: directive.mutatorNames.map((mutatorName) => mutatorName.toLowerCase()),
4467
- line: directiveLine(directive),
4468
- ignoreReason: directive.reason,
5138
+ mutatorNames: directed.directive.mutatorNames.map((mutatorName) => mutatorName.toLowerCase()),
5139
+ line: directiveLine(directed),
5140
+ ignoreReason: directed.directive.reason,
4469
5141
  previous: rule
4470
5142
  };
4471
5143
  }
4472
- function restoreRuleFor(rule, directive) {
5144
+ function restoreRuleFor(rule, directed) {
4473
5145
  return {
4474
5146
  kind: "Restore",
4475
- mutatorNames: directive.mutatorNames.map((mutatorName) => mutatorName.toLowerCase()),
4476
- line: directiveLine(directive),
5147
+ mutatorNames: directed.directive.mutatorNames.map((mutatorName) => mutatorName.toLowerCase()),
5148
+ line: directiveLine(directed),
4477
5149
  previous: rule
4478
5150
  };
4479
5151
  }
4480
- /** `next-line` directives carry the line they were written on; a block directive carries none. */
4481
- function directiveLine(directive) {
4482
- return Match.value(directive.scope).pipe(Match.when("next-line", () => commentLocation(directive.loc).start.line), Match.orElse(() => void 0));
5152
+ function directiveLine(directed) {
5153
+ return Match.value(directed.directive.scope).pipe(Match.when("next-line", () => directed.nodeLine), Match.orElse(() => void 0));
4483
5154
  }
4484
5155
  function commentLocation(loc) {
4485
5156
  return Option.getOrThrowWith(Option.fromNullishOr(loc), () => /* @__PURE__ */ new Error(MISSING_LOCATION));
4486
5157
  }
4487
- /** Warnings for the directive's mutator names that the configured mutators do not know. */
4488
- function mutatorWarnings(directive, allMutatorNames, originFileName) {
4489
- return directive.mutatorNames.filter((mutatorName) => mutatorName !== WILDCARD).filter((mutatorName) => !allMutatorNames.includes(mutatorName.toLowerCase())).map((mutatorName) => mutatorWarning(directive, mutatorName, originFileName));
5158
+ /** Warnings for the directive's mutator names that no registered mutator answers to, selected or not. */
5159
+ function mutatorWarnings(directive, knownMutatorNames, originFileName) {
5160
+ return directive.mutatorNames.filter((mutatorName) => mutatorName !== WILDCARD).filter((mutatorName) => !knownMutatorNames.includes(mutatorName.toLowerCase())).map((mutatorName) => mutatorWarning(directive, mutatorName, originFileName));
4490
5161
  }
4491
5162
  function mutatorWarning(directive, mutatorName, originFileName) {
4492
5163
  const loc = commentLocation(directive.loc);
@@ -4877,7 +5548,8 @@ function frozenContainer(value) {
4877
5548
  freezableChildren(object).forEach((child) => {
4878
5549
  deepFreeze(child);
4879
5550
  });
4880
- return Object.freeze(object);
5551
+ Object.freeze(object);
5552
+ return value;
4881
5553
  });
4882
5554
  }
4883
5555
  function freezableChildren(value) {
@@ -4971,19 +5643,21 @@ const emptyAppliedMutants = () => /* @__PURE__ */ new Map();
4971
5643
  function isMutateRangeList(value) {
4972
5644
  return Array.isArray(value);
4973
5645
  }
4974
- const transformScript = ({ root, originFileName, rawContent, offset, comments }, mutantCollector, { options, mutateDescription, basePath }, mutators, mutantPlacers) => {
5646
+ const transformScript = ({ root, originFileName, rawContent, offset, comments }, mutantCollector, { options, mutateDescription, basePath }, registry, mutantPlacers) => {
4975
5647
  const placementMap = /* @__PURE__ */ new Map();
4976
5648
  return Effect.gen(function* () {
4977
5649
  const lineTable = buildLineTable(rawContent);
4978
5650
  attachComments(root, comments, lineTable);
4979
5651
  let directiveRule = rootRule;
4980
- const mutatorEntries = Object.entries(Option.getOrElse(Option.fromNullishOr(mutators), () => allMutators));
5652
+ const mutatorRegistry = Option.getOrElse(Option.fromNullishOr(registry), () => DEFAULT_MUTATOR_REGISTRY);
5653
+ const selection = selectMutators(mutatorRegistry, options.optInMutations);
5654
+ const mutatorEntries = selection.active;
4981
5655
  const placers = Option.getOrElse(Option.fromNullishOr(mutantPlacers), () => allMutantPlacers);
4982
- const allMutatorNames = mutatorEntries.map(([name]) => name.toLowerCase());
5656
+ const knownMutatorNames = selection.known.map((name) => name.toLowerCase());
4983
5657
  const warnings = [];
4984
5658
  traverse(root, {
4985
5659
  enter(path) {
4986
- const result = processStrykerDirectives(directiveRule, path.node, allMutatorNames, originFileName);
5660
+ const result = processStrykerDirectives(directiveRule, path.node, knownMutatorNames, originFileName, nodeStartLine(path.node));
4987
5661
  directiveRule = result.rule;
4988
5662
  warnings.push(...result.warnings);
4989
5663
  visitNode(path);
@@ -4993,6 +5667,12 @@ const transformScript = ({ root, originFileName, rawContent, offset, comments },
4993
5667
  if (hasAppliedMutants(placement)) applyPlacement(path, placement);
4994
5668
  }
4995
5669
  });
5670
+ function nodeStartLine(node) {
5671
+ return Option.match(Option.fromNullishOr(spanOf(node)), {
5672
+ onNone: () => 0,
5673
+ onSome: (span) => positionFromLineTable(span.start, lineTable).line
5674
+ });
5675
+ }
4996
5676
  yield* placeHeaderIfNeeded(mutantCollector, originFileName, options, root);
4997
5677
  return warnings;
4998
5678
  function visitNode(path) {
@@ -5266,9 +5946,26 @@ function toOneBasedLineNumber(range) {
5266
5946
  function isIgnorer(value) {
5267
5947
  return Predicate.isObject(value) && typeof value["shouldIgnore"] === "function";
5268
5948
  }
5949
+ const EMPTY_MUTATION_NAMES = [];
5950
+ const KNOWN_OPT_IN_MUTATIONS = Object.keys(optInMutators);
5951
+ const requestedOptInMutations = (options) => Option.getOrElse(Option.fromNullishOr(options.optInMutations), () => EMPTY_MUTATION_NAMES);
5952
+ const unknownOptInMutations = (requested) => requested.filter((name) => !KNOWN_OPT_IN_MUTATIONS.includes(name));
5953
+ const listNames = (names) => Option.match(Option.fromNullishOr(names.at(0)), {
5954
+ onNone: () => "none",
5955
+ onSome: () => names.map((name) => `'${name}'`).join(", ")
5956
+ });
5957
+ const unknownOptInMutationsError = (requested) => Option.map(Option.fromNullishOr(unknownOptInMutations(requested).at(0)), () => InstrumentError.make({
5958
+ message: `Unknown opt-in mutations: ${listNames(unknownOptInMutations(requested))}. Known opt-in mutations: ${listNames(KNOWN_OPT_IN_MUTATIONS)}.`,
5959
+ cause: void 0
5960
+ }));
5961
+ const refuseUnknownOptInMutations = (requested) => Option.match(unknownOptInMutationsError(requested), {
5962
+ onNone: () => Effect.void,
5963
+ onSome: (error) => Effect.fail(error)
5964
+ });
5269
5965
  function toTransformerOptions(options) {
5270
5966
  const base = {
5271
5967
  excludedMutations: [...options.excludedMutations],
5968
+ optInMutations: [...requestedOptInMutations(options)],
5272
5969
  ignorers: options.ignorers.filter(isIgnorer)
5273
5970
  };
5274
5971
  if (options.noHeader !== void 0) return {
@@ -5290,6 +5987,7 @@ function printedFile(file, ast) {
5290
5987
  }];
5291
5988
  }
5292
5989
  const instrument = (files, options, basePath) => Effect.gen(function* () {
5990
+ yield* refuseUnknownOptInMutations(requestedOptInMutations(options));
5293
5991
  const schemaFiles = files.map((file) => ({
5294
5992
  name: file.name,
5295
5993
  content: file.content,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@systemfsoftware/stryker-js-instrumenter",
3
- "version": "8.0.1",
3
+ "version": "8.1.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/systemfsoftware/stryker-js-effect.git",
@@ -31,11 +31,12 @@
31
31
  "oxc-walker": "^1"
32
32
  },
33
33
  "devDependencies": {
34
+ "@effect/platform-node": "4.0.0-rc.116",
34
35
  "@effect/tsgo": "^0.45.0",
35
36
  "@microsoft/api-extractor": "^7",
36
37
  "@systemfsoftware/arethetypeswrong-cli": "^4.2.0",
37
- "@systemfsoftware/effect-gherkin-spec": "^4.2.1",
38
- "@systemfsoftware/oxlint-config-recommended": "^1.0.0",
38
+ "@systemfsoftware/effect-gherkin-spec": "^4.2.2",
39
+ "@systemfsoftware/oxlint-config-recommended": "^1.1.0",
39
40
  "@systemfsoftware/tsconfig": "^1.3.6",
40
41
  "@types/node": "^24",
41
42
  "oxlint": "^1",
@@ -44,8 +45,8 @@
44
45
  "tsdown": "^0.23.0",
45
46
  "typescript": "^7",
46
47
  "vitest": "^5",
47
- "@systemfsoftware/stryker-ignorer-interface": "^0.1.1",
48
48
  "@systemfsoftware/tsdown-config": "^0.1.0",
49
+ "@systemfsoftware/stryker-ignorer-interface": "^0.1.1",
49
50
  "@systemfsoftware/vitest-config": "^0.1.0"
50
51
  },
51
52
  "engines": {