@systemfsoftware/stryker-js-instrumenter 12.1.0 → 15.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 +56 -0
- package/README.md +10 -8
- package/dist/index.d.mts +956 -914
- package/dist/index.mjs +752 -326
- package/package.json +24 -15
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)
|
|
@@ -3830,10 +4098,6 @@ const printedAs = (code) => canonicalForm(code);
|
|
|
3830
4098
|
const printsSomething = (code) => printedAs(code).length > 0;
|
|
3831
4099
|
const samePrintedCode = (left, right) => [printsSomething(left), printedAs(left) === printedAs(right)].every(Boolean);
|
|
3832
4100
|
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
4101
|
const equivalentSuppression = (facts, policy) => defaultOnly(policy, () => Option.map(Option.filter(Option.some(facts), (candidate) => samePrintedCode(candidate.replacementCode, candidate.originalCode)), () => MutantSuppressed.make({
|
|
3838
4102
|
ruleId: "equivalent-to-original",
|
|
3839
4103
|
detail: `${printedAs(facts.replacementCode)} is the original code`
|
|
@@ -3842,7 +4106,7 @@ const duplicateSuppression = (facts, earlier, policy) => defaultOnly(policy, ()
|
|
|
3842
4106
|
ruleId: "duplicate-at-site",
|
|
3843
4107
|
detail: `${printedAs(facts.replacementCode)} is already planted at this site`
|
|
3844
4108
|
})));
|
|
3845
|
-
const outcomeOf = (facts, earlier, policy) => Option.getOrElse(Option.orElse(
|
|
4109
|
+
const outcomeOf = (facts, earlier, policy) => Option.getOrElse(Option.orElse(equivalentSuppression(facts, policy), () => duplicateSuppression(facts, earlier, policy)), () => MutantKept.make({}));
|
|
3846
4110
|
const foldCandidate = (state, facts, policy) => ({
|
|
3847
4111
|
outcomes: [...state.outcomes, outcomeOf(facts, state.earlier, policy)],
|
|
3848
4112
|
earlier: [...state.earlier, canonicalForm(facts.replacementCode)]
|
|
@@ -3858,6 +4122,75 @@ const mutantSetPolicy = Workflow.make({
|
|
|
3858
4122
|
decide: (command) => Result.succeed([...outcomesFor(command)])
|
|
3859
4123
|
});
|
|
3860
4124
|
//#endregion
|
|
4125
|
+
//#region src/subsume-mutants.workflow.ts
|
|
4126
|
+
const COMPLEMENT_ROWS = {
|
|
4127
|
+
"<": {
|
|
4128
|
+
complement: ">=",
|
|
4129
|
+
dominator: "<="
|
|
4130
|
+
},
|
|
4131
|
+
"<=": {
|
|
4132
|
+
complement: ">",
|
|
4133
|
+
dominator: "<"
|
|
4134
|
+
},
|
|
4135
|
+
">": {
|
|
4136
|
+
complement: "<=",
|
|
4137
|
+
dominator: ">="
|
|
4138
|
+
},
|
|
4139
|
+
">=": {
|
|
4140
|
+
complement: "<",
|
|
4141
|
+
dominator: ">"
|
|
4142
|
+
}
|
|
4143
|
+
};
|
|
4144
|
+
var RelationalSite = class extends S.TaggedClass()("RelationalSite", { operator: RelationalOperator }) {};
|
|
4145
|
+
var OtherSite = class extends S.TaggedClass()("OtherSite", {}) {};
|
|
4146
|
+
const SubsumptionSite = S.Union([RelationalSite, OtherSite]);
|
|
4147
|
+
var OrderingOperator = class extends S.TaggedClass()("OrderingOperator", { operator: RelationalOperator }) {};
|
|
4148
|
+
var OtherReplacement = class extends S.TaggedClass()("OtherReplacement", {}) {};
|
|
4149
|
+
const SubsumptionReplacement = S.Union([OrderingOperator, OtherReplacement]);
|
|
4150
|
+
const StaticStatus = S.Literals(["StaticallyKept", "StaticallyIgnored"]);
|
|
4151
|
+
const SubsumptionCandidateSchema = S.Struct({
|
|
4152
|
+
id: Mutant.MutantId,
|
|
4153
|
+
replacement: SubsumptionReplacement,
|
|
4154
|
+
status: StaticStatus
|
|
4155
|
+
});
|
|
4156
|
+
var SubsumeMutantsCommand = class extends S.TaggedClass()("SubsumeMutantsCommand", {
|
|
4157
|
+
policy: Options.MutantSetPolicy,
|
|
4158
|
+
site: SubsumptionSite,
|
|
4159
|
+
candidates: S.Array(SubsumptionCandidateSchema)
|
|
4160
|
+
}) {
|
|
4161
|
+
static [Workflow.InstrumentationBrand] = {};
|
|
4162
|
+
};
|
|
4163
|
+
const SubsumptionDecisionTypeId = Symbol.for("@systemfsoftware/stryker-js-instrumenter/SubsumptionDecision");
|
|
4164
|
+
var Unaffected = class extends S.TaggedClass()("Unaffected", {}) {
|
|
4165
|
+
[SubsumptionDecisionTypeId] = SubsumptionDecisionTypeId;
|
|
4166
|
+
};
|
|
4167
|
+
var Subsumed = class extends S.TaggedClass()("Subsumed", {
|
|
4168
|
+
rule: S.Literal("complement"),
|
|
4169
|
+
dominators: S.NonEmptyArray(Mutant.MutantId)
|
|
4170
|
+
}) {
|
|
4171
|
+
[SubsumptionDecisionTypeId] = SubsumptionDecisionTypeId;
|
|
4172
|
+
};
|
|
4173
|
+
const SubsumptionDecision = S.Union([Unaffected, Subsumed]);
|
|
4174
|
+
const SubsumptionDecisions = S.Array(SubsumptionDecision);
|
|
4175
|
+
const replacesWith = (candidate, operator) => Match.value(candidate.replacement).pipe(Match.tag("OrderingOperator", (replacement) => replacement.operator === operator), Match.tag("OtherReplacement", () => false), Match.exhaustive);
|
|
4176
|
+
const isKeptAs = (candidate, operator) => [candidate.status === "StaticallyKept", replacesWith(candidate, operator)].every(Boolean);
|
|
4177
|
+
const dominatorsOf = (candidates, operator) => Option.liftPredicate(candidates.filter((candidate) => isKeptAs(candidate, COMPLEMENT_ROWS[operator].dominator)).map((candidate) => candidate.id), Arr.isArrayNonEmpty);
|
|
4178
|
+
const decideAt = (candidates, operator) => (candidate) => Option.match(Option.flatMap(Option.liftPredicate(candidate, (kept) => isKeptAs(kept, COMPLEMENT_ROWS[operator].complement)), () => dominatorsOf(candidates, operator)), {
|
|
4179
|
+
onNone: () => Unaffected.make({}),
|
|
4180
|
+
onSome: (dominators) => Subsumed.make({
|
|
4181
|
+
rule: "complement",
|
|
4182
|
+
dominators
|
|
4183
|
+
})
|
|
4184
|
+
});
|
|
4185
|
+
const allUnaffected = (command) => command.candidates.map(() => Unaffected.make({}));
|
|
4186
|
+
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);
|
|
4187
|
+
const subsumeMutants = Workflow.make({
|
|
4188
|
+
command: SubsumeMutantsCommand,
|
|
4189
|
+
decision: SubsumptionDecisions,
|
|
4190
|
+
error: S.Never,
|
|
4191
|
+
decide: (command) => Result.succeed(Match.value(command.policy).pipe(Match.when("full", () => allUnaffected(command)), Match.when("default", () => decisionsAtSite(command)), Match.exhaustive))
|
|
4192
|
+
});
|
|
4193
|
+
//#endregion
|
|
3861
4194
|
//#region src/plan-mutants.workflow.ts
|
|
3862
4195
|
const WILDCARD = "all";
|
|
3863
4196
|
const NEXT_LINE = "next-line";
|
|
@@ -3865,21 +4198,31 @@ const RULE_SEPARATOR = ": ";
|
|
|
3865
4198
|
const policyWorkflow = mutantSetPolicy;
|
|
3866
4199
|
const policyCommand = MutantSetPolicyCommand;
|
|
3867
4200
|
const keptOutcome = MutantKept;
|
|
4201
|
+
const subsumptionWorkflow = subsumeMutants;
|
|
4202
|
+
const subsumptionCommand = SubsumeMutantsCommand;
|
|
4203
|
+
const unaffectedOutcome = Unaffected;
|
|
3868
4204
|
const ignoreReasonFor = (ruleId, detail) => `${ruleId}${RULE_SEPARATOR}${detail}`;
|
|
4205
|
+
const IgnorerAnswerSchema = S.Struct({
|
|
4206
|
+
ignorerName: S.String,
|
|
4207
|
+
answer: S.Unknown
|
|
4208
|
+
});
|
|
3869
4209
|
const MutantCandidateSchema = S.Struct({
|
|
3870
4210
|
id: Mutant.MutantId,
|
|
3871
4211
|
mutatorName: MutatorNameSchema,
|
|
3872
4212
|
replacementCode: S.String,
|
|
3873
4213
|
location: S.optional(Mutant.Location),
|
|
3874
|
-
|
|
3875
|
-
|
|
4214
|
+
ignorerAnswer: S.optional(IgnorerAnswerSchema),
|
|
4215
|
+
aridReason: S.optional(Mutant.IgnoreStatusReasonText),
|
|
4216
|
+
mutantSet: MutantSetFactsSchema,
|
|
4217
|
+
subsumption: SubsumptionReplacement
|
|
3876
4218
|
});
|
|
3877
4219
|
const PlannedMutantSchema = S.Struct({
|
|
3878
4220
|
id: Mutant.MutantId,
|
|
3879
4221
|
mutatorName: MutatorNameSchema,
|
|
3880
4222
|
replacementCode: S.String,
|
|
3881
4223
|
location: Mutant.Location,
|
|
3882
|
-
ignoreReason: S.optional(S.String)
|
|
4224
|
+
ignoreReason: S.optional(S.String),
|
|
4225
|
+
subsumption: S.optional(Mutant.Subsumed)
|
|
3883
4226
|
});
|
|
3884
4227
|
var PlanMutantsCommand = class extends S.TaggedClass()("PlanMutantsCommand", {
|
|
3885
4228
|
fileName: S.String,
|
|
@@ -3890,6 +4233,7 @@ var PlanMutantsCommand = class extends S.TaggedClass()("PlanMutantsCommand", {
|
|
|
3890
4233
|
rule: S.Array(LocatedDirectiveSchema),
|
|
3891
4234
|
directives: S.Array(LocatedDirectiveSchema),
|
|
3892
4235
|
candidates: S.Array(MutantCandidateSchema),
|
|
4236
|
+
site: SubsumptionSite,
|
|
3893
4237
|
mutantSetPolicy: Options.MutantSetPolicy
|
|
3894
4238
|
}) {
|
|
3895
4239
|
static [Workflow.InstrumentationBrand] = {};
|
|
@@ -3916,13 +4260,31 @@ var MutantWithoutLocation = class extends S.TaggedError()("MutantWithoutLocation
|
|
|
3916
4260
|
return `Mutant without a source location: ${this.mutatorName} in ${this.fileName}`;
|
|
3917
4261
|
}
|
|
3918
4262
|
};
|
|
4263
|
+
var IgnorerAnsweredWithoutReason = class extends S.TaggedError()("IgnorerAnsweredWithoutReason", {
|
|
4264
|
+
fileName: S.String,
|
|
4265
|
+
mutantId: Mutant.MutantId,
|
|
4266
|
+
ignorerName: S.String
|
|
4267
|
+
}) {
|
|
4268
|
+
get message() {
|
|
4269
|
+
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.`;
|
|
4270
|
+
}
|
|
4271
|
+
};
|
|
3919
4272
|
const reachedLine = (located, line) => Match.value(located.directive.scope).pipe(Match.when(NEXT_LINE, () => located.governedLine === line), Match.when("block", () => true), Match.exhaustive);
|
|
3920
4273
|
const namesMutator = (located, lowerMutatorName) => located.directive.mutatorNames.some((name) => [name === WILDCARD, name.toLowerCase() === lowerMutatorName].some(Boolean));
|
|
3921
4274
|
const lastReachingDirective = (rule, lowerMutatorName, line) => Option.fromNullishOr(rule.findLast((located) => [reachedLine(located, line), namesMutator(located, lowerMutatorName)].every(Boolean)));
|
|
3922
4275
|
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
4276
|
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
|
|
4277
|
+
const ignorerReasonText = (answered) => Option.filter(S.decodeUnknownOption(S.String)(answered.answer), (reason) => reason.trim().length > 0);
|
|
4278
|
+
const ignorerRefusalOf = (command, candidate) => Option.flatMap(Option.fromNullishOr(candidate.ignorerAnswer), (answered) => Option.match(ignorerReasonText(answered), {
|
|
4279
|
+
onNone: () => Option.some(IgnorerAnsweredWithoutReason.make({
|
|
4280
|
+
fileName: command.fileName,
|
|
4281
|
+
mutantId: candidate.id,
|
|
4282
|
+
ignorerName: answered.ignorerName
|
|
4283
|
+
})),
|
|
4284
|
+
onSome: () => Option.none()
|
|
4285
|
+
}));
|
|
4286
|
+
const ignorerReason = (candidate) => Option.map(Option.flatMap(Option.fromNullishOr(candidate.ignorerAnswer), ignorerReasonText), (reason) => ignoreReasonFor("ignorer", reason));
|
|
4287
|
+
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
4288
|
const policyOutcomes = (command) => Result.match(policyWorkflow(policyCommand.make({
|
|
3927
4289
|
policy: command.mutantSetPolicy,
|
|
3928
4290
|
candidates: command.candidates.map((candidate) => candidate.mutantSet)
|
|
@@ -3932,6 +4294,38 @@ const policyOutcomes = (command) => Result.match(policyWorkflow(policyCommand.ma
|
|
|
3932
4294
|
});
|
|
3933
4295
|
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
4296
|
const policyReasonsAt = (command) => policyOutcomes(command).map(policyReasonOf);
|
|
4297
|
+
const staticReasonsAt = (command) => {
|
|
4298
|
+
const policyReasons = policyReasonsAt(command);
|
|
4299
|
+
return command.candidates.map((candidate, index) => ignoreReasonOf(command, candidate, policyReasons[index]));
|
|
4300
|
+
};
|
|
4301
|
+
const staticStatusOf = (reason) => Option.match(Option.fromNullishOr(reason), {
|
|
4302
|
+
onNone: () => "StaticallyKept",
|
|
4303
|
+
onSome: () => "StaticallyIgnored"
|
|
4304
|
+
});
|
|
4305
|
+
const subsumptionsAt = (command, staticReasons) => Result.match(subsumptionWorkflow(subsumptionCommand.make({
|
|
4306
|
+
policy: command.mutantSetPolicy,
|
|
4307
|
+
site: command.site,
|
|
4308
|
+
candidates: command.candidates.map((candidate, index) => ({
|
|
4309
|
+
id: candidate.id,
|
|
4310
|
+
replacement: candidate.subsumption,
|
|
4311
|
+
status: staticStatusOf(staticReasons[index])
|
|
4312
|
+
}))
|
|
4313
|
+
})), {
|
|
4314
|
+
onFailure: () => command.candidates.map(() => unaffectedOutcome.make({})),
|
|
4315
|
+
onSuccess: (decisions) => decisions
|
|
4316
|
+
});
|
|
4317
|
+
const subsumedOf = (decision) => Match.value(decision).pipe(Match.tag("Subsumed", (subsumed) => Option.some({
|
|
4318
|
+
_tag: "Subsumed",
|
|
4319
|
+
rule: subsumed.rule,
|
|
4320
|
+
dominators: subsumed.dominators
|
|
4321
|
+
})), Match.tag("Unaffected", () => Option.none()), Match.exhaustive);
|
|
4322
|
+
const candidateReasonsAt = (command) => {
|
|
4323
|
+
const staticReasons = staticReasonsAt(command);
|
|
4324
|
+
return Arr.zip(staticReasons, subsumptionsAt(command, staticReasons)).map(([ignoreReason, decision]) => ({
|
|
4325
|
+
ignoreReason,
|
|
4326
|
+
subsumption: subsumedOf(decision)
|
|
4327
|
+
}));
|
|
4328
|
+
};
|
|
3935
4329
|
const unusedDirectives = (directives, mutatorNames) => directives.flatMap((located) => located.directive.mutatorNames.filter((mutatorName) => mutatorName !== WILDCARD).filter((mutatorName) => !mutatorNames.includes(mutatorName.toLowerCase())).map((mutatorName) => ({
|
|
3936
4330
|
directive: located.directive,
|
|
3937
4331
|
at: located.at,
|
|
@@ -3957,23 +4351,27 @@ const shiftedLocation = (location, offset) => ({
|
|
|
3957
4351
|
start: shiftedPosition(location.start, offset),
|
|
3958
4352
|
end: shiftedPosition(location.end, offset)
|
|
3959
4353
|
});
|
|
3960
|
-
const plannedMutant = (command, candidate,
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
4354
|
+
const plannedMutant = (command, candidate, reasons) => Option.match(ignorerRefusalOf(command, candidate), {
|
|
4355
|
+
onSome: (refusal) => Result.fail(refusal),
|
|
4356
|
+
onNone: () => Option.match(Option.fromNullishOr(candidate.location), {
|
|
4357
|
+
onNone: () => Result.fail(MutantWithoutLocation.make({
|
|
4358
|
+
fileName: command.fileName,
|
|
4359
|
+
mutatorName: candidate.mutatorName
|
|
4360
|
+
})),
|
|
4361
|
+
onSome: (location) => Result.succeed({
|
|
4362
|
+
id: candidate.id,
|
|
4363
|
+
mutatorName: candidate.mutatorName,
|
|
4364
|
+
replacementCode: candidate.replacementCode,
|
|
4365
|
+
location: shiftedLocation(location, command.offset),
|
|
4366
|
+
ignoreReason: reasons.ignoreReason,
|
|
4367
|
+
...Option.match(reasons.subsumption, {
|
|
4368
|
+
onNone: () => ({}),
|
|
4369
|
+
onSome: (subsumption) => ({ subsumption })
|
|
4370
|
+
})
|
|
4371
|
+
})
|
|
3971
4372
|
})
|
|
3972
4373
|
});
|
|
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
|
-
};
|
|
4374
|
+
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
4375
|
const withoutReason = (mutant) => mutant.ignoreReason === void 0;
|
|
3978
4376
|
const planOf = (command, mutants) => Match.value(mutants.some(withoutReason)).pipe(Match.when(true, () => MutantsPlanned.make({
|
|
3979
4377
|
mutants: [...mutants],
|
|
@@ -3986,9 +4384,10 @@ const planOf = (command, mutants) => Match.value(mutants.some(withoutReason)).pi
|
|
|
3986
4384
|
const planMutants = Workflow.make({
|
|
3987
4385
|
command: PlanMutantsCommand,
|
|
3988
4386
|
decision: S.Union([MutantsPlanned, MutantsFullyIgnored]),
|
|
3989
|
-
error: MutantWithoutLocation,
|
|
4387
|
+
error: S.Union([MutantWithoutLocation, IgnorerAnsweredWithoutReason]),
|
|
3990
4388
|
decide: (command) => Result.gen(function* () {
|
|
3991
|
-
const
|
|
4389
|
+
const reasons = candidateReasonsAt(command);
|
|
4390
|
+
const mutants = yield* plannedMutants(command, reasons);
|
|
3992
4391
|
return planOf(command, mutants);
|
|
3993
4392
|
})
|
|
3994
4393
|
});
|
|
@@ -4357,7 +4756,10 @@ const plannedWithNodes = (candidates, planned, fileName, includes) => planned.fl
|
|
|
4357
4756
|
onNone: () => [],
|
|
4358
4757
|
onSome: (candidate) => includes(mutant) ? [createMutant(mutant, fileName, candidate.node, candidate.replacement)] : []
|
|
4359
4758
|
}));
|
|
4360
|
-
const
|
|
4759
|
+
const ignorerAnswerFor = (node, ancestors, ignorers) => ignorers.reduce((answered, ignorer) => Option.orElse(answered, () => Option.map(Option.fromNullishOr(ignorer.shouldIgnore(node, ancestors)), (answer) => ({
|
|
4760
|
+
ignorerName: ignorer.name,
|
|
4761
|
+
answer
|
|
4762
|
+
}))), Option.none());
|
|
4361
4763
|
const hasKey = (node, key) => key in node;
|
|
4362
4764
|
const readKey = (node, key) => hasKey(node, key) ? node[key] : void 0;
|
|
4363
4765
|
const isMemberExpressionValue = (node) => nodeType(node) === "MemberExpression";
|
|
@@ -4367,23 +4769,47 @@ const isMemberOfIdentifierParts = (node) => isMemberExpressionValue(node) && has
|
|
|
4367
4769
|
function isNamedCallee(node) {
|
|
4368
4770
|
return isMemberOfIdentifierParts(node) && readKey(node, "computed") === false;
|
|
4369
4771
|
}
|
|
4370
|
-
const
|
|
4371
|
-
|
|
4772
|
+
const unshadowedGlobalOf = (name, context, table) => Option.filter(Option.filter(Option.some(name), S.is(AridGlobalObject)), (global) => isShadowed(global, context) === false && isImportedLocal(global, table) === false);
|
|
4773
|
+
const aridGlobalCalleeOf = (callee, context, table) => Match.value(callee).pipe(Match.when(isNamedCallee, (named) => Option.map(unshadowedGlobalOf(named.object.name, context, table), (name) => ({
|
|
4774
|
+
_tag: "Global",
|
|
4775
|
+
name,
|
|
4372
4776
|
member: named.property.name
|
|
4373
|
-
})), Match.orElse(() => Option.none()));
|
|
4374
|
-
const
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4777
|
+
}))), Match.orElse(() => Option.none()));
|
|
4778
|
+
const aridCalleeOf = (call, callFrame, policy, table) => {
|
|
4779
|
+
const context = toMutatorContext([call, ...ancestorsOfFrame(callFrame)], policy);
|
|
4780
|
+
return Option.orElse(Option.map(resolveCallee(call.callee, context, table), (callee) => ({
|
|
4781
|
+
_tag: "EffectExport",
|
|
4782
|
+
module: callee.module,
|
|
4783
|
+
exportName: callee.exportName
|
|
4784
|
+
})), () => aridGlobalCalleeOf(call.callee, context, table));
|
|
4785
|
+
};
|
|
4786
|
+
const aridCallFrame = (current, ancestor, ancestorFrame, policy, table) => ancestor.arguments.some((argument) => argument === current.node) ? {
|
|
4787
|
+
_tag: "ArgumentFrame",
|
|
4788
|
+
callee: aridCalleeOf(ancestor, ancestorFrame, policy, table)
|
|
4789
|
+
} : { _tag: "ThroughFrame" };
|
|
4790
|
+
const aridCallFrameFor = (current, ancestorFrame, policy, table) => {
|
|
4791
|
+
const ancestor = ancestorFrame.node;
|
|
4792
|
+
if (ancestor.type !== "CallExpression") return [];
|
|
4793
|
+
return [aridCallFrame(current, ancestor, ancestorFrame, policy, table)];
|
|
4794
|
+
};
|
|
4795
|
+
const functionBoundaryOf = (current) => isFunctionLike(current.node) ? [{ _tag: "FunctionBoundary" }] : [];
|
|
4796
|
+
const aridFramesOf = (frame, policy, table) => framesUpward(frame).flatMap((current) => [...functionBoundaryOf(current), ...Option.match(Option.fromNullishOr(current.parent), {
|
|
4379
4797
|
onNone: () => [],
|
|
4380
|
-
onSome: (parent) =>
|
|
4381
|
-
}));
|
|
4798
|
+
onSome: (parent) => aridCallFrameFor(current, parent, policy, table)
|
|
4799
|
+
})]);
|
|
4382
4800
|
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({
|
|
4801
|
+
const aridReasonOf = (frame, policy, table) => Match.value(aridCode(AridCodeCommand.make({
|
|
4384
4802
|
policy,
|
|
4385
|
-
frames: [...aridFramesOf(frame)]
|
|
4803
|
+
frames: [...aridFramesOf(frame, policy, table)]
|
|
4386
4804
|
}))).pipe(Match.when(Result.isSuccess, (decided) => aridStatusReason(decided.success)), Match.orElse(() => Option.none()));
|
|
4805
|
+
const subsumptionSiteOf = (node) => Option.match(relationalOperatorOf(node), {
|
|
4806
|
+
onNone: () => OtherSite.make({}),
|
|
4807
|
+
onSome: (operator) => RelationalSite.make({ operator })
|
|
4808
|
+
});
|
|
4809
|
+
const subsumptionReplacementOf = (mutatorName, replacement) => Option.match(Option.filter(relationalOperatorOf(replacement), () => mutatorName === "EqualityOperator"), {
|
|
4810
|
+
onNone: () => OtherReplacement.make({}),
|
|
4811
|
+
onSome: (operator) => OrderingOperator.make({ operator })
|
|
4812
|
+
});
|
|
4387
4813
|
const mutablesFor = (frame, location, context) => {
|
|
4388
4814
|
const ancestors = ancestorsOfFrame(frame);
|
|
4389
4815
|
const mutatorContext = toMutatorContext(ancestors, context.mutantSetPolicy);
|
|
@@ -4391,10 +4817,10 @@ const mutablesFor = (frame, location, context) => {
|
|
|
4391
4817
|
mutatorName,
|
|
4392
4818
|
replacement
|
|
4393
4819
|
})));
|
|
4394
|
-
|
|
4395
|
-
const
|
|
4820
|
+
if (replacements.length === 0) return [];
|
|
4821
|
+
const ignorerAnswer = Option.getOrUndefined(ignorerAnswerFor(frame.node, ancestors, context.ignorers));
|
|
4822
|
+
const aridReason = Option.getOrUndefined(aridReasonOf(frame, context.mutantSetPolicy, context.aridImportTable));
|
|
4396
4823
|
const originalCode = printNode(frame.node);
|
|
4397
|
-
const relationalSite = relationalSiteFacts(frame.node, mutatorContext);
|
|
4398
4824
|
return replacements.map(({ mutatorName, replacement }) => {
|
|
4399
4825
|
const replacementCode = printNode(replacement);
|
|
4400
4826
|
const tuple = {
|
|
@@ -4406,7 +4832,6 @@ const mutablesFor = (frame, location, context) => {
|
|
|
4406
4832
|
return {
|
|
4407
4833
|
node: frame.node,
|
|
4408
4834
|
replacement,
|
|
4409
|
-
aridReason,
|
|
4410
4835
|
data: {
|
|
4411
4836
|
id: mutantIdOf({
|
|
4412
4837
|
...tuple,
|
|
@@ -4415,12 +4840,13 @@ const mutablesFor = (frame, location, context) => {
|
|
|
4415
4840
|
mutatorName,
|
|
4416
4841
|
replacementCode,
|
|
4417
4842
|
location,
|
|
4418
|
-
|
|
4843
|
+
ignorerAnswer,
|
|
4844
|
+
...aridReason === void 0 ? {} : { aridReason },
|
|
4419
4845
|
mutantSet: {
|
|
4420
4846
|
originalCode,
|
|
4421
|
-
replacementCode
|
|
4422
|
-
|
|
4423
|
-
|
|
4847
|
+
replacementCode
|
|
4848
|
+
},
|
|
4849
|
+
subsumption: subsumptionReplacementOf(mutatorName, replacement)
|
|
4424
4850
|
}
|
|
4425
4851
|
};
|
|
4426
4852
|
});
|
|
@@ -4461,18 +4887,8 @@ const attachPlaceable = (mutants, frame, state, context) => Match.value(mutants.
|
|
|
4461
4887
|
})),
|
|
4462
4888
|
onSome: (target) => applyToClaim(mutants, target, state, context)
|
|
4463
4889
|
})), 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
4890
|
const collectPlan = (frame, candidates, plan, state, context) => {
|
|
4474
|
-
const
|
|
4475
|
-
const collected = plannedWithNodes(candidates, planned, context.fileName, () => true);
|
|
4891
|
+
const collected = plannedWithNodes(candidates, plan.mutants, context.fileName, () => true);
|
|
4476
4892
|
const nextState = {
|
|
4477
4893
|
...state,
|
|
4478
4894
|
mutants: [...state.mutants, ...collected],
|
|
@@ -4480,7 +4896,7 @@ const collectPlan = (frame, candidates, plan, state, context) => {
|
|
|
4480
4896
|
};
|
|
4481
4897
|
return Match.value(plan).pipe(Match.tag("MutantsPlanned", (plannedPlan) => {
|
|
4482
4898
|
const placeable = new Set(plannedPlan.placeableIds);
|
|
4483
|
-
const live = plannedWithNodes(candidates,
|
|
4899
|
+
const live = plannedWithNodes(candidates, plan.mutants, context.fileName, (mutant) => placeable.has(mutant.id));
|
|
4484
4900
|
return attachPlaceable(live, frame, {
|
|
4485
4901
|
...nextState,
|
|
4486
4902
|
hasLiveMutants: nextState.hasLiveMutants || live.length > 0
|
|
@@ -4497,6 +4913,7 @@ const planMutantsAt = (frame, candidates, directives, location, state, context)
|
|
|
4497
4913
|
rule: [...state.directiveRule],
|
|
4498
4914
|
directives: [...directives],
|
|
4499
4915
|
candidates: candidates.map((candidate) => candidate.data),
|
|
4916
|
+
site: subsumptionSiteOf(frame.node),
|
|
4500
4917
|
mutantSetPolicy: context.mutantSetPolicy
|
|
4501
4918
|
}));
|
|
4502
4919
|
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 +5032,9 @@ const refusalError = (refusal) => Match.value(refusal).pipe(Match.tag("NodeWitho
|
|
|
4615
5032
|
})), Match.tag("MutantWithoutLocation", (failed) => InstrumentError.make({
|
|
4616
5033
|
message: `Mutant without a source location: ${failed.mutatorName} in ${failed.fileName}`,
|
|
4617
5034
|
cause: void 0
|
|
5035
|
+
})), Match.tag("IgnorerAnsweredWithoutReason", (failed) => InstrumentError.make({
|
|
5036
|
+
message: failed.message,
|
|
5037
|
+
cause: failed
|
|
4618
5038
|
})), Match.exhaustive);
|
|
4619
5039
|
const transformScriptDataFirst = Effect.fn(SpanTaxonomy.Spans.instrumentTransformScript.name)(function* ({ root, originFileName, rawContent, offset, comments }, mutantCollector, { options, mutateDescription, basePath }) {
|
|
4620
5040
|
const lineStarts = lineStartsOf(rawContent);
|
|
@@ -4629,6 +5049,7 @@ const transformScriptDataFirst = Effect.fn(SpanTaxonomy.Spans.instrumentTransfor
|
|
|
4629
5049
|
allMutatorNames: options.mutators.known.map((name) => name.toLowerCase()),
|
|
4630
5050
|
excludedMutations: options.excludedMutations,
|
|
4631
5051
|
mutantSetPolicy: options.mutantSetPolicy,
|
|
5052
|
+
aridImportTable: buildImportTable(root, AridEffectModule.literals),
|
|
4632
5053
|
ignorers: options.ignorers,
|
|
4633
5054
|
ordinalOf: mutantCollector.ordinalOf
|
|
4634
5055
|
};
|
|
@@ -4875,7 +5296,7 @@ const spliceForAssigned = (request) => Option.match(request.registry.entryForFor
|
|
|
4875
5296
|
onSome: (entry) => spliceFileFor(request.file, entry)
|
|
4876
5297
|
});
|
|
4877
5298
|
//#endregion
|
|
4878
|
-
//#region src/
|
|
5299
|
+
//#region src/oxc-program.ts
|
|
4879
5300
|
const EMBEDDED_SCRIPT_FILE = "embedded-script.js";
|
|
4880
5301
|
const makeScriptParser = (oxc) => (source, scriptFormat) => {
|
|
4881
5302
|
const result = oxc.parseSync(EMBEDDED_SCRIPT_FILE, source, {
|
|
@@ -5044,6 +5465,11 @@ const admitInstrumentFiles = Workflow.make({
|
|
|
5044
5465
|
});
|
|
5045
5466
|
//#endregion
|
|
5046
5467
|
//#region src/instrument-files.cell.ts
|
|
5468
|
+
const skippedOf = (raw) => raw.skipped.map(({ extension, file, reason }) => InstrumentFileSkip.make({
|
|
5469
|
+
extension,
|
|
5470
|
+
file,
|
|
5471
|
+
reason
|
|
5472
|
+
}));
|
|
5047
5473
|
const printedFile = (raw, { file, ast }) => Result.map(print(ast, raw.registry.entryForFormat), (content) => ({
|
|
5048
5474
|
name: file.name,
|
|
5049
5475
|
mutate: file.mutate,
|
|
@@ -5056,12 +5482,12 @@ const printedFiles = (raw) => Effect.forEach(raw.parsed, (parsed) => Effect.from
|
|
|
5056
5482
|
const instrumentedResult = (raw) => Effect.map(printedFiles(raw), (files) => InstrumentResult.make({
|
|
5057
5483
|
files: [...files],
|
|
5058
5484
|
mutants: [...raw.mutants],
|
|
5059
|
-
skipped:
|
|
5485
|
+
skipped: skippedOf(raw)
|
|
5060
5486
|
}));
|
|
5061
5487
|
const skippedOnlyResult = (raw) => InstrumentResult.make({
|
|
5062
5488
|
files: [],
|
|
5063
5489
|
mutants: [],
|
|
5064
|
-
skipped:
|
|
5490
|
+
skipped: skippedOf(raw)
|
|
5065
5491
|
});
|
|
5066
5492
|
const readInstrumentFiles = Effect.fn(SpanTaxonomy.Spans.instrumentReadFiles.name)(function* (input) {
|
|
5067
5493
|
const outcomes = yield* Effect.forEach(input.files, (file) => fileOutcome({
|
|
@@ -5128,6 +5554,7 @@ const disableTypeChecks = dual((args) => Predicate.hasProperty(args[0], "content
|
|
|
5128
5554
|
var Instrument_exports = /* @__PURE__ */ __exportAll({
|
|
5129
5555
|
InstrumentFileSkip: () => InstrumentFileSkip,
|
|
5130
5556
|
InstrumenterContext: () => InstrumenterContext,
|
|
5557
|
+
MutateDescriptionSchema: () => MutateDescriptionSchema,
|
|
5131
5558
|
disableTypeChecks: () => disableTypeChecks,
|
|
5132
5559
|
instrument: () => instrument
|
|
5133
5560
|
});
|
|
@@ -5135,7 +5562,6 @@ var Instrument_exports = /* @__PURE__ */ __exportAll({
|
|
|
5135
5562
|
//#region src/Mutator/mod.ts
|
|
5136
5563
|
var Mutator_exports = /* @__PURE__ */ __exportAll({
|
|
5137
5564
|
defaultMutators: () => defaultMutators,
|
|
5138
|
-
optInMutators: () => optInMutators,
|
|
5139
5565
|
registryOf: () => registryOf,
|
|
5140
5566
|
selectMutators: () => selectMutators,
|
|
5141
5567
|
stockRegistry: () => stockRegistry
|