tstyche 1.0.0-beta.6 → 1.0.0-beta.8

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/build/tstyche.js CHANGED
@@ -1,11 +1,12 @@
1
- import { readFileSync, existsSync, writeFileSync, rmSync } from 'node:fs';
2
1
  import { fileURLToPath, pathToFileURL } from 'node:url';
2
+ import { createRequire } from 'node:module';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
+ import { existsSync, writeFileSync, rmSync } from 'node:fs';
5
6
  import fs from 'node:fs/promises';
6
- import { createRequire } from 'node:module';
7
- import { spawn } from 'node:child_process';
7
+ import vm from 'node:vm';
8
8
  import https from 'node:https';
9
+ import { spawn } from 'node:child_process';
9
10
 
10
11
  class EventEmitter {
11
12
  static #handlers = new Set();
@@ -22,10 +23,42 @@ class EventEmitter {
22
23
  }
23
24
  }
24
25
 
26
+ class Path {
27
+ static basename(filePath) {
28
+ return Path.normalizeSlashes(path.basename(filePath));
29
+ }
30
+ static dirname(filePath) {
31
+ return Path.normalizeSlashes(path.dirname(filePath));
32
+ }
33
+ static join(...filePaths) {
34
+ return Path.normalizeSlashes(path.join(...filePaths));
35
+ }
36
+ static normalizeSlashes(filePath) {
37
+ if (path.sep === "/") {
38
+ return filePath;
39
+ }
40
+ return filePath.replaceAll("\\", "/");
41
+ }
42
+ static relative(from, to) {
43
+ let relativePath = path.relative(from, to);
44
+ if (!relativePath.startsWith(".")) {
45
+ relativePath = `./${relativePath}`;
46
+ }
47
+ return Path.normalizeSlashes(relativePath);
48
+ }
49
+ static resolve(...filePaths) {
50
+ return Path.normalizeSlashes(path.resolve(...filePaths));
51
+ }
52
+ }
53
+
25
54
  class Environment {
55
+ static #isTypeScriptInstalled = Environment.#resolveIsTypeScriptInstalled();
26
56
  static #noColor = Environment.#resolveNoColor();
27
- static #storePath = Environment.#normalizePath(Environment.#resolveStorePath());
57
+ static #storePath = Environment.#resolveStorePath();
28
58
  static #timeout = Environment.#resolveTimeout();
59
+ static get isTypeScriptInstalled() {
60
+ return Environment.#isTypeScriptInstalled;
61
+ }
29
62
  static get noColor() {
30
63
  return Environment.#noColor;
31
64
  }
@@ -35,18 +68,21 @@ class Environment {
35
68
  static get timeout() {
36
69
  return Environment.#timeout;
37
70
  }
38
- static #normalizePath(value) {
39
- if (path.sep === "/") {
40
- return value;
41
- }
42
- return value.replace(/\\/g, "/");
43
- }
44
71
  static #parseBoolean(value) {
45
72
  if (value != null) {
46
73
  return ["1", "on", "t", "true", "y", "yes"].includes(value.toLowerCase());
47
74
  }
48
75
  return false;
49
76
  }
77
+ static #resolveIsTypeScriptInstalled() {
78
+ try {
79
+ createRequire(import.meta.url).resolve("typescript");
80
+ return true;
81
+ }
82
+ catch {
83
+ return false;
84
+ }
85
+ }
50
86
  static #resolveNoColor() {
51
87
  if (process.env["TSTYCHE_NO_COLOR"] != null) {
52
88
  return Environment.#parseBoolean(process.env["TSTYCHE_NO_COLOR"]);
@@ -58,18 +94,18 @@ class Environment {
58
94
  }
59
95
  static #resolveStorePath() {
60
96
  if (process.env["TSTYCHE_STORE_PATH"] != null) {
61
- return path.resolve(process.env["TSTYCHE_STORE_PATH"]);
97
+ return Path.resolve(process.env["TSTYCHE_STORE_PATH"]);
62
98
  }
63
99
  if (process.platform === "darwin") {
64
- return path.resolve(os.homedir(), "Library", "TSTyche");
100
+ return Path.resolve(os.homedir(), "Library", "TSTyche");
65
101
  }
66
102
  if (process.env["LocalAppData"] != null) {
67
- return path.resolve(process.env["LocalAppData"], "TSTyche");
103
+ return Path.resolve(process.env["LocalAppData"], "TSTyche");
68
104
  }
69
105
  if (process.env["XDG_DATA_HOME"] != null) {
70
- return path.resolve(process.env["XDG_DATA_HOME"], "TSTyche");
106
+ return Path.resolve(process.env["XDG_DATA_HOME"], "TSTyche");
71
107
  }
72
- return path.resolve(os.homedir(), ".local", "share", "TSTyche");
108
+ return Path.resolve(os.homedir(), ".local", "share", "TSTyche");
73
109
  }
74
110
  static #resolveTimeout() {
75
111
  if (process.env["TSTYCHE_TIMEOUT"] != null) {
@@ -105,12 +141,12 @@ class Scribbler {
105
141
  };
106
142
  }
107
143
  #escapeSequence(attributes) {
108
- return ["\x1b[", Array.isArray(attributes) ? attributes.join(";") : attributes, "m"].join("");
144
+ return ["\u001B[", Array.isArray(attributes) ? attributes.join(";") : attributes, "m"].join("");
109
145
  }
110
146
  #indentEachLine(lines, level) {
111
147
  const indentStep = " ";
112
148
  const notEmptyLineRegExp = /^(?!$)/gm;
113
- return lines.replace(notEmptyLineRegExp, indentStep.repeat(level));
149
+ return lines.replaceAll(notEmptyLineRegExp, indentStep.repeat(level));
114
150
  }
