@systemfsoftware/stryker-js-typescript-checker 1.4.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,14 +1,168 @@
1
1
  import { readFileSync } from "fs";
2
- import { PluginKind, Scope, commonTokens, declareFactoryPlugin, tokens } from "@systemfsoftware/stryker-js-plugin-api/plugin";
3
- import { Result, Schema } from "effect";
2
+ import { Checker, CheckerFailed } from "@systemfsoftware/stryker-js/Checker";
3
+ import { RunConfiguration, declarePlugin } from "@systemfsoftware/stryker-js/Plugin";
4
+ import * as Effect from "effect/Effect";
5
+ import * as FileSystem from "effect/FileSystem";
6
+ import * as Layer from "effect/Layer";
7
+ import * as Path from "effect/Path";
8
+ import * as S from "effect/Schema";
4
9
  import { EOL } from "os";
5
- import { CheckStatus } from "@systemfsoftware/stryker-js-plugin-api/check";
6
- import { split, strykerReportBugUrl } from "@systemfsoftware/stryker-js-util";
10
+ import { Cell, Wire, Workflow } from "@systemfsoftware/effect-cell-types";
11
+ import { Mutant, errorToString } from "@systemfsoftware/stryker-js/Mutant";
12
+ import { Predicate, Result, Schema } from "effect";
13
+ import { pipe } from "effect/Function";
14
+ import * as HashMap from "effect/HashMap";
15
+ import * as MutableHashMap from "effect/MutableHashMap";
16
+ import * as Option from "effect/Option";
7
17
  import { API, DiagnosticCategory } from "typescript/unstable/sync";
18
+ import * as Result$1 from "effect/Result";
8
19
  import { createRequire } from "module";
9
- import path from "path";
10
- import semver from "semver";
20
+ import { StrykerOptionsSchema } from "@systemfsoftware/stryker-js/Schema";
21
+ import * as Context from "effect/Context";
22
+ import * as MutableHashSet from "effect/MutableHashSet";
23
+ import * as Ref from "effect/Ref";
11
24
  import { SyntaxKind } from "typescript/unstable/ast";
25
+ //#region src/Checker.schema.ts
26
+ /**
27
+ * Checker — declarations for the TypeScript checker.
28
+ *
29
+ * Houses the wire types and error variants shared by the capability and its
30
+ * workflow. Decoded at the checker boundary; no I/O.
31
+ */
32
+ var CheckMutantsCommand = class extends S.TaggedClass()("CheckMutantsCommand", { mutants: S.Array(Mutant) }) {};
33
+ /**
34
+ * Every way the TypeScript compiler can fail while serving a check.
35
+ * One tagged error — callers branch only on failure itself; `reason` keeps
36
+ * cases distinguishable in reports.
37
+ */
38
+ var CompilerFailed = class extends S.TaggedError()("CompilerFailed", {
39
+ reason: S.Literals([
40
+ "not-initialized",
41
+ "no-projects",
42
+ "unknown-file-node",
43
+ "file-not-in-project"
44
+ ]),
45
+ subject: S.optional(S.String)
46
+ }) {
47
+ get message() {
48
+ switch (this.reason) {
49
+ case "not-initialized": return "The TypeScript compiler was used before it was initialized";
50
+ case "no-projects": return `No projects were found for ${this.subject ?? "the tsconfig"}`;
51
+ case "unknown-file-node": return `The file graph has no node for '${this.subject ?? "a file"}', which should not happen`;
52
+ case "file-not-in-project": return `'${this.subject ?? "a file"}' is part of your TypeScript project but could not be found on disk`;
53
+ }
54
+ }
55
+ };
56
+ //#endregion
57
+ //#region src/Checker.workflow.ts
58
+ /**
59
+ * Checker — pure check decision.
60
+ *
61
+ * `Workflow.make` lives here and only here. The workflow receives a fully
62
+ * decoded input (mutants + diagnostics + file graph) and produces the
63
+ * pass/compileError map without touching I/O.
64
+ */
65
+ var DiagnosticWithoutFileError = class extends S.TaggedError()("DiagnosticWithoutFileError", { text: Wire.mint(S.String) }) {};
66
+ var DiagnosticInUnrelatedFileError = class extends S.TaggedError()("DiagnosticInUnrelatedFileError", {
67
+ text: Wire.mint(S.String),
68
+ fileName: Wire.mint(S.String)
69
+ }) {};
70
+ const DiagnosticSchema = Wire.wire({
71
+ fileName: Wire.mint(S.optional(Wire.mint(S.String))),
72
+ text: Wire.mint(S.String)
73
+ });
74
+ const TSFileNodeSchema = Wire.mint(S.suspend(() => Wire.wire({
75
+ fileName: Wire.mint(S.String),
76
+ parents: Wire.mint(S.Array(TSFileNodeSchema)),
77
+ children: Wire.mint(S.Array(TSFileNodeSchema))
78
+ })));
79
+ var CheckMutantsInput = class extends S.TaggedClass()("CheckMutantsInput", {
80
+ mutants: S.Array(Mutant),
81
+ diagnostics: S.Array(DiagnosticSchema),
82
+ nodes: Wire.mint(S.Record(Wire.mint(S.String), TSFileNodeSchema))
83
+ }) {};
84
+ const normalizeFileName$3 = (fileName) => fileName.replace(/\\/g, "/");
85
+ const getMutantsWithReferenceToChildrenOrSelf = (node, mutants, nodesChecked = []) => {
86
+ if (nodesChecked.includes(node.fileName)) return [];
87
+ nodesChecked.push(node.fileName);
88
+ const relatedMutants = mutants.filter((m) => normalizeFileName$3(m.fileName) === node.fileName);
89
+ const childResult = node.children.flatMap((c) => getMutantsWithReferenceToChildrenOrSelf(c, mutants, nodesChecked));
90
+ return [...relatedMutants, ...childResult];
91
+ };
92
+ const classifyDiagnosticsPure = (diagnostics, mutants, nodes) => {
93
+ const definitive = {};
94
+ const needsRetest = {};
95
+ if (diagnostics.length > 0 && mutants.length === 1) {
96
+ const only = mutants[0];
97
+ if (only !== void 0) {
98
+ definitive[only.id] = [...diagnostics];
99
+ return Result$1.succeed({
100
+ definitive,
101
+ needsRetest: []
102
+ });
103
+ }
104
+ }
105
+ for (const diagnostic of diagnostics) {
106
+ const fileName = diagnostic.fileName;
107
+ if (fileName === void 0 || fileName === "") return Result$1.fail(new DiagnosticWithoutFileError({ text: diagnostic.text }));
108
+ const node = nodes[fileName];
109
+ if (node === void 0) return Result$1.fail(new DiagnosticInUnrelatedFileError({
110
+ text: diagnostic.text,
111
+ fileName
112
+ }));
113
+ const related = getMutantsWithReferenceToChildrenOrSelf(node, [...mutants]);
114
+ if (related.length === 0) for (const m of mutants) needsRetest[m.id] = m;
115
+ else if (related.length === 1) {
116
+ const only = related[0];
117
+ if (only !== void 0) {
118
+ const existing = definitive[only.id];
119
+ if (existing !== void 0) existing.push(diagnostic);
120
+ else definitive[only.id] = [diagnostic];
121
+ }
122
+ } else for (const m of related) needsRetest[m.id] = m;
123
+ }
124
+ const filteredRetest = Object.values(needsRetest).filter((m) => definitive[m.id] === void 0);
125
+ return Result$1.succeed({
126
+ definitive,
127
+ needsRetest: filteredRetest
128
+ });
129
+ };
130
+ const buildResult = (input) => {
131
+ const mutants = input.mutants;
132
+ const diagnostics = input.diagnostics;
133
+ const nodes = input.nodes;
134
+ const result = {};
135
+ for (const m of mutants) result[m.id] = { status: "passed" };
136
+ if (mutants.length === 0) return Result$1.succeed(result);
137
+ const first = mutants[0];
138
+ if (first === void 0 || nodes[normalizeFileName$3(first.fileName)] === void 0) return Result$1.succeed(result);
139
+ const classified = classifyDiagnosticsPure(diagnostics, mutants, nodes);
140
+ if (Result$1.isFailure(classified)) return Result$1.fail(classified.failure);
141
+ const { definitive } = classified.success;
142
+ for (const id of Object.keys(definitive)) {
143
+ const diags = definitive[id];
144
+ if (diags !== void 0) result[id] = {
145
+ status: "compileError",
146
+ reason: diags.map((d) => d.text).join("\n")
147
+ };
148
+ }
149
+ return Result$1.succeed(result);
150
+ };
151
+ const checkMutants = Workflow.make(CheckMutantsInput, (input) => buildResult(input));
152
+ //#endregion
153
+ //#region src/Compiler.schema.ts
154
+ /**
155
+ * Compiler — declarations for the TypeScript compiler and version guard.
156
+ */
157
+ /** The installed TypeScript version is below the supported floor. */
158
+ var UnsupportedTypeScriptVersionError = class extends Schema.TaggedError()("UnsupportedTypeScriptVersionError", { version: Schema.String }) {
159
+ get message() {
160
+ return `@systemfsoftware/stryker-js-typescript-checker only supports typescript@7.0.0 or higher. Found typescript@${this.version}`;
161
+ }
162
+ };
163
+ /** Requested file is not present in the hybrid in-memory file map. */
164
+ var HybridFileNotFoundError = class extends Schema.TaggedError()("HybridFileNotFoundError", { fileName: Schema.String }) {};
165
+ //#endregion
12
166
  //#region ../../../../../node_modules/.pnpm/@jsr+std__jsonc@1.0.2/node_modules/@jsr/std__jsonc/parse.js
