@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 +16 -0
- package/README.md +18 -0
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +798 -100
- package/package.json +5 -4
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
717
|
-
return isPlainRecord(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
|
|
723
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
1003
|
-
|
|
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
|
|
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
|
|
1038
|
+
return isTemplateExpressionTag(node) ? rangedExpressionOf(node["expression"]) : void 0;
|
|
1017
1039
|
}
|
|
1018
1040
|
function rangedExpressionOf(payload) {
|
|
1019
|
-
|
|
1020
|
-
start:
|
|
1021
|
-
end:
|
|
1041
|
+
if (isRangedBaseNode(payload)) return {
|
|
1042
|
+
start: payload.start,
|
|
1043
|
+
end: payload.end,
|
|
1022
1044
|
isExpression: true
|
|
1023
|
-
}
|
|
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 =
|
|
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
|
-
|
|
1113
|
-
|
|
1114
|
-
}));
|
|
1135
|
+
if (!hasHtmlField(ast)) throw new Error("Svelte AST without html");
|
|
1136
|
+
return ast["html"];
|
|
1115
1137
|
}
|
|
1116
1138
|
function instanceScriptRange(ast) {
|
|
1117
|
-
|
|
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
|
-
|
|
1121
|
-
start:
|
|
1122
|
-
end:
|
|
1143
|
+
if (isRange(content)) return {
|
|
1144
|
+
start: content.start,
|
|
1145
|
+
end: content.end,
|
|
1123
1146
|
isExpression: false
|
|
1124
|
-
}
|
|
1125
|
-
|
|
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
|
-
|
|
1133
|
-
|
|
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
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
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
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
};
|
|
3402
|
-
const
|
|
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
|
-
|
|
3471
|
-
return
|
|
3472
|
-
}
|
|
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) =>
|
|
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
|
|
3507
|
-
const
|
|
3508
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
4461
|
-
|
|
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,
|
|
5135
|
+
function ignoreRuleFor(rule, directed) {
|
|
4464
5136
|
return {
|
|
4465
5137
|
kind: "Ignore",
|
|
4466
|
-
mutatorNames: directive.mutatorNames.map((mutatorName) => mutatorName.toLowerCase()),
|
|
4467
|
-
line: directiveLine(
|
|
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,
|
|
5144
|
+
function restoreRuleFor(rule, directed) {
|
|
4473
5145
|
return {
|
|
4474
5146
|
kind: "Restore",
|
|
4475
|
-
mutatorNames: directive.mutatorNames.map((mutatorName) => mutatorName.toLowerCase()),
|
|
4476
|
-
line: directiveLine(
|
|
5147
|
+
mutatorNames: directed.directive.mutatorNames.map((mutatorName) => mutatorName.toLowerCase()),
|
|
5148
|
+
line: directiveLine(directed),
|
|
4477
5149
|
previous: rule
|
|
4478
5150
|
};
|
|
4479
5151
|
}
|
|
4480
|
-
|
|
4481
|
-
|
|
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
|
|
4488
|
-
function mutatorWarnings(directive,
|
|
4489
|
-
return directive.mutatorNames.filter((mutatorName) => mutatorName !== WILDCARD).filter((mutatorName) => !
|
|
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
|
-
|
|
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 },
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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.
|
|
38
|
-
"@systemfsoftware/oxlint-config-recommended": "^1.
|
|
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": {
|