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