@systemfsoftware/stryker-js-instrumenter 8.0.2 → 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 +10 -0
- package/README.md +18 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +688 -27
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
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
|
+
|
|
3
13
|
## 8.0.2
|
|
4
14
|
|
|
5
15
|
### 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
|
@@ -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",
|
|
@@ -3028,14 +3046,14 @@ function isTSType(node) {
|
|
|
3028
3046
|
function isNodeOfKind(node, kind) {
|
|
3029
3047
|
return node.type === kind;
|
|
3030
3048
|
}
|
|
3031
|
-
const FUNCTION_KINDS = {
|
|
3049
|
+
const FUNCTION_KINDS$1 = {
|
|
3032
3050
|
FunctionDeclaration: true,
|
|
3033
3051
|
FunctionExpression: true,
|
|
3034
3052
|
TSDeclareFunction: true,
|
|
3035
3053
|
TSEmptyBodyFunctionExpression: true
|
|
3036
3054
|
};
|
|
3037
3055
|
function isFunctionNode(node) {
|
|
3038
|
-
return FUNCTION_KINDS[node.type] === true;
|
|
3056
|
+
return FUNCTION_KINDS$1[node.type] === true;
|
|
3039
3057
|
}
|
|
3040
3058
|
function isAssignmentPattern(node) {
|
|
3041
3059
|
return node.type === "AssignmentPattern";
|
|
@@ -3556,6 +3574,591 @@ function objectCauseTextOf(cause, depth) {
|
|
|
3556
3574
|
return isObjectType(cause) ? textWithNested(ownCauseText(cause), causeText(fieldOf(cause, "cause"), depth + 1)) : void 0;
|
|
3557
3575
|
}
|
|
3558
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);
|
|
4161
|
+
//#endregion
|
|
3559
4162
|
//#region src/Mutator.ts
|
|
3560
4163
|
/**
|
|
3561
4164
|
* Mutator — every mutation operator and its registry.
|
|
@@ -4391,8 +4994,11 @@ function isUpdateExpression(node) {
|
|
|
4391
4994
|
*
|
|
4392
4995
|
* Naming each mutator here costs one line when a mutator is added and makes
|
|
4393
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.
|
|
4394
5000
|
*/
|
|
4395
|
-
const
|
|
5001
|
+
const defaultMutators = Object.freeze({
|
|
4396
5002
|
ArithmeticOperator: arithmeticOperatorMutator,
|
|
4397
5003
|
ArrayDeclaration: arrayDeclarationMutator,
|
|
4398
5004
|
ArrowFunction: arrowFunctionMutator,
|
|
@@ -4410,6 +5016,28 @@ const allMutators = Object.freeze({
|
|
|
4410
5016
|
UnaryOperator: unaryOperatorMutator,
|
|
4411
5017
|
UpdateOperator: updateOperatorMutator
|
|
4412
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
|
+
});
|
|
4413
5041
|
S.TaggedError("@systemfsoftware/stryker-js-instrumenter/Transformer.schema/PlacementFailed")("PlacementFailed", {
|
|
4414
5042
|
fileName: S.String,
|
|
4415
5043
|
placerName: S.String,
|
|
@@ -4425,6 +5053,10 @@ S.TaggedError("@systemfsoftware/stryker-js-instrumenter/Transformer.schema/Trans
|
|
|
4425
5053
|
const STRYKER_NAMESPACE_HELPER = "stryNS_9fa48";
|
|
4426
5054
|
const COVER_MUTANT_HELPER = "stryCov_9fa48";
|
|
4427
5055
|
const IS_MUTANT_ACTIVE_HELPER = "stryMutAct_9fa48";
|
|
5056
|
+
const DEFAULT_MUTATOR_REGISTRY = {
|
|
5057
|
+
defaults: defaultMutators,
|
|
5058
|
+
optIn: optInMutators
|
|
5059
|
+
};
|
|
4428
5060
|
function createMutantCollector() {
|
|
4429
5061
|
return [];
|
|
4430
5062
|
}
|
|
@@ -4460,11 +5092,11 @@ function directiveApplies(directive, lowerMutatorName, line) {
|
|
|
4460
5092
|
const NO_COMMENTS = [];
|
|
4461
5093
|
const PARSE_FAILURE = "Stryker directive without directive type or mutators";
|
|
4462
5094
|
const MISSING_LOCATION = "Comment without location";
|
|
4463
|
-
function processStrykerDirectives(rule, node,
|
|
5095
|
+
function processStrykerDirectives(rule, node, knownMutatorNames, originFileName, nodeLine) {
|
|
4464
5096
|
const directives = attachedComments(node).map(parseStrykerDirective).flatMap(Option.toArray);
|
|
4465
|
-
const warnings = directives.flatMap((directive) => mutatorWarnings(directive,
|
|
5097
|
+
const warnings = directives.flatMap((directive) => mutatorWarnings(directive, knownMutatorNames, originFileName));
|
|
4466
5098
|
return {
|
|
4467
|
-
rule: directives.reduce(applyStrykerDirective, rule),
|
|
5099
|
+
rule: directives.map((directive) => nextLineOn(directive, nodeLine)).reduce(applyStrykerDirective, rule),
|
|
4468
5100
|
warnings
|
|
4469
5101
|
};
|
|
4470
5102
|
}
|
|
@@ -4493,36 +5125,39 @@ function strykerDirective(match, loc) {
|
|
|
4493
5125
|
function matchGroup(match, group) {
|
|
4494
5126
|
return Option.getOrThrowWith(Option.fromNullishOr(match[group]), () => /* @__PURE__ */ new Error(PARSE_FAILURE));
|
|
4495
5127
|
}
|
|
4496
|
-
|
|
4497
|
-
|
|
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));
|
|
4498
5134
|
}
|
|
4499
|
-
function ignoreRuleFor(rule,
|
|
5135
|
+
function ignoreRuleFor(rule, directed) {
|
|
4500
5136
|
return {
|
|
4501
5137
|
kind: "Ignore",
|
|
4502
|
-
mutatorNames: directive.mutatorNames.map((mutatorName) => mutatorName.toLowerCase()),
|
|
4503
|
-
line: directiveLine(
|
|
4504
|
-
ignoreReason: directive.reason,
|
|
5138
|
+
mutatorNames: directed.directive.mutatorNames.map((mutatorName) => mutatorName.toLowerCase()),
|
|
5139
|
+
line: directiveLine(directed),
|
|
5140
|
+
ignoreReason: directed.directive.reason,
|
|
4505
5141
|
previous: rule
|
|
4506
5142
|
};
|
|
4507
5143
|
}
|
|
4508
|
-
function restoreRuleFor(rule,
|
|
5144
|
+
function restoreRuleFor(rule, directed) {
|
|
4509
5145
|
return {
|
|
4510
5146
|
kind: "Restore",
|
|
4511
|
-
mutatorNames: directive.mutatorNames.map((mutatorName) => mutatorName.toLowerCase()),
|
|
4512
|
-
line: directiveLine(
|
|
5147
|
+
mutatorNames: directed.directive.mutatorNames.map((mutatorName) => mutatorName.toLowerCase()),
|
|
5148
|
+
line: directiveLine(directed),
|
|
4513
5149
|
previous: rule
|
|
4514
5150
|
};
|
|
4515
5151
|
}
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
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));
|
|
4519
5154
|
}
|
|
4520
5155
|
function commentLocation(loc) {
|
|
4521
5156
|
return Option.getOrThrowWith(Option.fromNullishOr(loc), () => /* @__PURE__ */ new Error(MISSING_LOCATION));
|
|
4522
5157
|
}
|
|
4523
|
-
/** Warnings for the directive's mutator names that
|
|
4524
|
-
function mutatorWarnings(directive,
|
|
4525
|
-
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));
|
|
4526
5161
|
}
|
|
4527
5162
|
function mutatorWarning(directive, mutatorName, originFileName) {
|
|
4528
5163
|
const loc = commentLocation(directive.loc);
|
|
@@ -5008,19 +5643,21 @@ const emptyAppliedMutants = () => /* @__PURE__ */ new Map();
|
|
|
5008
5643
|
function isMutateRangeList(value) {
|
|
5009
5644
|
return Array.isArray(value);
|
|
5010
5645
|
}
|
|
5011
|
-
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) => {
|
|
5012
5647
|
const placementMap = /* @__PURE__ */ new Map();
|
|
5013
5648
|
return Effect.gen(function* () {
|
|
5014
5649
|
const lineTable = buildLineTable(rawContent);
|
|
5015
5650
|
attachComments(root, comments, lineTable);
|
|
5016
5651
|
let directiveRule = rootRule;
|
|
5017
|
-
const
|
|
5652
|
+
const mutatorRegistry = Option.getOrElse(Option.fromNullishOr(registry), () => DEFAULT_MUTATOR_REGISTRY);
|
|
5653
|
+
const selection = selectMutators(mutatorRegistry, options.optInMutations);
|
|
5654
|
+
const mutatorEntries = selection.active;
|
|
5018
5655
|
const placers = Option.getOrElse(Option.fromNullishOr(mutantPlacers), () => allMutantPlacers);
|
|
5019
|
-
const
|
|
5656
|
+
const knownMutatorNames = selection.known.map((name) => name.toLowerCase());
|
|
5020
5657
|
const warnings = [];
|
|
5021
5658
|
traverse(root, {
|
|
5022
5659
|
enter(path) {
|
|
5023
|
-
const result = processStrykerDirectives(directiveRule, path.node,
|
|
5660
|
+
const result = processStrykerDirectives(directiveRule, path.node, knownMutatorNames, originFileName, nodeStartLine(path.node));
|
|
5024
5661
|
directiveRule = result.rule;
|
|
5025
5662
|
warnings.push(...result.warnings);
|
|
5026
5663
|
visitNode(path);
|
|
@@ -5030,6 +5667,12 @@ const transformScript = ({ root, originFileName, rawContent, offset, comments },
|
|
|
5030
5667
|
if (hasAppliedMutants(placement)) applyPlacement(path, placement);
|
|
5031
5668
|
}
|
|
5032
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
|
+
}
|
|
5033
5676
|
yield* placeHeaderIfNeeded(mutantCollector, originFileName, options, root);
|
|
5034
5677
|
return warnings;
|
|
5035
5678
|
function visitNode(path) {
|
|
@@ -5303,9 +5946,26 @@ function toOneBasedLineNumber(range) {
|
|
|
5303
5946
|
function isIgnorer(value) {
|
|
5304
5947
|
return Predicate.isObject(value) && typeof value["shouldIgnore"] === "function";
|
|
5305
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
|
+
});
|
|
5306
5965
|
function toTransformerOptions(options) {
|
|
5307
5966
|
const base = {
|
|
5308
5967
|
excludedMutations: [...options.excludedMutations],
|
|
5968
|
+
optInMutations: [...requestedOptInMutations(options)],
|
|
5309
5969
|
ignorers: options.ignorers.filter(isIgnorer)
|
|
5310
5970
|
};
|
|
5311
5971
|
if (options.noHeader !== void 0) return {
|
|
@@ -5327,6 +5987,7 @@ function printedFile(file, ast) {
|
|
|
5327
5987
|
}];
|
|
5328
5988
|
}
|
|
5329
5989
|
const instrument = (files, options, basePath) => Effect.gen(function* () {
|
|
5990
|
+
yield* refuseUnknownOptInMutations(requestedOptInMutations(options));
|
|
5330
5991
|
const schemaFiles = files.map((file) => ({
|
|
5331
5992
|
name: file.name,
|
|
5332
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,6 +31,7 @@
|
|
|
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",
|
|
@@ -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": {
|