13
167
  /**
14
168
  * Converts a JSON with Comments (JSONC) string into an object.
@@ -229,26 +383,43 @@ function buildErrorMessage({ type, sourceText, position }) {
229
383
  return `Cannot parse JSONC: unexpected token "${token}" in JSONC at position ${position}`;
230
384
  }
231
385
  //#endregion
232
- //#region src/tsconfig-helpers.schema.ts
386
+ //#region src/Tsconfig.schema.ts
233
387
  /**
234
- * Error returned when a tsconfig file fails to parse or does not match the
235
- * shape this package consumes.
388
+ * Tsconfig declarations for the TypeScript configuration consumed by the checker.
389
+ *
390
+ * Typed by Effect Schema and decoded at the boundary; the compiler capability
391
+ * consumes only validated shapes.
236
392
  */
393
+ /** The configured tsconfig failed to parse or is not a shape this package can consume. */
237
394
  var TsConfigParseError = class extends Schema.TaggedError()("TsConfigParseError", {
238
395
  file: Schema.String,
239
396
  reason: Schema.String
240
397
  }) {};
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]);
398
+ /** The configured tsconfig file could not be read. */
399
+ var TsConfigNotFoundError = class extends Schema.TaggedError()("TsConfigNotFoundError", { file: Schema.String }) {
400
+ get message() {
401
+ return `The tsconfig file does not exist at: "${this.file}". Please configure the tsconfig file in your stryker.conf file using "tsconfigFile"`;
402
+ }
403
+ };
404
+ const TsConfigSchema = Schema.Struct({
405
+ references: Schema.optional(Schema.Array(Schema.Struct({ path: Schema.String }))),
406
+ compilerOptions: Schema.optional(Schema.Record(Schema.String, Schema.Unknown))
407
+ });
246
408
  //#endregion
247
- //#region src/tsconfig-helpers.ts
409
+ //#region src/Tsconfig.ts
410
+ /**
411
+ * Tsconfig — capability for reading and normalizing TypeScript project configs.
412
+ *
413
+ * Normalizes via Effect Schema and tightens compilation options for mutation
414
+ * checking (disabling quality checks, toggling emit for build-mode vs
415
+ * single-project).
416
+ */
417
+ const normalizeFileName$2 = (fileName) => fileName.replace(/\\/g, "/");
248
418
  const COMPILER_OPTIONS_OVERRIDES = Object.freeze({
249
419
  allowUnreachableCode: true,
250
420
  noUnusedLocals: false,
251
- noUnusedParameters: false
421
+ noUnusedParameters: false,
422
+ skipLibCheck: true
252
423
  });
