@systemfsoftware/stryker-js-typescript-checker 4.0.0 → 5.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 +91 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +522 -495
- package/package.json +14 -14
package/dist/index.mjs
CHANGED
|
@@ -6,16 +6,17 @@ import * as Layer from "effect/Layer";
|
|
|
6
6
|
import * as Path from "effect/Path";
|
|
7
7
|
import * as S from "effect/Schema";
|
|
8
8
|
import { Cell, Wire, Workflow } from "@systemfsoftware/effect-cell-types";
|
|
9
|
-
import { Mutant, errorToString } from "@systemfsoftware/stryker-js/Mutant";
|
|
9
|
+
import { Mutant, errorToString, normalizeFileName } from "@systemfsoftware/stryker-js/Mutant";
|
|
10
10
|
import { Predicate, Result, Schema } from "effect";
|
|
11
11
|
import * as HashMap from "effect/HashMap";
|
|
12
12
|
import * as Match from "effect/Match";
|
|
13
|
-
import * as MutableHashMap from "effect/MutableHashMap";
|
|
14
13
|
import * as Option from "effect/Option";
|
|
15
14
|
import { API, DiagnosticCategory } from "typescript/unstable/sync";
|
|
15
|
+
import * as Arr from "effect/Array";
|
|
16
16
|
import * as Result$1 from "effect/Result";
|
|
17
17
|
import { StrykerOptionsSchema } from "@systemfsoftware/stryker-js/Schema";
|
|
18
18
|
import * as Context from "effect/Context";
|
|
19
|
+
import * as MutableHashMap from "effect/MutableHashMap";
|
|
19
20
|
import * as MutableHashSet from "effect/MutableHashSet";
|
|
20
21
|
import * as Ref from "effect/Ref";
|
|
21
22
|
import { SyntaxKind } from "typescript/unstable/ast";
|
|
@@ -39,25 +40,29 @@ var typescript_checker_options_default = {
|
|
|
39
40
|
} }
|
|
40
41
|
};
|
|
41
42
|
//#endregion
|
|
42
|
-
//#region src/
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
text: Wire.mint(S.String),
|
|
46
|
-
fileName: Wire.mint(S.String)
|
|
47
|
-
}) {};
|
|
43
|
+
//#region src/CheckMutants.schema.ts
|
|
44
|
+
/** A file name the node map can be keyed by: non-empty, and naming an extension. */
|
|
45
|
+
const SourceFileSchema = Wire.mint(S.NonEmptyString.pipe(S.check(S.isPattern(/\.[^./\\]+$/))));
|
|
48
46
|
const DiagnosticSchema = Wire.wire({
|
|
49
|
-
fileName: Wire.mint(S.optional(
|
|
47
|
+
fileName: Wire.mint(S.optional(SourceFileSchema)),
|
|
50
48
|
text: Wire.mint(S.String)
|
|
51
49
|
});
|
|
52
50
|
const TSFileNodeSchema = Wire.mint(S.suspend(() => Wire.wire({
|
|
53
|
-
fileName:
|
|
51
|
+
fileName: SourceFileSchema,
|
|
54
52
|
parents: Wire.mint(S.Array(TSFileNodeSchema)),
|
|
55
53
|
children: Wire.mint(S.Array(TSFileNodeSchema))
|
|
56
54
|
})));
|
|
57
55
|
var CheckMutantsInput = class extends S.TaggedClass()("CheckMutantsInput", {
|
|
58
56
|
mutants: S.Array(Mutant),
|
|
59
57
|
diagnostics: S.Array(DiagnosticSchema),
|
|
60
|
-
nodes: Wire.mint(S.Record(
|
|
58
|
+
nodes: Wire.mint(S.Record(SourceFileSchema, TSFileNodeSchema))
|
|
59
|
+
}) {};
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/check-mutants.workflow.ts
|
|
62
|
+
var DiagnosticWithoutFileError = class extends S.TaggedError()("DiagnosticWithoutFileError", { text: S.String }) {};
|
|
63
|
+
var DiagnosticInUnrelatedFileError = class extends S.TaggedError()("DiagnosticInUnrelatedFileError", {
|
|
64
|
+
text: S.String,
|
|
65
|
+
fileName: S.String
|
|
61
66
|
}) {};
|
|
62
67
|
const CheckMutantsTypeId = Symbol.for("@systemfsoftware/stryker-js-typescript-checker/CheckMutants");
|
|
63
68
|
const MutantCheckStatusSchema = S.Union([S.Struct({ status: S.Literal("passed") }), S.Struct({
|
|
@@ -74,83 +79,90 @@ var RetestRequired = class extends S.TaggedClass()("RetestRequired", {
|
|
|
74
79
|
[CheckMutantsTypeId] = CheckMutantsTypeId;
|
|
75
80
|
};
|
|
76
81
|
const normalizeFileName$3 = (fileName) => fileName.replace(/\\/g, "/");
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return [...relatedMutants, ...childResult];
|
|
83
|
-
};
|
|
84
|
-
const classifyDiagnosticsPure = (diagnostics, mutants, nodes) => {
|
|
85
|
-
const definitive = {};
|
|
86
|
-
const needsRetest = {};
|
|
87
|
-
if (diagnostics.length > 0 && mutants.length === 1) {
|
|
88
|
-
const only = mutants[0];
|
|
89
|
-
if (only !== void 0) {
|
|
90
|
-
definitive[only.id] = [...diagnostics];
|
|
91
|
-
return Result$1.succeed({
|
|
92
|
-
definitive,
|
|
93
|
-
needsRetest: []
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
for (const diagnostic of diagnostics) {
|
|
98
|
-
const fileName = diagnostic.fileName;
|
|
99
|
-
if (fileName === void 0 || fileName === "") return Result$1.fail(new DiagnosticWithoutFileError({ text: diagnostic.text }));
|
|
100
|
-
const node = nodes[fileName];
|
|
101
|
-
if (node === void 0) return Result$1.fail(new DiagnosticInUnrelatedFileError({
|
|
82
|
+
const nodeAt = (fileName, nodes) => Option.filter(Option.fromUndefinedOr(nodes[fileName]), () => Object.hasOwn(nodes, fileName));
|
|
83
|
+
const nodeOf = (diagnostic, nodes) => Option.match(Option.filter(Option.fromUndefinedOr(diagnostic.fileName), (fileName) => fileName !== ""), {
|
|
84
|
+
onNone: () => Result$1.fail(new DiagnosticWithoutFileError({ text: diagnostic.text })),
|
|
85
|
+
onSome: (fileName) => Option.match(nodeAt(fileName, nodes), {
|
|
86
|
+
onNone: () => Result$1.fail(new DiagnosticInUnrelatedFileError({
|
|
102
87
|
text: diagnostic.text,
|
|
103
88
|
fileName
|
|
104
|
-
}))
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
89
|
+
})),
|
|
90
|
+
onSome: (node) => Result$1.succeed(node)
|
|
91
|
+
})
|
|
92
|
+
});
|
|
93
|
+
const walk = (node, mutants, visited) => Option.match(Option.filter(Option.some(node), (current) => !visited.includes(current.fileName)), {
|
|
94
|
+
onNone: () => [],
|
|
95
|
+
onSome: (current) => [...mutants.filter((mutant) => normalizeFileName$3(mutant.fileName) === current.fileName), ...current.children.flatMap((child) => walk(child, mutants, [...visited, current.fileName]))]
|
|
96
|
+
});
|
|
97
|
+
const emptyAccumulator = () => ({
|
|
98
|
+
definitive: HashMap.empty(),
|
|
99
|
+
needsRetest: HashMap.empty()
|
|
100
|
+
});
|
|
101
|
+
const addAll = (into, mutants) => mutants.reduce((accumulated, mutant) => HashMap.set(accumulated, mutant.id, mutant), into);
|
|
102
|
+
const appendDiagnostic = (into, mutantId, diagnostic) => HashMap.set(into, mutantId, [...Option.getOrElse(HashMap.get(into, mutantId), () => []), diagnostic]);
|
|
103
|
+
const classifyOne = (state, diagnostic, mutants, nodes) => Result$1.flatMap(nodeOf(diagnostic, nodes), (node) => {
|
|
104
|
+
const related = walk(node, mutants, []);
|
|
105
|
+
return Result$1.succeed(Option.match(Option.filter(Arr.head(related), () => related.length === 1), {
|
|
106
|
+
onSome: (only) => ({
|
|
107
|
+
definitive: appendDiagnostic(state.definitive, only.id, diagnostic),
|
|
108
|
+
needsRetest: state.needsRetest
|
|
109
|
+
}),
|
|
110
|
+
onNone: () => Option.match(Arr.head(related), {
|
|
111
|
+
onNone: () => ({
|
|
112
|
+
definitive: state.definitive,
|
|
113
|
+
needsRetest: addAll(state.needsRetest, mutants)
|
|
114
|
+
}),
|
|
115
|
+
onSome: () => ({
|
|
116
|
+
definitive: state.definitive,
|
|
117
|
+
needsRetest: addAll(state.needsRetest, related)
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
}));
|
|
121
|
+
});
|
|
122
|
+
const classifyDiagnostics = (diagnostics, mutants, nodes) => Option.match(Option.filter(Option.filter(Arr.head(mutants), () => mutants.length === 1), () => diagnostics.length > 0), {
|
|
123
|
+
onSome: (only) => Result$1.succeed({
|
|
124
|
+
definitive: HashMap.set(HashMap.empty(), only.id, [...diagnostics]),
|
|
125
|
+
needsRetest: []
|
|
126
|
+
}),
|
|
127
|
+
onNone: () => Result$1.map(diagnostics.reduce((accumulated, diagnostic) => Result$1.flatMap(accumulated, (state) => classifyOne(state, diagnostic, mutants, nodes)), Result$1.succeed(emptyAccumulator())), (state) => ({
|
|
128
|
+
definitive: state.definitive,
|
|
129
|
+
needsRetest: HashMap.toValues(state.needsRetest).filter((mutant) => !HashMap.has(state.definitive, mutant.id))
|
|
130
|
+
}))
|
|
131
|
+
});
|
|
132
|
+
const passedResults = (mutants) => mutants.map((mutant) => [mutant.id, { status: "passed" }]);
|
|
133
|
+
const checkResults = (mutants, classification) => {
|
|
134
|
+
const retestIds = classification.needsRetest.reduce((accumulated, mutant) => HashMap.set(accumulated, mutant.id, true), HashMap.empty());
|
|
135
|
+
return mutants.flatMap((mutant) => Option.match(HashMap.get(classification.definitive, mutant.id), {
|
|
136
|
+
onSome: (diagnostics) => [[mutant.id, {
|
|
142
137
|
status: "compileError",
|
|
143
|
-
reason:
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
results,
|
|
150
|
-
needsRetest: [...needsRetest]
|
|
138
|
+
reason: diagnostics.map((entry) => entry.text).join("\n")
|
|
139
|
+
}]],
|
|
140
|
+
onNone: () => Option.match(Option.filter(Option.some({ status: "passed" }), () => !HashMap.has(retestIds, mutant.id)), {
|
|
141
|
+
onSome: (status) => [[mutant.id, status]],
|
|
142
|
+
onNone: () => []
|
|
143
|
+
})
|
|
151
144
|
}));
|
|
152
145
|
};
|
|
153
|
-
const
|
|
146
|
+
const verdictOf = (mutants, classification) => {
|
|
147
|
+
const results = Object.fromEntries(checkResults(mutants, classification));
|
|
148
|
+
return Option.match(Arr.head(classification.needsRetest), {
|
|
149
|
+
onNone: () => CheckFinished.make({ results }),
|
|
150
|
+
onSome: () => RetestRequired.make({
|
|
151
|
+
results,
|
|
152
|
+
needsRetest: [...classification.needsRetest]
|
|
153
|
+
})
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
const classify = (input) => Result$1.map(classifyDiagnostics(input.diagnostics, input.mutants, input.nodes), (classification) => verdictOf(input.mutants, classification));
|
|
157
|
+
const withoutDisambiguation = (input) => Option.match(Arr.head(input.mutants), {
|
|
158
|
+
onNone: () => Option.some(CheckFinished.make({ results: {} })),
|
|
159
|
+
onSome: (first) => Option.map(Option.filter(Option.some(first), () => !Object.hasOwn(input.nodes, normalizeFileName$3(first.fileName))), () => CheckFinished.make({ results: Object.fromEntries(passedResults(input.mutants)) }))
|
|
160
|
+
});
|
|
161
|
+
const verdict = (input) => Option.match(withoutDisambiguation(input), {
|
|
162
|
+
onNone: () => classify(input),
|
|
163
|
+
onSome: (decision) => Result$1.succeed(decision)
|
|
164
|
+
});
|
|
165
|
+
const checkMutants = Workflow.make(CheckMutantsInput, verdict);
|
|
154
166
|
//#endregion
|
|
155
167
|
//#region src/Checker.schema.ts
|
|
156
168
|
/**
|
|
@@ -159,6 +171,7 @@ const checkMutants = Workflow.make(CheckMutantsInput, (input) => buildResult(inp
|
|
|
159
171
|
* Houses the wire types and error variants shared by the capability and its
|
|
160
172
|
* workflow. Decoded at the checker boundary; no I/O.
|
|
161
173
|
*/
|
|
174
|
+
const TypescriptCheckerOptionsSchema = S.Struct({ typescriptChecker: S.optional(S.Struct({ prioritizePerformanceOverAccuracy: S.optional(S.Boolean) })) });
|
|
162
175
|
var CheckMutantsCommand = class extends S.TaggedClass()("CheckMutantsCommand", { mutants: S.Array(Mutant) }) {};
|
|
163
176
|
/**
|
|
164
177
|
* Every way the TypeScript compiler can fail while serving a check.
|
|
@@ -175,12 +188,7 @@ var CompilerFailed = class extends S.TaggedError()("CompilerFailed", {
|
|
|
175
188
|
subject: S.optional(S.String)
|
|
176
189
|
}) {
|
|
177
190
|
get message() {
|
|
178
|
-
|
|
179
|
-
case "not-initialized": return "The TypeScript compiler was used before it was initialized";
|
|
180
|
-
case "no-projects": return `No projects were found for ${this.subject ?? "the tsconfig"}`;
|
|
181
|
-
case "unknown-file-node": return `The file graph has no node for '${this.subject ?? "a file"}', which should not happen`;
|
|
182
|
-
case "file-not-in-project": return `'${this.subject ?? "a file"}' is part of your TypeScript project but could not be found on disk`;
|
|
183
|
-
}
|
|
191
|
+
return Match.value(this.reason).pipe(Match.when("not-initialized", () => "The TypeScript compiler was used before it was initialized"), Match.when("no-projects", () => `No projects were found for ${this.subject ?? "the tsconfig"}`), Match.when("unknown-file-node", () => `The file graph has no node for '${this.subject ?? "a file"}', which should not happen`), Match.when("file-not-in-project", () => `'${this.subject ?? "a file"}' is part of your TypeScript project but could not be found on disk`), Match.exhaustive);
|
|
184
192
|
}
|
|
185
193
|
};
|
|
186
194
|
//#endregion
|
|
@@ -197,7 +205,7 @@ var UnsupportedTypeScriptVersionError = class extends Schema.TaggedError()("Unsu
|
|
|
197
205
|
/** Requested file is not present in the hybrid in-memory file map. */
|
|
198
206
|
var HybridFileNotFoundError = class extends Schema.TaggedError()("HybridFileNotFoundError", { fileName: Schema.String }) {};
|
|
199
207
|
//#endregion
|
|
200
|
-
//#region
|
|
208
|
+
//#region ../../../node_modules/.pnpm/@jsr+std__jsonc@1.0.2/node_modules/@jsr/std__jsonc/parse.js
|
|
201
209
|
/**
|
|
202
210
|
* Converts a JSON with Comments (JSONC) string into an object.
|
|
203
211
|
*
|
|
@@ -468,6 +476,10 @@ const LOW_EMIT_OPTIONS_FOR_PROJECT_REFERENCES = Object.freeze({
|
|
|
468
476
|
declaration: true,
|
|
469
477
|
composite: true
|
|
470
478
|
});
|
|
479
|
+
const reasonOfThrown = (error) => Match.value(error).pipe(Match.when(Predicate.isError, (thrown) => thrown.message), Match.when(Predicate.isString, (thrown) => thrown), Match.orElse((thrown) => Option.match(Option.filter(Option.fromUndefinedOr(JSON.stringify(thrown)), (text) => text.length > 0), {
|
|
480
|
+
onNone: () => "a non-Error value was thrown",
|
|
481
|
+
onSome: (text) => text
|
|
482
|
+
})));
|
|
471
483
|
/**
|
|
472
484
|
* Parses the raw text of a tsconfig file into a typed config, rejecting shapes this package cannot consume.
|
|
473
485
|
* @param fileName The tsconfig file name, used for error reporting
|
|
@@ -481,17 +493,9 @@ function parseTsConfig(fileName, jsonText) {
|
|
|
481
493
|
reason: error.message
|
|
482
494
|
}));
|
|
483
495
|
} catch (error) {
|
|
484
|
-
let reason;
|
|
485
|
-
if (error instanceof Error) reason = error.message;
|
|
486
|
-
else if (typeof error === "string") reason = error;
|
|
487
|
-
else {
|
|
488
|
-
const stringified = JSON.stringify(error);
|
|
489
|
-
if (stringified.length === 0) reason = "a non-Error value was thrown";
|
|
490
|
-
else reason = stringified;
|
|
491
|
-
}
|
|
492
496
|
return Result.fail(new TsConfigParseError({
|
|
493
497
|
file: fileName,
|
|
494
|
-
reason
|
|
498
|
+
reason: reasonOfThrown(error)
|
|
495
499
|
}));
|
|
496
500
|
}
|
|
497
501
|
}
|
|
@@ -503,36 +507,39 @@ const determineBuildModeEnabled = (tsconfigFileName, fsService) => Effect.gen(fu
|
|
|
503
507
|
onSuccess: (config) => config.references !== void 0
|
|
504
508
|
});
|
|
505
509
|
});
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
if (!useBuildMode && compilerOptions["declarationDir"] !== void 0 && compilerOptions["declarationDir"] !== null) delete compilerOptions["declarationDir"];
|
|
520
|
-
if (useBuildMode) {
|
|
521
|
-
delete compilerOptions["inlineSourceMap"];
|
|
522
|
-
delete compilerOptions["inlineSources"];
|
|
523
|
-
delete compilerOptions["mapRoute"];
|
|
524
|
-
delete compilerOptions["sourceRoot"];
|
|
525
|
-
delete compilerOptions["outFile"];
|
|
526
|
-
}
|
|
527
|
-
if (useBuildMode) return JSON.stringify({
|
|
510
|
+
const withCompilerOverrides = (config, extraOptions) => ({
|
|
511
|
+
...config.compilerOptions,
|
|
512
|
+
...COMPILER_OPTIONS_OVERRIDES,
|
|
513
|
+
...extraOptions
|
|
514
|
+
});
|
|
515
|
+
const projectReferencesJson = (config) => {
|
|
516
|
+
const compilerOptions = withCompilerOverrides(config, LOW_EMIT_OPTIONS_FOR_PROJECT_REFERENCES);
|
|
517
|
+
delete compilerOptions["inlineSourceMap"];
|
|
518
|
+
delete compilerOptions["inlineSources"];
|
|
519
|
+
delete compilerOptions["mapRoute"];
|
|
520
|
+
delete compilerOptions["sourceRoot"];
|
|
521
|
+
delete compilerOptions["outFile"];
|
|
522
|
+
return JSON.stringify({
|
|
528
523
|
...config,
|
|
529
524
|
compilerOptions
|
|
530
525
|
});
|
|
526
|
+
};
|
|
527
|
+
const singleProjectJson = (config) => {
|
|
528
|
+
const compilerOptions = withCompilerOverrides(config, NO_EMIT_OPTIONS_FOR_SINGLE_PROJECT);
|
|
529
|
+
if (compilerOptions["declarationDir"] !== null) delete compilerOptions["declarationDir"];
|
|
531
530
|
const { references: _references, ...withoutReferences } = config;
|
|
532
531
|
return JSON.stringify({
|
|
533
532
|
...withoutReferences,
|
|
534
533
|
compilerOptions
|
|
535
534
|
});
|
|
535
|
+
};
|
|
536
|
+
/**
|
|
537
|
+
* Overrides compiler options to speed up compilation and disable code quality
|
|
538
|
+
* checks irrelevant during mutation testing.
|
|
539
|
+
*/
|
|
540
|
+
function overrideOptions(config, useBuildMode) {
|
|
541
|
+
if (useBuildMode) return projectReferencesJson(config);
|
|
542
|
+
return singleProjectJson(config);
|
|
536
543
|
}
|
|
537
544
|
/**
|
|
538
545
|
* Retrieves the referenced config files based on parsed configuration.
|
|
@@ -548,35 +555,71 @@ function retrieveReferencedProjects(config, fromDirName, pathService) {
|
|
|
548
555
|
//#region src/Compiler.ts
|
|
549
556
|
const normalizeFileName$1 = (fileName) => fileName.replace(/\\/g, "/");
|
|
550
557
|
const findSourceMapRegex = /\/\/# sourceMappingURL=(.+)$/m;
|
|
558
|
+
/** A specifier that resolves relative to the importing file. */
|
|
559
|
+
const relativeSpecifierPattern = /^\.\.?\//;
|
|
560
|
+
/** A file that never belongs to the dependency graph: declarations and dependencies. */
|
|
561
|
+
const ignoredGraphFileNamePattern = /\.d\.ts$|node_modules/;
|
|
551
562
|
function getSourceMappingURL(content) {
|
|
552
563
|
return findSourceMapRegex.exec(content)?.[1];
|
|
553
564
|
}
|
|
554
565
|
let cachedTSVersion;
|
|
555
|
-
const
|
|
556
|
-
|
|
566
|
+
const isString = (value) => typeof value === "string";
|
|
567
|
+
const isNonEmptyString = (value) => value !== void 0 && value !== "";
|
|
568
|
+
const versionFieldOf = (raw) => {
|
|
569
|
+
if (Predicate.hasProperty(raw, "version")) return Option.some(raw.version);
|
|
570
|
+
return Option.none();
|
|
571
|
+
};
|
|
572
|
+
const readTypescriptPackageVersion = (fsService, pathService) => Effect.gen(function* () {
|
|
557
573
|
const urlString = import.meta.resolve("typescript/package.json");
|
|
558
574
|
const pkgPath = yield* pathService.fromFileUrl(new URL(urlString));
|
|
559
575
|
const text = yield* fsService.readFileString(pkgPath);
|
|
560
576
|
const raw = JSON.parse(text);
|
|
561
|
-
|
|
562
|
-
|
|
577
|
+
return Option.getOrElse(Option.flatMap(versionFieldOf(raw), (version) => Option.liftPredicate(version, isString)), () => "");
|
|
578
|
+
});
|
|
579
|
+
const getTSVersion = (fsService, pathService) => Effect.gen(function* () {
|
|
580
|
+
if (cachedTSVersion !== void 0) return cachedTSVersion;
|
|
581
|
+
const version = yield* readTypescriptPackageVersion(fsService, pathService);
|
|
563
582
|
cachedTSVersion = version;
|
|
564
583
|
return version;
|
|
565
584
|
});
|
|
585
|
+
const minimumSupportedTypeScriptVersion = {
|
|
586
|
+
major: 7,
|
|
587
|
+
minor: 0,
|
|
588
|
+
patch: 0
|
|
589
|
+
};
|
|
590
|
+
const versionComponent = (parts, index) => {
|
|
591
|
+
const part = parts[index];
|
|
592
|
+
if (part === void 0) return 0;
|
|
593
|
+
return Number.parseInt(part, 10);
|
|
594
|
+
};
|
|
595
|
+
/** Drops any pre-release (`-`) or build (`+`) suffix, keeping the numeric base. */
|
|
596
|
+
const parseTypeScriptVersion = (version) => {
|
|
597
|
+
const parts = version.replace(/[-+][\s\S]*$/, "").split(".");
|
|
598
|
+
return {
|
|
599
|
+
major: versionComponent(parts, 0),
|
|
600
|
+
minor: versionComponent(parts, 1),
|
|
601
|
+
patch: versionComponent(parts, 2)
|
|
602
|
+
};
|
|
603
|
+
};
|
|
604
|
+
const compareVersionNumbers = (left, right) => {
|
|
605
|
+
return [
|
|
606
|
+
left.major - right.major,
|
|
607
|
+
left.minor - right.minor,
|
|
608
|
+
left.patch - right.patch
|
|
609
|
+
].find((difference) => difference !== 0) ?? 0;
|
|
610
|
+
};
|
|
566
611
|
/**
|
|
567
612
|
* Whether a TypeScript version satisfies `>=7.0.0`. Pre-release suffixes are
|
|
568
613
|
* stripped so `7.0.0-beta` compares as `7.0.0`.
|
|
569
614
|
*/
|
|
570
615
|
function isSupportedTypescriptVersion(version) {
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
if (minor !== 0) return minor > 0;
|
|
579
|
-
return patch >= 0;
|
|
616
|
+
const parsed = parseTypeScriptVersion(version);
|
|
617
|
+
if (![
|
|
618
|
+
parsed.major,
|
|
619
|
+
parsed.minor,
|
|
620
|
+
parsed.patch
|
|
621
|
+
].every((part) => !Number.isNaN(part))) return false;
|
|
622
|
+
return compareVersionNumbers(parsed, minimumSupportedTypeScriptVersion) >= 0;
|
|
580
623
|
}
|
|
581
624
|
const guardTSVersion = (fsService, pathService) => Effect.gen(function* () {
|
|
582
625
|
const version = yield* getTSVersion(fsService, pathService);
|
|
@@ -617,13 +660,10 @@ function resetScriptFile(file) {
|
|
|
617
660
|
function getOffset(file, pos) {
|
|
618
661
|
const lines = file.originalContent.split("\n");
|
|
619
662
|
const lineCount = Math.min(pos.line, lines.length);
|
|
620
|
-
let offset =
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
offset += line.length + 1;
|
|
625
|
-
}
|
|
626
|
-
offset += pos.column;
|
|
663
|
+
let offset = pos.column;
|
|
664
|
+
lines.forEach((line, index) => {
|
|
665
|
+
if (index < lineCount) offset += line.length + 1;
|
|
666
|
+
});
|
|
627
667
|
return offset;
|
|
628
668
|
}
|
|
629
669
|
const makeEmptyFilesMap = () => MutableHashMap.empty();
|
|
@@ -635,67 +675,63 @@ const setInPlace = (map, key, value) => {
|
|
|
635
675
|
const makeHybridFileSystem = (fsService) => Effect.gen(function* () {
|
|
636
676
|
const filesRef = yield* Ref.make(makeEmptyFilesMap());
|
|
637
677
|
const overridesRef = yield* Ref.make(makeEmptyOverridesMap());
|
|
638
|
-
const
|
|
678
|
+
const memoryContent = (file) => {
|
|
679
|
+
if (file === void 0) return null;
|
|
680
|
+
return file.content;
|
|
681
|
+
};
|
|
682
|
+
const readFromSources = (files, overrides, fileName) => {
|
|
683
|
+
const override = MutableHashMap.get(overrides, fileName);
|
|
684
|
+
if (Option.isSome(override)) return override.value;
|
|
685
|
+
return Option.match(MutableHashMap.get(files, fileName), {
|
|
686
|
+
onNone: () => void 0,
|
|
687
|
+
onSome: memoryContent
|
|
688
|
+
});
|
|
689
|
+
};
|
|
690
|
+
const existsInSources = (files, overrides, fileName) => {
|
|
691
|
+
const override = MutableHashMap.get(overrides, fileName);
|
|
692
|
+
if (Option.isSome(override)) return true;
|
|
693
|
+
return Option.match(MutableHashMap.get(files, fileName), {
|
|
694
|
+
onNone: () => void 0,
|
|
695
|
+
onSome: (file) => file !== void 0
|
|
696
|
+
});
|
|
697
|
+
};
|
|
639
698
|
const fileSystem = {
|
|
640
699
|
readFile: (fileName) => {
|
|
641
700
|
const normalized = normalizeFileName$1(fileName);
|
|
642
|
-
if (
|
|
643
|
-
|
|
644
|
-
if (Option.isSome(overrideOpt)) return overrideOpt.value;
|
|
645
|
-
const files = filesRef.ref.current;
|
|
646
|
-
if (MutableHashMap.has(files, normalized)) {
|
|
647
|
-
const fileOpt = MutableHashMap.get(files, normalized);
|
|
648
|
-
if (Option.isSome(fileOpt)) {
|
|
649
|
-
const file = fileOpt.value;
|
|
650
|
-
if (file !== void 0) return file.content;
|
|
651
|
-
return null;
|
|
652
|
-
}
|
|
653
|
-
}
|
|
701
|
+
if (normalized.endsWith(".tsbuildinfo")) return null;
|
|
702
|
+
return readFromSources(filesRef.ref.current, overridesRef.ref.current, normalized);
|
|
654
703
|
},
|
|
655
704
|
fileExists: (fileName) => {
|
|
656
705
|
const normalized = normalizeFileName$1(fileName);
|
|
657
|
-
if (
|
|
658
|
-
|
|
659
|
-
const files = filesRef.ref.current;
|
|
660
|
-
if (MutableHashMap.has(files, normalized)) {
|
|
661
|
-
const opt = MutableHashMap.get(files, normalized);
|
|
662
|
-
if (Option.isSome(opt)) return opt.value !== void 0;
|
|
663
|
-
return false;
|
|
664
|
-
}
|
|
706
|
+
if (normalized.endsWith(".tsbuildinfo")) return false;
|
|
707
|
+
return existsInSources(filesRef.ref.current, overridesRef.ref.current, normalized);
|
|
665
708
|
},
|
|
666
709
|
directoryExists: () => void 0,
|
|
667
710
|
getAccessibleEntries: () => void 0,
|
|
668
711
|
realpath: () => void 0
|
|
669
712
|
};
|
|
713
|
+
const readFileFromDisk = (fileName) => Effect.gen(function* () {
|
|
714
|
+
const content = yield* fsService.readFileString(fileName).pipe(Effect.orElseSucceed(() => void 0));
|
|
715
|
+
const file = Option.getOrUndefined(Option.map(Option.fromUndefinedOr(content), (text) => makeScriptFile(text, fileName)));
|
|
716
|
+
yield* Ref.update(filesRef, (m) => setInPlace(m, fileName, file));
|
|
717
|
+
return file;
|
|
718
|
+
});
|
|
670
719
|
const getFile = (fileName) => Effect.gen(function* () {
|
|
671
720
|
const normalized = normalizeFileName$1(fileName);
|
|
672
721
|
const files = yield* Ref.get(filesRef);
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
}
|
|
677
|
-
const content = yield* fsService.readFileString(normalized).pipe(Effect.orElseSucceed(() => void 0));
|
|
678
|
-
if (content === void 0) {
|
|
679
|
-
yield* Ref.update(filesRef, (m) => setInPlace(m, normalized, void 0));
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
const file = makeScriptFile(content, normalized);
|
|
683
|
-
yield* Ref.update(filesRef, (m) => setInPlace(m, normalized, file));
|
|
684
|
-
return file;
|
|
722
|
+
const cached = MutableHashMap.get(files, normalized);
|
|
723
|
+
if (Option.isSome(cached)) return cached.value;
|
|
724
|
+
return yield* readFileFromDisk(normalized);
|
|
685
725
|
});
|
|
726
|
+
const fileForWrite = (existing, data, fileName) => {
|
|
727
|
+
if (existing === void 0) return makeScriptFile(data, fileName);
|
|
728
|
+
return withContent(existing, data);
|
|
729
|
+
};
|
|
686
730
|
const writeFile = (fileName, data) => Effect.gen(function* () {
|
|
687
731
|
const normalized = normalizeFileName$1(fileName);
|
|
688
732
|
const files = yield* Ref.get(filesRef);
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
if (Option.isSome(existingOpt)) existing = existingOpt.value;
|
|
692
|
-
if (existing !== void 0) {
|
|
693
|
-
const next = withContent(existing, data);
|
|
694
|
-
yield* Ref.update(filesRef, (m) => setInPlace(m, normalized, next));
|
|
695
|
-
} else {
|
|
696
|
-
const file = makeScriptFile(data, normalized);
|
|
697
|
-
yield* Ref.update(filesRef, (m) => setInPlace(m, normalized, file));
|
|
698
|
-
}
|
|
733
|
+
const existing = Option.getOrUndefined(MutableHashMap.get(files, normalized));
|
|
734
|
+
yield* Ref.update(filesRef, (m) => setInPlace(m, normalized, fileForWrite(existing, data, normalized)));
|
|
699
735
|
});
|
|
700
736
|
const mutateFile = (fileName, mutant) => Effect.gen(function* () {
|
|
701
737
|
const file = yield* getFile(fileName);
|
|
@@ -707,20 +743,13 @@ const makeHybridFileSystem = (fsService) => Effect.gen(function* () {
|
|
|
707
743
|
const resetFile = (fileName) => Effect.gen(function* () {
|
|
708
744
|
const normalized = normalizeFileName$1(fileName);
|
|
709
745
|
const files = yield* Ref.get(filesRef);
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
if (file !== void 0) {
|
|
714
|
-
const next = resetScriptFile(file);
|
|
715
|
-
yield* Ref.update(filesRef, (m) => setInPlace(m, normalized, next));
|
|
716
|
-
}
|
|
746
|
+
const file = Option.getOrUndefined(MutableHashMap.get(files, normalized));
|
|
747
|
+
if (file === void 0) return;
|
|
748
|
+
yield* Ref.update(filesRef, (m) => setInPlace(m, normalized, resetScriptFile(file)));
|
|
717
749
|
});
|
|
718
750
|
const existsInMemory = (fileName) => Effect.gen(function* () {
|
|
719
751
|
const files = yield* Ref.get(filesRef);
|
|
720
|
-
|
|
721
|
-
let file = void 0;
|
|
722
|
-
if (Option.isSome(opt)) file = opt.value;
|
|
723
|
-
return file !== void 0;
|
|
752
|
+
return Option.getOrUndefined(MutableHashMap.get(files, normalizeFileName$1(fileName))) !== void 0;
|
|
724
753
|
});
|
|
725
754
|
const setTsConfigOverrides = (overrides) => Ref.set(overridesRef, overrides);
|
|
726
755
|
return {
|
|
@@ -742,42 +771,50 @@ function makeTSFileNode(fileName) {
|
|
|
742
771
|
}
|
|
743
772
|
function getAllParentReferencesIncludingSelf(node, allParentReferences = MutableHashSet.empty()) {
|
|
744
773
|
MutableHashSet.add(allParentReferences, node);
|
|
745
|
-
|
|
774
|
+
node.parents.forEach((parent) => collectParentReference(parent, allParentReferences));
|
|
746
775
|
return allParentReferences;
|
|
747
776
|
}
|
|
748
|
-
function
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
while (MutableHashSet.size(mutantsToGroup) > 0) {
|
|
752
|
-
const group = [];
|
|
753
|
-
const groupNodes = MutableHashSet.empty();
|
|
754
|
-
const nodesToIgnore = MutableHashSet.empty();
|
|
755
|
-
for (const currentMutant of mutantsToGroup) {
|
|
756
|
-
const currentNode = findNode(currentMutant.fileName, nodes);
|
|
757
|
-
if (!MutableHashSet.has(nodesToIgnore, currentNode) && !parentsHaveOverlapWith(currentNode, groupNodes)) {
|
|
758
|
-
group.push(currentMutant.id);
|
|
759
|
-
MutableHashSet.add(groupNodes, currentNode);
|
|
760
|
-
MutableHashSet.remove(mutantsToGroup, currentMutant);
|
|
761
|
-
addRangeOfNodesToSet(nodesToIgnore, getAllParentReferencesIncludingSelf(currentNode));
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
groups.push(group);
|
|
765
|
-
}
|
|
766
|
-
return groups;
|
|
777
|
+
function collectParentReference(parent, allParentReferences) {
|
|
778
|
+
if (MutableHashSet.has(allParentReferences, parent)) return;
|
|
779
|
+
getAllParentReferencesIncludingSelf(parent, allParentReferences);
|
|
767
780
|
}
|
|
768
781
|
function addRangeOfNodesToSet(nodes, nodesToAdd) {
|
|
769
|
-
for (const
|
|
782
|
+
for (const node of nodesToAdd) MutableHashSet.add(nodes, node);
|
|
770
783
|
}
|
|
771
784
|
function findNode(fileName, nodes) {
|
|
772
|
-
const
|
|
773
|
-
if (Option.
|
|
774
|
-
|
|
775
|
-
if (Option.isSome(fallbackOption)) return fallbackOption.value;
|
|
776
|
-
throw new Error(`Node not in graph: ${fileName}`);
|
|
785
|
+
const node = Option.firstSomeOf([MutableHashMap.get(nodes, normalizeFileName$1(fileName)), MutableHashMap.get(nodes, fileName)]);
|
|
786
|
+
if (Option.isNone(node)) throw new Error(`Node not in graph: ${fileName}`);
|
|
787
|
+
return node.value;
|
|
777
788
|
}
|
|
778
789
|
function parentsHaveOverlapWith(currentNode, groupNodes) {
|
|
779
|
-
|
|
780
|
-
|
|
790
|
+
return Array.from(getAllParentReferencesIncludingSelf(currentNode)).some((parentNode) => MutableHashSet.has(groupNodes, parentNode));
|
|
791
|
+
}
|
|
792
|
+
function mutantCanJoinGroup(currentNode, group) {
|
|
793
|
+
if (MutableHashSet.has(group.ignoredNodes, currentNode)) return false;
|
|
794
|
+
return !parentsHaveOverlapWith(currentNode, group.nodes);
|
|
795
|
+
}
|
|
796
|
+
function addMutantToGroup(currentMutant, mutantsToGroup, group, nodes) {
|
|
797
|
+
const currentNode = findNode(currentMutant.fileName, nodes);
|
|
798
|
+
if (!mutantCanJoinGroup(currentNode, group)) return;
|
|
799
|
+
group.mutantIds.push(currentMutant.id);
|
|
800
|
+
MutableHashSet.add(group.nodes, currentNode);
|
|
801
|
+
MutableHashSet.remove(mutantsToGroup, currentMutant);
|
|
802
|
+
addRangeOfNodesToSet(group.ignoredNodes, getAllParentReferencesIncludingSelf(currentNode));
|
|
803
|
+
}
|
|
804
|
+
function takeGroup(mutantsToGroup, nodes) {
|
|
805
|
+
const group = {
|
|
806
|
+
mutantIds: [],
|
|
807
|
+
nodes: MutableHashSet.empty(),
|
|
808
|
+
ignoredNodes: MutableHashSet.empty()
|
|
809
|
+
};
|
|
810
|
+
for (const currentMutant of mutantsToGroup) addMutantToGroup(currentMutant, mutantsToGroup, group, nodes);
|
|
811
|
+
return group.mutantIds;
|
|
812
|
+
}
|
|
813
|
+
function createGroups(mutants, nodes) {
|
|
814
|
+
const groups = [];
|
|
815
|
+
const mutantsToGroup = MutableHashSet.fromIterable(mutants);
|
|
816
|
+
while (MutableHashSet.size(mutantsToGroup) > 0) groups.push(takeGroup(mutantsToGroup, nodes));
|
|
817
|
+
return groups;
|
|
781
818
|
}
|
|
782
819
|
var TypeScriptCompiler = class extends Context.Service()("@systemfsoftware/stryker-js-typescript-checker/TypeScriptCompiler") {};
|
|
783
820
|
const makeDummy = Effect.gen(function* () {
|
|
@@ -828,63 +865,91 @@ function makeTypescriptCompiler(options, fs, fsService, pathService) {
|
|
|
828
865
|
tsconfigFile: rawTsconfigFile
|
|
829
866
|
};
|
|
830
867
|
const stateRef = Ref.makeUnsafe(initialState);
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
868
|
+
const snapshotOf = (state) => {
|
|
869
|
+
if (state.snapshot === void 0) return Effect.fail(new CompilerFailed({ reason: "not-initialized" }));
|
|
870
|
+
return Effect.succeed(state.snapshot);
|
|
871
|
+
};
|
|
872
|
+
const programsOf = (snapshot, tsconfigFile) => {
|
|
873
|
+
const projects = snapshot.getProjects();
|
|
874
|
+
if (projects.length === 0) return Effect.fail(new CompilerFailed({
|
|
836
875
|
reason: "no-projects",
|
|
837
|
-
subject:
|
|
838
|
-
});
|
|
839
|
-
return projects.map((project) => project.program);
|
|
876
|
+
subject: tsconfigFile
|
|
877
|
+
}));
|
|
878
|
+
return Effect.succeed(projects.map((project) => project.program));
|
|
879
|
+
};
|
|
880
|
+
const getProgramsEffect = () => Effect.gen(function* () {
|
|
881
|
+
const state = yield* Ref.get(stateRef);
|
|
882
|
+
const snapshot = yield* snapshotOf(state);
|
|
883
|
+
return yield* programsOf(snapshot, state.tsconfigFile);
|
|
840
884
|
});
|
|
841
885
|
const guardTSConfigFileExistsEffect = Effect.gen(function* () {
|
|
842
886
|
const s = yield* Ref.get(stateRef);
|
|
843
887
|
yield* fsService.readFileString(s.tsconfigFile).pipe(Effect.mapError(() => new TsConfigNotFoundError({ file: s.tsconfigFile })));
|
|
844
888
|
});
|
|
845
|
-
const
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
const
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
if (current === void 0 || current === "" || MutableHashSet.has(processed, current)) continue;
|
|
853
|
-
MutableHashSet.add(processed, current);
|
|
854
|
-
const content = yield* fsService.readFileString(current);
|
|
855
|
-
const parsed = parseTsConfig(current, content);
|
|
856
|
-
if (Result.isFailure(parsed)) {
|
|
857
|
-
MutableHashMap.set(tsConfigOverrides, current, content);
|
|
858
|
-
continue;
|
|
859
|
-
}
|
|
860
|
-
MutableHashMap.set(tsConfigOverrides, current, overrideOptions(parsed.success, buildModeEnabled));
|
|
861
|
-
for (const referenced of retrieveReferencedProjects(parsed.success, pathService.dirname(current), pathService)) {
|
|
862
|
-
const normalized = normalizeFileName$1(referenced);
|
|
863
|
-
MutableHashSet.add(s.allTSConfigFiles, normalized);
|
|
864
|
-
toProcess.push(referenced);
|
|
865
|
-
}
|
|
889
|
+
const isBlankTsConfigPath = (current) => current === void 0 || current === "";
|
|
890
|
+
const isUnprocessedTsConfigPath = (current, processed) => !isBlankTsConfigPath(current) && !MutableHashSet.has(processed, current);
|
|
891
|
+
const recordParsedTsConfig = (current, config, traversal) => {
|
|
892
|
+
MutableHashMap.set(traversal.overrides, current, overrideOptions(config, traversal.buildModeEnabled));
|
|
893
|
+
for (const referenced of retrieveReferencedProjects(config, pathService.dirname(current), pathService)) {
|
|
894
|
+
MutableHashSet.add(traversal.allTsConfigFiles, normalizeFileName$1(referenced));
|
|
895
|
+
traversal.pending.push(referenced);
|
|
866
896
|
}
|
|
867
|
-
|
|
897
|
+
};
|
|
898
|
+
const recordTsConfig = (current, content, parsed, traversal) => {
|
|
899
|
+
if (Result.isFailure(parsed)) {
|
|
900
|
+
MutableHashMap.set(traversal.overrides, current, content);
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
recordParsedTsConfig(current, parsed.success, traversal);
|
|
904
|
+
};
|
|
905
|
+
const processNextTsConfig = (traversal) => Effect.gen(function* () {
|
|
906
|
+
const current = traversal.pending.pop();
|
|
907
|
+
if (!isUnprocessedTsConfigPath(current, traversal.processed)) return;
|
|
908
|
+
MutableHashSet.add(traversal.processed, current);
|
|
909
|
+
const content = yield* fsService.readFileString(current);
|
|
910
|
+
recordTsConfig(current, content, parseTsConfig(current, content), traversal);
|
|
911
|
+
});
|
|
912
|
+
const collectAllTSConfigFiles = (buildModeEnabled) => Effect.gen(function* () {
|
|
913
|
+
const state = yield* Ref.get(stateRef);
|
|
914
|
+
const traversal = {
|
|
915
|
+
overrides: MutableHashMap.empty(),
|
|
916
|
+
pending: [state.tsconfigFile],
|
|
917
|
+
processed: MutableHashSet.empty(),
|
|
918
|
+
allTsConfigFiles: state.allTSConfigFiles,
|
|
919
|
+
buildModeEnabled
|
|
920
|
+
};
|
|
921
|
+
while (traversal.pending.length > 0) yield* processNextTsConfig(traversal);
|
|
922
|
+
yield* fs.setTsConfigOverrides(traversal.overrides);
|
|
868
923
|
yield* Ref.update(stateRef, (prev) => ({
|
|
869
924
|
...prev,
|
|
870
|
-
allTSConfigFiles: MutableHashSet.fromIterable(
|
|
925
|
+
allTSConfigFiles: MutableHashSet.fromIterable(traversal.allTsConfigFiles)
|
|
871
926
|
}));
|
|
872
927
|
});
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
928
|
+
const importDeclarationSpecifierOf = (statement, sourceFile) => {
|
|
929
|
+
if (statement.kind !== SyntaxKind.ImportDeclaration) return Option.none();
|
|
930
|
+
let specifier;
|
|
931
|
+
statement.forEachChild((child) => {
|
|
932
|
+
if (child.kind === SyntaxKind.StringLiteral) specifier = child;
|
|
933
|
+
});
|
|
934
|
+
return Option.map(Option.fromUndefinedOr(specifier), (found) => found.getText(sourceFile));
|
|
935
|
+
};
|
|
936
|
+
const collectImportEqualsSpecifiers = (statement, sourceFile, into) => {
|
|
937
|
+
if (statement.kind !== SyntaxKind.ImportEqualsDeclaration) return;
|
|
938
|
+
statement.forEachChild((child) => {
|
|
882
939
|
if (child.kind === SyntaxKind.ExternalModuleReference) child.forEachChild((refChild) => {
|
|
883
|
-
if (refChild.kind === SyntaxKind.StringLiteral)
|
|
940
|
+
if (refChild.kind === SyntaxKind.StringLiteral) into.push(refChild.getText(sourceFile));
|
|
884
941
|
});
|
|
885
942
|
});
|
|
886
|
-
|
|
887
|
-
|
|
943
|
+
};
|
|
944
|
+
const extractImports = (sourceFile) => {
|
|
945
|
+
const result = [];
|
|
946
|
+
sourceFile.statements.forEach((statement) => {
|
|
947
|
+
const specifier = importDeclarationSpecifierOf(statement, sourceFile);
|
|
948
|
+
if (Option.isSome(specifier)) result.push(specifier.value);
|
|
949
|
+
collectImportEqualsSpecifiers(statement, sourceFile, result);
|
|
950
|
+
});
|
|
951
|
+
sourceFile.referencedFiles.forEach((ref) => result.push(ref.fileName));
|
|
952
|
+
sourceFile.typeReferenceDirectives.forEach((ref) => result.push(ref.fileName));
|
|
888
953
|
return result;
|
|
889
954
|
};
|
|
890
955
|
const getResolutionCandidates = (resolved, pathService) => {
|
|
@@ -922,127 +987,160 @@ function makeTypescriptCompiler(options, fs, fsService, pathService) {
|
|
|
922
987
|
};
|
|
923
988
|
const resolveModuleSpecifier = (sourceFileName, specifier, sourceFiles, pathService) => {
|
|
924
989
|
const cleaned = specifier.replace(/^['"]|['"]$/g, "");
|
|
925
|
-
if (!
|
|
990
|
+
if (!relativeSpecifierPattern.test(cleaned)) return;
|
|
926
991
|
const baseDir = pathService.dirname(sourceFileName);
|
|
927
992
|
const resolved = normalizeFileName$1(pathService.resolve(baseDir, cleaned));
|
|
928
|
-
|
|
929
|
-
|
|
993
|
+
return getResolutionCandidates(resolved, pathService).find((candidate) => MutableHashMap.has(sourceFiles, candidate));
|
|
994
|
+
};
|
|
995
|
+
const readFileText = (fileName) => Option.liftPredicate(fs.fileSystem.readFile?.(fileName), isString);
|
|
996
|
+
const sourcesFieldOf = (rawMap) => {
|
|
997
|
+
if (!Predicate.hasProperty(rawMap, "sources")) return Option.none();
|
|
998
|
+
return Option.liftPredicate(rawMap.sources, Array.isArray);
|
|
930
999
|
};
|
|
1000
|
+
const onlySourceOf = (sources) => {
|
|
1001
|
+
const names = sources.filter(isString);
|
|
1002
|
+
if (names.length !== 1) return Option.none();
|
|
1003
|
+
return Option.fromUndefinedOr(names[0]);
|
|
1004
|
+
};
|
|
1005
|
+
const sourcePathFromMap = (declarationFileName, reference, pathService) => {
|
|
1006
|
+
const sourceMapFileName = normalizeFileName$1(pathService.resolve(pathService.dirname(declarationFileName), reference));
|
|
1007
|
+
return Option.flatMap(Option.flatMap(readFileText(sourceMapFileName), (content) => Option.flatMap(sourcesFieldOf(JSON.parse(content)), onlySourceOf)), (source) => Option.some(normalizeFileName$1(pathService.resolve(pathService.dirname(sourceMapFileName), source))));
|
|
1008
|
+
};
|
|
1009
|
+
const sourceMappedFileName = (declarationFileName, pathService) => Option.flatMap(Option.flatMap(readFileText(declarationFileName), (content) => Option.liftPredicate(getSourceMappingURL(content), isNonEmptyString)), (reference) => sourcePathFromMap(declarationFileName, reference, pathService));
|
|
931
1010
|
const resolveTSInputFile = (dependencyFileName, pathService) => {
|
|
932
1011
|
if (!dependencyFileName.endsWith(".d.ts")) return dependencyFileName;
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
if (
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
return
|
|
1012
|
+
return Option.getOrElse(sourceMappedFileName(dependencyFileName, pathService), () => dependencyFileName);
|
|
1013
|
+
};
|
|
1014
|
+
const registerGraphFile = (fileName, sourceFiles) => {
|
|
1015
|
+
if (ignoredGraphFileNamePattern.test(fileName)) return;
|
|
1016
|
+
const normalized = normalizeFileName$1(fileName);
|
|
1017
|
+
MutableHashMap.set(sourceFiles, normalized, {
|
|
1018
|
+
fileName: normalized,
|
|
1019
|
+
imports: MutableHashSet.empty()
|
|
1020
|
+
});
|
|
1021
|
+
};
|
|
1022
|
+
const registerSourceFiles = (programs, sourceFiles) => {
|
|
1023
|
+
for (const program of programs) program.getSourceFileNames().forEach((fileName) => registerGraphFile(fileName, sourceFiles));
|
|
1024
|
+
};
|
|
1025
|
+
const isUsableResolution = (resolved) => resolved !== void 0 && resolved !== "";
|
|
1026
|
+
const addImportEdge = (fileName, importedFileName, sourceFiles) => {
|
|
1027
|
+
if (!MutableHashMap.has(sourceFiles, importedFileName)) return;
|
|
1028
|
+
Option.match(MutableHashMap.get(sourceFiles, fileName), {
|
|
1029
|
+
onNone: () => void 0,
|
|
1030
|
+
onSome: (entry) => MutableHashSet.add(entry.imports, importedFileName)
|
|
1031
|
+
});
|
|
1032
|
+
};
|
|
1033
|
+
const linkImport = (fileName, specifier, sourceFiles) => {
|
|
1034
|
+
const resolved = resolveModuleSpecifier(fileName, specifier, sourceFiles, pathService);
|
|
1035
|
+
if (!isUsableResolution(resolved)) return;
|
|
1036
|
+
addImportEdge(fileName, resolveTSInputFile(resolved, pathService), sourceFiles);
|
|
1037
|
+
};
|
|
1038
|
+
const linkFileImports = (fileName, programs, sourceFiles) => {
|
|
1039
|
+
const sourceFile = programs.map((program) => program.getSourceFile(fileName)).find((candidate) => candidate != null);
|
|
1040
|
+
if (sourceFile === void 0) return;
|
|
1041
|
+
extractImports(sourceFile).forEach((specifier) => linkImport(fileName, specifier, sourceFiles));
|
|
949
1042
|
};
|
|
950
1043
|
const buildDependencyGraph = (programs) => Effect.gen(function* () {
|
|
951
|
-
const
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
const normalized = normalizeFileName$1(fileName);
|
|
955
|
-
MutableHashMap.set(s.sourceFiles, normalized, {
|
|
956
|
-
fileName: normalized,
|
|
957
|
-
imports: MutableHashSet.empty()
|
|
958
|
-
});
|
|
959
|
-
}
|
|
960
|
-
for (const [fileName] of s.sourceFiles) {
|
|
961
|
-
const sourceFile = programs.map((p) => p.getSourceFile(fileName)).find((sf) => sf != null);
|
|
962
|
-
if (!sourceFile) continue;
|
|
963
|
-
const imports = extractImports(sourceFile);
|
|
964
|
-
for (const specifier of imports) {
|
|
965
|
-
const resolved = resolveModuleSpecifier(fileName, specifier, s.sourceFiles, pathService);
|
|
966
|
-
if (resolved !== void 0 && resolved !== "") {
|
|
967
|
-
const sourceFileName = resolveTSInputFile(resolved, pathService);
|
|
968
|
-
if (MutableHashMap.has(s.sourceFiles, sourceFileName)) {
|
|
969
|
-
const entryOpt = MutableHashMap.get(s.sourceFiles, fileName);
|
|
970
|
-
if (Option.isSome(entryOpt)) MutableHashSet.add(entryOpt.value.imports, sourceFileName);
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
}
|
|
1044
|
+
const state = yield* Ref.get(stateRef);
|
|
1045
|
+
registerSourceFiles(programs, state.sourceFiles);
|
|
1046
|
+
for (const [fileName] of state.sourceFiles) linkFileImports(fileName, programs, state.sourceFiles);
|
|
975
1047
|
yield* Ref.update(stateRef, (prev) => ({
|
|
976
1048
|
...prev,
|
|
977
|
-
sourceFiles: MutableHashMap.fromIterable(
|
|
1049
|
+
sourceFiles: MutableHashMap.fromIterable(state.sourceFiles)
|
|
978
1050
|
}));
|
|
979
1051
|
});
|
|
980
|
-
const
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
});
|
|
1052
|
+
const createEmptyNodes = (state) => {
|
|
1053
|
+
for (const [fileName] of state.sourceFiles) MutableHashMap.set(state.nodes, fileName, makeTSFileNode(fileName));
|
|
1054
|
+
};
|
|
1055
|
+
const childNodeOf = (state, fileName, imports) => Effect.gen(function* () {
|
|
1056
|
+
const node = MutableHashMap.get(state.nodes, fileName);
|
|
1057
|
+
if (Option.isNone(node)) return yield* new CompilerFailed({
|
|
1058
|
+
reason: "unknown-file-node",
|
|
1059
|
+
subject: fileName
|
|
1060
|
+
});
|
|
1061
|
+
const children = Array.from(imports).map((importName) => Option.getOrUndefined(MutableHashMap.get(state.nodes, importName))).filter((child) => child !== void 0);
|
|
1062
|
+
return {
|
|
1063
|
+
...node.value,
|
|
1064
|
+
children,
|
|
1065
|
+
parents: []
|
|
1066
|
+
};
|
|
1067
|
+
});
|
|
1068
|
+
const collectChildNodes = (state, withChildren) => Effect.gen(function* () {
|
|
1069
|
+
for (const [fileName, file] of state.sourceFiles) {
|
|
1070
|
+
const node = yield* childNodeOf(state, fileName, file.imports);
|
|
1071
|
+
MutableHashMap.set(withChildren, fileName, node);
|
|
1001
1072
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1073
|
+
});
|
|
1074
|
+
const replaceMapContents = (target, source) => {
|
|
1075
|
+
MutableHashMap.clear(target);
|
|
1076
|
+
for (const [key, value] of source) MutableHashMap.set(target, key, value);
|
|
1077
|
+
};
|
|
1078
|
+
const parentNodesOf = (node, nodes) => {
|
|
1079
|
+
const parents = [];
|
|
1080
|
+
MutableHashMap.forEach(nodes, (candidate) => {
|
|
1081
|
+
if (candidate.children.includes(node)) parents.push(candidate);
|
|
1082
|
+
});
|
|
1083
|
+
return parents;
|
|
1084
|
+
};
|
|
1085
|
+
const linkParentReferences = (state) => {
|
|
1004
1086
|
const withParents = MutableHashMap.empty();
|
|
1005
|
-
for (const [fileName, node] of
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
MutableHashMap.
|
|
1014
|
-
|
|
1087
|
+
for (const [fileName, node] of state.nodes) MutableHashMap.set(withParents, fileName, {
|
|
1088
|
+
...node,
|
|
1089
|
+
parents: parentNodesOf(node, state.nodes)
|
|
1090
|
+
});
|
|
1091
|
+
replaceMapContents(state.nodes, withParents);
|
|
1092
|
+
};
|
|
1093
|
+
const buildFileNodes = (state) => Effect.gen(function* () {
|
|
1094
|
+
createEmptyNodes(state);
|
|
1095
|
+
const withChildren = MutableHashMap.empty();
|
|
1096
|
+
yield* collectChildNodes(state, withChildren);
|
|
1097
|
+
replaceMapContents(state.nodes, withChildren);
|
|
1098
|
+
linkParentReferences(state);
|
|
1015
1099
|
yield* Ref.update(stateRef, (prev) => ({
|
|
1016
1100
|
...prev,
|
|
1017
|
-
nodes: MutableHashMap.fromIterable(
|
|
1101
|
+
nodes: MutableHashMap.fromIterable(state.nodes)
|
|
1102
|
+
}));
|
|
1103
|
+
});
|
|
1104
|
+
const getNodesEffect = Effect.gen(function* () {
|
|
1105
|
+
const state = yield* Ref.get(stateRef);
|
|
1106
|
+
if (MutableHashMap.size(state.nodes) > 0) return state.nodes;
|
|
1107
|
+
yield* buildFileNodes(state);
|
|
1108
|
+
return state.nodes;
|
|
1109
|
+
});
|
|
1110
|
+
const resetMutatedFiles = (mutants) => Effect.gen(function* () {
|
|
1111
|
+
for (const mutant of mutants) yield* fs.resetFile(mutant.fileName);
|
|
1112
|
+
});
|
|
1113
|
+
const applyMutant = (mutant) => Effect.gen(function* () {
|
|
1114
|
+
if ((yield* fs.getFile(mutant.fileName)) === void 0) return yield* new CompilerFailed({
|
|
1115
|
+
reason: "file-not-in-project",
|
|
1116
|
+
subject: mutant.fileName
|
|
1117
|
+
});
|
|
1118
|
+
yield* fs.mutateFile(mutant.fileName, mutant);
|
|
1119
|
+
});
|
|
1120
|
+
const applyMutants = (mutants) => Effect.gen(function* () {
|
|
1121
|
+
for (const mutant of mutants) yield* applyMutant(mutant);
|
|
1122
|
+
});
|
|
1123
|
+
const hasOpenSnapshot = (state) => state.api !== void 0 && state.snapshot !== void 0;
|
|
1124
|
+
const updateSnapshot = (state, changedFiles) => Effect.gen(function* () {
|
|
1125
|
+
const previous = state.snapshot;
|
|
1126
|
+
const next = state.api.updateSnapshot({
|
|
1127
|
+
openProjects: Array.from(state.allTSConfigFiles),
|
|
1128
|
+
fileChanges: { changed: changedFiles }
|
|
1129
|
+
});
|
|
1130
|
+
yield* Effect.sync(() => previous.dispose());
|
|
1131
|
+
yield* Ref.update(stateRef, (prev) => ({
|
|
1132
|
+
...prev,
|
|
1133
|
+
snapshot: next
|
|
1018
1134
|
}));
|
|
1019
|
-
return s.nodes;
|
|
1020
1135
|
});
|
|
1021
1136
|
const check = (mutants) => Effect.gen(function* () {
|
|
1022
1137
|
const state = yield* Ref.get(stateRef);
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
reason: "file-not-in-project",
|
|
1027
|
-
subject: mutant.fileName
|
|
1028
|
-
});
|
|
1029
|
-
yield* fs.mutateFile(mutant.fileName, mutant);
|
|
1030
|
-
}
|
|
1031
|
-
const mutatedFileNames = Array.from(MutableHashSet.fromIterable(mutants.map((m) => normalizeFileName$1(m.fileName))));
|
|
1138
|
+
yield* resetMutatedFiles(state.lastMutants);
|
|
1139
|
+
yield* applyMutants(mutants);
|
|
1140
|
+
const mutatedFileNames = Array.from(MutableHashSet.fromIterable(mutants.map((mutant) => normalizeFileName$1(mutant.fileName))));
|
|
1032
1141
|
const changedFiles = Array.from(MutableHashSet.fromIterable([...state.lastMutatedFileNames, ...mutatedFileNames]));
|
|
1033
1142
|
const current = yield* Ref.get(stateRef);
|
|
1034
|
-
if (current
|
|
1035
|
-
const oldSnapshot = current.snapshot;
|
|
1036
|
-
const nextSnapshot = current.api.updateSnapshot({
|
|
1037
|
-
openProjects: Array.from(current.allTSConfigFiles),
|
|
1038
|
-
fileChanges: { changed: changedFiles }
|
|
1039
|
-
});
|
|
1040
|
-
yield* Effect.sync(() => oldSnapshot.dispose());
|
|
1041
|
-
yield* Ref.update(stateRef, (prev) => ({
|
|
1042
|
-
...prev,
|
|
1043
|
-
snapshot: nextSnapshot
|
|
1044
|
-
}));
|
|
1045
|
-
}
|
|
1143
|
+
if (hasOpenSnapshot(current)) yield* updateSnapshot(current, changedFiles);
|
|
1046
1144
|
yield* Ref.update(stateRef, (prev) => ({
|
|
1047
1145
|
...prev,
|
|
1048
1146
|
lastMutants: [...mutants],
|
|
@@ -1088,11 +1186,7 @@ function makeTypescriptCompiler(options, fs, fsService, pathService) {
|
|
|
1088
1186
|
}));
|
|
1089
1187
|
});
|
|
1090
1188
|
const getLineAndCharacterOfPosition = (fileName, position) => Effect.gen(function* () {
|
|
1091
|
-
|
|
1092
|
-
for (const program of programs) {
|
|
1093
|
-
const sourceFile = program.getSourceFile(fileName);
|
|
1094
|
-
if (sourceFile) return sourceFile.getLineAndCharacterOfPosition(position);
|
|
1095
|
-
}
|
|
1189
|
+
return (yield* getProgramsEffect()).map((program) => program.getSourceFile(fileName)).find((sourceFile) => sourceFile !== void 0)?.getLineAndCharacterOfPosition(position);
|
|
1096
1190
|
});
|
|
1097
1191
|
return {
|
|
1098
1192
|
init,
|
|
@@ -1103,141 +1197,74 @@ function makeTypescriptCompiler(options, fs, fsService, pathService) {
|
|
|
1103
1197
|
};
|
|
1104
1198
|
}
|
|
1105
1199
|
//#endregion
|
|
1200
|
+
//#region src/mutant-groups.ts
|
|
1201
|
+
const groupsWithStrangers = (inside, outside, nodes) => {
|
|
1202
|
+
const groups = createGroups([...inside], nodes);
|
|
1203
|
+
if (outside.length > 0) return [outside.map((mutant) => mutant.id), ...groups];
|
|
1204
|
+
return groups;
|
|
1205
|
+
};
|
|
1206
|
+
const knownFileGroups = (mutants, nodes) => {
|
|
1207
|
+
const inside = mutants.filter((mutant) => Option.isSome(MutableHashMap.get(nodes, normalizeFileName(mutant.fileName))));
|
|
1208
|
+
const outside = mutants.filter((mutant) => Option.isNone(MutableHashMap.get(nodes, normalizeFileName(mutant.fileName))));
|
|
1209
|
+
if (inside.length === 0) return mutants.map((mutant) => [mutant.id]);
|
|
1210
|
+
return groupsWithStrangers(inside, outside, nodes);
|
|
1211
|
+
};
|
|
1212
|
+
const groupMutants = (mutants, nodes, prioritizePerformanceOverAccuracy) => {
|
|
1213
|
+
if (prioritizePerformanceOverAccuracy) return knownFileGroups(mutants, nodes);
|
|
1214
|
+
return [mutants.map((mutant) => mutant.id)];
|
|
1215
|
+
};
|
|
1216
|
+
//#endregion
|
|
1106
1217
|
//#region src/Checker.ts
|
|
1107
|
-
const normalizeFileName = (fileName) => fileName.replace(/\\/g, "/");
|
|
1108
|
-
function partitionMutantsForGrouping(mutants, nodes, prioritizePerformanceOverAccuracy) {
|
|
1109
|
-
if (!prioritizePerformanceOverAccuracy) return {
|
|
1110
|
-
inside: [],
|
|
1111
|
-
outside: [...mutants]
|
|
1112
|
-
};
|
|
1113
|
-
const outside = [];
|
|
1114
|
-
const inside = [];
|
|
1115
|
-
for (const m of mutants) if (Option.isNone(MutableHashMap.get(nodes, normalizeFileName(m.fileName)))) outside.push(m);
|
|
1116
|
-
else inside.push(m);
|
|
1117
|
-
return {
|
|
1118
|
-
inside,
|
|
1119
|
-
outside
|
|
1120
|
-
};
|
|
1121
|
-
}
|
|
1122
1218
|
function getPrioritize(options) {
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
if (typeof tc !== "object" || tc === null) return false;
|
|
1126
|
-
if (!Predicate.hasProperty(tc, "prioritizePerformanceOverAccuracy")) return false;
|
|
1127
|
-
const val = tc["prioritizePerformanceOverAccuracy"];
|
|
1128
|
-
if (typeof val === "boolean") return val;
|
|
1129
|
-
return false;
|
|
1219
|
+
const decoded = Schema.decodeUnknownOption(TypescriptCheckerOptionsSchema)(options);
|
|
1220
|
+
return Option.getOrElse(Option.flatMap(decoded, (value) => Option.fromUndefinedOr(value.typescriptChecker?.prioritizePerformanceOverAccuracy)), () => false);
|
|
1130
1221
|
}
|
|
1222
|
+
const refuse = (mutantIds, cause) => new CheckerFailed({
|
|
1223
|
+
checkerName: "typescript",
|
|
1224
|
+
mutantIds: [...mutantIds],
|
|
1225
|
+
cause: errorToString(cause)
|
|
1226
|
+
});
|
|
1227
|
+
const severityOf = (category) => Match.value(category).pipe(Match.when(DiagnosticCategory.Error, () => "error"), Match.when(DiagnosticCategory.Warning, () => "warning"), Match.when(DiagnosticCategory.Suggestion, () => "suggestion"), Match.orElse(() => "message"));
|
|
1228
|
+
const toCheckResult = (answer) => {
|
|
1229
|
+
if (answer.status === "passed") return { status: "passed" };
|
|
1230
|
+
return {
|
|
1231
|
+
status: "compileError",
|
|
1232
|
+
reason: answer.reason
|
|
1233
|
+
};
|
|
1234
|
+
};
|
|
1235
|
+
const mergeAnswers = (runs) => runs.reduce((merged, answers) => Object.entries(answers).reduce((into, [id, answer]) => HashMap.set(into, id, toCheckResult(answer)), merged), HashMap.empty());
|
|
1131
1236
|
const checkCell = Cell.layer({
|
|
1132
|
-
read: (command) => Effect.
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
cause: errorToString(cause)
|
|
1138
|
-
})));
|
|
1139
|
-
const nodes = {};
|
|
1140
|
-
for (const [k, v] of nodesHm) nodes[k] = v;
|
|
1141
|
-
const diagnostics = yield* compiler.check([...command.mutants]).pipe(Effect.mapError((cause) => new CheckerFailed({
|
|
1142
|
-
checkerName: "typescript",
|
|
1143
|
-
mutantIds: command.mutants.map((m) => m.id),
|
|
1144
|
-
cause: errorToString(cause)
|
|
1145
|
-
})));
|
|
1146
|
-
return new CheckMutantsInput({
|
|
1147
|
-
mutants: [...command.mutants],
|
|
1148
|
-
diagnostics: [...diagnostics],
|
|
1149
|
-
nodes
|
|
1150
|
-
});
|
|
1151
|
-
}),
|
|
1152
|
-
decode: (raw) => Result.succeed(raw),
|
|
1237
|
+
read: (command) => Effect.flatMap(TypeScriptCompiler, (compiler) => Effect.zipWith(compiler.nodes, compiler.check([...command.mutants]), (nodes, diagnostics) => new CheckMutantsInput({
|
|
1238
|
+
mutants: [...command.mutants],
|
|
1239
|
+
diagnostics: [...diagnostics],
|
|
1240
|
+
nodes: Object.fromEntries(nodes)
|
|
1241
|
+
}))).pipe(Effect.mapError((cause) => refuse(command.mutants.map((mutant) => mutant.id), cause))),
|
|
1153
1242
|
decide: checkMutants,
|
|
1154
|
-
encode: (outcome) => outcome,
|
|
1155
1243
|
write: (outcome) => Result.match(outcome, {
|
|
1156
|
-
onFailure: (failure) => Effect.fail(
|
|
1157
|
-
|
|
1158
|
-
mutantIds: [],
|
|
1159
|
-
cause: errorToString(failure)
|
|
1160
|
-
})),
|
|
1161
|
-
onSuccess: (decision) => Effect.succeed(decision)
|
|
1244
|
+
onFailure: (failure) => Effect.fail(refuse([], failure)),
|
|
1245
|
+
onSuccess: Effect.succeed
|
|
1162
1246
|
})
|
|
1163
1247
|
});
|
|
1164
1248
|
function makeCheckerService({ options, compiler }) {
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
const unknownError = error;
|
|
1173
|
-
if (typeof unknownError === "object" && unknownError !== null && "fileName" in unknownError && typeof unknownError.fileName === "string") {
|
|
1174
|
-
const fileName = unknownError.fileName;
|
|
1175
|
-
const lineAndCharacter = yield* compiler.getLineAndCharacterOfPosition(fileName, error.pos).pipe(Effect.orElseSucceed(() => void 0));
|
|
1176
|
-
location = `${fileName}(${(lineAndCharacter?.line ?? 0) + 1},${(lineAndCharacter?.character ?? 0) + 1}): `;
|
|
1177
|
-
} else if (error.fileName !== void 0 && error.fileName !== "") {
|
|
1178
|
-
const lineAndCharacter = yield* compiler.getLineAndCharacterOfPosition(error.fileName, error.pos).pipe(Effect.orElseSucceed(() => void 0));
|
|
1179
|
-
const line = (lineAndCharacter?.line ?? 0) + 1;
|
|
1180
|
-
const character = (lineAndCharacter?.character ?? 0) + 1;
|
|
1181
|
-
location = `${error.fileName}(${line},${character}): `;
|
|
1182
|
-
}
|
|
1183
|
-
return `${location}${severity} TS${error.code}: ${error.text}`;
|
|
1184
|
-
});
|
|
1185
|
-
const createErrorText = (errors) => Effect.gen(function* () {
|
|
1186
|
-
return (yield* Effect.forEach(errors, formatDiagnostic)).join("\n");
|
|
1249
|
+
const verify = Cell.provide(checkCell, Layer.succeed(TypeScriptCompiler, compiler));
|
|
1250
|
+
const positionOf = (error) => Option.match(Option.filter(Option.fromUndefinedOr(error.fileName), (fileName) => fileName !== ""), {
|
|
1251
|
+
onNone: () => Effect.succeed(""),
|
|
1252
|
+
onSome: (fileName) => compiler.getLineAndCharacterOfPosition(fileName, error.pos).pipe(Effect.orElseSucceed(() => void 0), Effect.map((at) => Option.match(Option.fromUndefinedOr(at), {
|
|
1253
|
+
onNone: () => `${fileName}(1,1): `,
|
|
1254
|
+
onSome: (position) => `${fileName}(${position.line + 1},${position.character + 1}): `
|
|
1255
|
+
})))
|
|
1187
1256
|
});
|
|
1257
|
+
const formatDiagnostic = (error) => positionOf(error).pipe(Effect.map((position) => `${position}${severityOf(error.category)} TS${error.code}: ${error.text}`));
|
|
1258
|
+
const createErrorText = (errors) => Effect.map(Effect.forEach(errors, formatDiagnostic), (parts) => parts.join("\n"));
|
|
1259
|
+
const soloRound = (mutant) => Cell.run(verify, new CheckMutantsCommand({ mutants: [mutant] })).pipe(Effect.map((decision) => decision.results));
|
|
1260
|
+
const soloRounds = (decision) => Match.value(decision).pipe(Match.tag("CheckFinished", () => Effect.succeed([])), Match.tag("RetestRequired", (retest) => Cell.run(verify, new CheckMutantsCommand({ mutants: [] })).pipe(Effect.flatMap(() => Effect.forEach(retest.needsRetest, soloRound)))), Match.exhaustive);
|
|
1188
1261
|
return {
|
|
1189
|
-
init: Effect.
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
if (errors.length > 0) {
|
|
1196
|
-
const text = yield* createErrorText(errors);
|
|
1197
|
-
return yield* new CheckerFailed({
|
|
1198
|
-
checkerName: "typescript",
|
|
1199
|
-
mutantIds: [],
|
|
1200
|
-
cause: errorToString(/* @__PURE__ */ new Error(`Typescript error(s) found in dry run compilation: ${text}`))
|
|
1201
|
-
});
|
|
1202
|
-
}
|
|
1203
|
-
}),
|
|
1204
|
-
check: (mutants) => Effect.gen(function* () {
|
|
1205
|
-
const applyOnce = (group) => Cell.run(checkCell, new CheckMutantsCommand({ mutants: [...group] })).pipe(Effect.provideService(TypeScriptCompiler, compiler));
|
|
1206
|
-
const first = yield* applyOnce(mutants);
|
|
1207
|
-
let map = HashMap.empty();
|
|
1208
|
-
const mergeResults = (results) => {
|
|
1209
|
-
for (const [id, value] of Object.entries(results)) if (value.status === "passed") map = HashMap.set(map, id, { status: "passed" });
|
|
1210
|
-
else map = HashMap.set(map, id, {
|
|
1211
|
-
status: "compileError",
|
|
1212
|
-
reason: value.reason
|
|
1213
|
-
});
|
|
1214
|
-
};
|
|
1215
|
-
mergeResults(first.results);
|
|
1216
|
-
yield* Match.value(first).pipe(Match.tag("CheckFinished", () => Effect.void), Match.tag("RetestRequired", (retest) => Effect.gen(function* () {
|
|
1217
|
-
yield* applyOnce([]);
|
|
1218
|
-
const originals = {};
|
|
1219
|
-
for (const m of mutants) originals[m.id] = m;
|
|
1220
|
-
for (const pending of retest.needsRetest) {
|
|
1221
|
-
const original = originals[pending.id];
|
|
1222
|
-
if (original === void 0) continue;
|
|
1223
|
-
const one = yield* applyOnce([original]);
|
|
1224
|
-
mergeResults(one.results);
|
|
1225
|
-
}
|
|
1226
|
-
})), Match.exhaustive);
|
|
1227
|
-
return map;
|
|
1228
|
-
}),
|
|
1229
|
-
group: (mutants) => Effect.gen(function* () {
|
|
1230
|
-
const nodes = yield* compiler.nodes.pipe(Effect.mapError((cause) => new CheckerFailed({
|
|
1231
|
-
checkerName: "typescript",
|
|
1232
|
-
mutantIds: mutants.map((m) => m.id),
|
|
1233
|
-
cause: errorToString(cause)
|
|
1234
|
-
})));
|
|
1235
|
-
const { inside, outside } = partitionMutantsForGrouping(mutants, nodes, getPrioritize(options));
|
|
1236
|
-
if (inside.length === 0) return mutants.map((m) => [m.id]);
|
|
1237
|
-
const groups = createGroups([...inside], nodes);
|
|
1238
|
-
if (outside.length > 0) return [outside.map((m) => m.id), ...groups];
|
|
1239
|
-
return groups;
|
|
1240
|
-
})
|
|
1262
|
+
init: compiler.init.pipe(Effect.mapError((cause) => refuse([], cause)), Effect.flatMap((errors) => {
|
|
1263
|
+
if (errors.length === 0) return Effect.void;
|
|
1264
|
+
return createErrorText(errors).pipe(Effect.map((text) => refuse([], /* @__PURE__ */ new Error(`Typescript error(s) found in dry run compilation: ${text}`))), Effect.flatMap(Effect.fail));
|
|
1265
|
+
})),
|
|
1266
|
+
check: (mutants) => Cell.run(verify, new CheckMutantsCommand({ mutants: [...mutants] })).pipe(Effect.flatMap((first) => Effect.map(soloRounds(first), (rounds) => mergeAnswers([first.results, ...rounds])))),
|
|
1267
|
+
group: (mutants) => compiler.nodes.pipe(Effect.map((nodes) => groupMutants(mutants, nodes, getPrioritize(options))), Effect.mapError((cause) => refuse(mutants.map((mutant) => mutant.id), cause)))
|
|
1241
1268
|
};
|
|
1242
1269
|
}
|
|
1243
1270
|
//#endregion
|