115
151
  render(element) {
116
152
  if (element != null) {
@@ -123,7 +159,7 @@ class Scribbler {
123
159
  }
124
160
  if (element.type === "ansi" && !this.#noColor) {
125
161
  const flags = typeof element.props?.["escapes"] === "string" || Array.isArray(element.props?.["escapes"])
126
- ? element.props?.["escapes"]
162
+ ? element.props["escapes"]
127
163
  : undefined;
128
164
  if (flags != null) {
129
165
  return this.#escapeSequence(flags);
@@ -201,6 +237,58 @@ class Line {
201
237
  }
202
238
  }
203
239
 
240
+ class Logger {
241
+ #noColor;
242
+ #scribbler;
243
+ #stderr;
244
+ #stdout;
245
+ constructor(options) {
246
+ this.#noColor = options?.noColor ?? Environment.noColor;
247
+ this.#stderr = options?.stderr ?? process.stderr;
248
+ this.#stdout = options?.stdout ?? process.stdout;
249
+ this.#scribbler = new Scribbler({ noColors: this.#noColor });
250
+ }
251
+ eraseLastLine() {
252
+ if (!this.isInteractive()) {
253
+ return;
254
+ }
255
+ this.#stdout.write("\u001B[1A\u001B[0K");
256
+ }
257
+ isInteractive() {
258
+ if ("isTTY" in this.#stdout && typeof this.#stdout.isTTY === "boolean") {
259
+ return this.#stdout.isTTY;
260
+ }
261
+ return false;
262
+ }
263
+ #write(stream, body) {
264
+ const elements = Array.isArray(body) ? body : [body];
265
+ for (const element of elements) {
266
+ if (element.$$typeof !== Symbol.for("tstyche:scribbler")) {
267
+ return;
268
+ }
269
+ stream.write(this.#scribbler.render(element));
270
+ }
271
+ }
272
+ writeError(body) {
273
+ this.#write(this.#stderr, body);
274
+ }
275
+ writeMessage(body) {
276
+ this.#write(this.#stdout, body);
277
+ }
278
+ writeWarning(body) {
279
+ this.#write(this.#stderr, body);
280
+ }
281
+ }
282
+
283
+ class Reporter {
284
+ resolvedConfig;
285
+ logger;
286
+ constructor(resolvedConfig) {
287
+ this.resolvedConfig = resolvedConfig;
288
+ this.logger = new Logger();
289
+ }
290
+ }
291
+
204
292
  function addsPackageStepText(compilerVersion, installationPath) {
205
293
  return (Scribbler.createElement(Line, null,
206
294
  Scribbler.createElement(Text, { color: "90" }, "adds"),
@@ -215,23 +303,6 @@ function describeNameText(name, indent = 0) {
215
303
  return Scribbler.createElement(Line, { indent: indent + 1 }, name);
216
304
  }
217
305
 
218
- class RelativePathText {
219
- props;
220
- constructor(props) {
221
- this.props = props;
222
- }
223
- render() {
224
- const relativePath = path.relative("", this.props.to).replace(/\\/g, "/");
225
- if (relativePath === "") {
226
- return Scribbler.createElement(Text, null, "./");
227
- }
228
- return (Scribbler.createElement(Text, null,
229
- relativePath.startsWith(".") ? "" : "./",
230
- relativePath,
231
- this.props.isDirectory === true ? "/" : ""));
232
- }
233
- }
234
-
235
306
  class CodeSpanText {
236
307
  props;
237
308
  constructor(props) {
@@ -244,12 +315,14 @@ class CodeSpanText {
244
315
  const lastLine = Math.min(firstLine + 5, lastLineInFile);
245
316
  const lineNumberMaxWidth = `${lastLine + 1}`.length;
246
317
  const codeSpan = [];
247
- for (let i = firstLine; i <= lastLine; i++) {
248
- const lineStart = this.props.file.getPositionOfLineAndCharacter(i, 0);
249
- const lineEnd = i === lastLineInFile ? this.props.file.text.length : this.props.file.getPositionOfLineAndCharacter(i + 1, 0);
250
- const lineNumberText = String(i + 1);
251
- const lineText = this.props.file.text.slice(lineStart, lineEnd).trimEnd().replace(/\t/g, " ");
252
- if (i === markedLine) {
318
+ for (let index = firstLine; index <= lastLine; index++) {
319
+ const lineStart = this.props.file.getPositionOfLineAndCharacter(index, 0);
320
+ const lineEnd = index === lastLineInFile
321
+ ? this.props.file.text.length
322
+ : this.props.file.getPositionOfLineAndCharacter(index + 1, 0);
323
+ const lineNumberText = String(index + 1);
324
+ const lineText = this.props.file.text.slice(lineStart, lineEnd).trimEnd().replaceAll("\t", " ");
325
+ if (index === markedLine) {
253
326
  codeSpan.push(Scribbler.createElement(Line, null,
254
327
  Scribbler.createElement(Text, { color: "31" }, ">"),
255
328
  " ",
@@ -257,8 +330,7 @@ class CodeSpanText {
257
330
  " ",
258
331
  Scribbler.createElement(Text, { color: "90" }, "|"),
259
332
  " ",
260
- lineText));
261
- codeSpan.push(Scribbler.createElement(Line, null,
333
+ lineText), Scribbler.createElement(Line, null,
262
334
  " ".repeat(lineNumberMaxWidth + 3),
263
335
  Scribbler.createElement(Text, { color: "90" }, "|"),
264
336
  " ".repeat(markedCharacter + 1),
@@ -281,8 +353,7 @@ class CodeSpanText {
281
353
  " ".repeat(lineNumberMaxWidth + 5),
282
354
  Scribbler.createElement(Text, { color: "90" }, "at"),
283
355
  " ",
284
- Scribbler.createElement(Text, { color: "36" },
285
- Scribbler.createElement(RelativePathText, { to: this.props.file.fileName })),
356
+ Scribbler.createElement(Text, { color: "36" }, Path.relative("", this.props.file.fileName)),
286
357
  Scribbler.createElement(Text, { color: "90" },
287
358
  ":",
288
359
  String(markedLine + 1),
@@ -343,10 +414,13 @@ class FileNameText {
343
414
  this.props = props;
344
415
  }
345
416
  render() {
417
+ const relativePath = Path.relative("", this.props.filePath);
418
+ const lastPathSeparator = relativePath.lastIndexOf("/");
419
+ const directoryNameText = relativePath.slice(0, lastPathSeparator + 1);
420
+ const fileNameText = relativePath.slice(lastPathSeparator + 1);
346
421
  return (Scribbler.createElement(Text, null,
347
- Scribbler.createElement(Text, { color: "90" },
348
- Scribbler.createElement(RelativePathText, { isDirectory: true, to: path.dirname(this.props.filePath) })),
349
- path.basename(this.props.filePath)));
422
+ Scribbler.createElement(Text, { color: "90" }, directoryNameText),
423
+ fileNameText));
350
424
  }
351
425
  }
352
426
  function fileStatusText(status, testFile) {
@@ -378,6 +452,160 @@ function fileViewText(lines, addEmptyFinalLine) {
378
452
  addEmptyFinalLine ? Scribbler.createElement(Line, null) : undefined));
379
453
  }
380
454
 
455
+ class JsonText {
456
+ props;
457
+ constructor(props) {
458
+ this.props = props;
459
+ }
460
+ render() {
461
+ return Scribbler.createElement(Line, null, JSON.stringify(this.#sortObject(this.props.input), null, 2));
462
+ }
463
+ #sortObject(target) {
464
+ if (Array.isArray(target)) {
465
+ return target;
466
+ }
467
+ return Object.keys(target)
468
+ .sort()
469
+ .reduce((result, key) => {
470
+ result[key] = target[key];
471
+ return result;
472
+ }, {});
473
+ }
474
+ }
475
+ function formattedText(input) {
476
+ if (typeof input === "string") {
477
+ return Scribbler.createElement(Line, null, input);
478
+ }
479
+ return Scribbler.createElement(JsonText, { input: input });
480
+ }
481
+
482
+ const usageExamples = [
483
+ ["tstyche", "Run all tests."],
484
+ ["tstyche path/to/first.test.ts", "Only run the test files with matching path."],
485
+ ["tstyche --target 4.7,5.3.2,current", "Test on all specified versions of TypeScript."],
486
+ ];
487
+ class HintText {
488
+ props;
489
+ constructor(props) {
490
+ this.props = props;
491
+ }
492
+ render() {
493
+ return (Scribbler.createElement(Text, { indent: 1, color: "90" }, this.props.children));
494
+ }
495
+ }
496
+ class HelpHeaderText {
497
+ props;
498
+ constructor(props) {
499
+ this.props = props;
500
+ }
501
+ render() {
502
+ const hint = (Scribbler.createElement(HintText, null,
503
+ Scribbler.createElement(Text, null, this.props.tstycheVersion)));
504
+ return (Scribbler.createElement(Line, null,
505
+ Scribbler.createElement(Text, null, "The TSTyche Type Test Runner"),
506
+ hint));
507
+ }
508
+ }
509
+ class CommandText {
510
+ props;
511
+ constructor(props) {
512
+ this.props = props;
513
+ }
514
+ render() {
515
+ let hint;
516
+ if (this.props.hint != null) {
517
+ hint = Scribbler.createElement(HintText, null, this.props.hint);
518
+ }
519
+ return (Scribbler.createElement(Line, { indent: 1 },
520
+ Scribbler.createElement(Text, { color: "34" }, this.props.text),
521
+ hint));
522
+ }
523
+ }
524
+ class OptionDescriptionText {
525
+ props;
526
+ constructor(props) {
527
+ this.props = props;
528
+ }
529
+ render() {
530
+ return Scribbler.createElement(Line, { indent: 1 }, this.props.text);
531
+ }
532
+ }
533
+ class CommandLineUsageText {
534
+ render() {
535
+ const usageText = usageExamples.map(([commandText, descriptionText]) => (Scribbler.createElement(Text, null,
536
+ Scribbler.createElement(CommandText, { text: commandText }),
537
+ Scribbler.createElement(OptionDescriptionText, { text: descriptionText }),
538
+ Scribbler.createElement(Line, null))));
539
+ return Scribbler.createElement(Text, null, usageText);
540
+ }
541
+ }
542
+ class CommandLineOptionNameText {
543
+ props;
544
+ constructor(props) {
545
+ this.props = props;
546
+ }
547
+ render() {
548
+ return Scribbler.createElement(Text, null,
549
+ "--",
550
+ this.props.text);
551
+ }
552
+ }
553
+ class CommandLineOptionHintText {
554
+ props;
555
+ constructor(props) {
556
+ this.props = props;
557
+ }
558
+ render() {
559
+ if (this.props.definition.brand === "list") {
560
+ return (Scribbler.createElement(Text, null,
561
+ this.props.definition.brand,
562
+ " of ",
563
+ this.props.definition.items.brand,
564
+ "s"));
565
+ }
566
+ return Scribbler.createElement(Text, null, this.props.definition.brand);
567
+ }
568
+ }
569
+ class CommandLineOptionsText {
570
+ props;
571
+ constructor(props) {
572
+ this.props = props;
573
+ }
574
+ render() {
575
+ const definitions = [...this.props.optionDefinitions.values()];
576
+ const optionsText = definitions.map((definition) => {
577
+ let hint;
578
+ if (definition.brand !== "true") {
579
+ hint = Scribbler.createElement(CommandLineOptionHintText, { definition: definition });
580
+ }
581
+ return (Scribbler.createElement(Text, null,
582
+ Scribbler.createElement(CommandText, { text: Scribbler.createElement(CommandLineOptionNameText, { text: definition.name }), hint: hint }),
583
+ Scribbler.createElement(OptionDescriptionText, { text: definition.description }),
584
+ Scribbler.createElement(Line, null)));
585
+ });
586
+ return (Scribbler.createElement(Text, null,
587
+ Scribbler.createElement(Line, null, "Command Line Options"),
588
+ Scribbler.createElement(Line, null),
589
+ optionsText));
590
+ }
591
+ }
592
+ class HelpFooterText {
593
+ render() {
594
+ return Scribbler.createElement(Line, null, "To learn more, visit https://tstyche.org");
595
+ }
596
+ }
597
+ function helpText(optionDefinitions, tstycheVersion) {
598
+ return (Scribbler.createElement(Text, null,
599
+ Scribbler.createElement(HelpHeaderText, { tstycheVersion: tstycheVersion }),
600
+ Scribbler.createElement(Line, null),
601
+ Scribbler.createElement(CommandLineUsageText, null),
602
+ Scribbler.createElement(Line, null),
603
+ Scribbler.createElement(CommandLineOptionsText, { optionDefinitions: optionDefinitions }),
604
+ Scribbler.createElement(Line, null),
605
+ Scribbler.createElement(HelpFooterText, null),
606
+ Scribbler.createElement(Line, null)));
607
+ }
608
+
381
609
  class RowText {
382
610
  props;
383
611
  constructor(props) {
@@ -550,11 +778,14 @@ class ProjectNameText {
550
778
  render() {
551
779
  return (Scribbler.createElement(Text, { color: "90" },
552
780
  " with ",
553
- Scribbler.createElement(RelativePathText, { to: this.props.filePath })));
781
+ Path.relative("", this.props.filePath)));
554
782
  }
555
783
  }
556
784
  function usesCompilerStepText(compilerVersion, tsconfigFilePath, options) {
557
- const projectPathText = typeof tsconfigFilePath === "string" ? Scribbler.createElement(ProjectNameText, { filePath: tsconfigFilePath }) : undefined;
785
+ let projectPathText;
786
+ if (tsconfigFilePath != null) {
787
+ projectPathText = Scribbler.createElement(ProjectNameText, { filePath: tsconfigFilePath });
788
+ }
558
789
  return (Scribbler.createElement(Text, null,
559
790
  options?.prependEmptyLine === true ? Scribbler.createElement(Line, null) : undefined,
560
791
  Scribbler.createElement(Line, null,
@@ -565,73 +796,21 @@ function usesCompilerStepText(compilerVersion, tsconfigFilePath, options) {
565
796
  Scribbler.createElement(Line, null)));
566
797
  }
567
798
 
568
- class Logger {
569
- #noColor;
570
- #scribbler;
571
- #stderr;
572
- #stdout;
573
- constructor(options) {
574
- this.#noColor = options?.noColor ?? Environment.noColor;
575
- this.#stderr = options?.stderr ?? process.stderr;
576
- this.#stdout = options?.stdout ?? process.stdout;
577
- this.#scribbler = new Scribbler({ noColors: this.#noColor });
578
- }
579
- eraseLastLine() {
580
- if (!this.isInteractive()) {
581
- return;
582
- }
583
- this.#stdout.write("\x1b[1A\x1b[0K");
584
- }
585
- isInteractive() {
586
- if ("isTTY" in this.#stdout && typeof this.#stdout.isTTY === "boolean") {
587
- return this.#stdout.isTTY;
588
- }
589
- return false;
590
- }
591
- #write(stream, body) {
592
- const elements = Array.isArray(body) ? body : [body];
593
- for (const element of elements) {
594
- if (element.$$typeof !== Symbol.for("tstyche:scribbler")) {
595
- return;
596
- }
597
- stream.write(this.#scribbler.render(element));
598
- }
599
- }
600
- writeError(body) {
601
- this.#write(this.#stderr, body);
602
- }
603
- writeMessage(body) {
604
- this.#write(this.#stdout, body);
605
- }
606
- writeWarning(body) {
607
- this.#write(this.#stderr, body);
608
- }
609
- }
610
-
611
- class Reporter {
612
- resolvedConfig;
613
- logger;
614
- constructor(resolvedConfig) {
615
- this.resolvedConfig = resolvedConfig;
616
- this.logger = new Logger();
617
- }
618
- }
619
-
620
- class SummaryReporter extends Reporter {
621
- handleEvent([eventName, payload]) {
622
- switch (eventName) {
623
- case "end":
624
- this.logger.writeMessage(summaryText({
625
- duration: payload.result.timing.duration,
626
- expectCount: payload.result.expectCount,
627
- fileCount: payload.result.fileCount,
628
- onlyMatch: payload.result.resolvedConfig.only,
629
- pathMatch: payload.result.resolvedConfig.pathMatch,
630
- skipMatch: payload.result.resolvedConfig.skip,
631
- targetCount: payload.result.targetCount,
632
- testCount: payload.result.testCount,
633
- }));
634
- break;
799
+ class SummaryReporter extends Reporter {
800
+ handleEvent([eventName, payload]) {
801
+ switch (eventName) {
802
+ case "end":
803
+ this.logger.writeMessage(summaryText({
804
+ duration: payload.result.timing.duration,
805
+ expectCount: payload.result.expectCount,
806
+ fileCount: payload.result.fileCount,
807
+ onlyMatch: payload.result.resolvedConfig.only,
808
+ pathMatch: payload.result.resolvedConfig.pathMatch,
809
+ skipMatch: payload.result.resolvedConfig.skip,
810
+ targetCount: payload.result.targetCount,
811
+ testCount: payload.result.testCount,
812
+ }));
813
+ break;
635
814
  }
636
815
  }
637
816
  }
@@ -651,6 +830,9 @@ class Diagnostic {
651
830
  if (options.code != null) {
652
831
  this.code = options.code;
653
832
  }
833
+ if (options.origin != null) {
834
+ this.origin = options.origin;
835
+ }
654
836
  if (options.related != null) {
655
837
  this.related = options.related;
656
838
  }
@@ -727,10 +909,6 @@ class FileViewService {
727
909
  return this.#messages.length > 0;
728
910
  }
729
911
  addMessage(message) {
730
- if (Array.isArray(message)) {
731
- this.#messages.push(...message);
732
- return;
733
- }
734
912
  this.#messages.push(message);
735
913
  }
736
914
  addTest(status, name) {
@@ -1196,53 +1374,39 @@ class TestMember {
1196
1374
  parent;
1197
1375
  flags;
1198
1376
  compiler;
1199
- diagnostics;
1377
+ diagnostics = new Set();
1200
1378
  members = [];
1201
- name;
1202
- typeChecker;
1379
+ name = "";
1203
1380
  constructor(brand, node, parent, flags) {
1204
1381
  this.brand = brand;
1205
1382
  this.node = node;
1206
1383
  this.parent = parent;
1207
1384
  this.flags = flags;
1208
1385
  this.compiler = parent.compiler;
1209
- this.typeChecker = parent.typeChecker;
1210
- this.diagnostics = this.#mapDiagnostics(node, this.parent);
1211
- this.name = this.#resolveName(node);
1212
- }
1213
- get ancestorNames() {
1214
- const ancestorNames = [];
1215
- let ancestor = this.parent;
1216
- while ("name" in ancestor) {
1217
- ancestorNames.unshift(ancestor.name);
1218
- ancestor = ancestor.parent;
1386
+ if (node.arguments[0] != null && this.compiler.isStringLiteralLike(node.arguments[0])) {
1387
+ this.name = node.arguments[0].text;
1219
1388
  }
1220
- return ancestorNames;
1221
- }
1222
- #mapDiagnostics(node, parent) {
1223
- const mapped = [];
1224
- const unmapped = [];
1225
1389
  if (node.arguments[1] != null &&
1226
1390
  parent.compiler.isFunctionLike(node.arguments[1]) &&
1227
1391
  parent.compiler.isBlock(node.arguments[1].body)) {
1228
1392
  const blockStart = node.arguments[1].body.getStart();
1229
1393
  const blockEnd = node.arguments[1].body.getEnd();
1230
- parent.diagnostics.forEach((diagnostic) => {
1394
+ for (const diagnostic of parent.diagnostics) {
1231
1395
  if (diagnostic.start != null && diagnostic.start >= blockStart && diagnostic.start <= blockEnd) {
1232
- mapped.push(diagnostic);
1233
- }
1234
- else {
1235
- unmapped.push(diagnostic);
1396
+ this.diagnostics.add(diagnostic);
1397
+ parent.diagnostics.delete(diagnostic);
1236
1398
  }
1237
- });
1238
- parent.diagnostics = unmapped;
1399
+ }
1239
1400
  }
1240
- return mapped;
1241
1401
  }
1242
- #resolveName(node) {
1243
- return node.arguments[0] !== undefined && this.compiler.isStringLiteral(node.arguments[0])
1244
- ? node.arguments[0].text
1245
- : "";
1402
+ get ancestorNames() {
1403
+ const ancestorNames = [];
1404
+ let ancestor = this.parent;
1405
+ while ("name" in ancestor) {
1406
+ ancestorNames.unshift(ancestor.name);
1407
+ ancestor = ancestor.parent;
1408
+ }
1409
+ return ancestorNames;
1246
1410
  }
1247
1411
  validate() {
1248
1412
  const diagnostics = [];
@@ -1282,14 +1446,24 @@ class Assertion extends TestMember {
1282
1446
  modifierNode;
1283
1447
  notNode;
1284
1448
  isNot;
1285
- name = "";
1286
1449
  constructor(brand, node, parent, flags, matcherNode, modifierNode, notNode) {
1287
1450
  super(brand, node, parent, flags);
1288
1451
  this.matcherNode = matcherNode;
1289
1452
  this.modifierNode = modifierNode;
1290
1453
  this.notNode = notNode;
1291
- this.diagnostics = this.#mapDiagnostics();
1292
1454
  this.isNot = notNode ? true : false;
1455
+ const argStart = this.source[0]?.getStart();
1456
+ const argEnd = this.source[0]?.getEnd();
1457
+ for (const diagnostic of parent.diagnostics) {
1458
+ if (diagnostic.start != null &&
1459
+ argStart != null &&
1460
+ argEnd != null &&
1461
+ diagnostic.start >= argStart &&
1462
+ diagnostic.start <= argEnd) {
1463
+ this.diagnostics.add(diagnostic);
1464
+ parent.diagnostics.delete(diagnostic);
1465
+ }
1466
+ }
1293
1467
  }
1294
1468
  get ancestorNames() {
1295
1469
  const ancestorNames = [];
@@ -1304,80 +1478,20 @@ class Assertion extends TestMember {
1304
1478
  get matcherName() {
1305
1479
  return this.matcherNode.expression.name;
1306
1480
  }
1307
- get sourceArguments() {
1481
+ get source() {
1482
+ if (this.node.typeArguments != null) {
1483
+ return this.node.typeArguments;
1484
+ }
1308
1485
  return this.node.arguments;
1309
1486
  }
1310
- get sourceType() {
1311
- if (!this.typeChecker) {
1312
- return;
1313
- }
1314
- if (this.node.typeArguments?.[0]) {
1315
- return {
1316
- source: 1,
1317
- type: this.typeChecker.getTypeFromTypeNode(this.node.typeArguments[0]),
1318
- };
1319
- }
1320
- if (this.node.arguments[0]) {
1321
- return {
1322
- source: 0,
1323
- type: this.typeChecker.getTypeAtLocation(this.node.arguments[0]),
1324
- };
1487
+ get target() {
1488
+ if (this.matcherNode.typeArguments != null) {
1489
+ return this.matcherNode.typeArguments;
1325
1490
  }
1326
- return;
1327
- }
1328
- get targetArguments() {
1329
1491
  return this.matcherNode.arguments;
1330
1492
  }
1331
- get targetType() {
1332
- if (!this.typeChecker) {
1333
- return;
1334
- }
1335
- if (this.matcherNode.typeArguments?.[0]) {
1336
- return {
1337
- source: 1,
1338
- type: this.typeChecker.getTypeFromTypeNode(this.matcherNode.typeArguments[0]),
1339
- };
1340
- }
1341
- if (this.matcherNode.arguments[0]) {
1342
- return {
1343
- source: 0,
1344
- type: this.typeChecker.getTypeAtLocation(this.matcherNode.arguments[0]),
1345
- };
1346
- }
1347
- return;
1348
- }
1349
- #mapDiagnostics() {
1350
- const mapped = [];
1351
- const unmapped = [];
1352
- let argStart;
1353
- let argEnd;
1354
- if (this.node.typeArguments?.[0]) {
1355
- argStart = this.node.typeArguments[0].getStart();
1356
- argEnd = this.node.typeArguments[0].getEnd();
1357
- }
1358
- else if (this.node.arguments[0]) {
1359
- argStart = this.node.arguments[0].getStart();
1360
- argEnd = this.node.arguments[0].getEnd();
1361
- }
1362
- this.parent.diagnostics.forEach((diagnostic) => {
1363
- if (diagnostic.start != null && diagnostic.start >= argStart && diagnostic.start <= argEnd) {
1364
- mapped.push(diagnostic);
1365
- }
1366
- else {
1367
- unmapped.push(diagnostic);
1368
- }
1369
- });
1370
- this.parent.diagnostics = unmapped;
1371
- return mapped;
1372
- }
1373
1493
  }
1374
1494
 
1375
- var AssertionSource;
1376
- (function (AssertionSource) {
1377
- AssertionSource[AssertionSource["Argument"] = 0] = "Argument";
1378
- AssertionSource[AssertionSource["TypeArgument"] = 1] = "TypeArgument";
1379
- })(AssertionSource || (AssertionSource = {}));
1380
-
1381
1495
  class IdentifierLookup {
1382
1496
  compiler;
1383
1497
  #identifiers;
@@ -1478,13 +1592,11 @@ class TestTree {
1478
1592
  compiler;
1479
1593
  diagnostics;
1480
1594
  sourceFile;
1481
- typeChecker;
1482
1595
  members = [];
1483
- constructor(compiler, diagnostics, sourceFile, typeChecker) {
1596
+ constructor(compiler, diagnostics, sourceFile) {
1484
1597
  this.compiler = compiler;
1485
1598
  this.diagnostics = diagnostics;
1486
1599
  this.sourceFile = sourceFile;
1487
- this.typeChecker = typeChecker;
1488
1600
  }
1489
1601
  get hasOnly() {
1490
1602
  function hasOnly(root) {
@@ -1561,8 +1673,8 @@ class CollectService {
1561
1673
  this.#collectTestMembers(node, identifiers, parent);
1562
1674
  });
1563
1675
  }
1564
- createTestTree(sourceFile, semanticDiagnostics = [], typeChecker) {
1565
- const testTree = new TestTree(this.compiler, semanticDiagnostics, sourceFile, typeChecker);
1676
+ createTestTree(sourceFile, semanticDiagnostics = []) {
1677
+ const testTree = new TestTree(this.compiler, new Set(semanticDiagnostics), sourceFile);
1566
1678
  this.#collectTestMembers(sourceFile, new IdentifierLookup(this.compiler), testTree);
1567
1679
  return testTree;
1568
1680
  }
@@ -1593,408 +1705,551 @@ var TestMemberFlags;
1593
1705
  TestMemberFlags[TestMemberFlags["Todo"] = 8] = "Todo";
1594
1706
  })(TestMemberFlags || (TestMemberFlags = {}));
1595
1707
 
1596
- class ProjectService {
1597
- compiler;
1598
- #service;
1599
- constructor(compiler) {
1600
- this.compiler = compiler;
1601
- function doNothing() {
1602
- }
1603
- function returnFalse() {
1604
- return false;
1605
- }
1606
- function returnUndefined() {
1607
- return undefined;
1608
- }
1609
- const noopWatcher = { close: doNothing };
1610
- const noopLogger = {
1611
- close: doNothing,
1612
- endGroup: doNothing,
1613
- getLogFileName: returnUndefined,
1614
- hasLevel: returnFalse,
1615
- info: doNothing,
1616
- loggingEnabled: returnFalse,
1617
- msg: doNothing,
1618
- perftrc: doNothing,
1619
- startGroup: doNothing,
1620
- };
1621
- const host = {
1622
- ...this.compiler.sys,
1623
- clearImmediate,
1624
- clearTimeout,
1625
- setImmediate,
1626
- setTimeout,
1627
- watchDirectory: () => noopWatcher,
1628
- watchFile: () => noopWatcher,
1708
+ class PrimitiveTypeMatcher {
1709
+ typeChecker;
1710
+ #targetTypeFlag;
1711
+ #targetTypeText;
1712
+ constructor(typeChecker, targetTypeFlag, targetTypeText) {
1713
+ this.typeChecker = typeChecker;
1714
+ this.#targetTypeFlag = targetTypeFlag;
1715
+ this.#targetTypeText = targetTypeText;
1716
+ }
1717
+ #explain(sourceType, isNot) {
1718
+ const sourceTypeText = this.typeChecker.typeToString(sourceType);
1719
+ return isNot
1720
+ ? [Diagnostic.error(`Type '${this.#targetTypeText}' is identical to type '${sourceTypeText}'.`)]
1721
+ : [Diagnostic.error(`Type '${this.#targetTypeText}' is not identical to type '${sourceTypeText}'.`)];
1722
+ }
1723
+ match(sourceType, isNot) {
1724
+ const isMatch = Boolean(sourceType.flags & this.#targetTypeFlag);
1725
+ return {
1726
+ explain: () => this.#explain(sourceType, isNot),
1727
+ isMatch,
1629
1728
  };
1630
- this.#service = new this.compiler.server.ProjectService({
1631
- cancellationToken: this.compiler.server.nullCancellationToken,
1632
- host,
1633
- logger: noopLogger,
1634
- session: undefined,
1635
- useInferredProjectPerProjectRoot: true,
1636
- useSingleInferredProject: false,
1637
- });
1638
1729
  }
1639
- closeFile(filePath) {
1640
- this.#service.closeClientFile(filePath);
1730
+ }
1731
+
1732
+ class ToBeAssignable {
1733
+ typeChecker;
1734
+ constructor(typeChecker) {
1735
+ this.typeChecker = typeChecker;
1641
1736
  }
1642
- getDefaultProject(filePath) {
1643
- return this.#service.getDefaultProjectForFile(this.compiler.server.toNormalizedPath(filePath), true);
1737
+ #explain(sourceType, targetType, isNot) {
1738
+ const sourceTypeText = this.typeChecker.typeToString(sourceType);
1739
+ const targetTypeText = this.typeChecker.typeToString(targetType);
1740
+ return isNot
1741
+ ? [Diagnostic.error(`Type '${targetTypeText}' is assignable to type '${sourceTypeText}'.`)]
1742
+ : [Diagnostic.error(`Type '${targetTypeText}' is not assignable to type '${sourceTypeText}'.`)];
1644
1743
  }
1645
- getLanguageService(filePath) {
1646
- const project = this.getDefaultProject(filePath);
1647
- if (!project) {
1648
- return;
1649
- }
1650
- return project.getLanguageService(true);
1744
+ match(sourceType, targetType, isNot) {
1745
+ const isMatch = this.typeChecker.isTypeAssignableTo(targetType, sourceType);
1746
+ return {
1747
+ explain: () => this.#explain(sourceType, targetType, isNot),
1748
+ isMatch,
1749
+ };
1651
1750
  }
1652
- openFile(filePath, sourceText, projectRootPath) {
1653
- const { configFileErrors, configFileName } = this.#service.openClientFile(filePath, sourceText, undefined, projectRootPath);
1654
- EventEmitter.dispatch([
1655
- "project:info",
1656
- { compilerVersion: this.compiler.version, projectConfigFilePath: configFileName },
1657
- ]);
1658
- if (configFileErrors && configFileErrors.length > 0) {
1659
- EventEmitter.dispatch([
1660
- "project:error",
1661
- { diagnostics: Diagnostic.fromDiagnostics(configFileErrors, this.compiler) },
1662
- ]);
1663
- }
1751
+ }
1752
+
1753
+ class ToEqual {
1754
+ typeChecker;
1755
+ constructor(typeChecker) {
1756
+ this.typeChecker = typeChecker;
1757
+ }
1758
+ #explain(sourceType, targetType, isNot) {
1759
+ const sourceTypeText = this.typeChecker.typeToString(sourceType);
1760
+ const targetTypeText = this.typeChecker.typeToString(targetType);
1761
+ return isNot
1762
+ ? [Diagnostic.error(`Type '${targetTypeText}' is identical to type '${sourceTypeText}'.`)]
1763
+ : [Diagnostic.error(`Type '${targetTypeText}' is not identical to type '${sourceTypeText}'.`)];
1764
+ }
1765
+ match(sourceType, targetType, isNot) {
1766
+ const isMatch = this.typeChecker.isTypeIdenticalTo(sourceType, targetType);
1767
+ return {
1768
+ explain: () => this.#explain(sourceType, targetType, isNot),
1769
+ isMatch,
1770
+ };
1664
1771
  }
1665
1772
  }
1666
1773
 
1667
- class Checker {
1774
+ class ToHaveProperty {
1668
1775
  compiler;
1669
- constructor(compiler) {
1776
+ typeChecker;
1777
+ constructor(compiler, typeChecker) {
1670
1778
  this.compiler = compiler;
1779
+ this.typeChecker = typeChecker;
1671
1780
  }
1672
- #assertNonNullish(value, message) {
1673
- if (value == null) {
1674
- throw Error(message);
1781
+ #explain(sourceType, targetType, isNot) {
1782
+ const sourceTypeText = this.typeChecker.typeToString(sourceType);
1783
+ let targetArgumentText;
1784
+ if (this.#isStringOrNumberLiteralType(targetType)) {
1785
+ targetArgumentText = String(targetType.value);
1675
1786
  }
1787
+ else {
1788
+ targetArgumentText = `[${this.compiler.unescapeLeadingUnderscores(targetType.symbol.escapedName)}]`;
1789
+ }
1790
+ return isNot
1791
+ ? [Diagnostic.error(`Property '${targetArgumentText}' exists on type '${sourceTypeText}'.`)]
1792
+ : [Diagnostic.error(`Property '${targetArgumentText}' does not exist on type '${sourceTypeText}'.`)];
1676
1793
  }
1677
- #assertNonNullishSourceType(assertion) {
1678
- this.#assertNonNullish(assertion.sourceType, "An argument for 'source' was not provided.");
1679
- }
1680
- #assertNonNullishTargetType(assertion) {
1681
- this.#assertNonNullish(assertion.targetType, "An argument for 'target' was not provided.");
1794
+ #isStringOrNumberLiteralType(type) {
1795
+ return Boolean(type.flags & this.compiler.TypeFlags.StringOrNumberLiteral);
1682
1796
  }
1683
- #assertNonNullishTypeChecker(assertion) {
1684
- this.#assertNonNullish(assertion.typeChecker, "The 'typeChecker' was not provided.");
1797
+ match(sourceType, targetType, isNot) {
1798
+ let targetArgumentText;
1799
+ if (this.#isStringOrNumberLiteralType(targetType)) {
1800
+ targetArgumentText = String(targetType.value);
1801
+ }
1802
+ else {
1803
+ targetArgumentText = this.compiler.unescapeLeadingUnderscores(targetType.escapedName);
1804
+ }
1805
+ const isMatch = sourceType.getProperties().some((property) => {
1806
+ return this.compiler.unescapeLeadingUnderscores(property.escapedName) === targetArgumentText;
1807
+ });
1808
+ return {
1809
+ explain: () => this.#explain(sourceType, targetType, isNot),
1810
+ isMatch,
1811
+ };
1685
1812
  }
1686
- #assertStringsOrNumber(expression) {
1687
- return (expression != null &&
1688
- (this.compiler.isStringLiteralLike(expression) || this.compiler.isNumericLiteral(expression)));
1813
+ }
1814
+
1815
+ class ToMatch {
1816
+ typeChecker;
1817
+ constructor(typeChecker) {
1818
+ this.typeChecker = typeChecker;
1689
1819
  }
1690
- #assertStringsOrNumbers(nodes) {
1691
- return nodes.every((expression) => this.#assertStringsOrNumber(expression));
1820
+ #explain(sourceType, targetType, isNot) {
1821
+ const sourceTypeText = this.typeChecker.typeToString(sourceType);
1822
+ const targetTypeText = this.typeChecker.typeToString(targetType);
1823
+ return isNot
1824
+ ? [Diagnostic.error(`Type '${targetTypeText}' is a subtype of type '${sourceTypeText}'.`)]
1825
+ : [Diagnostic.error(`Type '${targetTypeText}' is not a subtype of type '${sourceTypeText}'.`)];
1692
1826
  }
1693
- explain(assertion) {
1694
- this.#assertNonNullishTypeChecker(assertion);
1695
- const matcher = assertion.matcherName.getText();
1696
- const origin = {
1697
- breadcrumbs: assertion.ancestorNames,
1698
- end: assertion.matcherName.getEnd(),
1699
- file: assertion.matcherName.getSourceFile(),
1700
- start: assertion.matcherName.getStart(),
1827
+ match(sourceType, targetType, isNot) {
1828
+ const isMatch = this.typeChecker.isTypeSubtypeOf(sourceType, targetType);
1829
+ return {
1830
+ explain: () => this.#explain(sourceType, targetType, isNot),
1831
+ isMatch,
1701
1832
  };
1702
- switch (matcher) {
1703
- case "toBeAssignable": {
1704
- this.#assertNonNullishSourceType(assertion);
1705
- this.#assertNonNullishTargetType(assertion);
1706
- const sourceTypeText = assertion.typeChecker.typeToString(assertion.sourceType.type);
1707
- const targetTypeText = assertion.typeChecker.typeToString(assertion.targetType.type);
1708
- return [
1709
- Diagnostic.error(assertion.isNot
1710
- ? `Type '${targetTypeText}' is assignable to type '${sourceTypeText}'.`
1711
- : `Type '${targetTypeText}' is not assignable to type '${sourceTypeText}'.`, origin),
1833
+ }
1834
+ }
1835
+
1836
+ class ToRaiseError {
1837
+ compiler;
1838
+ typeChecker;
1839
+ constructor(compiler, typeChecker) {
1840
+ this.compiler = compiler;
1841
+ this.typeChecker = typeChecker;
1842
+ }
1843
+ #explain(source, targetTypes, isNot) {
1844
+ const sourceText = this.compiler.isTypeNode(source.node) ? "Type expression" : "Expression";
1845
+ if (source.diagnostics.length === 0) {
1846
+ return [Diagnostic.error(`${sourceText} did not raise a type error.`)];
1847
+ }
1848
+ if (isNot && targetTypes.length === 0) {
1849
+ const related = [
1850
+ Diagnostic.error(`The raised type error${source.diagnostics.length === 1 ? "" : "s"}:`),
1851
+ ...Diagnostic.fromDiagnostics(source.diagnostics, this.compiler),
1852
+ ];
1853
+ const text = `${sourceText} raised ${source.diagnostics.length === 1 ? "a" : source.diagnostics.length} type error${source.diagnostics.length === 1 ? "" : "s"}.`;
1854
+ return [Diagnostic.error(text).add({ related })];
1855
+ }
1856
+ if (source.diagnostics.length !== targetTypes.length) {
1857
+ const expectedText = source.diagnostics.length > targetTypes.length
1858
+ ? `only ${targetTypes.length} type error${targetTypes.length === 1 ? "" : "s"}`
1859
+ : `${targetTypes.length} type error${targetTypes.length === 1 ? "" : "s"}`;
1860
+ const foundText = source.diagnostics.length > targetTypes.length
1861
+ ? `${source.diagnostics.length}`
1862
+ : `only ${source.diagnostics.length}`;
1863
+ const related = [
1864
+ Diagnostic.error(`The raised type error${source.diagnostics.length === 1 ? "" : "s"}:`),
1865
+ ...Diagnostic.fromDiagnostics(source.diagnostics, this.compiler),
1866
+ ];
1867
+ const text = `Expected ${expectedText}, but ${foundText} ${source.diagnostics.length === 1 ? "was" : "were"} raised.`;
1868
+ return [Diagnostic.error(text).add({ related })];
1869
+ }
1870
+ const diagnostics = [];
1871
+ targetTypes.forEach((argument, index) => {
1872
+ const diagnostic = source.diagnostics[index];
1873
+ if (!diagnostic) {
1874
+ return;
1875
+ }
1876
+ const isMatch = this.#matchExpectedError(diagnostic, argument);
1877
+ if (!isNot && !isMatch) {
1878
+ const expectedText = this.#isStringLiteralType(argument)
1879
+ ? `matching substring '${argument.value}'`
1880
+ : `with code ${argument.value}`;
1881
+ const related = [
1882
+ Diagnostic.error("The raised type error:"),
1883
+ ...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
1884
+ ];
1885
+ const text = `${sourceText} did not raise a type error ${expectedText}.`;
1886
+ diagnostics.push(Diagnostic.error(text).add({ related }));
1887
+ }
1888
+ if (isNot && isMatch) {
1889
+ const expectedText = this.#isStringLiteralType(argument)
1890
+ ? `matching substring '${argument.value}'`
1891
+ : `with code ${argument.value}`;
1892
+ const related = [
1893
+ Diagnostic.error("The raised type error:"),
1894
+ ...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
1712
1895
  ];
1896
+ const text = `${sourceText} raised a type error ${expectedText}.`;
1897
+ diagnostics.push(Diagnostic.error(text).add({ related }));
1713
1898
  }
1899
+ });
1900
+ return diagnostics;
1901
+ }
1902
+ #isStringLiteralType(type) {
1903
+ return Boolean(type.flags & this.compiler.TypeFlags.StringLiteral);
1904
+ }
1905
+ match(source, targetTypes, isNot) {
1906
+ const explain = () => this.#explain(source, targetTypes, isNot);
1907
+ if (targetTypes.length === 0) {
1908
+ return {
1909
+ explain,
1910
+ isMatch: source.diagnostics.length > 0,
1911
+ };
1912
+ }
1913
+ if (source.diagnostics.length !== targetTypes.length) {
1914
+ return {
1915
+ explain,
1916
+ isMatch: false,
1917
+ };
1918
+ }
1919
+ return {
1920
+ explain,
1921
+ isMatch: targetTypes.every((type, index) => this.#matchExpectedError(source.diagnostics[index], type)),
1922
+ };
1923
+ }
1924
+ #matchExpectedError(diagnostic, type) {
1925
+ if (this.#isStringLiteralType(type)) {
1926
+ return this.compiler.flattenDiagnosticMessageText(diagnostic?.messageText, " ", 0).includes(type.value);
1927
+ }
1928
+ return type.value === diagnostic?.code;
1929
+ }
1930
+ }
1931
+
1932
+ class Expect {
1933
+ compiler;
1934
+ typeChecker;
1935
+ toBeAny;
1936
+ toBeAssignable;
1937
+ toBeBigInt;
1938
+ toBeBoolean;
1939
+ toBeNever;
1940
+ toBeNull;
1941
+ toBeNumber;
1942
+ toBeString;
1943
+ toBeSymbol;
1944
+ toBeUndefined;
1945
+ toBeUniqueSymbol;
1946
+ toBeUnknown;
1947
+ toBeVoid;
1948
+ toEqual;
1949
+ toHaveProperty;
1950
+ toMatch;
1951
+ toRaiseError;
1952
+ constructor(compiler, typeChecker) {
1953
+ this.compiler = compiler;
1954
+ this.typeChecker = typeChecker;
1955
+ this.toBeAny = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Any, "any");
1956
+ this.toBeAssignable = new ToBeAssignable(this.typeChecker);
1957
+ this.toBeBigInt = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.BigInt, "bigint");
1958
+ this.toBeBoolean = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Boolean, "boolean");
1959
+ this.toBeNever = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Never, "never");
1960
+ this.toBeNull = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Null, "null");
1961
+ this.toBeNumber = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Number, "number");
1962
+ this.toBeString = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.String, "string");
1963
+ this.toBeSymbol = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.ESSymbol, "symbol");
1964
+ this.toBeUndefined = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Undefined, "undefined");
1965
+ this.toBeUniqueSymbol = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.UniqueESSymbol, "unique symbol");
1966
+ this.toBeUnknown = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Unknown, "unknown");
1967
+ this.toBeVoid = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Void, "void");
1968
+ this.toEqual = new ToEqual(this.typeChecker);
1969
+ this.toHaveProperty = new ToHaveProperty(this.compiler, this.typeChecker);
1970
+ this.toMatch = new ToMatch(this.typeChecker);
1971
+ this.toRaiseError = new ToRaiseError(this.compiler, this.typeChecker);
1972
+ }
1973
+ static assertTypeChecker(typeChecker) {
1974
+ return ("isTypeAssignableTo" in typeChecker && "isTypeIdenticalTo" in typeChecker && "isTypeSubtypeOf" in typeChecker);
1975
+ }
1976
+ #getType(node) {
1977
+ return this.compiler.isExpression(node)
1978
+ ? this.typeChecker.getTypeAtLocation(node)
1979
+ : this.typeChecker.getTypeFromTypeNode(node);
1980
+ }
1981
+ #getTypes(nodes) {
1982
+ return nodes.map((node) => this.#getType(node));
1983
+ }
1984
+ #isArrayOfStringOrNumberLiteralTypes(types) {
1985
+ return types.every((type) => this.#isStringOrNumberLiteralType(type));
1986
+ }
1987
+ #isStringOrNumberLiteralType(type) {
1988
+ return Boolean(type.flags & this.compiler.TypeFlags.StringOrNumberLiteral);
1989
+ }
1990
+ #isUniqueSymbolType(type) {
1991
+ return Boolean(type.flags & this.compiler.TypeFlags.UniqueESSymbol);
1992
+ }
1993
+ match(assertion, expectResult) {
1994
+ const matcherNameText = assertion.matcherName.getText();
1995
+ switch (matcherNameText) {
1996
+ case "toBeAssignable":
1997
+ case "toEqual":
1998
+ case "toMatch":
1999
+ if (assertion.source[0] == null) {
2000
+ this.#onSourceArgumentMustBeProvided(assertion, expectResult);
2001
+ return;
2002
+ }
2003
+ if (assertion.target[0] == null) {
2004
+ this.#onTargetArgumentMustBeProvided(assertion, expectResult);
2005
+ return;
2006
+ }
2007
+ return this[matcherNameText].match(this.#getType(assertion.source[0]), this.#getType(assertion.target[0]), assertion.isNot);
1714
2008
  case "toBeAny":
1715
- return this.#isType(assertion, "any");
1716
2009
  case "toBeBigInt":
1717
- return this.#isType(assertion, "bigint");
1718
2010
  case "toBeBoolean":
1719
- return this.#isType(assertion, "boolean");
1720
2011
  case "toBeNever":
1721
- return this.#isType(assertion, "never");
1722
2012
  case "toBeNull":
1723
- return this.#isType(assertion, "null");
1724
2013
  case "toBeNumber":
1725
- return this.#isType(assertion, "number");
1726
2014
  case "toBeString":
1727
- return this.#isType(assertion, "string");
1728
2015
  case "toBeSymbol":
1729
- return this.#isType(assertion, "symbol");
1730
2016
  case "toBeUndefined":
1731
- return this.#isType(assertion, "undefined");
1732
2017
  case "toBeUniqueSymbol":
1733
- return this.#isType(assertion, "unique symbol");
1734
2018
  case "toBeUnknown":
1735
- return this.#isType(assertion, "unknown");
1736
2019
  case "toBeVoid":
1737
- return this.#isType(assertion, "void");
1738
- case "toEqual": {
1739
- this.#assertNonNullishSourceType(assertion);
1740
- this.#assertNonNullishTargetType(assertion);
1741
- const sourceTypeText = assertion.typeChecker.typeToString(assertion.sourceType.type);
1742
- const targetTypeText = assertion.typeChecker.typeToString(assertion.targetType.type);
1743
- return [
1744
- Diagnostic.error(assertion.isNot
1745
- ? `Type '${targetTypeText}' is identical to type '${sourceTypeText}'.`
1746
- : `Type '${targetTypeText}' is not identical to type '${sourceTypeText}'.`, origin),
1747
- ];
1748
- }
2020
+ if (assertion.source[0] == null) {
2021
+ this.#onSourceArgumentMustBeProvided(assertion, expectResult);
2022
+ return;
2023
+ }
2024
+ return this[matcherNameText].match(this.#getType(assertion.source[0]), assertion.isNot);
1749
2025
  case "toHaveProperty": {
1750
- this.#assertNonNullishSourceType(assertion);
1751
- this.#assertNonNullishTargetType(assertion);
1752
- const sourceText = assertion.typeChecker.typeToString(assertion.sourceType.type);
1753
- let targetArgumentText;
1754
- if (assertion.targetType.type.flags & this.compiler.TypeFlags.StringOrNumberLiteral) {
1755
- targetArgumentText = String(assertion.targetType.type.value);
2026
+ if (assertion.source[0] == null) {
2027
+ this.#onSourceArgumentMustBeProvided(assertion, expectResult);
2028
+ return;
1756
2029
  }
1757
- else if (assertion.targetType.type.flags & this.compiler.TypeFlags.UniqueESSymbol) {
1758
- targetArgumentText = `[${this.compiler.unescapeLeadingUnderscores(assertion.targetType.type.symbol.escapedName)}]`;
2030
+ const sourceType = this.#getType(assertion.source[0]);
2031
+ const nonPrimitiveType = { flags: this.compiler.TypeFlags.NonPrimitive };
2032
+ if (sourceType.flags & (this.compiler.TypeFlags.Any | this.compiler.TypeFlags.Never) ||
2033
+ !this.typeChecker.isTypeAssignableTo(sourceType, nonPrimitiveType)) {
2034
+ this.#onSourceArgumentMustBeObjectType(assertion.source[0], expectResult);
2035
+ return;
1759
2036
  }
1760
- else {
1761
- throw new Error("An argument for 'key' must be of type 'string | number | symbol'.");
2037
+ if (assertion.target[0] == null) {
2038
+ this.#onKeyArgumentMustBeProvided(assertion, expectResult);
2039
+ return;
1762
2040
  }
1763
- return [
1764
- Diagnostic.error(assertion.isNot
1765
- ? `Property '${targetArgumentText}' exists on type '${sourceText}'.`
1766
- : `Property '${targetArgumentText}' does not exist on type '${sourceText}'.`, origin),
1767
- ];
1768
- }
1769
- case "toMatch": {
1770
- this.#assertNonNullishSourceType(assertion);
1771
- this.#assertNonNullishTargetType(assertion);
1772
- const sourceTypeText = assertion.typeChecker.typeToString(assertion.sourceType.type);
1773
- const targetTypeText = assertion.typeChecker.typeToString(assertion.targetType.type);
1774
- return [
1775
- Diagnostic.error(assertion.isNot
1776
- ? `Type '${targetTypeText}' is a subtype of type '${sourceTypeText}'.`
1777
- : `Type '${targetTypeText}' is not a subtype of type '${sourceTypeText}'.`, origin),
1778
- ];
2041
+ const targetType = this.#getType(assertion.target[0]);
2042
+ if (!(this.#isStringOrNumberLiteralType(targetType) || this.#isUniqueSymbolType(targetType))) {
2043
+ this.#onKeyArgumentMustBeOfType(assertion.target[0], expectResult);
2044
+ return;
2045
+ }
2046
+ return this.toHaveProperty.match(sourceType, targetType, assertion.isNot);
1779
2047
  }
1780
2048
  case "toRaiseError": {
1781
- this.#assertNonNullishSourceType(assertion);
1782
- if (!this.#assertStringsOrNumbers(assertion.targetArguments)) {
1783
- throw new Error("An argument for 'target' must be of type 'string | number'.");
1784
- }
1785
- const sourceText = assertion.sourceType.source === 0 ? "Expression" : "Type definition";
1786
- if (assertion.diagnostics.length === 0) {
1787
- return [Diagnostic.error(`${sourceText} did not raise a type error.`, origin)];
1788
- }
1789
- if (assertion.isNot && assertion.targetArguments.length === 0) {
1790
- const related = [
1791
- Diagnostic.error(`The raised type error${assertion.diagnostics.length === 1 ? "" : "s"}:`),
1792
- ...Diagnostic.fromDiagnostics(assertion.diagnostics, this.compiler),
1793
- ];
1794
- return [
1795
- Diagnostic.error(`${sourceText} raised ${assertion.diagnostics.length === 1 ? "a" : assertion.diagnostics.length} type error${assertion.diagnostics.length === 1 ? "" : "s"}.`, origin).add({ related }),
1796
- ];
1797
- }
1798
- if (assertion.diagnostics.length !== assertion.targetArguments.length) {
1799
- const expectedText = assertion.diagnostics.length > assertion.targetArguments.length
1800
- ? `only ${assertion.targetArguments.length} type error${assertion.targetArguments.length === 1 ? "" : "s"}`
1801
- : `${assertion.targetArguments.length} type error${assertion.targetArguments.length === 1 ? "" : "s"}`;
1802
- const foundText = assertion.diagnostics.length > assertion.targetArguments.length
1803
- ? `${assertion.diagnostics.length}`
1804
- : `only ${assertion.diagnostics.length}`;
1805
- const related = [
1806
- Diagnostic.error(`The raised type error${assertion.diagnostics.length === 1 ? "" : "s"}:`),
1807
- ...Diagnostic.fromDiagnostics(assertion.diagnostics, this.compiler),
1808
- ];
1809
- const diagnostic = Diagnostic.error(`Expected ${expectedText}, but ${foundText} ${assertion.diagnostics.length === 1 ? "was" : "were"} raised.`, origin).add({
1810
- related,
1811
- });
1812
- return [diagnostic];
2049
+ if (assertion.source[0] == null) {
2050
+ this.#onSourceArgumentMustBeProvided(assertion, expectResult);
2051
+ return;
1813
2052
  }
1814
- const diagnostics = [];
1815
- assertion.targetArguments.forEach((argument, index) => {
1816
- const diagnostic = assertion.diagnostics[index];
1817
- if (!diagnostic) {
1818
- return;
1819
- }
1820
- const isMatch = this.#matchExpectedError(diagnostic, argument);
1821
- if (!assertion.isNot && !isMatch) {
1822
- const expectedText = this.compiler.isStringLiteralLike(argument)
1823
- ? `matching substring '${argument.text}'`
1824
- : `with code ${argument.text}`;
1825
- const related = [
1826
- Diagnostic.error("The raised type error:"),
1827
- ...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
1828
- ];
1829
- diagnostics.push(Diagnostic.error(`${sourceText} did not raise a type error ${expectedText}.`, origin).add({ related }));
1830
- }
1831
- if (assertion.isNot && isMatch) {
1832
- const expectedText = this.compiler.isStringLiteralLike(argument)
1833
- ? `matching substring '${argument.text}'`
1834
- : `with code ${argument.text}`;
1835
- const related = [
1836
- Diagnostic.error("The raised type error:"),
1837
- ...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
1838
- ];
1839
- diagnostics.push(Diagnostic.error(`${sourceText} raised a type error ${expectedText}.`, origin).add({ related }));
1840
- }
1841
- });
1842
- return diagnostics;
2053
+ const targetTypes = this.#getTypes(assertion.target);
2054
+ if (!this.#isArrayOfStringOrNumberLiteralTypes(targetTypes)) {
2055
+ this.#onTargetArgumentsMustBeStringOrNumberLiteralTypes(assertion.target, expectResult);
2056
+ return;
2057
+ }
2058
+ return this.toRaiseError.match({ diagnostics: [...assertion.diagnostics], node: assertion.source[0] }, targetTypes, assertion.isNot);
1843
2059
  }
1844
2060
  default:
1845
- throw new Error(`The '${matcher}' matcher is not supported.`);
2061
+ this.#onNotSupportedMatcherName(assertion, expectResult);
2062
+ return;
1846
2063
  }
1847
2064
  }
1848
- #hasTypeFlag(assertion, targetTypeFlag) {
1849
- this.#assertNonNullishSourceType(assertion);
1850
- return Boolean(assertion.sourceType.type.flags & targetTypeFlag);
2065
+ #onKeyArgumentMustBeOfType(node, expectResult) {
2066
+ const receivedTypeText = this.typeChecker.typeToString(this.#getType(node));
2067
+ const text = `An argument for 'key' must be of type 'string | number | symbol', received: '${receivedTypeText}'.`;
2068
+ const origin = {
2069
+ end: node.getEnd(),
2070
+ file: node.getSourceFile(),
2071
+ start: node.getStart(),
2072
+ };
2073
+ EventEmitter.dispatch(["expect:error", { diagnostics: [Diagnostic.error(text, origin)], result: expectResult }]);
1851
2074
  }
1852
- #isType(assertion, targetText) {
1853
- this.#assertNonNullishSourceType(assertion);
1854
- this.#assertNonNullishTypeChecker(assertion);
2075
+ #onKeyArgumentMustBeProvided(assertion, expectResult) {
1855
2076
  const origin = {
1856
- breadcrumbs: assertion.ancestorNames,
1857
2077
  end: assertion.matcherName.getEnd(),
1858
2078
  file: assertion.matcherName.getSourceFile(),
1859
2079
  start: assertion.matcherName.getStart(),
1860
2080
  };
1861
- const sourceText = assertion.typeChecker.typeToString(assertion.sourceType.type);
1862
- return [
1863
- Diagnostic.error(assertion.isNot
1864
- ? `Type '${targetText}' is identical to type '${sourceText}'.`
1865
- : `Type '${targetText}' is not identical to type '${sourceText}'.`, origin),
1866
- ];
2081
+ EventEmitter.dispatch([
2082
+ "expect:error",
2083
+ {
2084
+ diagnostics: [Diagnostic.error("An argument for 'key' must be provided.", origin)],
2085
+ result: expectResult,
2086
+ },
2087
+ ]);
1867
2088
  }
1868
- match(assertion) {
1869
- const matcher = assertion.matcherName.getText();
1870
- switch (matcher) {
1871
- case "toBeAssignable":
1872
- this.#assertNonNullishSourceType(assertion);
1873
- this.#assertNonNullishTargetType(assertion);
1874
- this.#assertNonNullish(assertion.typeChecker?.isTypeAssignableTo, "The 'isTypeAssignableTo' method is missing in the provided type checker.");
1875
- return assertion.typeChecker.isTypeAssignableTo(assertion.targetType.type, assertion.sourceType.type);
1876
- case "toBeAny": {
1877
- return this.#hasTypeFlag(assertion, this.compiler.TypeFlags.Any);
1878
- }
1879
- case "toBeBigInt": {
1880
- return this.#hasTypeFlag(assertion, this.compiler.TypeFlags.BigInt);
1881
- }
1882
- case "toBeBoolean": {
1883
- return this.#hasTypeFlag(assertion, this.compiler.TypeFlags.Boolean);
1884
- }
1885
- case "toBeNever": {
1886
- return this.#hasTypeFlag(assertion, this.compiler.TypeFlags.Never);
1887
- }
1888
- case "toBeNull": {
1889
- return this.#hasTypeFlag(assertion, this.compiler.TypeFlags.Null);
1890
- }
1891
- case "toBeNumber": {
1892
- return this.#hasTypeFlag(assertion, this.compiler.TypeFlags.Number);
1893
- }
1894
- case "toBeString": {
1895
- return this.#hasTypeFlag(assertion, this.compiler.TypeFlags.String);
1896
- }
1897
- case "toBeSymbol": {
1898
- return this.#hasTypeFlag(assertion, this.compiler.TypeFlags.ESSymbol);
1899
- }
1900
- case "toBeUndefined": {
1901
- return this.#hasTypeFlag(assertion, this.compiler.TypeFlags.Undefined);
1902
- }
1903
- case "toBeUniqueSymbol": {
1904
- return this.#hasTypeFlag(assertion, this.compiler.TypeFlags.UniqueESSymbol);
1905
- }
1906
- case "toBeUnknown": {
1907
- return this.#hasTypeFlag(assertion, this.compiler.TypeFlags.Unknown);
1908
- }
1909
- case "toBeVoid": {
1910
- return this.#hasTypeFlag(assertion, this.compiler.TypeFlags.Void);
1911
- }
1912
- case "toEqual": {
1913
- this.#assertNonNullishSourceType(assertion);
1914
- this.#assertNonNullishTargetType(assertion);
1915
- this.#assertNonNullish(assertion.typeChecker?.isTypeIdenticalTo, "The 'isTypeIdenticalTo' method is missing in the provided type checker.");
1916
- return assertion.typeChecker.isTypeIdenticalTo(assertion.sourceType.type, assertion.targetType.type);
1917
- }
1918
- case "toHaveProperty": {
1919
- this.#assertNonNullishSourceType(assertion);
1920
- this.#assertNonNullishTargetType(assertion);
1921
- if (!(assertion.sourceType.type.flags & this.compiler.TypeFlags.StructuredType)) {
1922
- const receivedText = assertion.typeChecker?.typeToString(assertion.sourceType.type);
1923
- throw new Error(`An argument for 'source' must be of object type, received: '${receivedText}'.`);
1924
- }
1925
- let targetArgumentText;
1926
- if (assertion.targetType.type.flags & this.compiler.TypeFlags.StringOrNumberLiteral) {
1927
- targetArgumentText = String(assertion.targetType.type.value);
1928
- }
1929
- else if (assertion.targetType.type.flags & this.compiler.TypeFlags.UniqueESSymbol) {
1930
- targetArgumentText = this.compiler.unescapeLeadingUnderscores(assertion.targetType.type.escapedName);
1931
- }
1932
- else {
1933
- throw new Error("An argument for 'key' must be of type 'string | number | symbol'.");
1934
- }
1935
- return assertion.sourceType.type.getProperties().some((property) => {
1936
- return this.compiler.unescapeLeadingUnderscores(property.escapedName) === targetArgumentText;
1937
- });
1938
- }
1939
- case "toMatch": {
1940
- this.#assertNonNullishSourceType(assertion);
1941
- this.#assertNonNullishTargetType(assertion);
1942
- this.#assertNonNullish(assertion.typeChecker?.isTypeSubtypeOf, "The 'isTypeSubtypeOf' method is missing in the provided type checker.");
1943
- return assertion.typeChecker.isTypeSubtypeOf(assertion.sourceType.type, assertion.targetType.type);
1944
- }
1945
- case "toRaiseError": {
1946
- if (!this.#assertStringsOrNumbers(assertion.targetArguments)) {
1947
- throw new Error("An argument for 'target' must be of type 'string | number'.");
1948
- }
1949
- if (assertion.targetArguments.length === 0) {
1950
- return assertion.diagnostics.length > 0;
1951
- }
1952
- if (assertion.diagnostics.length !== assertion.targetArguments.length) {
1953
- return false;
1954
- }
1955
- return assertion.targetArguments.every((expectedArgument, index) => {
1956
- if (this.compiler.isStringLiteralLike(expectedArgument)) {
1957
- return this.compiler
1958
- .flattenDiagnosticMessageText(assertion.diagnostics[index]?.messageText, " ", 0)
1959
- .includes(expectedArgument.text);
1960
- }
1961
- if (this.compiler.isNumericLiteral(expectedArgument)) {
1962
- return Number(expectedArgument.text) === assertion.diagnostics[index]?.code;
1963
- }
1964
- return false;
1965
- });
2089
+ #onNotSupportedMatcherName(assertion, expectResult) {
2090
+ const matcherNameText = assertion.matcherName.getText();
2091
+ const origin = {
2092
+ end: assertion.matcherName.getEnd(),
2093
+ file: assertion.matcherName.getSourceFile(),
2094
+ start: assertion.matcherName.getStart(),
2095
+ };
2096
+ EventEmitter.dispatch([
2097
+ "expect:error",
2098
+ {
2099
+ diagnostics: [Diagnostic.error(`The '${matcherNameText}()' matcher is not supported.`, origin)],
2100
+ result: expectResult,
2101
+ },
2102
+ ]);
2103
+ }
2104
+ #onSourceArgumentMustBeObjectType(node, expectResult) {
2105
+ const sourceText = this.compiler.isTypeNode(node) ? "A type argument for 'Source'" : "An argument for 'source'";
2106
+ const receivedTypeText = this.typeChecker.typeToString(this.#getType(node));
2107
+ const text = `${sourceText} must be of an object type, received: '${receivedTypeText}'.`;
2108
+ const origin = {
2109
+ end: node.getEnd(),
2110
+ file: node.getSourceFile(),
2111
+ start: node.getStart(),
2112
+ };
2113
+ EventEmitter.dispatch(["expect:error", { diagnostics: [Diagnostic.error(text, origin)], result: expectResult }]);
2114
+ }
2115
+ #onSourceArgumentMustBeProvided(assertion, expectResult) {
2116
+ const origin = {
2117
+ end: assertion.node.getEnd(),
2118
+ file: assertion.node.getSourceFile(),
2119
+ start: assertion.node.getStart(),
2120
+ };
2121
+ EventEmitter.dispatch([
2122
+ "expect:error",
2123
+ {
2124
+ diagnostics: [
2125
+ Diagnostic.error("An argument for 'source' or type argument for 'Source' must be provided.", origin),
2126
+ ],
2127
+ result: expectResult,
2128
+ },
2129
+ ]);
2130
+ }
2131
+ #onTargetArgumentMustBeProvided(assertion, expectResult) {
2132
+ const origin = {
2133
+ end: assertion.matcherName.getEnd(),
2134
+ file: assertion.matcherName.getSourceFile(),
2135
+ start: assertion.matcherName.getStart(),
2136
+ };
2137
+ EventEmitter.dispatch([
2138
+ "expect:error",
2139
+ {
2140
+ diagnostics: [
2141
+ Diagnostic.error("An argument for 'target' or type argument for 'Target' must be provided.", origin),
2142
+ ],
2143
+ result: expectResult,
2144
+ },
2145
+ ]);
2146
+ }
2147
+ #onTargetArgumentsMustBeStringOrNumberLiteralTypes(nodes, expectResult) {
2148
+ const diagnostics = [];
2149
+ for (const node of nodes) {
2150
+ const receivedType = this.#getType(node);
2151
+ if (!this.#isStringOrNumberLiteralType(receivedType)) {
2152
+ const receivedTypeText = this.typeChecker.typeToString(this.#getType(node));
2153
+ const origin = {
2154
+ end: node.getEnd(),
2155
+ file: node.getSourceFile(),
2156
+ start: node.getStart(),
2157
+ };
2158
+ diagnostics.push(Diagnostic.error(`An argument for 'target' must be of type 'string | number', received: '${receivedTypeText}'.`, origin));
1966
2159
  }
1967
- default:
1968
- throw new Error(`The '${matcher}' matcher is not supported.`);
1969
2160
  }
2161
+ EventEmitter.dispatch(["expect:error", { diagnostics, result: expectResult }]);
2162
+ }
2163
+ }
2164
+
2165
+ class ProjectService {
2166
+ compiler;
2167
+ #service;
2168
+ constructor(compiler) {
2169
+ this.compiler = compiler;
2170
+ function doNothing() {
2171
+ }
2172
+ function returnFalse() {
2173
+ return false;
2174
+ }
2175
+ function returnUndefined() {
2176
+ return undefined;
2177
+ }
2178
+ const noopWatcher = { close: doNothing };
2179
+ const noopLogger = {
2180
+ close: doNothing,
2181
+ endGroup: doNothing,
2182
+ getLogFileName: returnUndefined,
2183
+ hasLevel: returnFalse,
2184
+ info: doNothing,
2185
+ loggingEnabled: returnFalse,
2186
+ msg: doNothing,
2187
+ perftrc: doNothing,
2188
+ startGroup: doNothing,
2189
+ };
2190
+ const host = {
2191
+ ...this.compiler.sys,
2192
+ clearImmediate,
2193
+ clearTimeout,
2194
+ setImmediate,
2195
+ setTimeout,
2196
+ watchDirectory: () => noopWatcher,
2197
+ watchFile: () => noopWatcher,
2198
+ };
2199
+ this.#service = new this.compiler.server.ProjectService({
2200
+ allowLocalPluginLoads: true,
2201
+ cancellationToken: this.compiler.server.nullCancellationToken,
2202
+ host,
2203
+ logger: noopLogger,
2204
+ session: undefined,
2205
+ useInferredProjectPerProjectRoot: true,
2206
+ useSingleInferredProject: false,
2207
+ });
2208
+ }
2209
+ closeFile(filePath) {
2210
+ this.#service.closeClientFile(filePath);
2211
+ }
2212
+ getDefaultProject(filePath) {
2213
+ return this.#service.getDefaultProjectForFile(this.compiler.server.toNormalizedPath(filePath), true);
1970
2214
  }
1971
- #matchExpectedError(diagnostic, argument) {
1972
- if (this.compiler.isStringLiteralLike(argument)) {
1973
- return this.compiler.flattenDiagnosticMessageText(diagnostic.messageText, " ", 0).includes(argument.text);
2215
+ getLanguageService(filePath) {
2216
+ const project = this.getDefaultProject(filePath);
2217
+ if (!project) {
2218
+ return;
1974
2219
  }
1975
- if (this.compiler.isNumericLiteral(argument)) {
1976
- return Number(argument.text) === diagnostic.code;
2220
+ return project.getLanguageService(true);
2221
+ }
2222
+ openFile(filePath, sourceText, projectRootPath) {
2223
+ const { configFileErrors, configFileName } = this.#service.openClientFile(filePath, sourceText, undefined, projectRootPath);
2224
+ EventEmitter.dispatch([
2225
+ "project:info",
2226
+ { compilerVersion: this.compiler.version, projectConfigFilePath: configFileName },
2227
+ ]);
2228
+ if (configFileErrors && configFileErrors.length > 0) {
2229
+ EventEmitter.dispatch([
2230
+ "project:error",
2231
+ { diagnostics: Diagnostic.fromDiagnostics(configFileErrors, this.compiler) },
2232
+ ]);
1977
2233
  }
1978
- throw new Error("An argument for 'target' must be of type 'string | number'.");
1979
2234
  }
1980
2235
  }
1981
2236
 
1982
2237
  class TestTreeWorker {
1983
2238
  resolvedConfig;
1984
2239
  compiler;
1985
- #checker;
2240
+ #expect;
1986
2241
  #fileResult;
1987
2242
  #hasOnly;
1988
2243
  #position;
1989
2244
  #signal;
1990
- constructor(resolvedConfig, compiler, options) {
2245
+ constructor(resolvedConfig, compiler, expect, options) {
1991
2246
  this.resolvedConfig = resolvedConfig;
1992
2247
  this.compiler = compiler;
1993
- this.#checker = new Checker(compiler);
2248
+ this.#expect = expect;
2249
+ this.#fileResult = options.fileResult;
1994
2250
  this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
1995
2251
  this.#position = options.position;
1996
2252
  this.#signal = options.signal;
1997
- this.#fileResult = options.fileResult;
1998
2253
  }
1999
2254
  #resolveRunMode(mode, member) {
2000
2255
  if (member.flags & 1) {
@@ -2052,18 +2307,21 @@ class TestTreeWorker {
2052
2307
  EventEmitter.dispatch(["expect:skip", { result: expectResult }]);
2053
2308
  return;
2054
2309
  }
2055
- if (assertion.diagnostics.length > 0 && assertion.matcherName.getText() !== "toRaiseError") {
2310
+ if (assertion.diagnostics.size > 0 && assertion.matcherName.getText() !== "toRaiseError") {
2056
2311
  EventEmitter.dispatch([
2057
2312
  "expect:error",
2058
2313
  {
2059
- diagnostics: Diagnostic.fromDiagnostics(assertion.diagnostics, this.compiler),
2314
+ diagnostics: Diagnostic.fromDiagnostics([...assertion.diagnostics], this.compiler),
2060
2315
  result: expectResult,
2061
2316
  },
2062
2317
  ]);
2063
2318
  return;
2064
2319
  }
2065
- const isPass = this.#checker.match(assertion);
2066
- if (assertion.isNot ? !isPass : isPass) {
2320
+ const matchResult = this.#expect.match(assertion, expectResult);
2321
+ if (matchResult == null) {
2322
+ return;
2323
+ }
2324
+ if (assertion.isNot ? !matchResult.isMatch : matchResult.isMatch) {
2067
2325
  if (runMode & 1) {
2068
2326
  const text = ["The assertion was supposed to fail, but it passed.", "Consider removing the '.fail' flag."];
2069
2327
  const origin = {
@@ -2073,10 +2331,7 @@ class TestTreeWorker {
2073
2331
  };
2074
2332
  EventEmitter.dispatch([
2075
2333
  "expect:error",
2076
- {
2077
- diagnostics: [Diagnostic.error(text, origin)],
2078
- result: expectResult,
2079
- },
2334
+ { diagnostics: [Diagnostic.error(text, origin)], result: expectResult },
2080
2335
  ]);
2081
2336
  }
2082
2337
  else {
@@ -2088,13 +2343,20 @@ class TestTreeWorker {
2088
2343
  EventEmitter.dispatch(["expect:pass", { result: expectResult }]);
2089
2344
  }
2090
2345
  else {
2091
- EventEmitter.dispatch([
2092
- "expect:fail",
2093
- {
2094
- diagnostics: this.#checker.explain(assertion),
2095
- result: expectResult,
2096
- },
2097
- ]);
2346
+ const origin = {
2347
+ breadcrumbs: assertion.ancestorNames,
2348
+ end: assertion.matcherName.getEnd(),
2349
+ file: assertion.matcherName.getSourceFile(),
2350
+ start: assertion.matcherName.getStart(),
2351
+ };
2352
+ const diagnostics = [];
2353
+ for (const diagnostic of matchResult.explain()) {
2354
+ if (diagnostic.origin == null) {
2355
+ diagnostic.add({ origin });
2356
+ }
2357
+ diagnostics.push(diagnostic);
2358
+ }
2359
+ EventEmitter.dispatch(["expect:fail", { diagnostics, result: expectResult }]);
2098
2360
  }
2099
2361
  }
2100
2362
  }
@@ -2103,11 +2365,11 @@ class TestTreeWorker {
2103
2365
  EventEmitter.dispatch(["describe:start", { result: describeResult }]);
2104
2366
  runMode = this.#resolveRunMode(runMode, describe);
2105
2367
  if (!(runMode & 4 || (this.#hasOnly && !(runMode & 2)) || runMode & 8) &&
2106
- describe.diagnostics.length > 0) {
2368
+ describe.diagnostics.size > 0) {
2107
2369
  EventEmitter.dispatch([
2108
2370
  "file:error",
2109
2371
  {
2110
- diagnostics: Diagnostic.fromDiagnostics(describe.diagnostics, this.compiler),
2372
+ diagnostics: Diagnostic.fromDiagnostics([...describe.diagnostics], this.compiler),
2111
2373
  result: this.#fileResult,
2112
2374
  },
2113
2375
  ]);
@@ -2125,11 +2387,11 @@ class TestTreeWorker {
2125
2387
  EventEmitter.dispatch(["test:todo", { result: testResult }]);
2126
2388
  return;
2127
2389
  }
2128
- if (!(runMode & 4 || (this.#hasOnly && !(runMode & 2))) && test.diagnostics.length > 0) {
2390
+ if (!(runMode & 4 || (this.#hasOnly && !(runMode & 2))) && test.diagnostics.size > 0) {
2129
2391
  EventEmitter.dispatch([
2130
2392
  "test:error",
2131
2393
  {
2132
- diagnostics: Diagnostic.fromDiagnostics(test.diagnostics, this.compiler),
2394
+ diagnostics: Diagnostic.fromDiagnostics([...test.diagnostics], this.compiler),
2133
2395
  result: testResult,
2134
2396
  },
2135
2397
  ]);
@@ -2198,19 +2460,25 @@ class TestFileRunner {
2198
2460
  if (!sourceFile) {
2199
2461
  return;
2200
2462
  }
2201
- const typeChecker = program.getTypeChecker();
2202
- const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics, typeChecker);
2203
- if (testTree.diagnostics.length > 0) {
2463
+ const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics);
2464
+ if (testTree.diagnostics.size > 0) {
2204
2465
  EventEmitter.dispatch([
2205
2466
  "file:error",
2206
2467
  {
2207
- diagnostics: Diagnostic.fromDiagnostics(testTree.diagnostics, this.compiler),
2468
+ diagnostics: Diagnostic.fromDiagnostics([...testTree.diagnostics], this.compiler),
2208
2469
  result: fileResult,
2209
2470
  },
2210
2471
  ]);
2211
2472
  return;
2212
2473
  }
2213
- const testTreeWorker = new TestTreeWorker(this.resolvedConfig, this.compiler, {
2474
+ const typeChecker = program.getTypeChecker();
2475
+ if (!Expect.assertTypeChecker(typeChecker)) {
2476
+ const text = "The required 'isTypeAssignableTo()', 'isTypeIdenticalTo()' and 'isTypeSubtypeOf()' methods are missing in the provided type checker.";
2477
+ EventEmitter.dispatch(["file:error", { diagnostics: [Diagnostic.error(text)], result: fileResult }]);
2478
+ return;
2479
+ }
2480
+ const expect = new Expect(this.compiler, typeChecker);
2481
+ const testTreeWorker = new TestTreeWorker(this.resolvedConfig, this.compiler, expect, {
2214
2482
  fileResult,
2215
2483
  hasOnly: testTree.hasOnly,
2216
2484
  position,
@@ -2257,17 +2525,13 @@ class TSTyche {
2257
2525
  #abortController = new AbortController();
2258
2526
  #storeService;
2259
2527
  #taskRunner;
2528
+ static version = "1.0.0-beta.8";
2260
2529
  constructor(resolvedConfig, storeService) {
2261
2530
  this.resolvedConfig = resolvedConfig;
2262
2531
  this.#storeService = storeService;
2263
2532
  this.#taskRunner = new TaskRunner(this.resolvedConfig, this.#storeService);
2264
2533
  this.#addEventHandlers();
2265
2534
  }
2266
- static get version() {
2267
- const packageConfig = readFileSync(new URL("../package.json", import.meta.url), { encoding: "utf8" });
2268
- const { version } = JSON.parse(packageConfig);
2269
- return version;
2270
- }
2271
2535
  #addEventHandlers() {
2272
2536
  EventEmitter.addHandler(([eventName, payload]) => {
2273
2537
  if (eventName.includes("error") || eventName.includes("fail")) {
@@ -2325,19 +2589,19 @@ class OptionDefinitionsMap {
2325
2589
  name: "failFast",
2326
2590
  },
2327
2591
  {
2328
- brand: "boolean",
2329
- description: "Print the list of CLI options with brief descriptions and exit.",
2592
+ brand: "true",
2593
+ description: "Print the list of command line options with brief descriptions and exit.",
2330
2594
  group: 2,
2331
2595
  name: "help",
2332
2596
  },
2333
2597
  {
2334
- brand: "boolean",
2598
+ brand: "true",
2335
2599
  description: "Install specified versions of the 'typescript' package and exit.",
2336
2600
  group: 2,
2337
2601
  name: "install",
2338
2602
  },
2339
2603
  {
2340
- brand: "boolean",
2604
+ brand: "true",
2341
2605
  description: "Print the list of the selected test files and exit.",
2342
2606
  group: 2,
2343
2607
  name: "listFiles",
@@ -2349,7 +2613,7 @@ class OptionDefinitionsMap {
2349
2613
  name: "only",
2350
2614
  },
2351
2615
  {
2352
- brand: "boolean",
2616
+ brand: "true",
2353
2617
  description: "Remove all installed versions of the 'typescript' package and exit.",
2354
2618
  group: 2,
2355
2619
  name: "prune",
@@ -2361,7 +2625,7 @@ class OptionDefinitionsMap {
2361
2625
  name: "rootPath",
2362
2626
  },
2363
2627
  {
2364
- brand: "boolean",
2628
+ brand: "true",
2365
2629
  description: "Print the resolved configuration and exit.",
2366
2630
  group: 2,
2367
2631
  name: "showConfig",
@@ -2394,13 +2658,13 @@ class OptionDefinitionsMap {
2394
2658
  name: "testFileMatch",
2395
2659
  },
2396
2660
  {
2397
- brand: "boolean",
2661
+ brand: "true",
2398
2662
  description: "Fetch the 'typescript' package metadata from the registry and exit.",
2399
2663
  group: 2,
2400
2664
  name: "update",
2401
2665
  },
2402
2666
  {
2403
- brand: "boolean",
2667
+ brand: "true",
2404
2668
  description: "Print the version number and exit.",
2405
2669
  group: 2,
2406
2670
  name: "version",
@@ -2467,15 +2731,15 @@ class OptionUsageText {
2467
2731
  this.#optionDiagnosticText = new OptionDiagnosticText(this.#optionGroup);
2468
2732
  this.#storeService = storeService;
2469
2733
  }
2470
- get(optionName, optionBrand) {
2734
+ async get(optionName, optionBrand) {
2471
2735
  const usageText = [];
2472
2736
  switch (optionName) {
2473
2737
  case "target": {
2474
- const { supportedTags } = this.#storeService;
2738
+ const supportedTags = await this.#storeService.getSupportedTags();
2475
2739
  const supportedTagsText = `Supported tags: ${["'", supportedTags.join("', '"), "'"].join("")}.`;
2476
2740
  switch (this.#optionGroup) {
2477
2741
  case 2:
2478
- usageText.push("Argument for the '--target' option must be a single tag or a comma separated list.", "Usage examples: '--target 4.9', '--target 5.0.4', '--target 4.7,4.8,latest'.", supportedTagsText);
2742
+ usageText.push("Argument for the '--target' option must be a single tag or a comma separated list.", "Usage examples: '--target 4.9', '--target 5.0.4', '--target 4.7,5.3.2,current'.", supportedTagsText);
2479
2743
  break;
2480
2744
  case 4:
2481
2745
  usageText.push("Item of the 'target' list must be a supported version tag.", supportedTagsText);
@@ -2503,7 +2767,7 @@ class OptionValidator {
2503
2767
  this.#optionDiagnosticText = new OptionDiagnosticText(this.#optionGroup);
2504
2768
  this.#optionUsageText = new OptionUsageText(this.#optionGroup, this.#storeService);
2505
2769
  }
2506
- check(optionName, optionValue, optionBrand, origin) {
2770
+ async check(optionName, optionValue, optionBrand, origin) {
2507
2771
  switch (optionName) {
2508
2772
  case "config":
2509
2773
  case "rootPath":
@@ -2513,13 +2777,11 @@ class OptionValidator {
2513
2777
  }
2514
2778
  break;
2515
2779
  case "target":
2516
- {
2517
- if (!this.#storeService.validateTag(optionValue)) {
2518
- this.#onDiagnostic(Diagnostic.error([
2519
- this.#optionDiagnosticText.versionIsNotSupported(optionValue),
2520
- ...this.#optionUsageText.get(optionName, optionBrand),
2521
- ], origin));
2522
- }
2780
+ if (!(await this.#storeService.validateTag(optionValue))) {
2781
+ this.#onDiagnostic(Diagnostic.error([
2782
+ this.#optionDiagnosticText.versionIsNotSupported(optionValue),
2783
+ ...(await this.#optionUsageText.get(optionName, optionBrand)),
2784
+ ], origin));
2523
2785
  }
2524
2786
  break;
2525
2787
  }
@@ -2545,29 +2807,23 @@ class CommandLineOptionsWorker {
2545
2807
  this.#optionUsageText = new OptionUsageText(2, this.#storeService);
2546
2808
  this.#optionValidator = new OptionValidator(2, this.#storeService, this.#onDiagnostic);
2547
2809
  }
2548
- #normalizePath(filePath) {
2549
- if (path.sep === "/") {
2550
- return filePath;
2551
- }
2552
- return filePath.replace(/\\/g, "/");
2553
- }
2554
- #onExpectsArgumentDiagnostic(optionDefinition) {
2810
+ async #onExpectsArgumentDiagnostic(optionDefinition) {
2555
2811
  const text = [
2556
2812
  this.#optionDiagnosticText.expectsArgument(optionDefinition.name),
2557
- ...this.#optionUsageText.get(optionDefinition.name, optionDefinition.brand),
2813
+ ...(await this.#optionUsageText.get(optionDefinition.name, optionDefinition.brand)),
2558
2814
  ];
2559
2815
  this.#onDiagnostic(Diagnostic.error(text));
2560
2816
  }
2561
- parse(commandLineArgs) {
2817
+ async parse(commandLineArgs) {
2562
2818
  let index = 0;
2563
2819
  let arg = commandLineArgs[index];
2564
- while (arg !== undefined) {
2820
+ while (arg != null) {
2565
2821
  index++;
2566
2822
  if (arg.startsWith("--")) {
2567
2823
  const optionName = arg.slice(2);
2568
2824
  const optionDefinition = this.#commandLineOptionDefinitions.get(optionName);
2569
2825
  if (optionDefinition) {
2570
- index = this.#parseOptionValue(commandLineArgs, index, optionDefinition);
2826
+ index = await this.#parseOptionValue(commandLineArgs, index, optionDefinition);
2571
2827
  }
2572
2828
  else {
2573
2829
  this.#onDiagnostic(Diagnostic.error(this.#optionDiagnosticText.unknownOption(arg)));
@@ -2577,14 +2833,17 @@ class CommandLineOptionsWorker {
2577
2833
  this.#onDiagnostic(Diagnostic.error(this.#optionDiagnosticText.unknownOption(arg)));
2578
2834
  }
2579
2835
  else {
2580
- this.#pathMatch.push(arg);
2836
+ this.#pathMatch.push(Path.normalizeSlashes(arg));
2581
2837
  }
2582
2838
  arg = commandLineArgs[index];
2583
2839
  }
2584
2840
  }
2585
- #parseOptionValue(commandLineArgs, index, optionDefinition) {
2841
+ async #parseOptionValue(commandLineArgs, index, optionDefinition) {
2586
2842
  let optionValue = this.#resolveOptionValue(commandLineArgs[index]);
2587
2843
  switch (optionDefinition.brand) {
2844
+ case "true":
2845
+ this.#commandLineOptions[optionDefinition.name] = true;
2846
+ break;
2588
2847
  case "boolean":
2589
2848
  this.#commandLineOptions[optionDefinition.name] = optionValue !== "false";
2590
2849
  if (optionValue === "false" || optionValue === "true") {
@@ -2598,33 +2857,25 @@ class CommandLineOptionsWorker {
2598
2857
  .map((value) => value.trim())
2599
2858
  .filter((value) => value !== "");
2600
2859
  for (const optionValue of optionValues) {
2601
- this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
2860
+ await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
2602
2861
  }
2603
2862
  this.#commandLineOptions[optionDefinition.name] = optionValues;
2604
2863
  index++;
2605
2864
  break;
2606
2865
  }
2607
- this.#onExpectsArgumentDiagnostic(optionDefinition);
2608
- break;
2609
- case "number":
2610
- if (optionValue != null) {
2611
- this.#commandLineOptions[optionDefinition.name] = Number(optionValue);
2612
- index++;
2613
- break;
2614
- }
2615
- this.#onExpectsArgumentDiagnostic(optionDefinition);
2866
+ await this.#onExpectsArgumentDiagnostic(optionDefinition);
2616
2867
  break;
2617
2868
  case "string":
2618
2869
  if (optionValue != null) {
2619
2870
  if (optionDefinition.name === "config") {
2620
- optionValue = this.#normalizePath(path.resolve(optionValue));
2871
+ optionValue = Path.resolve(optionValue);
2621
2872
  }
2622
- this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
2873
+ await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
2623
2874
  this.#commandLineOptions[optionDefinition.name] = optionValue;
2624
2875
  index++;
2625
2876
  break;
2626
2877
  }
2627
- this.#onExpectsArgumentDiagnostic(optionDefinition);
2878
+ await this.#onExpectsArgumentDiagnostic(optionDefinition);
2628
2879
  break;
2629
2880
  }
2630
2881
  return index;
@@ -2660,12 +2911,6 @@ class ConfigFileOptionsWorker {
2660
2911
  return (node.kind === this.compiler.SyntaxKind.StringLiteral &&
2661
2912
  sourceFile.text.slice(this.#skipTrivia(node.pos, sourceFile), node.end).startsWith('"'));
2662
2913
  }
2663
- #normalizePath(filePath) {
2664
- if (path.sep === "/") {
2665
- return filePath;
2666
- }
2667
- return filePath.replace(/\\/g, "/");
2668
- }
2669
2914
  async parse(sourceText) {
2670
2915
  if (sourceText === "") {
2671
2916
  return;
@@ -2744,22 +2989,17 @@ class ConfigFileOptionsWorker {
2744
2989
  if (optionDefinition.brand === "string") {
2745
2990
  let value = valueExpression.text;
2746
2991
  if (optionDefinition.name === "rootPath") {
2747
- value = this.#normalizePath(path.resolve(path.dirname(this.#configFilePath), value));
2992
+ value = Path.resolve(Path.dirname(this.#configFilePath), value);
2748
2993
  }
2749
2994
  const origin = {
2750
2995
  end: valueExpression.end,
2751
2996
  file: sourceFile,
2752
2997
  start: this.#skipTrivia(valueExpression.pos, sourceFile),
2753
2998
  };
2754
- this.#optionValidator.check(optionDefinition.name, value, optionDefinition.brand, origin);
2999
+ await this.#optionValidator.check(optionDefinition.name, value, optionDefinition.brand, origin);
2755
3000
  return value;
2756
3001
  }
2757
3002
  break;
2758
- case this.compiler.SyntaxKind.NumericLiteral:
2759
- if (optionDefinition.brand === "number") {
2760
- return Number(valueExpression.text);
2761
- }
2762
- break;
2763
3003
  case this.compiler.SyntaxKind.ArrayLiteralExpression:
2764
3004
  if (optionDefinition.brand === "list") {
2765
3005
  const value = [];
@@ -2866,7 +3106,7 @@ class ConfigService {
2866
3106
  allowNoTestFiles: false,
2867
3107
  failFast: false,
2868
3108
  rootPath: "./",
2869
- target: ["latest"],
3109
+ target: [Environment.isTypeScriptInstalled ? "current" : "latest"],
2870
3110
  testFileMatch: ["**/*.tst.*", "**/__typetests__/*.test.*", "**/typetests/*.test.*"],
2871
3111
  };
2872
3112
  #pathMatch = [];
@@ -2884,25 +3124,19 @@ class ConfigService {
2884
3124
  static get defaultOptions() {
2885
3125
  return ConfigService.#defaultOptions;
2886
3126
  }
2887
- #normalizePath(filePath) {
2888
- if (path.sep === "/") {
2889
- return filePath;
2890
- }
2891
- return filePath.replace(/\\/g, "/");
2892
- }
2893
3127
  #onDiagnostic = (diagnostic) => {
2894
3128
  EventEmitter.dispatch(["config:error", { diagnostics: [diagnostic] }]);
2895
3129
  };
2896
- parseCommandLine(commandLineArgs) {
3130
+ async parseCommandLine(commandLineArgs) {
2897
3131
  this.#commandLineOptions = {};
2898
3132
  this.#pathMatch = [];
2899
3133
  const commandLineWorker = new CommandLineOptionsWorker(this.#commandLineOptions, this.#pathMatch, this.#storeService, this.#onDiagnostic);
2900
- commandLineWorker.parse(commandLineArgs);
3134
+ await commandLineWorker.parse(commandLineArgs);
2901
3135
  }
2902
3136
  async readConfigFile(filePath, sourceText) {
2903
- const configFilePath = filePath ?? this.#commandLineOptions.config ?? path.resolve("./tstyche.config.json");
3137
+ const configFilePath = filePath ?? this.#commandLineOptions.config ?? Path.resolve("./tstyche.config.json");
2904
3138
  this.#configFileOptions = {
2905
- rootPath: this.#normalizePath(path.dirname(configFilePath)),
3139
+ rootPath: Path.dirname(configFilePath),
2906
3140
  };
2907
3141
  let configFileText = sourceText ?? "";
2908
3142
  if (sourceText == null && existsSync(configFilePath)) {
@@ -2918,7 +3152,7 @@ class ConfigService {
2918
3152
  ...ConfigService.#defaultOptions,
2919
3153
  ...this.#configFileOptions,
2920
3154
  ...this.#commandLineOptions,
2921
- pathMatch: this.#pathMatch.map((match) => this.#normalizePath(match)),
3155
+ pathMatch: this.#pathMatch,
2922
3156
  };
2923
3157
  return mergedOptions;
2924
3158
  }
@@ -2927,7 +3161,7 @@ class ConfigService {
2927
3161
  let testFilePaths = this.compiler.sys.readDirectory(rootPath, undefined, undefined, testFileMatch);
2928
3162
  if (pathMatch.length > 0) {
2929
3163
  testFilePaths = testFilePaths.filter((testFilePath) => pathMatch.some((match) => {
2930
- const relativeTestFilePath = this.#normalizePath(`./${path.relative("", testFilePath)}`);
3164
+ const relativeTestFilePath = Path.relative("", testFilePath);
2931
3165
  return relativeTestFilePath.toLowerCase().includes(match.toLowerCase());
2932
3166
  }));
2933
3167
  }
@@ -2951,6 +3185,7 @@ var OptionBrand;
2951
3185
  OptionBrand["String"] = "string";
2952
3186
  OptionBrand["Number"] = "number";
2953
3187
  OptionBrand["Boolean"] = "boolean";
3188
+ OptionBrand["True"] = "true";
2954
3189
  OptionBrand["List"] = "list";
2955
3190
  OptionBrand["Object"] = "object";
2956
3191
  })(OptionBrand || (OptionBrand = {}));
@@ -2961,162 +3196,27 @@ var OptionGroup;
2961
3196
  OptionGroup[OptionGroup["ConfigFile"] = 4] = "ConfigFile";
2962
3197
  })(OptionGroup || (OptionGroup = {}));
2963
3198
 
2964
- class Lock {
2965
- #lockFilePath;
2966
- static #lockSuffix = "__lock__";
2967
- constructor(targetPath) {
2968
- this.#lockFilePath = Lock.#getLockFilePath(targetPath);
2969
- writeFileSync(this.#lockFilePath, "");
2970
- process.on("exit", () => {
2971
- this.release();
2972
- });
2973
- }
2974
- static #getLockFilePath(targetPath) {
2975
- return `${targetPath}${Lock.#lockSuffix}`;
3199
+ class ManifestWorker {
3200
+ #manifestFileName = "store-manifest.json";
3201
+ #manifestFilePath;
3202
+ #onDiagnostic;
3203
+ #prune;
3204
+ #registryUrl = new URL("https://registry.npmjs.org");
3205
+ #storePath;
3206
+ #timeout = Environment.timeout * 1000;
3207
+ #version = "1";
3208
+ constructor(storePath, onDiagnostic, prune) {
3209
+ this.#storePath = storePath;
3210
+ this.#onDiagnostic = onDiagnostic;
3211
+ this.#manifestFilePath = Path.join(storePath, this.#manifestFileName);
3212
+ this.#prune = prune;
2976
3213
  }
2977
- static async isLocked(targetPath, options) {
2978
- let isLocked = existsSync(Lock.#getLockFilePath(targetPath));
2979
- if (!isLocked) {
2980
- return isLocked;
2981
- }
2982
- if (options?.timeout == null) {
2983
- return isLocked;
2984
- }
2985
- const waitStartTime = Date.now();
2986
- while (isLocked) {
2987
- if (options.signal?.aborted === true) {
2988
- break;
2989
- }
2990
- if (Date.now() - waitStartTime > options.timeout) {
2991
- options.onDiagnostic?.(`Lock wait timeout of ${options.timeout / 1000}s was exceeded.`);
2992
- break;
2993
- }
2994
- await Lock.#sleep(1000);
2995
- isLocked = existsSync(Lock.#getLockFilePath(targetPath));
3214
+ async #create(signal) {
3215
+ const manifest = await this.#load(signal);
3216
+ if (manifest != null) {
3217
+ await this.persist(manifest);
2996
3218
  }
2997
- return isLocked;
2998
- }
2999
- release() {
3000
- rmSync(this.#lockFilePath, { force: true });
3001
- }
3002
- static async #sleep(time) {
3003
- return new Promise((resolve) => setTimeout(resolve, time));
3004
- }
3005
- }
3006
-
3007
- class CompilerModuleWorker {
3008
- #cachePath;
3009
- #onDiagnostic;
3010
- #readyFileName = "__ready__";
3011
- #timeout = Environment.timeout * 1000;
3012
- constructor(cachePath, onDiagnostic) {
3013
- this.#cachePath = cachePath;
3014
- this.#onDiagnostic = onDiagnostic;
3015
- }
3016
- async ensure(compilerVersion, signal) {
3017
- const installationPath = path.join(this.#cachePath, compilerVersion);
3018
- const readyFilePath = path.join(installationPath, this.#readyFileName);
3019
- const tsserverFilePath = path.join(installationPath, "node_modules", "typescript", "lib", "tsserverlibrary.js");
3020
- const typescriptFilePath = path.join(installationPath, "node_modules", "typescript", "lib", "typescript.js");
3021
- if (await Lock.isLocked(installationPath, {
3022
- onDiagnostic: (text) => {
3023
- this.#onDiagnostic(Diagnostic.error([`Failed to install 'typescript@${compilerVersion}'.`, text]));
3024
- },
3025
- signal,
3026
- timeout: this.#timeout,
3027
- })) {
3028
- return;
3029
- }
3030
- if (existsSync(readyFilePath)) {
3031
- return tsserverFilePath;
3032
- }
3033
- EventEmitter.dispatch(["store:info", { compilerVersion, installationPath: this.#normalizePath(installationPath) }]);
3034
- try {
3035
- await fs.mkdir(installationPath, { recursive: true });
3036
- const lock = new Lock(installationPath);
3037
- await fs.writeFile(path.join(installationPath, "package.json"), this.#getPackageJson(compilerVersion));
3038
- await this.#installPackage(installationPath, signal);
3039
- await fs.writeFile(tsserverFilePath, await this.#getPatched(compilerVersion, tsserverFilePath));
3040
- await fs.writeFile(typescriptFilePath, await this.#getPatched(compilerVersion, typescriptFilePath));
3041
- await fs.writeFile(readyFilePath, "");
3042
- lock.release();
3043
- }
3044
- catch (error) {
3045
- this.#onDiagnostic(Diagnostic.fromError(`Failed to install 'typescript@${compilerVersion}'.`, error));
3046
- }
3047
- return tsserverFilePath;
3048
- }
3049
- #getPackageJson(version) {
3050
- const packageJson = {
3051
- name: "@tstyche/typescript",
3052
- version,
3053
- description: "Do not change. This package was generated by TSTyche",
3054
- private: true,
3055
- license: "MIT",
3056
- dependencies: {
3057
- typescript: version,
3058
- },
3059
- };
3060
- return JSON.stringify(packageJson, null, 2);
3061
- }
3062
- async #getPatched(version, filePath) {
3063
- function ts5Patch(match, indent) {
3064
- return [match, indent, "isTypeIdenticalTo,", indent, "isTypeSubtypeOf,"].join("");
3065
- }
3066
- function ts4Patch(match, indent) {
3067
- return [match, indent, "isTypeIdenticalTo: isTypeIdenticalTo,", indent, "isTypeSubtypeOf: isTypeSubtypeOf,"].join("");
3068
- }
3069
- const fileContent = await fs.readFile(filePath, { encoding: "utf8" });
3070
- if (version.startsWith("5")) {
3071
- return fileContent.replace(/(\s+)isTypeAssignableTo,/, ts5Patch);
3072
- }
3073
- else {
3074
- return fileContent.replace(/(\s+)isTypeAssignableTo: isTypeAssignableTo,/, ts4Patch);
3075
- }
3076
- }
3077
- async #installPackage(cwd, signal) {
3078
- const args = ["install", "--ignore-scripts", "--no-bin-links", "--no-package-lock"];
3079
- return new Promise((resolve, reject) => {
3080
- const spawnedNpm = spawn("npm", args, {
3081
- cwd,
3082
- shell: true,
3083
- signal,
3084
- stdio: "ignore",
3085
- timeout: this.#timeout,
3086
- });
3087
- spawnedNpm.on("error", (error) => {
3088
- reject(error);
3089
- });
3090
- spawnedNpm.on("close", (code, signal) => {
3091
- if (code === 0) {
3092
- resolve();
3093
- }
3094
- if (signal != null) {
3095
- reject(new Error(`Setup timeout of ${this.#timeout / 1000}s was exceeded.`));
3096
- }
3097
- reject(new Error(`Process exited with code ${String(code)}.`));
3098
- });
3099
- });
3100
- }
3101
- #normalizePath(filePath) {
3102
- if (path.sep === "/") {
3103
- return filePath;
3104
- }
3105
- return filePath.replace(/\\/g, "/");
3106
- }
3107
- }
3108
-
3109
- class ManifestWorker {
3110
- #cachePath;
3111
- #manifestFileName = "store-manifest.json";
3112
- #manifestFilePath;
3113
- #prune;
3114
- #registryUrl = new URL("https://registry.npmjs.org");
3115
- #timeout = Environment.timeout * 1000;
3116
- constructor(cachePath, prune) {
3117
- this.#cachePath = cachePath;
3118
- this.#manifestFilePath = path.join(cachePath, this.#manifestFileName);
3119
- this.#prune = prune;
3219
+ return manifest;
3120
3220
  }
3121
3221
  async #fetch(signal) {
3122
3222
  return new Promise((resolve, reject) => {
@@ -3154,8 +3254,9 @@ class ManifestWorker {
3154
3254
  }
3155
3255
  return false;
3156
3256
  }
3157
- async #load(signal, isUpdate = false) {
3257
+ async #load(signal, options = { quite: false }) {
3158
3258
  const manifest = {
3259
+ $version: this.#version,
3159
3260
  lastUpdated: Date.now(),
3160
3261
  resolutions: {},
3161
3262
  versions: [],
@@ -3167,13 +3268,13 @@ class ManifestWorker {
3167
3268
  abortController.abort(`Setup timeout of ${this.#timeout / 1000}s was exceeded.`);
3168
3269
  }, { once: true });
3169
3270
  signal?.addEventListener("abort", () => {
3170
- abortController.abort(`Fetch got canceled by request.`);
3271
+ abortController.abort("Fetch got canceled by request.");
3171
3272
  }, { once: true });
3172
3273
  try {
3173
3274
  packageMetadata = await this.#fetch(abortController.signal);
3174
3275
  }
3175
3276
  catch (error) {
3176
- if (!isUpdate) {
3277
+ if (!options.quite) {
3177
3278
  const text = [`Failed to fetch metadata of the 'typescript' package from '${this.#registryUrl.href}'.`];
3178
3279
  if (error instanceof Error && error.name !== "AbortError") {
3179
3280
  text.push("Might be there is an issue with the registry or the network connection.");
@@ -3202,18 +3303,10 @@ class ManifestWorker {
3202
3303
  }
3203
3304
  return manifest;
3204
3305
  }
3205
- #onDiagnostic(diagnostic) {
3206
- EventEmitter.dispatch(["store:error", { diagnostics: [diagnostic] }]);
3207
- }
3208
- async open(signal) {
3306
+ async open(signal, options) {
3209
3307
  let manifest;
3210
3308
  if (!existsSync(this.#manifestFilePath)) {
3211
- manifest = await this.#load(signal);
3212
- if (manifest == null) {
3213
- return;
3214
- }
3215
- await this.persist(manifest);
3216
- return manifest;
3309
+ return this.#create(signal);
3217
3310
  }
3218
3311
  let manifestText;
3219
3312
  try {
@@ -3228,85 +3321,248 @@ class ManifestWorker {
3228
3321
  try {
3229
3322
  manifest = JSON.parse(manifestText);
3230
3323
  }
3231
- catch (error) {
3232
- this.#onDiagnostic(Diagnostic.fromError([
3233
- `Failed to parse '${this.#manifestFilePath}'.`,
3234
- "Cached files appeared to be corrupt and got removed. Try running 'tstyche' again.",
3235
- ], error));
3324
+ catch {
3236
3325
  }
3237
- if (manifest == null) {
3326
+ if (manifest == null || manifest.$version !== this.#version) {
3238
3327
  await this.#prune();
3239
- return;
3328
+ return this.#create(signal);
3240
3329
  }
3241
- if (this.isOutdated(manifest)) {
3242
- manifest = await this.update(manifest, signal);
3330
+ if (this.isOutdated(manifest) || options?.refresh === true) {
3331
+ const quite = options?.refresh !== true;
3332
+ const freshManifest = await this.#load(signal, { quite });
3333
+ if (freshManifest != null) {
3334
+ manifest = { ...manifest, ...freshManifest };
3335
+ await this.persist(manifest);
3336
+ }
3243
3337
  }
3244
3338
  return manifest;
3245
3339
  }
3246
3340
  async persist(manifest) {
3247
- if (!existsSync(this.#cachePath)) {
3248
- await fs.mkdir(this.#cachePath, { recursive: true });
3341
+ if (!existsSync(this.#storePath)) {
3342
+ await fs.mkdir(this.#storePath, { recursive: true });
3249
3343
  }
3250
3344
  await fs.writeFile(this.#manifestFilePath, JSON.stringify(manifest));
3251
3345
  }
3252
- async update(manifest, signal) {
3253
- const freshManifest = await this.#load(signal, true);
3254
- if (freshManifest != null) {
3255
- manifest = { ...manifest, ...freshManifest };
3256
- await this.persist(manifest);
3346
+ }
3347
+
3348
+ class Lock {
3349
+ #lockFilePath;
3350
+ static #lockSuffix = "__lock__";
3351
+ constructor(targetPath) {
3352
+ this.#lockFilePath = Lock.#getLockFilePath(targetPath);
3353
+ writeFileSync(this.#lockFilePath, "");
3354
+ process.on("exit", () => {
3355
+ this.release();
3356
+ });
3357
+ }
3358
+ static #getLockFilePath(targetPath) {
3359
+ return `${targetPath}${Lock.#lockSuffix}`;
3360
+ }
3361
+ static async isLocked(targetPath, options) {
3362
+ let isLocked = existsSync(Lock.#getLockFilePath(targetPath));
3363
+ if (!isLocked) {
3364
+ return isLocked;
3257
3365
  }
3258
- return manifest;
3366
+ if (options?.timeout == null) {
3367
+ return isLocked;
3368
+ }
3369
+ const waitStartTime = Date.now();
3370
+ while (isLocked) {
3371
+ if (options.signal?.aborted === true) {
3372
+ break;
3373
+ }
3374
+ if (Date.now() - waitStartTime > options.timeout) {
3375
+ options.onDiagnostic?.(`Lock wait timeout of ${options.timeout / 1000}s was exceeded.`);
3376
+ break;
3377
+ }
3378
+ await Lock.#sleep(1000);
3379
+ isLocked = existsSync(Lock.#getLockFilePath(targetPath));
3380
+ }
3381
+ return isLocked;
3382
+ }
3383
+ release() {
3384
+ rmSync(this.#lockFilePath, { force: true });
3385
+ }
3386
+ static async #sleep(time) {
3387
+ return new Promise((resolve) => setTimeout(resolve, time));
3388
+ }
3389
+ }
3390
+
3391
+ class Version {
3392
+ static satisfies(source, target) {
3393
+ const sourceElements = source.split(/\.|-/);
3394
+ const targetElements = target.split(/\.|-/);
3395
+ function compare(index = 0) {
3396
+ const sourceElement = sourceElements[index];
3397
+ const targetElement = targetElements[index];
3398
+ if (sourceElement > targetElement) {
3399
+ return true;
3400
+ }
3401
+ if (sourceElement === targetElement) {
3402
+ if (index === targetElements.length - 1) {
3403
+ return true;
3404
+ }
3405
+ return compare(index + 1);
3406
+ }
3407
+ return false;
3408
+ }
3409
+ return compare();
3410
+ }
3411
+ }
3412
+
3413
+ class PackageInstaller {
3414
+ #onDiagnostic;
3415
+ #readyFileName = "__ready__";
3416
+ #storePath;
3417
+ #timeout = Environment.timeout * 1000;
3418
+ constructor(storePath, onDiagnostic) {
3419
+ this.#storePath = storePath;
3420
+ this.#onDiagnostic = onDiagnostic;
3421
+ }
3422
+ async ensure(compilerVersion, signal) {
3423
+ const installationPath = Path.join(this.#storePath, compilerVersion);
3424
+ const readyFilePath = Path.join(installationPath, this.#readyFileName);
3425
+ if (await Lock.isLocked(installationPath, {
3426
+ onDiagnostic: (text) => {
3427
+ this.#onDiagnostic(Diagnostic.error([`Failed to install 'typescript@${compilerVersion}'.`, text]));
3428
+ },
3429
+ signal,
3430
+ timeout: this.#timeout,
3431
+ })) {
3432
+ return;
3433
+ }
3434
+ if (!existsSync(readyFilePath)) {
3435
+ EventEmitter.dispatch(["store:info", { compilerVersion, installationPath }]);
3436
+ try {
3437
+ await fs.mkdir(installationPath, { recursive: true });
3438
+ const lock = new Lock(installationPath);
3439
+ const packageJson = {
3440
+ name: "tstyche-typescript",
3441
+ version: compilerVersion,
3442
+ description: "Do not change. This package was generated by TSTyche",
3443
+ private: true,
3444
+ license: "MIT",
3445
+ dependencies: {
3446
+ typescript: compilerVersion,
3447
+ },
3448
+ };
3449
+ await fs.writeFile(Path.join(installationPath, "package.json"), JSON.stringify(packageJson, null, 2));
3450
+ await this.#install(installationPath, signal);
3451
+ await fs.writeFile(readyFilePath, "");
3452
+ lock.release();
3453
+ }
3454
+ catch (error) {
3455
+ this.#onDiagnostic(Diagnostic.fromError(`Failed to install 'typescript@${compilerVersion}'.`, error));
3456
+ }
3457
+ }
3458
+ if (Version.satisfies(compilerVersion, "5.3")) {
3459
+ return Path.join(installationPath, "node_modules", "typescript", "lib", "typescript.js");
3460
+ }
3461
+ return Path.join(installationPath, "node_modules", "typescript", "lib", "tsserverlibrary.js");
3462
+ }
3463
+ async #install(cwd, signal) {
3464
+ const args = ["install", "--ignore-scripts", "--no-bin-links", "--no-package-lock"];
3465
+ return new Promise((resolve, reject) => {
3466
+ const spawnedNpm = spawn("npm", args, {
3467
+ cwd,
3468
+ shell: true,
3469
+ signal,
3470
+ stdio: "ignore",
3471
+ timeout: this.#timeout,
3472
+ });
3473
+ spawnedNpm.on("error", (error) => {
3474
+ reject(error);
3475
+ });
3476
+ spawnedNpm.on("close", (code, signal) => {
3477
+ if (code === 0) {
3478
+ resolve();
3479
+ }
3480
+ if (signal != null) {
3481
+ reject(new Error(`Setup timeout of ${this.#timeout / 1000}s was exceeded.`));
3482
+ }
3483
+ reject(new Error(`Process exited with code ${String(code)}.`));
3484
+ });
3485
+ });
3259
3486
  }
3260
3487
  }
3261
3488
 
3262
3489
  class StoreService {
3263
- #cachePath;
3264
- #compilerModuleWorker;
3490
+ #compilerInstanceCache = new Map();
3265
3491
  #manifest;
3266
3492
  #manifestWorker;
3267
3493
  #nodeRequire = createRequire(import.meta.url);
3494
+ #packageInstaller;
3495
+ #storePath;
3268
3496
  constructor() {
3269
- this.#cachePath = Environment.storePath;
3270
- this.#compilerModuleWorker = new CompilerModuleWorker(this.#cachePath, this.#onDiagnostic);
3271
- this.#manifestWorker = new ManifestWorker(this.#cachePath, async () => this.prune());
3497
+ this.#storePath = Environment.storePath;
3498
+ this.#packageInstaller = new PackageInstaller(this.#storePath, this.#onDiagnostic);
3499
+ this.#manifestWorker = new ManifestWorker(this.#storePath, this.#onDiagnostic, this.prune);
3272
3500
  }
3273
- get supportedTags() {
3501
+ async getSupportedTags(signal) {
3502
+ await this.open(signal);
3274
3503
  if (!this.#manifest) {
3275
- this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
3276
3504
  return [];
3277
3505
  }
3278
3506
  return [...Object.keys(this.#manifest.resolutions), ...this.#manifest.versions, "current"].sort();
3279
3507
  }
3280
3508
  async install(tag, signal) {
3281
- if (!this.#manifest) {
3282
- this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
3509
+ if (tag === "current") {
3283
3510
  return;
3284
3511
  }
3285
- const version = this.resolveTag(tag);
3512
+ const version = await this.resolveTag(tag, signal);
3286
3513
  if (version == null) {
3287
3514
  this.#onDiagnostic(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
3288
3515
  return;
3289
3516
  }
3290
- return this.#compilerModuleWorker.ensure(version, signal);
3517
+ return this.#packageInstaller.ensure(version, signal);
3291
3518
  }
3292
3519
  async load(tag, signal) {
3520
+ let compilerInstance = this.#compilerInstanceCache.get(tag);
3521
+ if (compilerInstance != null) {
3522
+ return compilerInstance;
3523
+ }
3293
3524
  let modulePath;
3294
- if (tag === "local") {
3525
+ if (tag === "current") {
3295
3526
  try {
3296
- modulePath = this.#nodeRequire.resolve("typescript/lib/tsserverlibrary.js");
3527
+ modulePath = this.#nodeRequire.resolve("typescript");
3297
3528
  }
3298
- catch {
3299
- tag = "latest";
3529
+ catch (error) {
3530
+ this.#onDiagnostic(Diagnostic.fromError("Failed to resolve locally installed 'typescript' package. It might be not installed.", error));
3300
3531
  }
3301
3532
  }
3302
3533
  if (modulePath == null) {
3303
- modulePath = await this.install(tag, signal);
3534
+ if (tag === "current") {
3535
+ return;
3536
+ }
3537
+ const version = await this.resolveTag(tag, signal);
3538
+ if (version == null) {
3539
+ this.#onDiagnostic(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
3540
+ return;
3541
+ }
3542
+ compilerInstance = this.#compilerInstanceCache.get(version);
3543
+ if (compilerInstance != null) {
3544
+ return compilerInstance;
3545
+ }
3546
+ modulePath = await this.#packageInstaller.ensure(version, signal);
3304
3547
  }
3305
3548
  if (modulePath != null) {
3306
- return this.#nodeRequire(modulePath);
3549
+ compilerInstance = await this.#loadModule(modulePath);
3550
+ this.#compilerInstanceCache.set(tag, compilerInstance);
3551
+ this.#compilerInstanceCache.set(compilerInstance.version, compilerInstance);
3552
+ return compilerInstance;
3307
3553
  }
3308
3554
  return;
3309
3555
  }
3556
+ async #loadModule(modulePath) {
3557
+ const exports = {};
3558
+ const require = createRequire(modulePath);
3559
+ const module = { exports };
3560
+ let sourceText = await fs.readFile(modulePath, { encoding: "utf8" });
3561
+ sourceText = sourceText.replace("isTypeAssignableTo,", "isTypeAssignableTo, isTypeIdenticalTo, isTypeSubtypeOf,");
3562
+ const compiledWrapper = vm.compileFunction(sourceText, ["exports", "require", "module", "__filename", "__dirname"], { filename: modulePath });
3563
+ compiledWrapper(exports, require, module, modulePath, Path.dirname(modulePath));
3564
+ return module.exports;
3565
+ }
3310
3566
  #onDiagnostic = (diagnostic) => {
3311
3567
  EventEmitter.dispatch(["store:error", { diagnostics: [diagnostic] }]);
3312
3568
  };
@@ -3316,22 +3572,17 @@ class StoreService {
3316
3572
  }
3317
3573
  this.#manifest = await this.#manifestWorker.open(signal);
3318
3574
  }
3319
- async prune() {
3320
- await fs.rm(this.#cachePath, { force: true, recursive: true });
3321
- }
3322
- resolveTag(tag) {
3575
+ prune = async () => {
3576
+ await fs.rm(this.#storePath, { force: true, recursive: true });
3577
+ };
3578
+ async resolveTag(tag, signal) {
3579
+ if (tag === "current") {
3580
+ return tag;
3581
+ }
3582
+ await this.open(signal);
3323
3583
  if (!this.#manifest) {
3324
- this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
3325
3584
  return;
3326
3585
  }
3327
- if (tag === "current") {
3328
- try {
3329
- tag = this.#nodeRequire("typescript").version;
3330
- }
3331
- catch (error) {
3332
- this.#onDiagnostic(Diagnostic.fromError("Failed to resolve tag 'current'. The 'typescript' package might be not installed.", error));
3333
- }
3334
- }
3335
3586
  if (this.#manifest.versions.includes(tag)) {
3336
3587
  return tag;
3337
3588
  }
@@ -3346,15 +3597,14 @@ class StoreService {
3346
3597
  return version;
3347
3598
  }
3348
3599
  async update(signal) {
3349
- if (!this.#manifest) {
3350
- this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
3351
- return;
3352
- }
3353
- await this.#manifestWorker.update(this.#manifest, signal);
3600
+ await this.#manifestWorker.open(signal, { refresh: true });
3354
3601
  }
3355
- validateTag(tag) {
3602
+ async validateTag(tag, signal) {
3603
+ if (tag === "current") {
3604
+ return true;
3605
+ }
3606
+ await this.open(signal);
3356
3607
  if (!this.#manifest) {
3357
- this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
3358
3608
  return false;
3359
3609
  }
3360
3610
  if (this.#manifest.versions.includes(tag) || tag in this.#manifest.resolutions || tag === "current") {
@@ -3371,154 +3621,6 @@ class StoreService {
3371
3621
  }
3372
3622
  }
3373
3623
 
3374
- class JsonText {
3375
- props;
3376
- constructor(props) {
3377
- this.props = props;
3378
- }
3379
- render() {
3380
- return Scribbler.createElement(Line, null, JSON.stringify(this.#sortObject(this.props.input), null, 2));
3381
- }
3382
- #sortObject(target) {
3383
- if (Array.isArray(target)) {
3384
- return target;
3385
- }
3386
- return Object.keys(target)
3387
- .sort()
3388
- .reduce((result, key) => {
3389
- result[key] = target[key];
3390
- return result;
3391
- }, {});
3392
- }
3393
- }
3394
- function formattedText(input) {
3395
- if (typeof input === "string") {
3396
- return Scribbler.createElement(Line, null, input);
3397
- }
3398
- return Scribbler.createElement(JsonText, { input: input });
3399
- }
3400
-
3401
- const usageExamples = [
3402
- ["tstyche", "Run all tests."],
3403
- ["tstyche path/to/first.test.ts second", "Only run the test files with matching path."],
3404
- ["tstyche --target 4.7,4.8,latest", "Test on all specified versions of TypeScript."],
3405
- ];
3406
- class HintText {
3407
- props;
3408
- constructor(props) {
3409
- this.props = props;
3410
- }
3411
- render() {
3412
- return (Scribbler.createElement(Text, { indent: 1, color: "90" }, this.props.children));
3413
- }
3414
- }
3415
- class HelpHeaderText {
3416
- props;
3417
- constructor(props) {
3418
- this.props = props;
3419
- }
3420
- render() {
3421
- const hint = (Scribbler.createElement(HintText, null,
3422
- Scribbler.createElement(Text, null, this.props.tstycheVersion)));
3423
- return (Scribbler.createElement(Line, null,
3424
- Scribbler.createElement(Text, null, "The TSTyche Type Test Runner"),
3425
- hint));
3426
- }
3427
- }
3428
- class CommandText {
3429
- props;
3430
- constructor(props) {
3431
- this.props = props;
3432
- }
3433
- render() {
3434
- let hint = undefined;
3435
- if (this.props.hint != null) {
3436
- hint = Scribbler.createElement(HintText, null, this.props.hint);
3437
- }
3438
- return (Scribbler.createElement(Line, { indent: 1 },
3439
- Scribbler.createElement(Text, { color: "34" }, this.props.text),
3440
- hint));
3441
- }
3442
- }
3443
- class OptionDescriptionText {
3444
- props;
3445
- constructor(props) {
3446
- this.props = props;
3447
- }
3448
- render() {
3449
- return Scribbler.createElement(Line, { indent: 1 }, this.props.text);
3450
- }
3451
- }
3452
- class CliUsageText {
3453
- render() {
3454
- const usageText = usageExamples.map(([commandText, descriptionText]) => (Scribbler.createElement(Text, null,
3455
- Scribbler.createElement(CommandText, { text: commandText }),
3456
- Scribbler.createElement(OptionDescriptionText, { text: descriptionText }),
3457
- Scribbler.createElement(Line, null))));
3458
- return Scribbler.createElement(Text, null, usageText);
3459
- }
3460
- }
3461
- class OptionNameText {
3462
- props;
3463
- constructor(props) {
3464
- this.props = props;
3465
- }
3466
- render() {
3467
- return Scribbler.createElement(Text, null,
3468
- "--",
3469
- this.props.text);
3470
- }
3471
- }
3472
- class OptionHintText {
3473
- props;
3474
- constructor(props) {
3475
- this.props = props;
3476
- }
3477
- render() {
3478
- if (this.props.definition.brand === "list") {
3479
- return (Scribbler.createElement(Text, null,
3480
- this.props.definition.brand,
3481
- " of ",
3482
- this.props.definition.items.brand,
3483
- "s"));
3484
- }
3485
- return Scribbler.createElement(Text, null, this.props.definition.brand);
3486
- }
3487
- }
3488
- class CliOptionsText {
3489
- props;
3490
- constructor(props) {
3491
- this.props = props;
3492
- }
3493
- render() {
3494
- const definitions = Array.from(this.props.optionDefinitions.values());
3495
- const optionsText = definitions.map((definition) => (Scribbler.createElement(Text, null,
3496
- Scribbler.createElement(CommandText, { text: Scribbler.createElement(OptionNameText, { text: definition.name }), hint: Scribbler.createElement(OptionHintText, { definition: definition }) }),
3497
- Scribbler.createElement(OptionDescriptionText, { text: definition.description }),
3498
- Scribbler.createElement(Line, null))));
3499
- return (Scribbler.createElement(Text, null,
3500
- Scribbler.createElement(Line, null, "CLI Options"),
3501
- Scribbler.createElement(Line, null),
3502
- optionsText));
3503
- }
3504
- }
3505
- class HelpFooterText {
3506
- render() {
3507
- return Scribbler.createElement(Line, null, "To learn more, visit https://tstyche.org");
3508
- }
3509
- }
3510
- function helpText(optionDefinitions, tstycheVersion) {
3511
- return (Scribbler.createElement(Text, null,
3512
- Scribbler.createElement(HelpHeaderText, { tstycheVersion: tstycheVersion }),
3513
- Scribbler.createElement(Line, null),
3514
- Scribbler.createElement(CliUsageText, null),
3515
- Scribbler.createElement(Line, null),
3516
- Scribbler.createElement(CliOptionsText, { optionDefinitions: optionDefinitions }),
3517
- Scribbler.createElement(Line, null),
3518
- Scribbler.createElement(HelpFooterText, null),
3519
- Scribbler.createElement(Line, null)));
3520
- }
3521
-
3522
3624
  class Cli {
3523
3625
  #abortController = new AbortController();
3524
3626
  #logger;
@@ -3551,36 +3653,35 @@ class Cli {
3551
3653
  break;
3552
3654
  }
3553
3655
  };
3554
- async run(commandLineArgs) {
3656
+ async run(commandLineArguments) {
3555
3657
  EventEmitter.addHandler(this.#onStartupEvent);
3556
- await this.#storeService.open(this.#abortController.signal);
3557
- if (this.#process.exitCode === 1) {
3658
+ if (commandLineArguments.includes("--help")) {
3659
+ const commandLineOptionDefinitions = OptionDefinitionsMap.for(2);
3660
+ this.#logger.writeMessage(helpText(commandLineOptionDefinitions, TSTyche.version));
3558
3661
  return;
3559
3662
  }
3560
- const compiler = await this.#storeService.load("local", this.#abortController.signal);
3561
- if (!compiler) {
3663
+ if (commandLineArguments.includes("--prune")) {
3664
+ await this.#storeService.prune();
3562
3665
  return;
3563
3666
  }
3564
- const configService = new ConfigService(compiler, this.#storeService);
3565
- configService.parseCommandLine(commandLineArgs);
3566
- if (this.#process.exitCode === 1) {
3667
+ if (commandLineArguments.includes("--version")) {
3668
+ this.#logger.writeMessage(formattedText(TSTyche.version));
3567
3669
  return;
3568
3670
  }
3569
- if (configService.commandLineOptions.prune === true) {
3570
- await this.#storeService.prune();
3671
+ if (commandLineArguments.includes("--update")) {
3672
+ await this.#storeService.update(this.#abortController.signal);
3571
3673
  return;
3572
3674
  }
3573
- if (configService.commandLineOptions.help === true) {
3574
- const commandLineOptionDefinitions = OptionDefinitionsMap.for(2);
3575
- this.#logger.writeMessage(helpText(commandLineOptionDefinitions, TSTyche.version));
3675
+ if (this.#process.exitCode === 1) {
3576
3676
  return;
3577
3677
  }
3578
- if (configService.commandLineOptions.update === true) {
3579
- await this.#storeService.update();
3678
+ const compiler = await this.#storeService.load(Environment.isTypeScriptInstalled ? "current" : "latest", this.#abortController.signal);
3679
+ if (!compiler) {
3580
3680
  return;
3581
3681
  }
3582
- if (configService.commandLineOptions.version === true) {
3583
- this.#logger.writeMessage(formattedText(TSTyche.version));
3682
+ const configService = new ConfigService(compiler, this.#storeService);
3683
+ await configService.parseCommandLine(commandLineArguments);
3684
+ if (this.#process.exitCode === 1) {
3584
3685
  return;
3585
3686
  }
3586
3687
  await configService.readConfigFile();
@@ -3617,4 +3718,4 @@ class Cli {
3617
3718
  }
3618
3719
  }
3619
3720
 
3620
- export { Assertion, AssertionSource, Checker, Cli, CollectService, Color, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, Environment, EventEmitter, ExpectResult, FileResult, Line, Logger, OptionBrand, OptionDefinitionsMap, OptionGroup, ProjectResult, ProjectService, Reporter, Result, ResultCount, ResultManager, ResultStatus, ResultTiming, Scribbler, StoreService, SummaryReporter, TSTyche, TargetResult, TaskRunner, TestMember, TestMemberBrand, TestMemberFlags, TestResult, TestTree, Text, ThoroughReporter, addsPackageStepText, describeNameText, diagnosticText, fileStatusText, fileViewText, summaryText, testNameText, usesCompilerStepText };
3721
+ export { Assertion, Cli, CollectService, Color, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, Environment, EventEmitter, Expect, ExpectResult, FileResult, Line, Logger, OptionBrand, OptionDefinitionsMap, OptionGroup, Path, ProjectResult, ProjectService, Reporter, Result, ResultCount, ResultManager, ResultStatus, ResultTiming, Scribbler, StoreService, SummaryReporter, TSTyche, TargetResult, TaskRunner, TestMember, TestMemberBrand, TestMemberFlags, TestResult, TestTree, Text, ThoroughReporter, addsPackageStepText, describeNameText, diagnosticText, fileStatusText, fileViewText, formattedText, helpText, summaryText, testNameText, usesCompilerStepText };