253
424
  const NO_EMIT_OPTIONS_FOR_SINGLE_PROJECT = Object.freeze({
254
425
  noEmit: true,
@@ -260,19 +431,9 @@ const LOW_EMIT_OPTIONS_FOR_PROJECT_REFERENCES = Object.freeze({
260
431
  emitDeclarationOnly: true,
261
432
  noEmit: false,
262
433
  declarationMap: true,
263
- declaration: true
434
+ declaration: true,
435
+ composite: true
264
436
  });
265
- let cachedTSVersion;
266
- function getTSVersion() {
267
- if (cachedTSVersion === void 0) {
268
- const require = createRequire(import.meta.url);
269
- cachedTSVersion = Schema.decodeUnknownSync(Schema.Struct({ version: Schema.String }))(JSON.parse(readFileSync(require.resolve("typescript/package.json"), "utf-8"))).version;
270
- }
271
- return cachedTSVersion;
272
- }
273
- function guardTSVersion(version = getTSVersion()) {
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}`);
275
- }
276
437
  /**
277
438
  * Parses the raw text of a tsconfig file into a typed config, rejecting shapes this package cannot consume.
278
439
  * @param fileName The tsconfig file name, used for error reporting
@@ -286,33 +447,40 @@ function parseTsConfig(fileName, jsonText) {
286
447
  reason: error.message
287
448
  }));
288
449
  } catch (error) {
450
+ let reason;
451
+ if (error instanceof Error) reason = error.message;
452
+ else if (typeof error === "string") reason = error;
453
+ else {
454
+ const stringified = JSON.stringify(error);
455
+ if (stringified.length === 0) reason = "a non-Error value was thrown";
456
+ else reason = stringified;
457
+ }
289
458
  return Result.fail(new TsConfigParseError({
290
459
  file: fileName,
291
- reason: error instanceof Error ? error.message : typeof error === "string" ? error : JSON.stringify(error) ?? "a non-Error value was thrown"
460
+ reason
292
461
  }));
293
462
  }
294
463
  }
295
- /**
296
- * Determines whether or not to use `--build` mode based on "references" being there in the config file
297
- * @param tsconfigFileName The tsconfig file to parse
298
- */
299
- function determineBuildModeEnabled(tsconfigFileName) {
300
- const parsed = parseTsConfig(tsconfigFileName, readFileSync(tsconfigFileName, "utf-8"));
464
+ /** Whether `--build` mode should be enabled based on `references` in the tsconfig. */
465
+ const determineBuildModeEnabled = (tsconfigFileName, fsService) => Effect.gen(function* () {
466
+ const parsed = parseTsConfig(tsconfigFileName, yield* fsService.readFileString(tsconfigFileName));
301
467
  return Result.match(parsed, {
302
468
  onFailure: () => false,
303
469
  onSuccess: (config) => config.references !== void 0
304
470
  });
305
- }
471
+ });
306
472
  /**
307
- * Overrides some options to speed up compilation and disable some code quality checks we don't want during mutation testing
308
- * @param config The parsed config file
309
- * @param useBuildMode whether or not `--build` mode is used
473
+ * Overrides compiler options to speed up compilation and disable code quality
474
+ * checks irrelevant during mutation testing.
310
475
  */
311
476
  function overrideOptions(config, useBuildMode) {
477
+ let extraOptions;
478
+ if (useBuildMode) extraOptions = LOW_EMIT_OPTIONS_FOR_PROJECT_REFERENCES;
479
+ else extraOptions = NO_EMIT_OPTIONS_FOR_SINGLE_PROJECT;
312
480
  const compilerOptions = {
313
481
  ...config.compilerOptions,
314
482
  ...COMPILER_OPTIONS_OVERRIDES,
315
- ...useBuildMode ? LOW_EMIT_OPTIONS_FOR_PROJECT_REFERENCES : NO_EMIT_OPTIONS_FOR_SINGLE_PROJECT
483
+ ...extraOptions
316
484
  };
317
485
  if (!useBuildMode && compilerOptions["declarationDir"] !== void 0 && compilerOptions["declarationDir"] !== null) delete compilerOptions["declarationDir"];
318
486
  if (useBuildMode) {
@@ -322,99 +490,247 @@ function overrideOptions(config, useBuildMode) {
322
490
  delete compilerOptions["sourceRoot"];
323
491
  delete compilerOptions["outFile"];
324
492
  }
325
- return JSON.stringify({
493
+ if (useBuildMode) return JSON.stringify({
326
494
  ...config,
327
495
  compilerOptions
328
496
  });
497
+ const { references: _references, ...withoutReferences } = config;
498
+ return JSON.stringify({
499
+ ...withoutReferences,
500
+ compilerOptions
501
+ });
329
502
  }
330
503
  /**
331
- * Retrieves the referenced config files based on parsed configuration
332
- * @param config The parsed config file
333
- * @param fromDirName The directory where to resolve from
504
+ * Retrieves the referenced config files based on parsed configuration.
334
505
  */
335
- function retrieveReferencedProjects(config, fromDirName) {
506
+ function retrieveReferencedProjects(config, fromDirName, pathService) {
336
507
  return (config.references ?? []).map((reference) => {
337
- let resolved = path.resolve(fromDirName, reference.path);
338
- if (!path.basename(resolved).endsWith(".json")) resolved = path.join(resolved, "tsconfig.json");
339
- return toPosixFileName(resolved);
508
+ let resolved = pathService.resolve(fromDirName, reference.path);
509
+ if (!pathService.basename(resolved).endsWith(".json")) resolved = pathService.join(resolved, "tsconfig.json");
510
+ return normalizeFileName$2(resolved);
340
511
  });
341
512
  }
513
+ //#endregion
514
+ //#region src/Compiler.ts
342
515
  /**
343
- * Replaces backslashes with forward slashes (used by typescript)
344
- * @param fileName The file name that may contain backslashes `\`
345
- * @returns posix and ts complaint file name (with `/`)
346
- */
347
- function toPosixFileName(fileName) {
348
- return fileName.replace(/\\/g, "/");
349
- }
350
- /**
351
- * Find source file in declaration file
352
- * @param content The content of the declaration file
353
- * @returns URL of the source file or undefined if not found
516
+ * Compiler capability that hosts the TypeScript language service, the
517
+ * in-memory file system, and the file-graph used for grouping.
518
+ *
519
+ * All `typescript/unstable/*` interaction is confined here; callers consume
520
+ * only the Effect-typed service surface.
354
521
  */
522
+ const normalizeFileName$1 = (fileName) => fileName.replace(/\\/g, "/");
355
523
  const findSourceMapRegex = /\/\/# sourceMappingURL=(.+)$/m;
356
524
  function getSourceMappingURL(content) {
357
- findSourceMapRegex.lastIndex = 0;
358
525
  return findSourceMapRegex.exec(content)?.[1];
359
526
  }
360
- //#endregion
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
527
+ let cachedTSVersion;
528
+ const getTSVersion = (fsService) => Effect.gen(function* () {
529
+ if (cachedTSVersion !== void 0) return cachedTSVersion;
530
+ const pkgPath = createRequire(import.meta.url).resolve("typescript/package.json");
531
+ const text = yield* fsService.readFileString(pkgPath);
532
+ const raw = JSON.parse(text);
533
+ let version = "";
534
+ if (Predicate.hasProperty(raw, "version") && typeof raw.version === "string") version = raw.version;
535
+ cachedTSVersion = version;
536
+ return version;
537
+ });
395
538
  /**
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.
539
+ * Whether a TypeScript version satisfies `>=7.0.0`. Pre-release suffixes are
540
+ * stripped so `7.0.0-beta` compares as `7.0.0`.
403
541
  */
542
+ function isSupportedTypescriptVersion(version) {
543
+ const dashBase = version.split("-")[0] ?? version;
544
+ const parts = (dashBase.split("+")[0] ?? dashBase).split(".").map((p) => Number.parseInt(p, 10));
545
+ const major = parts[0] ?? 0;
546
+ const minor = parts[1] ?? 0;
547
+ const patch = parts[2] ?? 0;
548
+ if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch)) return false;
549
+ if (major !== 7) return major > 7;
550
+ if (minor !== 0) return minor > 0;
551
+ return patch >= 0;
552
+ }
553
+ const guardTSVersion = (fsService) => Effect.gen(function* () {
554
+ const version = yield* getTSVersion(fsService);
555
+ if (!isSupportedTypescriptVersion(version)) return yield* new UnsupportedTypeScriptVersionError({ version });
556
+ });
557
+ function makeScriptFile(content, fileName, modifiedTime = /* @__PURE__ */ new Date()) {
558
+ return {
559
+ content,
560
+ fileName,
561
+ originalContent: content,
562
+ modifiedTime
563
+ };
564
+ }
565
+ function withContent(file, content) {
566
+ return {
567
+ ...file,
568
+ content,
569
+ modifiedTime: /* @__PURE__ */ new Date()
570
+ };
571
+ }
572
+ function mutateScriptFile(file, mutant) {
573
+ const start = getOffset(file, mutant.location.start);
574
+ const end = getOffset(file, mutant.location.end);
575
+ const content = `${file.originalContent.slice(0, start)}${mutant.replacement}${file.originalContent.slice(end)}`;
576
+ return {
577
+ ...file,
578
+ content,
579
+ modifiedTime: /* @__PURE__ */ new Date()
580
+ };
581
+ }
582
+ function resetScriptFile(file) {
583
+ return {
584
+ ...file,
585
+ content: file.originalContent,
586
+ modifiedTime: /* @__PURE__ */ new Date()
587
+ };
588
+ }
589
+ function getOffset(file, pos) {
590
+ const lines = file.originalContent.split("\n");
591
+ const lineCount = Math.min(pos.line, lines.length);
592
+ let offset = 0;
593
+ for (let i = 0; i < lineCount; i++) {
594
+ const line = lines[i];
595
+ if (line === void 0) break;
596
+ offset += line.length + 1;
597
+ }
598
+ offset += pos.column;
599
+ return offset;
600
+ }
601
+ const makeEmptyFilesMap = () => MutableHashMap.empty();
602
+ const makeEmptyOverridesMap = () => MutableHashMap.empty();
603
+ const setInPlace = (map, key, value) => {
604
+ MutableHashMap.set(map, key, value);
605
+ return map;
606
+ };
607
+ const makeHybridFileSystem = (fsService) => Effect.gen(function* () {
608
+ const filesRef = yield* Ref.make(makeEmptyFilesMap());
609
+ const overridesRef = yield* Ref.make(makeEmptyOverridesMap());
610
+ const fileNameIsBuildInfo = (fileName) => fileName.endsWith(".tsbuildinfo");
611
+ const fileSystem = {
612
+ readFile: (fileName) => {
613
+ const normalized = normalizeFileName$1(fileName);
614
+ if (fileNameIsBuildInfo(normalized)) return null;
615
+ const overrideOpt = MutableHashMap.get(overridesRef.ref.current, normalized);
616
+ if (Option.isSome(overrideOpt)) return overrideOpt.value;
617
+ const files = filesRef.ref.current;
618
+ if (MutableHashMap.has(files, normalized)) {
619
+ const fileOpt = MutableHashMap.get(files, normalized);
620
+ if (Option.isSome(fileOpt)) {
621
+ const file = fileOpt.value;
622
+ if (file !== void 0) return file.content;
623
+ return null;
624
+ }
625
+ }
626
+ },
627
+ fileExists: (fileName) => {
628
+ const normalized = normalizeFileName$1(fileName);
629
+ if (fileNameIsBuildInfo(normalized)) return false;
630
+ if (MutableHashMap.has(overridesRef.ref.current, normalized)) return true;
631
+ const files = filesRef.ref.current;
632
+ if (MutableHashMap.has(files, normalized)) {
633
+ const opt = MutableHashMap.get(files, normalized);
634
+ if (Option.isSome(opt)) return opt.value !== void 0;
635
+ return false;
636
+ }
637
+ },
638
+ directoryExists: () => void 0,
639
+ getAccessibleEntries: () => void 0,
640
+ realpath: () => void 0
641
+ };
642
+ const getFile = (fileName) => Effect.gen(function* () {
643
+ const normalized = normalizeFileName$1(fileName);
644
+ const files = yield* Ref.get(filesRef);
645
+ if (MutableHashMap.has(files, normalized)) {
646
+ const opt = MutableHashMap.get(files, normalized);
647
+ if (Option.isSome(opt)) return opt.value;
648
+ }
649
+ const content = yield* fsService.readFileString(normalized).pipe(Effect.orElseSucceed(() => void 0));
650
+ if (content === void 0) {
651
+ yield* Ref.update(filesRef, (m) => setInPlace(m, normalized, void 0));
652
+ return;
653
+ }
654
+ const file = makeScriptFile(content, normalized);
655
+ yield* Ref.update(filesRef, (m) => setInPlace(m, normalized, file));
656
+ return file;
657
+ });
658
+ const writeFile = (fileName, data) => Effect.gen(function* () {
659
+ const normalized = normalizeFileName$1(fileName);
660
+ const files = yield* Ref.get(filesRef);
661
+ const existingOpt = MutableHashMap.get(files, normalized);
662
+ let existing = void 0;
663
+ if (Option.isSome(existingOpt)) existing = existingOpt.value;
664
+ if (existing !== void 0) {
665
+ const next = withContent(existing, data);
666
+ yield* Ref.update(filesRef, (m) => setInPlace(m, normalized, next));
667
+ } else {
668
+ const file = makeScriptFile(data, normalized);
669
+ yield* Ref.update(filesRef, (m) => setInPlace(m, normalized, file));
670
+ }
671
+ });
672
+ const mutateFile = (fileName, mutant) => Effect.gen(function* () {
673
+ const file = yield* getFile(fileName);
674
+ if (file === void 0) return yield* new HybridFileNotFoundError({ fileName });
675
+ const next = mutateScriptFile(file, mutant);
676
+ const normalized = normalizeFileName$1(fileName);
677
+ yield* Ref.update(filesRef, (m) => setInPlace(m, normalized, next));
678
+ });
679
+ const resetFile = (fileName) => Effect.gen(function* () {
680
+ const normalized = normalizeFileName$1(fileName);
681
+ const files = yield* Ref.get(filesRef);
682
+ const opt = MutableHashMap.get(files, normalized);
683
+ let file = void 0;
684
+ if (Option.isSome(opt)) file = opt.value;
685
+ if (file !== void 0) {
686
+ const next = resetScriptFile(file);
687
+ yield* Ref.update(filesRef, (m) => setInPlace(m, normalized, next));
688
+ }
689
+ });
690
+ const existsInMemory = (fileName) => Effect.gen(function* () {
691
+ const files = yield* Ref.get(filesRef);
692
+ const opt = MutableHashMap.get(files, normalizeFileName$1(fileName));
693
+ let file = void 0;
694
+ if (Option.isSome(opt)) file = opt.value;
695
+ return file !== void 0;
696
+ });
697
+ const setTsConfigOverrides = (overrides) => Ref.set(overridesRef, overrides);
698
+ return {
699
+ fileSystem,
700
+ getFile,
701
+ writeFile,
702
+ mutateFile,
703
+ resetFile,
704
+ existsInMemory,
705
+ setTsConfigOverrides
706
+ };
707
+ });
708
+ function makeTSFileNode(fileName) {
709
+ return {
710
+ fileName,
711
+ parents: [],
712
+ children: []
713
+ };
714
+ }
715
+ function getAllParentReferencesIncludingSelf(node, allParentReferences = MutableHashSet.empty()) {
716
+ MutableHashSet.add(allParentReferences, node);
717
+ for (const parent of node.parents) if (!MutableHashSet.has(allParentReferences, parent)) getAllParentReferencesIncludingSelf(parent, allParentReferences);
718
+ return allParentReferences;
719
+ }
404
720
  function createGroups(mutants, nodes) {
405
721
  const groups = [];
406
- const mutantsToGroup = new Set(mutants);
407
- while (mutantsToGroup.size) {
722
+ const mutantsToGroup = MutableHashSet.fromIterable(mutants);
723
+ while (MutableHashSet.size(mutantsToGroup) > 0) {
408
724
  const group = [];
409
- const groupNodes = /* @__PURE__ */ new Set();
410
- const nodesToIgnore = /* @__PURE__ */ new Set();
725
+ const groupNodes = MutableHashSet.empty();
726
+ const nodesToIgnore = MutableHashSet.empty();
411
727
  for (const currentMutant of mutantsToGroup) {
412
728
  const currentNode = findNode(currentMutant.fileName, nodes);
413
- if (!nodesToIgnore.has(currentNode) && !parentsHaveOverlapWith(currentNode, groupNodes)) {
729
+ if (!MutableHashSet.has(nodesToIgnore, currentNode) && !parentsHaveOverlapWith(currentNode, groupNodes)) {
414
730
  group.push(currentMutant.id);
415
- groupNodes.add(currentNode);
416
- mutantsToGroup.delete(currentMutant);
417
- addRangeOfNodesToSet(nodesToIgnore, currentNode.getAllParentReferencesIncludingSelf());
731
+ MutableHashSet.add(groupNodes, currentNode);
732
+ MutableHashSet.remove(mutantsToGroup, currentMutant);
733
+ addRangeOfNodesToSet(nodesToIgnore, getAllParentReferencesIncludingSelf(currentNode));
418
734
  }
419
735
  }
420
736
  groups.push(group);
@@ -422,264 +738,111 @@ function createGroups(mutants, nodes) {
422
738
  return groups;
423
739
  }
424
740
  function addRangeOfNodesToSet(nodes, nodesToAdd) {
425
- for (const parent of nodesToAdd) nodes.add(parent);
741
+ for (const parent of nodesToAdd) MutableHashSet.add(nodes, parent);
426
742
  }
427
743
  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;
744
+ const nodeOption = MutableHashMap.get(nodes, normalizeFileName$1(fileName));
745
+ if (Option.isSome(nodeOption)) return nodeOption.value;
746
+ const fallbackOption = MutableHashMap.get(nodes, fileName);
747
+ if (Option.isSome(fallbackOption)) return fallbackOption.value;
748
+ throw new Error(`Node not in graph: ${fileName}`);
431
749
  }
432
750
  function parentsHaveOverlapWith(currentNode, groupNodes) {
433
- for (const parentNode of currentNode.getAllParentReferencesIncludingSelf()) if (groupNodes.has(parentNode)) return true;
751
+ for (const parentNode of getAllParentReferencesIncludingSelf(currentNode)) if (MutableHashSet.has(groupNodes, parentNode)) return true;
434
752
  return false;
435
753
  }
436
- const tsCompiler = "tsCompiler";
437
- //#endregion
438
- //#region src/project/script-file.ts
439
- var ScriptFile = class {
440
- content;
441
- fileName;
442
- modifiedTime;
443
- originalContent;
444
- constructor(content, fileName, modifiedTime = /* @__PURE__ */ new Date()) {
445
- this.content = content;
446
- this.fileName = fileName;
447
- this.modifiedTime = modifiedTime;
448
- this.originalContent = content;
449
- }
450
- write(content) {
451
- this.content = content;
452
- this.touch();
453
- }
454
- mutate(mutant) {
455
- const start = this.getOffset(mutant.location.start);
456
- const end = this.getOffset(mutant.location.end);
457
- this.content = `${this.originalContent.slice(0, start)}${mutant.replacement}${this.originalContent.slice(end)}`;
458
- this.touch();
459
- }
460
- getOffset(pos) {
461
- const lines = this.originalContent.split("\n");
462
- const lineCount = Math.min(pos.line, lines.length);
463
- let offset = 0;
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
- }
469
- offset += pos.column;
470
- return offset;
471
- }
472
- resetMutant() {
473
- this.content = this.originalContent;
474
- this.touch();
475
- }
476
- touch() {
477
- this.modifiedTime = /* @__PURE__ */ new Date();
478
- }
479
- };
480
- //#endregion
481
- //#region src/project/hybrid-file-system.ts
482
- /**
483
- * A very simple hybrid file system.
484
- * * Readonly from disk
485
- * * Writes in-memory
486
- * * Hard caching
487
- * * Ability to mutate one file
488
- */
489
- var HybridFileSystem = class {
490
- files = /* @__PURE__ */ new Map();
491
- /**
492
- * Map of absolute tsconfig file paths to their adjusted JSON content.
493
- * This allows the TS7 API to read overridden compiler options.
494
- */
495
- tsConfigOverrides = /* @__PURE__ */ new Map();
496
- readFile = (fileName) => {
497
- const normalized = toPosixFileName(fileName);
498
- if (this.fileNameIsBuildInfo(normalized)) return null;
499
- const override = this.tsConfigOverrides.get(normalized);
500
- if (override !== void 0) return override;
501
- const file = this.files.get(normalized);
502
- if (file) return file.content;
503
- if (file === void 0 && this.files.has(normalized)) return null;
754
+ var TypeScriptCompiler = class extends Context.Service()("@systemfsoftware/stryker-js-typescript-checker/TypeScriptCompiler") {};
755
+ const makeDummy = Effect.gen(function* () {
756
+ const stateRef = yield* Ref.make({
757
+ api: void 0,
758
+ snapshot: void 0,
759
+ sourceFiles: MutableHashMap.empty(),
760
+ nodes: MutableHashMap.empty(),
761
+ lastMutants: [],
762
+ lastMutatedFileNames: [],
763
+ allTSConfigFiles: MutableHashSet.fromIterable(["tsconfig.json"]),
764
+ tsconfigFile: "tsconfig.json"
765
+ });
766
+ yield* Effect.addFinalizer(() => Effect.gen(function* () {
767
+ const s = yield* Ref.get(stateRef);
768
+ yield* Effect.sync(() => s.snapshot?.dispose());
769
+ yield* Effect.sync(() => s.api?.close());
770
+ }));
771
+ return {
772
+ init: Effect.succeed([]),
773
+ check: () => Effect.succeed([]),
774
+ nodes: Ref.get(stateRef).pipe(Effect.map((s) => s.nodes)),
775
+ close: Effect.gen(function* () {
776
+ const s = yield* Ref.get(stateRef);
777
+ yield* Effect.sync(() => s.snapshot?.dispose());
778
+ yield* Effect.sync(() => s.api?.close());
779
+ yield* Ref.update(stateRef, (prev) => ({
780
+ ...prev,
781
+ snapshot: void 0,
782
+ api: void 0
783
+ }));
784
+ }),
785
+ getLineAndCharacterOfPosition: () => Effect.succeed(void 0)
504
786
  };
505
- fileExists = (fileName) => {
506
- const normalized = toPosixFileName(fileName);
507
- if (this.fileNameIsBuildInfo(normalized)) return false;
508
- if (this.tsConfigOverrides.has(normalized)) return true;
509
- if (this.files.has(normalized)) return this.files.get(normalized) !== void 0;
787
+ });
788
+ Layer.effect(TypeScriptCompiler)(makeDummy);
789
+ function makeTypescriptCompiler(options, fs, fsService, pathService) {
790
+ if (!S.is(StrykerOptionsSchema)(options)) throw new Error("Invalid StrykerOptions");
791
+ const rawTsconfigFile = normalizeFileName$1(options.tsconfigFile);
792
+ const initialState = {
793
+ api: void 0,
794
+ snapshot: void 0,
795
+ sourceFiles: MutableHashMap.empty(),
796
+ nodes: MutableHashMap.empty(),
797
+ lastMutants: [],
798
+ lastMutatedFileNames: [],
799
+ allTSConfigFiles: MutableHashSet.fromIterable([rawTsconfigFile]),
800
+ tsconfigFile: rawTsconfigFile
510
801
  };
511
- directoryExists = () => {};
512
- getAccessibleEntries = () => {};
513
- realpath = () => {};
514
- writeFile(fileName, data) {
515
- const normalized = toPosixFileName(fileName);
516
- const existingFile = this.files.get(normalized);
517
- if (existingFile) existingFile.write(data);
518
- else this.files.set(normalized, new ScriptFile(data, normalized));
519
- }
520
- getFile(fileName) {
521
- const normalized = toPosixFileName(fileName);
522
- if (!this.files.has(normalized)) try {
523
- const content = readFileSync(normalized, "utf-8");
524
- this.files.set(normalized, new ScriptFile(content, normalized));
525
- } catch {
526
- this.files.set(normalized, void 0);
527
- }
528
- return this.files.get(normalized);
529
- }
530
- mutateFile(fileName, mutant) {
531
- const file = this.getFile(fileName);
532
- if (!file) throw new Error(`Tried to mutate file "${fileName}" but it could not be found.`);
533
- file.mutate(mutant);
534
- }
535
- resetFile(fileName) {
536
- this.getFile(fileName)?.resetMutant();
537
- }
538
- existsInMemory(fileName) {
539
- return this.files.get(toPosixFileName(fileName)) !== void 0;
540
- }
541
- fileNameIsBuildInfo(fileName) {
542
- return fileName.endsWith(".tsbuildinfo");
543
- }
544
- };
545
- //#endregion
546
- //#region src/typescript-compiler.ts
547
- var TypescriptCompiler = class {
548
- log;
549
- options;
550
- fs;
551
- static inject = tokens(commonTokens.logger, commonTokens.options, "fs");
552
- allTSConfigFiles;
553
- tsconfigFile;
554
- api;
555
- snapshot;
556
- sourceFiles = /* @__PURE__ */ new Map();
557
- _nodes = /* @__PURE__ */ new Map();
558
- lastMutants = [];
559
- lastMutatedFileNames = [];
560
- constructor(log, options, fs) {
561
- this.log = log;
562
- this.options = options;
563
- this.fs = fs;
564
- this.tsconfigFile = toPosixFileName(path.resolve(toPosixFileName(this.options.tsconfigFile)));
565
- this.allTSConfigFiles = /* @__PURE__ */ new Set([this.tsconfigFile]);
566
- }
567
- async init() {
568
- guardTSVersion();
569
- this.guardTSConfigFileExists();
570
- const buildModeEnabled = determineBuildModeEnabled(this.tsconfigFile);
571
- this.collectAllTSConfigFiles(buildModeEnabled);
572
- this.api = new API({ fs: this.fs });
573
- this.snapshot = this.api.updateSnapshot({ openProjects: [...this.allTSConfigFiles] });
574
- const programs = this.getPrograms();
575
- this.buildDependencyGraph(programs);
576
- return this.check([]);
577
- }
578
- async check(mutants) {
579
- for (const mutant of this.lastMutants) this.fs.resetFile(mutant.fileName);
580
- for (const mutant of mutants) {
581
- const file = this.fs.getFile(mutant.fileName);
582
- if (!file) throw new Error(`Tried to check file "${mutant.fileName}" (which is part of your typescript project), but it could not be found.`);
583
- file.mutate(mutant);
584
- }
585
- const mutatedFileNames = [...new Set(mutants.map((m) => toPosixFileName(m.fileName)))];
586
- const changedFiles = [.../* @__PURE__ */ new Set([...this.lastMutatedFileNames, ...mutatedFileNames])];
587
- if (this.api && this.snapshot) {
588
- const oldSnapshot = this.snapshot;
589
- this.snapshot = this.api.updateSnapshot({
590
- openProjects: [...this.allTSConfigFiles],
591
- fileChanges: { changed: changedFiles }
592
- });
593
- oldSnapshot.dispose();
594
- }
595
- this.lastMutants = mutants;
596
- this.lastMutatedFileNames = mutatedFileNames;
597
- return this.getPrograms().flatMap((program) => [
598
- ...program.getConfigFileParsingDiagnostics(),
599
- ...program.getSemanticDiagnostics(),
600
- ...program.getProgramDiagnostics()
601
- ]).filter((diagnostic) => diagnostic.category === DiagnosticCategory.Error);
602
- }
603
- get nodes() {
604
- if (!this._nodes.size) {
605
- for (const [fileName] of this.sourceFiles) {
606
- const node = new TSFileNode(fileName, [], []);
607
- this._nodes.set(fileName, node);
608
- }
609
- for (const [fileName, file] of this.sourceFiles) {
610
- const node = this._nodes.get(fileName);
611
- if (node == null) throw new Error(`Node for file '${fileName}' could not be found. This should not happen.`);
612
- node.children = [...file.imports].map((importName) => this._nodes.get(importName)).filter((n) => n != null);
613
- }
614
- for (const [, node] of this._nodes) {
615
- node.parents = [];
616
- for (const [, n] of this._nodes) if (n.children.includes(node)) node.parents.push(n);
617
- }
618
- }
619
- return this._nodes;
620
- }
621
- close() {
622
- this.snapshot?.dispose();
623
- this.api?.close();
624
- }
625
- getLineAndCharacterOfPosition(fileName, position) {
626
- for (const program of this.getPrograms()) {
627
- const sourceFile = program.getSourceFile(fileName);
628
- if (sourceFile) return sourceFile.getLineAndCharacterOfPosition(position);
629
- }
630
- }
631
- getPrograms() {
632
- if (!this.snapshot) throw new Error("TypescriptCompiler not initialized");
633
- const projects = this.snapshot.getProjects();
634
- if (projects.length === 0) throw new Error(`No projects found for ${this.tsconfigFile}`);
802
+ const stateRef = Ref.makeUnsafe(initialState);
803
+ const getProgramsEffect = () => Effect.gen(function* () {
804
+ const s = yield* Ref.get(stateRef);
805
+ if (!s.snapshot) return yield* new CompilerFailed({ reason: "not-initialized" });
806
+ const projects = s.snapshot.getProjects();
807
+ if (projects.length === 0) return yield* new CompilerFailed({
808
+ reason: "no-projects",
809
+ subject: s.tsconfigFile
810
+ });
635
811
  return projects.map((project) => project.program);
636
- }
637
- collectAllTSConfigFiles(buildModeEnabled) {
638
- const tsConfigOverrides = /* @__PURE__ */ new Map();
639
- const toProcess = [this.tsconfigFile];
640
- const processed = /* @__PURE__ */ new Set();
812
+ });
813
+ const guardTSConfigFileExistsEffect = Effect.gen(function* () {
814
+ const s = yield* Ref.get(stateRef);
815
+ yield* fsService.readFileString(s.tsconfigFile).pipe(Effect.mapError(() => new TsConfigNotFoundError({ file: s.tsconfigFile })));
816
+ });
817
+ const collectAllTSConfigFiles = (buildModeEnabled) => Effect.gen(function* () {
818
+ const s = yield* Ref.get(stateRef);
819
+ const tsConfigOverrides = MutableHashMap.empty();
820
+ const toProcess = [s.tsconfigFile];
821
+ const processed = MutableHashSet.empty();
641
822
  while (toProcess.length > 0) {
642
823
  const current = toProcess.pop();
643
- if (!current || processed.has(current)) continue;
644
- processed.add(current);
645
- const content = readFileSync(current, "utf-8");
824
+ if (current === void 0 || current === "" || MutableHashSet.has(processed, current)) continue;
825
+ MutableHashSet.add(processed, current);
826
+ const content = yield* fsService.readFileString(current);
646
827
  const parsed = parseTsConfig(current, content);
647
828
  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);
649
- tsConfigOverrides.set(current, content);
829
+ MutableHashMap.set(tsConfigOverrides, current, content);
650
830
  continue;
651
831
  }
652
- tsConfigOverrides.set(current, overrideOptions(parsed.success, buildModeEnabled));
653
- for (const referenced of retrieveReferencedProjects(parsed.success, path.dirname(current))) {
654
- this.allTSConfigFiles.add(referenced);
832
+ MutableHashMap.set(tsConfigOverrides, current, overrideOptions(parsed.success, buildModeEnabled));
833
+ for (const referenced of retrieveReferencedProjects(parsed.success, pathService.dirname(current), pathService)) {
834
+ const normalized = normalizeFileName$1(referenced);
835
+ MutableHashSet.add(s.allTSConfigFiles, normalized);
655
836
  toProcess.push(referenced);
656
837
  }
657
838
  }
658
- this.fs.tsConfigOverrides = tsConfigOverrides;
659
- }
660
- buildDependencyGraph(programs) {
661
- for (const program of programs) for (const fileName of program.getSourceFileNames()) {
662
- if (fileName.endsWith(".d.ts") || fileName.includes("node_modules")) continue;
663
- const normalized = toPosixFileName(fileName);
664
- this.sourceFiles.set(normalized, {
665
- fileName: normalized,
666
- imports: /* @__PURE__ */ new Set()
667
- });
668
- }
669
- for (const [fileName] of this.sourceFiles) {
670
- const sourceFile = programs.map((p) => p.getSourceFile(fileName)).find((sf) => sf != null);
671
- if (!sourceFile) continue;
672
- const imports = this.extractImports(sourceFile);
673
- for (const specifier of imports) {
674
- const resolved = this.resolveModuleSpecifier(fileName, specifier);
675
- if (resolved) {
676
- const sourceFileName = this.resolveTSInputFile(resolved);
677
- if (this.sourceFiles.has(sourceFileName)) this.sourceFiles.get(fileName)?.imports.add(sourceFileName);
678
- }
679
- }
680
- }
681
- }
682
- extractImports(sourceFile) {
839
+ yield* fs.setTsConfigOverrides(tsConfigOverrides);
840
+ yield* Ref.update(stateRef, (prev) => ({
841
+ ...prev,
842
+ allTSConfigFiles: MutableHashSet.fromIterable(s.allTSConfigFiles)
843
+ }));
844
+ });
845
+ const extractImports = (sourceFile) => {
683
846
  const result = [];
684
847
  for (const statement of sourceFile.statements) if (statement.kind === SyntaxKind.ImportDeclaration) {
685
848
  let spec;
@@ -695,17 +858,9 @@ var TypescriptCompiler = class {
695
858
  for (const ref of sourceFile.referencedFiles) result.push(ref.fileName);
696
859
  for (const ref of sourceFile.typeReferenceDirectives) result.push(ref.fileName);
697
860
  return result;
698
- }
699
- resolveModuleSpecifier(sourceFileName, specifier) {
700
- const cleaned = specifier.replace(/^['"]|['"]$/g, "");
701
- if (!cleaned.startsWith("./") && !cleaned.startsWith("../")) return;
702
- const baseDir = path.dirname(sourceFileName);
703
- const resolved = toPosixFileName(path.resolve(baseDir, cleaned));
704
- const candidates = this.getResolutionCandidates(resolved);
705
- for (const candidate of candidates) if (this.sourceFiles.has(candidate)) return candidate;
706
- }
707
- getResolutionCandidates(resolved) {
708
- const extension = path.extname(resolved);
861
+ };
862
+ const getResolutionCandidates = (resolved, pathService) => {
863
+ const extension = pathService.extname(resolved);
709
864
  if (extension) {
710
865
  const withoutExt = resolved.slice(0, -extension.length);
711
866
  return [
@@ -736,124 +891,332 @@ var TypescriptCompiler = class {
736
891
  `${resolved}/index.mjs`,
737
892
  `${resolved}/index.cjs`
738
893
  ];
739
- }
740
- resolveTSInputFile(dependencyFileName) {
894
+ };
895
+ const resolveModuleSpecifier = (sourceFileName, specifier, sourceFiles, pathService) => {
896
+ const cleaned = specifier.replace(/^['"]|['"]$/g, "");
897
+ if (!cleaned.startsWith("./") && !cleaned.startsWith("../")) return;
898
+ const baseDir = pathService.dirname(sourceFileName);
899
+ const resolved = normalizeFileName$1(pathService.resolve(baseDir, cleaned));
900
+ const candidates = getResolutionCandidates(resolved, pathService);
901
+ for (const candidate of candidates) if (MutableHashMap.has(sourceFiles, candidate)) return candidate;
902
+ };
903
+ const resolveTSInputFile = (dependencyFileName, pathService) => {
741
904
  if (!dependencyFileName.endsWith(".d.ts")) return dependencyFileName;
742
- const file = this.fs.getFile(dependencyFileName);
743
- if (!file) return dependencyFileName;
744
- const sourceMappingURL = getSourceMappingURL(file.content);
745
- if (!sourceMappingURL) return dependencyFileName;
746
- const sourceMapFileName = toPosixFileName(path.resolve(path.dirname(dependencyFileName), sourceMappingURL));
747
- const sourceMap = this.fs.getFile(sourceMapFileName);
748
- if (!sourceMap) {
749
- this.log.warn(`Could not find sourcemap ${sourceMapFileName}`);
750
- return dependencyFileName;
751
- }
752
- const sources = Schema.decodeUnknownSync(Schema.Struct({ sources: Schema.optional(Schema.Array(Schema.String)) }))(JSON.parse(sourceMap.content)).sources;
905
+ const content = fs.fileSystem.readFile?.(dependencyFileName);
906
+ if (typeof content !== "string") return dependencyFileName;
907
+ const sourceMappingURL = getSourceMappingURL(content);
908
+ if (sourceMappingURL === void 0 || sourceMappingURL === "") return dependencyFileName;
909
+ const sourceMapFileName = normalizeFileName$1(pathService.resolve(pathService.dirname(dependencyFileName), sourceMappingURL));
910
+ const sourceMapContent = fs.fileSystem.readFile?.(sourceMapFileName);
911
+ if (typeof sourceMapContent !== "string") return dependencyFileName;
912
+ const rawMap = JSON.parse(sourceMapContent);
913
+ let sources;
914
+ if (Predicate.hasProperty(rawMap, "sources") && Array.isArray(rawMap.sources)) sources = rawMap.sources.filter((s) => typeof s === "string");
753
915
  if (sources?.length === 1) {
754
916
  const sourcePath = sources[0];
755
917
  if (sourcePath === void 0) return dependencyFileName;
756
- return toPosixFileName(path.resolve(path.dirname(sourceMapFileName), sourcePath));
918
+ return normalizeFileName$1(pathService.resolve(pathService.dirname(sourceMapFileName), sourcePath));
757
919
  }
758
920
  return dependencyFileName;
759
- }
760
- guardTSConfigFileExists() {
761
- try {
762
- readFileSync(this.tsconfigFile, "utf-8");
763
- } catch {
764
- throw new Error(`The tsconfig file does not exist at: "${this.tsconfigFile}". Please configure the tsconfig file in your stryker.conf file using "tsconfigFile"`);
765
- }
766
- }
767
- };
768
- //#endregion
769
- //#region src/typescript-checker.ts
770
- const typescriptCheckerLoggerFactory = Object.assign((loggerFactory, target) => {
771
- const targetName = target?.name ?? TypescriptChecker.name;
772
- return loggerFactory(targetName === TypescriptChecker.name ? TypescriptChecker.name : `${TypescriptChecker.name}.${targetName}`);
773
- }, { inject: tokens(commonTokens.getLogger, commonTokens.target) });
774
- const create = Object.assign((injector) => injector.provideFactory(commonTokens.logger, typescriptCheckerLoggerFactory, Scope.Transient).provideClass("fs", HybridFileSystem).provideClass(tsCompiler, TypescriptCompiler).injectClass(TypescriptChecker), { inject: tokens(commonTokens.injector) });
775
- var TypescriptChecker = class {
776
- logger;
777
- tsCompiler;
778
- static inject = tokens(commonTokens.logger, commonTokens.options, tsCompiler);
779
- options;
780
- constructor(logger, options, tsCompiler) {
781
- this.logger = logger;
782
- this.tsCompiler = tsCompiler;
783
- this.options = options;
784
- }
785
- async init() {
786
- const errors = await this.tsCompiler.init();
787
- if (errors.length) throw new Error(`Typescript error(s) found in dry run compilation: ${this.createErrorText(errors)}`);
788
- }
789
- async check(mutants) {
790
- const result = Object.fromEntries(mutants.map((mutant) => [mutant.id, { status: CheckStatus.Passed }]));
791
- const firstMutant = mutants[0];
792
- if (!firstMutant || !this.tsCompiler.nodes.get(toPosixFileName(firstMutant.fileName))) return result;
793
- const mutantErrorRelationMap = await this.checkErrors(mutants, {}, this.tsCompiler.nodes);
794
- for (const [id, errors] of Object.entries(mutantErrorRelationMap)) result[id] = {
795
- status: CheckStatus.CompileError,
796
- reason: this.createErrorText(errors)
797
- };
798
- return result;
799
- }
800
- group(mutants) {
801
- if (!this.options.typescriptChecker?.prioritizePerformanceOverAccuracy) return Promise.resolve(mutants.map((m) => [m.id]));
802
- const { nodes } = this.tsCompiler;
803
- const [mutantsOutsideProject, mutantsInProject] = split(mutants, (m) => nodes.get(toPosixFileName(m.fileName)) == null);
804
- const groups = createGroups(mutantsInProject, nodes);
805
- if (mutantsOutsideProject.length) return Promise.resolve([mutantsOutsideProject.map((m) => m.id), ...groups]);
806
- else return Promise.resolve(groups);
807
- }
808
- async checkErrors(mutants, errorsMap, nodes) {
809
- const errors = await this.tsCompiler.check(mutants);
810
- const mutantsThatCouldNotBeTestedInGroups = /* @__PURE__ */ new Set();
811
- if (errors.length && mutants.length === 1) {
812
- const onlyMutant = mutants[0];
813
- if (onlyMutant !== void 0) errorsMap[onlyMutant.id] = errors;
814
- return errorsMap;
921
+ };
922
+ const buildDependencyGraph = (programs) => Effect.gen(function* () {
923
+ const s = yield* Ref.get(stateRef);
924
+ for (const program of programs) for (const fileName of program.getSourceFileNames()) {
925
+ if (fileName.endsWith(".d.ts") || fileName.includes("node_modules")) continue;
926
+ const normalized = normalizeFileName$1(fileName);
927
+ MutableHashMap.set(s.sourceFiles, normalized, {
928
+ fileName: normalized,
929
+ imports: MutableHashSet.empty()
930
+ });
815
931
  }
816
- for (const error of errors) {
817
- if (!error.fileName) throw new Error(`Typescript error: '${error.text}' was reported without a corresponding file. This shouldn't happen. Please open an issue using this link: ${strykerReportBugUrl(`[BUG]: TypeScript checker reports compile error without a corresponding file: ${error.text}`)}`);
818
- const nodeErrorWasThrownIn = nodes.get(error.fileName);
819
- if (!nodeErrorWasThrownIn) throw new Error(`Typescript error: '${error.text}' was reported in an unrelated file (${error.fileName}). This file is not part of your project, or referenced from your project. This shouldn't happen, please open an issue using this link: ${strykerReportBugUrl(`[BUG]: TypeScript checker reports compile error in an unrelated file: ${error.text}`)}`);
820
- const mutantsRelatedToError = nodeErrorWasThrownIn.getMutantsWithReferenceToChildrenOrSelf(mutants);
821
- if (mutantsRelatedToError.length === 0) for (const mutant of mutants) mutantsThatCouldNotBeTestedInGroups.add(mutant);
822
- else if (mutantsRelatedToError.length === 1) {
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];
932
+ for (const [fileName] of s.sourceFiles) {
933
+ const sourceFile = programs.map((p) => p.getSourceFile(fileName)).find((sf) => sf != null);
934
+ if (!sourceFile) continue;
935
+ const imports = extractImports(sourceFile);
936
+ for (const specifier of imports) {
937
+ const resolved = resolveModuleSpecifier(fileName, specifier, s.sourceFiles, pathService);
938
+ if (resolved !== void 0 && resolved !== "") {
939
+ const sourceFileName = resolveTSInputFile(resolved, pathService);
940
+ if (MutableHashMap.has(s.sourceFiles, sourceFileName)) {
941
+ const entryOpt = MutableHashMap.get(s.sourceFiles, fileName);
942
+ if (Option.isSome(entryOpt)) MutableHashSet.add(entryOpt.value.imports, sourceFileName);
943
+ }
828
944
  }
829
- } else for (const mutant of mutantsRelatedToError) mutantsThatCouldNotBeTestedInGroups.add(mutant);
945
+ }
830
946
  }
831
- if (mutantsThatCouldNotBeTestedInGroups.size) await this.tsCompiler.check([]);
832
- for (const mutant of mutantsThatCouldNotBeTestedInGroups) {
833
- if (errorsMap[mutant.id]) continue;
834
- await this.checkErrors([mutant], errorsMap, nodes);
947
+ yield* Ref.update(stateRef, (prev) => ({
948
+ ...prev,
949
+ sourceFiles: MutableHashMap.fromIterable(s.sourceFiles)
950
+ }));
951
+ });
952
+ const getNodesEffect = Effect.gen(function* () {
953
+ const s = yield* Ref.get(stateRef);
954
+ if (MutableHashMap.size(s.nodes) > 0) return s.nodes;
955
+ for (const [fileName] of s.sourceFiles) {
956
+ const node = makeTSFileNode(fileName);
957
+ MutableHashMap.set(s.nodes, fileName, node);
835
958
  }
836
- return errorsMap;
837
- }
838
- createErrorText(errors) {
839
- return errors.map((error) => this.formatDiagnostic(error)).join(EOL);
959
+ const withChildren = MutableHashMap.empty();
960
+ for (const [fileName, file] of s.sourceFiles) {
961
+ const nodeOpt = MutableHashMap.get(s.nodes, fileName);
962
+ if (Option.isNone(nodeOpt)) return yield* new CompilerFailed({
963
+ reason: "unknown-file-node",
964
+ subject: fileName
965
+ });
966
+ const node = nodeOpt.value;
967
+ const children = Array.from(file.imports).map((importName) => Option.getOrUndefined(MutableHashMap.get(s.nodes, importName))).filter((n) => n !== void 0);
968
+ MutableHashMap.set(withChildren, fileName, {
969
+ ...node,
970
+ children,
971
+ parents: []
972
+ });
973
+ }
974
+ MutableHashMap.clear(s.nodes);
975
+ for (const [k, v] of withChildren) MutableHashMap.set(s.nodes, k, v);
976
+ const withParents = MutableHashMap.empty();
977
+ for (const [fileName, node] of s.nodes) {
978
+ const parents = [];
979
+ for (const [, n] of s.nodes) if (n.children.includes(node)) parents.push(n);
980
+ MutableHashMap.set(withParents, fileName, {
981
+ ...node,
982
+ parents
983
+ });
984
+ }
985
+ MutableHashMap.clear(s.nodes);
986
+ for (const [k, v] of withParents) MutableHashMap.set(s.nodes, k, v);
987
+ yield* Ref.update(stateRef, (prev) => ({
988
+ ...prev,
989
+ nodes: MutableHashMap.fromIterable(s.nodes)
990
+ }));
991
+ return s.nodes;
992
+ });
993
+ const check = (mutants) => Effect.gen(function* () {
994
+ const state = yield* Ref.get(stateRef);
995
+ for (const mutant of state.lastMutants) yield* fs.resetFile(mutant.fileName);
996
+ for (const mutant of mutants) {
997
+ if (!(yield* fs.getFile(mutant.fileName))) return yield* new CompilerFailed({
998
+ reason: "file-not-in-project",
999
+ subject: mutant.fileName
1000
+ });
1001
+ yield* fs.mutateFile(mutant.fileName, mutant);
1002
+ }
1003
+ const mutatedFileNames = Array.from(MutableHashSet.fromIterable(mutants.map((m) => normalizeFileName$1(m.fileName))));
1004
+ const changedFiles = Array.from(MutableHashSet.fromIterable([...state.lastMutatedFileNames, ...mutatedFileNames]));
1005
+ const current = yield* Ref.get(stateRef);
1006
+ if (current.api && current.snapshot) {
1007
+ const oldSnapshot = current.snapshot;
1008
+ const nextSnapshot = current.api.updateSnapshot({
1009
+ openProjects: Array.from(current.allTSConfigFiles),
1010
+ fileChanges: { changed: changedFiles }
1011
+ });
1012
+ yield* Effect.sync(() => oldSnapshot.dispose());
1013
+ yield* Ref.update(stateRef, (prev) => ({
1014
+ ...prev,
1015
+ snapshot: nextSnapshot
1016
+ }));
1017
+ }
1018
+ yield* Ref.update(stateRef, (prev) => ({
1019
+ ...prev,
1020
+ lastMutants: [...mutants],
1021
+ lastMutatedFileNames: mutatedFileNames
1022
+ }));
1023
+ return (yield* getProgramsEffect()).flatMap((program) => [
1024
+ ...program.getConfigFileParsingDiagnostics(),
1025
+ ...program.getSemanticDiagnostics(),
1026
+ ...program.getProgramDiagnostics()
1027
+ ]).filter((diagnostic) => diagnostic.category === DiagnosticCategory.Error);
1028
+ });
1029
+ const init = Effect.gen(function* () {
1030
+ yield* guardTSVersion(fsService);
1031
+ const absoluteTsconfigFile = normalizeFileName$1(pathService.resolve(rawTsconfigFile));
1032
+ yield* Ref.update(stateRef, (prev) => ({
1033
+ ...prev,
1034
+ tsconfigFile: absoluteTsconfigFile,
1035
+ allTSConfigFiles: MutableHashSet.fromIterable([absoluteTsconfigFile])
1036
+ }));
1037
+ yield* guardTSConfigFileExistsEffect;
1038
+ const buildModeEnabled = yield* determineBuildModeEnabled(absoluteTsconfigFile, fsService);
1039
+ yield* collectAllTSConfigFiles(buildModeEnabled);
1040
+ const s = yield* Ref.get(stateRef);
1041
+ const api = new API({ fs: fs.fileSystem });
1042
+ const snapshot = api.updateSnapshot({ openProjects: Array.from(s.allTSConfigFiles) });
1043
+ yield* Ref.update(stateRef, (prev) => ({
1044
+ ...prev,
1045
+ api,
1046
+ snapshot
1047
+ }));
1048
+ const programs = yield* getProgramsEffect();
1049
+ yield* buildDependencyGraph(programs);
1050
+ return yield* check([]);
1051
+ });
1052
+ const close = Effect.gen(function* () {
1053
+ const s = yield* Ref.get(stateRef);
1054
+ yield* Effect.sync(() => s.snapshot?.dispose());
1055
+ yield* Effect.sync(() => s.api?.close());
1056
+ yield* Ref.update(stateRef, (prev) => ({
1057
+ ...prev,
1058
+ snapshot: void 0,
1059
+ api: void 0
1060
+ }));
1061
+ });
1062
+ const getLineAndCharacterOfPosition = (fileName, position) => Effect.gen(function* () {
1063
+ const programs = yield* getProgramsEffect();
1064
+ for (const program of programs) {
1065
+ const sourceFile = program.getSourceFile(fileName);
1066
+ if (sourceFile) return sourceFile.getLineAndCharacterOfPosition(position);
1067
+ }
1068
+ });
1069
+ return {
1070
+ init,
1071
+ check,
1072
+ nodes: getNodesEffect,
1073
+ close,
1074
+ getLineAndCharacterOfPosition
1075
+ };
1076
+ }
1077
+ //#endregion
1078
+ //#region src/Checker.ts
1079
+ /**
1080
+ * Checker — capability that validates mutants against the TypeScript compiler.
1081
+ *
1082
+ * Bridges the checker plugin protocol (`@systemfsoftware/stryker-js/Checker`)
1083
+ * to the compiler service and the pure `checkMutants` workflow. Diagnostics
1084
+ * are classified without I/O; the file graph is sourced from the compiler.
1085
+ */
1086
+ const normalizeFileName = (fileName) => fileName.replace(/\\/g, "/");
1087
+ /**
1088
+ * Pure grouping decision: separates mutants inside the project graph from
1089
+ * those outside it, honouring `prioritizePerformanceOverAccuracy`.
1090
+ */
1091
+ function partitionMutantsForGrouping(mutants, nodes, prioritizePerformanceOverAccuracy) {
1092
+ if (!prioritizePerformanceOverAccuracy) return {
1093
+ inside: [],
1094
+ outside: [...mutants]
1095
+ };
1096
+ const outside = [];
1097
+ const inside = [];
1098
+ for (const m of mutants) if (Option.isNone(MutableHashMap.get(nodes, normalizeFileName(m.fileName)))) outside.push(m);
1099
+ else inside.push(m);
1100
+ return {
1101
+ inside,
1102
+ outside
1103
+ };
1104
+ }
1105
+ function getPrioritize(options) {
1106
+ if (!Predicate.hasProperty(options, "typescriptChecker")) return false;
1107
+ const tc = options["typescriptChecker"];
1108
+ if (typeof tc !== "object" || tc === null) return false;
1109
+ if (!Predicate.hasProperty(tc, "prioritizePerformanceOverAccuracy")) return false;
1110
+ const val = tc["prioritizePerformanceOverAccuracy"];
1111
+ if (typeof val === "boolean") return val;
1112
+ return false;
1113
+ }
1114
+ const makeCheckDescription = (compiler) => pipe(Cell.read((command) => Effect.gen(function* () {
1115
+ const nodesHm = yield* compiler.nodes.pipe(Effect.mapError((cause) => new CheckerFailed({
1116
+ checkerName: "typescript",
1117
+ mutantIds: command.mutants.map((m) => m.id),
1118
+ cause: errorToString(cause)
1119
+ })));
1120
+ const nodes = {};
1121
+ for (const [k, v] of nodesHm) nodes[k] = v;
1122
+ const diagnostics = yield* compiler.check([...command.mutants]).pipe(Effect.mapError((cause) => new CheckerFailed({
1123
+ checkerName: "typescript",
1124
+ mutantIds: command.mutants.map((m) => m.id),
1125
+ cause: errorToString(cause)
1126
+ })));
1127
+ return new CheckMutantsInput({
1128
+ mutants: [...command.mutants],
1129
+ diagnostics: [...diagnostics],
1130
+ nodes
1131
+ });
1132
+ })), Cell.decode((raw) => Result.succeed(raw)), Cell.decide(checkMutants), Cell.encode((outcome) => outcome), Cell.write((outcome) => Result.match(outcome, {
1133
+ onFailure: (failure) => Effect.fail(new CheckerFailed({
1134
+ checkerName: "typescript",
1135
+ mutantIds: [],
1136
+ cause: errorToString(failure)
1137
+ })),
1138
+ onSuccess: (record) => {
1139
+ let map = HashMap.empty();
1140
+ for (const [id, value] of Object.entries(record)) if (value.status === "passed") map = HashMap.set(map, id, { status: "passed" });
1141
+ else map = HashMap.set(map, id, {
1142
+ status: "compileError",
1143
+ reason: value.reason
1144
+ });
1145
+ return Effect.succeed(map);
840
1146
  }
841
- formatDiagnostic(error) {
842
- const severity = error.category === DiagnosticCategory.Error ? "error" : error.category === DiagnosticCategory.Warning ? "warning" : error.category === DiagnosticCategory.Suggestion ? "suggestion" : "message";
1147
+ })));
1148
+ function makeCheckerService({ options, compiler }) {
1149
+ const formatDiagnostic = (error) => Effect.gen(function* () {
1150
+ let severity;
1151
+ if (error.category === DiagnosticCategory.Error) severity = "error";
1152
+ else if (error.category === DiagnosticCategory.Warning) severity = "warning";
1153
+ else if (error.category === DiagnosticCategory.Suggestion) severity = "suggestion";
1154
+ else severity = "message";
843
1155
  let location = "";
844
- if (error.fileName) {
845
- const lineAndCharacter = this.tsCompiler.getLineAndCharacterOfPosition(error.fileName, error.pos);
1156
+ const unknownError = error;
1157
+ if (typeof unknownError === "object" && unknownError !== null && "fileName" in unknownError && typeof unknownError.fileName === "string") {
1158
+ const fileName = unknownError.fileName;
1159
+ const lineAndCharacter = yield* compiler.getLineAndCharacterOfPosition(fileName, error.pos).pipe(Effect.orElseSucceed(() => void 0));
1160
+ location = `${fileName}(${(lineAndCharacter?.line ?? 0) + 1},${(lineAndCharacter?.character ?? 0) + 1}): `;
1161
+ } else if (error.fileName !== void 0 && error.fileName !== "") {
1162
+ const lineAndCharacter = yield* compiler.getLineAndCharacterOfPosition(error.fileName, error.pos).pipe(Effect.orElseSucceed(() => void 0));
846
1163
  const line = (lineAndCharacter?.line ?? 0) + 1;
847
1164
  const character = (lineAndCharacter?.character ?? 0) + 1;
848
1165
  location = `${error.fileName}(${line},${character}): `;
849
1166
  }
850
1167
  return `${location}${severity} TS${error.code}: ${error.text}`;
851
- }
852
- };
1168
+ });
1169
+ const createErrorText = (errors) => Effect.gen(function* () {
1170
+ return (yield* Effect.forEach(errors, formatDiagnostic)).join(EOL);
1171
+ });
1172
+ return {
1173
+ init: Effect.gen(function* () {
1174
+ const errors = yield* compiler.init.pipe(Effect.mapError((cause) => new CheckerFailed({
1175
+ checkerName: "typescript",
1176
+ mutantIds: [],
1177
+ cause: errorToString(cause)
1178
+ })));
1179
+ if (errors.length > 0) {
1180
+ const text = yield* createErrorText(errors);
1181
+ return yield* new CheckerFailed({
1182
+ checkerName: "typescript",
1183
+ mutantIds: [],
1184
+ cause: errorToString(/* @__PURE__ */ new Error(`Typescript error(s) found in dry run compilation: ${text}`))
1185
+ });
1186
+ }
1187
+ }),
1188
+ check: (mutants) => Effect.gen(function* () {
1189
+ const command = new CheckMutantsCommand({ mutants: [...mutants] });
1190
+ const description = makeCheckDescription(compiler);
1191
+ return yield* Cell.apply(description, command);
1192
+ }),
1193
+ group: (mutants) => Effect.gen(function* () {
1194
+ const nodes = yield* compiler.nodes.pipe(Effect.mapError((cause) => new CheckerFailed({
1195
+ checkerName: "typescript",
1196
+ mutantIds: mutants.map((m) => m.id),
1197
+ cause: errorToString(cause)
1198
+ })));
1199
+ const { inside, outside } = partitionMutantsForGrouping(mutants, nodes, getPrioritize(options));
1200
+ if (inside.length === 0) return mutants.map((m) => [m.id]);
1201
+ const groups = createGroups([...inside], nodes);
1202
+ if (outside.length > 0) return [outside.map((m) => m.id), ...groups];
1203
+ return groups;
1204
+ })
1205
+ };
1206
+ }
853
1207
  //#endregion
854
1208
  //#region src/index.ts
855
- const strykerPlugins = [declareFactoryPlugin(PluginKind.Checker, "typescript", create)];
856
- const createTypescriptChecker = create;
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")));
1209
+ const strykerPlugins = [declarePlugin("Checker", "typescript", Layer.effect(Checker, Effect.gen(function* () {
1210
+ const options = yield* RunConfiguration;
1211
+ const fsService = yield* FileSystem.FileSystem;
1212
+ const pathService = yield* Path.Path;
1213
+ return makeCheckerService({
1214
+ options,
1215
+ compiler: makeTypescriptCompiler(options, yield* makeHybridFileSystem(fsService), fsService, pathService)
1216
+ });
1217
+ })))];
1218
+ const rawSchema = JSON.parse(readFileSync(new URL("../schema/typescript-checker-options.json", import.meta.url), "utf-8"));
1219
+ if (!S.is(S.Record(S.String, S.Unknown))(rawSchema)) throw new Error("Invalid typescript-checker schema file");
1220
+ const strykerValidationSchema = rawSchema;
858
1221
  //#endregion
859
- export { createTypescriptChecker, strykerPlugins, strykerValidationSchema };
1222
+ export { strykerPlugins, strykerValidationSchema };