@systemfsoftware/stryker-js-typescript-checker 3.0.2 → 4.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/index.mjs +125 -114
  3. package/package.json +7 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @systemfsoftware/stryker-js-typescript-checker
2
2
 
3
+ ## 4.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - CheckMutantsDecision is now a branded tagged union CheckFinished|RetestRequired instead of a plain record; consumer dispatch is exhaustive over the tags
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies:
12
+ - @systemfsoftware/effect-cell-types@6.0.0
13
+ - @systemfsoftware/stryker-js@1.0.0
14
+
15
+ ## 3.0.3
16
+
17
+ ### Patch Changes
18
+
19
+ - Refreshed builds on the platform-services dependency graph; the packages no longer reach for host builtins directly. No CLI flags or option names change.
20
+
21
+ - Updated dependencies:
22
+ - @systemfsoftware/stryker-js@0.2.0
23
+
3
24
  ## 3.0.2
4
25
 
5
26
  ### Patch Changes
package/dist/index.mjs CHANGED
@@ -1,4 +1,3 @@
1
- import { readFileSync } from "fs";
2
1
  import { Checker, CheckerFailed } from "@systemfsoftware/stryker-js/Checker";
3
2
  import { RunConfiguration, declarePlugin } from "@systemfsoftware/stryker-js/Plugin";
4
3
  import * as Effect from "effect/Effect";
@@ -6,62 +5,41 @@ import * as FileSystem from "effect/FileSystem";
6
5
  import * as Layer from "effect/Layer";
7
6
  import * as Path from "effect/Path";
8
7
  import * as S from "effect/Schema";
9
- import { EOL } from "os";
10
8
  import { Cell, Wire, Workflow } from "@systemfsoftware/effect-cell-types";
11
9
  import { Mutant, errorToString } from "@systemfsoftware/stryker-js/Mutant";
12
10
  import { Predicate, Result, Schema } from "effect";
13
- import { pipe } from "effect/Function";
14
11
  import * as HashMap from "effect/HashMap";
12
+ import * as Match from "effect/Match";
15
13
  import * as MutableHashMap from "effect/MutableHashMap";
16
14
  import * as Option from "effect/Option";
17
15
  import { API, DiagnosticCategory } from "typescript/unstable/sync";
18
16
  import * as Result$1 from "effect/Result";
19
- import { createRequire } from "module";
20
17
  import { StrykerOptionsSchema } from "@systemfsoftware/stryker-js/Schema";
21
18
  import * as Context from "effect/Context";
22
19
  import * as MutableHashSet from "effect/MutableHashSet";
23
20
  import * as Ref from "effect/Ref";
24
21
  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
- }
22
+ //#region schema/typescript-checker-options.json
23
+ var typescript_checker_options_default = {
24
+ $schema: "http://json-schema.org/draft-07/schema",
25
+ title: "TypescriptCheckerPluginOptions",
26
+ type: "object",
27
+ additionalProperties: false,
28
+ properties: { "typescriptChecker": {
29
+ "description": "Configuration for @systemfsoftware/stryker-js-typescript-checker",
30
+ "title": "TypescriptCheckerOptions",
31
+ "additionalProperties": false,
32
+ "type": "object",
33
+ "default": {},
34
+ "properties": { "prioritizePerformanceOverAccuracy": {
35
+ "description": "Configures the performance of the TypescriptChecker. Setting this to false results in a slower, but more accurate result.",
36
+ "type": "boolean",
37
+ "default": true
38
+ } }
39
+ } }
55
40
  };
56
41
  //#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 results plus
63
- * `needsRetest` without touching I/O.
64
- */
42
+ //#region src/check-mutants.workflow.ts
65
43
  var DiagnosticWithoutFileError = class extends S.TaggedError()("DiagnosticWithoutFileError", { text: Wire.mint(S.String) }) {};
