@systemfsoftware/stryker-js-instrumenter 10.0.1 → 12.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 +27 -0
- package/README.md +3 -1
- package/dist/index.d.mts +172 -295
- package/dist/index.mjs +552 -301
- package/package.json +13 -10
package/dist/index.mjs
CHANGED
|
@@ -8,10 +8,14 @@ import * as S from "effect/Schema";
|
|
|
8
8
|
import * as Effect from "effect/Effect";
|
|
9
9
|
import { dual } from "effect/Function";
|
|
10
10
|
import { Handle, Sandwich, Workflow } from "@systemfsoftware/effect-cell-types";
|
|
11
|
-
import
|
|
12
|
-
import * as SchemaGetter from "effect/SchemaGetter";
|
|
11
|
+
import { Mutant, Options } from "@systemfsoftware/stryker-js-plugin-interface";
|
|
13
12
|
import { RegExpParser, visitRegExpAST } from "@eslint-community/regexpp";
|
|
14
13
|
import * as Arr from "effect/Array";
|
|
14
|
+
import * as Order from "effect/Order";
|
|
15
|
+
import * as SchemaGetter from "effect/SchemaGetter";
|
|
16
|
+
import { SpanTaxonomy } from "@systemfsoftware/stryker-js-cli-contract";
|
|
17
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
18
|
+
import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils.js";
|
|
15
19
|
//#region src/ErrorText.schema.ts
|
|
16
20
|
var ErrorText = class extends S.Class("ErrorText")({ text: S.NonEmptyString }) {};
|
|
17
21
|
var CauseText = class extends S.Class("CauseText")({ text: S.NonEmptyString }) {};
|
|
@@ -106,71 +110,9 @@ var ErrorText_exports = /* @__PURE__ */ __exportAll({
|
|
|
106
110
|
errorTextOf: () => errorTextOf$2
|
|
107
111
|
});
|
|
108
112
|
//#endregion
|
|
109
|
-
//#region src/Location.schema.ts
|
|
110
|
-
const Offset = S.Int.check(S.isGreaterThanOrEqualTo(0));
|
|
111
|
-
/** A 1-based line: the first line of a file is line 1. */
|
|
112
|
-
const Line = S.Int.check(S.isGreaterThanOrEqualTo(1));
|
|
113
|
-
/** A 1-based column: the first character of a line is column 1. */
|
|
114
|
-
const Column = S.Int.check(S.isGreaterThanOrEqualTo(1));
|
|
115
|
-
const Position = S.Struct({
|
|
116
|
-
line: Line,
|
|
117
|
-
column: Column
|
|
118
|
-
});
|
|
119
|
-
const positionOrder = Order.combine(Order.mapInput(Order.Number, (position) => position.line), Order.mapInput(Order.Number, (position) => position.column));
|
|
120
|
-
const inOrder = (order) => (ends) => ({
|
|
121
|
-
start: Order.min(order)(ends.start, ends.end),
|
|
122
|
-
end: Order.max(order)(ends.start, ends.end)
|
|
123
|
-
});
|
|
124
|
-
const notReversed = (order) => (ends) => Order.isLessThanOrEqualTo(order)(ends.start, ends.end);
|
|
125
|
-
const PositionEnds = S.Struct({
|
|
126
|
-
start: Position,
|
|
127
|
-
end: Position
|
|
128
|
-
});
|
|
129
|
-
const Location = S.declare((value) => S.is(PositionEnds)(value) && notReversed(positionOrder)(value), {
|
|
130
|
-
expected: "a location whose end is not before its start",
|
|
131
|
-
toCodecArbitrary: () => S.link()(PositionEnds, {
|
|
132
|
-
decode: SchemaGetter.transform(inOrder(positionOrder)),
|
|
133
|
-
encode: SchemaGetter.transform((ends) => ends)
|
|
134
|
-
})
|
|
135
|
-
});
|
|
136
|
-
const OpenEnds = S.Struct({
|
|
137
|
-
start: Position,
|
|
138
|
-
end: S.optional(Position)
|
|
139
|
-
});
|
|
140
|
-
const closedEnds = (ends) => Option.map(Option.fromUndefinedOr(ends.end), (end) => ({
|
|
141
|
-
start: ends.start,
|
|
142
|
-
end
|
|
143
|
-
}));
|
|
144
|
-
const OpenEndLocation = S.declare((value) => S.is(OpenEnds)(value) && Option.match(closedEnds(value), {
|
|
145
|
-
onNone: () => true,
|
|
146
|
-
onSome: notReversed(positionOrder)
|
|
147
|
-
}), {
|
|
148
|
-
expected: "a location whose end, when present, is not before its start",
|
|
149
|
-
toCodecArbitrary: () => S.link()(OpenEnds, {
|
|
150
|
-
decode: SchemaGetter.transform((ends) => Option.getOrElse(Option.map(closedEnds(ends), inOrder(positionOrder)), () => ends)),
|
|
151
|
-
encode: SchemaGetter.transform((ends) => ends)
|
|
152
|
-
})
|
|
153
|
-
});
|
|
154
|
-
const OffsetEnds = S.Struct({
|
|
155
|
-
start: Offset,
|
|
156
|
-
end: Offset
|
|
157
|
-
});
|
|
158
|
-
const Span = S.declare((value) => S.is(OffsetEnds)(value) && notReversed(Order.Number)(value), {
|
|
159
|
-
expected: "a span whose end is not before its start",
|
|
160
|
-
toCodecArbitrary: () => S.link()(OffsetEnds, {
|
|
161
|
-
decode: SchemaGetter.transform(inOrder(Order.Number)),
|
|
162
|
-
encode: SchemaGetter.transform((ends) => ends)
|
|
163
|
-
})
|
|
164
|
-
});
|
|
165
|
-
const ScriptOrigin = S.Struct({
|
|
166
|
-
line: Line,
|
|
167
|
-
columnShift: Offset
|
|
168
|
-
});
|
|
169
|
-
S.is(Offset), S.is(Line), S.is(Column), S.is(Location), S.is(Span);
|
|
170
|
-
//#endregion
|
|
171
113
|
//#region src/directives/directive.schema.ts
|
|
172
|
-
const MutatorNameSchema = S.String.pipe(S.check(S.isPattern(/^[a-zA-Z]
|
|
173
|
-
const DirectiveReasonSchema = S.String.pipe(S.check(S.isPattern(/^\S(?:[^\r\n\u2028\u2029]*\S)?$/)));
|
|
114
|
+
const MutatorNameSchema = S.String.pipe(S.check(S.isPattern(/^(?:[a-z][a-z0-9]*(?:-[a-z0-9]+)*\/)?[a-zA-Z][a-zA-Z0-9]*(?: [a-zA-Z][a-zA-Z0-9]*)*$/u)));
|
|
115
|
+
const DirectiveReasonSchema = S.String.pipe(S.check(S.isPattern(/^\S(?:[^\r\n\u2028\u2029]*\S)?$/u)));
|
|
174
116
|
const DirectiveSchema = S.Struct({
|
|
175
117
|
action: S.Literals(["disable", "restore"]),
|
|
176
118
|
scope: S.Literals(["next-line", "block"]),
|
|
@@ -179,106 +121,15 @@ const DirectiveSchema = S.Struct({
|
|
|
179
121
|
});
|
|
180
122
|
const LocatedDirectiveSchema = S.Struct({
|
|
181
123
|
directive: DirectiveSchema,
|
|
182
|
-
at: Position,
|
|
183
|
-
governedLine: Line
|
|
124
|
+
at: Mutant.Position,
|
|
125
|
+
governedLine: Mutant.Line
|
|
184
126
|
});
|
|
185
127
|
S.Struct({
|
|
186
128
|
directive: DirectiveSchema,
|
|
187
|
-
at: Position,
|
|
129
|
+
at: Mutant.Position,
|
|
188
130
|
mutatorName: MutatorNameSchema
|
|
189
131
|
});
|
|
190
132
|
//#endregion
|
|
191
|
-
//#region src/Mutant.schema.ts
|
|
192
|
-
const MutantStatusSchema = S.Literals([
|
|
193
|
-
"Killed",
|
|
194
|
-
"Survived",
|
|
195
|
-
"NoCoverage",
|
|
196
|
-
"CompileError",
|
|
197
|
-
"RuntimeError",
|
|
198
|
-
"Timeout",
|
|
199
|
-
"Ignored",
|
|
200
|
-
"Pending"
|
|
201
|
-
]);
|
|
202
|
-
const SurvivorStatusSchema = S.Literals(["Survived", "NoCoverage"]);
|
|
203
|
-
const RememberedStatusSchema = S.Literals([
|
|
204
|
-
"Survived",
|
|
205
|
-
"Killed",
|
|
206
|
-
"Timeout",
|
|
207
|
-
"NoCoverage",
|
|
208
|
-
"Ignored"
|
|
209
|
-
]);
|
|
210
|
-
const EphemeralStatusSchema = S.Literals([
|
|
211
|
-
"CompileError",
|
|
212
|
-
"RuntimeError",
|
|
213
|
-
"Pending"
|
|
214
|
-
]);
|
|
215
|
-
const ActionableStatusSchema = S.Literals([
|
|
216
|
-
"Survived",
|
|
217
|
-
"NoCoverage",
|
|
218
|
-
"Timeout",
|
|
219
|
-
"RuntimeError"
|
|
220
|
-
]);
|
|
221
|
-
const MutantId = S.String.check(S.isPattern(/^(0|[1-9][0-9]*)$/)).pipe(S.brand("MutantId"));
|
|
222
|
-
const MutatorName = S.NonEmptyString.pipe(S.brand("MutatorName"));
|
|
223
|
-
const CanonicalFileName = S.String.pipe(S.decodeTo(S.String.pipe(S.check(S.isPattern(/^[^\\]+$/)), S.brand("CanonicalFileName")), {
|
|
224
|
-
decode: SchemaGetter.transform((fileName) => fileName.replace(/\\/g, "/")),
|
|
225
|
-
encode: SchemaGetter.transform((canonical) => canonical)
|
|
226
|
-
}));
|
|
227
|
-
/**
|
|
228
|
-
* A mutant's file location in the mutation-testing-report-schema contract:
|
|
229
|
-
* 1-based line and 1-based column, the same base the JSON report and the
|
|
230
|
-
* machine stream emit. Every producer on the instrument path (node spans,
|
|
231
|
-
* embedded-region shifts) targets this base; no downstream layer converts.
|
|
232
|
-
*/
|
|
233
|
-
const Mutant = S.TaggedStruct("Mutant", {
|
|
234
|
-
id: MutantId,
|
|
235
|
-
fileName: CanonicalFileName,
|
|
236
|
-
mutatorName: MutatorName,
|
|
237
|
-
replacement: S.String,
|
|
238
|
-
location: Location,
|
|
239
|
-
status: S.optional(MutantStatusSchema),
|
|
240
|
-
statusReason: S.optional(S.String),
|
|
241
|
-
coveredBy: S.String.pipe(S.Array, S.optional),
|
|
242
|
-
static: S.optional(S.Boolean),
|
|
243
|
-
testsCompleted: S.optional(S.Finite),
|
|
244
|
-
description: S.optional(S.String)
|
|
245
|
-
}).check(S.makeFilter((mutant) => mutant.statusReason === void 0 || mutant.status !== void 0, { message: "a mutant carries a status reason only together with a status" }));
|
|
246
|
-
const MutantFromUnknown = S.Unknown.pipe(S.decodeTo(Mutant));
|
|
247
|
-
const RunOptionsFields = {
|
|
248
|
-
timeout: S.Finite.pipe(S.check(S.isGreaterThanOrEqualTo(0))),
|
|
249
|
-
disableBail: S.Boolean
|
|
250
|
-
};
|
|
251
|
-
const MutantActivationSchema = S.Literals(["runtime", "static"]);
|
|
252
|
-
const HitCount = S.Int.pipe(S.check(S.isGreaterThanOrEqualTo(0)));
|
|
253
|
-
const MutantRunOptionsSchema = S.Struct({
|
|
254
|
-
...RunOptionsFields,
|
|
255
|
-
activeMutant: Mutant,
|
|
256
|
-
sandboxFileName: S.String,
|
|
257
|
-
mutantActivation: MutantActivationSchema,
|
|
258
|
-
reloadEnvironment: S.Boolean,
|
|
259
|
-
testFilter: S.String.pipe(S.Array, S.optionalKey),
|
|
260
|
-
hitLimit: S.optionalKey(HitCount)
|
|
261
|
-
});
|
|
262
|
-
const MutantCoverageSchema = S.Struct({
|
|
263
|
-
perTest: S.Record(S.String, S.Record(MutantId, HitCount)),
|
|
264
|
-
static: S.Record(MutantId, HitCount)
|
|
265
|
-
});
|
|
266
|
-
var InstrumenterContext = class extends S.Class("InstrumenterContext")({
|
|
267
|
-
activeMutant: S.optional(S.String),
|
|
268
|
-
currentTestId: S.optional(S.String),
|
|
269
|
-
mutantCoverage: S.optional(MutantCoverageSchema),
|
|
270
|
-
hitCount: S.optional(HitCount),
|
|
271
|
-
hitLimit: S.optional(HitCount)
|
|
272
|
-
}) {
|
|
273
|
-
static NAMESPACE = "__stryker__";
|
|
274
|
-
static MUTATION_COVERAGE_OBJECT = "mutantCoverage";
|
|
275
|
-
static ACTIVE_MUTANT = "activeMutant";
|
|
276
|
-
static CURRENT_TEST_ID = "currentTestId";
|
|
277
|
-
static HIT_COUNT = "hitCount";
|
|
278
|
-
static HIT_LIMIT = "hitLimit";
|
|
279
|
-
static ACTIVE_MUTANT_ENV_VARIABLE = "__STRYKER_ACTIVE_MUTANT__";
|
|
280
|
-
};
|
|
281
|
-
//#endregion
|
|
282
133
|
//#region src/Instrument.schema.ts
|
|
283
134
|
var InstrumentError = class extends S.TaggedError("@systemfsoftware/stryker-js-instrumenter/Instrument.schema/InstrumentError")("InstrumentError", {
|
|
284
135
|
message: S.String,
|
|
@@ -291,9 +142,9 @@ var InstrumentError = class extends S.TaggedError("@systemfsoftware/stryker-js-i
|
|
|
291
142
|
});
|
|
292
143
|
}
|
|
293
144
|
};
|
|
294
|
-
const MutateDescriptionSchema = S.Union([S.Boolean, S.Array(Location)]);
|
|
145
|
+
const MutateDescriptionSchema = S.Union([S.Boolean, S.Array(Mutant.Location)]);
|
|
295
146
|
const FileSchema = S.Struct({
|
|
296
|
-
name: CanonicalFileName,
|
|
147
|
+
name: Mutant.CanonicalFileName,
|
|
297
148
|
content: S.String,
|
|
298
149
|
mutate: MutateDescriptionSchema
|
|
299
150
|
});
|
|
@@ -301,8 +152,9 @@ const IgnorerSchema = S.Unknown;
|
|
|
301
152
|
S.Struct({
|
|
302
153
|
excludedMutations: S.Array(MutatorNameSchema),
|
|
303
154
|
ignorers: S.Array(IgnorerSchema),
|
|
304
|
-
|
|
305
|
-
|
|
155
|
+
mutators: S.Unknown,
|
|
156
|
+
mutantSetPolicy: Options.MutantSetPolicy,
|
|
157
|
+
noHeader: S.optional(S.Boolean)
|
|
306
158
|
});
|
|
307
159
|
var InstrumentFileSkip = class extends S.TaggedClass()("InstrumentFileSkip", {
|
|
308
160
|
file: S.String,
|
|
@@ -318,7 +170,7 @@ var InstrumentFilesCommand = class extends S.TaggedClass()("InstrumentFilesComma
|
|
|
318
170
|
};
|
|
319
171
|
var InstrumentResult = class extends S.TaggedClass()("InstrumentResult", {
|
|
320
172
|
files: S.Array(FileSchema),
|
|
321
|
-
mutants: S.Array(Mutant),
|
|
173
|
+
mutants: S.Array(Mutant.Mutant),
|
|
322
174
|
skipped: S.Array(InstrumentFileSkip)
|
|
323
175
|
}) {};
|
|
324
176
|
const PlacerNameSchema = S.Literals([
|
|
@@ -359,6 +211,68 @@ var MutantsUnplaced = class extends S.TaggedError()("MutantsUnplaced", {
|
|
|
359
211
|
};
|
|
360
212
|
var PlacementRefused = class extends S.TaggedError()("PlacementRefused", { message: S.String }) {};
|
|
361
213
|
//#endregion
|
|
214
|
+
//#region src/relational-sufficient-sets.ts
|
|
215
|
+
/**
|
|
216
|
+
* The sufficient sets of non-redundant relational-operator mutations.
|
|
217
|
+
*
|
|
218
|
+
* Source: René Just and Franz Schweiggert, "Higher Accuracy and Lower Run Time:
|
|
219
|
+
* Efficient Mutation Analysis Using Non-Redundant Mutation Operators", Journal
|
|
220
|
+
* of Software Testing, Verification and Reliability 24(5), 2014, Table III
|
|
221
|
+
* (https://homes.cs.washington.edu/~rjust/publ/non_redundant_mutants_jstvr_2014.pdf).
|
|
222
|
+
*
|
|
223
|
+
* The paper groups every input tuple into the three intervals `a < b`,
|
|
224
|
+
* `a == b`, and `a > b`. For each relational operator exactly one
|
|
225
|
+
* minimal-distance mutation covers each interval, and those three mutations
|
|
226
|
+
* subsume every other relational replacement. Each row below lists the two
|
|
227
|
+
* replacement operators and the boolean the whole comparison collapses to. The
|
|
228
|
+
* sets assume numeric operands without `NaN`; `mutantSetPolicy: 'full'`
|
|
229
|
+
* restores every variant.
|
|
230
|
+
*/
|
|
231
|
+
const RELATIONAL_OPERATORS = [
|
|
232
|
+
"<",
|
|
233
|
+
"<=",
|
|
234
|
+
">",
|
|
235
|
+
">="
|
|
236
|
+
];
|
|
237
|
+
const isRelationalOperator = (operator) => RELATIONAL_OPERATORS.some((relational) => relational === operator);
|
|
238
|
+
const SUFFICIENT_RELATIONAL_SETS = {
|
|
239
|
+
"<": {
|
|
240
|
+
replacements: ["<=", "!="],
|
|
241
|
+
literal: false
|
|
242
|
+
},
|
|
243
|
+
"<=": {
|
|
244
|
+
replacements: ["<", "=="],
|
|
245
|
+
literal: true
|
|
246
|
+
},
|
|
247
|
+
">": {
|
|
248
|
+
replacements: [">=", "!="],
|
|
249
|
+
literal: false
|
|
250
|
+
},
|
|
251
|
+
">=": {
|
|
252
|
+
replacements: [">", "=="],
|
|
253
|
+
literal: true
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region src/Location.schema.ts
|
|
258
|
+
const Offset = S.Int.check(S.isGreaterThanOrEqualTo(0));
|
|
259
|
+
const OffsetEnds = S.Struct({
|
|
260
|
+
start: Offset,
|
|
261
|
+
end: Offset
|
|
262
|
+
});
|
|
263
|
+
const Span = S.declare((value) => S.is(OffsetEnds)(value) && Mutant.notReversed(Order.Number)(value), {
|
|
264
|
+
expected: "a span whose end is not before its start",
|
|
265
|
+
toCodecArbitrary: () => S.link()(OffsetEnds, {
|
|
266
|
+
decode: SchemaGetter.transform(Mutant.inOrder(Order.Number)),
|
|
267
|
+
encode: SchemaGetter.transform((ends) => ends)
|
|
268
|
+
})
|
|
269
|
+
});
|
|
270
|
+
const ScriptOrigin = S.Struct({
|
|
271
|
+
line: Mutant.Line,
|
|
272
|
+
columnShift: Offset
|
|
273
|
+
});
|
|
274
|
+
S.is(Offset), S.is(Span);
|
|
275
|
+
//#endregion
|
|
362
276
|
//#region src/Location.ts
|
|
363
277
|
const LINE_TERMINATOR = /\r\n|[\n\r\u2028\u2029]/g;
|
|
364
278
|
const lineStartsOf = (text) => [0, ...[...text.matchAll(LINE_TERMINATOR)].map((match) => match.index + match[0].length)];
|
|
@@ -386,7 +300,7 @@ const positionAtOffset = (lineStarts, offset, low, high, start) => Boolean$1.mat
|
|
|
386
300
|
})
|
|
387
301
|
});
|
|
388
302
|
const positionAt = dual(2, (lineStarts, offset) => positionAtOffset(lineStarts, offset, 0, lineStarts.length - 1, 0));
|
|
389
|
-
const locationOf = dual(2, (lineStarts, span) => Location.make({
|
|
303
|
+
const locationOf = dual(2, (lineStarts, span) => Mutant.Location.make({
|
|
390
304
|
start: positionAt(lineStarts, span.start),
|
|
391
305
|
end: positionAt(lineStarts, span.end)
|
|
392
306
|
}));
|
|
@@ -1423,7 +1337,7 @@ function toApiMutant(mutant) {
|
|
|
1423
1337
|
mutatorName: mutant.mutatorName,
|
|
1424
1338
|
replacement: mutant.replacementCode
|
|
1425
1339
|
};
|
|
1426
|
-
return S.decodeResult(Mutant)(mutant.ignoreReason === void 0 ? baseFields : {
|
|
1340
|
+
return S.decodeResult(Mutant.Mutant)(mutant.ignoreReason === void 0 ? baseFields : {
|
|
1427
1341
|
...baseFields,
|
|
1428
1342
|
statusReason: mutant.ignoreReason,
|
|
1429
1343
|
status: "Ignored"
|
|
@@ -1844,9 +1758,19 @@ const booleanOperators = Object.freeze([
|
|
|
1844
1758
|
">=",
|
|
1845
1759
|
"||"
|
|
1846
1760
|
]);
|
|
1847
|
-
const conditionalExpressionMutator = (node, context) => Match.value(isTestOfLoop(node, context)).pipe(Match.when(true, () => [booleanLiteral(false)]), Match.orElse(() => conditionTestMutants(node, context)));
|
|
1761
|
+
const conditionalExpressionMutator = (node, context) => Match.value(isTestOfLoop(node, context)).pipe(Match.when(true, () => withSufficientConditional(node, context, [booleanLiteral(false)])), Match.orElse(() => conditionTestMutants(node, context)));
|
|
1848
1762
|
function conditionTestMutants(node, context) {
|
|
1849
|
-
return Match.value(isTestOfCondition(node, context)).pipe(Match.when(true, () => [booleanLiteral(true), booleanLiteral(false)]), Match.orElse(() => booleanExpressionMutants(node, context)));
|
|
1763
|
+
return Match.value(isTestOfCondition(node, context)).pipe(Match.when(true, () => withSufficientConditional(node, context, [booleanLiteral(true), booleanLiteral(false)])), Match.orElse(() => booleanExpressionMutants(node, context)));
|
|
1764
|
+
}
|
|
1765
|
+
function withSufficientConditional(node, context, replacements) {
|
|
1766
|
+
const literal = sufficientRelationalLiteral(node, context);
|
|
1767
|
+
return literal === void 0 ? replacements : appendBooleanLiteral(replacements, literal);
|
|
1768
|
+
}
|
|
1769
|
+
function sufficientRelationalLiteral(node, context) {
|
|
1770
|
+
return Option.getOrUndefined(Option.map(Option.filter(Option.fromNullishOr(relationalSiteFacts(node, context)), (facts) => [facts.inConditionPosition, context.mutantSetPolicy === "default"].every(Boolean)), (facts) => SUFFICIENT_RELATIONAL_SETS[facts.operator].literal));
|
|
1771
|
+
}
|
|
1772
|
+
function appendBooleanLiteral(replacements, literal) {
|
|
1773
|
+
return replacements.some((replacement) => isBooleanLiteral(replacement) && replacement.value === literal) ? replacements : [...replacements, booleanLiteral(literal)];
|
|
1850
1774
|
}
|
|
1851
1775
|
function booleanExpressionMutants(node, context) {
|
|
1852
1776
|
return Match.value(isBooleanExpression(node)).pipe(Match.when(true, () => booleanExpressionReplacements(context)), Match.orElse(() => statementMutants(node)));
|
|
@@ -1909,6 +1833,40 @@ function isTestBearingStatement(node) {
|
|
|
1909
1833
|
function testOfStatement(node) {
|
|
1910
1834
|
return Match.value(node).pipe(Match.when(isTestBearingStatement, (statement) => statement.test ?? void 0), Match.orElse(() => void 0));
|
|
1911
1835
|
}
|
|
1836
|
+
const isConditionalExpression = (node) => node.type === "ConditionalExpression";
|
|
1837
|
+
function isTestOfConditionalExpression(node, context) {
|
|
1838
|
+
return Option.exists(Option.filter(Option.fromNullishOr(context.parent), isConditionalExpression), (parent) => parent.test === node);
|
|
1839
|
+
}
|
|
1840
|
+
function isConditionPosition(node, context) {
|
|
1841
|
+
return [
|
|
1842
|
+
isTestOfLoop(node, context),
|
|
1843
|
+
isTestOfCondition(node, context),
|
|
1844
|
+
isTestOfConditionalExpression(node, context)
|
|
1845
|
+
].some(Boolean);
|
|
1846
|
+
}
|
|
1847
|
+
function binaryOperatorOf(node) {
|
|
1848
|
+
return node.type === "BinaryExpression" ? node.operator : void 0;
|
|
1849
|
+
}
|
|
1850
|
+
const isRelationalComparison = (node) => Option.exists(Option.fromNullishOr(binaryOperatorOf(node)), isRelationalOperator);
|
|
1851
|
+
const relationalSiteFactsDataFirst = (node, context) => Option.getOrUndefined(Option.map(Option.filter(Option.fromNullishOr(binaryOperatorOf(node)), isRelationalOperator), (operator) => ({
|
|
1852
|
+
operator,
|
|
1853
|
+
inConditionPosition: isConditionPosition(node, context)
|
|
1854
|
+
})));
|
|
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
|
+
}
|
|
1912
1870
|
function isBooleanExpression(node) {
|
|
1913
1871
|
return isOperatorExpression(node) && booleanOperators.includes(node.operator);
|
|
1914
1872
|
}
|
|
@@ -1926,12 +1884,23 @@ const operators = {
|
|
|
1926
1884
|
"!==": ["==="]
|
|
1927
1885
|
};
|
|
1928
1886
|
const EQUALITY_OPERATOR_KEYS = Object.keys(operators);
|
|
1929
|
-
const equalityOperatorMutator = (node) => Match.value(node).pipe(Match.when(isEqualityBinary, (binary) => mutatedEqualityOperators(binary)), Match.orElse(() => NO_MUTANTS));
|
|
1887
|
+
const equalityOperatorMutator = (node, context) => Match.value(node).pipe(Match.when(isEqualityBinary, (binary) => mutatedEqualityOperators(binary, context)), Match.orElse(() => NO_MUTANTS));
|
|
1930
1888
|
function isEqualityBinary(node) {
|
|
1931
1889
|
return node.type === "BinaryExpression" && EQUALITY_OPERATOR_KEYS.includes(node.operator);
|
|
1932
1890
|
}
|
|
1933
|
-
function mutatedEqualityOperators(binary) {
|
|
1934
|
-
|
|
1891
|
+
function mutatedEqualityOperators(binary, context) {
|
|
1892
|
+
const base = operators[binary.operator].map((operator) => withOperator(binary, operator));
|
|
1893
|
+
return isRelationalConditionSite(binary, context) ? [...base, ...sufficientAdditions(binary)] : base;
|
|
1894
|
+
}
|
|
1895
|
+
function isRelationalConditionSite(node, context) {
|
|
1896
|
+
return [
|
|
1897
|
+
context.mutantSetPolicy === "default",
|
|
1898
|
+
isRelationalComparison(node),
|
|
1899
|
+
isConditionPosition(node, context)
|
|
1900
|
+
].every(Boolean);
|
|
1901
|
+
}
|
|
1902
|
+
function sufficientAdditions(binary) {
|
|
1903
|
+
return isRelationalOperator(binary.operator) ? SUFFICIENT_RELATIONAL_SETS[binary.operator].replacements.filter((operator) => !operators[binary.operator].some((replacement) => replacement === operator)).map((operator) => withOperator(binary, operator)) : NO_MUTANTS;
|
|
1935
1904
|
}
|
|
1936
1905
|
const logicalOperatorReplacements = Object.freeze({
|
|
1937
1906
|
"&&": "||",
|
|
@@ -2214,6 +2183,27 @@ const optInMutators = Object.freeze({
|
|
|
2214
2183
|
SynchronizationRemoval: synchronizationRemovalMutator,
|
|
2215
2184
|
FinalizerEscape: finalizerEscapeMutator
|
|
2216
2185
|
});
|
|
2186
|
+
const stockRegistry = {
|
|
2187
|
+
defaults: defaultMutators,
|
|
2188
|
+
optIn: optInMutators
|
|
2189
|
+
};
|
|
2190
|
+
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
|
+
]);
|
|
2196
|
+
const entries = catalogs.flatMap((catalog) => catalog.entries);
|
|
2197
|
+
const entriesOfTier = (tier) => entries.filter((entry) => entry.tier === tier).flatMap((entry) => Option.match(Option.fromNullishOr(implementations.get(entry.name)), {
|
|
2198
|
+
onNone: () => [],
|
|
2199
|
+
onSome: (implementation) => [[entry.name, implementation]]
|
|
2200
|
+
}));
|
|
2201
|
+
return {
|
|
2202
|
+
defaults: Object.fromEntries(entriesOfTier("default")),
|
|
2203
|
+
optIn: Object.fromEntries(entriesOfTier("optIn"))
|
|
2204
|
+
};
|
|
2205
|
+
};
|
|
2206
|
+
const registryOf = dual((args) => args.length >= 2, registryOfDataFirst);
|
|
2217
2207
|
/**
|
|
2218
2208
|
* The entries a run applies, and the names its directives may reference.
|
|
2219
2209
|
*
|
|
@@ -2237,7 +2227,7 @@ const selectMutators = dual((args) => args.length >= 2, selectMutatorsDataFirst)
|
|
|
2237
2227
|
var ParseFailed = class extends S.TaggedError("@systemfsoftware/stryker-js-instrumenter/Parser.schema/ParseFailed")("ParseFailed", {
|
|
2238
2228
|
fileName: S.String,
|
|
2239
2229
|
message: S.String,
|
|
2240
|
-
location: Position,
|
|
2230
|
+
location: Mutant.Position,
|
|
2241
2231
|
cause: S.Defect()
|
|
2242
2232
|
}) {
|
|
2243
2233
|
get message() {
|
|
@@ -2248,7 +2238,7 @@ var ParseFailed = class extends S.TaggedError("@systemfsoftware/stryker-js-instr
|
|
|
2248
2238
|
//#region src/Parser.service.ts
|
|
2249
2239
|
const oxcModule = Effect.cached(Effect.promise(() => import("oxc-parser")));
|
|
2250
2240
|
const loadOxc = Effect.flatMap(oxcModule, (load) => load);
|
|
2251
|
-
const parseWithOxcDataFirst = Effect.fn(
|
|
2241
|
+
const parseWithOxcDataFirst = Effect.fn(SpanTaxonomy.Spans.instrumentParserParseWithOxc.name)(function* (text, fileName, lang) {
|
|
2252
2242
|
const result = (yield* loadOxc).parseSync(fileName, text, {
|
|
2253
2243
|
lang,
|
|
2254
2244
|
range: true
|
|
@@ -2275,7 +2265,7 @@ const oxcParseFailure = (errors, fileName, lineStarts) => Option.match(Option.fr
|
|
|
2275
2265
|
});
|
|
2276
2266
|
const oxcErrorLabelStart = (error) => Option.getOrElse(Option.map(Option.fromNullishOr(error.labels.at(0)), (label) => label.start), () => 0);
|
|
2277
2267
|
const createParser = () => ({ parse: (code, fileName, scriptFormat) => Match.value(scriptFormat).pipe(Match.when("js", () => parseJS(code, fileName)), Match.when("ts", () => parseTS(code, fileName)), Match.when("tsx", () => parseTsx(code, fileName)), Match.exhaustive) });
|
|
2278
|
-
const parseJSDataFirst = Effect.fn(
|
|
2268
|
+
const parseJSDataFirst = Effect.fn(SpanTaxonomy.Spans.instrumentParserParseJS.name)(function* (text, fileName) {
|
|
2279
2269
|
const parsed = yield* parseWithOxc(text, fileName, "js");
|
|
2280
2270
|
return {
|
|
2281
2271
|
originFileName: fileName,
|
|
@@ -2286,7 +2276,7 @@ const parseJSDataFirst = Effect.fn("stryker.instrument.parser.parseJS")(function
|
|
|
2286
2276
|
};
|
|
2287
2277
|
});
|
|
2288
2278
|
const parseJS = dual((args) => args.length >= 2, parseJSDataFirst);
|
|
2289
|
-
const parseTSDataFirst = Effect.fn(
|
|
2279
|
+
const parseTSDataFirst = Effect.fn(SpanTaxonomy.Spans.instrumentParserParseTS.name)(function* (text, fileName) {
|
|
2290
2280
|
const parsed = yield* parseWithOxc(text, fileName, "ts");
|
|
2291
2281
|
return {
|
|
2292
2282
|
originFileName: fileName,
|
|
@@ -2297,7 +2287,7 @@ const parseTSDataFirst = Effect.fn("stryker.instrument.parser.parseTS")(function
|
|
|
2297
2287
|
};
|
|
2298
2288
|
});
|
|
2299
2289
|
const parseTS = dual((args) => args.length >= 2, parseTSDataFirst);
|
|
2300
|
-
const parseTsxDataFirst = Effect.fn(
|
|
2290
|
+
const parseTsxDataFirst = Effect.fn(SpanTaxonomy.Spans.instrumentParserParseTsx.name)(function* (text, fileName) {
|
|
2301
2291
|
const parsed = yield* parseWithOxc(text, fileName, "tsx");
|
|
2302
2292
|
return {
|
|
2303
2293
|
root: parsed.root,
|
|
@@ -2782,7 +2772,7 @@ const logicalExpressionText = (ctx, node, prec) => {
|
|
|
2782
2772
|
};
|
|
2783
2773
|
const conditionalExpressionText = (ctx, node, prec) => {
|
|
2784
2774
|
const myPrec = PREC.Conditional;
|
|
2785
|
-
const testStr = wrapIfNeeded(ctx, node.test, precOf(node.test), myPrec,
|
|
2775
|
+
const testStr = wrapIfNeeded(ctx, node.test, precOf(node.test), myPrec, CONDITIONAL_TEST_WRAPPED_KINDS[node.test.type] === true);
|
|
2786
2776
|
const consStr = dispatchNode(ctx, node.consequent, PREC.Assignment);
|
|
2787
2777
|
const altStr = dispatchNode(ctx, node.alternate, PREC.Assignment);
|
|
2788
2778
|
return parenthesizedIf(myPrec < prec, `${testStr} ? ${consStr} : ${altStr}`);
|
|
@@ -3180,6 +3170,7 @@ const MEMBER_OBJECT_WRAPPED_KINDS = {
|
|
|
3180
3170
|
AwaitExpression: true,
|
|
3181
3171
|
YieldExpression: true
|
|
3182
3172
|
};
|
|
3173
|
+
const CONDITIONAL_TEST_WRAPPED_KINDS = { ConditionalExpression: true };
|
|
3183
3174
|
const CALLEE_WRAPPED_KINDS = {
|
|
3184
3175
|
SequenceExpression: true,
|
|
3185
3176
|
ConditionalExpression: true
|
|
@@ -3400,9 +3391,117 @@ const resolveFormat = Workflow.make({
|
|
|
3400
3391
|
error: FormatOverrideUnclaimed,
|
|
3401
3392
|
decide: (command) => Match.value(selectionFor(command)).pipe(Match.tag("ClaimSelected", ({ claim }) => Result.succeed(assignedFrom(claim, command))), Match.tag("PinUnclaimed", ({ formatId }) => Result.fail(overrideUnclaimedFrom(command, formatId))), Match.tag("ExtensionUnclaimed", () => Result.succeed(skippedFrom(command))), Match.exhaustive)
|
|
3402
3393
|
});
|
|
3394
|
+
const AridRuleId = S.Literals([
|
|
3395
|
+
"arid-logging",
|
|
3396
|
+
"arid-telemetry",
|
|
3397
|
+
"arid-time",
|
|
3398
|
+
"arid-config-default",
|
|
3399
|
+
"arid-memoization"
|
|
3400
|
+
]);
|
|
3401
|
+
const AridCalleeSchema = S.Struct({
|
|
3402
|
+
object: S.String,
|
|
3403
|
+
member: S.String
|
|
3404
|
+
});
|
|
3405
|
+
const AridFrameSchema = S.Struct({
|
|
3406
|
+
callee: S.Option(AridCalleeSchema),
|
|
3407
|
+
childIsArgument: S.Boolean
|
|
3408
|
+
});
|
|
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
|
+
const ARID_RULES_IN_PRECEDENCE_ORDER = [
|
|
3415
|
+
{
|
|
3416
|
+
ruleId: "arid-logging",
|
|
3417
|
+
matches: (callee) => readsMemberPrefixed(callee, "Effect", "log")
|
|
3418
|
+
},
|
|
3419
|
+
{
|
|
3420
|
+
ruleId: "arid-logging",
|
|
3421
|
+
matches: (callee) => NAMED_LOGGERS.includes(callee.object)
|
|
3422
|
+
},
|
|
3423
|
+
{
|
|
3424
|
+
ruleId: "arid-telemetry",
|
|
3425
|
+
matches: (callee) => readsMember(callee, "Effect", "withSpan")
|
|
3426
|
+
},
|
|
3427
|
+
{
|
|
3428
|
+
ruleId: "arid-telemetry",
|
|
3429
|
+
matches: (callee) => readsMemberPrefixed(callee, "Effect", "annotate")
|
|
3430
|
+
},
|
|
3431
|
+
{
|
|
3432
|
+
ruleId: "arid-telemetry",
|
|
3433
|
+
matches: (callee) => readsMember(callee, "Effect", "withLogSpan")
|
|
3434
|
+
},
|
|
3435
|
+
{
|
|
3436
|
+
ruleId: "arid-telemetry",
|
|
3437
|
+
matches: (callee) => readsNamespace(callee, "Metric")
|
|
3438
|
+
},
|
|
3439
|
+
{
|
|
3440
|
+
ruleId: "arid-time",
|
|
3441
|
+
matches: (callee) => readsMember(callee, "Effect", "sleep")
|
|
3442
|
+
},
|
|
3443
|
+
{
|
|
3444
|
+
ruleId: "arid-time",
|
|
3445
|
+
matches: (callee) => readsNamespace(callee, "Schedule")
|
|
3446
|
+
},
|
|
3447
|
+
{
|
|
3448
|
+
ruleId: "arid-time",
|
|
3449
|
+
matches: (callee) => readsNamespace(callee, "Duration")
|
|
3450
|
+
},
|
|
3451
|
+
{
|
|
3452
|
+
ruleId: "arid-time",
|
|
3453
|
+
matches: (callee) => readsMember(callee, "Date", "now")
|
|
3454
|
+
},
|
|
3455
|
+
{
|
|
3456
|
+
ruleId: "arid-config-default",
|
|
3457
|
+
matches: (callee) => readsMember(callee, "Config", "withDefault")
|
|
3458
|
+
},
|
|
3459
|
+
{
|
|
3460
|
+
ruleId: "arid-memoization",
|
|
3461
|
+
matches: (callee) => readsMember(callee, "Effect", "cached")
|
|
3462
|
+
},
|
|
3463
|
+
{
|
|
3464
|
+
ruleId: "arid-memoization",
|
|
3465
|
+
matches: (callee) => readsMember(callee, "Effect", "cachedWithTTL")
|
|
3466
|
+
}
|
|
3467
|
+
];
|
|
3468
|
+
const ruleFor = (callee) => Option.map(Option.fromNullishOr(ARID_RULES_IN_PRECEDENCE_ORDER.find((rule) => rule.matches(callee))), (rule) => rule.ruleId);
|
|
3469
|
+
const calleeDetail = (callee) => `${callee.object}.${callee.member}`;
|
|
3470
|
+
var AridCodeCommand = class extends S.TaggedClass()("AridCodeCommand", {
|
|
3471
|
+
policy: Options.MutantSetPolicy,
|
|
3472
|
+
frames: S.Array(AridFrameSchema)
|
|
3473
|
+
}) {
|
|
3474
|
+
static [Workflow.InstrumentationBrand] = {};
|
|
3475
|
+
};
|
|
3476
|
+
const AridCodeDecisionTypeId = Symbol.for("@systemfsoftware/stryker-js-instrumenter/AridCodeDecision");
|
|
3477
|
+
var AridSuppressed = class extends S.TaggedClass()("AridSuppressed", {
|
|
3478
|
+
ruleId: AridRuleId,
|
|
3479
|
+
detail: S.String
|
|
3480
|
+
}) {
|
|
3481
|
+
[AridCodeDecisionTypeId] = AridCodeDecisionTypeId;
|
|
3482
|
+
};
|
|
3483
|
+
var AridKept = class extends S.TaggedClass()("AridKept", {}) {
|
|
3484
|
+
[AridCodeDecisionTypeId] = AridCodeDecisionTypeId;
|
|
3485
|
+
};
|
|
3486
|
+
const matchOfFrame = (frame) => Option.flatMap(Option.filter(frame.callee, () => frame.childIsArgument), (callee) => Option.map(ruleFor(callee), (ruleId) => ({
|
|
3487
|
+
ruleId,
|
|
3488
|
+
callee
|
|
3489
|
+
})));
|
|
3490
|
+
const firstMatch = (frames) => Option.firstSomeOf(frames.map(matchOfFrame));
|
|
3491
|
+
const defaultPolicyDecision = (frames) => Match.value(firstMatch(frames)).pipe(Match.when(Option.isSome, (found) => AridSuppressed.make({
|
|
3492
|
+
ruleId: found.value.ruleId,
|
|
3493
|
+
detail: calleeDetail(found.value.callee)
|
|
3494
|
+
})), Match.when(Option.isNone, () => AridKept.make({})), Match.exhaustive);
|
|
3495
|
+
const decisionFor = (command) => Match.value(command.policy).pipe(Match.when("default", () => defaultPolicyDecision(command.frames)), Match.when("full", () => AridKept.make({})), Match.exhaustive);
|
|
3496
|
+
const aridCode = Workflow.make({
|
|
3497
|
+
command: AridCodeCommand,
|
|
3498
|
+
decision: S.Union([AridSuppressed, AridKept]),
|
|
3499
|
+
error: S.Never,
|
|
3500
|
+
decide: (command) => Result.succeed(decisionFor(command))
|
|
3501
|
+
});
|
|
3403
3502
|
//#endregion
|
|
3404
3503
|
//#region src/directives/decode-directive.workflow.ts
|
|
3405
|
-
const DIRECTIVE_PATTERN = /^\s?Stryker (disable|restore)(?: (next-line))? ([a-zA-
|
|
3504
|
+
const DIRECTIVE_PATTERN = /^\s?Stryker (disable|restore)(?: (next-line))? ([a-zA-Z0-9,/ -]+)(?::(.+)?)?/;
|
|
3406
3505
|
const DEFAULT_REASON = "Ignored using a comment";
|
|
3407
3506
|
const NEXT_LINE$1 = "next-line";
|
|
3408
3507
|
var DecodeDirectiveCommand = class extends S.TaggedClass()("DecodeDirectiveCommand", { commentText: S.String }) {
|
|
@@ -3456,6 +3555,23 @@ const foldRule = Workflow.make({
|
|
|
3456
3555
|
decide: (command) => Match.value(command.directive.directive.action).pipe(Match.when("restore", () => Result.succeed(RestoreFolded.make({ rule: [...command.rule, command.directive] }))), Match.when("disable", () => Result.succeed(DisableFolded.make({ rule: [...command.rule, command.directive] }))), Match.exhaustive)
|
|
3457
3556
|
});
|
|
3458
3557
|
//#endregion
|
|
3558
|
+
//#region src/InstrumentContext.schema.ts
|
|
3559
|
+
var InstrumenterContext = class extends S.Class("InstrumenterContext")({
|
|
3560
|
+
activeMutant: S.optional(S.String),
|
|
3561
|
+
currentTestId: S.optional(S.String),
|
|
3562
|
+
mutantCoverage: S.optional(Mutant.MutantCoverageSchema),
|
|
3563
|
+
hitCount: S.optional(Mutant.HitCount),
|
|
3564
|
+
hitLimit: S.optional(Mutant.HitCount)
|
|
3565
|
+
}) {
|
|
3566
|
+
static NAMESPACE = "__stryker__";
|
|
3567
|
+
static MUTATION_COVERAGE_OBJECT = "mutantCoverage";
|
|
3568
|
+
static ACTIVE_MUTANT = "activeMutant";
|
|
3569
|
+
static CURRENT_TEST_ID = "currentTestId";
|
|
3570
|
+
static HIT_COUNT = "hitCount";
|
|
3571
|
+
static HIT_LIMIT = "hitLimit";
|
|
3572
|
+
static ACTIVE_MUTANT_ENV_VARIABLE = "__STRYKER_ACTIVE_MUTANT__";
|
|
3573
|
+
};
|
|
3574
|
+
//#endregion
|
|
3459
3575
|
//#region src/InstrumentHeader.ts
|
|
3460
3576
|
const hasLeadingComments$1 = (value) => Predicate.hasProperty(value, "leadingComments") && Array.isArray(value.leadingComments);
|
|
3461
3577
|
const STRYKER_NAMESPACE_HELPER = "stryNS_9fa48";
|
|
@@ -3542,6 +3658,18 @@ function commentedHeader(firstHeader, leadingComments, rest) {
|
|
|
3542
3658
|
return [cloned, ...rest];
|
|
3543
3659
|
}
|
|
3544
3660
|
//#endregion
|
|
3661
|
+
//#region src/MutantIdentity.ts
|
|
3662
|
+
const MUTANT_ID_HEX_LENGTH = 16;
|
|
3663
|
+
const lengthPrefixed = (value) => `${value.length}:${value}`;
|
|
3664
|
+
const tupleFields = (tuple) => [
|
|
3665
|
+
tuple.fileName,
|
|
3666
|
+
tuple.mutatorName,
|
|
3667
|
+
tuple.originalCode,
|
|
3668
|
+
tuple.replacementCode
|
|
3669
|
+
];
|
|
3670
|
+
const mutantTupleKey = (tuple) => tupleFields(tuple).map(lengthPrefixed).join("");
|
|
3671
|
+
const mutantIdOf = (identity) => Mutant.MutantId.make(bytesToHex(sha256(utf8ToBytes([...tupleFields(identity), `${identity.ordinal}`].map(lengthPrefixed).join("")))).slice(0, MUTANT_ID_HEX_LENGTH));
|
|
3672
|
+
//#endregion
|
|
3545
3673
|
//#region src/place-mutants.workflow.ts
|
|
3546
3674
|
const PlacementFactsSchema = S.Struct({
|
|
3547
3675
|
isExpression: S.Boolean,
|
|
@@ -3555,7 +3683,7 @@ const ReplacementFactsSchema = S.Struct({
|
|
|
3555
3683
|
isSwitchCase: S.Boolean
|
|
3556
3684
|
});
|
|
3557
3685
|
const PlacedMutantSchema = S.Struct({
|
|
3558
|
-
id: MutantId,
|
|
3686
|
+
id: Mutant.MutantId,
|
|
3559
3687
|
mutatorName: MutatorNameSchema,
|
|
3560
3688
|
replacement: ReplacementFactsSchema
|
|
3561
3689
|
});
|
|
@@ -3569,19 +3697,19 @@ var PlaceMutantsCommand = class extends S.TaggedClass()("PlaceMutantsCommand", {
|
|
|
3569
3697
|
const PlacementDecisionTypeId = Symbol.for("@systemfsoftware/stryker-js-instrumenter/PlacementDecision");
|
|
3570
3698
|
var ExpressionSite = class extends S.TaggedClass()("ExpressionSite", {
|
|
3571
3699
|
fileName: S.String,
|
|
3572
|
-
mutantIds: S.Array(MutantId)
|
|
3700
|
+
mutantIds: S.Array(Mutant.MutantId)
|
|
3573
3701
|
}) {
|
|
3574
3702
|
[PlacementDecisionTypeId] = PlacementDecisionTypeId;
|
|
3575
3703
|
};
|
|
3576
3704
|
var StatementSite = class extends S.TaggedClass()("StatementSite", {
|
|
3577
3705
|
fileName: S.String,
|
|
3578
|
-
mutantIds: S.Array(MutantId)
|
|
3706
|
+
mutantIds: S.Array(Mutant.MutantId)
|
|
3579
3707
|
}) {
|
|
3580
3708
|
[PlacementDecisionTypeId] = PlacementDecisionTypeId;
|
|
3581
3709
|
};
|
|
3582
3710
|
var SwitchCaseSite = class extends S.TaggedClass()("SwitchCaseSite", {
|
|
3583
3711
|
fileName: S.String,
|
|
3584
|
-
mutantIds: S.Array(MutantId)
|
|
3712
|
+
mutantIds: S.Array(Mutant.MutantId)
|
|
3585
3713
|
}) {
|
|
3586
3714
|
[PlacementDecisionTypeId] = PlacementDecisionTypeId;
|
|
3587
3715
|
};
|
|
@@ -3593,7 +3721,7 @@ const ExpectedKindSchema = S.Literals([
|
|
|
3593
3721
|
var MutantKindMismatch = class extends S.TaggedError()("MutantKindMismatch", {
|
|
3594
3722
|
fileName: S.String,
|
|
3595
3723
|
placer: PlacerNameSchema,
|
|
3596
|
-
mutantId: MutantId,
|
|
3724
|
+
mutantId: Mutant.MutantId,
|
|
3597
3725
|
mutatorName: MutatorNameSchema,
|
|
3598
3726
|
expected: ExpectedKindSchema
|
|
3599
3727
|
}) {
|
|
@@ -3654,53 +3782,129 @@ const placeMutants = Workflow.make({
|
|
|
3654
3782
|
})
|
|
3655
3783
|
});
|
|
3656
3784
|
//#endregion
|
|
3785
|
+
//#region src/mutant-set-policy.workflow.ts
|
|
3786
|
+
const MutantSetFactsSchema = S.Struct({
|
|
3787
|
+
originalCode: S.String,
|
|
3788
|
+
replacementCode: S.String,
|
|
3789
|
+
relationalSufficient: S.Boolean
|
|
3790
|
+
});
|
|
3791
|
+
const MutantSetRuleId = S.Literals([
|
|
3792
|
+
"redundant-relational",
|
|
3793
|
+
"equivalent-to-original",
|
|
3794
|
+
"duplicate-at-site"
|
|
3795
|
+
]);
|
|
3796
|
+
var MutantSetPolicyCommand = class extends S.TaggedClass()("MutantSetPolicyCommand", {
|
|
3797
|
+
policy: Options.MutantSetPolicy,
|
|
3798
|
+
candidates: S.Array(MutantSetFactsSchema)
|
|
3799
|
+
}) {
|
|
3800
|
+
static [Workflow.InstrumentationBrand] = {};
|
|
3801
|
+
};
|
|
3802
|
+
const MutantSetOutcomeTypeId = Symbol.for("@systemfsoftware/stryker-js-instrumenter/MutantSetOutcome");
|
|
3803
|
+
var MutantSuppressed = class extends S.TaggedClass()("MutantSuppressed", {
|
|
3804
|
+
ruleId: MutantSetRuleId,
|
|
3805
|
+
detail: S.String
|
|
3806
|
+
}) {
|
|
3807
|
+
[MutantSetOutcomeTypeId] = MutantSetOutcomeTypeId;
|
|
3808
|
+
};
|
|
3809
|
+
var MutantKept = class extends S.TaggedClass()("MutantKept", {}) {
|
|
3810
|
+
[MutantSetOutcomeTypeId] = MutantSetOutcomeTypeId;
|
|
3811
|
+
};
|
|
3812
|
+
const MutantSetOutcome = S.Union([MutantSuppressed, MutantKept]);
|
|
3813
|
+
const MutantSetDecision = S.Array(MutantSetOutcome);
|
|
3814
|
+
const canonicalForm = (text) => stripOuterParentheses(text.trim());
|
|
3815
|
+
const stripOuterParentheses = (text) => Match.value(wrapsWhole(text)).pipe(Match.when(true, () => stripOuterParentheses(text.slice(1, -1).trim())), Match.orElse(() => text));
|
|
3816
|
+
const parenDepthDelta = (character) => Match.value(character).pipe(Match.when("(", () => 1), Match.when(")", () => -1), Match.orElse(() => 0));
|
|
3817
|
+
const depthsAfter = (text) => text.split("").reduce((depths, character) => [...depths, Option.getOrElse(Arr.last(depths), () => 0) + parenDepthDelta(character)], []);
|
|
3818
|
+
const wrapsWhole = (text) => {
|
|
3819
|
+
const depths = depthsAfter(text);
|
|
3820
|
+
return [
|
|
3821
|
+
text.startsWith("("),
|
|
3822
|
+
text.endsWith(")"),
|
|
3823
|
+
depths.length > 0,
|
|
3824
|
+
depths.slice(0, -1).every((depth) => depth >= 1),
|
|
3825
|
+
Option.getOrElse(Arr.last(depths), () => 0) === 0
|
|
3826
|
+
].every(Boolean);
|
|
3827
|
+
};
|
|
3828
|
+
const defaultOnly = (policy, value) => Match.value(policy).pipe(Match.when("full", () => Option.none()), Match.when("default", value), Match.exhaustive);
|
|
3829
|
+
const printedAs = (code) => canonicalForm(code);
|
|
3830
|
+
const printsSomething = (code) => printedAs(code).length > 0;
|
|
3831
|
+
const samePrintedCode = (left, right) => [printsSomething(left), printedAs(left) === printedAs(right)].every(Boolean);
|
|
3832
|
+
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
|
+
const equivalentSuppression = (facts, policy) => defaultOnly(policy, () => Option.map(Option.filter(Option.some(facts), (candidate) => samePrintedCode(candidate.replacementCode, candidate.originalCode)), () => MutantSuppressed.make({
|
|
3838
|
+
ruleId: "equivalent-to-original",
|
|
3839
|
+
detail: `${printedAs(facts.replacementCode)} is the original code`
|
|
3840
|
+
})));
|
|
3841
|
+
const duplicateSuppression = (facts, earlier, policy) => defaultOnly(policy, () => Option.map(Option.filter(Option.some(facts), (candidate) => alreadyPrinted(candidate.replacementCode, earlier)), () => MutantSuppressed.make({
|
|
3842
|
+
ruleId: "duplicate-at-site",
|
|
3843
|
+
detail: `${printedAs(facts.replacementCode)} is already planted at this site`
|
|
3844
|
+
})));
|
|
3845
|
+
const outcomeOf = (facts, earlier, policy) => Option.getOrElse(Option.orElse(relationalSuppression(facts, policy), () => Option.orElse(equivalentSuppression(facts, policy), () => duplicateSuppression(facts, earlier, policy))), () => MutantKept.make({}));
|
|
3846
|
+
const foldCandidate = (state, facts, policy) => ({
|
|
3847
|
+
outcomes: [...state.outcomes, outcomeOf(facts, state.earlier, policy)],
|
|
3848
|
+
earlier: [...state.earlier, canonicalForm(facts.replacementCode)]
|
|
3849
|
+
});
|
|
3850
|
+
const outcomesFor = (command) => command.candidates.reduce((state, facts) => foldCandidate(state, facts, command.policy), {
|
|
3851
|
+
outcomes: [],
|
|
3852
|
+
earlier: []
|
|
3853
|
+
}).outcomes;
|
|
3854
|
+
const mutantSetPolicy = Workflow.make({
|
|
3855
|
+
command: MutantSetPolicyCommand,
|
|
3856
|
+
decision: MutantSetDecision,
|
|
3857
|
+
error: S.Never,
|
|
3858
|
+
decide: (command) => Result.succeed([...outcomesFor(command)])
|
|
3859
|
+
});
|
|
3860
|
+
//#endregion
|
|
3657
3861
|
//#region src/plan-mutants.workflow.ts
|
|
3658
3862
|
const WILDCARD = "all";
|
|
3659
3863
|
const NEXT_LINE = "next-line";
|
|
3864
|
+
const RULE_SEPARATOR = ": ";
|
|
3865
|
+
const policyWorkflow = mutantSetPolicy;
|
|
3866
|
+
const policyCommand = MutantSetPolicyCommand;
|
|
3867
|
+
const keptOutcome = MutantKept;
|
|
3868
|
+
const ignoreReasonFor = (ruleId, detail) => `${ruleId}${RULE_SEPARATOR}${detail}`;
|
|
3660
3869
|
const MutantCandidateSchema = S.Struct({
|
|
3870
|
+
id: Mutant.MutantId,
|
|
3661
3871
|
mutatorName: MutatorNameSchema,
|
|
3662
3872
|
replacementCode: S.String,
|
|
3663
|
-
location: S.optional(Location),
|
|
3664
|
-
ignorerReason: S.optional(S.String)
|
|
3873
|
+
location: S.optional(Mutant.Location),
|
|
3874
|
+
ignorerReason: S.optional(S.String),
|
|
3875
|
+
mutantSet: MutantSetFactsSchema
|
|
3665
3876
|
});
|
|
3666
3877
|
const PlannedMutantSchema = S.Struct({
|
|
3667
|
-
id: MutantId,
|
|
3878
|
+
id: Mutant.MutantId,
|
|
3668
3879
|
mutatorName: MutatorNameSchema,
|
|
3669
3880
|
replacementCode: S.String,
|
|
3670
|
-
location: Location,
|
|
3881
|
+
location: Mutant.Location,
|
|
3671
3882
|
ignoreReason: S.optional(S.String)
|
|
3672
3883
|
});
|
|
3673
|
-
const MutantCounterSchema = S.Int.pipe(S.check(S.isBetween({
|
|
3674
|
-
minimum: 0,
|
|
3675
|
-
maximum: 1e6
|
|
3676
|
-
})));
|
|
3677
|
-
const MutantCountSchema = S.Int.pipe(S.check(S.isGreaterThanOrEqualTo(0)));
|
|
3678
3884
|
var PlanMutantsCommand = class extends S.TaggedClass()("PlanMutantsCommand", {
|
|
3679
3885
|
fileName: S.String,
|
|
3680
|
-
firstIndex: MutantCounterSchema,
|
|
3681
3886
|
offset: ScriptOrigin,
|
|
3682
|
-
line: Line,
|
|
3887
|
+
line: Mutant.Line,
|
|
3683
3888
|
mutatorNames: S.Array(MutatorNameSchema),
|
|
3684
3889
|
excludedMutations: S.Array(MutatorNameSchema),
|
|
3685
3890
|
rule: S.Array(LocatedDirectiveSchema),
|
|
3686
3891
|
directives: S.Array(LocatedDirectiveSchema),
|
|
3687
|
-
candidates: S.Array(MutantCandidateSchema)
|
|
3892
|
+
candidates: S.Array(MutantCandidateSchema),
|
|
3893
|
+
mutantSetPolicy: Options.MutantSetPolicy
|
|
3688
3894
|
}) {
|
|
3689
3895
|
static [Workflow.InstrumentationBrand] = {};
|
|
3690
3896
|
};
|
|
3691
3897
|
const MutantPlanTypeId = Symbol.for("@systemfsoftware/stryker-js-instrumenter/MutantPlan");
|
|
3692
3898
|
var MutantsPlanned = class extends S.TaggedClass()("MutantsPlanned", {
|
|
3693
3899
|
mutants: S.Array(PlannedMutantSchema),
|
|
3694
|
-
|
|
3695
|
-
warnings: S.Array(S.String)
|
|
3696
|
-
nextIndex: MutantCountSchema
|
|
3900
|
+
placeableIds: S.Array(Mutant.MutantId),
|
|
3901
|
+
warnings: S.Array(S.String)
|
|
3697
3902
|
}) {
|
|
3698
3903
|
[MutantPlanTypeId] = MutantPlanTypeId;
|
|
3699
3904
|
};
|
|
3700
3905
|
var MutantsFullyIgnored = class extends S.TaggedClass()("MutantsFullyIgnored", {
|
|
3701
3906
|
mutants: S.Array(PlannedMutantSchema),
|
|
3702
|
-
warnings: S.Array(S.String)
|
|
3703
|
-
nextIndex: MutantCountSchema
|
|
3907
|
+
warnings: S.Array(S.String)
|
|
3704
3908
|
}) {
|
|
3705
3909
|
[MutantPlanTypeId] = MutantPlanTypeId;
|
|
3706
3910
|
};
|
|
@@ -3715,9 +3919,19 @@ var MutantWithoutLocation = class extends S.TaggedError()("MutantWithoutLocation
|
|
|
3715
3919
|
const reachedLine = (located, line) => Match.value(located.directive.scope).pipe(Match.when(NEXT_LINE, () => located.governedLine === line), Match.when("block", () => true), Match.exhaustive);
|
|
3716
3920
|
const namesMutator = (located, lowerMutatorName) => located.directive.mutatorNames.some((name) => [name === WILDCARD, name.toLowerCase() === lowerMutatorName].some(Boolean));
|
|
3717
3921
|
const lastReachingDirective = (rule, lowerMutatorName, line) => Option.fromNullishOr(rule.findLast((located) => [reachedLine(located, line), namesMutator(located, lowerMutatorName)].every(Boolean)));
|
|
3718
|
-
const directiveReason = (rule, mutatorName, line) => Option.flatMap(lastReachingDirective(rule, mutatorName.toLowerCase(), line), (located) => Match.value(located.directive.action).pipe(Match.when("disable", () => Option.some(located.directive.reason)), Match.when("restore", () => Option.none()), Match.exhaustive));
|
|
3719
|
-
const exclusionReason = (excludedMutations, mutatorName) => Match.value(excludedMutations.includes(mutatorName)).pipe(Match.when(true, () => Option.some(`Ignored because of excluded mutation "${mutatorName}"`)), Match.when(false, () => Option.none()), Match.exhaustive);
|
|
3720
|
-
const
|
|
3922
|
+
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
|
+
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 ignorerReason = (candidate) => Option.map(Option.fromNullishOr(candidate.ignorerReason), (reason) => ignoreReasonFor("ignorer", reason));
|
|
3925
|
+
const ignoreReasonOf = (command, candidate, policyReason) => Option.getOrUndefined(Option.orElse(Option.orElse(Option.orElse(directiveReason(command.rule, candidate.mutatorName, command.line), () => exclusionReason(command.excludedMutations, candidate.mutatorName)), () => ignorerReason(candidate)), () => Option.fromNullishOr(policyReason)));
|
|
3926
|
+
const policyOutcomes = (command) => Result.match(policyWorkflow(policyCommand.make({
|
|
3927
|
+
policy: command.mutantSetPolicy,
|
|
3928
|
+
candidates: command.candidates.map((candidate) => candidate.mutantSet)
|
|
3929
|
+
})), {
|
|
3930
|
+
onFailure: () => command.candidates.map(() => keptOutcome.make({})),
|
|
3931
|
+
onSuccess: (outcomes) => outcomes
|
|
3932
|
+
});
|
|
3933
|
+
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
|
+
const policyReasonsAt = (command) => policyOutcomes(command).map(policyReasonOf);
|
|
3721
3935
|
const unusedDirectives = (directives, mutatorNames) => directives.flatMap((located) => located.directive.mutatorNames.filter((mutatorName) => mutatorName !== WILDCARD).filter((mutatorName) => !mutatorNames.includes(mutatorName.toLowerCase())).map((mutatorName) => ({
|
|
3722
3936
|
directive: located.directive,
|
|
3723
3937
|
at: located.at,
|
|
@@ -3743,30 +3957,31 @@ const shiftedLocation = (location, offset) => ({
|
|
|
3743
3957
|
start: shiftedPosition(location.start, offset),
|
|
3744
3958
|
end: shiftedPosition(location.end, offset)
|
|
3745
3959
|
});
|
|
3746
|
-
const plannedMutant = (command, candidate,
|
|
3960
|
+
const plannedMutant = (command, candidate, policyReason) => Option.match(Option.fromNullishOr(candidate.location), {
|
|
3747
3961
|
onNone: () => Result.fail(MutantWithoutLocation.make({
|
|
3748
3962
|
fileName: command.fileName,
|
|
3749
3963
|
mutatorName: candidate.mutatorName
|
|
3750
3964
|
})),
|
|
3751
3965
|
onSome: (location) => Result.succeed({
|
|
3752
|
-
id:
|
|
3966
|
+
id: candidate.id,
|
|
3753
3967
|
mutatorName: candidate.mutatorName,
|
|
3754
3968
|
replacementCode: candidate.replacementCode,
|
|
3755
3969
|
location: shiftedLocation(location, command.offset),
|
|
3756
|
-
ignoreReason: ignoreReasonOf(command, candidate)
|
|
3970
|
+
ignoreReason: ignoreReasonOf(command, candidate, policyReason)
|
|
3757
3971
|
})
|
|
3758
3972
|
});
|
|
3759
|
-
const plannedMutants = (command) =>
|
|
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
|
+
};
|
|
3760
3977
|
const withoutReason = (mutant) => mutant.ignoreReason === void 0;
|
|
3761
3978
|
const planOf = (command, mutants) => Match.value(mutants.some(withoutReason)).pipe(Match.when(true, () => MutantsPlanned.make({
|
|
3762
3979
|
mutants: [...mutants],
|
|
3763
|
-
|
|
3764
|
-
warnings: warningsOf(command)
|
|
3765
|
-
nextIndex: command.firstIndex + mutants.length
|
|
3980
|
+
placeableIds: mutants.filter(withoutReason).map((mutant) => mutant.id),
|
|
3981
|
+
warnings: warningsOf(command)
|
|
3766
3982
|
})), Match.when(false, () => MutantsFullyIgnored.make({
|
|
3767
3983
|
mutants: [...mutants],
|
|
3768
|
-
warnings: warningsOf(command)
|
|
3769
|
-
nextIndex: command.firstIndex + mutants.length
|
|
3984
|
+
warnings: warningsOf(command)
|
|
3770
3985
|
})), Match.exhaustive);
|
|
3771
3986
|
const planMutants = Workflow.make({
|
|
3772
3987
|
command: PlanMutantsCommand,
|
|
@@ -3790,15 +4005,15 @@ const traversalFailure = (cause) => InstrumentError.make({
|
|
|
3790
4005
|
message: cause instanceof Error ? cause.message : errorTextOf$1(cause),
|
|
3791
4006
|
cause
|
|
3792
4007
|
});
|
|
3793
|
-
const DEFAULT_MUTATOR_REGISTRY = {
|
|
3794
|
-
defaults: defaultMutators,
|
|
3795
|
-
optIn: optInMutators
|
|
3796
|
-
};
|
|
3797
4008
|
const createMutantCollector = () => {
|
|
3798
4009
|
const mutants = [];
|
|
4010
|
+
const occurrences = /* @__PURE__ */ new Map();
|
|
3799
4011
|
return {
|
|
3800
|
-
|
|
3801
|
-
|
|
4012
|
+
ordinalOf: (tuple) => {
|
|
4013
|
+
const key = mutantTupleKey(tuple);
|
|
4014
|
+
const ordinal = occurrences.get(key) ?? 0;
|
|
4015
|
+
occurrences.set(key, ordinal + 1);
|
|
4016
|
+
return ordinal;
|
|
3802
4017
|
},
|
|
3803
4018
|
append: (added) => {
|
|
3804
4019
|
mutants.push(...added);
|
|
@@ -4101,9 +4316,8 @@ const MUTATION_OFFSET = {
|
|
|
4101
4316
|
line: 1,
|
|
4102
4317
|
columnShift: 0
|
|
4103
4318
|
};
|
|
4104
|
-
const initialFoldState = (
|
|
4319
|
+
const initialFoldState = () => ({
|
|
4105
4320
|
directiveRule: [],
|
|
4106
|
-
nextIndex: firstIndex,
|
|
4107
4321
|
mutants: [],
|
|
4108
4322
|
warnings: [],
|
|
4109
4323
|
claims: [],
|
|
@@ -4139,29 +4353,77 @@ const replacementRecord = (mutant, applied) => {
|
|
|
4139
4353
|
}
|
|
4140
4354
|
};
|
|
4141
4355
|
};
|
|
4142
|
-
const plannedWithNodes = (candidates, planned, fileName) =>
|
|
4356
|
+
const plannedWithNodes = (candidates, planned, fileName, includes) => planned.flatMap((mutant, index) => Option.match(Option.fromNullishOr(candidates[index]), {
|
|
4143
4357
|
onNone: () => [],
|
|
4144
|
-
onSome: (
|
|
4358
|
+
onSome: (candidate) => includes(mutant) ? [createMutant(mutant, fileName, candidate.node, candidate.replacement)] : []
|
|
4145
4359
|
}));
|
|
4146
4360
|
const ignorersReasonFor = (node, ancestors, ignorers) => ignorers.reduce((reason, ignorer) => Option.orElse(reason, () => Option.fromNullishOr(ignorer.shouldIgnore(node, ancestors))), Option.none());
|
|
4361
|
+
const hasKey = (node, key) => key in node;
|
|
4362
|
+
const readKey = (node, key) => hasKey(node, key) ? node[key] : void 0;
|
|
4363
|
+
const isMemberExpressionValue = (node) => nodeType(node) === "MemberExpression";
|
|
4364
|
+
const isIdentifierValue = (value) => value !== void 0 && readKey(value, "type") === "Identifier";
|
|
4365
|
+
const hasIdentifierParts = (member) => [isIdentifierValue(readKey(member, "property")), isIdentifierValue(readKey(member, "object"))].every(Boolean);
|
|
4366
|
+
const isMemberOfIdentifierParts = (node) => isMemberExpressionValue(node) && hasIdentifierParts(node);
|
|
4367
|
+
function isNamedCallee(node) {
|
|
4368
|
+
return isMemberOfIdentifierParts(node) && readKey(node, "computed") === false;
|
|
4369
|
+
}
|
|
4370
|
+
const aridCalleeOf = (callee) => Match.value(callee).pipe(Match.when(isNamedCallee, (named) => Option.some({
|
|
4371
|
+
object: named.object.name,
|
|
4372
|
+
member: named.property.name
|
|
4373
|
+
})), Match.orElse(() => Option.none()));
|
|
4374
|
+
const aridFrameFor = (child, ancestor) => ancestor.type === "CallExpression" ? Option.some({
|
|
4375
|
+
callee: aridCalleeOf(ancestor.callee),
|
|
4376
|
+
childIsArgument: ancestor.arguments.some((argument) => argument === child)
|
|
4377
|
+
}) : Option.none();
|
|
4378
|
+
const aridFramesOf = (frame) => framesUpward(frame).flatMap((current) => Option.match(Option.fromNullishOr(current.parent), {
|
|
4379
|
+
onNone: () => [],
|
|
4380
|
+
onSome: (parent) => Option.toArray(aridFrameFor(current.node, parent.node))
|
|
4381
|
+
}));
|
|
4382
|
+
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({
|
|
4384
|
+
policy,
|
|
4385
|
+
frames: [...aridFramesOf(frame)]
|
|
4386
|
+
}))).pipe(Match.when(Result.isSuccess, (decided) => aridStatusReason(decided.success)), Match.orElse(() => Option.none()));
|
|
4147
4387
|
const mutablesFor = (frame, location, context) => {
|
|
4148
4388
|
const ancestors = ancestorsOfFrame(frame);
|
|
4149
|
-
const mutatorContext = toMutatorContext(ancestors);
|
|
4389
|
+
const mutatorContext = toMutatorContext(ancestors, context.mutantSetPolicy);
|
|
4150
4390
|
const replacements = context.mutatorEntries.flatMap(([mutatorName, mutate]) => [...mutate(frame.node, mutatorContext)].map((replacement) => ({
|
|
4151
4391
|
mutatorName,
|
|
4152
4392
|
replacement
|
|
4153
4393
|
})));
|
|
4154
4394
|
const ignorerReason = replacements.length === 0 ? void 0 : Option.getOrUndefined(ignorersReasonFor(frame.node, ancestors, context.ignorers));
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4395
|
+
const aridReason = aridReasonOf(frame, context.mutantSetPolicy);
|
|
4396
|
+
const originalCode = printNode(frame.node);
|
|
4397
|
+
const relationalSite = relationalSiteFacts(frame.node, mutatorContext);
|
|
4398
|
+
return replacements.map(({ mutatorName, replacement }) => {
|
|
4399
|
+
const replacementCode = printNode(replacement);
|
|
4400
|
+
const tuple = {
|
|
4401
|
+
fileName: context.fileName,
|
|
4159
4402
|
mutatorName,
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4403
|
+
originalCode,
|
|
4404
|
+
replacementCode
|
|
4405
|
+
};
|
|
4406
|
+
return {
|
|
4407
|
+
node: frame.node,
|
|
4408
|
+
replacement,
|
|
4409
|
+
aridReason,
|
|
4410
|
+
data: {
|
|
4411
|
+
id: mutantIdOf({
|
|
4412
|
+
...tuple,
|
|
4413
|
+
ordinal: context.ordinalOf(tuple)
|
|
4414
|
+
}),
|
|
4415
|
+
mutatorName,
|
|
4416
|
+
replacementCode,
|
|
4417
|
+
location,
|
|
4418
|
+
ignorerReason,
|
|
4419
|
+
mutantSet: {
|
|
4420
|
+
originalCode,
|
|
4421
|
+
replacementCode,
|
|
4422
|
+
relationalSufficient: relationalSufficientReplacement(relationalSite, replacement)
|
|
4423
|
+
}
|
|
4424
|
+
}
|
|
4425
|
+
};
|
|
4426
|
+
});
|
|
4165
4427
|
};
|
|
4166
4428
|
const mutateRangesOf = (mutateDescription) => Option.filter(Option.some(mutateDescription), isMutateRangeList);
|
|
4167
4429
|
const isOutsideMutateRanges = (location, mutateDescription) => Option.exists(mutateRangesOf(mutateDescription), (ranges) => ranges.every((range) => !locationOverlaps(range, location)));
|
|
@@ -4199,30 +4461,43 @@ const attachPlaceable = (mutants, frame, state, context) => Match.value(mutants.
|
|
|
4199
4461
|
})),
|
|
4200
4462
|
onSome: (target) => applyToClaim(mutants, target, state, context)
|
|
4201
4463
|
})), 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)));
|
|
4202
4473
|
const collectPlan = (frame, candidates, plan, state, context) => {
|
|
4203
|
-
const
|
|
4474
|
+
const planned = withAridReasons(candidates, plan.mutants);
|
|
4475
|
+
const collected = plannedWithNodes(candidates, planned, context.fileName, () => true);
|
|
4204
4476
|
const nextState = {
|
|
4205
4477
|
...state,
|
|
4206
4478
|
mutants: [...state.mutants, ...collected],
|
|
4207
|
-
warnings: [...state.warnings, ...plan.warnings]
|
|
4208
|
-
nextIndex: plan.nextIndex
|
|
4479
|
+
warnings: [...state.warnings, ...plan.warnings]
|
|
4209
4480
|
};
|
|
4210
|
-
return Match.value(plan).pipe(Match.tag("MutantsPlanned", (
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4481
|
+
return Match.value(plan).pipe(Match.tag("MutantsPlanned", (plannedPlan) => {
|
|
4482
|
+
const placeable = new Set(plannedPlan.placeableIds);
|
|
4483
|
+
const live = plannedWithNodes(candidates, planned, context.fileName, (mutant) => placeable.has(mutant.id) && mutant.ignoreReason === void 0);
|
|
4484
|
+
return attachPlaceable(live, frame, {
|
|
4485
|
+
...nextState,
|
|
4486
|
+
hasLiveMutants: nextState.hasLiveMutants || live.length > 0
|
|
4487
|
+
}, context);
|
|
4488
|
+
}), Match.tag("MutantsFullyIgnored", () => Result.succeed(nextState)), Match.exhaustive);
|
|
4214
4489
|
};
|
|
4215
4490
|
const planMutantsAt = (frame, candidates, directives, location, state, context) => {
|
|
4216
4491
|
const plan = planMutants(PlanMutantsCommand.make({
|
|
4217
4492
|
fileName: context.fileName,
|
|
4218
|
-
firstIndex: state.nextIndex,
|
|
4219
4493
|
offset: context.offset,
|
|
4220
4494
|
line: location.start.line,
|
|
4221
4495
|
mutatorNames: [...context.allMutatorNames],
|
|
4222
4496
|
excludedMutations: [...context.excludedMutations],
|
|
4223
4497
|
rule: [...state.directiveRule],
|
|
4224
4498
|
directives: [...directives],
|
|
4225
|
-
candidates: candidates.map((candidate) => candidate.data)
|
|
4499
|
+
candidates: candidates.map((candidate) => candidate.data),
|
|
4500
|
+
mutantSetPolicy: context.mutantSetPolicy
|
|
4226
4501
|
}));
|
|
4227
4502
|
return Match.value(plan).pipe(Match.when(Result.isFailure, (failed) => Result.fail(failed.failure)), Match.orElse((succeeded) => collectPlan(frame, candidates, succeeded.success, state, context)));
|
|
4228
4503
|
};
|
|
@@ -4285,10 +4560,10 @@ const foldPlacements = (frame, state, context) => Result.flatMap(locationOfNode(
|
|
|
4285
4560
|
};
|
|
4286
4561
|
return Match.value(shouldSkipNode(frame, location, context.mutateDescription)).pipe(Match.when(true, () => Result.succeed(ruled)), Match.when(false, () => visitFrame(frame, directives, location, ruled, context)), Match.exhaustive);
|
|
4287
4562
|
});
|
|
4288
|
-
const planInstrumentation = (root,
|
|
4563
|
+
const planInstrumentation = (root, context) => Result.map(foldPlacements({
|
|
4289
4564
|
node: root,
|
|
4290
4565
|
parent: null
|
|
4291
|
-
}, initialFoldState(
|
|
4566
|
+
}, initialFoldState(), context), (state) => ({
|
|
4292
4567
|
mutants: state.mutants,
|
|
4293
4568
|
placements: state.placements,
|
|
4294
4569
|
warnings: state.warnings,
|
|
@@ -4341,23 +4616,24 @@ const refusalError = (refusal) => Match.value(refusal).pipe(Match.tag("NodeWitho
|
|
|
4341
4616
|
message: `Mutant without a source location: ${failed.mutatorName} in ${failed.fileName}`,
|
|
4342
4617
|
cause: void 0
|
|
4343
4618
|
})), Match.exhaustive);
|
|
4344
|
-
const transformScriptDataFirst = Effect.fn(
|
|
4619
|
+
const transformScriptDataFirst = Effect.fn(SpanTaxonomy.Spans.instrumentTransformScript.name)(function* ({ root, originFileName, rawContent, offset, comments }, mutantCollector, { options, mutateDescription, basePath }) {
|
|
4345
4620
|
const lineStarts = lineStartsOf(rawContent);
|
|
4346
4621
|
attachComments(make(root), comments, lineStarts);
|
|
4347
|
-
const selection = selectMutators(DEFAULT_MUTATOR_REGISTRY, options.optInMutations);
|
|
4348
4622
|
const context = {
|
|
4349
4623
|
fileName: originFileName,
|
|
4350
4624
|
lineStarts,
|
|
4351
4625
|
mutateDescription,
|
|
4352
4626
|
offset: offset ?? MUTATION_OFFSET,
|
|
4353
4627
|
basePath,
|
|
4354
|
-
mutatorEntries:
|
|
4355
|
-
allMutatorNames:
|
|
4628
|
+
mutatorEntries: options.mutators.active,
|
|
4629
|
+
allMutatorNames: options.mutators.known.map((name) => name.toLowerCase()),
|
|
4356
4630
|
excludedMutations: options.excludedMutations,
|
|
4357
|
-
|
|
4631
|
+
mutantSetPolicy: options.mutantSetPolicy,
|
|
4632
|
+
ignorers: options.ignorers,
|
|
4633
|
+
ordinalOf: mutantCollector.ordinalOf
|
|
4358
4634
|
};
|
|
4359
4635
|
const planned = yield* Effect.try({
|
|
4360
|
-
try: () => planInstrumentation(root,
|
|
4636
|
+
try: () => planInstrumentation(root, context),
|
|
4361
4637
|
catch: traversalFailure
|
|
4362
4638
|
});
|
|
4363
4639
|
const plan = yield* Match.value(planned).pipe(Match.when(Result.isFailure, (refused) => {
|
|
@@ -4370,11 +4646,12 @@ const transformScriptDataFirst = Effect.fn("stryker.instrument.transform.script"
|
|
|
4370
4646
|
return plan.warnings;
|
|
4371
4647
|
});
|
|
4372
4648
|
const transformScript = dual((args) => args.length >= 3, transformScriptDataFirst);
|
|
4373
|
-
function toMutatorContext(ancestors) {
|
|
4649
|
+
function toMutatorContext(ancestors, mutantSetPolicy) {
|
|
4374
4650
|
return {
|
|
4375
4651
|
parent: ancestors[0],
|
|
4376
4652
|
grandParent: ancestors[1],
|
|
4377
|
-
ancestors: [...ancestors]
|
|
4653
|
+
ancestors: [...ancestors],
|
|
4654
|
+
mutantSetPolicy
|
|
4378
4655
|
};
|
|
4379
4656
|
}
|
|
4380
4657
|
const toError = (value) => value instanceof Error ? value : new Error("Unexpected error", { cause: value });
|
|
@@ -4530,12 +4807,11 @@ const coreFormatRegistry = formatRegistry([
|
|
|
4530
4807
|
]);
|
|
4531
4808
|
const resolveFileDataFirst = (registry, fileName, formatIdOverride) => resolveFormat(resolutionCommandOf(registry, fileName, formatIdOverride));
|
|
4532
4809
|
const resolveFile = dual((args) => typeof args[0] !== "string", resolveFileDataFirst);
|
|
4533
|
-
const NO_OPT_IN_MUTATIONS = [];
|
|
4534
|
-
const optInMutationsOf = (options) => options.optInMutations ?? NO_OPT_IN_MUTATIONS;
|
|
4535
4810
|
const isIgnorer = (value) => Predicate.isObject(value) && typeof value["shouldIgnore"] === "function";
|
|
4536
4811
|
const toTransformerOptions = (options) => ({
|
|
4537
4812
|
excludedMutations: [...options.excludedMutations],
|
|
4538
|
-
|
|
4813
|
+
mutators: options.mutators,
|
|
4814
|
+
mutantSetPolicy: options.mutantSetPolicy,
|
|
4539
4815
|
ignorers: options.ignorers.filter(isIgnorer),
|
|
4540
4816
|
...options.noHeader !== void 0 ? { noHeader: options.noHeader } : {}
|
|
4541
4817
|
});
|
|
@@ -4675,7 +4951,7 @@ const frameworkEntryOfDataFirst = (moduleName, framework) => ({
|
|
|
4675
4951
|
},
|
|
4676
4952
|
owner: moduleName,
|
|
4677
4953
|
ownerVersion: framework.claim.ownerVersion,
|
|
4678
|
-
parse: Effect.fn(
|
|
4954
|
+
parse: Effect.fn(SpanTaxonomy.Spans.instrumentFrameworkEntryParse.name)(function* (text, fileName) {
|
|
4679
4955
|
const context = frameworkContextOf(yield* loadFrameworkToolkit);
|
|
4680
4956
|
const lineStarts = lineStartsOf(text);
|
|
4681
4957
|
const result = yield* runHook(moduleName, "parse", fileName, () => framework.parse(text, context));
|
|
@@ -4692,7 +4968,7 @@ const frameworkEntryOfDataFirst = (moduleName, framework) => ({
|
|
|
4692
4968
|
}),
|
|
4693
4969
|
transform: (ast, mutantCollector, context) => Option.match(embeddedOf(ast), {
|
|
4694
4970
|
onNone: () => Effect.fail(embeddedAstError(ast)),
|
|
4695
|
-
onSome: Effect.fn(
|
|
4971
|
+
onSome: Effect.fn(SpanTaxonomy.Spans.instrumentFrameworkEntryTransform.name)(function* (embedded) {
|
|
4696
4972
|
const warnings = yield* Effect.forEach(embedded.scripts, (script) => context.transform(script.ast, mutantCollector, context));
|
|
4697
4973
|
embedded.document = yield* runHook(moduleName, "transform", embedded.originFileName, () => framework.transform(embedded.document, embedded.context));
|
|
4698
4974
|
return warnings.flat();
|
|
@@ -4731,7 +5007,7 @@ const readDisable = (input) => Effect.sync(() => {
|
|
|
4731
5007
|
registry: input.registry
|
|
4732
5008
|
};
|
|
4733
5009
|
});
|
|
4734
|
-
const disableTypeChecksCell = Sandwich.named(
|
|
5010
|
+
const disableTypeChecksCell = Sandwich.named(SpanTaxonomy.Spans.instrumentDisableTypeChecks.name)((input) => readDisable(input)).decide(resolveFormat).write({
|
|
4735
5011
|
FormatAssigned: (assigned, raw) => spliceForAssigned({
|
|
4736
5012
|
file: raw.file,
|
|
4737
5013
|
registry: raw.registry,
|
|
@@ -4787,7 +5063,7 @@ const skippedOnlyResult = (raw) => InstrumentResult.make({
|
|
|
4787
5063
|
mutants: [],
|
|
4788
5064
|
skipped: [...raw.skipped]
|
|
4789
5065
|
});
|
|
4790
|
-
const readInstrumentFiles = Effect.fn(
|
|
5066
|
+
const readInstrumentFiles = Effect.fn(SpanTaxonomy.Spans.instrumentReadFiles.name)(function* (input) {
|
|
4791
5067
|
const outcomes = yield* Effect.forEach(input.files, (file) => fileOutcome({
|
|
4792
5068
|
registry: input.registry,
|
|
4793
5069
|
file
|
|
@@ -4826,29 +5102,15 @@ const instrumentFilesCell = Sandwich.named("stryker.instrument.files")((input) =
|
|
|
4826
5102
|
//#endregion
|
|
4827
5103
|
//#region src/Instrument.service.ts
|
|
4828
5104
|
const toSchemaFile = (file) => ({
|
|
4829
|
-
name: CanonicalFileName.make(file.name),
|
|
5105
|
+
name: Mutant.CanonicalFileName.make(file.name),
|
|
4830
5106
|
content: file.content,
|
|
4831
5107
|
mutate: file.mutate
|
|
4832
5108
|
});
|
|
4833
|
-
const
|
|
4834
|
-
const unknownOptInMutations = (requested) => requested.filter((name) => !KNOWN_OPT_IN_MUTATIONS.includes(name));
|
|
4835
|
-
const listNames = (names) => Option.match(Option.fromNullishOr(names.at(0)), {
|
|
4836
|
-
onNone: () => "none",
|
|
4837
|
-
onSome: () => names.map((name) => `'${name}'`).join(", ")
|
|
4838
|
-
});
|
|
4839
|
-
const unknownOptInMutationsError = (requested) => Option.map(Option.fromNullishOr(unknownOptInMutations(requested).at(0)), () => InstrumentError.make({
|
|
4840
|
-
message: `Unknown opt-in mutations: ${listNames(unknownOptInMutations(requested))}. Known opt-in mutations: ${listNames(KNOWN_OPT_IN_MUTATIONS)}.`,
|
|
4841
|
-
cause: void 0
|
|
4842
|
-
}));
|
|
4843
|
-
const refuseUnknownOptInMutations = (options) => Option.match(unknownOptInMutationsError(optInMutationsOf(options)), {
|
|
4844
|
-
onNone: () => Effect.void,
|
|
4845
|
-
onSome: (error) => Effect.fail(error)
|
|
4846
|
-
});
|
|
4847
|
-
const instrumentDataFirst = (files, options, registry = coreFormatRegistry) => refuseUnknownOptInMutations(options).pipe(Effect.andThen(instrumentFilesCell.run({
|
|
5109
|
+
const instrumentDataFirst = (files, options, registry = coreFormatRegistry) => instrumentFilesCell.run({
|
|
4848
5110
|
files: files.map(toSchemaFile),
|
|
4849
5111
|
options,
|
|
4850
5112
|
registry
|
|
4851
|
-
})
|
|
5113
|
+
});
|
|
4852
5114
|
const instrument = dual((args) => Array.isArray(args[0]), instrumentDataFirst);
|
|
4853
5115
|
const disableTypeChecksDataFirst = (file, registry = coreFormatRegistry) => disableTypeChecksCell.run({
|
|
4854
5116
|
file: toSchemaFile(file),
|
|
@@ -4865,36 +5127,25 @@ const disableTypeChecks = dual((args) => Predicate.hasProperty(args[0], "content
|
|
|
4865
5127
|
//#region src/Instrument/mod.ts
|
|
4866
5128
|
var Instrument_exports = /* @__PURE__ */ __exportAll({
|
|
4867
5129
|
InstrumentFileSkip: () => InstrumentFileSkip,
|
|
5130
|
+
InstrumenterContext: () => InstrumenterContext,
|
|
4868
5131
|
disableTypeChecks: () => disableTypeChecks,
|
|
4869
5132
|
instrument: () => instrument
|
|
4870
5133
|
});
|
|
4871
5134
|
//#endregion
|
|
4872
|
-
//#region src/
|
|
4873
|
-
var
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
MutantActivationSchema: () => MutantActivationSchema,
|
|
4884
|
-
MutantCoverageSchema: () => MutantCoverageSchema,
|
|
4885
|
-
MutantFromUnknown: () => MutantFromUnknown,
|
|
4886
|
-
MutantId: () => MutantId,
|
|
4887
|
-
MutantRunOptionsSchema: () => MutantRunOptionsSchema,
|
|
4888
|
-
MutantStatusSchema: () => MutantStatusSchema,
|
|
4889
|
-
MutatorName: () => MutatorName,
|
|
5135
|
+
//#region src/Mutator/mod.ts
|
|
5136
|
+
var Mutator_exports = /* @__PURE__ */ __exportAll({
|
|
5137
|
+
defaultMutators: () => defaultMutators,
|
|
5138
|
+
optInMutators: () => optInMutators,
|
|
5139
|
+
registryOf: () => registryOf,
|
|
5140
|
+
selectMutators: () => selectMutators,
|
|
5141
|
+
stockRegistry: () => stockRegistry
|
|
5142
|
+
});
|
|
5143
|
+
//#endregion
|
|
5144
|
+
//#region src/Source/mod.ts
|
|
5145
|
+
var Source_exports = /* @__PURE__ */ __exportAll({
|
|
4890
5146
|
Offset: () => Offset,
|
|
4891
|
-
OpenEndLocation: () => OpenEndLocation,
|
|
4892
|
-
Position: () => Position,
|
|
4893
|
-
RememberedStatusSchema: () => RememberedStatusSchema,
|
|
4894
|
-
RunOptionsFields: () => RunOptionsFields,
|
|
4895
5147
|
ScriptOrigin: () => ScriptOrigin,
|
|
4896
|
-
Span: () => Span
|
|
4897
|
-
SurvivorStatusSchema: () => SurvivorStatusSchema
|
|
5148
|
+
Span: () => Span
|
|
4898
5149
|
});
|
|
4899
5150
|
//#endregion
|
|
4900
|
-
export { ErrorText_exports as ErrorText, Format_exports as Format, Instrument_exports as Instrument,
|
|
5151
|
+
export { ErrorText_exports as ErrorText, Format_exports as Format, Instrument_exports as Instrument, Mutator_exports as Mutator, Source_exports as Source, lineStartsOf, locationOf, offsetAt, originAt, positionAt };
|