@systemfsoftware/stryker-js-instrumenter 9.0.1 → 10.0.1
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 +38 -0
- package/dist/index.d.mts +338 -168
- package/dist/index.mjs +871 -801
- package/package.json +6 -6
package/dist/index.mjs
CHANGED
|
@@ -8,18 +8,19 @@ 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 * as
|
|
12
|
-
import * as
|
|
11
|
+
import * as Order from "effect/Order";
|
|
12
|
+
import * as SchemaGetter from "effect/SchemaGetter";
|
|
13
13
|
import { RegExpParser, visitRegExpAST } from "@eslint-community/regexpp";
|
|
14
|
+
import * as Arr from "effect/Array";
|
|
14
15
|
//#region src/ErrorText.schema.ts
|
|
15
|
-
var ErrorText = class
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
static fromCause = (cause) => Option.flatMap(nonEmptyText(causeChainTextOf(cause)), (text) => CauseText.makeOption({ text }));
|
|
20
|
-
};
|
|
16
|
+
var ErrorText = class extends S.Class("ErrorText")({ text: S.NonEmptyString }) {};
|
|
17
|
+
var CauseText = class extends S.Class("CauseText")({ text: S.NonEmptyString }) {};
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/error-text.ts
|
|
21
20
|
const nonEmptyText = Option.liftPredicate(S.is(S.NonEmptyString));
|
|
22
|
-
|
|
21
|
+
const errorTextOf$2 = (cause) => Option.flatMap(nonEmptyText(errorTextValueOf(cause)), (text) => ErrorText.makeOption({ text }));
|
|
22
|
+
const causeTextOf = (cause) => Option.flatMap(nonEmptyText(causeChainTextOf(cause)), (text) => CauseText.makeOption({ text }));
|
|
23
|
+
function errorTextValueOf(error) {
|
|
23
24
|
return Match.value(error).pipe(Match.when(isEmptyError, () => ""), Match.when(Match.instanceOf(Error), errorText), Match.orElse(() => stringifyNonError(error)));
|
|
24
25
|
}
|
|
25
26
|
function causeChainTextOf(cause) {
|
|
@@ -87,10 +88,10 @@ function causeText(cause, depth) {
|
|
|
87
88
|
function missingCauseTextOf(cause, depth) {
|
|
88
89
|
return Boolean$1.match(isMissingCause(cause), {
|
|
89
90
|
onTrue: () => void 0,
|
|
90
|
-
onFalse: () =>
|
|
91
|
+
onFalse: () => causeNodeTextOf(cause, depth)
|
|
91
92
|
});
|
|
92
93
|
}
|
|
93
|
-
function
|
|
94
|
+
function causeNodeTextOf(cause, depth) {
|
|
94
95
|
return Match.value(cause).pipe(Match.when(Match.string, (text) => textIfNonEmpty(text)), Match.orElse((value) => objectCauseTextOf(value, depth)));
|
|
95
96
|
}
|
|
96
97
|
function objectCauseTextOf(cause, depth) {
|
|
@@ -100,93 +101,92 @@ function objectCauseTextOf(cause, depth) {
|
|
|
100
101
|
//#region src/ErrorText/mod.ts
|
|
101
102
|
var ErrorText_exports = /* @__PURE__ */ __exportAll({
|
|
102
103
|
CauseText: () => CauseText,
|
|
103
|
-
ErrorText: () => ErrorText
|
|
104
|
+
ErrorText: () => ErrorText,
|
|
105
|
+
causeTextOf: () => causeTextOf,
|
|
106
|
+
errorTextOf: () => errorTextOf$2
|
|
104
107
|
});
|
|
105
108
|
//#endregion
|
|
106
109
|
//#region src/Location.schema.ts
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
+
})
|
|
121
135
|
});
|
|
122
|
-
const
|
|
123
|
-
start:
|
|
124
|
-
end: S.optional(
|
|
136
|
+
const OpenEnds = S.Struct({
|
|
137
|
+
start: Position,
|
|
138
|
+
end: S.optional(Position)
|
|
125
139
|
});
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
*/
|
|
130
|
-
const ReportLocationFromMutant = LocationSchema$1;
|
|
131
|
-
const NonNegativeInt = S.Int.pipe(S.check(S.isGreaterThanOrEqualTo(0)));
|
|
132
|
-
var LineTable = class extends S.Class("LineTable")({ lineStarts: S.NonEmptyArray(NonNegativeInt) }) {
|
|
133
|
-
positionAt(offset) {
|
|
134
|
-
return positionOf(this.lineStarts, offset);
|
|
135
|
-
}
|
|
136
|
-
locationAt(span) {
|
|
137
|
-
return {
|
|
138
|
-
start: this.positionAt(span.start),
|
|
139
|
-
end: this.positionAt(span.end)
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
zeroBasedPositionAt(offset) {
|
|
143
|
-
return zeroBasedPositionOf(this.lineStarts, offset);
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
const LINE_TERMINATOR = /\r\n|[\n\r\u2028\u2029]/g;
|
|
147
|
-
const lineStartsOf = (text) => ({ lineStarts: [0, ...[...text.matchAll(LINE_TERMINATOR)].map((match) => endOfMatch(match))] });
|
|
148
|
-
const endOfMatch = (match) => (match.index ?? 0) + match[0].length;
|
|
149
|
-
const canonicalTextOf = (table) => Boolean$1.match(table.lineStarts.length === 1, {
|
|
150
|
-
onTrue: () => "",
|
|
151
|
-
onFalse: () => `${Arr.zip(table.lineStarts.slice(0, -1), table.lineStarts.slice(1)).map(([start, next]) => " ".repeat(next - start - 1)).join("\n")}\n`
|
|
152
|
-
});
|
|
153
|
-
const LineTableFromText = S.String.pipe(S.decodeTo(LineTable, {
|
|
154
|
-
decode: SGetter.transform(lineStartsOf),
|
|
155
|
-
encode: SGetter.transform(canonicalTextOf)
|
|
140
|
+
const closedEnds = (ends) => Option.map(Option.fromUndefinedOr(ends.end), (end) => ({
|
|
141
|
+
start: ends.start,
|
|
142
|
+
end
|
|
156
143
|
}));
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
+
//#region src/directives/directive.schema.ts
|
|
172
|
+
const MutatorNameSchema = S.String.pipe(S.check(S.isPattern(/^[a-zA-Z]+(?: [a-zA-Z]+)*$/)));
|
|
173
|
+
const DirectiveReasonSchema = S.String.pipe(S.check(S.isPattern(/^\S(?:[^\r\n\u2028\u2029]*\S)?$/)));
|
|
174
|
+
const DirectiveSchema = S.Struct({
|
|
175
|
+
action: S.Literals(["disable", "restore"]),
|
|
176
|
+
scope: S.Literals(["next-line", "block"]),
|
|
177
|
+
mutatorNames: S.Array(MutatorNameSchema).check(S.isMinLength(1)),
|
|
178
|
+
reason: DirectiveReasonSchema
|
|
179
|
+
});
|
|
180
|
+
const LocatedDirectiveSchema = S.Struct({
|
|
181
|
+
directive: DirectiveSchema,
|
|
182
|
+
at: Position,
|
|
183
|
+
governedLine: Line
|
|
184
|
+
});
|
|
185
|
+
S.Struct({
|
|
186
|
+
directive: DirectiveSchema,
|
|
187
|
+
at: Position,
|
|
188
|
+
mutatorName: MutatorNameSchema
|
|
189
|
+
});
|
|
190
190
|
//#endregion
|
|
191
191
|
//#region src/Mutant.schema.ts
|
|
192
192
|
const MutantStatusSchema = S.Literals([
|
|
@@ -199,11 +199,30 @@ const MutantStatusSchema = S.Literals([
|
|
|
199
199
|
"Ignored",
|
|
200
200
|
"Pending"
|
|
201
201
|
]);
|
|
202
|
-
const
|
|
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"));
|
|
203
222
|
const MutatorName = S.NonEmptyString.pipe(S.brand("MutatorName"));
|
|
204
223
|
const CanonicalFileName = S.String.pipe(S.decodeTo(S.String.pipe(S.check(S.isPattern(/^[^\\]+$/)), S.brand("CanonicalFileName")), {
|
|
205
|
-
decode:
|
|
206
|
-
encode:
|
|
224
|
+
decode: SchemaGetter.transform((fileName) => fileName.replace(/\\/g, "/")),
|
|
225
|
+
encode: SchemaGetter.transform((canonical) => canonical)
|
|
207
226
|
}));
|
|
208
227
|
/**
|
|
209
228
|
* A mutant's file location in the mutation-testing-report-schema contract:
|
|
@@ -211,19 +230,19 @@ const CanonicalFileName = S.String.pipe(S.decodeTo(S.String.pipe(S.check(S.isPat
|
|
|
211
230
|
* machine stream emit. Every producer on the instrument path (node spans,
|
|
212
231
|
* embedded-region shifts) targets this base; no downstream layer converts.
|
|
213
232
|
*/
|
|
214
|
-
|
|
233
|
+
const Mutant = S.TaggedStruct("Mutant", {
|
|
215
234
|
id: MutantId,
|
|
216
235
|
fileName: CanonicalFileName,
|
|
217
236
|
mutatorName: MutatorName,
|
|
218
237
|
replacement: S.String,
|
|
219
|
-
location:
|
|
238
|
+
location: Location,
|
|
220
239
|
status: S.optional(MutantStatusSchema),
|
|
221
240
|
statusReason: S.optional(S.String),
|
|
222
241
|
coveredBy: S.String.pipe(S.Array, S.optional),
|
|
223
242
|
static: S.optional(S.Boolean),
|
|
224
243
|
testsCompleted: S.optional(S.Finite),
|
|
225
244
|
description: S.optional(S.String)
|
|
226
|
-
}) {};
|
|
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" }));
|
|
227
246
|
const MutantFromUnknown = S.Unknown.pipe(S.decodeTo(Mutant));
|
|
228
247
|
const RunOptionsFields = {
|
|
229
248
|
timeout: S.Finite.pipe(S.check(S.isGreaterThanOrEqualTo(0))),
|
|
@@ -241,15 +260,15 @@ const MutantRunOptionsSchema = S.Struct({
|
|
|
241
260
|
hitLimit: S.optionalKey(HitCount)
|
|
242
261
|
});
|
|
243
262
|
const MutantCoverageSchema = S.Struct({
|
|
244
|
-
perTest: S.Record(S.String, S.Record(
|
|
245
|
-
static: S.Record(
|
|
263
|
+
perTest: S.Record(S.String, S.Record(MutantId, HitCount)),
|
|
264
|
+
static: S.Record(MutantId, HitCount)
|
|
246
265
|
});
|
|
247
266
|
var InstrumenterContext = class extends S.Class("InstrumenterContext")({
|
|
248
267
|
activeMutant: S.optional(S.String),
|
|
249
268
|
currentTestId: S.optional(S.String),
|
|
250
269
|
mutantCoverage: S.optional(MutantCoverageSchema),
|
|
251
|
-
hitCount: S.optional(
|
|
252
|
-
hitLimit: S.optional(
|
|
270
|
+
hitCount: S.optional(HitCount),
|
|
271
|
+
hitLimit: S.optional(HitCount)
|
|
253
272
|
}) {
|
|
254
273
|
static NAMESPACE = "__stryker__";
|
|
255
274
|
static MUTATION_COVERAGE_OBJECT = "mutantCoverage";
|
|
@@ -272,21 +291,15 @@ var InstrumentError = class extends S.TaggedError("@systemfsoftware/stryker-js-i
|
|
|
272
291
|
});
|
|
273
292
|
}
|
|
274
293
|
};
|
|
275
|
-
const MutateDescriptionSchema = S.Union([S.Boolean, S.Array(
|
|
276
|
-
const SourceLineSchema = S.Int.pipe(S.check(S.isGreaterThanOrEqualTo(1)));
|
|
277
|
-
const SourceColumnSchema = S.Int.pipe(S.check(S.isGreaterThanOrEqualTo(0)));
|
|
278
|
-
const NodePositionSchema = S.Struct({
|
|
279
|
-
line: S.Int,
|
|
280
|
-
column: S.Int
|
|
281
|
-
});
|
|
294
|
+
const MutateDescriptionSchema = S.Union([S.Boolean, S.Array(Location)]);
|
|
282
295
|
const FileSchema = S.Struct({
|
|
283
|
-
name:
|
|
296
|
+
name: CanonicalFileName,
|
|
284
297
|
content: S.String,
|
|
285
298
|
mutate: MutateDescriptionSchema
|
|
286
299
|
});
|
|
287
300
|
const IgnorerSchema = S.Unknown;
|
|
288
301
|
S.Struct({
|
|
289
|
-
excludedMutations: S.Array(
|
|
302
|
+
excludedMutations: S.Array(MutatorNameSchema),
|
|
290
303
|
ignorers: S.Array(IgnorerSchema),
|
|
291
304
|
noHeader: S.optional(S.Boolean),
|
|
292
305
|
optInMutations: S.String.pipe(S.Array, S.optional)
|
|
@@ -316,13 +329,81 @@ const PlacerNameSchema = S.Literals([
|
|
|
316
329
|
var MutantsUnapplied = class extends S.TaggedError()("MutantsUnapplied", {
|
|
317
330
|
fileName: S.String,
|
|
318
331
|
placer: PlacerNameSchema,
|
|
319
|
-
mutatorNames: S.Array(
|
|
332
|
+
mutatorNames: S.Array(MutatorNameSchema),
|
|
320
333
|
cause: S.Defect()
|
|
321
|
-
}) {
|
|
334
|
+
}) {
|
|
335
|
+
get message() {
|
|
336
|
+
return `Failed to apply ${this.mutatorNames.join(", ")} in ${this.fileName}`;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
322
339
|
var MutantNotApplied = class extends S.TaggedError()("MutantNotApplied", {
|
|
323
340
|
fileName: S.String,
|
|
324
|
-
mutatorName:
|
|
325
|
-
}) {
|
|
341
|
+
mutatorName: MutatorNameSchema
|
|
342
|
+
}) {
|
|
343
|
+
get message() {
|
|
344
|
+
return `Could not apply the ${this.mutatorName} mutant in ${this.fileName}`;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
var NodeWithoutSpan = class extends S.TaggedError()("NodeWithoutSpan", { fileName: S.String }) {
|
|
348
|
+
get message() {
|
|
349
|
+
return `Node without a span in ${this.fileName}`;
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
var MutantsUnplaced = class extends S.TaggedError()("MutantsUnplaced", {
|
|
353
|
+
fileName: S.String,
|
|
354
|
+
detail: S.String
|
|
355
|
+
}) {
|
|
356
|
+
get message() {
|
|
357
|
+
return `Mutants cannot be placed. This shouldn't happen! Unplaced mutants: ${this.detail}`;
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
var PlacementRefused = class extends S.TaggedError()("PlacementRefused", { message: S.String }) {};
|
|
361
|
+
//#endregion
|
|
362
|
+
//#region src/Location.ts
|
|
363
|
+
const LINE_TERMINATOR = /\r\n|[\n\r\u2028\u2029]/g;
|
|
364
|
+
const lineStartsOf = (text) => [0, ...[...text.matchAll(LINE_TERMINATOR)].map((match) => match.index + match[0].length)];
|
|
365
|
+
const middleIndex = (low, high) => low + (high - low >> 1);
|
|
366
|
+
const positionAtOffset = (lineStarts, offset, low, high, start) => Boolean$1.match(low > high, {
|
|
367
|
+
onTrue: () => ({
|
|
368
|
+
line: low,
|
|
369
|
+
column: offset - start + 1
|
|
370
|
+
}),
|
|
371
|
+
onFalse: () => Option.match(Arr.get(lineStarts, middleIndex(low, high)), {
|
|
372
|
+
onNone: () => ({
|
|
373
|
+
line: low,
|
|
374
|
+
column: offset - start + 1
|
|
375
|
+
}),
|
|
376
|
+
onSome: (found) => Boolean$1.match(found === offset, {
|
|
377
|
+
onTrue: () => ({
|
|
378
|
+
line: middleIndex(low, high) + 1,
|
|
379
|
+
column: 1
|
|
380
|
+
}),
|
|
381
|
+
onFalse: () => Boolean$1.match(found < offset, {
|
|
382
|
+
onTrue: () => positionAtOffset(lineStarts, offset, middleIndex(low, high) + 1, high, found),
|
|
383
|
+
onFalse: () => positionAtOffset(lineStarts, offset, low, middleIndex(low, high) - 1, start)
|
|
384
|
+
})
|
|
385
|
+
})
|
|
386
|
+
})
|
|
387
|
+
});
|
|
388
|
+
const positionAt = dual(2, (lineStarts, offset) => positionAtOffset(lineStarts, offset, 0, lineStarts.length - 1, 0));
|
|
389
|
+
const locationOf = dual(2, (lineStarts, span) => Location.make({
|
|
390
|
+
start: positionAt(lineStarts, span.start),
|
|
391
|
+
end: positionAt(lineStarts, span.end)
|
|
392
|
+
}));
|
|
393
|
+
const originAt = dual(2, (lineStarts, offset) => {
|
|
394
|
+
const position = positionAt(lineStarts, offset);
|
|
395
|
+
return ScriptOrigin.make({
|
|
396
|
+
line: position.line,
|
|
397
|
+
columnShift: position.column - 1
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
const offsetAt = dual(2, (lineStarts, position) => Option.flatMap(Arr.get(lineStarts, position.line - 1), (start) => {
|
|
401
|
+
const offset = start + position.column - 1;
|
|
402
|
+
return Option.match(Arr.get(lineStarts, position.line), {
|
|
403
|
+
onNone: () => Option.some(offset),
|
|
404
|
+
onSome: (next) => Option.liftPredicate(offset, (candidate) => candidate < next)
|
|
405
|
+
});
|
|
406
|
+
}));
|
|
326
407
|
//#endregion
|
|
327
408
|
//#region src/Ast.handle.ts
|
|
328
409
|
const formatKeyOf = (ast) => ast.format === "embedded" ? ast.formatId : ast.format;
|
|
@@ -560,14 +641,14 @@ const switchCase = dual((args) => isDataFirstArity(args, 2), (test, consequent,
|
|
|
560
641
|
consequent: [...consequent]
|
|
561
642
|
}, loc));
|
|
562
643
|
const cloneNode = (node) => structuredClone(node);
|
|
563
|
-
const attachComments = dual((args) => args.length >= 3, (self, comments,
|
|
644
|
+
const attachComments = dual((args) => args.length >= 3, (self, comments, lineStarts) => Boolean$1.match(comments.length === 0, {
|
|
564
645
|
onTrue: () => void 0,
|
|
565
646
|
onFalse: () => {
|
|
566
647
|
const nodes = collectNodes(self.root).filter((entry) => entry.node !== self.root);
|
|
567
648
|
nodes.sort((a, b) => a.start - b.start);
|
|
568
649
|
const groups = groupComments(nodes, comments);
|
|
569
|
-
assignComments(groups.leading,
|
|
570
|
-
assignComments(groups.trailing,
|
|
650
|
+
assignComments(groups.leading, lineStarts, "leadingComments");
|
|
651
|
+
assignComments(groups.trailing, lineStarts, "trailingComments");
|
|
571
652
|
}
|
|
572
653
|
}));
|
|
573
654
|
const groupComments = (nodes, comments) => {
|
|
@@ -593,12 +674,12 @@ const commentHost = (nodes, comment) => {
|
|
|
593
674
|
};
|
|
594
675
|
const followingNode = (nodes, comment) => Option.getOrUndefined(Option.map(Option.fromNullishOr(nodes.find((candidate) => candidate.start >= comment.end)), (entry) => entry.node));
|
|
595
676
|
const precedingStatement = (nodes, comment) => Option.getOrUndefined(Option.map(Option.fromNullishOr(nodes.findLast((candidate) => candidate.end <= comment.start && isStatementKind(candidate.node))), (entry) => entry.node));
|
|
596
|
-
const assignComments = (map,
|
|
677
|
+
const assignComments = (map, lineStarts, field) => map.forEach((list, node) => Object.assign(node, { [field]: list.map((comment) => ({
|
|
597
678
|
...comment,
|
|
598
|
-
loc: {
|
|
599
|
-
start:
|
|
600
|
-
end:
|
|
601
|
-
}
|
|
679
|
+
loc: locationOf(lineStarts, {
|
|
680
|
+
start: comment.start,
|
|
681
|
+
end: comment.end
|
|
682
|
+
})
|
|
602
683
|
})) }));
|
|
603
684
|
const pushComment = (map, node, comment) => {
|
|
604
685
|
Option.match(Option.fromNullishOr(map.get(node)), {
|
|
@@ -615,72 +696,41 @@ const WALK_SKIPPED_KEYS = {
|
|
|
615
696
|
start: true,
|
|
616
697
|
end: true
|
|
617
698
|
};
|
|
618
|
-
const walkNodes = (root, visitors) => visitIfNode(root, {
|
|
619
|
-
key: null,
|
|
620
|
-
index: null
|
|
621
|
-
}, visitors);
|
|
622
|
-
const visitNode = (node, context, visitors) => {
|
|
623
|
-
const state = { skipped: false };
|
|
624
|
-
visitors.enter(node, context, { skip: () => {
|
|
625
|
-
state.skipped = true;
|
|
626
|
-
} });
|
|
627
|
-
Boolean$1.match(state.skipped, {
|
|
628
|
-
onTrue: () => void 0,
|
|
629
|
-
onFalse: () => visitChildren(node, visitors)
|
|
630
|
-
});
|
|
631
|
-
visitors.leave(node, context);
|
|
632
|
-
};
|
|
633
|
-
const visitChildren = (node, visitors) => Object.keys(node).forEach((key) => Boolean$1.match(WALK_SKIPPED_KEYS[key] === true, {
|
|
634
|
-
onTrue: () => void 0,
|
|
635
|
-
onFalse: () => visitSlot(node[key], key, visitors)
|
|
636
|
-
}));
|
|
637
699
|
const isChildList = (value) => Array.isArray(value);
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
700
|
+
const childSlotsOf = (node) => Object.keys(node).flatMap((key) => WALK_SKIPPED_KEYS[key] === true ? [] : childSlotsAt(key, node[key]));
|
|
701
|
+
const childSlotsAt = (key, value) => isChildList(value) ? listSlotsAt(key, value) : singleSlotAt(key, value);
|
|
702
|
+
const listSlotsAt = (key, value) => value.flatMap((item, index) => isAstNode(item) ? [{
|
|
703
|
+
key,
|
|
704
|
+
index,
|
|
705
|
+
node: item
|
|
706
|
+
}] : []);
|
|
707
|
+
const singleSlotAt = (key, value) => Option.match(Option.filter(Option.some(value), isAstNode), {
|
|
708
|
+
onNone: () => [],
|
|
709
|
+
onSome: (node) => [{
|
|
644
710
|
key,
|
|
645
|
-
index: null
|
|
646
|
-
|
|
711
|
+
index: null,
|
|
712
|
+
node
|
|
713
|
+
}]
|
|
714
|
+
});
|
|
715
|
+
const asWalkedNode = (value) => Option.filter(Option.some(value), isAstNode);
|
|
716
|
+
const childNodes = (node) => Option.match(asWalkedNode(node), {
|
|
717
|
+
onNone: () => [],
|
|
718
|
+
onSome: (walked) => childSlotsOf(walked).filter((slot) => !isCommentKey(slot.key)).map((slot) => ({
|
|
719
|
+
key: slot.key,
|
|
720
|
+
index: slot.index,
|
|
721
|
+
node: slot.node
|
|
722
|
+
}))
|
|
647
723
|
});
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
},
|
|
659
|
-
leave(node) {
|
|
660
|
-
ancestors.pop();
|
|
661
|
-
visitors.leave?.(node, [...ancestors]);
|
|
662
|
-
}
|
|
663
|
-
});
|
|
664
|
-
};
|
|
665
|
-
const collectNodes = (root) => {
|
|
666
|
-
const out = [];
|
|
667
|
-
walker(root, { enter(node) {
|
|
668
|
-
appendEntry(node, out);
|
|
669
|
-
} });
|
|
670
|
-
return out;
|
|
671
|
-
};
|
|
672
|
-
const appendEntry = (node, out) => {
|
|
673
|
-
Option.match(Option.fromNullishOr(spanOf(node)), {
|
|
674
|
-
onNone: () => void 0,
|
|
675
|
-
onSome: (span) => {
|
|
676
|
-
out.push({
|
|
677
|
-
node,
|
|
678
|
-
start: span.start,
|
|
679
|
-
end: span.end
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
});
|
|
683
|
-
};
|
|
724
|
+
const collectNodes = (root) => Option.toArray(nodeEntryOf(root)).concat(childEntriesOf(root));
|
|
725
|
+
const childEntriesOf = (root) => Option.match(asWalkedNode(root), {
|
|
726
|
+
onNone: () => [],
|
|
727
|
+
onSome: (node) => childSlotsOf(node).flatMap((slot) => collectNodes(slot.node))
|
|
728
|
+
});
|
|
729
|
+
const nodeEntryOf = (node) => Option.map(Option.fromNullishOr(spanOf(node)), (span) => ({
|
|
730
|
+
node,
|
|
731
|
+
start: span.start,
|
|
732
|
+
end: span.end
|
|
733
|
+
}));
|
|
684
734
|
function isAstNode(value) {
|
|
685
735
|
return Predicate.isObject(value) && typeof value["type"] === "string";
|
|
686
736
|
}
|
|
@@ -689,32 +739,41 @@ const COMMENT_KEYS = {
|
|
|
689
739
|
trailingComments: true
|
|
690
740
|
};
|
|
691
741
|
const isCommentKey = (key) => typeof key === "string" && COMMENT_KEYS[key] === true;
|
|
692
|
-
const walkTraverse = (root,
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
742
|
+
const walkTraverse = (root, visitors) => Option.match(asWalkedNode(root), {
|
|
743
|
+
onNone: () => void 0,
|
|
744
|
+
onSome: (node) => traverseNode(node, null, {
|
|
745
|
+
key: null,
|
|
746
|
+
index: null
|
|
747
|
+
}, visitors)
|
|
748
|
+
});
|
|
749
|
+
const skipControls = () => {
|
|
750
|
+
let skipped = false;
|
|
751
|
+
return {
|
|
752
|
+
skipped: () => skipped,
|
|
753
|
+
skip: () => {
|
|
754
|
+
skipped = true;
|
|
699
755
|
}
|
|
700
|
-
}
|
|
756
|
+
};
|
|
701
757
|
};
|
|
702
|
-
const
|
|
703
|
-
const readPath = (stack, node, controls, context, visitors) => {
|
|
758
|
+
const traverseNode = (node, parentPath, context, visitors) => {
|
|
704
759
|
Boolean$1.match(isCommentKey(context.key), {
|
|
705
760
|
onTrue: () => void 0,
|
|
706
761
|
onFalse: () => {
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
notify(visitors.enter,
|
|
762
|
+
const controls = skipControls();
|
|
763
|
+
const path = createPath(node, parentPath, controls, context);
|
|
764
|
+
notify(visitors.enter, path);
|
|
765
|
+
Boolean$1.match(controls.skipped(), {
|
|
766
|
+
onTrue: () => void 0,
|
|
767
|
+
onFalse: () => childSlotsOf(node).forEach((slot) => traverseNode(slot.node, path, {
|
|
768
|
+
key: slot.key,
|
|
769
|
+
index: slot.index
|
|
770
|
+
}, visitors))
|
|
771
|
+
});
|
|
772
|
+
notify(visitors.exit, path);
|
|
710
773
|
}
|
|
711
774
|
});
|
|
712
775
|
};
|
|
713
|
-
const
|
|
714
|
-
onTrue: () => void 0,
|
|
715
|
-
onFalse: () => notify(visitors.exit, stack.pop())
|
|
716
|
-
});
|
|
717
|
-
const parentOf = (stack) => Option.getOrNull(Arr.last(stack));
|
|
776
|
+
const traverse = dual(2, (self, visitors) => walkTraverse(self.root, visitors));
|
|
718
777
|
const notify = (visitor, path) => Option.match(Option.all({
|
|
719
778
|
visitor: Option.fromNullishOr(visitor),
|
|
720
779
|
path: Option.fromNullishOr(path)
|
|
@@ -877,13 +936,6 @@ const EMPTY_IMPORT_TABLE = Object.freeze({
|
|
|
877
936
|
bareFunctions: /* @__PURE__ */ new Map(),
|
|
878
937
|
pipeBindings: /* @__PURE__ */ new Set()
|
|
879
938
|
});
|
|
880
|
-
const importTables = /* @__PURE__ */ new WeakMap();
|
|
881
|
-
const importTableFor = (program) => Option.getOrElse(Option.fromNullishOr(importTables.get(program)), () => rememberImportTable(program));
|
|
882
|
-
function rememberImportTable(program) {
|
|
883
|
-
const table = buildImportTable(program);
|
|
884
|
-
importTables.set(program, table);
|
|
885
|
-
return table;
|
|
886
|
-
}
|
|
887
939
|
const buildImportTable = (program) => Arr.reduce(Arr.flatMap(Arr.filter(program.body, isImportDeclaration$1), importContributions), EMPTY_IMPORT_TABLE, applyContribution);
|
|
888
940
|
const withEntry = (entries, key, value) => new Map([...entries, [key, value]]);
|
|
889
941
|
const withMember = (members, value) => /* @__PURE__ */ new Set([...members, value]);
|
|
@@ -935,7 +987,7 @@ const namespaceBindingFor = (source) => Match.value(source).pipe(Match.when((can
|
|
|
935
987
|
}))));
|
|
936
988
|
const moduleNameFromName = (name) => Arr.findFirst(MODULE_NAMES, (module) => module === name);
|
|
937
989
|
const moduleNameFromSource = (source) => Arr.findFirst(MODULE_NAMES, (module) => source === `${EFFECT_SOURCE}/${module}`);
|
|
938
|
-
const resolveEffectCallDataFirst = (node, context) => Option.flatMap(programOf(context), (program) => resolveIn(node, context,
|
|
990
|
+
const resolveEffectCallDataFirst = (node, context) => Option.flatMap(programOf(context), (program) => resolveIn(node, context, buildImportTable(program)));
|
|
939
991
|
const resolveEffectCall = dual((args) => args.length >= 2, resolveEffectCallDataFirst);
|
|
940
992
|
const programOf = (context) => Arr.findLast(context.ancestors, isProgram);
|
|
941
993
|
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()));
|
|
@@ -1341,9 +1393,10 @@ function eqNode(a, b) {
|
|
|
1341
1393
|
return identity !== void 0 && identity === nodeIdentity(b);
|
|
1342
1394
|
}
|
|
1343
1395
|
function nodeIdentity(node) {
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1396
|
+
return Option.match(Option.fromNullishOr(spanOf(node)), {
|
|
1397
|
+
onNone: () => void 0,
|
|
1398
|
+
onSome: (span) => `${node.type}:${span.start}:${span.end}`
|
|
1399
|
+
});
|
|
1347
1400
|
}
|
|
1348
1401
|
function orDefault(value, fallback) {
|
|
1349
1402
|
return value ?? fallback;
|
|
@@ -1391,14 +1444,15 @@ function cloneWithReplacement(mutant, originalTree) {
|
|
|
1391
1444
|
function hasReplaced(root, original, replacement) {
|
|
1392
1445
|
let applied = false;
|
|
1393
1446
|
traverse(make(root), { enter(path) {
|
|
1394
|
-
|
|
1447
|
+
applied = applied || replaceFirstMatch(path, original, replacement);
|
|
1395
1448
|
} });
|
|
1396
1449
|
return applied;
|
|
1397
1450
|
}
|
|
1398
1451
|
function replaceFirstMatch(path, original, replacement) {
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1452
|
+
return Match.value(eqNode(path.node, original)).pipe(Match.when(false, () => false), Match.orElse(() => {
|
|
1453
|
+
path.replaceWith(replacement);
|
|
1454
|
+
return true;
|
|
1455
|
+
}));
|
|
1402
1456
|
}
|
|
1403
1457
|
/**
|
|
1404
1458
|
* The mutations of a regular expression pattern.
|
|
@@ -1427,17 +1481,16 @@ function replaceFirstMatch(path, original, replacement) {
|
|
|
1427
1481
|
* quantifier removal ahead of class negation.
|
|
1428
1482
|
*/
|
|
1429
1483
|
function mutateRegexPattern(pattern, flags) {
|
|
1430
|
-
|
|
1431
|
-
return parseRegexMutants(pattern, orDefault(flags, ""));
|
|
1484
|
+
return pattern.length === 0 ? [] : parseRegexMutants(pattern, orDefault(flags, ""));
|
|
1432
1485
|
}
|
|
1433
1486
|
function parseRegexMutants(pattern, flags) {
|
|
1434
1487
|
try {
|
|
1435
1488
|
const groups = collectSplices(pattern, flags);
|
|
1436
|
-
groups.rest.
|
|
1489
|
+
const rest = groups.rest.toSorted((a, b) => a.start - b.start || a.priority - b.priority);
|
|
1437
1490
|
return [
|
|
1438
1491
|
...groups.bol,
|
|
1439
1492
|
...groups.eol,
|
|
1440
|
-
...
|
|
1493
|
+
...rest
|
|
1441
1494
|
].map((splice) => spliceText(pattern, splice));
|
|
1442
1495
|
} catch {
|
|
1443
1496
|
return [];
|
|
@@ -1500,18 +1553,17 @@ function negationMarker(negate) {
|
|
|
1500
1553
|
}
|
|
1501
1554
|
function collectCharacterClass(characterClass, groups) {
|
|
1502
1555
|
const pos = characterClass.start + 1;
|
|
1503
|
-
|
|
1556
|
+
groups.rest.push(Match.value(characterClass.negate).pipe(Match.when(true, () => ({
|
|
1504
1557
|
start: pos,
|
|
1505
1558
|
end: pos + 1,
|
|
1506
1559
|
text: "",
|
|
1507
1560
|
priority: 1
|
|
1508
|
-
})
|
|
1509
|
-
else groups.rest.push({
|
|
1561
|
+
})), Match.orElse(() => ({
|
|
1510
1562
|
start: pos,
|
|
1511
1563
|
end: pos,
|
|
1512
1564
|
text: "^",
|
|
1513
1565
|
priority: 1
|
|
1514
|
-
});
|
|
1566
|
+
}))));
|
|
1515
1567
|
}
|
|
1516
1568
|
function collectCharacterSet(characterSet, groups) {
|
|
1517
1569
|
pushWhen(groups.rest, characterSetSplice(characterSet));
|
|
@@ -1541,12 +1593,10 @@ function classSplice(characterSet) {
|
|
|
1541
1593
|
/** `\d`-style sets invert by letter case, `\p{}` by `p`/`P`. */
|
|
1542
1594
|
function invertedMarker(marker) {
|
|
1543
1595
|
const propertyMarker = PROPERTY_MARKERS[marker];
|
|
1544
|
-
|
|
1545
|
-
return invertLetterCase(marker);
|
|
1596
|
+
return propertyMarker === void 0 ? invertLetterCase(marker) : propertyMarker;
|
|
1546
1597
|
}
|
|
1547
1598
|
function invertLetterCase(letter) {
|
|
1548
|
-
|
|
1549
|
-
return letter.toUpperCase();
|
|
1599
|
+
return letter === letter.toUpperCase() ? letter.toLowerCase() : letter.toUpperCase();
|
|
1550
1600
|
}
|
|
1551
1601
|
function collectQuantifier(quantifier, groups) {
|
|
1552
1602
|
groups.rest.push({
|
|
@@ -1562,11 +1612,15 @@ function anchorRemoval(assertion, pattern) {
|
|
|
1562
1612
|
end: assertion.end,
|
|
1563
1613
|
text: ""
|
|
1564
1614
|
};
|
|
1565
|
-
|
|
1566
|
-
return splice;
|
|
1615
|
+
return spliceText(pattern, splice).length === 0 ? void 0 : splice;
|
|
1567
1616
|
}
|
|
1568
1617
|
function pushWhen(list, splice) {
|
|
1569
|
-
|
|
1618
|
+
Option.match(Option.fromNullishOr(splice), {
|
|
1619
|
+
onNone: () => void 0,
|
|
1620
|
+
onSome: (present) => {
|
|
1621
|
+
list.push(present);
|
|
1622
|
+
}
|
|
1623
|
+
});
|
|
1570
1624
|
}
|
|
1571
1625
|
function spliceText(pattern, splice) {
|
|
1572
1626
|
return pattern.slice(0, splice.start) + splice.text + pattern.slice(splice.end);
|
|
@@ -1620,16 +1674,14 @@ function isStringConcatenation(node) {
|
|
|
1620
1674
|
}
|
|
1621
1675
|
/** A chained `a + b + c` carries its value on the innermost left operand's right side. */
|
|
1622
1676
|
function outerLeftOperand(node) {
|
|
1623
|
-
|
|
1624
|
-
return node.left;
|
|
1677
|
+
return node.left.type === "BinaryExpression" ? node.left.right : node.left;
|
|
1625
1678
|
}
|
|
1626
1679
|
const arrayDeclarationMutator = (node) => Match.value(node).pipe(Match.when(isArrayExpression, (array) => [arrayDeclarationReplacement(array)]), Match.when(isArrayConstructorCall, (construct) => [arrayConstructorReplacement(construct)]), Match.orElse(() => NO_MUTANTS));
|
|
1627
1680
|
function isArrayExpression(node) {
|
|
1628
1681
|
return node.type === "ArrayExpression";
|
|
1629
1682
|
}
|
|
1630
1683
|
function arrayDeclarationReplacement(array) {
|
|
1631
|
-
|
|
1632
|
-
return arrayExpression([stringLiteral("Stryker was here")]);
|
|
1684
|
+
return array.elements.length > 0 ? arrayExpression([]) : arrayExpression([stringLiteral("Stryker was here")]);
|
|
1633
1685
|
}
|
|
1634
1686
|
function isArrayConstructorCall(node) {
|
|
1635
1687
|
return isCallOrNewExpression(node) && isArrayIdentifier(node.callee);
|
|
@@ -1642,12 +1694,11 @@ function isArrayIdentifier(node) {
|
|
|
1642
1694
|
}
|
|
1643
1695
|
function arrayConstructorReplacement(construct) {
|
|
1644
1696
|
const mutatedCallArgs = constructorArguments(construct.arguments);
|
|
1645
|
-
|
|
1646
|
-
return
|
|
1697
|
+
const callee = cloneNode(construct.callee);
|
|
1698
|
+
return construct.type === "NewExpression" ? newExpression(callee, mutatedCallArgs) : callExpression(callee, mutatedCallArgs);
|
|
1647
1699
|
}
|
|
1648
1700
|
function constructorArguments(args) {
|
|
1649
|
-
|
|
1650
|
-
return [arrayExpression([])];
|
|
1701
|
+
return args.length > 0 ? [] : [arrayExpression([])];
|
|
1651
1702
|
}
|
|
1652
1703
|
const arrowFunctionMutator = (node) => Match.value(node).pipe(Match.when(isExpressionBodiedArrow, () => [arrowFunctionExpression([], identifier("undefined"))]), Match.orElse(() => NO_MUTANTS));
|
|
1653
1704
|
function isExpressionBodiedArrow(node) {
|
|
@@ -1683,8 +1734,7 @@ function isStringLiteral(value) {
|
|
|
1683
1734
|
return nodeType(value) === "Literal" && hasStringValue(value);
|
|
1684
1735
|
}
|
|
1685
1736
|
function hasStringValue(value) {
|
|
1686
|
-
|
|
1687
|
-
return typeof value["value"] === "string";
|
|
1737
|
+
return Predicate.hasProperty(value, "value") && typeof value["value"] === "string";
|
|
1688
1738
|
}
|
|
1689
1739
|
const stringAssignmentTypes = Object.freeze([
|
|
1690
1740
|
"&&=",
|
|
@@ -1767,8 +1817,7 @@ function hasSuperInChildren(node) {
|
|
|
1767
1817
|
return Object.keys(node).some((key) => containsSuperInValue(propertyOf(node, key)));
|
|
1768
1818
|
}
|
|
1769
1819
|
function containsSuperInValue(value) {
|
|
1770
|
-
|
|
1771
|
-
return containsSuperCall(value);
|
|
1820
|
+
return Array.isArray(value) ? value.some(containsSuperCall) : containsSuperCall(value);
|
|
1772
1821
|
}
|
|
1773
1822
|
const booleanLiteralMutator = (node) => Match.value(node).pipe(Match.when(isBooleanLiteral, (literal) => [booleanLiteral(!literal.value)]), Match.when(isNegatedPrefix, (unary) => [cloneNode(unary.argument)]), Match.orElse(() => NO_MUTANTS));
|
|
1774
1823
|
function isBooleanLiteral(node) {
|
|
@@ -1919,8 +1968,12 @@ const baseReplacements = {
|
|
|
1919
1968
|
setUTCSeconds: "setUTCMilliseconds"
|
|
1920
1969
|
};
|
|
1921
1970
|
const noReverseReplacements = ["getUTCDate", "setUTCDate"];
|
|
1922
|
-
const
|
|
1923
|
-
|
|
1971
|
+
const isReversible = (key, value) => value !== null && !noReverseReplacements.includes(key);
|
|
1972
|
+
const reverseReplacements = Object.fromEntries(Object.entries(baseReplacements).flatMap(([key, value]) => isReversible(key, value) ? [[value, key]] : []));
|
|
1973
|
+
const replacements = {
|
|
1974
|
+
...baseReplacements,
|
|
1975
|
+
...reverseReplacements
|
|
1976
|
+
};
|
|
1924
1977
|
const methodExpressionMutator = (node) => Match.value(node).pipe(Match.when(isCallExpression, (call) => methodCallMutants(call)), Match.orElse(() => NO_MUTANTS));
|
|
1925
1978
|
function methodCallMutants(call) {
|
|
1926
1979
|
return Match.value(methodMutation(call)).pipe(Match.when(isMethodMutation, (mutation) => [methodExpressionReplacement(mutation)]), Match.orElse(() => NO_MUTANTS));
|
|
@@ -1934,7 +1987,9 @@ function methodMutation(call) {
|
|
|
1934
1987
|
return Match.value(callee).pipe(Match.when(void 0, () => void 0), Match.orElse((member) => mutationFor(call, member)));
|
|
1935
1988
|
}
|
|
1936
1989
|
function mutationFor(call, callee) {
|
|
1937
|
-
|
|
1990
|
+
const methodName = callee.property.name;
|
|
1991
|
+
const replacement = Object.hasOwn(replacements, methodName) ? replacements[methodName] : void 0;
|
|
1992
|
+
return Match.value(replacement).pipe(Match.when(void 0, () => void 0), Match.orElse((newName) => ({
|
|
1938
1993
|
call,
|
|
1939
1994
|
callee,
|
|
1940
1995
|
newName
|
|
@@ -2017,16 +2072,17 @@ const PLACEHOLDER = "Stryker was here!";
|
|
|
2017
2072
|
const stringLiteralMutator = (node, context) => Match.value(node).pipe(Match.when(isTemplateLiteral, (template) => templateMutants(template)), Match.when(isStringLiteral, (literal) => mutantsWhen(isValidParent(literal, context), () => [stringLiteral(replacementText(literal.value.length === 0))])), Match.orElse(() => NO_MUTANTS));
|
|
2018
2073
|
function templateMutants(template) {
|
|
2019
2074
|
const first = template.quasis[0];
|
|
2020
|
-
|
|
2021
|
-
|
|
2075
|
+
return Option.match(Option.fromNullishOr(first), {
|
|
2076
|
+
onNone: () => NO_MUTANTS,
|
|
2077
|
+
onSome: (present) => [emptyOrPlaceholderTemplate(template, present)]
|
|
2078
|
+
});
|
|
2022
2079
|
}
|
|
2023
2080
|
function emptyOrPlaceholderTemplate(template, first) {
|
|
2024
2081
|
const isEmptyTemplate = [template.quasis.length === 1, first.value.raw.length === 0].every(Boolean);
|
|
2025
2082
|
return templateLiteral([templateElement(replacementText(isEmptyTemplate))], []);
|
|
2026
2083
|
}
|
|
2027
2084
|
function replacementText(isEmpty) {
|
|
2028
|
-
|
|
2029
|
-
return "";
|
|
2085
|
+
return isEmpty ? PLACEHOLDER : "";
|
|
2030
2086
|
}
|
|
2031
2087
|
function isValidParent(child, context) {
|
|
2032
2088
|
const parent = context.parent;
|
|
@@ -2181,10 +2237,7 @@ const selectMutators = dual((args) => args.length >= 2, selectMutatorsDataFirst)
|
|
|
2181
2237
|
var ParseFailed = class extends S.TaggedError("@systemfsoftware/stryker-js-instrumenter/Parser.schema/ParseFailed")("ParseFailed", {
|
|
2182
2238
|
fileName: S.String,
|
|
2183
2239
|
message: S.String,
|
|
2184
|
-
location:
|
|
2185
|
-
line: S.Finite,
|
|
2186
|
-
column: S.Finite
|
|
2187
|
-
}),
|
|
2240
|
+
location: Position,
|
|
2188
2241
|
cause: S.Defect()
|
|
2189
2242
|
}) {
|
|
2190
2243
|
get message() {
|
|
@@ -2195,61 +2248,70 @@ var ParseFailed = class extends S.TaggedError("@systemfsoftware/stryker-js-instr
|
|
|
2195
2248
|
//#region src/Parser.service.ts
|
|
2196
2249
|
const oxcModule = Effect.cached(Effect.promise(() => import("oxc-parser")));
|
|
2197
2250
|
const loadOxc = Effect.flatMap(oxcModule, (load) => load);
|
|
2198
|
-
const parseWithOxcDataFirst = (text, fileName, lang)
|
|
2251
|
+
const parseWithOxcDataFirst = Effect.fn("stryker.instrument.parser.parseWithOxc")(function* (text, fileName, lang) {
|
|
2199
2252
|
const result = (yield* loadOxc).parseSync(fileName, text, {
|
|
2200
2253
|
lang,
|
|
2201
2254
|
range: true
|
|
2202
2255
|
});
|
|
2203
|
-
const
|
|
2204
|
-
const failure = oxcParseFailure(result.errors, fileName,
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2256
|
+
const lineStarts = lineStartsOf(text);
|
|
2257
|
+
const failure = oxcParseFailure(result.errors, fileName, lineStarts);
|
|
2258
|
+
return yield* Option.match(Option.fromNullishOr(failure), {
|
|
2259
|
+
onNone: () => Effect.succeed({
|
|
2260
|
+
root: result.program,
|
|
2261
|
+
comments: result.comments
|
|
2262
|
+
}),
|
|
2263
|
+
onSome: (failed) => Effect.fail(failed)
|
|
2264
|
+
});
|
|
2210
2265
|
});
|
|
2211
2266
|
const parseWithOxc = dual((args) => args.length >= 3, parseWithOxcDataFirst);
|
|
2212
|
-
const oxcParseFailure = (errors, fileName,
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
return ParseFailed.make({
|
|
2267
|
+
const oxcParseFailure = (errors, fileName, lineStarts) => Option.match(Option.fromNullishOr(errors.at(0)), {
|
|
2268
|
+
onNone: () => void 0,
|
|
2269
|
+
onSome: (first) => ParseFailed.make({
|
|
2216
2270
|
fileName,
|
|
2217
2271
|
message: first.message,
|
|
2218
|
-
location:
|
|
2272
|
+
location: positionAt(lineStarts, oxcErrorLabelStart(first)),
|
|
2219
2273
|
cause: errors.map((reported) => reported.message)
|
|
2220
|
-
})
|
|
2221
|
-
};
|
|
2222
|
-
const oxcErrorLabelStart = (error) =>
|
|
2223
|
-
const label = error.labels.at(0);
|
|
2224
|
-
if (label === void 0) return 0;
|
|
2225
|
-
return label.start;
|
|
2226
|
-
};
|
|
2274
|
+
})
|
|
2275
|
+
});
|
|
2276
|
+
const oxcErrorLabelStart = (error) => Option.getOrElse(Option.map(Option.fromNullishOr(error.labels.at(0)), (label) => label.start), () => 0);
|
|
2227
2277
|
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) });
|
|
2228
|
-
const parseJSDataFirst =
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2278
|
+
const parseJSDataFirst = Effect.fn("stryker.instrument.parser.parseJS")(function* (text, fileName) {
|
|
2279
|
+
const parsed = yield* parseWithOxc(text, fileName, "js");
|
|
2280
|
+
return {
|
|
2281
|
+
originFileName: fileName,
|
|
2282
|
+
rawContent: text,
|
|
2283
|
+
format: "js",
|
|
2284
|
+
root: parsed.root,
|
|
2285
|
+
comments: parsed.comments
|
|
2286
|
+
};
|
|
2287
|
+
});
|
|
2235
2288
|
const parseJS = dual((args) => args.length >= 2, parseJSDataFirst);
|
|
2236
|
-
const parseTSDataFirst =
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2289
|
+
const parseTSDataFirst = Effect.fn("stryker.instrument.parser.parseTS")(function* (text, fileName) {
|
|
2290
|
+
const parsed = yield* parseWithOxc(text, fileName, "ts");
|
|
2291
|
+
return {
|
|
2292
|
+
originFileName: fileName,
|
|
2293
|
+
rawContent: text,
|
|
2294
|
+
format: "ts",
|
|
2295
|
+
root: parsed.root,
|
|
2296
|
+
comments: parsed.comments
|
|
2297
|
+
};
|
|
2298
|
+
});
|
|
2243
2299
|
const parseTS = dual((args) => args.length >= 2, parseTSDataFirst);
|
|
2244
|
-
const parseTsxDataFirst =
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2300
|
+
const parseTsxDataFirst = Effect.fn("stryker.instrument.parser.parseTsx")(function* (text, fileName) {
|
|
2301
|
+
const parsed = yield* parseWithOxc(text, fileName, "tsx");
|
|
2302
|
+
return {
|
|
2303
|
+
root: parsed.root,
|
|
2304
|
+
comments: parsed.comments,
|
|
2305
|
+
format: "tsx",
|
|
2306
|
+
originFileName: fileName,
|
|
2307
|
+
rawContent: text
|
|
2308
|
+
};
|
|
2309
|
+
});
|
|
2251
2310
|
const parseTsx = dual((args) => args.length >= 2, parseTsxDataFirst);
|
|
2252
2311
|
//#endregion
|
|
2312
|
+
//#region src/print/PrintFailed.schema.ts
|
|
2313
|
+
var PrintFailed = class extends S.TaggedError("@systemfsoftware/stryker-js-instrumenter/print/PrintFailed.schema/PrintFailed")("PrintFailed", { message: S.String }) {};
|
|
2314
|
+
//#endregion
|
|
2253
2315
|
//#region src/print/SourceText.ts
|
|
2254
2316
|
const printProgramDataFirst = (program, opts = {}) => programText(opts, program);
|
|
2255
2317
|
const printProgram = dual((args) => Predicate.hasProperty(args[0], "type"), printProgramDataFirst);
|
|
@@ -3239,28 +3301,19 @@ const externalModuleArgumentText = (value) => Boolean$1.match(Predicate.isTruthy
|
|
|
3239
3301
|
function printDataFirst(file, entryForFormat) {
|
|
3240
3302
|
const context = { print: (inner) => print(inner, entryForFormat) };
|
|
3241
3303
|
return Option.match(entryForFormat(formatKeyOf(file)), {
|
|
3242
|
-
onNone: () =>
|
|
3243
|
-
onSome: (entry) => entry.print(file, context)
|
|
3304
|
+
onNone: () => Result.fail(PrintFailed.make({ message: `No registered format renders the "${formatKeyOf(file)}" AST` })),
|
|
3305
|
+
onSome: (entry) => Result.succeed(entry.print(file, context))
|
|
3244
3306
|
});
|
|
3245
3307
|
}
|
|
3246
3308
|
const print = dual((args) => args.length >= 2, printDataFirst);
|
|
3247
|
-
|
|
3248
|
-
throw new Error(`No registered format renders the "${formatKeyOf(file)}" AST`);
|
|
3249
|
-
}
|
|
3250
|
-
function hashbangOf(root) {
|
|
3251
|
-
if (!hasHashbang(root)) return null;
|
|
3252
|
-
return printHashbang(root.hashbang);
|
|
3253
|
-
}
|
|
3309
|
+
const hashbangOf = (root) => hasHashbang(root) ? printHashbang(root.hashbang) : null;
|
|
3254
3310
|
const hasHashbang = (root) => Predicate.hasProperty(root, "hashbang") && Predicate.isObject(root.hashbang);
|
|
3255
3311
|
const printHashbang = (field) => ({
|
|
3256
3312
|
type: "Hashbang",
|
|
3257
3313
|
value: hashbangValue(field),
|
|
3258
3314
|
start: field.start
|
|
3259
3315
|
});
|
|
3260
|
-
|
|
3261
|
-
if (typeof field.value !== "string") return "";
|
|
3262
|
-
return field.value;
|
|
3263
|
-
}
|
|
3316
|
+
const hashbangValue = (field) => typeof field.value === "string" ? field.value : "";
|
|
3264
3317
|
const printScriptAst$1 = (root) => printProgram(root, { hashbang: hashbangOf(root) });
|
|
3265
3318
|
const jsPrintDataFirst = (file) => printScriptAst$1(file.root);
|
|
3266
3319
|
const jsPrint = dual((args) => args.length >= 2, jsPrintDataFirst);
|
|
@@ -3302,7 +3355,11 @@ var FormatOverrideUnclaimed = class extends S.TaggedError()("FormatOverrideUncla
|
|
|
3302
3355
|
fileName: S.String,
|
|
3303
3356
|
formatId: S.String,
|
|
3304
3357
|
reason: S.String
|
|
3305
|
-
}) {
|
|
3358
|
+
}) {
|
|
3359
|
+
get message() {
|
|
3360
|
+
return `Cannot instrument ${this.fileName}: ${this.reason}`;
|
|
3361
|
+
}
|
|
3362
|
+
};
|
|
3306
3363
|
const pinOf = (command) => Option.fromUndefinedOr(command.formatId);
|
|
3307
3364
|
const matchesCommand = (claim, command) => Match.value(pinOf(command)).pipe(Match.when(Option.isSome, (pin) => claim.formatId === pin.value), Match.orElse(() => claim.extensions.includes(command.extension)));
|
|
3308
3365
|
const claimFor = (command) => command.claims.find((claim) => matchesCommand(claim, command));
|
|
@@ -3344,51 +3401,10 @@ const resolveFormat = Workflow.make({
|
|
|
3344
3401
|
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)
|
|
3345
3402
|
});
|
|
3346
3403
|
//#endregion
|
|
3347
|
-
//#region src/directives/directive.schema.ts
|
|
3348
|
-
const MutatorNameSchema = S.String.pipe(S.check(S.isPattern(/^[a-zA-Z]+(?: [a-zA-Z]+)*$/)));
|
|
3349
|
-
const DirectiveReasonSchema = S.String.pipe(S.check(S.isPattern(/^\S(?:[\s\S]*\S)?$/)));
|
|
3350
|
-
const DirectiveSchema = S.Struct({
|
|
3351
|
-
action: S.Literals(["disable", "restore"]),
|
|
3352
|
-
scope: S.Literals(["next-line", "block"]),
|
|
3353
|
-
mutatorNames: S.Array(MutatorNameSchema).check(S.isMinLength(1)),
|
|
3354
|
-
reason: DirectiveReasonSchema
|
|
3355
|
-
});
|
|
3356
|
-
const LocatedPositionSchema = S.Struct({
|
|
3357
|
-
line: SourceLineSchema,
|
|
3358
|
-
column: SourceColumnSchema
|
|
3359
|
-
});
|
|
3360
|
-
const LocatedDirectiveSchema = S.Struct({
|
|
3361
|
-
directive: DirectiveSchema,
|
|
3362
|
-
at: LocatedPositionSchema,
|
|
3363
|
-
governedLine: SourceLineSchema
|
|
3364
|
-
});
|
|
3365
|
-
S.Struct({
|
|
3366
|
-
directive: DirectiveSchema,
|
|
3367
|
-
at: LocatedPositionSchema,
|
|
3368
|
-
mutatorName: S.String
|
|
3369
|
-
});
|
|
3370
|
-
//#endregion
|
|
3371
3404
|
//#region src/directives/decode-directive.workflow.ts
|
|
3372
3405
|
const DIRECTIVE_PATTERN = /^\s?Stryker (disable|restore)(?: (next-line))? ([a-zA-Z, ]+)(?::(.+)?)?/;
|
|
3373
3406
|
const DEFAULT_REASON = "Ignored using a comment";
|
|
3374
3407
|
const NEXT_LINE$1 = "next-line";
|
|
3375
|
-
S.Struct({
|
|
3376
|
-
clause: S.Literals([
|
|
3377
|
-
" Stryker disable",
|
|
3378
|
-
" Stryker restore",
|
|
3379
|
-
" Stryker disable next-line",
|
|
3380
|
-
" Stryker restore next-line",
|
|
3381
|
-
" Stryker enable",
|
|
3382
|
-
" nothing"
|
|
3383
|
-
]),
|
|
3384
|
-
nameLetters: S.Array(S.Literals([
|
|
3385
|
-
"a",
|
|
3386
|
-
"b",
|
|
3387
|
-
"Z",
|
|
3388
|
-
",",
|
|
3389
|
-
" "
|
|
3390
|
-
])).check(S.isMinLength(1))
|
|
3391
|
-
});
|
|
3392
3408
|
var DecodeDirectiveCommand = class extends S.TaggedClass()("DecodeDirectiveCommand", { commentText: S.String }) {
|
|
3393
3409
|
static [Workflow.InstrumentationBrand] = {};
|
|
3394
3410
|
};
|
|
@@ -3496,27 +3512,34 @@ const loadHeader = Effect.map(parseWithOxc(INSTRUMENTATION_HEADER_SOURCE, "instr
|
|
|
3496
3512
|
instrumentationHeaderValue = parsed.root.body;
|
|
3497
3513
|
return parsed.root.body;
|
|
3498
3514
|
});
|
|
3499
|
-
const instrumentationHeader =
|
|
3515
|
+
const instrumentationHeader = Option.match(Option.fromNullishOr(instrumentationHeaderValue), {
|
|
3516
|
+
onNone: () => loadHeader,
|
|
3517
|
+
onSome: (header) => Effect.succeed(header)
|
|
3518
|
+
});
|
|
3500
3519
|
const placeHeader = (root) => Effect.map(headerFor(root), (header) => {
|
|
3501
3520
|
root.body.unshift(...header);
|
|
3502
3521
|
});
|
|
3503
|
-
const headerFor = (root) => Effect.map(instrumentationHeader, (header) => Option.match(leadingCommentsOf(root), {
|
|
3522
|
+
const headerFor = (root) => Effect.map(instrumentationHeader, (header) => Option.match(Option.all([Option.fromNullishOr(header[0]), leadingCommentsOf(root)]), {
|
|
3504
3523
|
onNone: () => header,
|
|
3505
|
-
onSome: (leadingComments) =>
|
|
3524
|
+
onSome: ([firstHeader, leadingComments]) => commentedHeader(firstHeader, leadingComments, header.slice(1))
|
|
3506
3525
|
}));
|
|
3507
|
-
const placeHeaderIfNeededDataFirst = (placed, options, root) => placed
|
|
3526
|
+
const placeHeaderIfNeededDataFirst = (placed, options, root) => Boolean$1.match(placed, {
|
|
3527
|
+
onTrue: () => placeHeaderWhenWanted(options, root),
|
|
3528
|
+
onFalse: () => Effect.void
|
|
3529
|
+
});
|
|
3508
3530
|
const placeHeaderIfNeeded = dual((args) => args.length >= 3, placeHeaderIfNeededDataFirst);
|
|
3509
|
-
const placeHeaderWhenWanted = (options, root) => options.noHeader === true
|
|
3531
|
+
const placeHeaderWhenWanted = (options, root) => Boolean$1.match(options.noHeader === true, {
|
|
3532
|
+
onTrue: () => Effect.void,
|
|
3533
|
+
onFalse: () => placeHeader(root)
|
|
3534
|
+
});
|
|
3510
3535
|
function leadingCommentsOf(root) {
|
|
3511
3536
|
const first = root.body[0];
|
|
3512
|
-
|
|
3513
|
-
return Option.some(first.leadingComments);
|
|
3537
|
+
return Option.map(Option.liftPredicate(first, hasLeadingComments$1), (value) => value.leadingComments);
|
|
3514
3538
|
}
|
|
3515
|
-
function commentedHeader(leadingComments,
|
|
3516
|
-
const firstHeader = Option.getOrThrowWith(Option.fromNullishOr(header[0]), () => /* @__PURE__ */ new Error("Instrumentation header is empty"));
|
|
3539
|
+
function commentedHeader(firstHeader, leadingComments, rest) {
|
|
3517
3540
|
const cloned = cloneNode(firstHeader);
|
|
3518
3541
|
Object.assign(cloned, { leadingComments });
|
|
3519
|
-
return cloned;
|
|
3542
|
+
return [cloned, ...rest];
|
|
3520
3543
|
}
|
|
3521
3544
|
//#endregion
|
|
3522
3545
|
//#region src/place-mutants.workflow.ts
|
|
@@ -3532,8 +3555,8 @@ const ReplacementFactsSchema = S.Struct({
|
|
|
3532
3555
|
isSwitchCase: S.Boolean
|
|
3533
3556
|
});
|
|
3534
3557
|
const PlacedMutantSchema = S.Struct({
|
|
3535
|
-
id:
|
|
3536
|
-
mutatorName:
|
|
3558
|
+
id: MutantId,
|
|
3559
|
+
mutatorName: MutatorNameSchema,
|
|
3537
3560
|
replacement: ReplacementFactsSchema
|
|
3538
3561
|
});
|
|
3539
3562
|
var PlaceMutantsCommand = class extends S.TaggedClass()("PlaceMutantsCommand", {
|
|
@@ -3546,19 +3569,19 @@ var PlaceMutantsCommand = class extends S.TaggedClass()("PlaceMutantsCommand", {
|
|
|
3546
3569
|
const PlacementDecisionTypeId = Symbol.for("@systemfsoftware/stryker-js-instrumenter/PlacementDecision");
|
|
3547
3570
|
var ExpressionSite = class extends S.TaggedClass()("ExpressionSite", {
|
|
3548
3571
|
fileName: S.String,
|
|
3549
|
-
mutantIds: S.Array(
|
|
3572
|
+
mutantIds: S.Array(MutantId)
|
|
3550
3573
|
}) {
|
|
3551
3574
|
[PlacementDecisionTypeId] = PlacementDecisionTypeId;
|
|
3552
3575
|
};
|
|
3553
3576
|
var StatementSite = class extends S.TaggedClass()("StatementSite", {
|
|
3554
3577
|
fileName: S.String,
|
|
3555
|
-
mutantIds: S.Array(
|
|
3578
|
+
mutantIds: S.Array(MutantId)
|
|
3556
3579
|
}) {
|
|
3557
3580
|
[PlacementDecisionTypeId] = PlacementDecisionTypeId;
|
|
3558
3581
|
};
|
|
3559
3582
|
var SwitchCaseSite = class extends S.TaggedClass()("SwitchCaseSite", {
|
|
3560
3583
|
fileName: S.String,
|
|
3561
|
-
mutantIds: S.Array(
|
|
3584
|
+
mutantIds: S.Array(MutantId)
|
|
3562
3585
|
}) {
|
|
3563
3586
|
[PlacementDecisionTypeId] = PlacementDecisionTypeId;
|
|
3564
3587
|
};
|
|
@@ -3570,11 +3593,19 @@ const ExpectedKindSchema = S.Literals([
|
|
|
3570
3593
|
var MutantKindMismatch = class extends S.TaggedError()("MutantKindMismatch", {
|
|
3571
3594
|
fileName: S.String,
|
|
3572
3595
|
placer: PlacerNameSchema,
|
|
3573
|
-
mutantId:
|
|
3574
|
-
mutatorName:
|
|
3596
|
+
mutantId: MutantId,
|
|
3597
|
+
mutatorName: MutatorNameSchema,
|
|
3575
3598
|
expected: ExpectedKindSchema
|
|
3576
|
-
}) {
|
|
3577
|
-
|
|
3599
|
+
}) {
|
|
3600
|
+
get message() {
|
|
3601
|
+
return `Expected ${this.expected} for mutant ${this.mutantId} (${this.mutatorName}) in ${this.fileName}`;
|
|
3602
|
+
}
|
|
3603
|
+
};
|
|
3604
|
+
var NoPlacerClaimsNode = class extends S.TaggedError()("NoPlacerClaimsNode", { fileName: S.String }) {
|
|
3605
|
+
get message() {
|
|
3606
|
+
return `No placer claims the node in ${this.fileName}`;
|
|
3607
|
+
}
|
|
3608
|
+
};
|
|
3578
3609
|
const claimingPlacer = (facts) => Match.value(facts).pipe(Match.when({
|
|
3579
3610
|
isExpression: true,
|
|
3580
3611
|
expressionIsValid: true
|
|
@@ -3624,23 +3655,19 @@ const placeMutants = Workflow.make({
|
|
|
3624
3655
|
});
|
|
3625
3656
|
//#endregion
|
|
3626
3657
|
//#region src/plan-mutants.workflow.ts
|
|
3627
|
-
const LocationSchema = S.Struct({
|
|
3628
|
-
start: NodePositionSchema,
|
|
3629
|
-
end: NodePositionSchema
|
|
3630
|
-
});
|
|
3631
3658
|
const WILDCARD = "all";
|
|
3632
3659
|
const NEXT_LINE = "next-line";
|
|
3633
3660
|
const MutantCandidateSchema = S.Struct({
|
|
3634
|
-
mutatorName:
|
|
3661
|
+
mutatorName: MutatorNameSchema,
|
|
3635
3662
|
replacementCode: S.String,
|
|
3636
|
-
location: S.optional(
|
|
3663
|
+
location: S.optional(Location),
|
|
3637
3664
|
ignorerReason: S.optional(S.String)
|
|
3638
3665
|
});
|
|
3639
3666
|
const PlannedMutantSchema = S.Struct({
|
|
3640
|
-
id:
|
|
3641
|
-
mutatorName:
|
|
3667
|
+
id: MutantId,
|
|
3668
|
+
mutatorName: MutatorNameSchema,
|
|
3642
3669
|
replacementCode: S.String,
|
|
3643
|
-
location:
|
|
3670
|
+
location: Location,
|
|
3644
3671
|
ignoreReason: S.optional(S.String)
|
|
3645
3672
|
});
|
|
3646
3673
|
const MutantCounterSchema = S.Int.pipe(S.check(S.isBetween({
|
|
@@ -3651,10 +3678,10 @@ const MutantCountSchema = S.Int.pipe(S.check(S.isGreaterThanOrEqualTo(0)));
|
|
|
3651
3678
|
var PlanMutantsCommand = class extends S.TaggedClass()("PlanMutantsCommand", {
|
|
3652
3679
|
fileName: S.String,
|
|
3653
3680
|
firstIndex: MutantCounterSchema,
|
|
3654
|
-
offset:
|
|
3655
|
-
line:
|
|
3656
|
-
mutatorNames: S.Array(
|
|
3657
|
-
excludedMutations: S.Array(
|
|
3681
|
+
offset: ScriptOrigin,
|
|
3682
|
+
line: Line,
|
|
3683
|
+
mutatorNames: S.Array(MutatorNameSchema),
|
|
3684
|
+
excludedMutations: S.Array(MutatorNameSchema),
|
|
3658
3685
|
rule: S.Array(LocatedDirectiveSchema),
|
|
3659
3686
|
directives: S.Array(LocatedDirectiveSchema),
|
|
3660
3687
|
candidates: S.Array(MutantCandidateSchema)
|
|
@@ -3679,8 +3706,12 @@ var MutantsFullyIgnored = class extends S.TaggedClass()("MutantsFullyIgnored", {
|
|
|
3679
3706
|
};
|
|
3680
3707
|
var MutantWithoutLocation = class extends S.TaggedError()("MutantWithoutLocation", {
|
|
3681
3708
|
fileName: S.String,
|
|
3682
|
-
mutatorName:
|
|
3683
|
-
}) {
|
|
3709
|
+
mutatorName: MutatorNameSchema
|
|
3710
|
+
}) {
|
|
3711
|
+
get message() {
|
|
3712
|
+
return `Mutant without a source location: ${this.mutatorName} in ${this.fileName}`;
|
|
3713
|
+
}
|
|
3714
|
+
};
|
|
3684
3715
|
const reachedLine = (located, line) => Match.value(located.directive.scope).pipe(Match.when(NEXT_LINE, () => located.governedLine === line), Match.when("block", () => true), Match.exhaustive);
|
|
3685
3716
|
const namesMutator = (located, lowerMutatorName) => located.directive.mutatorNames.some((name) => [name === WILDCARD, name.toLowerCase() === lowerMutatorName].some(Boolean));
|
|
3686
3717
|
const lastReachingDirective = (rule, lowerMutatorName, line) => Option.fromNullishOr(rule.findLast((located) => [reachedLine(located, line), namesMutator(located, lowerMutatorName)].every(Boolean)));
|
|
@@ -3697,12 +3728,13 @@ const unusedDirectiveWarning = (unused, fileName) => {
|
|
|
3697
3728
|
};
|
|
3698
3729
|
const warningsOf = (command) => unusedDirectives(command.directives, command.mutatorNames).map((unused) => unusedDirectiveWarning(unused, command.fileName));
|
|
3699
3730
|
/**
|
|
3700
|
-
*
|
|
3701
|
-
*
|
|
3702
|
-
*
|
|
3703
|
-
*
|
|
3731
|
+
* The node span (`source`) is a 1-based position inside the embedded script;
|
|
3732
|
+
* the region origin (`offset`) is where that script begins in its host file.
|
|
3733
|
+
* The region's first line carries its column shift, later lines start at
|
|
3734
|
+
* column 1, so the shift adds `offset.line - 1` lines and — only when the node
|
|
3735
|
+
* sits on the region's first line — `offset.columnShift`.
|
|
3704
3736
|
*/
|
|
3705
|
-
const columnOffsetOf = (source, offset) => Match.value(source.line === 1).pipe(Match.when(true, () => offset.
|
|
3737
|
+
const columnOffsetOf = (source, offset) => Match.value(source.line === 1).pipe(Match.when(true, () => offset.columnShift), Match.when(false, () => 0), Match.exhaustive);
|
|
3706
3738
|
const shiftedPosition = (source, offset) => ({
|
|
3707
3739
|
column: source.column + columnOffsetOf(source, offset),
|
|
3708
3740
|
line: source.line + offset.line - 1
|
|
@@ -3717,7 +3749,7 @@ const plannedMutant = (command, candidate, index) => Option.match(Option.fromNul
|
|
|
3717
3749
|
mutatorName: candidate.mutatorName
|
|
3718
3750
|
})),
|
|
3719
3751
|
onSome: (location) => Result.succeed({
|
|
3720
|
-
id: `${command.firstIndex + index}
|
|
3752
|
+
id: PlannedMutantSchema.fields.id.make(`${command.firstIndex + index}`),
|
|
3721
3753
|
mutatorName: candidate.mutatorName,
|
|
3722
3754
|
replacementCode: candidate.replacementCode,
|
|
3723
3755
|
location: shiftedLocation(location, command.offset),
|
|
@@ -3753,7 +3785,11 @@ const comparePositions = (a, b) => {
|
|
|
3753
3785
|
};
|
|
3754
3786
|
const locationIncluded = (haystack, needle) => comparePositions(haystack.start, needle.start) <= 0 && comparePositions(haystack.end, needle.end) >= 0;
|
|
3755
3787
|
const locationOverlaps = (a, b) => comparePositions(a.start, b.end) <= 0 && comparePositions(a.end, b.start) >= 0;
|
|
3756
|
-
const errorTextOf$1 = (cause) => Option.getOrElse(Option.map(
|
|
3788
|
+
const errorTextOf$1 = (cause) => Option.getOrElse(Option.map(errorTextOf$2(cause), (rendered) => rendered.text), () => "");
|
|
3789
|
+
const traversalFailure = (cause) => InstrumentError.make({
|
|
3790
|
+
message: cause instanceof Error ? cause.message : errorTextOf$1(cause),
|
|
3791
|
+
cause
|
|
3792
|
+
});
|
|
3757
3793
|
const DEFAULT_MUTATOR_REGISTRY = {
|
|
3758
3794
|
defaults: defaultMutators,
|
|
3759
3795
|
optIn: optInMutators
|
|
@@ -3776,28 +3812,20 @@ const locatedDirective = (comment, governedLine) => Option.flatMap(Option.fromNu
|
|
|
3776
3812
|
at: loc.start,
|
|
3777
3813
|
governedLine
|
|
3778
3814
|
})));
|
|
3779
|
-
const directivesOf = (node,
|
|
3815
|
+
const directivesOf = (node, governedLine) => attachedComments(node).flatMap((comment) => Option.toArray(locatedDirective(comment, governedLine)));
|
|
3780
3816
|
const foldInto = (rule, directive) => Match.value(foldRule(FoldRuleCommand.make({
|
|
3781
3817
|
rule,
|
|
3782
3818
|
directive
|
|
3783
3819
|
}))).pipe(Match.when(Result.isSuccess, (folded) => folded.success.rule), Match.orElse(() => rule));
|
|
3784
3820
|
const NO_COMMENTS = [];
|
|
3785
|
-
|
|
3786
|
-
if (!hasLeadingComments(node)) return NO_COMMENTS;
|
|
3787
|
-
return node.leadingComments;
|
|
3788
|
-
}
|
|
3821
|
+
const attachedComments = (node) => hasLeadingComments(node) ? node.leadingComments : NO_COMMENTS;
|
|
3789
3822
|
const hasLeadingComments = (value) => Predicate.hasProperty(value, "leadingComments") && Array.isArray(value.leadingComments);
|
|
3790
|
-
function
|
|
3791
|
-
const ancestors = [];
|
|
3792
|
-
for (let current = path.parentPath; current !== null; current = current.parentPath) ancestors.push(current.node);
|
|
3793
|
-
return ancestors;
|
|
3794
|
-
}
|
|
3795
|
-
function isTypeNode(path) {
|
|
3823
|
+
function isTypeNode(node) {
|
|
3796
3824
|
return [
|
|
3797
|
-
tsTypeAnnotationNodeTypes.includes(
|
|
3798
|
-
flowTypeAnnotationNodeTypes.includes(
|
|
3799
|
-
isDeclareVariableStatement(
|
|
3800
|
-
isDeclareModule(
|
|
3825
|
+
tsTypeAnnotationNodeTypes.includes(node.type),
|
|
3826
|
+
flowTypeAnnotationNodeTypes.includes(node.type),
|
|
3827
|
+
isDeclareVariableStatement(node),
|
|
3828
|
+
isDeclareModule(node)
|
|
3801
3829
|
].some((isType) => isType);
|
|
3802
3830
|
}
|
|
3803
3831
|
function isDeclareVariableStatement(node) {
|
|
@@ -3833,8 +3861,8 @@ const flowTypeAnnotationNodeTypes = Object.freeze([
|
|
|
3833
3861
|
"OpaqueType",
|
|
3834
3862
|
"TypeAlias"
|
|
3835
3863
|
]);
|
|
3836
|
-
function isImportDeclaration(
|
|
3837
|
-
return nodeType(
|
|
3864
|
+
function isImportDeclaration(node) {
|
|
3865
|
+
return nodeType(node) === "TSImportEqualsDeclaration" || node.type === "ImportDeclaration";
|
|
3838
3866
|
}
|
|
3839
3867
|
function mutantTestExpression(mutantId) {
|
|
3840
3868
|
return callExpression(identifier(IS_MUTANT_ACTIVE_HELPER), [stringLiteral(mutantId)]);
|
|
@@ -3842,41 +3870,33 @@ function mutantTestExpression(mutantId) {
|
|
|
3842
3870
|
function mutationCoverageSequenceExpression(mutants, targetExpression) {
|
|
3843
3871
|
const mutantIds = [...mutants].map((mutant) => stringLiteral(mutant.id));
|
|
3844
3872
|
const sequence = [callExpression(identifier(COVER_MUTANT_HELPER), mutantIds)];
|
|
3845
|
-
|
|
3846
|
-
return sequenceExpression(sequence);
|
|
3873
|
+
return sequenceExpression(targetExpression === void 0 ? sequence : [...sequence, targetExpression]);
|
|
3847
3874
|
}
|
|
3848
3875
|
const refusalPlacer = (refusal) => Match.value(refusal).pipe(Match.tag("MutantKindMismatch", (mismatch) => mismatch.placer), Match.tag("MutantsUnapplied", (unapplied) => unapplied.placer), Match.tag("NoPlacerClaimsNode", () => "no placer"), Match.tag("MutantNotApplied", () => "no placer"), Match.exhaustive);
|
|
3849
3876
|
const refusalDetail = (refusal) => Match.value(refusal).pipe(Match.tag("MutantKindMismatch", (mismatch) => `Expected ${mismatch.expected} for mutant ${mismatch.mutantId}`), Match.tag("MutantsUnapplied", (unapplied) => `Failed to apply ${unapplied.mutatorNames.join(", ")}`), Match.tag("NoPlacerClaimsNode", () => "No placer claims the node"), Match.tag("MutantNotApplied", (unapplied) => `Could not apply the ${unapplied.mutatorName} mutant`), Match.exhaustive);
|
|
3850
|
-
const
|
|
3877
|
+
const placementFailureMessage = (refusal, node, mutants, fileName, lineStarts, basePath) => {
|
|
3851
3878
|
const message = `${refusalPlacer(refusal)} could not place mutants with type(s): "${placementListFormat.format(mutants.map((mutant) => mutant.mutatorName))}"`;
|
|
3852
|
-
return
|
|
3879
|
+
return `${placementLocation(node, fileName, lineStarts, basePath)} ${message}. Either remove this file from the list of files to be mutated, or exclude the mutator (using \`mutator.excludedMutations\`). Original error: ${refusalDetail(refusal)}`;
|
|
3853
3880
|
};
|
|
3854
3881
|
function nodeOfKind(mutant, node, isKind, kind) {
|
|
3855
|
-
return
|
|
3856
|
-
}
|
|
3857
|
-
function narrowNode(node, isKind, message) {
|
|
3858
|
-
if (isKind(node)) return node;
|
|
3859
|
-
throw new Error(message);
|
|
3882
|
+
return isKind(node) ? Result.succeed(node) : Result.fail(/* @__PURE__ */ new Error(`Cannot place mutant ${mutant.id}: expected ${kind}, got ${node.type}`));
|
|
3860
3883
|
}
|
|
3861
3884
|
function expressionOf(node) {
|
|
3862
|
-
return
|
|
3885
|
+
return isExpressionKind(node) ? Result.succeed(node) : Result.fail(/* @__PURE__ */ new Error(`Expected an expression, got ${node.type}`));
|
|
3863
3886
|
}
|
|
3864
3887
|
function statementOf(node) {
|
|
3865
|
-
return
|
|
3888
|
+
return isStatementKind(node) ? Result.succeed(node) : Result.fail(/* @__PURE__ */ new Error(`Expected a statement, got ${node.type}`));
|
|
3866
3889
|
}
|
|
3867
3890
|
function isSwitchCase(node) {
|
|
3868
3891
|
return nodeType(node) === "SwitchCase";
|
|
3869
3892
|
}
|
|
3870
3893
|
function switchCaseOf(node) {
|
|
3871
|
-
return
|
|
3894
|
+
return isSwitchCase(node) ? Result.succeed(node) : Result.fail(/* @__PURE__ */ new Error(`Expected a switch case, got ${node.type}`));
|
|
3872
3895
|
}
|
|
3873
|
-
const fileNameWithin = (basePath, fileName) =>
|
|
3874
|
-
|
|
3875
|
-
return relativeTo(basePath, fileName);
|
|
3876
|
-
};
|
|
3877
|
-
function placementLocation(node, fileName, lineTable, basePath) {
|
|
3896
|
+
const fileNameWithin = (basePath, fileName) => basePath === void 0 ? fileName : relativeTo(basePath, fileName);
|
|
3897
|
+
function placementLocation(node, fileName, lineStarts, basePath) {
|
|
3878
3898
|
const relativeFile = fileNameWithin(basePath, fileName);
|
|
3879
|
-
const position = Option.map(Option.fromNullishOr(spanOf(node)), (span) =>
|
|
3899
|
+
const position = Option.map(Option.fromNullishOr(spanOf(node)), (span) => positionAt(lineStarts, span.start));
|
|
3880
3900
|
return Option.match(position, {
|
|
3881
3901
|
onNone: () => `${relativeFile}:undefined:undefined`,
|
|
3882
3902
|
onSome: (at) => `${relativeFile}:${at.line}:${at.column}`
|
|
@@ -3886,27 +3906,25 @@ const placementListFormat = new Intl.ListFormat("en");
|
|
|
3886
3906
|
const normalizeSeparators = (value) => value.replace(/\\/g, "/");
|
|
3887
3907
|
const withTrailingSlash = (basePath) => {
|
|
3888
3908
|
const normalized = normalizeSeparators(basePath);
|
|
3889
|
-
|
|
3890
|
-
return `${normalized}/`;
|
|
3909
|
+
return normalized.endsWith("/") ? normalized : `${normalized}/`;
|
|
3891
3910
|
};
|
|
3892
3911
|
const relativeTo = (basePath, fileName) => {
|
|
3893
3912
|
const prefix = withTrailingSlash(basePath);
|
|
3894
3913
|
const normalizedFile = normalizeSeparators(fileName);
|
|
3895
|
-
|
|
3896
|
-
return normalizedFile.slice(prefix.length);
|
|
3914
|
+
return normalizedFile.startsWith(prefix) ? normalizedFile.slice(prefix.length) : fileName;
|
|
3897
3915
|
};
|
|
3898
3916
|
function classOrFunctionExpressionNamedIfNeeded(path) {
|
|
3899
|
-
return
|
|
3917
|
+
return Option.flatMap(Option.filter(Option.some(path.node), isAnonymousFunctionOrClass), (node) => nameFromParent(path, node));
|
|
3900
3918
|
}
|
|
3901
3919
|
function nameFromParent(path, node) {
|
|
3902
3920
|
return Match.value(path.parentPath?.node).pipe(Match.when(isVariableDeclarator, (declarator) => adoptDeclaredName(node, declarator)), Match.when({
|
|
3903
3921
|
type: "Property",
|
|
3904
3922
|
key: { type: "Identifier" }
|
|
3905
|
-
}, () => namedPropertyValue(path, node)), Match.orElse(() =>
|
|
3923
|
+
}, () => namedPropertyValue(path, node)), Match.orElse(() => Option.none()));
|
|
3906
3924
|
}
|
|
3907
3925
|
/** A property value only survives by name when the declaration above it carries one. */
|
|
3908
3926
|
function namedPropertyValue(path, node) {
|
|
3909
|
-
return Match.value(path.getStatementParent()?.node.type).pipe(Match.when("VariableDeclaration", () => node), Match.orElse(() =>
|
|
3927
|
+
return Match.value(path.getStatementParent()?.node.type).pipe(Match.when("VariableDeclaration", () => Option.some(node)), Match.orElse(() => Option.none()));
|
|
3910
3928
|
}
|
|
3911
3929
|
function adoptIdentifier(node, identifier) {
|
|
3912
3930
|
node.id = identifier;
|
|
@@ -3919,14 +3937,11 @@ function isFunctionOrClassExpression(node) {
|
|
|
3919
3937
|
return node.type === "FunctionExpression" || node.type === "ClassExpression";
|
|
3920
3938
|
}
|
|
3921
3939
|
function arrowFunctionExpressionNamedIfNeeded(path) {
|
|
3922
|
-
return Match.value(path.node).pipe(Match.when({ type: "ArrowFunctionExpression" }, (node) => arrowNamedByDeclarator(node, path.parentPath)), Match.orElse(() =>
|
|
3940
|
+
return Match.value(path.node).pipe(Match.when({ type: "ArrowFunctionExpression" }, (node) => arrowNamedByDeclarator(node, path.parentPath)), Match.orElse(() => Option.none()));
|
|
3923
3941
|
}
|
|
3924
3942
|
/** An arrow bound to a named declaration is re-emitted as a named function. */
|
|
3925
3943
|
function arrowNamedByDeclarator(node, parentPath) {
|
|
3926
|
-
return Option.
|
|
3927
|
-
onNone: () => void 0,
|
|
3928
|
-
onSome: (identifier) => namedArrowExpression(node, identifier)
|
|
3929
|
-
});
|
|
3944
|
+
return Option.map(declaratorIdentifier(parentPath), (identifier) => namedArrowExpression(node, identifier));
|
|
3930
3945
|
}
|
|
3931
3946
|
function namedArrowExpression(node, identifier) {
|
|
3932
3947
|
const declaration = variableDeclaration("const", [variableDeclarator(identifier, node)]);
|
|
@@ -3939,10 +3954,7 @@ function isVariableDeclarator(node) {
|
|
|
3939
3954
|
return nodeType(node) === "VariableDeclarator";
|
|
3940
3955
|
}
|
|
3941
3956
|
function adoptDeclaredName(node, declarator) {
|
|
3942
|
-
return Option.
|
|
3943
|
-
onNone: () => void 0,
|
|
3944
|
-
onSome: (identifier) => adoptIdentifier(node, identifier)
|
|
3945
|
-
});
|
|
3957
|
+
return Option.map(declaredName(declarator), (identifier) => adoptIdentifier(node, identifier));
|
|
3946
3958
|
}
|
|
3947
3959
|
function declaredName(declarator) {
|
|
3948
3960
|
return Match.value(declarator.id).pipe(Match.when(isIdentifierReference, (identifier) => Option.some(identifier)), Match.orElse(() => Option.none()));
|
|
@@ -3951,10 +3963,10 @@ function isIdentifierReference(node) {
|
|
|
3951
3963
|
return nodeType(node) === "Identifier";
|
|
3952
3964
|
}
|
|
3953
3965
|
function nameIfAnonymous(path) {
|
|
3954
|
-
return classOrFunctionExpressionNamedIfNeeded(path)
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3966
|
+
return Option.match(Option.orElse(classOrFunctionExpressionNamedIfNeeded(path), () => arrowFunctionExpressionNamedIfNeeded(path)), {
|
|
3967
|
+
onNone: () => expressionOf(path.node),
|
|
3968
|
+
onSome: (named) => Result.succeed(named)
|
|
3969
|
+
});
|
|
3958
3970
|
}
|
|
3959
3971
|
function isChainLink(node) {
|
|
3960
3972
|
return [
|
|
@@ -3972,51 +3984,45 @@ function isCallExpressionNode(node) {
|
|
|
3972
3984
|
function isNonNullExpression(node) {
|
|
3973
3985
|
return nodeType(node) === "TSNonNullExpression";
|
|
3974
3986
|
}
|
|
3975
|
-
function isValidExpression(
|
|
3976
|
-
|
|
3977
|
-
return parent === null || !isUnmutatableContext(path, parent);
|
|
3987
|
+
function isValidExpression(node, parent) {
|
|
3988
|
+
return parent === null || !isUnmutatableContext(node, parent);
|
|
3978
3989
|
}
|
|
3979
|
-
function isUnmutatableContext(
|
|
3990
|
+
function isUnmutatableContext(node, parent) {
|
|
3980
3991
|
return [
|
|
3981
|
-
isObjectPropertyKey(
|
|
3982
|
-
isPartOfChain(
|
|
3992
|
+
isObjectPropertyKey(node, parent),
|
|
3993
|
+
isPartOfChain(node, parent),
|
|
3983
3994
|
isTaggedTemplateTag(parent),
|
|
3984
|
-
isDeletedOperand(
|
|
3985
|
-
isAssignedTarget(
|
|
3995
|
+
isDeletedOperand(node, parent),
|
|
3996
|
+
isAssignedTarget(node, parent)
|
|
3986
3997
|
].some((invalid) => invalid);
|
|
3987
3998
|
}
|
|
3988
|
-
function isObjectPropertyKey(
|
|
3989
|
-
|
|
3990
|
-
return parentNode.type === "Property" && parentNode.key === path.node;
|
|
3999
|
+
function isObjectPropertyKey(node, parent) {
|
|
4000
|
+
return parent.type === "Property" && parent.key === node;
|
|
3991
4001
|
}
|
|
3992
4002
|
function isTaggedTemplateTag(parent) {
|
|
3993
|
-
return parent.
|
|
4003
|
+
return parent.type === "TaggedTemplateExpression";
|
|
3994
4004
|
}
|
|
3995
|
-
function isDeletedOperand(
|
|
3996
|
-
|
|
3997
|
-
return parentNode.type === "UnaryExpression" && parentNode.operator === "delete";
|
|
4005
|
+
function isDeletedOperand(node, parent) {
|
|
4006
|
+
return parent.type === "UnaryExpression" && parent.operator === "delete";
|
|
3998
4007
|
}
|
|
3999
|
-
function isAssignedTarget(
|
|
4000
|
-
|
|
4001
|
-
return parentNode.type === "AssignmentExpression" && parentNode.left === path.node;
|
|
4008
|
+
function isAssignedTarget(node, parent) {
|
|
4009
|
+
return parent.type === "AssignmentExpression" && parent.left === node;
|
|
4002
4010
|
}
|
|
4003
|
-
function isPartOfChain(
|
|
4004
|
-
return isChainLink(
|
|
4011
|
+
function isPartOfChain(node, parent) {
|
|
4012
|
+
return isChainLink(node) && chainContinuesIn(node, parent);
|
|
4005
4013
|
}
|
|
4006
|
-
function chainContinuesIn(
|
|
4014
|
+
function chainContinuesIn(node, parent) {
|
|
4007
4015
|
return [
|
|
4008
|
-
isMemberAccessParent(
|
|
4009
|
-
isNonNullExpression(parent
|
|
4010
|
-
isCalleeParent(
|
|
4016
|
+
isMemberAccessParent(node, parent),
|
|
4017
|
+
isNonNullExpression(parent),
|
|
4018
|
+
isCalleeParent(node, parent)
|
|
4011
4019
|
].some((continues) => continues);
|
|
4012
4020
|
}
|
|
4013
|
-
function isMemberAccessParent(
|
|
4014
|
-
|
|
4015
|
-
return parentNode.type === "MemberExpression" && isNotACallOnTheNode(parentNode, path.node);
|
|
4021
|
+
function isMemberAccessParent(node, parent) {
|
|
4022
|
+
return parent.type === "MemberExpression" && isNotACallOnTheNode(parent, node);
|
|
4016
4023
|
}
|
|
4017
|
-
function isCalleeParent(
|
|
4018
|
-
|
|
4019
|
-
return parentNode.type === "CallExpression" && parentNode.callee === path.node;
|
|
4024
|
+
function isCalleeParent(node, parent) {
|
|
4025
|
+
return parent.type === "CallExpression" && parent.callee === node;
|
|
4020
4026
|
}
|
|
4021
4027
|
function isNotACallOnTheNode(member, node) {
|
|
4022
4028
|
return !(member.computed && member.property === node);
|
|
@@ -4033,27 +4039,31 @@ function isParenthesizedWrapper(value) {
|
|
|
4033
4039
|
const expressionMutantPlacer = {
|
|
4034
4040
|
name: "expression",
|
|
4035
4041
|
place(path, appliedMutants) {
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4042
|
+
return Result.gen(function* () {
|
|
4043
|
+
const base = yield* nameIfAnonymous(path);
|
|
4044
|
+
const sequenced = mutationCoverageSequenceExpression(appliedMutants.keys(), base);
|
|
4045
|
+
const expression = yield* [...appliedMutants].reduce((accumulated, [mutant, appliedMutant]) => Result.flatMap(accumulated, (current) => Result.map(nodeOfKind(mutant, unwrapParenthesizedExpression(appliedMutant), isExpressionKind, "an expression"), (replacement) => conditionalExpression(mutantTestExpression(mutant.id), replacement, current))), Result.succeed(sequenced));
|
|
4046
|
+
path.replaceWith(expression);
|
|
4047
|
+
});
|
|
4040
4048
|
}
|
|
4041
4049
|
};
|
|
4042
4050
|
const statementMutantPlacer = {
|
|
4043
4051
|
name: "statement",
|
|
4044
4052
|
place(path, appliedMutants) {
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4053
|
+
return Result.gen(function* () {
|
|
4054
|
+
const statements = yield* statementsOf(path);
|
|
4055
|
+
const body = [expressionStatement(mutationCoverageSequenceExpression(appliedMutants.keys())), ...statements];
|
|
4056
|
+
const statement = yield* [...appliedMutants].reduce((accumulated, entry) => Result.flatMap(accumulated, (current) => guardedStatement(current, entry)), Result.succeed(blockStatement(body)));
|
|
4057
|
+
path.replaceWith(wrappedStatement(path, statement));
|
|
4058
|
+
});
|
|
4048
4059
|
}
|
|
4049
4060
|
};
|
|
4050
|
-
|
|
4061
|
+
const statementsOf = (path) => {
|
|
4051
4062
|
const node = path.node;
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
}
|
|
4063
|
+
return node.type === "BlockStatement" ? Result.succeed(node.body) : Result.map(statementOf(node), (statement) => [statement]);
|
|
4064
|
+
};
|
|
4055
4065
|
function guardedStatement(statement, entry) {
|
|
4056
|
-
return
|
|
4066
|
+
return Result.map(nodeOfKind(entry[0], entry[1], isStatementKind, "a statement"), (narrowed) => ifStatement(mutantTestExpression(entry[0].id), blockStatement([narrowed]), statement));
|
|
4057
4067
|
}
|
|
4058
4068
|
function wrappedStatement(path, statement) {
|
|
4059
4069
|
return Match.value(nodeType(path.node)).pipe(Match.when("BlockStatement", () => blockStatement([statement])), Match.orElse(() => statement));
|
|
@@ -4064,13 +4074,11 @@ const placerBuilders = Object.freeze({
|
|
|
4064
4074
|
"switch-case": {
|
|
4065
4075
|
name: "switch-case",
|
|
4066
4076
|
place(path, appliedMutants) {
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
}
|
|
4073
|
-
path.replaceWith(switchCase(currentCase.test, [consequence]));
|
|
4077
|
+
return Result.gen(function* () {
|
|
4078
|
+
const currentCase = yield* switchCaseOf(path.node);
|
|
4079
|
+
const consequence = yield* [...appliedMutants].reduce((accumulated, [mutant, appliedMutant]) => Result.flatMap(accumulated, (current) => Result.map(nodeOfKind(mutant, appliedMutant, isSwitchCase, "a switch case"), (appliedCase) => ifStatement(mutantTestExpression(mutant.id), blockStatement(appliedCase.consequent), current))), Result.succeed(blockStatement([expressionStatement(mutationCoverageSequenceExpression(appliedMutants.keys())), ...currentCase.consequent])));
|
|
4080
|
+
path.replaceWith(switchCase(currentCase.test, [consequence]));
|
|
4081
|
+
});
|
|
4074
4082
|
}
|
|
4075
4083
|
}
|
|
4076
4084
|
});
|
|
@@ -4086,231 +4094,281 @@ const transformDataFirst = (ast, mutantCollector, transformerContext) => {
|
|
|
4086
4094
|
}))));
|
|
4087
4095
|
};
|
|
4088
4096
|
const transform = dual((args) => args.length >= 3, transformDataFirst);
|
|
4089
|
-
const emptyAppliedMutants = () => /* @__PURE__ */ new Map();
|
|
4090
|
-
const traversalFailure = (cause) => InstrumentError.make({
|
|
4091
|
-
message: cause instanceof Error ? cause.message : errorTextOf$1(cause),
|
|
4092
|
-
cause
|
|
4093
|
-
});
|
|
4094
4097
|
function isMutateRangeList(value) {
|
|
4095
4098
|
return Array.isArray(value);
|
|
4096
4099
|
}
|
|
4097
|
-
const
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
isSwitchCase: nodeType(path.node) === "SwitchCase",
|
|
4137
|
-
expressionIsValid: isValidExpression(path)
|
|
4138
|
-
};
|
|
4139
|
-
}
|
|
4140
|
-
function placerNameOf(site) {
|
|
4141
|
-
return Match.value(site).pipe(Match.tag("ExpressionSite", () => "expression"), Match.tag("StatementSite", () => "statement"), Match.tag("SwitchCaseSite", () => "switch-case"), Match.exhaustive);
|
|
4142
|
-
}
|
|
4143
|
-
function addToPlacementMapIfPossible(path) {
|
|
4144
|
-
const facts = placementFacts(path);
|
|
4145
|
-
const claimed = placeMutants(PlaceMutantsCommand.make({
|
|
4146
|
-
fileName: originFileName,
|
|
4147
|
-
facts,
|
|
4148
|
-
mutants: []
|
|
4149
|
-
}));
|
|
4150
|
-
Match.value(claimed).pipe(Match.when(Result.isSuccess, () => placementMap.set(path.node, {
|
|
4151
|
-
appliedMutants: emptyAppliedMutants(),
|
|
4152
|
-
facts
|
|
4153
|
-
})), Match.orElse(() => void 0));
|
|
4154
|
-
}
|
|
4155
|
-
function hasAppliedMutants(placement) {
|
|
4156
|
-
return placement !== void 0 && placement.appliedMutants.size > 0;
|
|
4157
|
-
}
|
|
4158
|
-
function replacementRecord(mutant, applied) {
|
|
4159
|
-
const replacement = unwrapParenthesizedExpression(applied);
|
|
4160
|
-
return {
|
|
4161
|
-
id: mutant.id,
|
|
4162
|
-
mutatorName: mutant.mutatorName,
|
|
4163
|
-
replacement: {
|
|
4164
|
-
isExpression: isExpressionKind(replacement),
|
|
4165
|
-
isStatement: isStatementKind(replacement),
|
|
4166
|
-
isSwitchCase: nodeType(replacement) === "SwitchCase"
|
|
4167
|
-
}
|
|
4168
|
-
};
|
|
4100
|
+
const MUTATION_OFFSET = {
|
|
4101
|
+
line: 1,
|
|
4102
|
+
columnShift: 0
|
|
4103
|
+
};
|
|
4104
|
+
const initialFoldState = (firstIndex) => ({
|
|
4105
|
+
directiveRule: [],
|
|
4106
|
+
nextIndex: firstIndex,
|
|
4107
|
+
mutants: [],
|
|
4108
|
+
warnings: [],
|
|
4109
|
+
claims: [],
|
|
4110
|
+
placements: [],
|
|
4111
|
+
hasLiveMutants: false
|
|
4112
|
+
});
|
|
4113
|
+
const framesUpward = (frame) => [frame, ...framesAbove(frame)];
|
|
4114
|
+
const framesAbove = (frame) => Option.match(Option.fromNullishOr(frame.parent), {
|
|
4115
|
+
onNone: () => [],
|
|
4116
|
+
onSome: (parent) => framesUpward(parent)
|
|
4117
|
+
});
|
|
4118
|
+
const parentNodeOf = (frame) => Option.getOrNull(Option.map(Option.fromNullishOr(frame.parent), (parent) => parent.node));
|
|
4119
|
+
const ancestorsOfFrame = (frame) => Option.match(Option.fromNullishOr(frame.parent), {
|
|
4120
|
+
onNone: () => [],
|
|
4121
|
+
onSome: (parent) => [parent.node, ...ancestorsOfFrame(parent)]
|
|
4122
|
+
});
|
|
4123
|
+
const placementFactsOf = (frame) => ({
|
|
4124
|
+
isExpression: isExpressionKind(frame.node),
|
|
4125
|
+
isStatement: isStatementKind(frame.node),
|
|
4126
|
+
isSwitchCase: nodeType(frame.node) === "SwitchCase",
|
|
4127
|
+
expressionIsValid: isValidExpression(frame.node, parentNodeOf(frame))
|
|
4128
|
+
});
|
|
4129
|
+
const placerNameOf = (site) => Match.value(site).pipe(Match.tag("ExpressionSite", () => "expression"), Match.tag("StatementSite", () => "statement"), Match.tag("SwitchCaseSite", () => "switch-case"), Match.exhaustive);
|
|
4130
|
+
const replacementRecord = (mutant, applied) => {
|
|
4131
|
+
const replacement = unwrapParenthesizedExpression(applied);
|
|
4132
|
+
return {
|
|
4133
|
+
id: mutant.id,
|
|
4134
|
+
mutatorName: mutant.mutatorName,
|
|
4135
|
+
replacement: {
|
|
4136
|
+
isExpression: isExpressionKind(replacement),
|
|
4137
|
+
isStatement: isStatementKind(replacement),
|
|
4138
|
+
isSwitchCase: nodeType(replacement) === "SwitchCase"
|
|
4169
4139
|
}
|
|
4170
|
-
|
|
4171
|
-
|
|
4140
|
+
};
|
|
4141
|
+
};
|
|
4142
|
+
const plannedWithNodes = (candidates, planned, fileName) => candidates.flatMap((candidate, index) => Option.match(Option.fromNullishOr(planned[index]), {
|
|
4143
|
+
onNone: () => [],
|
|
4144
|
+
onSome: (mutant) => [createMutant(mutant, fileName, candidate.node, candidate.replacement)]
|
|
4145
|
+
}));
|
|
4146
|
+
const ignorersReasonFor = (node, ancestors, ignorers) => ignorers.reduce((reason, ignorer) => Option.orElse(reason, () => Option.fromNullishOr(ignorer.shouldIgnore(node, ancestors))), Option.none());
|
|
4147
|
+
const mutablesFor = (frame, location, context) => {
|
|
4148
|
+
const ancestors = ancestorsOfFrame(frame);
|
|
4149
|
+
const mutatorContext = toMutatorContext(ancestors);
|
|
4150
|
+
const replacements = context.mutatorEntries.flatMap(([mutatorName, mutate]) => [...mutate(frame.node, mutatorContext)].map((replacement) => ({
|
|
4151
|
+
mutatorName,
|
|
4152
|
+
replacement
|
|
4153
|
+
})));
|
|
4154
|
+
const ignorerReason = replacements.length === 0 ? void 0 : Option.getOrUndefined(ignorersReasonFor(frame.node, ancestors, context.ignorers));
|
|
4155
|
+
return replacements.map(({ mutatorName, replacement }) => ({
|
|
4156
|
+
node: frame.node,
|
|
4157
|
+
replacement,
|
|
4158
|
+
data: {
|
|
4159
|
+
mutatorName,
|
|
4160
|
+
replacementCode: printNode(replacement),
|
|
4161
|
+
location,
|
|
4162
|
+
ignorerReason
|
|
4172
4163
|
}
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4164
|
+
}));
|
|
4165
|
+
};
|
|
4166
|
+
const mutateRangesOf = (mutateDescription) => Option.filter(Option.some(mutateDescription), isMutateRangeList);
|
|
4167
|
+
const isOutsideMutateRanges = (location, mutateDescription) => Option.exists(mutateRangesOf(mutateDescription), (ranges) => ranges.every((range) => !locationOverlaps(range, location)));
|
|
4168
|
+
const isInsideMutateRanges = (location, mutateDescription) => Option.exists(mutateRangesOf(mutateDescription), (ranges) => ranges.some((range) => locationIncluded(range, location)));
|
|
4169
|
+
const shouldMutateAt = (location, mutateDescription) => mutateDescription === true || isInsideMutateRanges(location, mutateDescription);
|
|
4170
|
+
const shouldSkipNode = (frame, location, mutateDescription) => [
|
|
4171
|
+
isTypeNode(frame.node),
|
|
4172
|
+
isImportDeclaration(frame.node),
|
|
4173
|
+
nodeType(frame.node) === "Decorator",
|
|
4174
|
+
mutateDescription === false,
|
|
4175
|
+
isOutsideMutateRanges(location, mutateDescription)
|
|
4176
|
+
].some((skip) => skip);
|
|
4177
|
+
const locationOfNode = (frame, context) => Option.match(Option.map(Option.fromNullishOr(spanOf(frame.node)), (span) => locationOf(context.lineStarts, span)), {
|
|
4178
|
+
onNone: () => Result.fail(NodeWithoutSpan.make({ fileName: context.fileName })),
|
|
4179
|
+
onSome: Result.succeed
|
|
4180
|
+
});
|
|
4181
|
+
const refusedPlacement = (refusal, node, mutants, context) => PlacementRefused.make({ message: placementFailureMessage(refusal, node, mutants, context.fileName, context.lineStarts, context.basePath) });
|
|
4182
|
+
const applyMutantToClaim = (mutant, target, appliedSoFar, context) => Result.mapError(applyMutant(mutant, target), (failure) => refusedPlacement(failure, target, appliedSoFar, context));
|
|
4183
|
+
const applyToClaim = (mutants, target, state, context) => {
|
|
4184
|
+
const existing = state.claims.find((claim) => claim.node === target.node);
|
|
4185
|
+
const priorMutants = existing === void 0 ? [] : existing.applied.map(([mutant]) => mutant);
|
|
4186
|
+
const applied = mutants.reduce((accumulated, mutant) => Result.flatMap(accumulated, (entries) => Result.map(applyMutantToClaim(mutant, target.node, [...priorMutants, ...entries.map(([entry]) => entry)], context), (tree) => [...entries, [mutant, tree]])), Result.succeed([]));
|
|
4187
|
+
return Result.map(applied, (entries) => ({
|
|
4188
|
+
...state,
|
|
4189
|
+
claims: state.claims.map((claim) => claim.node === target.node ? {
|
|
4190
|
+
...claim,
|
|
4191
|
+
applied: [...claim.applied, ...entries]
|
|
4192
|
+
} : claim)
|
|
4193
|
+
}));
|
|
4194
|
+
};
|
|
4195
|
+
const attachPlaceable = (mutants, frame, state, context) => Match.value(mutants.length > 0).pipe(Match.when(false, () => Result.succeed(state)), Match.when(true, () => Option.match(Option.fromNullishOr(framesUpward(frame).find((candidate) => state.claims.some((claim) => claim.node === candidate.node))), {
|
|
4196
|
+
onNone: () => Result.fail(MutantsUnplaced.make({
|
|
4197
|
+
fileName: context.fileName,
|
|
4198
|
+
detail: JSON.stringify(mutants, null, 2)
|
|
4199
|
+
})),
|
|
4200
|
+
onSome: (target) => applyToClaim(mutants, target, state, context)
|
|
4201
|
+
})), Match.exhaustive);
|
|
4202
|
+
const collectPlan = (frame, candidates, plan, state, context) => {
|
|
4203
|
+
const collected = plannedWithNodes(candidates, plan.mutants, context.fileName);
|
|
4204
|
+
const nextState = {
|
|
4205
|
+
...state,
|
|
4206
|
+
mutants: [...state.mutants, ...collected],
|
|
4207
|
+
warnings: [...state.warnings, ...plan.warnings],
|
|
4208
|
+
nextIndex: plan.nextIndex
|
|
4209
|
+
};
|
|
4210
|
+
return Match.value(plan).pipe(Match.tag("MutantsPlanned", (planned) => attachPlaceable(plannedWithNodes(candidates, planned.placeable, context.fileName), frame, {
|
|
4211
|
+
...nextState,
|
|
4212
|
+
hasLiveMutants: true
|
|
4213
|
+
}, context)), Match.orElse(() => Result.succeed(nextState)));
|
|
4214
|
+
};
|
|
4215
|
+
const planMutantsAt = (frame, candidates, directives, location, state, context) => {
|
|
4216
|
+
const plan = planMutants(PlanMutantsCommand.make({
|
|
4217
|
+
fileName: context.fileName,
|
|
4218
|
+
firstIndex: state.nextIndex,
|
|
4219
|
+
offset: context.offset,
|
|
4220
|
+
line: location.start.line,
|
|
4221
|
+
mutatorNames: [...context.allMutatorNames],
|
|
4222
|
+
excludedMutations: [...context.excludedMutations],
|
|
4223
|
+
rule: [...state.directiveRule],
|
|
4224
|
+
directives: [...directives],
|
|
4225
|
+
candidates: candidates.map((candidate) => candidate.data)
|
|
4226
|
+
}));
|
|
4227
|
+
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
|
+
};
|
|
4229
|
+
const candidatesFor = (frame, location, context) => Match.value(shouldMutateAt(location, context.mutateDescription)).pipe(Match.when(true, () => mutablesFor(frame, location, context)), Match.orElse(() => []));
|
|
4230
|
+
const needsPlan = (candidates, directives) => [candidates.length > 0, directives.length > 0].some(Boolean);
|
|
4231
|
+
const planAtNode = (frame, directives, location, state, context) => {
|
|
4232
|
+
const candidates = candidatesFor(frame, location, context);
|
|
4233
|
+
return Match.value(needsPlan(candidates, directives)).pipe(Match.when(false, () => Result.succeed(state)), Match.when(true, () => planMutantsAt(frame, candidates, directives, location, state, context)), Match.exhaustive);
|
|
4234
|
+
};
|
|
4235
|
+
const claimSelf = (frame, facts, claims, context) => {
|
|
4236
|
+
const claimed = placeMutants(PlaceMutantsCommand.make({
|
|
4237
|
+
fileName: context.fileName,
|
|
4238
|
+
facts,
|
|
4239
|
+
mutants: []
|
|
4240
|
+
}));
|
|
4241
|
+
return Match.value(claimed.pipe(Result.isSuccess)).pipe(Match.when(true, () => [...claims, {
|
|
4242
|
+
node: frame.node,
|
|
4243
|
+
facts,
|
|
4244
|
+
applied: []
|
|
4245
|
+
}]), Match.orElse(() => claims));
|
|
4246
|
+
};
|
|
4247
|
+
const siteOfClaim = (claim, context) => {
|
|
4248
|
+
const mutants = claim.applied.map(([mutant]) => mutant);
|
|
4249
|
+
const decision = placeMutants(PlaceMutantsCommand.make({
|
|
4250
|
+
fileName: context.fileName,
|
|
4251
|
+
facts: claim.facts,
|
|
4252
|
+
mutants: claim.applied.map(([mutant, applied]) => replacementRecord(mutant, applied))
|
|
4253
|
+
}));
|
|
4254
|
+
return Match.value(decision).pipe(Match.when(Result.isFailure, (refused) => Result.fail(refusedPlacement(refused.failure, claim.node, mutants, context))), Match.orElse((decided) => Result.succeed(decided.success)));
|
|
4255
|
+
};
|
|
4256
|
+
const emitClaim = (claim, state, context) => Match.value(claim.applied.length > 0).pipe(Match.when(false, () => Result.succeed(state)), Match.when(true, () => Result.map(siteOfClaim(claim, context), (site) => ({
|
|
4257
|
+
...state,
|
|
4258
|
+
placements: [...state.placements, {
|
|
4259
|
+
node: claim.node,
|
|
4260
|
+
placer: placerNameOf(site),
|
|
4261
|
+
applied: claim.applied
|
|
4262
|
+
}]
|
|
4263
|
+
}))), Match.exhaustive);
|
|
4264
|
+
const emitPlacement = (frame, state, context) => Option.match(Option.fromNullishOr(state.claims.find((claim) => claim.node === frame.node)), {
|
|
4265
|
+
onNone: () => Result.succeed(state),
|
|
4266
|
+
onSome: (claim) => emitClaim(claim, state, context)
|
|
4267
|
+
});
|
|
4268
|
+
const foldChildren = (frame, state, context) => childNodes(frame.node).reduce((accumulated, child) => Result.flatMap(accumulated, (current) => foldPlacements({
|
|
4269
|
+
node: child.node,
|
|
4270
|
+
parent: frame
|
|
4271
|
+
}, current, context)), Result.succeed(state));
|
|
4272
|
+
const visitFrame = (frame, directives, location, state, context) => {
|
|
4273
|
+
const facts = placementFactsOf(frame);
|
|
4274
|
+
const claimed = claimSelf(frame, facts, state.claims, context);
|
|
4275
|
+
return Result.flatMap(planAtNode(frame, directives, location, {
|
|
4276
|
+
...state,
|
|
4277
|
+
claims: claimed
|
|
4278
|
+
}, context), (afterPlan) => Result.flatMap(foldChildren(frame, afterPlan, context), (afterChildren) => emitPlacement(frame, afterChildren, context)));
|
|
4279
|
+
};
|
|
4280
|
+
const foldPlacements = (frame, state, context) => Result.flatMap(locationOfNode(frame, context), (location) => {
|
|
4281
|
+
const directives = directivesOf(frame.node, location.start.line);
|
|
4282
|
+
const ruled = {
|
|
4283
|
+
...state,
|
|
4284
|
+
directiveRule: directives.reduce(foldInto, state.directiveRule)
|
|
4285
|
+
};
|
|
4286
|
+
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
|
+
});
|
|
4288
|
+
const planInstrumentation = (root, firstIndex, context) => Result.map(foldPlacements({
|
|
4289
|
+
node: root,
|
|
4290
|
+
parent: null
|
|
4291
|
+
}, initialFoldState(firstIndex), context), (state) => ({
|
|
4292
|
+
mutants: state.mutants,
|
|
4293
|
+
placements: state.placements,
|
|
4294
|
+
warnings: state.warnings,
|
|
4295
|
+
hasLiveMutants: state.hasLiveMutants
|
|
4296
|
+
}));
|
|
4297
|
+
const applyOnePlacement = (placement, path, previous, context) => {
|
|
4298
|
+
const result = placerBuilders[placement.placer].place(path, new Map(placement.applied));
|
|
4299
|
+
return Result.match(result, {
|
|
4300
|
+
onFailure: (cause) => new Error(placementFailureMessage(MutantsUnapplied.make({
|
|
4301
|
+
fileName: context.fileName,
|
|
4302
|
+
placer: placement.placer,
|
|
4303
|
+
mutatorNames: placement.applied.map(([mutant]) => mutant.mutatorName),
|
|
4304
|
+
cause: toError(cause)
|
|
4305
|
+
}), placement.node, placement.applied.map(([mutant]) => mutant), context.fileName, context.lineStarts, context.basePath)),
|
|
4306
|
+
onSuccess: () => previous
|
|
4307
|
+
});
|
|
4308
|
+
};
|
|
4309
|
+
const applyPlacementsToAst = (root, places, context) => {
|
|
4310
|
+
let failure;
|
|
4311
|
+
traverse(make(root), { exit(path) {
|
|
4312
|
+
Option.match(Option.fromNullishOr(places.get(path.node)), {
|
|
4313
|
+
onNone: () => void 0,
|
|
4314
|
+
onSome: (placement) => {
|
|
4315
|
+
failure = applyOnePlacement(placement, path, failure, context);
|
|
4184
4316
|
}
|
|
4185
|
-
}
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
}
|
|
4198
|
-
function candidateSteps(path) {
|
|
4199
|
-
return Match.value(shouldMutate(path)).pipe(Match.when(true, () => mutablesFor(path)), Match.when(false, () => []), Match.exhaustive);
|
|
4200
|
-
}
|
|
4201
|
-
function needsPlan(candidates, directives) {
|
|
4202
|
-
return [candidates.length > 0, directives.length > 0].some(Boolean);
|
|
4203
|
-
}
|
|
4204
|
-
function planAndPlace(path, candidates, directives) {
|
|
4205
|
-
Match.value(planFor(path, candidates, directives)).pipe(Match.when(Option.isSome, (toPlace) => placeOnPath(path, toPlace.value)), Match.orElse(() => void 0));
|
|
4206
|
-
}
|
|
4207
|
-
function planFor(path, candidates, directives) {
|
|
4208
|
-
const plan = planMutants(PlanMutantsCommand.make({
|
|
4209
|
-
fileName: originFileName,
|
|
4210
|
-
firstIndex: mutantCollector.nextIndex,
|
|
4211
|
-
offset: offset ?? {
|
|
4212
|
-
line: 1,
|
|
4213
|
-
column: 0
|
|
4214
|
-
},
|
|
4215
|
-
line: getNodeLocation(path.node).start.line,
|
|
4216
|
-
mutatorNames: allMutatorNames,
|
|
4217
|
-
excludedMutations: options.excludedMutations,
|
|
4218
|
-
rule: directiveRule,
|
|
4219
|
-
directives: [...directives],
|
|
4220
|
-
candidates: candidates.map((candidate) => candidate.data)
|
|
4221
|
-
}));
|
|
4222
|
-
return Match.value(plan).pipe(Match.when(Result.isFailure, (failed) => raisePlanFailure(failed.failure)), Match.orElse((succeeded) => collectPlanned(candidates, succeeded.success)));
|
|
4223
|
-
}
|
|
4224
|
-
function raisePlanFailure(failure) {
|
|
4225
|
-
throw new Error(`Mutant without a source location: ${failure.mutatorName} in ${failure.fileName}`);
|
|
4226
|
-
}
|
|
4227
|
-
function collectPlanned(candidates, plan) {
|
|
4228
|
-
mutantCollector.append(plannedWithNodes(candidates, plan.mutants));
|
|
4229
|
-
warnings.push(...plan.warnings);
|
|
4230
|
-
return Match.value(plan).pipe(Match.tag("MutantsPlanned", (planned) => {
|
|
4231
|
-
hasLiveMutants = true;
|
|
4232
|
-
return Option.some(plannedWithNodes(candidates, planned.placeable));
|
|
4233
|
-
}), Match.orElse(() => Option.none()));
|
|
4234
|
-
}
|
|
4235
|
-
function plannedWithNodes(candidates, planned) {
|
|
4236
|
-
return candidates.flatMap((candidate, index) => Option.match(Option.fromNullishOr(planned[index]), {
|
|
4237
|
-
onNone: () => [],
|
|
4238
|
-
onSome: (mutant) => [createMutant(mutant, originFileName, candidate.node, candidate.replacement)]
|
|
4239
|
-
}));
|
|
4240
|
-
}
|
|
4241
|
-
function placeOnPath(path, mutantsToPlace) {
|
|
4242
|
-
const placementPath = requiredPlacementPath(path, mutantsToPlace);
|
|
4243
|
-
const placement = requiredPlacement(placementPath.node);
|
|
4244
|
-
mutantsToPlace.forEach((mutant) => {
|
|
4245
|
-
Match.value(applyMutant(mutant, placementPath.node)).pipe(Match.when(Result.isFailure, (failed) => raisePlacementRefusal(failed.failure, placement, path)), Match.orElse((applied) => placement.appliedMutants.set(mutant, applied.success)));
|
|
4246
|
-
});
|
|
4247
|
-
}
|
|
4248
|
-
function requiredPlacementPath(path, mutantsToPlace) {
|
|
4249
|
-
return Option.getOrThrowWith(Option.fromNullishOr(path.find((ancestor) => placementMap.has(ancestor.node))), () => unplacedMutantsError(mutantsToPlace));
|
|
4250
|
-
}
|
|
4251
|
-
function unplacedMutantsError(mutantsToPlace) {
|
|
4252
|
-
return /* @__PURE__ */ new Error(`Mutants cannot be placed. This shouldn't happen! Unplaced mutants: ${JSON.stringify(mutantsToPlace, null, 2)}`);
|
|
4253
|
-
}
|
|
4254
|
-
function requiredPlacement(node) {
|
|
4255
|
-
return Option.getOrThrowWith(Option.fromNullishOr(placementMap.get(node)), () => /* @__PURE__ */ new Error("Placement not found for node"));
|
|
4256
|
-
}
|
|
4257
|
-
function shouldSkip(path) {
|
|
4258
|
-
return [
|
|
4259
|
-
isTypeNode(path),
|
|
4260
|
-
isImportDeclaration(path),
|
|
4261
|
-
nodeType(path.node) === "Decorator",
|
|
4262
|
-
mutateDescription === false,
|
|
4263
|
-
isOutsideMutateRanges(path)
|
|
4264
|
-
].some((skip) => skip);
|
|
4265
|
-
}
|
|
4266
|
-
function mutateRanges() {
|
|
4267
|
-
return Option.filter(Option.some(mutateDescription), isMutateRangeList);
|
|
4268
|
-
}
|
|
4269
|
-
function isOutsideMutateRanges(path) {
|
|
4270
|
-
return Option.exists(mutateRanges(), (ranges) => ranges.every((range) => !locationOverlaps(range, getNodeLocation(path.node))));
|
|
4271
|
-
}
|
|
4272
|
-
function shouldMutate(path) {
|
|
4273
|
-
return mutateDescription === true || isInsideMutateRanges(path);
|
|
4274
|
-
}
|
|
4275
|
-
function isInsideMutateRanges(path) {
|
|
4276
|
-
return Option.exists(mutateRanges(), (ranges) => ranges.some((range) => locationIncluded(range, getNodeLocation(path.node))));
|
|
4277
|
-
}
|
|
4278
|
-
function getNodeLocation(node) {
|
|
4279
|
-
const span = spanOf(node);
|
|
4280
|
-
if (span === void 0) throw new Error("Node without a span");
|
|
4281
|
-
return {
|
|
4282
|
-
start: lineTable.positionAt(span.start),
|
|
4283
|
-
end: lineTable.positionAt(span.end)
|
|
4284
|
-
};
|
|
4285
|
-
}
|
|
4286
|
-
function mutablesFor(path) {
|
|
4287
|
-
const ancestors = ancestorsOf(path);
|
|
4288
|
-
const context = toMutatorContext(ancestors);
|
|
4289
|
-
const location = Option.map(Option.fromNullishOr(spanOf(path.node)), (span) => ({
|
|
4290
|
-
start: lineTable.positionAt(span.start),
|
|
4291
|
-
end: lineTable.positionAt(span.end)
|
|
4292
|
-
}));
|
|
4293
|
-
const replacements = mutatorEntries.flatMap(([mutatorName, mutate]) => [...mutate(path.node, context)].map((replacement) => ({
|
|
4294
|
-
mutatorName,
|
|
4295
|
-
replacement
|
|
4296
|
-
})));
|
|
4297
|
-
const ignorerReason = replacements.length === 0 ? void 0 : Option.getOrUndefined(ignorersReason(path.node, ancestors));
|
|
4298
|
-
return replacements.map(({ mutatorName, replacement }) => ({
|
|
4299
|
-
node: path.node,
|
|
4300
|
-
replacement,
|
|
4301
|
-
data: {
|
|
4302
|
-
mutatorName,
|
|
4303
|
-
replacementCode: printNode(replacement),
|
|
4304
|
-
location: Option.getOrUndefined(location),
|
|
4305
|
-
ignorerReason
|
|
4306
|
-
}
|
|
4307
|
-
}));
|
|
4308
|
-
}
|
|
4309
|
-
function ignorersReason(node, ancestors) {
|
|
4310
|
-
return options.ignorers.reduce((reason, ignorer) => Option.orElse(reason, () => Option.fromNullishOr(ignorer.shouldIgnore(node, ancestors))), Option.none());
|
|
4311
|
-
}
|
|
4317
|
+
});
|
|
4318
|
+
} });
|
|
4319
|
+
return failure;
|
|
4320
|
+
};
|
|
4321
|
+
const applyPlan = (root, plan, context) => {
|
|
4322
|
+
const places = new Map(plan.placements.map((placement) => [placement.node, placement]));
|
|
4323
|
+
return Option.match(Option.fromNullishOr(applyPlacementsToAst(root, places, context)), {
|
|
4324
|
+
onNone: () => Effect.void,
|
|
4325
|
+
onSome: (error) => Effect.fail(InstrumentError.make({
|
|
4326
|
+
message: error.message,
|
|
4327
|
+
cause: error
|
|
4328
|
+
}))
|
|
4312
4329
|
});
|
|
4313
4330
|
};
|
|
4331
|
+
const refusalError = (refusal) => Match.value(refusal).pipe(Match.tag("NodeWithoutSpan", () => InstrumentError.make({
|
|
4332
|
+
message: "Node without a span",
|
|
4333
|
+
cause: void 0
|
|
4334
|
+
})), Match.tag("MutantsUnplaced", (unplaced) => InstrumentError.make({
|
|
4335
|
+
message: `Mutants cannot be placed. This shouldn't happen! Unplaced mutants: ${unplaced.detail}`,
|
|
4336
|
+
cause: void 0
|
|
4337
|
+
})), Match.tag("PlacementRefused", (refused) => InstrumentError.make({
|
|
4338
|
+
message: refused.message,
|
|
4339
|
+
cause: new Error(refused.message)
|
|
4340
|
+
})), Match.tag("MutantWithoutLocation", (failed) => InstrumentError.make({
|
|
4341
|
+
message: `Mutant without a source location: ${failed.mutatorName} in ${failed.fileName}`,
|
|
4342
|
+
cause: void 0
|
|
4343
|
+
})), Match.exhaustive);
|
|
4344
|
+
const transformScriptDataFirst = Effect.fn("stryker.instrument.transform.script")(function* ({ root, originFileName, rawContent, offset, comments }, mutantCollector, { options, mutateDescription, basePath }) {
|
|
4345
|
+
const lineStarts = lineStartsOf(rawContent);
|
|
4346
|
+
attachComments(make(root), comments, lineStarts);
|
|
4347
|
+
const selection = selectMutators(DEFAULT_MUTATOR_REGISTRY, options.optInMutations);
|
|
4348
|
+
const context = {
|
|
4349
|
+
fileName: originFileName,
|
|
4350
|
+
lineStarts,
|
|
4351
|
+
mutateDescription,
|
|
4352
|
+
offset: offset ?? MUTATION_OFFSET,
|
|
4353
|
+
basePath,
|
|
4354
|
+
mutatorEntries: selection.active,
|
|
4355
|
+
allMutatorNames: selection.known.map((name) => name.toLowerCase()),
|
|
4356
|
+
excludedMutations: options.excludedMutations,
|
|
4357
|
+
ignorers: options.ignorers
|
|
4358
|
+
};
|
|
4359
|
+
const planned = yield* Effect.try({
|
|
4360
|
+
try: () => planInstrumentation(root, mutantCollector.nextIndex, context),
|
|
4361
|
+
catch: traversalFailure
|
|
4362
|
+
});
|
|
4363
|
+
const plan = yield* Match.value(planned).pipe(Match.when(Result.isFailure, (refused) => {
|
|
4364
|
+
const error = refusalError(refused.failure);
|
|
4365
|
+
return Effect.fail(error);
|
|
4366
|
+
}), Match.orElse((succeeded) => Effect.succeed(succeeded.success)));
|
|
4367
|
+
mutantCollector.append(plan.mutants);
|
|
4368
|
+
yield* applyPlan(root, plan, context);
|
|
4369
|
+
yield* placeHeaderIfNeeded(plan.hasLiveMutants, options, root);
|
|
4370
|
+
return plan.warnings;
|
|
4371
|
+
});
|
|
4314
4372
|
const transformScript = dual((args) => args.length >= 3, transformScriptDataFirst);
|
|
4315
4373
|
function toMutatorContext(ancestors) {
|
|
4316
4374
|
return {
|
|
@@ -4319,10 +4377,7 @@ function toMutatorContext(ancestors) {
|
|
|
4319
4377
|
ancestors: [...ancestors]
|
|
4320
4378
|
};
|
|
4321
4379
|
}
|
|
4322
|
-
|
|
4323
|
-
if (value instanceof Error) return value;
|
|
4324
|
-
return new Error("Unexpected error", { cause: value });
|
|
4325
|
-
}
|
|
4380
|
+
const toError = (value) => value instanceof Error ? value : new Error("Unexpected error", { cause: value });
|
|
4326
4381
|
//#endregion
|
|
4327
4382
|
//#region src/TypeCheckDisablers.ts
|
|
4328
4383
|
const tsDirectiveLikeRegEx = /@(ts-[a-z-]+)/;
|
|
@@ -4332,18 +4387,15 @@ function disableTypeCheckingInScript(ast) {
|
|
|
4332
4387
|
return prefixWithNoCheck(removeTSDirectives(ast.rawContent, ast.comments));
|
|
4333
4388
|
}
|
|
4334
4389
|
function prefixWithNoCheck(code) {
|
|
4335
|
-
|
|
4336
|
-
return afterLeadingComment(code);
|
|
4390
|
+
return code.startsWith("#") ? afterHashbang(code) : afterLeadingComment(code);
|
|
4337
4391
|
}
|
|
4338
4392
|
function afterHashbang(code) {
|
|
4339
4393
|
const newLineIndex = code.indexOf("\n");
|
|
4340
|
-
|
|
4341
|
-
return `${code.substring(0, newLineIndex)}\n// @ts-nocheck\n${code.substring(newLineIndex + 1)}`;
|
|
4394
|
+
return newLineIndex <= 0 ? code : `${code.substring(0, newLineIndex)}\n// @ts-nocheck\n${code.substring(newLineIndex + 1)}`;
|
|
4342
4395
|
}
|
|
4343
4396
|
function afterLeadingComment(code) {
|
|
4344
4397
|
const leadingComment = leadingCommentOf(code);
|
|
4345
|
-
|
|
4346
|
-
return `${leadingComment.concat("\n")}// @ts-nocheck\n${code.substring(leadingComment.length)}`;
|
|
4398
|
+
return leadingComment === void 0 ? `// @ts-nocheck\n${code}` : `${leadingComment.concat("\n")}// @ts-nocheck\n${code.substring(leadingComment.length)}`;
|
|
4347
4399
|
}
|
|
4348
4400
|
function leadingCommentOf(code) {
|
|
4349
4401
|
return STARTING_COMMENT.exec(code)?.[0];
|
|
@@ -4365,31 +4417,26 @@ function removeRanges(text, ranges) {
|
|
|
4365
4417
|
return remaining.pruned + text.substring(remaining.cursor);
|
|
4366
4418
|
}
|
|
4367
4419
|
function tryParseTSDirective(comment) {
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4420
|
+
return Option.match(Option.fromNullishOr(commentDirectiveRegEx.exec(comment.value)), {
|
|
4421
|
+
onNone: () => void 0,
|
|
4422
|
+
onSome: (match) => directiveRangeAt(comment.start, lengthOf(match[1]), lengthOf(match[2]))
|
|
4423
|
+
});
|
|
4424
|
+
}
|
|
4425
|
+
const lengthOf = (text) => Option.getOrElse(Option.map(Option.fromNullishOr(text), (value) => value.length), () => 0);
|
|
4426
|
+
const directiveRangeAt = (commentStart, prefixLength, nameLength) => {
|
|
4427
|
+
const startPos = commentStart + prefixLength + 2;
|
|
4373
4428
|
return {
|
|
4374
4429
|
startPos,
|
|
4375
|
-
endPos: startPos +
|
|
4430
|
+
endPos: startPos + nameLength + 1
|
|
4376
4431
|
};
|
|
4377
|
-
}
|
|
4378
|
-
function requirePart(part, message) {
|
|
4379
|
-
if (part === void 0) throw new Error(message);
|
|
4380
|
-
return part;
|
|
4381
|
-
}
|
|
4432
|
+
};
|
|
4382
4433
|
//#endregion
|
|
4383
4434
|
//#region src/Format.ts
|
|
4384
|
-
const
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
};
|
|
4388
|
-
const
|
|
4389
|
-
const requireTsFamilyAst = (ast) => Match.value(ast).pipe(Match.when({ format: "ts" }, (ts) => ts), Match.when({ format: "tsx" }, (tsx) => tsx), Match.orElse((other) => rejectAst(other, "a ts script")));
|
|
4390
|
-
function rejectAst(ast, expected) {
|
|
4391
|
-
throw new Error(`Expected ${expected} AST, received the "${ast.format}" format`);
|
|
4392
|
-
}
|
|
4435
|
+
const scriptAstError = (ast) => InstrumentError.make({
|
|
4436
|
+
message: `Expected a script AST, received the "${ast.format}" format`,
|
|
4437
|
+
cause: void 0
|
|
4438
|
+
});
|
|
4439
|
+
const scriptAstOf = (ast) => Match.value(ast).pipe(Match.when({ format: "embedded" }, () => Option.none()), Match.orElse((script) => Option.some(script)));
|
|
4393
4440
|
const extensionOf = (fileName) => {
|
|
4394
4441
|
const dot = fileName.lastIndexOf(".");
|
|
4395
4442
|
return dot > Math.max(fileName.lastIndexOf("/"), fileName.lastIndexOf("\\")) ? fileName.slice(dot).toLowerCase() : "";
|
|
@@ -4421,12 +4468,19 @@ const resolutionCommandOfDataFirst = (registry, fileName, formatIdOverride) => F
|
|
|
4421
4468
|
const resolutionCommandOf = dual((args) => typeof args[0] !== "string", resolutionCommandOfDataFirst);
|
|
4422
4469
|
const scriptHooks = (scriptFormat) => ({
|
|
4423
4470
|
parse: (text, fileName, _context) => parseScriptFormat(scriptFormat, text, fileName),
|
|
4424
|
-
transform: (ast, mutantCollector, context) =>
|
|
4471
|
+
transform: (ast, mutantCollector, context) => Option.match(scriptAstOf(ast), {
|
|
4472
|
+
onNone: () => Effect.fail(scriptAstError(ast)),
|
|
4473
|
+
onSome: (script) => transformScript(script, mutantCollector, context)
|
|
4474
|
+
}),
|
|
4425
4475
|
print: (ast, context) => printScriptAst(ast, context),
|
|
4426
|
-
disableTypeChecks: (ast) =>
|
|
4476
|
+
disableTypeChecks: (ast) => Option.match(scriptAstOf(ast), {
|
|
4477
|
+
onNone: () => Effect.fail(scriptAstError(ast)),
|
|
4478
|
+
onSome: (script) => Effect.succeed(disableTypeCheckingInScript(script))
|
|
4479
|
+
})
|
|
4427
4480
|
});
|
|
4428
4481
|
const parseScriptFormat = (scriptFormat, text, fileName) => Match.value(scriptFormat).pipe(Match.when("js", () => parseJS(text, fileName)), Match.when("ts", () => parseTS(text, fileName)), Match.when("tsx", () => parseTsx(text, fileName)), Match.exhaustive);
|
|
4429
|
-
const printScriptAst = (ast, context) => ast.format
|
|
4482
|
+
const printScriptAst = (ast, context) => Match.value(ast).pipe(Match.when({ format: "js" }, (js) => jsPrint(js, context)), Match.when({ format: "ts" }, (ts) => tsPrint(ts, context)), Match.when({ format: "tsx" }, (tsx) => tsPrint(tsx, context)), Match.orElse(printNothingForEmbeddedAst));
|
|
4483
|
+
const printNothingForEmbeddedAst = () => "";
|
|
4430
4484
|
const coreFormatRegistry = formatRegistry([
|
|
4431
4485
|
{
|
|
4432
4486
|
claim: {
|
|
@@ -4545,20 +4599,9 @@ const spliceForAssigned = (request) => Option.match(request.registry.entryForFor
|
|
|
4545
4599
|
onSome: (entry) => spliceFileFor(request.file, entry)
|
|
4546
4600
|
});
|
|
4547
4601
|
//#endregion
|
|
4548
|
-
//#region src/
|
|
4549
|
-
const toOneBasedOrigin = (origin) => ({
|
|
4550
|
-
line: origin.line + 1,
|
|
4551
|
-
column: origin.column
|
|
4552
|
-
});
|
|
4602
|
+
//#region src/drivers/oxc-program.ts
|
|
4553
4603
|
const EMBEDDED_SCRIPT_FILE = "embedded-script.js";
|
|
4554
|
-
const
|
|
4555
|
-
const oxc = yield* loadOxc;
|
|
4556
|
-
return {
|
|
4557
|
-
parseScript: (source, scriptFormat) => parseScriptProgram(oxc, source, scriptFormat),
|
|
4558
|
-
header: yield* instrumentationHeader
|
|
4559
|
-
};
|
|
4560
|
-
});
|
|
4561
|
-
const parseScriptProgram = (oxc, source, scriptFormat) => {
|
|
4604
|
+
const makeScriptParser = (oxc) => (source, scriptFormat) => {
|
|
4562
4605
|
const result = oxc.parseSync(EMBEDDED_SCRIPT_FILE, source, {
|
|
4563
4606
|
lang: scriptFormat,
|
|
4564
4607
|
range: true
|
|
@@ -4568,19 +4611,29 @@ const parseScriptProgram = (oxc, source, scriptFormat) => {
|
|
|
4568
4611
|
fileName: EMBEDDED_SCRIPT_FILE,
|
|
4569
4612
|
message: first.message,
|
|
4570
4613
|
location: {
|
|
4571
|
-
line:
|
|
4572
|
-
column:
|
|
4614
|
+
line: 1,
|
|
4615
|
+
column: 1
|
|
4573
4616
|
},
|
|
4574
4617
|
cause: first
|
|
4575
4618
|
});
|
|
4576
4619
|
return result.program;
|
|
4577
4620
|
};
|
|
4621
|
+
//#endregion
|
|
4622
|
+
//#region src/FrameworkEntry.ts
|
|
4623
|
+
const loadFrameworkToolkit = Effect.gen(function* () {
|
|
4624
|
+
const oxc = yield* loadOxc;
|
|
4625
|
+
const header = yield* instrumentationHeader;
|
|
4626
|
+
return {
|
|
4627
|
+
parseScript: makeScriptParser(oxc),
|
|
4628
|
+
header
|
|
4629
|
+
};
|
|
4630
|
+
});
|
|
4578
4631
|
const frameworkContextOf = (toolkit) => ({
|
|
4579
4632
|
parseScript: toolkit.parseScript,
|
|
4580
4633
|
printScript: (script) => printProgram(script, { hashbang: null }),
|
|
4581
4634
|
instrumentationHeader: () => toolkit.header
|
|
4582
4635
|
});
|
|
4583
|
-
const embeddedScriptsOf = (document, rawContent, originFileName,
|
|
4636
|
+
const embeddedScriptsOf = (document, rawContent, originFileName, lineStarts) => document.regions.flatMap((region, index) => {
|
|
4584
4637
|
return [{
|
|
4585
4638
|
region: index,
|
|
4586
4639
|
ast: {
|
|
@@ -4589,14 +4642,16 @@ const embeddedScriptsOf = (document, rawContent, originFileName, lineTable) => d
|
|
|
4589
4642
|
comments: [],
|
|
4590
4643
|
rawContent: rawContent.slice(region.start, region.end),
|
|
4591
4644
|
originFileName,
|
|
4592
|
-
offset:
|
|
4645
|
+
offset: originAt(lineStarts, region.start)
|
|
4593
4646
|
}
|
|
4594
4647
|
}];
|
|
4595
4648
|
});
|
|
4596
|
-
const
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
};
|
|
4649
|
+
const embeddedAstError = (ast) => InstrumentError.make({
|
|
4650
|
+
message: `Expected an embedded document AST, received the "${ast.format}" format`,
|
|
4651
|
+
cause: void 0
|
|
4652
|
+
});
|
|
4653
|
+
const embeddedOf = (ast) => Match.value(ast).pipe(Match.when({ format: "embedded" }, (embedded) => Option.some(embedded)), Match.orElse(() => Option.none()));
|
|
4654
|
+
const printNothingForNonEmbeddedAst = () => "";
|
|
4600
4655
|
const frameworkFailure = (moduleName, fileName, detail) => InstrumentError.make({
|
|
4601
4656
|
message: `The framework "${moduleName}" could not parse ${fileName}: ${detail}`,
|
|
4602
4657
|
cause: new Error(detail)
|
|
@@ -4605,7 +4660,7 @@ const hookFailure = (moduleName, hook, fileName, cause) => InstrumentError.make(
|
|
|
4605
4660
|
message: `The framework "${moduleName}" threw while running ${hook} for ${fileName}: ${errorTextOf(cause)}`,
|
|
4606
4661
|
cause
|
|
4607
4662
|
});
|
|
4608
|
-
const errorTextOf = (cause) => Option.getOrElse(Option.map(
|
|
4663
|
+
const errorTextOf = (cause) => Option.getOrElse(Option.map(errorTextOf$2(cause), (rendered) => rendered.text), () => "");
|
|
4609
4664
|
const runHook = (moduleName, hook, fileName, run) => Effect.try({
|
|
4610
4665
|
try: run,
|
|
4611
4666
|
catch: (cause) => hookFailure(moduleName, hook, fileName, cause)
|
|
@@ -4620,9 +4675,9 @@ const frameworkEntryOfDataFirst = (moduleName, framework) => ({
|
|
|
4620
4675
|
},
|
|
4621
4676
|
owner: moduleName,
|
|
4622
4677
|
ownerVersion: framework.claim.ownerVersion,
|
|
4623
|
-
parse:
|
|
4678
|
+
parse: Effect.fn("stryker.instrument.framework_entry.parse")(function* (text, fileName) {
|
|
4624
4679
|
const context = frameworkContextOf(yield* loadFrameworkToolkit);
|
|
4625
|
-
const
|
|
4680
|
+
const lineStarts = lineStartsOf(text);
|
|
4626
4681
|
const result = yield* runHook(moduleName, "parse", fileName, () => framework.parse(text, context));
|
|
4627
4682
|
const document = yield* settled(moduleName, fileName, result);
|
|
4628
4683
|
return {
|
|
@@ -4632,23 +4687,25 @@ const frameworkEntryOfDataFirst = (moduleName, framework) => ({
|
|
|
4632
4687
|
rawContent: text,
|
|
4633
4688
|
document,
|
|
4634
4689
|
context,
|
|
4635
|
-
scripts: embeddedScriptsOf(document, text, fileName,
|
|
4690
|
+
scripts: embeddedScriptsOf(document, text, fileName, lineStarts)
|
|
4636
4691
|
};
|
|
4637
4692
|
}),
|
|
4638
|
-
transform: (ast, mutantCollector, context) =>
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4693
|
+
transform: (ast, mutantCollector, context) => Option.match(embeddedOf(ast), {
|
|
4694
|
+
onNone: () => Effect.fail(embeddedAstError(ast)),
|
|
4695
|
+
onSome: Effect.fn("stryker.instrument.framework_entry.transform")(function* (embedded) {
|
|
4696
|
+
const warnings = yield* Effect.forEach(embedded.scripts, (script) => context.transform(script.ast, mutantCollector, context));
|
|
4697
|
+
embedded.document = yield* runHook(moduleName, "transform", embedded.originFileName, () => framework.transform(embedded.document, embedded.context));
|
|
4698
|
+
return warnings.flat();
|
|
4699
|
+
})
|
|
4643
4700
|
}),
|
|
4644
|
-
print: (ast) => {
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
},
|
|
4648
|
-
disableTypeChecks: (ast) => {
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
}
|
|
4701
|
+
print: (ast) => Option.match(embeddedOf(ast), {
|
|
4702
|
+
onNone: printNothingForNonEmbeddedAst,
|
|
4703
|
+
onSome: (embedded) => framework.print(embedded.document, embedded.context)
|
|
4704
|
+
}),
|
|
4705
|
+
disableTypeChecks: (ast) => Option.match(embeddedOf(ast), {
|
|
4706
|
+
onNone: () => Effect.fail(embeddedAstError(ast)),
|
|
4707
|
+
onSome: (embedded) => runHook(moduleName, "disableTypeChecks", embedded.originFileName, () => framework.disableTypeChecks(embedded.rawContent)).pipe(Effect.flatMap((result) => settled(moduleName, embedded.originFileName, result)))
|
|
4708
|
+
})
|
|
4652
4709
|
});
|
|
4653
4710
|
const frameworkEntryOf = dual((args) => args.length >= 2, frameworkEntryOfDataFirst);
|
|
4654
4711
|
//#endregion
|
|
@@ -4711,22 +4768,26 @@ const admitInstrumentFiles = Workflow.make({
|
|
|
4711
4768
|
});
|
|
4712
4769
|
//#endregion
|
|
4713
4770
|
//#region src/instrument-files.cell.ts
|
|
4714
|
-
const printedFile = (raw, { file, ast }) => ({
|
|
4771
|
+
const printedFile = (raw, { file, ast }) => Result.map(print(ast, raw.registry.entryForFormat), (content) => ({
|
|
4715
4772
|
name: file.name,
|
|
4716
4773
|
mutate: file.mutate,
|
|
4717
|
-
content
|
|
4718
|
-
});
|
|
4719
|
-
const
|
|
4720
|
-
|
|
4774
|
+
content
|
|
4775
|
+
}));
|
|
4776
|
+
const printedFiles = (raw) => Effect.forEach(raw.parsed, (parsed) => Effect.fromResult(printedFile(raw, parsed)), { concurrency: 1 }).pipe(Effect.mapError((failure) => InstrumentError.make({
|
|
4777
|
+
message: failure.message,
|
|
4778
|
+
cause: failure
|
|
4779
|
+
})));
|
|
4780
|
+
const instrumentedResult = (raw) => Effect.map(printedFiles(raw), (files) => InstrumentResult.make({
|
|
4781
|
+
files: [...files],
|
|
4721
4782
|
mutants: [...raw.mutants],
|
|
4722
4783
|
skipped: [...raw.skipped]
|
|
4723
|
-
});
|
|
4784
|
+
}));
|
|
4724
4785
|
const skippedOnlyResult = (raw) => InstrumentResult.make({
|
|
4725
4786
|
files: [],
|
|
4726
4787
|
mutants: [],
|
|
4727
4788
|
skipped: [...raw.skipped]
|
|
4728
4789
|
});
|
|
4729
|
-
const readInstrumentFiles =
|
|
4790
|
+
const readInstrumentFiles = Effect.fn("stryker.instrument.read_files")(function* (input) {
|
|
4730
4791
|
const outcomes = yield* Effect.forEach(input.files, (file) => fileOutcome({
|
|
4731
4792
|
registry: input.registry,
|
|
4732
4793
|
file
|
|
@@ -4755,7 +4816,7 @@ const readInstrumentFiles = (input) => Effect.gen(function* () {
|
|
|
4755
4816
|
};
|
|
4756
4817
|
});
|
|
4757
4818
|
const instrumentFilesCell = Sandwich.named("stryker.instrument.files")((input) => readInstrumentFiles(input)).decide(admitInstrumentFiles).write({
|
|
4758
|
-
InstrumentFilesAdmitted: (_admitted, raw) =>
|
|
4819
|
+
InstrumentFilesAdmitted: (_admitted, raw) => instrumentedResult(raw),
|
|
4759
4820
|
InstrumentFilesSkippedOnly: (_skipped, raw) => Effect.succeed(skippedOnlyResult(raw)),
|
|
4760
4821
|
CommandRejected: ({ issue }) => Effect.fail(InstrumentError.make({
|
|
4761
4822
|
message: issue,
|
|
@@ -4765,7 +4826,7 @@ const instrumentFilesCell = Sandwich.named("stryker.instrument.files")((input) =
|
|
|
4765
4826
|
//#endregion
|
|
4766
4827
|
//#region src/Instrument.service.ts
|
|
4767
4828
|
const toSchemaFile = (file) => ({
|
|
4768
|
-
name: file.name,
|
|
4829
|
+
name: CanonicalFileName.make(file.name),
|
|
4769
4830
|
content: file.content,
|
|
4770
4831
|
mutate: file.mutate
|
|
4771
4832
|
});
|
|
@@ -4810,9 +4871,14 @@ var Instrument_exports = /* @__PURE__ */ __exportAll({
|
|
|
4810
4871
|
//#endregion
|
|
4811
4872
|
//#region src/Mutant/mod.ts
|
|
4812
4873
|
var Mutant_exports = /* @__PURE__ */ __exportAll({
|
|
4874
|
+
ActionableStatusSchema: () => ActionableStatusSchema,
|
|
4813
4875
|
CanonicalFileName: () => CanonicalFileName,
|
|
4876
|
+
Column: () => Column,
|
|
4877
|
+
EphemeralStatusSchema: () => EphemeralStatusSchema,
|
|
4878
|
+
HitCount: () => HitCount,
|
|
4814
4879
|
InstrumenterContext: () => InstrumenterContext,
|
|
4815
|
-
|
|
4880
|
+
Line: () => Line,
|
|
4881
|
+
Location: () => Location,
|
|
4816
4882
|
Mutant: () => Mutant,
|
|
4817
4883
|
MutantActivationSchema: () => MutantActivationSchema,
|
|
4818
4884
|
MutantCoverageSchema: () => MutantCoverageSchema,
|
|
@@ -4821,10 +4887,14 @@ var Mutant_exports = /* @__PURE__ */ __exportAll({
|
|
|
4821
4887
|
MutantRunOptionsSchema: () => MutantRunOptionsSchema,
|
|
4822
4888
|
MutantStatusSchema: () => MutantStatusSchema,
|
|
4823
4889
|
MutatorName: () => MutatorName,
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4890
|
+
Offset: () => Offset,
|
|
4891
|
+
OpenEndLocation: () => OpenEndLocation,
|
|
4892
|
+
Position: () => Position,
|
|
4893
|
+
RememberedStatusSchema: () => RememberedStatusSchema,
|
|
4894
|
+
RunOptionsFields: () => RunOptionsFields,
|
|
4895
|
+
ScriptOrigin: () => ScriptOrigin,
|
|
4896
|
+
Span: () => Span,
|
|
4897
|
+
SurvivorStatusSchema: () => SurvivorStatusSchema
|
|
4828
4898
|
});
|
|
4829
4899
|
//#endregion
|
|
4830
|
-
export { ErrorText_exports as ErrorText, Format_exports as Format, Instrument_exports as Instrument, Mutant_exports as Mutant };
|
|
4900
|
+
export { ErrorText_exports as ErrorText, Format_exports as Format, Instrument_exports as Instrument, Mutant_exports as Mutant, lineStartsOf, locationOf, offsetAt, originAt, positionAt };
|