66
44
  var DiagnosticInUnrelatedFileError = class extends S.TaggedError()("DiagnosticInUnrelatedFileError", {
67
45
  text: Wire.mint(S.String),
@@ -81,6 +59,20 @@ var CheckMutantsInput = class extends S.TaggedClass()("CheckMutantsInput", {
81
59
  diagnostics: S.Array(DiagnosticSchema),
82
60
  nodes: Wire.mint(S.Record(Wire.mint(S.String), TSFileNodeSchema))
83
61
  }) {};
62
+ const CheckMutantsTypeId = Symbol.for("@systemfsoftware/stryker-js-typescript-checker/CheckMutants");
63
+ const MutantCheckStatusSchema = S.Union([S.Struct({ status: S.Literal("passed") }), S.Struct({
64
+ status: S.Literal("compileError"),
65
+ reason: S.String
66
+ })]);
67
+ var CheckFinished = class extends S.TaggedClass()("CheckFinished", { results: S.Record(S.String, MutantCheckStatusSchema) }) {
68
+ [CheckMutantsTypeId] = CheckMutantsTypeId;
69
+ };
70
+ var RetestRequired = class extends S.TaggedClass()("RetestRequired", {
71
+ results: S.Record(S.String, MutantCheckStatusSchema),
72
+ needsRetest: S.Array(Mutant)
73
+ }) {
74
+ [CheckMutantsTypeId] = CheckMutantsTypeId;
75
+ };
84
76
  const normalizeFileName$3 = (fileName) => fileName.replace(/\\/g, "/");
85
77
  const getMutantsWithReferenceToChildrenOrSelf = (node, mutants, nodesChecked = []) => {
86
78
  if (nodesChecked.includes(node.fileName)) return [];
@@ -131,18 +123,12 @@ const buildResult = (input) => {
131
123
  const mutants = input.mutants;
132
124
  const diagnostics = input.diagnostics;
133
125
  const nodes = input.nodes;
134
- if (mutants.length === 0) return Result$1.succeed({
135
- results: {},
136
- needsRetest: []
137
- });
126
+ if (mutants.length === 0) return Result$1.succeed(CheckFinished.make({ results: {} }));
138
127
  const first = mutants[0];
139
128
  if (first === void 0 || nodes[normalizeFileName$3(first.fileName)] === void 0) {
140
129
  const results = {};
141
130
  for (const m of mutants) results[m.id] = { status: "passed" };
142
- return Result$1.succeed({
143
- results,
144
- needsRetest: []
145
- });
131
+ return Result$1.succeed(CheckFinished.make({ results }));
146
132
  }
147
133
  const classified = classifyDiagnosticsPure(diagnostics, mutants, nodes);
148
134
  if (Result$1.isFailure(classified)) return Result$1.fail(classified.failure);
@@ -158,13 +144,46 @@ const buildResult = (input) => {
158
144
  };
159
145
  else if (retestIds[m.id] !== true) results[m.id] = { status: "passed" };
160
146
  }
161
- return Result$1.succeed({
147
+ if (needsRetest.length === 0) return Result$1.succeed(CheckFinished.make({ results }));
148
+ return Result$1.succeed(RetestRequired.make({
162
149
  results,
163
- needsRetest
164
- });
150
+ needsRetest: [...needsRetest]
151
+ }));
165
152
  };
166
153
  const checkMutants = Workflow.make(CheckMutantsInput, (input) => buildResult(input));
167
154
  //#endregion
155
+ //#region src/Checker.schema.ts
156
+ /**
157
+ * Checker — declarations for the TypeScript checker.
158
+ *
159
+ * Houses the wire types and error variants shared by the capability and its
160
+ * workflow. Decoded at the checker boundary; no I/O.
161
+ */
162
+ var CheckMutantsCommand = class extends S.TaggedClass()("CheckMutantsCommand", { mutants: S.Array(Mutant) }) {};
163
+ /**
164
+ * Every way the TypeScript compiler can fail while serving a check.
165
+ * One tagged error — callers branch only on failure itself; `reason` keeps
166
+ * cases distinguishable in reports.
167
+ */
168
+ var CompilerFailed = class extends S.TaggedError()("CompilerFailed", {
169
+ reason: S.Literals([
170
+ "not-initialized",
171
+ "no-projects",
172
+ "unknown-file-node",
173
+ "file-not-in-project"
174
+ ]),
175
+ subject: S.optional(S.String)
176
+ }) {
177
+ get message() {
178
+ switch (this.reason) {
179
+ case "not-initialized": return "The TypeScript compiler was used before it was initialized";
180
+ case "no-projects": return `No projects were found for ${this.subject ?? "the tsconfig"}`;
181
+ case "unknown-file-node": return `The file graph has no node for '${this.subject ?? "a file"}', which should not happen`;
182
+ case "file-not-in-project": return `'${this.subject ?? "a file"}' is part of your TypeScript project but could not be found on disk`;
183
+ }
184
+ }
185
+ };
186
+ //#endregion
168
187
  //#region src/Compiler.schema.ts
169
188
  /**
170
189
  * Compiler — declarations for the TypeScript compiler and version guard.
@@ -527,22 +546,16 @@ function retrieveReferencedProjects(config, fromDirName, pathService) {
527
546
  }
528
547
  //#endregion
529
548
  //#region src/Compiler.ts
530
- /**
531
- * Compiler — capability that hosts the TypeScript language service, the
532
- * in-memory file system, and the file-graph used for grouping.
533
- *
534
- * All `typescript/unstable/*` interaction is confined here; callers consume
535
- * only the Effect-typed service surface.
536
- */
537
549
  const normalizeFileName$1 = (fileName) => fileName.replace(/\\/g, "/");
538
550
  const findSourceMapRegex = /\/\/# sourceMappingURL=(.+)$/m;
539
551
  function getSourceMappingURL(content) {
540
552
  return findSourceMapRegex.exec(content)?.[1];
541
553
  }
542
554
  let cachedTSVersion;
543
- const getTSVersion = (fsService) => Effect.gen(function* () {
555
+ const getTSVersion = (fsService, pathService) => Effect.gen(function* () {
544
556
  if (cachedTSVersion !== void 0) return cachedTSVersion;
545
- const pkgPath = createRequire(import.meta.url).resolve("typescript/package.json");
557
+ const urlString = import.meta.resolve("typescript/package.json");
558
+ const pkgPath = yield* pathService.fromFileUrl(new URL(urlString));
546
559
  const text = yield* fsService.readFileString(pkgPath);
547
560
  const raw = JSON.parse(text);
548
561
  let version = "";
@@ -565,8 +578,8 @@ function isSupportedTypescriptVersion(version) {
565
578
  if (minor !== 0) return minor > 0;
566
579
  return patch >= 0;
567
580
  }
568
- const guardTSVersion = (fsService) => Effect.gen(function* () {
569
- const version = yield* getTSVersion(fsService);
581
+ const guardTSVersion = (fsService, pathService) => Effect.gen(function* () {
582
+ const version = yield* getTSVersion(fsService, pathService);
570
583
  if (!isSupportedTypescriptVersion(version)) return yield* new UnsupportedTypeScriptVersionError({ version });
571
584
  });
572
585
  function makeScriptFile(content, fileName, modifiedTime = /* @__PURE__ */ new Date()) {
@@ -1042,7 +1055,7 @@ function makeTypescriptCompiler(options, fs, fsService, pathService) {
1042
1055
  ]).filter((diagnostic) => diagnostic.category === DiagnosticCategory.Error);
1043
1056
  });
1044
1057
  const init = Effect.gen(function* () {
1045
- yield* guardTSVersion(fsService);
1058
+ yield* guardTSVersion(fsService, pathService);
1046
1059
  const absoluteTsconfigFile = normalizeFileName$1(pathService.resolve(rawTsconfigFile));
1047
1060
  yield* Ref.update(stateRef, (prev) => ({
1048
1061
  ...prev,
@@ -1091,18 +1104,7 @@ function makeTypescriptCompiler(options, fs, fsService, pathService) {
1091
1104
  }
1092
1105
  //#endregion
1093
1106
  //#region src/Checker.ts
1094
- /**
1095
- * Checker — capability that validates mutants against the TypeScript compiler.
1096
- *
1097
- * Bridges the checker plugin protocol (`@systemfsoftware/stryker-js/Checker`)
1098
- * to the compiler service and the pure `checkMutants` workflow. Diagnostics
1099
- * are classified without I/O; the file graph is sourced from the compiler.
1100
- */
1101
1107
  const normalizeFileName = (fileName) => fileName.replace(/\\/g, "/");
1102
- /**
1103
- * Pure grouping decision: separates mutants inside the project graph from
1104
- * those outside it, honouring `prioritizePerformanceOverAccuracy`.
1105
- */
1106
1108
  function partitionMutantsForGrouping(mutants, nodes, prioritizePerformanceOverAccuracy) {
1107
1109
  if (!prioritizePerformanceOverAccuracy) return {
1108
1110
  inside: [],
@@ -1126,32 +1128,39 @@ function getPrioritize(options) {
1126
1128
  if (typeof val === "boolean") return val;
1127
1129
  return false;
1128
1130
  }
1129
- const makeCheckDescription = (compiler) => pipe(Cell.read((command) => Effect.gen(function* () {
1130
- const nodesHm = yield* compiler.nodes.pipe(Effect.mapError((cause) => new CheckerFailed({
1131
- checkerName: "typescript",
1132
- mutantIds: command.mutants.map((m) => m.id),
1133
- cause: errorToString(cause)
1134
- })));
1135
- const nodes = {};
1136
- for (const [k, v] of nodesHm) nodes[k] = v;
1137
- const diagnostics = yield* compiler.check([...command.mutants]).pipe(Effect.mapError((cause) => new CheckerFailed({
1138
- checkerName: "typescript",
1139
- mutantIds: command.mutants.map((m) => m.id),
1140
- cause: errorToString(cause)
1141
- })));
1142
- return new CheckMutantsInput({
1143
- mutants: [...command.mutants],
1144
- diagnostics: [...diagnostics],
1145
- nodes
1146
- });
1147
- })), Cell.decode((raw) => Result.succeed(raw)), Cell.decide(checkMutants), Cell.encode((outcome) => outcome), Cell.write((outcome) => Result.match(outcome, {
1148
- onFailure: (failure) => Effect.fail(new CheckerFailed({
1149
- checkerName: "typescript",
1150
- mutantIds: [],
1151
- cause: errorToString(failure)
1152
- })),
1153
- onSuccess: (decision) => Effect.succeed(decision)
1154
- })));
1131
+ const checkCell = Cell.layer({
1132
+ read: (command) => Effect.gen(function* () {
1133
+ const compiler = yield* TypeScriptCompiler;
1134
+ const nodesHm = yield* compiler.nodes.pipe(Effect.mapError((cause) => new CheckerFailed({
1135
+ checkerName: "typescript",
1136
+ mutantIds: command.mutants.map((m) => m.id),
1137
+ cause: errorToString(cause)
1138
+ })));
1139
+ const nodes = {};
1140
+ for (const [k, v] of nodesHm) nodes[k] = v;
1141
+ const diagnostics = yield* compiler.check([...command.mutants]).pipe(Effect.mapError((cause) => new CheckerFailed({
1142
+ checkerName: "typescript",
1143
+ mutantIds: command.mutants.map((m) => m.id),
1144
+ cause: errorToString(cause)
1145
+ })));
1146
+ return new CheckMutantsInput({
1147
+ mutants: [...command.mutants],
1148
+ diagnostics: [...diagnostics],
1149
+ nodes
1150
+ });
1151
+ }),
1152
+ decode: (raw) => Result.succeed(raw),
1153
+ decide: checkMutants,
1154
+ encode: (outcome) => outcome,
1155
+ write: (outcome) => Result.match(outcome, {
1156
+ onFailure: (failure) => Effect.fail(new CheckerFailed({
1157
+ checkerName: "typescript",
1158
+ mutantIds: [],
1159
+ cause: errorToString(failure)
1160
+ })),
1161
+ onSuccess: (decision) => Effect.succeed(decision)
1162
+ })
1163
+ });
1155
1164
  function makeCheckerService({ options, compiler }) {
1156
1165
  const formatDiagnostic = (error) => Effect.gen(function* () {
1157
1166
  let severity;
@@ -1174,7 +1183,7 @@ function makeCheckerService({ options, compiler }) {
1174
1183
  return `${location}${severity} TS${error.code}: ${error.text}`;
1175
1184
  });
1176
1185
  const createErrorText = (errors) => Effect.gen(function* () {
1177
- return (yield* Effect.forEach(errors, formatDiagnostic)).join(EOL);
1186
+ return (yield* Effect.forEach(errors, formatDiagnostic)).join("\n");
1178
1187
  });
1179
1188
  return {
1180
1189
  init: Effect.gen(function* () {
@@ -1193,8 +1202,7 @@ function makeCheckerService({ options, compiler }) {
1193
1202
  }
1194
1203
  }),
1195
1204
  check: (mutants) => Effect.gen(function* () {
1196
- const description = makeCheckDescription(compiler);
1197
- const applyOnce = (group) => Cell.apply(description, new CheckMutantsCommand({ mutants: [...group] }));
1205
+ const applyOnce = (group) => Cell.run(checkCell, new CheckMutantsCommand({ mutants: [...group] })).pipe(Effect.provideService(TypeScriptCompiler, compiler));
1198
1206
  const first = yield* applyOnce(mutants);
1199
1207
  let map = HashMap.empty();
1200
1208
  const mergeResults = (results) => {
@@ -1205,14 +1213,17 @@ function makeCheckerService({ options, compiler }) {
1205
1213
  });
1206
1214
  };
1207
1215
  mergeResults(first.results);
1208
- if (first.needsRetest.length > 0) yield* applyOnce([]);
1209
- const originals = {};
1210
- for (const m of mutants) originals[m.id] = m;
1211
- for (const pending of first.needsRetest) {
1212
- const original = originals[pending.id];
1213
- if (original === void 0) continue;
1214
- mergeResults((yield* applyOnce([original])).results);
1215
- }
1216
+ yield* Match.value(first).pipe(Match.tag("CheckFinished", () => Effect.void), Match.tag("RetestRequired", (retest) => Effect.gen(function* () {
1217
+ yield* applyOnce([]);
1218
+ const originals = {};
1219
+ for (const m of mutants) originals[m.id] = m;
1220
+ for (const pending of retest.needsRetest) {
1221
+ const original = originals[pending.id];
1222
+ if (original === void 0) continue;
1223
+ const one = yield* applyOnce([original]);
1224
+ mergeResults(one.results);
1225
+ }
1226
+ })), Match.exhaustive);
1216
1227
  return map;
1217
1228
  }),
1218
1229
  group: (mutants) => Effect.gen(function* () {
@@ -1240,7 +1251,7 @@ const strykerPlugins = [declarePlugin("Checker", "typescript", Layer.effect(Chec
1240
1251
  compiler: makeTypescriptCompiler(options, yield* makeHybridFileSystem(fsService), fsService, pathService)
1241
1252
  });
1242
1253
  })))];
1243
- const rawSchema = JSON.parse(readFileSync(new URL("../schema/typescript-checker-options.json", import.meta.url), "utf-8"));
1254
+ const rawSchema = typescript_checker_options_default;
1244
1255
  if (!S.is(S.Record(S.String, S.Unknown))(rawSchema)) throw new Error("Invalid typescript-checker schema file");
1245
1256
  const strykerValidationSchema = rawSchema;
1246
1257
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@systemfsoftware/stryker-js-typescript-checker",
3
- "version": "3.0.2",
3
+ "version": "4.0.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/systemfsoftware/systemfsoftware.git",
@@ -22,10 +22,11 @@
22
22
  "dependencies": {
23
23
  "effect": "^4.0.0-rc.112",
24
24
  "typescript": "^7",
25
- "@systemfsoftware/effect-cell-types": "^5.0.2",
26
- "@systemfsoftware/stryker-js": "^0.1.1"
25
+ "@systemfsoftware/stryker-js": "^1.0.0",
26
+ "@systemfsoftware/effect-cell-types": "^6.0.0"
27
27
  },
28
28
  "devDependencies": {
29
+ "@effect/platform-node": "^4.0.0-rc.112",
29
30
  "@effect/platform-node-shared": "^4.0.0-rc.112",
30
31
  "@std/jsonc": "npm:@jsr/std__jsonc@^1.0.2",
31
32
  "@systemfsoftware/arethetypeswrong-cli": "^1.1.1",
@@ -34,13 +35,13 @@
34
35
  "rimraf": "^6.1.3",
35
36
  "tsdown": "^0.22.14",
36
37
  "vitest": "^4",
37
- "@systemfsoftware/all": "^1.0.2",
38
+ "@systemfsoftware/all": "^1.1.2",
38
39
  "@systemfsoftware/effect-gherkin-spec": "^4.0.0",
39
- "@systemfsoftware/effect-schema-vite": "^2.0.2",
40
40
  "@systemfsoftware/effect-schema-law": "^2.0.1",
41
41
  "@systemfsoftware/tsconfig": "^1.3.3",
42
42
  "@systemfsoftware/vitest-config": "^0.1.0",
43
- "@systemfsoftware/oxlint-config": "^0.1.0"
43
+ "@systemfsoftware/oxlint-config": "^0.1.0",
44
+ "@systemfsoftware/effect-schema-vite": "^2.0.2"
44
45
  },
45
46
  "inlinedDependencies": {
46
47
  "@jsr/std__jsonc": "1.0.2"