@systemfsoftware/stryker-js-typescript-checker 1.2.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/LICENSE +203 -21
- package/README.md +31 -0
- package/dist/index.d.mts +19 -19
- package/dist/index.mjs +131 -119
- package/package.json +22 -9
- package/.turbo/turbo-build.log +0 -21
- package/AGENTS.md +0 -7
- package/oxlint.config.ts +0 -9
- package/src/fs/hybrid-file-system.ts +0 -118
- package/src/fs/index.ts +0 -2
- package/src/fs/script-file.ts +0 -44
- package/src/grouping/create-groups.ts +0 -77
- package/src/grouping/ts-file-node.ts +0 -54
- package/src/index.ts +0 -18
- package/src/plugin-tokens.ts +0 -2
- package/src/tsconfig-helpers.ts +0 -187
- package/src/typescript-checker-options-with-stryker-options.ts +0 -9
- package/src/typescript-checker.ts +0 -223
- package/src/typescript-compiler.ts +0 -412
- package/test/integration/e2e-plugin-entry.it.spec.ts +0 -153
- package/test/integration/nodenext-project.it.spec.ts +0 -101
- package/test/integration/project-references.it.spec.ts +0 -146
- package/test/integration/project-with-ts-buildinfo.it.spec.ts +0 -92
- package/test/integration/single-project.it.spec.ts +0 -298
- package/test/integration/tsconfig-helpers.it.spec.ts +0 -218
- package/test/integration/typescript-checkers-errors.it.spec.ts +0 -112
- package/test/unit/fs/hybrid-file-system.spec.ts +0 -109
- package/test/unit/grouping/create-groups.spec.ts +0 -122
- package/test/unit/grouping/ts-file-node.spec.ts +0 -132
- package/testResources/errors/compile-error/add.ts +0 -3
- package/testResources/errors/compile-error/tsconfig.json +0 -5
- package/testResources/errors/empty-dir/.gitkeep +0 -0
- package/testResources/errors/invalid-tsconfig/tsconfig.json +0 -1
- package/testResources/nodenext-project/package.json +0 -6
- package/testResources/nodenext-project/src/index.ts +0 -5
- package/testResources/nodenext-project/src/util.ts +0 -3
- package/testResources/nodenext-project/tsconfig.json +0 -16
- package/testResources/project-references/src/index.ts +0 -5
- package/testResources/project-references/src/job.ts +0 -6
- package/testResources/project-references/src/tsconfig.json +0 -10
- package/testResources/project-references/tsconfig.root.json +0 -7
- package/testResources/project-references/tsconfig.settings.json +0 -15
- package/testResources/project-references/utils/math.ts +0 -3
- package/testResources/project-references/utils/text.ts +0 -3
- package/testResources/project-references/utils/tsconfig.json +0 -7
- package/testResources/project-with-ts-buildinfo/src/index.ts +0 -3
- package/testResources/project-with-ts-buildinfo/tsconfig.json +0 -15
- package/testResources/single-project/src/counter.ts +0 -9
- package/testResources/single-project/src/errorInFileAbove2Mutants/counter.ts +0 -9
- package/testResources/single-project/src/errorInFileAbove2Mutants/todo-counter.ts +0 -7
- package/testResources/single-project/src/errorInFileAbove2Mutants/todo.spec.ts +0 -11
- package/testResources/single-project/src/errorInFileAbove2Mutants/todo.ts +0 -22
- package/testResources/single-project/src/not-type-checked.js +0 -1
- package/testResources/single-project/src/todo.spec.ts +0 -11
- package/testResources/single-project/src/todo.ts +0 -22
- package/testResources/single-project/tsconfig.json +0 -15
- package/tsconfig.json +0 -26
- package/tsdown.config.ts +0 -16
- package/vitest.config.ts +0 -13
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
|
-
import { PluginKind, Scope, commonTokens, declareFactoryPlugin, tokens } from "@stryker-
|
|
2
|
+
import { PluginKind, Scope, commonTokens, declareFactoryPlugin, tokens } from "@systemfsoftware/stryker-js-plugin-api/plugin";
|
|
3
|
+
import { Result, Schema } from "effect";
|
|
3
4
|
import { EOL } from "os";
|
|
4
|
-
import { CheckStatus } from "@stryker-mutator/api/check";
|
|
5
5
|
import { split, strykerReportBugUrl } from "@stryker-mutator/util";
|
|
6
|
+
import { CheckStatus } from "@systemfsoftware/stryker-js-plugin-api/check";
|
|
6
7
|
import { API, DiagnosticCategory } from "typescript/unstable/sync";
|
|
7
8
|
import { createRequire } from "module";
|
|
8
9
|
import path from "path";
|
|
9
|
-
import { Data, Either, Schema } from "effect";
|
|
10
10
|
import semver from "semver";
|
|
11
11
|
import { SyntaxKind } from "typescript/unstable/ast";
|
|
12
12
|
//#region ../../../node_modules/.pnpm/@jsr+std__jsonc@1.0.2/node_modules/@jsr/std__jsonc/parse.js
|
|
@@ -224,13 +224,26 @@ function buildErrorMessage({ type, sourceText, position }) {
|
|
|
224
224
|
token = ",";
|
|
225
225
|
break;
|
|
226
226
|
case "NullOrTrueOrFalseOrNumber":
|
|
227
|
-
case "String":
|
|
228
|
-
token = 30 < sourceText.length ? `${sourceText.slice(0, 30)}...` : sourceText;
|
|
229
|
-
break;
|
|
227
|
+
case "String": token = 30 < sourceText.length ? `${sourceText.slice(0, 30)}...` : sourceText;
|
|
230
228
|
}
|
|
231
229
|
return `Cannot parse JSONC: unexpected token "${token}" in JSONC at position ${position}`;
|
|
232
230
|
}
|
|
233
231
|
//#endregion
|
|
232
|
+
//#region src/tsconfig-helpers.schema.ts
|
|
233
|
+
/**
|
|
234
|
+
* Error returned when a tsconfig file fails to parse or does not match the
|
|
235
|
+
* shape this package consumes.
|
|
236
|
+
*/
|
|
237
|
+
var TsConfigParseError = class extends Schema.TaggedError()("TsConfigParseError", {
|
|
238
|
+
file: Schema.String,
|
|
239
|
+
reason: Schema.String
|
|
240
|
+
}) {};
|
|
241
|
+
const JsonRecord = Schema.Record(Schema.String, Schema.Unknown);
|
|
242
|
+
const TsConfigSchema = Schema.StructWithRest(Schema.Struct({
|
|
243
|
+
references: Schema.optional(Schema.Array(Schema.StructWithRest(Schema.Struct({ path: Schema.String }), [JsonRecord]))),
|
|
244
|
+
compilerOptions: Schema.optional(JsonRecord)
|
|
245
|
+
}), [JsonRecord]);
|
|
246
|
+
//#endregion
|
|
234
247
|
//#region src/tsconfig-helpers.ts
|
|
235
248
|
const COMPILER_OPTIONS_OVERRIDES = Object.freeze({
|
|
236
249
|
allowUnreachableCode: true,
|
|
@@ -253,7 +266,7 @@ let cachedTSVersion;
|
|
|
253
266
|
function getTSVersion() {
|
|
254
267
|
if (cachedTSVersion === void 0) {
|
|
255
268
|
const require = createRequire(import.meta.url);
|
|
256
|
-
cachedTSVersion = JSON.parse(readFileSync(require.resolve("typescript/package.json"), "utf-8")).version;
|
|
269
|
+
cachedTSVersion = Schema.decodeUnknownSync(Schema.Struct({ version: Schema.String }))(JSON.parse(readFileSync(require.resolve("typescript/package.json"), "utf-8"))).version;
|
|
257
270
|
}
|
|
258
271
|
return cachedTSVersion;
|
|
259
272
|
}
|
|
@@ -261,18 +274,6 @@ function guardTSVersion(version = getTSVersion()) {
|
|
|
261
274
|
if (!semver.satisfies(version, ">=7.0.0", { includePrerelease: true })) throw new Error(`@systemfsoftware/stryker-js-typescript-checker only supports typescript@7.0.0 or higher. Found typescript@${version}`);
|
|
262
275
|
}
|
|
263
276
|
/**
|
|
264
|
-
* Error returned when a tsconfig file fails to parse or does not match the shape this package consumes.
|
|
265
|
-
*/
|
|
266
|
-
var TsConfigParseError = class extends Data.TaggedError("TsConfigParseError") {};
|
|
267
|
-
const JsonRecord = Schema.Record({
|
|
268
|
-
key: Schema.String,
|
|
269
|
-
value: Schema.Unknown
|
|
270
|
-
});
|
|
271
|
-
const TsConfigSchema = Schema.Struct({
|
|
272
|
-
references: Schema.optional(Schema.Array(Schema.Struct({ path: Schema.String }, JsonRecord))),
|
|
273
|
-
compilerOptions: Schema.optional(JsonRecord)
|
|
274
|
-
}, JsonRecord);
|
|
275
|
-
/**
|
|
276
277
|
* Parses the raw text of a tsconfig file into a typed config, rejecting shapes this package cannot consume.
|
|
277
278
|
* @param fileName The tsconfig file name, used for error reporting
|
|
278
279
|
* @param jsonText The raw tsconfig content
|
|
@@ -280,14 +281,14 @@ const TsConfigSchema = Schema.Struct({
|
|
|
280
281
|
function parseTsConfig(fileName, jsonText) {
|
|
281
282
|
try {
|
|
282
283
|
const value = parse(jsonText.replace(/^\uFEFF/, ""));
|
|
283
|
-
return
|
|
284
|
+
return Result.mapError(Schema.decodeUnknownResult(TsConfigSchema)(value), (error) => new TsConfigParseError({
|
|
284
285
|
file: fileName,
|
|
285
|
-
reason:
|
|
286
|
+
reason: error.message
|
|
286
287
|
}));
|
|
287
288
|
} catch (error) {
|
|
288
|
-
return
|
|
289
|
+
return Result.fail(new TsConfigParseError({
|
|
289
290
|
file: fileName,
|
|
290
|
-
reason: error instanceof Error ? error.message :
|
|
291
|
+
reason: error instanceof Error ? error.message : typeof error === "string" ? error : JSON.stringify(error) ?? "a non-Error value was thrown"
|
|
291
292
|
}));
|
|
292
293
|
}
|
|
293
294
|
}
|
|
@@ -297,9 +298,9 @@ function parseTsConfig(fileName, jsonText) {
|
|
|
297
298
|
*/
|
|
298
299
|
function determineBuildModeEnabled(tsconfigFileName) {
|
|
299
300
|
const parsed = parseTsConfig(tsconfigFileName, readFileSync(tsconfigFileName, "utf-8"));
|
|
300
|
-
return
|
|
301
|
-
|
|
302
|
-
|
|
301
|
+
return Result.match(parsed, {
|
|
302
|
+
onFailure: () => false,
|
|
303
|
+
onSuccess: (config) => config.references !== void 0
|
|
303
304
|
});
|
|
304
305
|
}
|
|
305
306
|
/**
|
|
@@ -357,7 +358,84 @@ function getSourceMappingURL(content) {
|
|
|
357
358
|
return findSourceMapRegex.exec(content)?.[1];
|
|
358
359
|
}
|
|
359
360
|
//#endregion
|
|
360
|
-
//#region src/
|
|
361
|
+
//#region src/grouping/ts-file-node.ts
|
|
362
|
+
var TSFileNode = class {
|
|
363
|
+
fileName;
|
|
364
|
+
parents;
|
|
365
|
+
children;
|
|
366
|
+
constructor(fileName, parents, children) {
|
|
367
|
+
this.fileName = fileName;
|
|
368
|
+
this.parents = parents;
|
|
369
|
+
this.children = children;
|
|
370
|
+
}
|
|
371
|
+
getAllParentReferencesIncludingSelf(allParentReferences = /* @__PURE__ */ new Set()) {
|
|
372
|
+
allParentReferences.add(this);
|
|
373
|
+
this.parents.forEach((parent) => {
|
|
374
|
+
if (!allParentReferences.has(parent)) parent.getAllParentReferencesIncludingSelf(allParentReferences);
|
|
375
|
+
});
|
|
376
|
+
return allParentReferences;
|
|
377
|
+
}
|
|
378
|
+
getAllChildReferencesIncludingSelf(allChildReferences = /* @__PURE__ */ new Set()) {
|
|
379
|
+
allChildReferences.add(this);
|
|
380
|
+
this.children.forEach((child) => {
|
|
381
|
+
if (!allChildReferences.has(child)) child.getAllChildReferencesIncludingSelf(allChildReferences);
|
|
382
|
+
});
|
|
383
|
+
return allChildReferences;
|
|
384
|
+
}
|
|
385
|
+
getMutantsWithReferenceToChildrenOrSelf(mutants, nodesChecked = []) {
|
|
386
|
+
if (nodesChecked.includes(this.fileName)) return [];
|
|
387
|
+
nodesChecked.push(this.fileName);
|
|
388
|
+
const relatedMutants = mutants.filter((m) => toPosixFileName(m.fileName) === this.fileName);
|
|
389
|
+
const childResult = this.children.flatMap((c) => c.getMutantsWithReferenceToChildrenOrSelf(mutants, nodesChecked));
|
|
390
|
+
return [...relatedMutants, ...childResult];
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
//#endregion
|
|
394
|
+
//#region src/grouping/create-groups.ts
|
|
395
|
+
/**
|
|
396
|
+
* To speed up the type-checking we want to check multiple mutants at once.
|
|
397
|
+
* When multiple mutants in different files don't have overlap in affected files (or have small overlap), we can type-check them simultaneously.
|
|
398
|
+
* These mutants who can be tested at the same time are called a group.
|
|
399
|
+
* Therefore, the return type is an array of arrays, in other words: an array of groups.
|
|
400
|
+
*
|
|
401
|
+
* @param mutants All the mutants of the test project.
|
|
402
|
+
* @param nodes A graph representation of the test project.
|
|
403
|
+
*/
|
|
404
|
+
function createGroups(mutants, nodes) {
|
|
405
|
+
const groups = [];
|
|
406
|
+
const mutantsToGroup = new Set(mutants);
|
|
407
|
+
while (mutantsToGroup.size) {
|
|
408
|
+
const group = [];
|
|
409
|
+
const groupNodes = /* @__PURE__ */ new Set();
|
|
410
|
+
const nodesToIgnore = /* @__PURE__ */ new Set();
|
|
411
|
+
for (const currentMutant of mutantsToGroup) {
|
|
412
|
+
const currentNode = findNode(currentMutant.fileName, nodes);
|
|
413
|
+
if (!nodesToIgnore.has(currentNode) && !parentsHaveOverlapWith(currentNode, groupNodes)) {
|
|
414
|
+
group.push(currentMutant.id);
|
|
415
|
+
groupNodes.add(currentNode);
|
|
416
|
+
mutantsToGroup.delete(currentMutant);
|
|
417
|
+
addRangeOfNodesToSet(nodesToIgnore, currentNode.getAllParentReferencesIncludingSelf());
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
groups.push(group);
|
|
421
|
+
}
|
|
422
|
+
return groups;
|
|
423
|
+
}
|
|
424
|
+
function addRangeOfNodesToSet(nodes, nodesToAdd) {
|
|
425
|
+
for (const parent of nodesToAdd) nodes.add(parent);
|
|
426
|
+
}
|
|
427
|
+
function findNode(fileName, nodes) {
|
|
428
|
+
const node = nodes.get(toPosixFileName(fileName));
|
|
429
|
+
if (node == null) throw new Error(`Node not in graph: ${fileName}`);
|
|
430
|
+
return node;
|
|
431
|
+
}
|
|
432
|
+
function parentsHaveOverlapWith(currentNode, groupNodes) {
|
|
433
|
+
for (const parentNode of currentNode.getAllParentReferencesIncludingSelf()) if (groupNodes.has(parentNode)) return true;
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
const tsCompiler = "tsCompiler";
|
|
437
|
+
//#endregion
|
|
438
|
+
//#region src/project/script-file.ts
|
|
361
439
|
var ScriptFile = class {
|
|
362
440
|
content;
|
|
363
441
|
fileName;
|
|
@@ -381,8 +459,13 @@ var ScriptFile = class {
|
|
|
381
459
|
}
|
|
382
460
|
getOffset(pos) {
|
|
383
461
|
const lines = this.originalContent.split("\n");
|
|
462
|
+
const lineCount = Math.min(pos.line, lines.length);
|
|
384
463
|
let offset = 0;
|
|
385
|
-
for (let i = 0; i <
|
|
464
|
+
for (let i = 0; i < lineCount; i++) {
|
|
465
|
+
const line = lines[i];
|
|
466
|
+
if (line === void 0) break;
|
|
467
|
+
offset += line.length + 1;
|
|
468
|
+
}
|
|
386
469
|
offset += pos.column;
|
|
387
470
|
return offset;
|
|
388
471
|
}
|
|
@@ -395,7 +478,7 @@ var ScriptFile = class {
|
|
|
395
478
|
}
|
|
396
479
|
};
|
|
397
480
|
//#endregion
|
|
398
|
-
//#region src/
|
|
481
|
+
//#region src/project/hybrid-file-system.ts
|
|
399
482
|
/**
|
|
400
483
|
* A very simple hybrid file system.
|
|
401
484
|
* * Readonly from disk
|
|
@@ -460,83 +543,6 @@ var HybridFileSystem = class {
|
|
|
460
543
|
}
|
|
461
544
|
};
|
|
462
545
|
//#endregion
|
|
463
|
-
//#region src/grouping/ts-file-node.ts
|
|
464
|
-
var TSFileNode = class {
|
|
465
|
-
fileName;
|
|
466
|
-
parents;
|
|
467
|
-
children;
|
|
468
|
-
constructor(fileName, parents, children) {
|
|
469
|
-
this.fileName = fileName;
|
|
470
|
-
this.parents = parents;
|
|
471
|
-
this.children = children;
|
|
472
|
-
}
|
|
473
|
-
getAllParentReferencesIncludingSelf(allParentReferences = /* @__PURE__ */ new Set()) {
|
|
474
|
-
allParentReferences.add(this);
|
|
475
|
-
this.parents.forEach((parent) => {
|
|
476
|
-
if (!allParentReferences.has(parent)) parent.getAllParentReferencesIncludingSelf(allParentReferences);
|
|
477
|
-
});
|
|
478
|
-
return allParentReferences;
|
|
479
|
-
}
|
|
480
|
-
getAllChildReferencesIncludingSelf(allChildReferences = /* @__PURE__ */ new Set()) {
|
|
481
|
-
allChildReferences.add(this);
|
|
482
|
-
this.children.forEach((child) => {
|
|
483
|
-
if (!allChildReferences.has(child)) child.getAllChildReferencesIncludingSelf(allChildReferences);
|
|
484
|
-
});
|
|
485
|
-
return allChildReferences;
|
|
486
|
-
}
|
|
487
|
-
getMutantsWithReferenceToChildrenOrSelf(mutants, nodesChecked = []) {
|
|
488
|
-
if (nodesChecked.includes(this.fileName)) return [];
|
|
489
|
-
nodesChecked.push(this.fileName);
|
|
490
|
-
const relatedMutants = mutants.filter((m) => toPosixFileName(m.fileName) === this.fileName);
|
|
491
|
-
const childResult = this.children.flatMap((c) => c.getMutantsWithReferenceToChildrenOrSelf(mutants, nodesChecked));
|
|
492
|
-
return [...relatedMutants, ...childResult];
|
|
493
|
-
}
|
|
494
|
-
};
|
|
495
|
-
//#endregion
|
|
496
|
-
//#region src/grouping/create-groups.ts
|
|
497
|
-
/**
|
|
498
|
-
* To speed up the type-checking we want to check multiple mutants at once.
|
|
499
|
-
* When multiple mutants in different files don't have overlap in affected files (or have small overlap), we can type-check them simultaneously.
|
|
500
|
-
* These mutants who can be tested at the same time are called a group.
|
|
501
|
-
* Therefore, the return type is an array of arrays, in other words: an array of groups.
|
|
502
|
-
*
|
|
503
|
-
* @param mutants All the mutants of the test project.
|
|
504
|
-
* @param nodes A graph representation of the test project.
|
|
505
|
-
*/
|
|
506
|
-
function createGroups(mutants, nodes) {
|
|
507
|
-
const groups = [];
|
|
508
|
-
const mutantsToGroup = new Set(mutants);
|
|
509
|
-
while (mutantsToGroup.size) {
|
|
510
|
-
const group = [];
|
|
511
|
-
const groupNodes = /* @__PURE__ */ new Set();
|
|
512
|
-
const nodesToIgnore = /* @__PURE__ */ new Set();
|
|
513
|
-
for (const currentMutant of mutantsToGroup) {
|
|
514
|
-
const currentNode = findNode(currentMutant.fileName, nodes);
|
|
515
|
-
if (!nodesToIgnore.has(currentNode) && !parentsHaveOverlapWith(currentNode, groupNodes)) {
|
|
516
|
-
group.push(currentMutant.id);
|
|
517
|
-
groupNodes.add(currentNode);
|
|
518
|
-
mutantsToGroup.delete(currentMutant);
|
|
519
|
-
addRangeOfNodesToSet(nodesToIgnore, currentNode.getAllParentReferencesIncludingSelf());
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
groups.push(group);
|
|
523
|
-
}
|
|
524
|
-
return groups;
|
|
525
|
-
}
|
|
526
|
-
function addRangeOfNodesToSet(nodes, nodesToAdd) {
|
|
527
|
-
for (const parent of nodesToAdd) nodes.add(parent);
|
|
528
|
-
}
|
|
529
|
-
function findNode(fileName, nodes) {
|
|
530
|
-
const node = nodes.get(toPosixFileName(fileName));
|
|
531
|
-
if (node == null) throw new Error(`Node not in graph: ${fileName}`);
|
|
532
|
-
return node;
|
|
533
|
-
}
|
|
534
|
-
function parentsHaveOverlapWith(currentNode, groupNodes) {
|
|
535
|
-
for (const parentNode of currentNode.getAllParentReferencesIncludingSelf()) if (groupNodes.has(parentNode)) return true;
|
|
536
|
-
return false;
|
|
537
|
-
}
|
|
538
|
-
const tsCompiler = "tsCompiler";
|
|
539
|
-
//#endregion
|
|
540
546
|
//#region src/typescript-compiler.ts
|
|
541
547
|
var TypescriptCompiler = class {
|
|
542
548
|
log;
|
|
@@ -592,7 +598,7 @@ var TypescriptCompiler = class {
|
|
|
592
598
|
...program.getConfigFileParsingDiagnostics(),
|
|
593
599
|
...program.getSemanticDiagnostics(),
|
|
594
600
|
...program.getProgramDiagnostics()
|
|
595
|
-
]);
|
|
601
|
+
]).filter((diagnostic) => diagnostic.category === DiagnosticCategory.Error);
|
|
596
602
|
}
|
|
597
603
|
get nodes() {
|
|
598
604
|
if (!this._nodes.size) {
|
|
@@ -638,13 +644,13 @@ var TypescriptCompiler = class {
|
|
|
638
644
|
processed.add(current);
|
|
639
645
|
const content = readFileSync(current, "utf-8");
|
|
640
646
|
const parsed = parseTsConfig(current, content);
|
|
641
|
-
if (
|
|
642
|
-
this.log.warn(`Could not parse tsconfig file "%s": %s. Compiler-option overrides and project-reference walking were skipped for this file, so mutants may be misreported as compile errors.`, current, parsed.
|
|
647
|
+
if (Result.isFailure(parsed)) {
|
|
648
|
+
this.log.warn(`Could not parse tsconfig file "%s": %s. Compiler-option overrides and project-reference walking were skipped for this file, so mutants may be misreported as compile errors.`, current, parsed.failure.reason);
|
|
643
649
|
tsConfigOverrides.set(current, content);
|
|
644
650
|
continue;
|
|
645
651
|
}
|
|
646
|
-
tsConfigOverrides.set(current, overrideOptions(parsed.
|
|
647
|
-
for (const referenced of retrieveReferencedProjects(parsed.
|
|
652
|
+
tsConfigOverrides.set(current, overrideOptions(parsed.success, buildModeEnabled));
|
|
653
|
+
for (const referenced of retrieveReferencedProjects(parsed.success, path.dirname(current))) {
|
|
648
654
|
this.allTSConfigFiles.add(referenced);
|
|
649
655
|
toProcess.push(referenced);
|
|
650
656
|
}
|
|
@@ -743,9 +749,10 @@ var TypescriptCompiler = class {
|
|
|
743
749
|
this.log.warn(`Could not find sourcemap ${sourceMapFileName}`);
|
|
744
750
|
return dependencyFileName;
|
|
745
751
|
}
|
|
746
|
-
const sources = JSON.parse(sourceMap.content).sources;
|
|
752
|
+
const sources = Schema.decodeUnknownSync(Schema.Struct({ sources: Schema.optional(Schema.Array(Schema.String)) }))(JSON.parse(sourceMap.content)).sources;
|
|
747
753
|
if (sources?.length === 1) {
|
|
748
|
-
const
|
|
754
|
+
const sourcePath = sources[0];
|
|
755
|
+
if (sourcePath === void 0) return dependencyFileName;
|
|
749
756
|
return toPosixFileName(path.resolve(path.dirname(sourceMapFileName), sourcePath));
|
|
750
757
|
}
|
|
751
758
|
return dependencyFileName;
|
|
@@ -781,7 +788,8 @@ var TypescriptChecker = class {
|
|
|
781
788
|
}
|
|
782
789
|
async check(mutants) {
|
|
783
790
|
const result = Object.fromEntries(mutants.map((mutant) => [mutant.id, { status: CheckStatus.Passed }]));
|
|
784
|
-
|
|
791
|
+
const firstMutant = mutants[0];
|
|
792
|
+
if (!firstMutant || !this.tsCompiler.nodes.get(toPosixFileName(firstMutant.fileName))) return result;
|
|
785
793
|
const mutantErrorRelationMap = await this.checkErrors(mutants, {}, this.tsCompiler.nodes);
|
|
786
794
|
for (const [id, errors] of Object.entries(mutantErrorRelationMap)) result[id] = {
|
|
787
795
|
status: CheckStatus.CompileError,
|
|
@@ -801,7 +809,8 @@ var TypescriptChecker = class {
|
|
|
801
809
|
const errors = await this.tsCompiler.check(mutants);
|
|
802
810
|
const mutantsThatCouldNotBeTestedInGroups = /* @__PURE__ */ new Set();
|
|
803
811
|
if (errors.length && mutants.length === 1) {
|
|
804
|
-
|
|
812
|
+
const onlyMutant = mutants[0];
|
|
813
|
+
if (onlyMutant !== void 0) errorsMap[onlyMutant.id] = errors;
|
|
805
814
|
return errorsMap;
|
|
806
815
|
}
|
|
807
816
|
for (const error of errors) {
|
|
@@ -811,9 +820,12 @@ var TypescriptChecker = class {
|
|
|
811
820
|
const mutantsRelatedToError = nodeErrorWasThrownIn.getMutantsWithReferenceToChildrenOrSelf(mutants);
|
|
812
821
|
if (mutantsRelatedToError.length === 0) for (const mutant of mutants) mutantsThatCouldNotBeTestedInGroups.add(mutant);
|
|
813
822
|
else if (mutantsRelatedToError.length === 1) {
|
|
814
|
-
const
|
|
815
|
-
if (
|
|
816
|
-
|
|
823
|
+
const onlyRelatedMutant = mutantsRelatedToError[0];
|
|
824
|
+
if (onlyRelatedMutant !== void 0) {
|
|
825
|
+
const mutantId = onlyRelatedMutant.id;
|
|
826
|
+
if (errorsMap[mutantId]) errorsMap[mutantId].push(error);
|
|
827
|
+
else errorsMap[mutantId] = [error];
|
|
828
|
+
}
|
|
817
829
|
} else for (const mutant of mutantsRelatedToError) mutantsThatCouldNotBeTestedInGroups.add(mutant);
|
|
818
830
|
}
|
|
819
831
|
if (mutantsThatCouldNotBeTestedInGroups.size) await this.tsCompiler.check([]);
|
|
@@ -842,6 +854,6 @@ var TypescriptChecker = class {
|
|
|
842
854
|
//#region src/index.ts
|
|
843
855
|
const strykerPlugins = [declareFactoryPlugin(PluginKind.Checker, "typescript", create)];
|
|
844
856
|
const createTypescriptChecker = create;
|
|
845
|
-
const strykerValidationSchema = JSON.parse(readFileSync(new URL("../schema/typescript-checker-options.json", import.meta.url), "utf-8"));
|
|
857
|
+
const strykerValidationSchema = Schema.decodeUnknownSync(Schema.Record(Schema.String, Schema.Unknown))(JSON.parse(readFileSync(new URL("../schema/typescript-checker-options.json", import.meta.url), "utf-8")));
|
|
846
858
|
//#endregion
|
|
847
859
|
export { createTypescriptChecker, strykerPlugins, strykerValidationSchema };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@systemfsoftware/stryker-js-typescript-checker",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/systemfsoftware/systemfsoftware.git",
|
|
@@ -10,37 +10,50 @@
|
|
|
10
10
|
"bugs": "https://github.com/systemfsoftware/systemfsoftware/issues",
|
|
11
11
|
"description": "TypeScript checker plugin for Stryker — TS7 native",
|
|
12
12
|
"type": "module",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"schema"
|
|
16
|
+
],
|
|
13
17
|
"exports": {
|
|
14
18
|
".": "./dist/index.mjs",
|
|
15
19
|
"./package.json": "./package.json"
|
|
16
20
|
},
|
|
17
21
|
"license": "Apache-2.0",
|
|
18
22
|
"dependencies": {
|
|
19
|
-
"@stryker-mutator/api": "^9.6.1",
|
|
20
23
|
"@stryker-mutator/util": "^9.6.1",
|
|
21
|
-
"effect": "
|
|
24
|
+
"effect": "4.0.0-rc.108",
|
|
22
25
|
"semver": "^7.7.0",
|
|
23
26
|
"tslib": "~2.8.0",
|
|
24
|
-
"typescript": "^7"
|
|
27
|
+
"typescript": "^7",
|
|
28
|
+
"@systemfsoftware/stryker-js-plugin-api": "^1.0.0"
|
|
25
29
|
},
|
|
26
30
|
"devDependencies": {
|
|
27
31
|
"@std/jsonc": "npm:@jsr/std__jsonc@^1.0.2",
|
|
32
|
+
"@systemfsoftware/arethetypeswrong-cli": "^1.1.1",
|
|
28
33
|
"@types/node": "^24",
|
|
29
34
|
"@types/semver": "^7.5.8",
|
|
35
|
+
"oxlint": "^1.77.0",
|
|
30
36
|
"rimraf": "^6.1.3",
|
|
31
|
-
"tsdown": "^0.22.
|
|
32
|
-
"
|
|
37
|
+
"tsdown": "^0.22.14",
|
|
38
|
+
"typed-inject": "~5.0.0",
|
|
39
|
+
"vitest": "^4.1.10",
|
|
40
|
+
"@systemfsoftware/effect-gherkin-spec": "^1.0.0",
|
|
33
41
|
"@systemfsoftware/vitest-config": "^0.1.0",
|
|
34
|
-
"@systemfsoftware/tsconfig": "^1.3.
|
|
42
|
+
"@systemfsoftware/tsconfig": "^1.3.2",
|
|
43
|
+
"@systemfsoftware/oxlint-config": "^0.1.0"
|
|
35
44
|
},
|
|
36
45
|
"inlinedDependencies": {
|
|
37
46
|
"@jsr/std__jsonc": "1.0.2"
|
|
38
47
|
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"provenance": true
|
|
50
|
+
},
|
|
39
51
|
"scripts": {
|
|
40
52
|
"clean": "rimraf dist",
|
|
41
53
|
"build": "tsdown",
|
|
42
|
-
"typecheck": "tsc --noEmit",
|
|
54
|
+
"typecheck": "tsc --noEmit --incremental",
|
|
55
|
+
"attw": "attw --pack .",
|
|
43
56
|
"test": "vitest run",
|
|
44
|
-
"lint": "oxlint ."
|
|
57
|
+
"lint": "f=${OXLINT_FORMAT:-${AGENT:+agent}}; oxlint . --format=${f:-default}"
|
|
45
58
|
}
|
|
46
59
|
}
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
$ tsdown
|
|
2
|
-
[34mℹ[39m [34mtsdown v0.22.9[39m powered by [38;2;255;126;23mrolldown v1.2.0[39m
|
|
3
|
-
[34mℹ[39m config file: [4m/home/runner/work/systemfsoftware/systemfsoftware/packages/stryker-js/typescript-checker/tsdown.config.ts[24m
|
|
4
|
-
[34mℹ[39m entry: [34m./src/index.ts[39m
|
|
5
|
-
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
6
|
-
|
|
7
|
-
[43m WARN [49m `noExternal` is deprecated. Use `deps.alwaysBundle` instead.
|
|
8
|
-
|
|
9
|
-
[34mℹ[39m Build start
|
|
10
|
-
|
|
11
|
-
[43m WARN [49m TypeScript 7.0 does not yet have a stable API and is experimental. Some options will be unavailable.
|
|
12
|
-
|
|
13
|
-
[34mℹ[39m Emit types with typescript@7.0.2
|
|
14
|
-
[34mℹ[39m Hint: consider adding [34mdeps.onlyBundle[39m option to avoid unintended bundling of dependencies, or set [34mdeps.onlyBundle: false[39m to disable this hint.
|
|
15
|
-
See more at [4mhttps://tsdown.dev/options/dependencies#deps-onlybundle[24m
|
|
16
|
-
Detected dependencies in bundle:
|
|
17
|
-
- [34m@jsr/std__jsonc[39m
|
|
18
|
-
[34mℹ[39m [2mdist/[22m[1mindex.mjs[22m [2m31.51 kB[22m [2m│ gzip: 8.65 kB[22m
|
|
19
|
-
[34mℹ[39m [2mdist/[22m[32m[1mindex.d.mts[22m[39m [2m 4.66 kB[22m [2m│ gzip: 1.52 kB[22m
|
|
20
|
-
[34mℹ[39m 2 files, total: 36.17 kB
|
|
21
|
-
[32m✔[39m Build complete in [32m635ms[39m
|
package/AGENTS.md
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# AGENTS.md — `@systemfsoftware/stryker-js-typescript-checker`
|
|
2
|
-
|
|
3
|
-
> **Location:** `packages/stryker-js/typescript-checker/` — TypeScript checker plugin for Stryker, TS7-native. Universal agent rules live in the root `AGENTS.md`; this file carries only `typescript-checker/`-specific deltas.
|
|
4
|
-
|
|
5
|
-
Extends `@stryker-mutator/api` checker protocol, compiling projects directly with `typescript` against their own tsconfig (parsed via `@std/jsonc`). Built via tsdown, single entrypoint.
|
|
6
|
-
|
|
7
|
-
🛑 Don't depend on `typescript` from the host project — peer dep resolved at runtime.
|
package/oxlint.config.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'fs'
|
|
2
|
-
|
|
3
|
-
import type { FileSystem, FileSystemEntries } from 'typescript/unstable/fs'
|
|
4
|
-
|
|
5
|
-
import { toPosixFileName } from '../tsconfig-helpers.js'
|
|
6
|
-
|
|
7
|
-
import { ScriptFile } from './script-file.js'
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* A very simple hybrid file system.
|
|
11
|
-
* * Readonly from disk
|
|
12
|
-
* * Writes in-memory
|
|
13
|
-
* * Hard caching
|
|
14
|
-
* * Ability to mutate one file
|
|
15
|
-
*/
|
|
16
|
-
export class HybridFileSystem implements FileSystem {
|
|
17
|
-
private readonly files = new Map<string, ScriptFile | undefined>()
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Map of absolute tsconfig file paths to their adjusted JSON content.
|
|
21
|
-
* This allows the TS7 API to read overridden compiler options.
|
|
22
|
-
*/
|
|
23
|
-
public tsConfigOverrides = new Map<string, string>()
|
|
24
|
-
|
|
25
|
-
public readFile = (fileName: string): string | null | undefined => {
|
|
26
|
-
const normalized = toPosixFileName(fileName)
|
|
27
|
-
if (this.fileNameIsBuildInfo(normalized)) {
|
|
28
|
-
return null
|
|
29
|
-
}
|
|
30
|
-
const override = this.tsConfigOverrides.get(normalized)
|
|
31
|
-
if (override !== undefined) {
|
|
32
|
-
return override
|
|
33
|
-
}
|
|
34
|
-
const file = this.files.get(normalized)
|
|
35
|
-
if (file) {
|
|
36
|
-
return file.content
|
|
37
|
-
}
|
|
38
|
-
if (file === undefined && this.files.has(normalized)) {
|
|
39
|
-
// File was previously read and does not exist
|
|
40
|
-
return null
|
|
41
|
-
}
|
|
42
|
-
return undefined
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
public fileExists = (fileName: string): boolean | undefined => {
|
|
46
|
-
const normalized = toPosixFileName(fileName)
|
|
47
|
-
if (this.fileNameIsBuildInfo(normalized)) {
|
|
48
|
-
return false
|
|
49
|
-
}
|
|
50
|
-
if (this.tsConfigOverrides.has(normalized)) {
|
|
51
|
-
return true
|
|
52
|
-
}
|
|
53
|
-
if (this.files.has(normalized)) {
|
|
54
|
-
return this.files.get(normalized) !== undefined
|
|
55
|
-
}
|
|
56
|
-
return undefined
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
public directoryExists = (): boolean | undefined => {
|
|
60
|
-
return undefined
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
public getAccessibleEntries = (): FileSystemEntries | undefined => {
|
|
64
|
-
return undefined
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
public realpath = (): string | undefined => {
|
|
68
|
-
return undefined
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
public writeFile(fileName: string, data: string): void {
|
|
72
|
-
const normalized = toPosixFileName(fileName)
|
|
73
|
-
const existingFile = this.files.get(normalized)
|
|
74
|
-
if (existingFile) {
|
|
75
|
-
existingFile.write(data)
|
|
76
|
-
} else {
|
|
77
|
-
this.files.set(normalized, new ScriptFile(data, normalized))
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
public getFile(fileName: string): ScriptFile | undefined {
|
|
82
|
-
const normalized = toPosixFileName(fileName)
|
|
83
|
-
if (!this.files.has(normalized)) {
|
|
84
|
-
try {
|
|
85
|
-
const content = readFileSync(normalized, 'utf-8')
|
|
86
|
-
this.files.set(normalized, new ScriptFile(content, normalized))
|
|
87
|
-
} catch {
|
|
88
|
-
this.files.set(normalized, undefined)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return this.files.get(normalized)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
public mutateFile(
|
|
95
|
-
fileName: string,
|
|
96
|
-
mutant: Parameters<ScriptFile['mutate']>[0],
|
|
97
|
-
): void {
|
|
98
|
-
const file = this.getFile(fileName)
|
|
99
|
-
if (!file) {
|
|
100
|
-
throw new Error(`Tried to mutate file "${fileName}" but it could not be found.`)
|
|
101
|
-
}
|
|
102
|
-
file.mutate(mutant)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
public resetFile(fileName: string): void {
|
|
106
|
-
const file = this.getFile(fileName)
|
|
107
|
-
file?.resetMutant()
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
public existsInMemory(fileName: string): boolean {
|
|
111
|
-
const file = this.files.get(toPosixFileName(fileName))
|
|
112
|
-
return file !== undefined
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
private fileNameIsBuildInfo(fileName: string): boolean {
|
|
116
|
-
return fileName.endsWith('.tsbuildinfo')
|
|
117
|
-
}
|
|
118
|
-
}
|
package/src/fs/index.ts
DELETED
package/src/fs/script-file.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { Mutant, Position } from '@stryker-mutator/api/core'
|
|
2
|
-
|
|
3
|
-
export class ScriptFile {
|
|
4
|
-
private readonly originalContent: string
|
|
5
|
-
|
|
6
|
-
constructor(
|
|
7
|
-
public content: string,
|
|
8
|
-
public fileName: string,
|
|
9
|
-
public modifiedTime = new Date(),
|
|
10
|
-
) {
|
|
11
|
-
this.originalContent = content
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
public write(content: string): void {
|
|
15
|
-
this.content = content
|
|
16
|
-
this.touch()
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
public mutate(mutant: Pick<Mutant, 'location' | 'replacement'>): void {
|
|
20
|
-
const start = this.getOffset(mutant.location.start)
|
|
21
|
-
const end = this.getOffset(mutant.location.end)
|
|
22
|
-
this.content = `${this.originalContent.slice(0, start)}${mutant.replacement}${this.originalContent.slice(end)}`
|
|
23
|
-
this.touch()
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
private getOffset(pos: Position): number {
|
|
27
|
-
const lines = this.originalContent.split('\n')
|
|
28
|
-
let offset = 0
|
|
29
|
-
for (let i = 0; i < pos.line && i < lines.length; i++) {
|
|
30
|
-
offset += lines[i]!.length + 1 // +1 for the newline character
|
|
31
|
-
}
|
|
32
|
-
offset += pos.column
|
|
33
|
-
return offset
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
public resetMutant(): void {
|
|
37
|
-
this.content = this.originalContent
|
|
38
|
-
this.touch()
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
private touch(): void {
|
|
42
|
-
this.modifiedTime = new Date()
|
|
43
|
-
}
|
|
44
|
-
}
|