@systemfsoftware/stryker-js-typescript-checker 1.2.2 → 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.
Files changed (55) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +203 -21
  3. package/README.md +31 -0
  4. package/dist/index.d.mts +19 -19
  5. package/dist/index.mjs +132 -122
  6. package/package.json +23 -10
  7. package/.turbo/turbo-build.log +0 -21
  8. package/AGENTS.md +0 -7
  9. package/oxlint.config.ts +0 -9
  10. package/src/fs/hybrid-file-system.ts +0 -118
  11. package/src/fs/index.ts +0 -2
  12. package/src/fs/script-file.ts +0 -44
  13. package/src/grouping/create-groups.ts +0 -77
  14. package/src/grouping/ts-file-node.ts +0 -54
  15. package/src/index.ts +0 -18
  16. package/src/plugin-tokens.ts +0 -2
  17. package/src/tsconfig-helpers.ts +0 -187
  18. package/src/typescript-checker-options-with-stryker-options.ts +0 -9
  19. package/src/typescript-checker.ts +0 -223
  20. package/src/typescript-compiler.ts +0 -412
  21. package/test/integration/e2e-plugin-entry.it.spec.ts +0 -153
  22. package/test/integration/project-references.it.spec.ts +0 -146
  23. package/test/integration/project-with-ts-buildinfo.it.spec.ts +0 -92
  24. package/test/integration/single-project.it.spec.ts +0 -298
  25. package/test/integration/tsconfig-helpers.it.spec.ts +0 -199
  26. package/test/integration/typescript-checkers-errors.it.spec.ts +0 -112
  27. package/test/unit/fs/hybrid-file-system.spec.ts +0 -109
  28. package/test/unit/grouping/create-groups.spec.ts +0 -122
  29. package/test/unit/grouping/ts-file-node.spec.ts +0 -132
  30. package/testResources/errors/compile-error/add.ts +0 -3
  31. package/testResources/errors/compile-error/tsconfig.json +0 -6
  32. package/testResources/errors/empty-dir/.gitkeep +0 -0
  33. package/testResources/errors/invalid-tsconfig/tsconfig.json +0 -1
  34. package/testResources/project-references/src/index.ts +0 -5
  35. package/testResources/project-references/src/job.ts +0 -6
  36. package/testResources/project-references/src/tsconfig.json +0 -10
  37. package/testResources/project-references/tsconfig.root.json +0 -7
  38. package/testResources/project-references/tsconfig.settings.json +0 -17
  39. package/testResources/project-references/utils/math.ts +0 -3
  40. package/testResources/project-references/utils/text.ts +0 -3
  41. package/testResources/project-references/utils/tsconfig.json +0 -7
  42. package/testResources/project-with-ts-buildinfo/src/index.ts +0 -3
  43. package/testResources/project-with-ts-buildinfo/tsconfig.json +0 -17
  44. package/testResources/single-project/src/counter.ts +0 -9
  45. package/testResources/single-project/src/errorInFileAbove2Mutants/counter.ts +0 -9
  46. package/testResources/single-project/src/errorInFileAbove2Mutants/todo-counter.ts +0 -7
  47. package/testResources/single-project/src/errorInFileAbove2Mutants/todo.spec.ts +0 -11
  48. package/testResources/single-project/src/errorInFileAbove2Mutants/todo.ts +0 -22
  49. package/testResources/single-project/src/not-type-checked.js +0 -1
  50. package/testResources/single-project/src/todo.spec.ts +0 -11
  51. package/testResources/single-project/src/todo.ts +0 -22
  52. package/testResources/single-project/tsconfig.json +0 -17
  53. package/tsconfig.json +0 -26
  54. package/tsdown.config.ts +0 -16
  55. 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-mutator/api/plugin";
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 Either.mapLeft(Schema.decodeUnknownEither(TsConfigSchema)(value), (issue) => new TsConfigParseError({
284
+ return Result.mapError(Schema.decodeUnknownResult(TsConfigSchema)(value), (error) => new TsConfigParseError({
284
285
  file: fileName,
285
- reason: issue.message
286
+ reason: error.message
286
287
  }));
287
288
  } catch (error) {
288
- return Either.left(new TsConfigParseError({
289
+ return Result.fail(new TsConfigParseError({
289
290
  file: fileName,
290
- reason: error instanceof Error ? error.message : String(error)
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 Either.match(parsed, {
301
- onLeft: () => false,
302
- onRight: (config) => config.references !== void 0
301
+ return Result.match(parsed, {
302
+ onFailure: () => false,
303
+ onSuccess: (config) => config.references !== void 0
303
304
  });
304
305
  }
305
306
  /**
@@ -311,9 +312,7 @@ function overrideOptions(config, useBuildMode) {
311
312
  const compilerOptions = {
312
313
  ...config.compilerOptions,
313
314
  ...COMPILER_OPTIONS_OVERRIDES,
314
- ...useBuildMode ? LOW_EMIT_OPTIONS_FOR_PROJECT_REFERENCES : NO_EMIT_OPTIONS_FOR_SINGLE_PROJECT,
315
- target: "es2022",
316
- moduleResolution: "bundler"
315
+ ...useBuildMode ? LOW_EMIT_OPTIONS_FOR_PROJECT_REFERENCES : NO_EMIT_OPTIONS_FOR_SINGLE_PROJECT
317
316
  };
318
317
  if (!useBuildMode && compilerOptions["declarationDir"] !== void 0 && compilerOptions["declarationDir"] !== null) delete compilerOptions["declarationDir"];
319
318
  if (useBuildMode) {
@@ -359,7 +358,84 @@ function getSourceMappingURL(content) {
359
358
  return findSourceMapRegex.exec(content)?.[1];
360
359
  }
361
360
  //#endregion
362
- //#region src/fs/script-file.ts
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
363
439
  var ScriptFile = class {
364
440
  content;
365
441
  fileName;
@@ -383,8 +459,13 @@ var ScriptFile = class {
383
459
  }
384
460
  getOffset(pos) {
385
461
  const lines = this.originalContent.split("\n");
462
+ const lineCount = Math.min(pos.line, lines.length);
386
463
  let offset = 0;
387
- for (let i = 0; i < pos.line && i < lines.length; i++) offset += lines[i].length + 1;
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
+ }
388
469
  offset += pos.column;
389
470
  return offset;
390
471
  }
@@ -397,7 +478,7 @@ var ScriptFile = class {
397
478
  }
398
479
  };
399
480
  //#endregion
400
- //#region src/fs/hybrid-file-system.ts
481
+ //#region src/project/hybrid-file-system.ts
401
482
  /**
402
483
  * A very simple hybrid file system.
403
484
  * * Readonly from disk
@@ -462,83 +543,6 @@ var HybridFileSystem = class {
462
543
  }
463
544
  };
464
545
  //#endregion
465
- //#region src/grouping/ts-file-node.ts
466
- var TSFileNode = class {
467
- fileName;
468
- parents;
469
- children;
470
- constructor(fileName, parents, children) {
471
- this.fileName = fileName;
472
- this.parents = parents;
473
- this.children = children;
474
- }
475
- getAllParentReferencesIncludingSelf(allParentReferences = /* @__PURE__ */ new Set()) {
476
- allParentReferences.add(this);
477
- this.parents.forEach((parent) => {
478
- if (!allParentReferences.has(parent)) parent.getAllParentReferencesIncludingSelf(allParentReferences);
479
- });
480
- return allParentReferences;
481
- }
482
- getAllChildReferencesIncludingSelf(allChildReferences = /* @__PURE__ */ new Set()) {
483
- allChildReferences.add(this);
484
- this.children.forEach((child) => {
485
- if (!allChildReferences.has(child)) child.getAllChildReferencesIncludingSelf(allChildReferences);
486
- });
487
- return allChildReferences;
488
- }
489
- getMutantsWithReferenceToChildrenOrSelf(mutants, nodesChecked = []) {
490
- if (nodesChecked.includes(this.fileName)) return [];
491
- nodesChecked.push(this.fileName);
492
- const relatedMutants = mutants.filter((m) => toPosixFileName(m.fileName) === this.fileName);
493
- const childResult = this.children.flatMap((c) => c.getMutantsWithReferenceToChildrenOrSelf(mutants, nodesChecked));
494
- return [...relatedMutants, ...childResult];
495
- }
496
- };
497
- //#endregion
498
- //#region src/grouping/create-groups.ts
499
- /**
500
- * To speed up the type-checking we want to check multiple mutants at once.
501
- * When multiple mutants in different files don't have overlap in affected files (or have small overlap), we can type-check them simultaneously.
502
- * These mutants who can be tested at the same time are called a group.
503
- * Therefore, the return type is an array of arrays, in other words: an array of groups.
504
- *
505
- * @param mutants All the mutants of the test project.
506
- * @param nodes A graph representation of the test project.
507
- */
508
- function createGroups(mutants, nodes) {
509
- const groups = [];
510
- const mutantsToGroup = new Set(mutants);
511
- while (mutantsToGroup.size) {
512
- const group = [];
513
- const groupNodes = /* @__PURE__ */ new Set();
514
- const nodesToIgnore = /* @__PURE__ */ new Set();
515
- for (const currentMutant of mutantsToGroup) {
516
- const currentNode = findNode(currentMutant.fileName, nodes);
517
- if (!nodesToIgnore.has(currentNode) && !parentsHaveOverlapWith(currentNode, groupNodes)) {
518
- group.push(currentMutant.id);
519
- groupNodes.add(currentNode);
520
- mutantsToGroup.delete(currentMutant);
521
- addRangeOfNodesToSet(nodesToIgnore, currentNode.getAllParentReferencesIncludingSelf());
522
- }
523
- }
524
- groups.push(group);
525
- }
526
- return groups;
527
- }
528
- function addRangeOfNodesToSet(nodes, nodesToAdd) {
529
- for (const parent of nodesToAdd) nodes.add(parent);
530
- }
531
- function findNode(fileName, nodes) {
532
- const node = nodes.get(toPosixFileName(fileName));
533
- if (node == null) throw new Error(`Node not in graph: ${fileName}`);
534
- return node;
535
- }
536
- function parentsHaveOverlapWith(currentNode, groupNodes) {
537
- for (const parentNode of currentNode.getAllParentReferencesIncludingSelf()) if (groupNodes.has(parentNode)) return true;
538
- return false;
539
- }
540
- const tsCompiler = "tsCompiler";
541
- //#endregion
542
546
  //#region src/typescript-compiler.ts
543
547
  var TypescriptCompiler = class {
544
548
  log;
@@ -594,7 +598,7 @@ var TypescriptCompiler = class {
594
598
  ...program.getConfigFileParsingDiagnostics(),
595
599
  ...program.getSemanticDiagnostics(),
596
600
  ...program.getProgramDiagnostics()
597
- ]);
601
+ ]).filter((diagnostic) => diagnostic.category === DiagnosticCategory.Error);
598
602
  }
599
603
  get nodes() {
600
604
  if (!this._nodes.size) {
@@ -640,13 +644,13 @@ var TypescriptCompiler = class {
640
644
  processed.add(current);
641
645
  const content = readFileSync(current, "utf-8");
642
646
  const parsed = parseTsConfig(current, content);
643
- if (Either.isLeft(parsed)) {
644
- 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.left.reason);
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);
645
649
  tsConfigOverrides.set(current, content);
646
650
  continue;
647
651
  }
648
- tsConfigOverrides.set(current, overrideOptions(parsed.right, buildModeEnabled));
649
- for (const referenced of retrieveReferencedProjects(parsed.right, path.dirname(current))) {
652
+ tsConfigOverrides.set(current, overrideOptions(parsed.success, buildModeEnabled));
653
+ for (const referenced of retrieveReferencedProjects(parsed.success, path.dirname(current))) {
650
654
  this.allTSConfigFiles.add(referenced);
651
655
  toProcess.push(referenced);
652
656
  }
@@ -745,9 +749,10 @@ var TypescriptCompiler = class {
745
749
  this.log.warn(`Could not find sourcemap ${sourceMapFileName}`);
746
750
  return dependencyFileName;
747
751
  }
748
- 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;
749
753
  if (sources?.length === 1) {
750
- const [sourcePath] = sources;
754
+ const sourcePath = sources[0];
755
+ if (sourcePath === void 0) return dependencyFileName;
751
756
  return toPosixFileName(path.resolve(path.dirname(sourceMapFileName), sourcePath));
752
757
  }
753
758
  return dependencyFileName;
@@ -783,7 +788,8 @@ var TypescriptChecker = class {
783
788
  }
784
789
  async check(mutants) {
785
790
  const result = Object.fromEntries(mutants.map((mutant) => [mutant.id, { status: CheckStatus.Passed }]));
786
- if (!this.tsCompiler.nodes.get(toPosixFileName(mutants[0].fileName))) return result;
791
+ const firstMutant = mutants[0];
792
+ if (!firstMutant || !this.tsCompiler.nodes.get(toPosixFileName(firstMutant.fileName))) return result;
787
793
  const mutantErrorRelationMap = await this.checkErrors(mutants, {}, this.tsCompiler.nodes);
788
794
  for (const [id, errors] of Object.entries(mutantErrorRelationMap)) result[id] = {
789
795
  status: CheckStatus.CompileError,
@@ -803,7 +809,8 @@ var TypescriptChecker = class {
803
809
  const errors = await this.tsCompiler.check(mutants);
804
810
  const mutantsThatCouldNotBeTestedInGroups = /* @__PURE__ */ new Set();
805
811
  if (errors.length && mutants.length === 1) {
806
- errorsMap[mutants[0].id] = errors;
812
+ const onlyMutant = mutants[0];
813
+ if (onlyMutant !== void 0) errorsMap[onlyMutant.id] = errors;
807
814
  return errorsMap;
808
815
  }
809
816
  for (const error of errors) {
@@ -813,9 +820,12 @@ var TypescriptChecker = class {
813
820
  const mutantsRelatedToError = nodeErrorWasThrownIn.getMutantsWithReferenceToChildrenOrSelf(mutants);
814
821
  if (mutantsRelatedToError.length === 0) for (const mutant of mutants) mutantsThatCouldNotBeTestedInGroups.add(mutant);
815
822
  else if (mutantsRelatedToError.length === 1) {
816
- const mutantId = mutantsRelatedToError[0].id;
817
- if (errorsMap[mutantId]) errorsMap[mutantId].push(error);
818
- else errorsMap[mutantId] = [error];
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
+ }
819
829
  } else for (const mutant of mutantsRelatedToError) mutantsThatCouldNotBeTestedInGroups.add(mutant);
820
830
  }
821
831
  if (mutantsThatCouldNotBeTestedInGroups.size) await this.tsCompiler.check([]);
@@ -844,6 +854,6 @@ var TypescriptChecker = class {
844
854
  //#region src/index.ts
845
855
  const strykerPlugins = [declareFactoryPlugin(PluginKind.Checker, "typescript", create)];
846
856
  const createTypescriptChecker = create;
847
- 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")));
848
858
  //#endregion
849
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.2.2",
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": "^3.22.0",
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.9",
32
- "vitest": "^4",
33
- "@systemfsoftware/tsconfig": "^1.3.0",
34
- "@systemfsoftware/vitest-config": "^0.1.0"
37
+ "tsdown": "^0.22.14",
38
+ "typed-inject": "~5.0.0",
39
+ "vitest": "^4.1.10",
40
+ "@systemfsoftware/effect-gherkin-spec": "^1.0.0",
41
+ "@systemfsoftware/vitest-config": "^0.1.0",
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
  }
@@ -1,21 +0,0 @@
1
- $ tsdown
2
- ℹ tsdown v0.22.9 powered by rolldown v1.2.0
3
- ℹ config file: /home/runner/work/systemfsoftware/systemfsoftware/packages/stryker-js/typescript-checker/tsdown.config.ts
4
- ℹ entry: ./src/index.ts
5
-
6
- ℹ tsconfig: tsconfig.json
7
- ℹ Build start
8
-  WARN  `noExternal` is deprecated. Use `deps.alwaysBundle` instead.
9
-
10
-
11
-  WARN  TypeScript 7.0 does not yet have a stable API and is experimental. Some options will be unavailable.
12
-
13
- ℹ Emit types with typescript@7.0.2
14
- ℹ Hint: consider adding deps.onlyBundle option to avoid unintended bundling of dependencies, or set deps.onlyBundle: false to disable this hint.
15
- See more at https://tsdown.dev/options/dependencies#deps-onlybundle
16
- Detected dependencies in bundle:
17
- - @jsr/std__jsonc
18
- ℹ dist/index.mjs 31.56 kB │ gzip: 8.67 kB
19
- ℹ dist/index.d.mts  4.66 kB │ gzip: 1.52 kB
20
- ℹ 2 files, total: 36.22 kB
21
- ✔ Build complete in 561ms
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,9 +0,0 @@
1
- import { defineConfig } from 'oxlint'
2
-
3
- export default defineConfig({
4
- rules: {
5
- // TypeScript already reports unused locals; avoids false positives in test files.
6
- 'no-unused-vars': 'off',
7
- },
8
- ignorePatterns: ['**/testResources/**'],
9
- })
@@ -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
@@ -1,2 +0,0 @@
1
- export * from './hybrid-file-system.js'
2
- export * from './script-file.js'
@@ -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
- }