tstyche 2.0.0 → 2.1.1

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
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
  import process from 'node:process';
4
4
  import { createRequire } from 'node:module';
5
5
  import os from 'node:os';
6
- import { existsSync, writeFileSync, rmSync } from 'node:fs';
6
+ import { watch, existsSync, writeFileSync, rmSync } from 'node:fs';
7
7
  import fs from 'node:fs/promises';
8
8
  import vm from 'node:vm';
9
9
  import { spawn } from 'node:child_process';
@@ -505,7 +505,7 @@ class Environment {
505
505
  }
506
506
  static #resolveTimeout() {
507
507
  if (process.env["TSTYCHE_TIMEOUT"] != null) {
508
- return Number(process.env["TSTYCHE_TIMEOUT"]);
508
+ return Number.parseFloat(process.env["TSTYCHE_TIMEOUT"]);
509
509
  }
510
510
  return 30;
511
511
  }
@@ -589,7 +589,7 @@ class Scribbler {
589
589
  #visitChildren(children) {
590
590
  const text = [];
591
591
  for (const child of children) {
592
- if (typeof child === "string") {
592
+ if (typeof child === "string" || typeof child === "number") {
593
593
  text.push(child);
594
594
  continue;
595
595
  }
@@ -613,41 +613,75 @@ function describeNameText(name, indent = 0) {
613
613
  return jsx(Line, { indent: indent + 1, children: name });
614
614
  }
615
615
 
616
- function CodeSpanText(diagnosticOrigin) {
616
+ function BreadcrumbsText({ ancestor }) {
617
+ const text = [];
618
+ while ("name" in ancestor) {
619
+ text.push(ancestor.name);
620
+ ancestor = ancestor.parent;
621
+ }
622
+ text.push("");
623
+ return jsx(Text, { color: "90", children: text.reverse().join(" ❭ ") });
624
+ }
625
+ function CodeLineText({ gutterWidth, lineNumber, lineNumberColor = "90", lineText }) {
626
+ return (jsx(Line, { children: [jsx(Text, { color: lineNumberColor, children: lineNumber.toString().padStart(gutterWidth) }), jsx(Text, { color: "90", children: " | " }), lineText] }));
627
+ }
628
+ function SquiggleLineText({ gutterWidth, indentWidth = 0, squiggleColor, squiggleWidth }) {
629
+ return (jsx(Line, { children: [" ".repeat(gutterWidth), jsx(Text, { color: "90", children: " | " }), " ".repeat(indentWidth), jsx(Text, { color: squiggleColor, children: "~".repeat(squiggleWidth === 0 ? 1 : squiggleWidth) })] }));
630
+ }
631
+ function CodeSpanText({ diagnosticCategory, diagnosticOrigin }) {
617
632
  const lastLineInFile = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.sourceFile.text.length).line;
618
- const { character: markedCharacter, line: markedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.start);
619
- const firstLine = Math.max(markedLine - 2, 0);
633
+ const { character: firstMarkedLineCharacter, line: firstMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.start);
634
+ const { character: lastMarkedLineCharacter, line: lastMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.end);
635
+ const firstLine = Math.max(firstMarkedLine - 2, 0);
620
636
  const lastLine = Math.min(firstLine + 5, lastLineInFile);
621
- const lineNumberMaxWidth = String(lastLine + 1).length;
637
+ const gutterWidth = (lastLine + 1).toString().length + 2;
638
+ let highlightColor;
639
+ switch (diagnosticCategory) {
640
+ case "error": {
641
+ highlightColor = "31";
642
+ break;
643
+ }
644
+ case "warning": {
645
+ highlightColor = "33";
646
+ break;
647
+ }
648
+ }
622
649
  const codeSpan = [];
623
650
  for (let index = firstLine; index <= lastLine; index++) {
624
651
  const lineStart = diagnosticOrigin.sourceFile.getPositionOfLineAndCharacter(index, 0);
625
652
  const lineEnd = index === lastLineInFile
626
653
  ? diagnosticOrigin.sourceFile.text.length
627
654
  : diagnosticOrigin.sourceFile.getPositionOfLineAndCharacter(index + 1, 0);
628
- const lineNumberText = String(index + 1);
629
655
  const lineText = diagnosticOrigin.sourceFile.text.slice(lineStart, lineEnd).trimEnd().replace(/\t/g, " ");
630
- if (index === markedLine) {
631
- codeSpan.push(jsx(Line, { children: [jsx(Text, { color: "31", children: ">" }), jsx(Text, { children: " " }), lineNumberText.padStart(lineNumberMaxWidth), jsx(Text, { children: " " }), jsx(Text, { color: "90", children: "|" }), " ", lineText] }), jsx(Line, { children: [" ".repeat(lineNumberMaxWidth + 3), jsx(Text, { color: "90", children: "|" }), " ".repeat(markedCharacter + 1), jsx(Text, { color: "31", children: "^" })] }));
656
+ if (index >= firstMarkedLine && index <= lastMarkedLine) {
657
+ codeSpan.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumber: index + 1, lineNumberColor: highlightColor, lineText: lineText }));
658
+ if (index === firstMarkedLine) {
659
+ const squiggleLength = index === lastMarkedLine
660
+ ? lastMarkedLineCharacter - firstMarkedLineCharacter
661
+ : lineText.length - firstMarkedLineCharacter;
662
+ codeSpan.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, indentWidth: firstMarkedLineCharacter, squiggleColor: highlightColor, squiggleWidth: squiggleLength }));
663
+ }
664
+ else if (index === lastMarkedLine) {
665
+ codeSpan.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleColor: highlightColor, squiggleWidth: lastMarkedLineCharacter }));
666
+ }
667
+ else {
668
+ codeSpan.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleColor: highlightColor, squiggleWidth: lineText.length }));
669
+ }
632
670
  }
633
671
  else {
634
- codeSpan.push(jsx(Line, { children: [" ".repeat(2), jsx(Text, { color: "90", children: [lineNumberText.padStart(lineNumberMaxWidth), " | ", lineText || ""] })] }));
672
+ codeSpan.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumber: index + 1, lineText: lineText }));
635
673
  }
636
674
  }
637
- const breadcrumbs = diagnosticOrigin.breadcrumbs?.flatMap((ancestor) => [
638
- jsx(Text, { color: "90", children: " ❭ " }),
639
- jsx(Text, { children: ancestor }),
640
- ]);
641
- const location = (jsx(Line, { children: [" ".repeat(lineNumberMaxWidth + 5), jsx(Text, { color: "90", children: "at" }), jsx(Text, { children: " " }), jsx(Text, { color: "36", children: Path.relative("", diagnosticOrigin.sourceFile.fileName) }), jsx(Text, { color: "90", children: [":", String(markedLine + 1), ":", String(markedCharacter + 1)] }), breadcrumbs] }));
675
+ const location = (jsx(Line, { children: [" ".repeat(gutterWidth + 2), jsx(Text, { color: "90", children: " at " }), jsx(Text, { color: "36", children: Path.relative("", diagnosticOrigin.sourceFile.fileName) }), jsx(Text, { color: "90", children: `:${firstMarkedLine + 1}:${firstMarkedLineCharacter + 1}` }), diagnosticOrigin.assertion && jsx(BreadcrumbsText, { ancestor: diagnosticOrigin.assertion.parent })] }));
642
676
  return (jsx(Text, { children: [codeSpan, jsx(Line, {}), location] }));
643
677
  }
644
678
 
645
679
  function DiagnosticText({ diagnostic }) {
646
- const code = typeof diagnostic.code === "string" ? jsx(Text, { color: "90", children: [" ", diagnostic.code] }) : undefined;
680
+ const code = diagnostic.code ? jsx(Text, { color: "90", children: [" ", diagnostic.code] }) : undefined;
647
681
  const text = Array.isArray(diagnostic.text) ? diagnostic.text : [diagnostic.text];
648
682
  const message = text.map((text, index) => (jsx(Text, { children: [index === 1 ? jsx(Line, {}) : undefined, jsx(Line, { children: [text, code] })] })));
649
683
  const related = diagnostic.related?.map((relatedDiagnostic) => jsx(DiagnosticText, { diagnostic: relatedDiagnostic }));
650
- const codeSpan = diagnostic.origin ? (jsx(Text, { children: [jsx(Line, {}), jsx(CodeSpanText, { ...diagnostic.origin })] })) : undefined;
684
+ const codeSpan = diagnostic.origin ? (jsx(Text, { children: [jsx(Line, {}), jsx(CodeSpanText, { diagnosticCategory: diagnostic.category, diagnosticOrigin: diagnostic.origin })] })) : undefined;
651
685
  return (jsx(Text, { children: [message, codeSpan, jsx(Line, {}), jsx(Text, { indent: 2, children: related })] }));
652
686
  }
653
687
  function diagnosticText(diagnostic) {
@@ -724,11 +758,7 @@ function HelpHeaderText({ tstycheVersion }) {
724
758
  return (jsx(Line, { children: ["The TSTyche Type Test Runner", jsx(HintText, { children: tstycheVersion })] }));
725
759
  }
726
760
  function CommandText({ hint, text }) {
727
- let hintText;
728
- if (hint != null) {
729
- hintText = jsx(HintText, { children: hint });
730
- }
731
- return (jsx(Line, { indent: 1, children: [jsx(Text, { color: "34", children: text }), hintText] }));
761
+ return (jsx(Line, { indent: 1, children: [jsx(Text, { color: "34", children: text }), hint && jsx(HintText, { children: hint })] }));
732
762
  }
733
763
  function OptionDescriptionText({ text }) {
734
764
  return jsx(Line, { indent: 1, children: text });
@@ -743,11 +773,11 @@ function CommandLineUsageText() {
743
773
  return jsx(Text, { children: usageText });
744
774
  }
745
775
  function CommandLineOptionNameText({ text }) {
746
- return jsx(Text, { children: ["--", text] });
776
+ return jsx(Text, { children: `--${text}` });
747
777
  }
748
778
  function CommandLineOptionHintText({ definition }) {
749
779
  if (definition.brand === "list") {
750
- return (jsx(Text, { children: [definition.brand, " of ", definition.items.brand, "s"] }));
780
+ return jsx(Text, { children: `${definition.brand} of ${definition.items.brand}s` });
751
781
  }
752
782
  return jsx(Text, { children: definition.brand });
753
783
  }
@@ -790,21 +820,21 @@ class OutputService {
790
820
  eraseLastLine() {
791
821
  this.#stdout.write("\u001B[1A\u001B[0K");
792
822
  }
793
- #write(stream, body) {
794
- const elements = Array.isArray(body) ? body : [body];
823
+ #writeTo(stream, element) {
824
+ const elements = Array.isArray(element) ? element : [element];
795
825
  for (const element of elements) {
796
826
  stream.write(this.#scribbler.render(element));
797
827
  }
798
828
  this.#isClear = false;
799
829
  }
800
- writeError(body) {
801
- this.#write(this.#stderr, body);
830
+ writeError(element) {
831
+ this.#writeTo(this.#stderr, element);
802
832
  }
803
- writeMessage(body) {
804
- this.#write(this.#stdout, body);
833
+ writeMessage(element) {
834
+ this.#writeTo(this.#stdout, element);
805
835
  }
806
- writeWarning(body) {
807
- this.#write(this.#stderr, body);
836
+ writeWarning(element) {
837
+ this.#writeTo(this.#stderr, element);
808
838
  }
809
839
  }
810
840
 
@@ -812,18 +842,16 @@ function RowText({ label, text }) {
812
842
  return (jsx(Line, { children: [`${label}:`.padEnd(12), text] }));
813
843
  }
814
844
  function CountText({ failed, passed, skipped, todo, total }) {
815
- return (jsx(Text, { children: [failed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "31", children: [String(failed), " failed"] }), jsx(Text, { children: ", " })] })) : undefined, skipped > 0 ? (jsx(Text, { children: [jsx(Text, { color: "33", children: [String(skipped), " skipped"] }), jsx(Text, { children: ", " })] })) : undefined, todo > 0 ? (jsx(Text, { children: [jsx(Text, { color: "35", children: [String(todo), " todo"] }), jsx(Text, { children: ", " })] })) : undefined, passed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "32", children: [String(passed), " passed"] }), jsx(Text, { children: ", " })] })) : undefined, jsx(Text, { children: [String(total), jsx(Text, { children: " total" })] })] }));
845
+ return (jsx(Text, { children: [failed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "31", children: [failed, " failed"] }), jsx(Text, { children: ", " })] })) : undefined, skipped > 0 ? (jsx(Text, { children: [jsx(Text, { color: "33", children: [skipped, " skipped"] }), jsx(Text, { children: ", " })] })) : undefined, todo > 0 ? (jsx(Text, { children: [jsx(Text, { color: "35", children: [todo, " todo"] }), jsx(Text, { children: ", " })] })) : undefined, passed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "32", children: [passed, " passed"] }), jsx(Text, { children: ", " })] })) : undefined, jsx(Text, { children: [total, " total"] })] }));
816
846
  }
817
- function DurationText({ duration }) {
818
- const minutes = Math.floor(duration / 60);
819
- const seconds = duration % 60;
820
- return (jsx(Text, { children: [minutes > 0 ? `${String(minutes)}m ` : undefined, `${String(Math.round(seconds * 10) / 10)}s`] }));
847
+ function DurationText({ seconds }) {
848
+ return jsx(Text, { children: `${Math.round(seconds * 10) / 10}s` });
821
849
  }
822
850
  function MatchText({ text }) {
823
851
  if (typeof text === "string") {
824
852
  return jsx(Text, { children: ["'", text, "'"] });
825
853
  }
826
- if (text.length <= 1) {
854
+ if (text.length === 1) {
827
855
  return jsx(Text, { children: ["'", ...text, "'"] });
828
856
  }
829
857
  const lastItem = text.pop();
@@ -835,7 +863,7 @@ function RanFilesText({ onlyMatch, pathMatch, skipMatch }) {
835
863
  testNameMatchText.push(jsx(Text, { children: [jsx(Text, { color: "90", children: "matching " }), jsx(MatchText, { text: onlyMatch })] }));
836
864
  }
837
865
  if (skipMatch != null) {
838
- testNameMatchText.push(jsx(Text, { children: [onlyMatch == null ? undefined : jsx(Text, { color: "90", children: " and " }), jsx(Text, { color: "90", children: "not matching " }), jsx(MatchText, { text: skipMatch })] }));
866
+ testNameMatchText.push(jsx(Text, { children: [onlyMatch && jsx(Text, { color: "90", children: " and " }), jsx(Text, { color: "90", children: "not matching " }), jsx(MatchText, { text: skipMatch })] }));
839
867
  }
840
868
  let pathMatchText;
841
869
  if (pathMatch.length > 0) {
@@ -851,7 +879,7 @@ function summaryText({ duration, expectCount, fileCount, onlyMatch, pathMatch, s
851
879
  const fileCountText = (jsx(RowText, { label: "Test files", text: jsx(CountText, { failed: fileCount.failed, passed: fileCount.passed, skipped: fileCount.skipped, todo: fileCount.todo, total: fileCount.total }) }));
852
880
  const testCountText = (jsx(RowText, { label: "Tests", text: jsx(CountText, { failed: testCount.failed, passed: testCount.passed, skipped: testCount.skipped, todo: testCount.todo, total: testCount.total }) }));
853
881
  const assertionCountText = (jsx(RowText, { label: "Assertions", text: jsx(CountText, { failed: expectCount.failed, passed: expectCount.passed, skipped: expectCount.skipped, todo: expectCount.todo, total: expectCount.total }) }));
854
- return (jsx(Text, { children: [targetCountText, fileCountText, testCount.total > 0 ? testCountText : undefined, expectCount.total > 0 ? assertionCountText : undefined, jsx(RowText, { label: "Duration", text: jsx(DurationText, { duration: duration / 1000 }) }), jsx(Line, {}), jsx(RanFilesText, { onlyMatch: onlyMatch, pathMatch: pathMatch, skipMatch: skipMatch })] }));
882
+ return (jsx(Text, { children: [targetCountText, fileCountText, testCount.total > 0 ? testCountText : undefined, expectCount.total > 0 ? assertionCountText : undefined, jsx(RowText, { label: "Duration", text: jsx(DurationText, { seconds: duration / 1000 }) }), jsx(Line, {}), jsx(RanFilesText, { onlyMatch: onlyMatch, pathMatch: pathMatch, skipMatch: skipMatch })] }));
855
883
  }
856
884
 
857
885
  function StatusText({ status }) {
@@ -1155,7 +1183,6 @@ class WatchReporter extends Reporter {
1155
1183
 
1156
1184
  class CancellationToken {
1157
1185
  #isCancelled = false;
1158
- #handlers = new Set();
1159
1186
  #reason;
1160
1187
  get isCancellationRequested() {
1161
1188
  return this.#isCancelled;
@@ -1165,16 +1192,10 @@ class CancellationToken {
1165
1192
  }
1166
1193
  cancel(reason) {
1167
1194
  if (!this.#isCancelled) {
1168
- for (const handler of this.#handlers) {
1169
- handler(reason);
1170
- }
1171
1195
  this.#isCancelled = true;
1172
1196
  this.#reason = reason;
1173
1197
  }
1174
1198
  }
1175
- onCancellationRequested(handler) {
1176
- this.#handlers.add(handler);
1177
- }
1178
1199
  reset() {
1179
1200
  if (this.#isCancelled) {
1180
1201
  this.#isCancelled = false;
@@ -1188,10 +1209,10 @@ var CancellationReason;
1188
1209
  CancellationReason["ConfigChange"] = "configChange";
1189
1210
  CancellationReason["ConfigError"] = "configError";
1190
1211
  CancellationReason["FailFast"] = "failFast";
1212
+ CancellationReason["WatchClose"] = "watchClose";
1191
1213
  })(CancellationReason || (CancellationReason = {}));
1192
1214
 
1193
1215
  class Watcher {
1194
- #abortController = new AbortController();
1195
1216
  #onChanged;
1196
1217
  #onRemoved;
1197
1218
  #recursive;
@@ -1204,34 +1225,28 @@ class Watcher {
1204
1225
  this.#recursive = options?.recursive;
1205
1226
  }
1206
1227
  close() {
1207
- this.#abortController.abort();
1208
- }
1209
- async watch() {
1210
- this.#watcher = fs.watch(this.#targetPath, { recursive: this.#recursive, signal: this.#abortController.signal });
1211
- try {
1212
- for await (const event of this.#watcher) {
1213
- if (event.filename != null) {
1214
- const filePath = Path.resolve(this.#targetPath, event.filename);
1215
- if (existsSync(filePath)) {
1216
- await this.#onChanged(filePath);
1217
- }
1218
- else {
1219
- await this.#onRemoved(filePath);
1220
- }
1228
+ this.#watcher?.close();
1229
+ }
1230
+ watch() {
1231
+ this.#watcher = watch(this.#targetPath, { recursive: this.#recursive }, (_eventType, fileName) => {
1232
+ if (fileName != null) {
1233
+ const filePath = Path.resolve(this.#targetPath, fileName);
1234
+ if (existsSync(filePath)) {
1235
+ this.#onChanged(filePath);
1236
+ }
1237
+ else {
1238
+ this.#onRemoved(filePath);
1221
1239
  }
1222
1240
  }
1223
- }
1224
- catch (error) {
1225
- if (error instanceof Error && error.name === "AbortError") ;
1226
- }
1241
+ });
1227
1242
  }
1228
1243
  }
1229
1244
 
1230
1245
  class FileWatcher extends Watcher {
1231
1246
  constructor(targetPath, onChanged) {
1232
- const onChangedFile = async (filePath) => {
1247
+ const onChangedFile = (filePath) => {
1233
1248
  if (filePath === targetPath) {
1234
- await onChanged();
1249
+ onChanged();
1235
1250
  }
1236
1251
  };
1237
1252
  super(Path.dirname(targetPath), onChangedFile);
@@ -1239,29 +1254,33 @@ class FileWatcher extends Watcher {
1239
1254
  }
1240
1255
 
1241
1256
  class DiagnosticOrigin {
1242
- breadcrumbs;
1257
+ assertion;
1243
1258
  end;
1244
1259
  sourceFile;
1245
1260
  start;
1246
- constructor(start, end, sourceFile, breadcrumbs) {
1261
+ constructor(start, end, sourceFile, assertion) {
1247
1262
  this.start = start;
1248
1263
  this.end = end;
1249
1264
  this.sourceFile = sourceFile;
1250
- this.breadcrumbs = breadcrumbs;
1265
+ this.assertion = assertion;
1266
+ }
1267
+ static fromAssertion(assertion) {
1268
+ const node = assertion.matcherName;
1269
+ return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertion);
1251
1270
  }
1252
1271
  static fromJsonNode(node, sourceFile, skipTrivia) {
1253
1272
  return new DiagnosticOrigin(skipTrivia(node.pos, sourceFile), node.end, sourceFile);
1254
1273
  }
1255
- static fromNode(node, breadcrumbs) {
1256
- return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), breadcrumbs);
1274
+ static fromNode(node, assertion) {
1275
+ return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertion);
1257
1276
  }
1258
1277
  }
1259
1278
 
1260
1279
  class Diagnostic {
1261
1280
  category;
1262
1281
  code;
1263
- related;
1264
1282
  origin;
1283
+ related;
1265
1284
  text;
1266
1285
  constructor(text, category, origin) {
1267
1286
  this.text = text;
@@ -1272,9 +1291,6 @@ class Diagnostic {
1272
1291
  if (options.code != null) {
1273
1292
  this.code = options.code;
1274
1293
  }
1275
- if (options.origin != null) {
1276
- this.origin = options.origin;
1277
- }
1278
1294
  if (options.related != null) {
1279
1295
  this.related = options.related;
1280
1296
  }
@@ -1283,16 +1299,22 @@ class Diagnostic {
1283
1299
  static error(text, origin) {
1284
1300
  return new Diagnostic(text, "error", origin);
1285
1301
  }
1302
+ extendWith(text, origin) {
1303
+ return new Diagnostic([this.text, text].flat(), this.category, origin ?? this.origin);
1304
+ }
1286
1305
  static fromDiagnostics(diagnostics, compiler) {
1287
1306
  return diagnostics.map((diagnostic) => {
1288
- const category = "error";
1289
- const code = `ts(${String(diagnostic.code)})`;
1307
+ const code = `ts(${diagnostic.code})`;
1290
1308
  let origin;
1291
- const text = compiler.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
1292
- if (Diagnostic.isTsDiagnosticWithLocation(diagnostic)) {
1309
+ if (Diagnostic.#isTsDiagnosticWithLocation(diagnostic)) {
1293
1310
  origin = new DiagnosticOrigin(diagnostic.start, diagnostic.start + diagnostic.length, diagnostic.file);
1294
1311
  }
1295
- return new Diagnostic(text, category, origin).add({ code });
1312
+ let related;
1313
+ if (diagnostic.relatedInformation != null) {
1314
+ related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation, compiler);
1315
+ }
1316
+ const text = compiler.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
1317
+ return new Diagnostic(text, "error", origin).add({ code, related });
1296
1318
  });
1297
1319
  }
1298
1320
  static fromError(text, error) {
@@ -1306,7 +1328,7 @@ class Diagnostic {
1306
1328
  }
1307
1329
  return Diagnostic.error(messageText);
1308
1330
  }
1309
- static isTsDiagnosticWithLocation(diagnostic) {
1331
+ static #isTsDiagnosticWithLocation(diagnostic) {
1310
1332
  return diagnostic.file != null && diagnostic.start != null && diagnostic.length != null;
1311
1333
  }
1312
1334
  static warning(text, origin) {
@@ -1428,7 +1450,7 @@ class SelectService {
1428
1450
  isTestFile(filePath) {
1429
1451
  return this.#isFileIncluded(Path.relative(this.#resolvedConfig.rootPath, filePath));
1430
1452
  }
1431
- #onDiagnostic(diagnostic) {
1453
+ #onDiagnostics(diagnostic) {
1432
1454
  EventEmitter.dispatch(["select:error", { diagnostics: [diagnostic] }]);
1433
1455
  }
1434
1456
  async selectFiles() {
@@ -1436,18 +1458,21 @@ class SelectService {
1436
1458
  const testFilePaths = [];
1437
1459
  await this.#visitDirectory(currentPath, testFilePaths);
1438
1460
  if (testFilePaths.length === 0) {
1439
- this.#onDiagnostic(Diagnostic.error(SelectDiagnosticText.noTestFilesWereSelected(this.#resolvedConfig)));
1461
+ this.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereSelected(this.#resolvedConfig)));
1440
1462
  }
1441
1463
  return testFilePaths.sort();
1442
1464
  }
1443
1465
  async #visitDirectory(currentPath, testFilePaths) {
1444
1466
  const targetPath = Path.join(this.#resolvedConfig.rootPath, currentPath);
1445
- let entries = [];
1467
+ let entries;
1446
1468
  try {
1447
1469
  entries = await fs.readdir(targetPath, { withFileTypes: true });
1448
1470
  }
1449
1471
  catch {
1450
1472
  }
1473
+ if (!entries) {
1474
+ return;
1475
+ }
1451
1476
  for (const entry of entries) {
1452
1477
  let entryMeta;
1453
1478
  if (entry.isSymbolicLink()) {
@@ -1455,12 +1480,14 @@ class SelectService {
1455
1480
  entryMeta = await fs.stat([targetPath, entry.name].join("/"));
1456
1481
  }
1457
1482
  catch {
1458
- continue;
1459
1483
  }
1460
1484
  }
1461
1485
  else {
1462
1486
  entryMeta = entry;
1463
1487
  }
1488
+ if (!entryMeta) {
1489
+ continue;
1490
+ }
1464
1491
  const entryPath = [currentPath, entry.name].join("/");
1465
1492
  if (entryMeta.isDirectory() && this.#isDirectoryIncluded(entryPath)) {
1466
1493
  await this.#visitDirectory(entryPath, testFilePaths);
@@ -1473,16 +1500,30 @@ class SelectService {
1473
1500
  }
1474
1501
  }
1475
1502
 
1476
- class Timer {
1503
+ class Debounce {
1504
+ #delay;
1505
+ #onResolve;
1506
+ #resolve;
1477
1507
  #timeout;
1478
- clear() {
1508
+ constructor(delay, onResolve) {
1509
+ this.#delay = delay;
1510
+ this.#onResolve = onResolve;
1511
+ }
1512
+ clearTimeout() {
1479
1513
  clearTimeout(this.#timeout);
1480
1514
  }
1481
- set(callback, delay, arg) {
1482
- new Promise((resolve) => {
1483
- this.#timeout = setTimeout(resolve, delay, arg);
1484
- }).then(async (arg) => {
1485
- await callback(arg);
1515
+ refreshTimeout() {
1516
+ this.clearTimeout();
1517
+ this.#timeout = setTimeout(() => {
1518
+ this.#resolve?.(this.#onResolve());
1519
+ }, this.#delay);
1520
+ }
1521
+ resolveWith(value) {
1522
+ this.#resolve?.(value);
1523
+ }
1524
+ setup() {
1525
+ return new Promise((resolve) => {
1526
+ this.#resolve = resolve;
1486
1527
  });
1487
1528
  }
1488
1529
  }
@@ -1491,16 +1532,33 @@ class WatchService {
1491
1532
  #changedTestFiles = new Map();
1492
1533
  #inputService;
1493
1534
  #resolvedConfig;
1494
- #runCallback;
1495
1535
  #selectService;
1496
- #timer = new Timer();
1497
1536
  #watchers = [];
1498
1537
  #watchedTestFiles;
1499
- constructor(resolvedConfig, runCallback, selectService, testFiles) {
1538
+ constructor(resolvedConfig, selectService, testFiles) {
1500
1539
  this.#resolvedConfig = resolvedConfig;
1501
- this.#runCallback = runCallback;
1502
1540
  this.#selectService = selectService;
1503
1541
  this.#watchedTestFiles = new Map(testFiles.map((testFile) => [testFile.path, testFile]));
1542
+ }
1543
+ #onDiagnostics(diagnostic) {
1544
+ EventEmitter.dispatch(["watch:error", { diagnostics: [diagnostic] }]);
1545
+ }
1546
+ async *watch(cancellationToken) {
1547
+ const onResolve = () => {
1548
+ const testFiles = [...this.#changedTestFiles.values()];
1549
+ this.#changedTestFiles.clear();
1550
+ return testFiles;
1551
+ };
1552
+ const debounce = new Debounce(100, onResolve);
1553
+ const onClose = (reason) => {
1554
+ debounce.clearTimeout();
1555
+ this.#inputService?.close();
1556
+ for (const watcher of this.#watchers) {
1557
+ watcher.close();
1558
+ }
1559
+ cancellationToken.cancel(reason);
1560
+ debounce.resolveWith([]);
1561
+ };
1504
1562
  const onInput = (chunk) => {
1505
1563
  switch (chunk.toLowerCase()) {
1506
1564
  case "\u0003":
@@ -1508,47 +1566,23 @@ class WatchService {
1508
1566
  case "\u001B":
1509
1567
  case "q":
1510
1568
  case "x": {
1511
- this.close();
1569
+ onClose("watchClose");
1512
1570
  break;
1513
1571
  }
1514
1572
  case "\u000D":
1515
1573
  case "\u0020":
1516
1574
  case "a": {
1517
- this.#runAll();
1575
+ debounce.clearTimeout();
1576
+ if (this.#watchedTestFiles.size !== 0) {
1577
+ debounce.resolveWith([...this.#watchedTestFiles.values()]);
1578
+ }
1518
1579
  break;
1519
1580
  }
1520
1581
  }
1521
1582
  };
1522
1583
  this.#inputService = new InputService(onInput);
1523
- }
1524
- close() {
1525
- this.#inputService.close();
1526
- this.#timer.clear();
1527
- for (const watcher of this.#watchers) {
1528
- watcher.close();
1529
- }
1530
- }
1531
- #onDiagnostic(diagnostic) {
1532
- EventEmitter.dispatch(["watch:error", { diagnostics: [diagnostic] }]);
1533
- }
1534
- #runAll() {
1535
- if (this.#watchedTestFiles.size !== 0) {
1536
- this.#runCallback([...this.#watchedTestFiles.values()]);
1537
- }
1538
- }
1539
- #runChanged() {
1540
- this.#timer.clear();
1541
- if (this.#changedTestFiles.size !== 0) {
1542
- const runCallback = async (changedTestFiles) => {
1543
- const testFiles = [...changedTestFiles.values()];
1544
- this.#changedTestFiles.clear();
1545
- await this.#runCallback(testFiles);
1546
- };
1547
- this.#timer.set(runCallback, 100, this.#changedTestFiles);
1548
- }
1549
- }
1550
- watch(cancellationToken) {
1551
1584
  const onChangedFile = (filePath) => {
1585
+ debounce.refreshTimeout();
1552
1586
  let testFile = this.#watchedTestFiles.get(filePath);
1553
1587
  if (testFile != null) {
1554
1588
  this.#changedTestFiles.set(filePath, testFile);
@@ -1558,26 +1592,35 @@ class WatchService {
1558
1592
  this.#changedTestFiles.set(filePath, testFile);
1559
1593
  this.#watchedTestFiles.set(filePath, testFile);
1560
1594
  }
1561
- this.#runChanged();
1562
1595
  };
1563
1596
  const onRemovedFile = (filePath) => {
1564
1597
  this.#changedTestFiles.delete(filePath);
1565
1598
  this.#watchedTestFiles.delete(filePath);
1566
1599
  if (this.#watchedTestFiles.size === 0) {
1567
- this.#onDiagnostic(Diagnostic.error(SelectDiagnosticText.noTestFilesWereLeft(this.#resolvedConfig)));
1600
+ debounce.clearTimeout();
1601
+ this.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereLeft(this.#resolvedConfig)));
1568
1602
  }
1569
1603
  };
1570
1604
  this.#watchers.push(new Watcher(this.#resolvedConfig.rootPath, onChangedFile, onRemovedFile, { recursive: true }));
1571
1605
  const onChangedConfigFile = () => {
1572
- cancellationToken?.cancel("configChange");
1606
+ onClose("configChange");
1573
1607
  };
1574
1608
  this.#watchers.push(new FileWatcher(this.#resolvedConfig.configFilePath, onChangedConfigFile));
1575
- return Promise.all(this.#watchers.map((watcher) => watcher.watch()));
1609
+ for (const watcher of this.#watchers) {
1610
+ watcher.watch();
1611
+ }
1612
+ while (!cancellationToken.isCancellationRequested) {
1613
+ const testFiles = await debounce.setup();
1614
+ if (testFiles.length > 0) {
1615
+ yield testFiles;
1616
+ }
1617
+ }
1576
1618
  }
1577
1619
  }
1578
1620
 
1579
1621
  class TestMember {
1580
1622
  brand;
1623
+ #compiler;
1581
1624
  diagnostics = new Set();
1582
1625
  flags;
1583
1626
  members = [];
@@ -1586,6 +1629,7 @@ class TestMember {
1586
1629
  parent;
1587
1630
  constructor(compiler, brand, node, parent, flags) {
1588
1631
  this.brand = brand;
1632
+ this.#compiler = compiler;
1589
1633
  this.node = node;
1590
1634
  this.parent = parent;
1591
1635
  this.flags = flags;
@@ -1605,23 +1649,20 @@ class TestMember {
1605
1649
  }
1606
1650
  }
1607
1651
  }
1608
- get ancestorNames() {
1609
- const ancestorNames = [];
1610
- let ancestor = this.parent;
1611
- while ("name" in ancestor) {
1612
- ancestorNames.unshift(ancestor.name);
1613
- ancestor = ancestor.parent;
1614
- }
1615
- return ancestorNames;
1616
- }
1617
1652
  validate() {
1618
1653
  const diagnostics = [];
1619
1654
  const getText = (node) => `'${node.expression.getText()}()' cannot be nested within '${this.node.expression.getText()}()'.`;
1655
+ const getParentCallExpression = (node) => {
1656
+ while (!this.#compiler.isCallExpression(node.parent)) {
1657
+ node = node.parent;
1658
+ }
1659
+ return node.parent;
1660
+ };
1620
1661
  switch (this.brand) {
1621
1662
  case "describe": {
1622
1663
  for (const member of this.members) {
1623
1664
  if (member.brand === "expect") {
1624
- diagnostics.push(Diagnostic.error(getText(member.node), DiagnosticOrigin.fromNode(member.node)));
1665
+ diagnostics.push(Diagnostic.error(getText(member.node), DiagnosticOrigin.fromNode(getParentCallExpression(member.node))));
1625
1666
  }
1626
1667
  }
1627
1668
  break;
@@ -1650,43 +1691,21 @@ class Assertion extends TestMember {
1650
1691
  this.isNot = notNode != null;
1651
1692
  this.matcherNode = matcherNode;
1652
1693
  this.modifierNode = modifierNode;
1653
- const argStart = this.source[0]?.getStart();
1654
- const argEnd = this.source[0]?.getEnd();
1655
1694
  for (const diagnostic of parent.diagnostics) {
1656
- if (diagnostic.start != null &&
1657
- argStart != null &&
1658
- argEnd != null &&
1659
- diagnostic.start >= argStart &&
1660
- diagnostic.start <= argEnd) {
1695
+ if (diagnostic.start != null && diagnostic.start >= this.source.pos && diagnostic.start <= this.source.end) {
1661
1696
  this.diagnostics.add(diagnostic);
1662
1697
  parent.diagnostics.delete(diagnostic);
1663
1698
  }
1664
1699
  }
1665
1700
  }
1666
- get ancestorNames() {
1667
- const ancestorNames = [];
1668
- if ("ancestorNames" in this.parent) {
1669
- ancestorNames.push(...this.parent.ancestorNames);
1670
- }
1671
- if ("name" in this.parent) {
1672
- ancestorNames.push(this.parent.name);
1673
- }
1674
- return ancestorNames;
1675
- }
1676
1701
  get matcherName() {
1677
1702
  return this.matcherNode.expression.name;
1678
1703
  }
1679
1704
  get source() {
1680
- if (this.node.typeArguments != null) {
1681
- return this.node.typeArguments;
1682
- }
1683
- return this.node.arguments;
1705
+ return this.node.typeArguments ?? this.node.arguments;
1684
1706
  }
1685
1707
  get target() {
1686
- if (this.matcherNode.typeArguments != null) {
1687
- return this.matcherNode.typeArguments;
1688
- }
1689
- return this.matcherNode.arguments;
1708
+ return this.matcherNode.typeArguments ?? this.matcherNode.arguments;
1690
1709
  }
1691
1710
  }
1692
1711
 
@@ -1707,12 +1726,6 @@ class IdentifierLookup {
1707
1726
  namespace: undefined,
1708
1727
  };
1709
1728
  }
1710
- clone() {
1711
- return {
1712
- namedImports: { ...this.#identifiers.namedImports },
1713
- namespace: this.#identifiers.namespace,
1714
- };
1715
- }
1716
1729
  handleImportDeclaration(node) {
1717
1730
  if (this.#moduleSpecifiers.includes(node.moduleSpecifier.getText()) &&
1718
1731
  node.importClause?.isTypeOnly !== true &&
@@ -1774,7 +1787,7 @@ class IdentifierLookup {
1774
1787
  else {
1775
1788
  identifierName = Object.keys(this.#identifiers.namedImports).find((key) => this.#identifiers.namedImports[key] === expression.getText());
1776
1789
  }
1777
- if (identifierName == null) {
1790
+ if (!identifierName) {
1778
1791
  return;
1779
1792
  }
1780
1793
  switch (identifierName) {
@@ -1808,40 +1821,10 @@ class TestTree {
1808
1821
 
1809
1822
  class CollectService {
1810
1823
  #compiler;
1811
- #matcherIdentifiers = [
1812
- "toBe",
1813
- "toBeAny",
1814
- "toBeAssignable",
1815
- "toBeAssignableTo",
1816
- "toBeAssignableWith",
1817
- "toBeBigInt",
1818
- "toBeBoolean",
1819
- "toBeNever",
1820
- "toBeNull",
1821
- "toBeNumber",
1822
- "toBeString",
1823
- "toBeSymbol",
1824
- "toBeUndefined",
1825
- "toBeUniqueSymbol",
1826
- "toBeUnknown",
1827
- "toBeVoid",
1828
- "toEqual",
1829
- "toHaveProperty",
1830
- "toMatch",
1831
- "toRaiseError",
1832
- ];
1833
- #modifierIdentifiers = ["type"];
1834
- #notIdentifier = "not";
1835
1824
  constructor(compiler) {
1836
1825
  this.#compiler = compiler;
1837
1826
  }
1838
1827
  #collectTestMembers(node, identifiers, parent) {
1839
- if (this.#compiler.isBlock(node)) {
1840
- this.#compiler.forEachChild(node, (node) => {
1841
- this.#collectTestMembers(node, new IdentifierLookup(this.#compiler, identifiers.clone()), parent);
1842
- });
1843
- return;
1844
- }
1845
1828
  if (this.#compiler.isCallExpression(node)) {
1846
1829
  const meta = identifiers.resolveTestMemberMeta(node);
1847
1830
  if (meta != null && (meta.brand === "describe" || meta.brand === "test")) {
@@ -1853,13 +1836,13 @@ class CollectService {
1853
1836
  return;
1854
1837
  }
1855
1838
  if (meta != null && meta.brand === "expect") {
1856
- const modifierNode = this.#getMatchingChainNode(node, this.#modifierIdentifiers);
1839
+ const modifierNode = this.#getChainedNode(node, "type");
1857
1840
  if (!modifierNode) {
1858
1841
  return;
1859
1842
  }
1860
- const notNode = this.#getMatchingChainNode(modifierNode, [this.#notIdentifier]);
1861
- const matcherNode = this.#getMatchingChainNode(notNode ?? modifierNode, this.#matcherIdentifiers)?.parent;
1862
- if (matcherNode == null || !this.#isMatcherNode(matcherNode)) {
1843
+ const notNode = this.#getChainedNode(modifierNode, "not");
1844
+ const matcherNode = this.#getChainedNode(notNode ?? modifierNode)?.parent;
1845
+ if (!matcherNode || !this.#isMatcherNode(matcherNode)) {
1863
1846
  return;
1864
1847
  }
1865
1848
  const assertion = new Assertion(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, modifierNode, notNode);
@@ -1874,8 +1857,6 @@ class CollectService {
1874
1857
  identifiers.handleImportDeclaration(node);
1875
1858
  return;
1876
1859
  }
1877
- if (this.#compiler.isVariableDeclaration(node)) ;
1878
- if (this.#compiler.isBinaryExpression(node)) ;
1879
1860
  this.#compiler.forEachChild(node, (node) => {
1880
1861
  this.#collectTestMembers(node, identifiers, parent);
1881
1862
  });
@@ -1885,11 +1866,14 @@ class CollectService {
1885
1866
  this.#collectTestMembers(sourceFile, new IdentifierLookup(this.#compiler), testTree);
1886
1867
  return testTree;
1887
1868
  }
1888
- #getMatchingChainNode({ parent }, name) {
1889
- if (this.#compiler.isPropertyAccessExpression(parent) && name.includes(parent.name.getText())) {
1890
- return parent;
1869
+ #getChainedNode({ parent }, name) {
1870
+ if (!this.#compiler.isPropertyAccessExpression(parent)) {
1871
+ return;
1891
1872
  }
1892
- return;
1873
+ if (name != null && name !== parent.name.getText()) {
1874
+ return;
1875
+ }
1876
+ return parent;
1893
1877
  }
1894
1878
  #isMatcherNode(node) {
1895
1879
  return this.#compiler.isCallExpression(node) && this.#compiler.isPropertyAccessExpression(node.expression);
@@ -1911,220 +1895,599 @@ var TestMemberFlags;
1911
1895
  TestMemberFlags[TestMemberFlags["Todo"] = 8] = "Todo";
1912
1896
  })(TestMemberFlags || (TestMemberFlags = {}));
1913
1897
 
1898
+ class ExpectDiagnosticText {
1899
+ static argumentOrTypeArgumentMustBeProvided(argumentNameText, typeArgumentNameText) {
1900
+ return `An argument for '${argumentNameText}' or type argument for '${typeArgumentNameText}' must be provided.`;
1901
+ }
1902
+ static argumentMustBe(argumentNameText, expectedText) {
1903
+ return `An argument for '${argumentNameText}' must be ${expectedText}.`;
1904
+ }
1905
+ static argumentMustBeProvided(argumentNameText) {
1906
+ return `An argument for '${argumentNameText}' must be provided.`;
1907
+ }
1908
+ static componentAcceptsProps(isTypeNode) {
1909
+ return `${isTypeNode ? "Component type" : "Component"} accepts props of the given type.`;
1910
+ }
1911
+ static componentDoesNotAcceptProps(isTypeNode) {
1912
+ return `${isTypeNode ? "Component type" : "Component"} does not accept props of the given type.`;
1913
+ }
1914
+ static matcherIsDeprecated(matcherNameText) {
1915
+ return [
1916
+ `The '.${matcherNameText}()' matcher is deprecated and will be removed in TSTyche 3.`,
1917
+ "To learn more, visit https://tstyche.org/releases/tstyche-2",
1918
+ ];
1919
+ }
1920
+ static matcherIsNotSupported(matcherNameText) {
1921
+ return `The '.${matcherNameText}()' matcher is not supported.`;
1922
+ }
1923
+ static overloadGaveTheFollowingError(index, count, signatureText) {
1924
+ return `Overload ${index} of ${count}, '${signatureText}', gave the following error.`;
1925
+ }
1926
+ static raisedTypeError(count = 1) {
1927
+ return `The raised type error${count === 1 ? "" : "s"}:`;
1928
+ }
1929
+ static typeArgumentMustBe(argumentNameText, expectedText) {
1930
+ return `A type argument for '${argumentNameText}' must be ${expectedText}.`;
1931
+ }
1932
+ static typeDidNotRaiseError(isTypeNode) {
1933
+ return `${isTypeNode ? "Type" : "Expression type"} did not raise a type error.`;
1934
+ }
1935
+ static typeDidNotRaiseMatchingError(isTypeNode) {
1936
+ return `${isTypeNode ? "Type" : "Expression type"} did not raise a matching type error.`;
1937
+ }
1938
+ static typeDoesNotHaveProperty(typeText, propertyNameText) {
1939
+ return `Type '${typeText}' does not have property '${propertyNameText}'.`;
1940
+ }
1941
+ static typeDoesMatch(sourceTypeText, targetTypeText) {
1942
+ return `Type '${sourceTypeText}' does match type '${targetTypeText}'.`;
1943
+ }
1944
+ static typeDoesNotMatch(sourceTypeText, targetTypeText) {
1945
+ return `Type '${sourceTypeText}' does not match type '${targetTypeText}'.`;
1946
+ }
1947
+ static typeHasProperty(typeText, propertyNameText) {
1948
+ return `Type '${typeText}' has property '${propertyNameText}'.`;
1949
+ }
1950
+ static typeIs(typeText) {
1951
+ return `Type is '${typeText}'.`;
1952
+ }
1953
+ static typeIsAssignableTo(sourceTypeText, targetTypeText) {
1954
+ return `Type '${sourceTypeText}' is assignable to type '${targetTypeText}'.`;
1955
+ }
1956
+ static typeIsAssignableWith(sourceTypeText, targetTypeText) {
1957
+ return `Type '${sourceTypeText}' is assignable with type '${targetTypeText}'.`;
1958
+ }
1959
+ static typeIsIdenticalTo(sourceTypeText, targetTypeText) {
1960
+ return `Type '${sourceTypeText}' is identical to type '${targetTypeText}'.`;
1961
+ }
1962
+ static typeIsNotAssignableTo(sourceTypeText, targetTypeText) {
1963
+ return `Type '${sourceTypeText}' is not assignable to type '${targetTypeText}'.`;
1964
+ }
1965
+ static typeIsNotAssignableWith(sourceTypeText, targetTypeText) {
1966
+ return `Type '${sourceTypeText}' is not assignable with type '${targetTypeText}'.`;
1967
+ }
1968
+ static typeIsNotCompatibleWith(sourceTypeText, targetTypeText) {
1969
+ return `Type '${sourceTypeText}' is not compatible with type '${targetTypeText}'.`;
1970
+ }
1971
+ static typeIsNotIdenticalTo(sourceTypeText, targetTypeText) {
1972
+ return `Type '${sourceTypeText}' is not identical to type '${targetTypeText}'.`;
1973
+ }
1974
+ static typeRaisedError(isTypeNode, count, targetCount) {
1975
+ let countText = "a";
1976
+ if (count > 1 || targetCount > 1) {
1977
+ countText = count > targetCount ? `${count}` : `only ${count}`;
1978
+ }
1979
+ return `${isTypeNode ? "Type" : "Expression type"} raised ${countText} type error${count === 1 ? "" : "s"}.`;
1980
+ }
1981
+ static typeRaisedMatchingError(isTypeNode) {
1982
+ return `${isTypeNode ? "Type" : "Expression type"} raised a matching type error.`;
1983
+ }
1984
+ static typeRequiresProperty(typeText, propertyNameText) {
1985
+ return `Type '${typeText}' requires property '${propertyNameText}'.`;
1986
+ }
1987
+ static typesOfPropertyAreNotCompatible(propertyNameText) {
1988
+ return `Types of property '${propertyNameText}' are not compatible.`;
1989
+ }
1990
+ }
1991
+
1992
+ class MatchWorker {
1993
+ assertion;
1994
+ #compiler;
1995
+ #signatureCache = new Map();
1996
+ #typeCache = new Map();
1997
+ #typeChecker;
1998
+ constructor(compiler, typeChecker, assertion) {
1999
+ this.#compiler = compiler;
2000
+ this.#typeChecker = typeChecker;
2001
+ this.assertion = assertion;
2002
+ }
2003
+ checkIsAssignableTo(sourceNode, targetNode) {
2004
+ const relation = this.#typeChecker.relation.assignable;
2005
+ return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
2006
+ }
2007
+ checkIsAssignableWith(sourceNode, targetNode) {
2008
+ const relation = this.#typeChecker.relation.assignable;
2009
+ return this.#checkIsRelatedTo(targetNode, sourceNode, relation);
2010
+ }
2011
+ checkIsIdenticalTo(sourceNode, targetNode) {
2012
+ const relation = this.#typeChecker.relation.identity;
2013
+ return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
2014
+ }
2015
+ checkIsSubtype(sourceNode, targetNode) {
2016
+ const relation = this.#typeChecker.relation.subtype;
2017
+ return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
2018
+ }
2019
+ #checkIsRelatedTo(sourceNode, targetNode, relation) {
2020
+ const sourceType = this.getType(sourceNode);
2021
+ const targetType = this.getType(targetNode);
2022
+ return this.#typeChecker.isTypeRelatedTo(sourceType, targetType, relation);
2023
+ }
2024
+ extendsObjectType(type) {
2025
+ const nonPrimitiveType = { flags: this.#compiler.TypeFlags.NonPrimitive };
2026
+ return this.#typeChecker.isTypeAssignableTo(type, nonPrimitiveType);
2027
+ }
2028
+ getParameterType(signature, index) {
2029
+ const parameter = signature.getDeclaration().parameters[index];
2030
+ if (!parameter) {
2031
+ return;
2032
+ }
2033
+ return this.#getTypeOfNode(parameter);
2034
+ }
2035
+ getSignatures(node) {
2036
+ let signatures = this.#signatureCache.get(node);
2037
+ if (!signatures) {
2038
+ const type = this.getType(node);
2039
+ signatures = type.getCallSignatures();
2040
+ if (signatures.length === 0) {
2041
+ signatures = type.getConstructSignatures();
2042
+ }
2043
+ }
2044
+ return signatures;
2045
+ }
2046
+ getTypeText(node) {
2047
+ const type = this.getType(node);
2048
+ return this.#typeChecker.typeToString(type);
2049
+ }
2050
+ getType(node) {
2051
+ return this.#compiler.isExpression(node) ? this.#getTypeOfNode(node) : this.#getTypeOfTypeNode(node);
2052
+ }
2053
+ #getTypeOfNode(node) {
2054
+ let type = this.#typeCache.get(node);
2055
+ if (!type) {
2056
+ type = this.#typeChecker.getTypeAtLocation(node);
2057
+ }
2058
+ return type;
2059
+ }
2060
+ #getTypeOfTypeNode(node) {
2061
+ let type = this.#typeCache.get(node);
2062
+ if (!type) {
2063
+ type = this.#typeChecker.getTypeFromTypeNode(node);
2064
+ }
2065
+ return type;
2066
+ }
2067
+ isAnyOrNeverType(type) {
2068
+ return !!(type.flags & (this.#compiler.TypeFlags.Any | this.#compiler.TypeFlags.Never));
2069
+ }
2070
+ isStringOrNumberLiteralType(type) {
2071
+ return !!(type.flags & this.#compiler.TypeFlags.StringOrNumberLiteral);
2072
+ }
2073
+ isObjectType(type) {
2074
+ return !!(type.flags & this.#compiler.TypeFlags.Object);
2075
+ }
2076
+ isUnionType(type) {
2077
+ return !!(type.flags & this.#compiler.TypeFlags.Union);
2078
+ }
2079
+ isUniqueSymbolType(type) {
2080
+ return !!(type.flags & this.#compiler.TypeFlags.UniqueESSymbol);
2081
+ }
2082
+ resolveDiagnosticOrigin(symbol, enclosingNode) {
2083
+ if (symbol.valueDeclaration != null &&
2084
+ (this.#compiler.isPropertySignature(symbol.valueDeclaration) ||
2085
+ this.#compiler.isPropertyAssignment(symbol.valueDeclaration) ||
2086
+ this.#compiler.isShorthandPropertyAssignment(symbol.valueDeclaration)) &&
2087
+ symbol.valueDeclaration.getStart() >= enclosingNode.getStart() &&
2088
+ symbol.valueDeclaration.getEnd() <= enclosingNode.getEnd()) {
2089
+ return DiagnosticOrigin.fromNode(symbol.valueDeclaration.name, this.assertion);
2090
+ }
2091
+ return DiagnosticOrigin.fromNode(enclosingNode, this.assertion);
2092
+ }
2093
+ }
2094
+
1914
2095
  class PrimitiveTypeMatcher {
1915
2096
  #targetTypeFlag;
1916
- typeChecker;
1917
- constructor(typeChecker, targetTypeFlag) {
1918
- this.typeChecker = typeChecker;
2097
+ constructor(targetTypeFlag) {
1919
2098
  this.#targetTypeFlag = targetTypeFlag;
1920
2099
  }
1921
- #explain(sourceType) {
1922
- const sourceTypeText = this.typeChecker.typeToString(sourceType);
1923
- return [Diagnostic.error(`The source type is '${sourceTypeText}'.`)];
2100
+ #explain(matchWorker, sourceNode) {
2101
+ const sourceTypeText = matchWorker.getTypeText(sourceNode);
2102
+ const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
2103
+ return [Diagnostic.error(ExpectDiagnosticText.typeIs(sourceTypeText), origin)];
1924
2104
  }
1925
- match(sourceType) {
1926
- const isMatch = Boolean(sourceType.flags & this.#targetTypeFlag);
2105
+ match(matchWorker, sourceNode) {
2106
+ const sourceType = matchWorker.getType(sourceNode);
2107
+ const isMatch = !!(sourceType.flags & this.#targetTypeFlag);
1927
2108
  return {
1928
- explain: () => this.#explain(sourceType),
2109
+ explain: () => this.#explain(matchWorker, sourceNode),
1929
2110
  isMatch,
1930
2111
  };
1931
2112
  }
1932
2113
  }
1933
2114
 
1934
- class RelationMatcherBase {
1935
- relationExplanationVerb = "is";
1936
- typeChecker;
1937
- constructor(typeChecker) {
1938
- this.typeChecker = typeChecker;
1939
- }
1940
- explain(sourceType, targetType, isNot) {
1941
- const sourceTypeText = this.typeChecker.typeToString(sourceType);
1942
- const targetTypeText = this.typeChecker.typeToString(targetType);
1943
- return isNot
1944
- ? [
1945
- Diagnostic.error(`Type '${sourceTypeText}' ${this.relationExplanationVerb} ${this.relationExplanationText} type '${targetTypeText}'.`),
1946
- ]
1947
- : [
1948
- Diagnostic.error(`Type '${sourceTypeText}' ${this.relationExplanationVerb} not ${this.relationExplanationText} type '${targetTypeText}'.`),
1949
- ];
2115
+ class ToAcceptProps {
2116
+ #compiler;
2117
+ #typeChecker;
2118
+ constructor(compiler, typeChecker) {
2119
+ this.#compiler = compiler;
2120
+ this.#typeChecker = typeChecker;
1950
2121
  }
1951
- match(sourceType, targetType, isNot) {
1952
- const isMatch = this.typeChecker.isTypeRelatedTo(sourceType, targetType, this.relation);
2122
+ #explain(matchWorker, sourceNode, targetNode) {
2123
+ const signatures = matchWorker.getSignatures(sourceNode);
2124
+ return signatures.reduce((accumulator, signature, index) => {
2125
+ let diagnostic;
2126
+ const introText = matchWorker.assertion.isNot
2127
+ ? ExpectDiagnosticText.componentAcceptsProps(this.#compiler.isTypeNode(sourceNode))
2128
+ : ExpectDiagnosticText.componentDoesNotAcceptProps(this.#compiler.isTypeNode(sourceNode));
2129
+ const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
2130
+ if (signatures.length > 1) {
2131
+ const signatureText = this.#typeChecker.signatureToString(signature, sourceNode);
2132
+ const overloadText = ExpectDiagnosticText.overloadGaveTheFollowingError(index + 1, signatures.length, signatureText);
2133
+ diagnostic = Diagnostic.error([introText, overloadText], origin);
2134
+ }
2135
+ else {
2136
+ diagnostic = Diagnostic.error([introText], origin);
2137
+ }
2138
+ const { diagnostics, isMatch } = this.#explainProperties(matchWorker, signature, targetNode, diagnostic);
2139
+ if (matchWorker.assertion.isNot ? isMatch : !isMatch) {
2140
+ accumulator.push(...diagnostics);
2141
+ }
2142
+ return accumulator;
2143
+ }, []);
2144
+ }
2145
+ #isOptionalProperty(symbol) {
2146
+ return symbol.declarations?.every((declaration) => this.#compiler.isPropertySignature(declaration) && declaration.questionToken != null);
2147
+ }
2148
+ #checkProperties(matchWorker, sourceType, targetType) {
2149
+ const check = (sourceType, targetType) => {
2150
+ for (const targetProperty of targetType.getProperties()) {
2151
+ const targetPropertyName = targetProperty.getName();
2152
+ const sourceProperty = sourceType?.getProperty(targetPropertyName);
2153
+ if (!sourceProperty) {
2154
+ return false;
2155
+ }
2156
+ if (this.#isOptionalProperty(targetProperty) && !this.#isOptionalProperty(sourceProperty)) {
2157
+ return false;
2158
+ }
2159
+ const targetPropertyType = this.#typeChecker.getTypeOfSymbol(targetProperty);
2160
+ const sourcePropertyType = this.#typeChecker.getTypeOfSymbol(sourceProperty);
2161
+ if (!this.#typeChecker.isTypeAssignableTo(targetPropertyType, sourcePropertyType)) {
2162
+ return false;
2163
+ }
2164
+ }
2165
+ if (sourceType != null) {
2166
+ const sourceProperties = sourceType.getProperties();
2167
+ for (const sourceProperty of sourceProperties) {
2168
+ const targetProperty = targetType.getProperty(sourceProperty.getName());
2169
+ if (!targetProperty && !this.#isOptionalProperty(sourceProperty)) {
2170
+ return false;
2171
+ }
2172
+ }
2173
+ }
2174
+ return true;
2175
+ };
2176
+ if (sourceType != null && matchWorker.isUnionType(sourceType)) {
2177
+ return sourceType.types.some((sourceType) => check(sourceType, targetType));
2178
+ }
2179
+ return check(sourceType, targetType);
2180
+ }
2181
+ #explainProperties(matchWorker, signature, targetNode, diagnostic) {
2182
+ const sourceType = matchWorker.getParameterType(signature, 0);
2183
+ const sourceTypeText = sourceType != null ? this.#typeChecker.typeToString(sourceType) : "{}";
2184
+ const targetType = matchWorker.getType(targetNode);
2185
+ const targetTypeText = this.#typeChecker.typeToString(targetType);
2186
+ const explain = (sourceType, targetType, diagnostic) => {
2187
+ const sourceTypeText = sourceType != null ? this.#typeChecker.typeToString(sourceType) : "{}";
2188
+ const diagnostics = [];
2189
+ for (const targetProperty of targetType.getProperties()) {
2190
+ const targetPropertyName = targetProperty.getName();
2191
+ const sourceProperty = sourceType?.getProperty(targetPropertyName);
2192
+ if (!sourceProperty) {
2193
+ const text = [
2194
+ ExpectDiagnosticText.typeIsNotCompatibleWith(sourceTypeText, targetTypeText),
2195
+ ExpectDiagnosticText.typeDoesNotHaveProperty(sourceTypeText, targetPropertyName),
2196
+ ];
2197
+ const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
2198
+ diagnostics.push(diagnostic.extendWith(text, origin));
2199
+ continue;
2200
+ }
2201
+ if (this.#isOptionalProperty(targetProperty) && !this.#isOptionalProperty(sourceProperty)) {
2202
+ const text = [
2203
+ ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText),
2204
+ ExpectDiagnosticText.typeRequiresProperty(sourceTypeText, targetPropertyName),
2205
+ ];
2206
+ const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
2207
+ diagnostics.push(diagnostic.extendWith(text, origin));
2208
+ continue;
2209
+ }
2210
+ const targetPropertyType = this.#typeChecker.getTypeOfSymbol(targetProperty);
2211
+ const sourcePropertyType = this.#typeChecker.getTypeOfSymbol(sourceProperty);
2212
+ if (!this.#typeChecker.isTypeAssignableTo(targetPropertyType, sourcePropertyType)) {
2213
+ const targetPropertyTypeText = this.#typeChecker.typeToString(targetPropertyType);
2214
+ const sourcePropertyTypeText = this.#typeChecker.typeToString(sourcePropertyType);
2215
+ const text = [
2216
+ ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText),
2217
+ ExpectDiagnosticText.typesOfPropertyAreNotCompatible(targetPropertyName),
2218
+ ExpectDiagnosticText.typeIsNotAssignableWith(sourcePropertyTypeText, targetPropertyTypeText),
2219
+ ];
2220
+ const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
2221
+ diagnostics.push(diagnostic.extendWith(text, origin));
2222
+ }
2223
+ }
2224
+ if (sourceType != null) {
2225
+ for (const sourceProperty of sourceType.getProperties()) {
2226
+ const sourcePropertyName = sourceProperty.getName();
2227
+ const targetProperty = targetType.getProperty(sourcePropertyName);
2228
+ if (!targetProperty && !this.#isOptionalProperty(sourceProperty)) {
2229
+ const text = [
2230
+ ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText),
2231
+ ExpectDiagnosticText.typeRequiresProperty(sourceTypeText, sourcePropertyName),
2232
+ ];
2233
+ diagnostics.push(diagnostic.extendWith(text));
2234
+ }
2235
+ }
2236
+ }
2237
+ if (diagnostics.length === 0) {
2238
+ const text = ExpectDiagnosticText.typeIsAssignableWith(sourceTypeText, targetTypeText);
2239
+ diagnostics.push(diagnostic.extendWith(text));
2240
+ return { diagnostics, isMatch: true };
2241
+ }
2242
+ return { diagnostics, isMatch: false };
2243
+ };
2244
+ if (sourceType != null && matchWorker.isUnionType(sourceType)) {
2245
+ let accumulator = [];
2246
+ const isMatch = sourceType.types.some((sourceType) => {
2247
+ const text = matchWorker.assertion.isNot
2248
+ ? ExpectDiagnosticText.typeIsAssignableWith(sourceTypeText, targetTypeText)
2249
+ : ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText);
2250
+ const { diagnostics, isMatch } = explain(sourceType, targetType, diagnostic.extendWith(text));
2251
+ if (isMatch) {
2252
+ accumulator = diagnostics;
2253
+ }
2254
+ else {
2255
+ accumulator.push(...diagnostics);
2256
+ }
2257
+ return isMatch;
2258
+ });
2259
+ return { diagnostics: accumulator, isMatch };
2260
+ }
2261
+ return explain(sourceType, targetType, diagnostic);
2262
+ }
2263
+ match(matchWorker, sourceNode, targetNode, onDiagnostics) {
2264
+ const diagnostics = [];
2265
+ const signatures = matchWorker.getSignatures(sourceNode);
2266
+ if (signatures.length === 0) {
2267
+ const expectedText = "of a function or class type";
2268
+ const text = this.#compiler.isTypeNode(sourceNode)
2269
+ ? ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText)
2270
+ : ExpectDiagnosticText.argumentMustBe("source", expectedText);
2271
+ const origin = DiagnosticOrigin.fromNode(sourceNode);
2272
+ diagnostics.push(Diagnostic.error(text, origin));
2273
+ }
2274
+ const targetType = matchWorker.getType(targetNode);
2275
+ if (!matchWorker.isObjectType(targetType)) {
2276
+ const expectedText = "of an object type";
2277
+ const text = this.#compiler.isTypeNode(targetNode)
2278
+ ? ExpectDiagnosticText.typeArgumentMustBe("Target", expectedText)
2279
+ : ExpectDiagnosticText.argumentMustBe("target", expectedText);
2280
+ const origin = DiagnosticOrigin.fromNode(targetNode);
2281
+ diagnostics.push(Diagnostic.error(text, origin));
2282
+ }
2283
+ if (diagnostics.length > 0) {
2284
+ onDiagnostics(diagnostics);
2285
+ return;
2286
+ }
2287
+ const isMatch = signatures.some((signature) => {
2288
+ const sourceType = matchWorker.getParameterType(signature, 0);
2289
+ return this.#checkProperties(matchWorker, sourceType, targetType);
2290
+ });
1953
2291
  return {
1954
- explain: () => this.explain(sourceType, targetType, isNot),
2292
+ explain: () => this.#explain(matchWorker, sourceNode, targetNode),
1955
2293
  isMatch,
1956
2294
  };
1957
2295
  }
1958
2296
  }
1959
2297
 
2298
+ class RelationMatcherBase {
2299
+ explain(matchWorker, sourceNode, targetNode) {
2300
+ const sourceTypeText = matchWorker.getTypeText(sourceNode);
2301
+ const targetTypeText = matchWorker.getTypeText(targetNode);
2302
+ const text = matchWorker.assertion.isNot
2303
+ ? this.explainText(sourceTypeText, targetTypeText)
2304
+ : this.explainNotText(sourceTypeText, targetTypeText);
2305
+ const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
2306
+ return [Diagnostic.error(text, origin)];
2307
+ }
2308
+ }
2309
+
1960
2310
  class ToBe extends RelationMatcherBase {
1961
- relation = this.typeChecker.relation.identity;
1962
- relationExplanationText = "identical to";
2311
+ explainText = ExpectDiagnosticText.typeIsIdenticalTo;
2312
+ explainNotText = ExpectDiagnosticText.typeIsNotIdenticalTo;
2313
+ match(matchWorker, sourceNode, targetNode) {
2314
+ return {
2315
+ explain: () => this.explain(matchWorker, sourceNode, targetNode),
2316
+ isMatch: matchWorker.checkIsIdenticalTo(sourceNode, targetNode),
2317
+ };
2318
+ }
1963
2319
  }
1964
2320
 
1965
2321
  class ToBeAssignableTo extends RelationMatcherBase {
1966
- relation = this.typeChecker.relation.assignable;
1967
- relationExplanationText = "assignable to";
2322
+ explainText = ExpectDiagnosticText.typeIsAssignableTo;
2323
+ explainNotText = ExpectDiagnosticText.typeIsNotAssignableTo;
2324
+ match(matchWorker, sourceNode, targetNode) {
2325
+ return {
2326
+ explain: () => this.explain(matchWorker, sourceNode, targetNode),
2327
+ isMatch: matchWorker.checkIsAssignableTo(sourceNode, targetNode),
2328
+ };
2329
+ }
1968
2330
  }
1969
2331
 
1970
2332
  class ToBeAssignableWith extends RelationMatcherBase {
1971
- relation = this.typeChecker.relation.assignable;
1972
- relationExplanationText = "assignable with";
1973
- match(sourceType, targetType, isNot) {
1974
- const isMatch = this.typeChecker.isTypeRelatedTo(targetType, sourceType, this.relation);
2333
+ explainText = ExpectDiagnosticText.typeIsAssignableWith;
2334
+ explainNotText = ExpectDiagnosticText.typeIsNotAssignableWith;
2335
+ match(matchWorker, sourceNode, targetNode) {
1975
2336
  return {
1976
- explain: () => this.explain(sourceType, targetType, isNot),
1977
- isMatch,
2337
+ explain: () => this.explain(matchWorker, sourceNode, targetNode),
2338
+ isMatch: matchWorker.checkIsAssignableWith(sourceNode, targetNode),
1978
2339
  };
1979
2340
  }
1980
2341
  }
1981
2342
 
1982
2343
  class ToHaveProperty {
1983
- compiler;
1984
- typeChecker;
1985
- constructor(compiler, typeChecker) {
1986
- this.compiler = compiler;
1987
- this.typeChecker = typeChecker;
2344
+ #compiler;
2345
+ constructor(compiler) {
2346
+ this.#compiler = compiler;
1988
2347
  }
1989
- #explain(sourceType, targetType, isNot) {
1990
- const sourceTypeText = this.typeChecker.typeToString(sourceType);
1991
- let targetArgumentText;
1992
- if (this.#isStringOrNumberLiteralType(targetType)) {
1993
- targetArgumentText = String(targetType.value);
2348
+ #explain(matchWorker, sourceNode, targetNode) {
2349
+ const sourceTypeText = matchWorker.getTypeText(sourceNode);
2350
+ const targetType = matchWorker.getType(targetNode);
2351
+ let propertyNameText;
2352
+ if (matchWorker.isStringOrNumberLiteralType(targetType)) {
2353
+ propertyNameText = targetType.value.toString();
1994
2354
  }
1995
2355
  else {
1996
- targetArgumentText = `[${this.compiler.unescapeLeadingUnderscores(targetType.symbol.escapedName)}]`;
2356
+ propertyNameText = `[${this.#compiler.unescapeLeadingUnderscores(targetType.symbol.escapedName)}]`;
1997
2357
  }
1998
- return isNot
1999
- ? [Diagnostic.error(`Property '${targetArgumentText}' exists on type '${sourceTypeText}'.`)]
2000
- : [Diagnostic.error(`Property '${targetArgumentText}' does not exist on type '${sourceTypeText}'.`)];
2001
- }
2002
- #isStringOrNumberLiteralType(type) {
2003
- return Boolean(type.flags & this.compiler.TypeFlags.StringOrNumberLiteral);
2358
+ const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
2359
+ return matchWorker.assertion.isNot
2360
+ ? [Diagnostic.error(ExpectDiagnosticText.typeHasProperty(sourceTypeText, propertyNameText), origin)]
2361
+ : [Diagnostic.error(ExpectDiagnosticText.typeDoesNotHaveProperty(sourceTypeText, propertyNameText), origin)];
2004
2362
  }
2005
- match(sourceType, targetType, isNot) {
2006
- let targetArgumentText;
2007
- if (this.#isStringOrNumberLiteralType(targetType)) {
2008
- targetArgumentText = String(targetType.value);
2363
+ match(matchWorker, sourceNode, targetNode, onDiagnostics) {
2364
+ const diagnostics = [];
2365
+ const sourceType = matchWorker.getType(sourceNode);
2366
+ if (matchWorker.isAnyOrNeverType(sourceType) || !matchWorker.extendsObjectType(sourceType)) {
2367
+ const expectedText = "of an object type";
2368
+ const text = this.#compiler.isTypeNode(sourceNode)
2369
+ ? ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText)
2370
+ : ExpectDiagnosticText.argumentMustBe("source", expectedText);
2371
+ const origin = DiagnosticOrigin.fromNode(sourceNode);
2372
+ diagnostics.push(Diagnostic.error(text, origin));
2373
+ }
2374
+ const targetType = matchWorker.getType(targetNode);
2375
+ let propertyNameText;
2376
+ if (matchWorker.isStringOrNumberLiteralType(targetType)) {
2377
+ propertyNameText = targetType.value.toString();
2378
+ }
2379
+ else if (matchWorker.isUniqueSymbolType(targetType)) {
2380
+ propertyNameText = this.#compiler.unescapeLeadingUnderscores(targetType.escapedName);
2009
2381
  }
2010
2382
  else {
2011
- targetArgumentText = this.compiler.unescapeLeadingUnderscores(targetType.escapedName);
2383
+ const expectedText = "of type 'string | number | symbol'";
2384
+ const text = ExpectDiagnosticText.argumentMustBe("key", expectedText);
2385
+ const origin = DiagnosticOrigin.fromNode(targetNode);
2386
+ diagnostics.push(Diagnostic.error(text, origin));
2387
+ }
2388
+ if (diagnostics.length > 0) {
2389
+ onDiagnostics(diagnostics);
2390
+ return;
2012
2391
  }
2013
2392
  const isMatch = sourceType.getProperties().some((property) => {
2014
- return this.compiler.unescapeLeadingUnderscores(property.escapedName) === targetArgumentText;
2393
+ return this.#compiler.unescapeLeadingUnderscores(property.escapedName) === propertyNameText;
2015
2394
  });
2016
2395
  return {
2017
- explain: () => this.#explain(sourceType, targetType, isNot),
2396
+ explain: () => this.#explain(matchWorker, sourceNode, targetNode),
2018
2397
  isMatch,
2019
2398
  };
2020
2399
  }
2021
2400
  }
2022
2401
 
2023
2402
  class ToMatch extends RelationMatcherBase {
2024
- relation = this.typeChecker.relation.subtype;
2025
- relationExplanationText = "match";
2026
- relationExplanationVerb = "does";
2403
+ explainText = ExpectDiagnosticText.typeDoesMatch;
2404
+ explainNotText = ExpectDiagnosticText.typeDoesNotMatch;
2405
+ match(matchWorker, sourceNode, targetNode) {
2406
+ return {
2407
+ explain: () => this.explain(matchWorker, sourceNode, targetNode),
2408
+ isMatch: matchWorker.checkIsSubtype(sourceNode, targetNode),
2409
+ };
2410
+ }
2027
2411
  }
2028
2412
 
2029
2413
  class ToRaiseError {
2030
- compiler;
2031
- typeChecker;
2032
- constructor(compiler, typeChecker) {
2033
- this.compiler = compiler;
2034
- this.typeChecker = typeChecker;
2414
+ #compiler;
2415
+ constructor(compiler) {
2416
+ this.#compiler = compiler;
2035
2417
  }
2036
- #explain(source, targetTypes, isNot) {
2037
- const sourceText = this.compiler.isTypeNode(source.node) ? "Type expression" : "Expression";
2038
- if (source.diagnostics.length === 0) {
2039
- return [Diagnostic.error(`${sourceText} did not raise a type error.`)];
2418
+ #explain(matchWorker, sourceNode, targetNodes) {
2419
+ const isTypeNode = this.#compiler.isTypeNode(sourceNode);
2420
+ const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
2421
+ if (matchWorker.assertion.diagnostics.size === 0) {
2422
+ const text = ExpectDiagnosticText.typeDidNotRaiseError(isTypeNode);
2423
+ return [Diagnostic.error(text, origin)];
2040
2424
  }
2041
- if (isNot && targetTypes.length === 0) {
2042
- const related = [
2043
- Diagnostic.error(`The raised type error${source.diagnostics.length === 1 ? "" : "s"}:`),
2044
- ...Diagnostic.fromDiagnostics(source.diagnostics, this.compiler),
2045
- ];
2046
- const text = `${sourceText} raised ${source.diagnostics.length === 1 ? "a" : String(source.diagnostics.length)} type error${source.diagnostics.length === 1 ? "" : "s"}.`;
2047
- return [Diagnostic.error(text).add({ related })];
2048
- }
2049
- if (source.diagnostics.length !== targetTypes.length) {
2050
- const expectedText = source.diagnostics.length > targetTypes.length
2051
- ? `only ${String(targetTypes.length)} type error${targetTypes.length === 1 ? "" : "s"}`
2052
- : `${String(targetTypes.length)} type error${targetTypes.length === 1 ? "" : "s"}`;
2053
- const foundText = source.diagnostics.length > targetTypes.length
2054
- ? String(source.diagnostics.length)
2055
- : `only ${String(source.diagnostics.length)}`;
2425
+ if (matchWorker.assertion.diagnostics.size !== targetNodes.length) {
2426
+ const count = matchWorker.assertion.diagnostics.size;
2427
+ const text = ExpectDiagnosticText.typeRaisedError(isTypeNode, count, targetNodes.length);
2056
2428
  const related = [
2057
- Diagnostic.error(`The raised type error${source.diagnostics.length === 1 ? "" : "s"}:`),
2058
- ...Diagnostic.fromDiagnostics(source.diagnostics, this.compiler),
2429
+ Diagnostic.error(ExpectDiagnosticText.raisedTypeError(count)),
2430
+ ...Diagnostic.fromDiagnostics([...matchWorker.assertion.diagnostics], this.#compiler),
2059
2431
  ];
2060
- const text = `Expected ${expectedText}, but ${foundText} ${source.diagnostics.length === 1 ? "was" : "were"} raised.`;
2061
- return [Diagnostic.error(text).add({ related })];
2062
- }
2063
- const diagnostics = [];
2064
- targetTypes.forEach((argument, index) => {
2065
- const diagnostic = source.diagnostics[index];
2066
- if (!diagnostic) {
2067
- return;
2068
- }
2069
- const isMatch = this.#matchExpectedError(diagnostic, argument);
2070
- if (!isNot && !isMatch) {
2071
- const expectedText = this.#isStringLiteralType(argument)
2072
- ? `matching substring '${argument.value}'`
2073
- : `with code ${String(argument.value)}`;
2074
- const related = [
2075
- Diagnostic.error("The raised type error:"),
2076
- ...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
2077
- ];
2078
- const text = `${sourceText} did not raise a type error ${expectedText}.`;
2079
- diagnostics.push(Diagnostic.error(text).add({ related }));
2080
- }
2081
- if (isNot && isMatch) {
2082
- const expectedText = this.#isStringLiteralType(argument)
2083
- ? `matching substring '${argument.value}'`
2084
- : `with code ${String(argument.value)}`;
2432
+ return [Diagnostic.error(text, origin).add({ related })];
2433
+ }
2434
+ return [...matchWorker.assertion.diagnostics].reduce((accumulator, diagnostic, index) => {
2435
+ const targetNode = targetNodes[index];
2436
+ const isMatch = this.#matchExpectedError(diagnostic, targetNode);
2437
+ if (matchWorker.assertion.isNot ? isMatch : !isMatch) {
2438
+ const text = matchWorker.assertion.isNot
2439
+ ? ExpectDiagnosticText.typeRaisedMatchingError(isTypeNode)
2440
+ : ExpectDiagnosticText.typeDidNotRaiseMatchingError(isTypeNode);
2441
+ const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
2085
2442
  const related = [
2086
- Diagnostic.error("The raised type error:"),
2087
- ...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
2443
+ Diagnostic.error(ExpectDiagnosticText.raisedTypeError()),
2444
+ ...Diagnostic.fromDiagnostics([diagnostic], this.#compiler),
2088
2445
  ];
2089
- const text = `${sourceText} raised a type error ${expectedText}.`;
2090
- diagnostics.push(Diagnostic.error(text).add({ related }));
2446
+ accumulator.push(Diagnostic.error(text, origin).add({ related }));
2091
2447
  }
2092
- });
2093
- return diagnostics;
2094
- }
2095
- #isStringLiteralType(type) {
2096
- return Boolean(type.flags & this.compiler.TypeFlags.StringLiteral);
2448
+ return accumulator;
2449
+ }, []);
2097
2450
  }
2098
- match(source, targetTypes, isNot) {
2099
- const explain = () => this.#explain(source, targetTypes, isNot);
2100
- if (targetTypes.length === 0) {
2101
- return {
2102
- explain,
2103
- isMatch: source.diagnostics.length > 0,
2104
- };
2451
+ match(matchWorker, sourceNode, targetNodes, onDiagnostics) {
2452
+ const diagnostics = [];
2453
+ for (const targetNode of targetNodes) {
2454
+ if (!(this.#compiler.isStringLiteralLike(targetNode) || this.#compiler.isNumericLiteral(targetNode))) {
2455
+ const expectedText = "a string or number literal";
2456
+ const text = ExpectDiagnosticText.argumentMustBe("target", expectedText);
2457
+ const origin = DiagnosticOrigin.fromNode(targetNode);
2458
+ diagnostics.push(Diagnostic.error(text, origin));
2459
+ }
2105
2460
  }
2106
- if (source.diagnostics.length !== targetTypes.length) {
2107
- return {
2108
- explain,
2109
- isMatch: false,
2110
- };
2461
+ if (diagnostics.length > 0) {
2462
+ onDiagnostics(diagnostics);
2463
+ return;
2464
+ }
2465
+ let isMatch;
2466
+ if (targetNodes.length === 0) {
2467
+ isMatch = matchWorker.assertion.diagnostics.size > 0;
2468
+ }
2469
+ else {
2470
+ isMatch =
2471
+ matchWorker.assertion.diagnostics.size === targetNodes.length &&
2472
+ [...matchWorker.assertion.diagnostics].every((diagnostic, index) => this.#matchExpectedError(diagnostic, targetNodes[index]));
2111
2473
  }
2112
2474
  return {
2113
- explain,
2114
- isMatch: targetTypes.every((type, index) => this.#matchExpectedError(source.diagnostics[index], type)),
2475
+ explain: () => this.#explain(matchWorker, sourceNode, targetNodes),
2476
+ isMatch,
2115
2477
  };
2116
2478
  }
2117
- #matchExpectedError(diagnostic, type) {
2118
- if (this.#isStringLiteralType(type)) {
2119
- return this.compiler.flattenDiagnosticMessageText(diagnostic?.messageText, " ", 0).includes(type.value);
2479
+ #matchExpectedError(diagnostic, targetNode) {
2480
+ if (this.#compiler.isStringLiteralLike(targetNode)) {
2481
+ return this.#compiler.flattenDiagnosticMessageText(diagnostic.messageText, " ", 0).includes(targetNode.text);
2120
2482
  }
2121
- return type.value === diagnostic?.code;
2483
+ return Number.parseInt(targetNode.text) === diagnostic.code;
2122
2484
  }
2123
2485
  }
2124
2486
 
2125
- class Expect {
2487
+ class ExpectService {
2126
2488
  #compiler;
2127
2489
  #typeChecker;
2490
+ toAcceptProps;
2128
2491
  toBe;
2129
2492
  toBeAny;
2130
2493
  toBeAssignable;
@@ -2148,66 +2511,63 @@ class Expect {
2148
2511
  constructor(compiler, typeChecker) {
2149
2512
  this.#compiler = compiler;
2150
2513
  this.#typeChecker = typeChecker;
2151
- this.toBe = new ToBe(typeChecker);
2152
- this.toBeAny = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.Any);
2153
- this.toBeAssignable = new ToBeAssignableWith(typeChecker);
2154
- this.toBeAssignableTo = new ToBeAssignableTo(typeChecker);
2155
- this.toBeAssignableWith = new ToBeAssignableWith(typeChecker);
2156
- this.toBeBigInt = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.BigInt);
2157
- this.toBeBoolean = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.Boolean);
2158
- this.toBeNever = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.Never);
2159
- this.toBeNull = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.Null);
2160
- this.toBeNumber = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.Number);
2161
- this.toBeString = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.String);
2162
- this.toBeSymbol = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.ESSymbol);
2163
- this.toBeUndefined = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.Undefined);
2164
- this.toBeUniqueSymbol = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.UniqueESSymbol);
2165
- this.toBeUnknown = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.Unknown);
2166
- this.toBeVoid = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.Void);
2167
- this.toEqual = new ToBe(typeChecker);
2168
- this.toHaveProperty = new ToHaveProperty(compiler, typeChecker);
2169
- this.toMatch = new ToMatch(typeChecker);
2170
- this.toRaiseError = new ToRaiseError(compiler, typeChecker);
2514
+ this.toAcceptProps = new ToAcceptProps(compiler, typeChecker);
2515
+ this.toBe = new ToBe();
2516
+ this.toBeAny = new PrimitiveTypeMatcher(compiler.TypeFlags.Any);
2517
+ this.toBeAssignable = new ToBeAssignableWith();
2518
+ this.toBeAssignableTo = new ToBeAssignableTo();
2519
+ this.toBeAssignableWith = new ToBeAssignableWith();
2520
+ this.toBeBigInt = new PrimitiveTypeMatcher(compiler.TypeFlags.BigInt);
2521
+ this.toBeBoolean = new PrimitiveTypeMatcher(compiler.TypeFlags.Boolean);
2522
+ this.toBeNever = new PrimitiveTypeMatcher(compiler.TypeFlags.Never);
2523
+ this.toBeNull = new PrimitiveTypeMatcher(compiler.TypeFlags.Null);
2524
+ this.toBeNumber = new PrimitiveTypeMatcher(compiler.TypeFlags.Number);
2525
+ this.toBeString = new PrimitiveTypeMatcher(compiler.TypeFlags.String);
2526
+ this.toBeSymbol = new PrimitiveTypeMatcher(compiler.TypeFlags.ESSymbol);
2527
+ this.toBeUndefined = new PrimitiveTypeMatcher(compiler.TypeFlags.Undefined);
2528
+ this.toBeUniqueSymbol = new PrimitiveTypeMatcher(compiler.TypeFlags.UniqueESSymbol);
2529
+ this.toBeUnknown = new PrimitiveTypeMatcher(compiler.TypeFlags.Unknown);
2530
+ this.toBeVoid = new PrimitiveTypeMatcher(compiler.TypeFlags.Void);
2531
+ this.toEqual = new ToBe();
2532
+ this.toHaveProperty = new ToHaveProperty(compiler);
2533
+ this.toMatch = new ToMatch();
2534
+ this.toRaiseError = new ToRaiseError(compiler);
2171
2535
  }
2172
2536
  static assertTypeChecker(typeChecker) {
2173
2537
  return "isTypeRelatedTo" in typeChecker && "relation" in typeChecker;
2174
2538
  }
2175
- #getType(node) {
2176
- return this.#compiler.isExpression(node)
2177
- ? this.#typeChecker.getTypeAtLocation(node)
2178
- : this.#typeChecker.getTypeFromTypeNode(node);
2179
- }
2180
- #getTypes(nodes) {
2181
- return nodes.map((node) => this.#getType(node));
2182
- }
2183
- #isArrayOfStringOrNumberLiteralTypes(types) {
2184
- return types.every((type) => this.#isStringOrNumberLiteralType(type));
2185
- }
2186
- #isStringOrNumberLiteralType(type) {
2187
- return Boolean(type.flags & this.#compiler.TypeFlags.StringOrNumberLiteral);
2188
- }
2189
- #isUniqueSymbolType(type) {
2190
- return Boolean(type.flags & this.#compiler.TypeFlags.UniqueESSymbol);
2539
+ #handleDeprecated(matcherNameText, assertion) {
2540
+ switch (matcherNameText) {
2541
+ case "toBeAssignable":
2542
+ case "toEqual": {
2543
+ const text = ExpectDiagnosticText.matcherIsDeprecated(matcherNameText);
2544
+ const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
2545
+ EventEmitter.dispatch(["deprecation:info", { diagnostics: [Diagnostic.warning(text, origin)] }]);
2546
+ break;
2547
+ }
2548
+ }
2191
2549
  }
2192
- match(assertion, expectResult) {
2550
+ match(assertion, onDiagnostics) {
2193
2551
  const matcherNameText = assertion.matcherName.getText();
2552
+ this.#handleDeprecated(matcherNameText, assertion);
2553
+ if (!assertion.source[0]) {
2554
+ this.#onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
2555
+ return;
2556
+ }
2557
+ const matchWorker = new MatchWorker(this.#compiler, this.#typeChecker, assertion);
2194
2558
  switch (matcherNameText) {
2195
- case "toBeAssignable":
2196
- case "toEqual":
2197
- this.#onDeprecatedMatcher(assertion);
2559
+ case "toAcceptProps":
2198
2560
  case "toBe":
2561
+ case "toBeAssignable":
2199
2562
  case "toBeAssignableTo":
2200
2563
  case "toBeAssignableWith":
2564
+ case "toEqual":
2201
2565
  case "toMatch": {
2202
- if (assertion.source[0] == null) {
2203
- this.#onSourceArgumentMustBeProvided(assertion, expectResult);
2566
+ if (!assertion.target[0]) {
2567
+ this.#onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
2204
2568
  return;
2205
2569
  }
2206
- if (assertion.target[0] == null) {
2207
- this.#onTargetArgumentMustBeProvided(assertion, expectResult);
2208
- return;
2209
- }
2210
- return this[matcherNameText].match(this.#getType(assertion.source[0]), this.#getType(assertion.target[0]), assertion.isNot);
2570
+ return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
2211
2571
  }
2212
2572
  case "toBeAny":
2213
2573
  case "toBeBigInt":
@@ -2221,131 +2581,40 @@ class Expect {
2221
2581
  case "toBeUniqueSymbol":
2222
2582
  case "toBeUnknown":
2223
2583
  case "toBeVoid": {
2224
- if (assertion.source[0] == null) {
2225
- this.#onSourceArgumentMustBeProvided(assertion, expectResult);
2226
- return;
2227
- }
2228
- return this[matcherNameText].match(this.#getType(assertion.source[0]));
2584
+ return this[matcherNameText].match(matchWorker, assertion.source[0]);
2229
2585
  }
2230
2586
  case "toHaveProperty": {
2231
- if (assertion.source[0] == null) {
2232
- this.#onSourceArgumentMustBeProvided(assertion, expectResult);
2233
- return;
2234
- }
2235
- const sourceType = this.#getType(assertion.source[0]);
2236
- const nonPrimitiveType = { flags: this.#compiler.TypeFlags.NonPrimitive };
2237
- if (sourceType.flags & (this.#compiler.TypeFlags.Any | this.#compiler.TypeFlags.Never) ||
2238
- !this.#typeChecker.isTypeRelatedTo(sourceType, nonPrimitiveType, this.#typeChecker.relation.assignable)) {
2239
- this.#onSourceArgumentMustBeObjectType(assertion.source[0], expectResult);
2587
+ if (!assertion.target[0]) {
2588
+ this.#onTargetArgumentMustBeProvided("key", assertion, onDiagnostics);
2240
2589
  return;
2241
2590
  }
2242
- if (assertion.target[0] == null) {
2243
- this.#onKeyArgumentMustBeProvided(assertion, expectResult);
2244
- return;
2245
- }
2246
- const targetType = this.#getType(assertion.target[0]);
2247
- if (!(this.#isStringOrNumberLiteralType(targetType) || this.#isUniqueSymbolType(targetType))) {
2248
- this.#onKeyArgumentMustBeOfType(assertion.target[0], expectResult);
2249
- return;
2250
- }
2251
- return this.toHaveProperty.match(sourceType, targetType, assertion.isNot);
2591
+ return this.toHaveProperty.match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
2252
2592
  }
2253
2593
  case "toRaiseError": {
2254
- if (assertion.source[0] == null) {
2255
- this.#onSourceArgumentMustBeProvided(assertion, expectResult);
2256
- return;
2257
- }
2258
- const targetTypes = this.#getTypes(assertion.target);
2259
- if (!this.#isArrayOfStringOrNumberLiteralTypes(targetTypes)) {
2260
- this.#onTargetArgumentsMustBeStringOrNumberLiteralTypes(assertion.target, expectResult);
2261
- return;
2262
- }
2263
- return this.toRaiseError.match({ diagnostics: [...assertion.diagnostics], node: assertion.source[0] }, targetTypes, assertion.isNot);
2594
+ return this.toRaiseError.match(matchWorker, assertion.source[0], [...assertion.target], onDiagnostics);
2264
2595
  }
2265
2596
  default: {
2266
- this.#onNotSupportedMatcherName(assertion, expectResult);
2267
- return;
2597
+ const text = ExpectDiagnosticText.matcherIsNotSupported(matcherNameText);
2598
+ const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
2599
+ onDiagnostics(Diagnostic.error(text, origin));
2268
2600
  }
2269
2601
  }
2602
+ return;
2270
2603
  }
2271
- #onDeprecatedMatcher(assertion) {
2272
- const matcherNameText = assertion.matcherName.getText();
2273
- const text = [
2274
- `'.${matcherNameText}()' is deprecated and will be removed in TSTyche 3.`,
2275
- "To learn more, visit https://tstyche.org/release-notes/tstyche-2",
2276
- ];
2277
- const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
2278
- EventEmitter.dispatch(["deprecation:info", { diagnostics: [Diagnostic.warning(text, origin)] }]);
2279
- }
2280
- #onKeyArgumentMustBeOfType(node, expectResult) {
2281
- const receivedTypeText = this.#typeChecker.typeToString(this.#getType(node));
2282
- const text = `An argument for 'key' must be of type 'string | number | symbol', received: '${receivedTypeText}'.`;
2283
- const origin = DiagnosticOrigin.fromNode(node);
2284
- EventEmitter.dispatch(["expect:error", { diagnostics: [Diagnostic.error(text, origin)], result: expectResult }]);
2604
+ #onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics) {
2605
+ const text = ExpectDiagnosticText.argumentOrTypeArgumentMustBeProvided("source", "Source");
2606
+ const origin = DiagnosticOrigin.fromNode(assertion.node.expression);
2607
+ onDiagnostics(Diagnostic.error(text, origin));
2285
2608
  }
2286
- #onKeyArgumentMustBeProvided(assertion, expectResult) {
2609
+ #onTargetArgumentMustBeProvided(argumentNameText, assertion, onDiagnostics) {
2610
+ const text = ExpectDiagnosticText.argumentMustBeProvided(argumentNameText);
2287
2611
  const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
2288
- EventEmitter.dispatch([
2289
- "expect:error",
2290
- {
2291
- diagnostics: [Diagnostic.error("An argument for 'key' must be provided.", origin)],
2292
- result: expectResult,
2293
- },
2294
- ]);
2295
- }
2296
- #onNotSupportedMatcherName(assertion, expectResult) {
2297
- const matcherNameText = assertion.matcherName.getText();
2298
- const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
2299
- EventEmitter.dispatch([
2300
- "expect:error",
2301
- {
2302
- diagnostics: [Diagnostic.error(`The '${matcherNameText}()' matcher is not supported.`, origin)],
2303
- result: expectResult,
2304
- },
2305
- ]);
2306
- }
2307
- #onSourceArgumentMustBeObjectType(node, expectResult) {
2308
- const sourceText = this.#compiler.isTypeNode(node) ? "A type argument for 'Source'" : "An argument for 'source'";
2309
- const receivedTypeText = this.#typeChecker.typeToString(this.#getType(node));
2310
- const text = `${sourceText} must be of an object type, received: '${receivedTypeText}'.`;
2311
- const origin = DiagnosticOrigin.fromNode(node);
2312
- EventEmitter.dispatch(["expect:error", { diagnostics: [Diagnostic.error(text, origin)], result: expectResult }]);
2313
- }
2314
- #onSourceArgumentMustBeProvided(assertion, expectResult) {
2315
- const origin = DiagnosticOrigin.fromNode(assertion.node);
2316
- EventEmitter.dispatch([
2317
- "expect:error",
2318
- {
2319
- diagnostics: [
2320
- Diagnostic.error("An argument for 'source' or type argument for 'Source' must be provided.", origin),
2321
- ],
2322
- result: expectResult,
2323
- },
2324
- ]);
2612
+ onDiagnostics(Diagnostic.error(text, origin));
2325
2613
  }
2326
- #onTargetArgumentMustBeProvided(assertion, expectResult) {
2614
+ #onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics) {
2615
+ const text = ExpectDiagnosticText.argumentOrTypeArgumentMustBeProvided("target", "Target");
2327
2616
  const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
2328
- EventEmitter.dispatch([
2329
- "expect:error",
2330
- {
2331
- diagnostics: [
2332
- Diagnostic.error("An argument for 'target' or type argument for 'Target' must be provided.", origin),
2333
- ],
2334
- result: expectResult,
2335
- },
2336
- ]);
2337
- }
2338
- #onTargetArgumentsMustBeStringOrNumberLiteralTypes(nodes, expectResult) {
2339
- const diagnostics = [];
2340
- for (const node of nodes) {
2341
- const receivedType = this.#getType(node);
2342
- if (!this.#isStringOrNumberLiteralType(receivedType)) {
2343
- const receivedTypeText = this.#typeChecker.typeToString(this.#getType(node));
2344
- const origin = DiagnosticOrigin.fromNode(node);
2345
- diagnostics.push(Diagnostic.error(`An argument for 'target' must be of type 'string | number', received: '${receivedTypeText}'.`, origin));
2346
- }
2347
- }
2348
- EventEmitter.dispatch(["expect:error", { diagnostics, result: expectResult }]);
2617
+ onDiagnostics(Diagnostic.error(text, origin));
2349
2618
  }
2350
2619
  }
2351
2620
 
@@ -2445,7 +2714,7 @@ class ProjectService {
2445
2714
  defaultCompilerOptions.module = "preserve";
2446
2715
  }
2447
2716
  if (Version.isSatisfiedWith(this.#compiler.version, "5.0")) {
2448
- defaultCompilerOptions["allowImportingTsExtensions"] = true;
2717
+ defaultCompilerOptions.allowImportingTsExtensions = true;
2449
2718
  defaultCompilerOptions.moduleResolution = "bundler";
2450
2719
  }
2451
2720
  return defaultCompilerOptions;
@@ -2478,19 +2747,19 @@ class ProjectService {
2478
2747
  class TestTreeWorker {
2479
2748
  #compiler;
2480
2749
  #cancellationToken;
2481
- #expect;
2750
+ #expectService;
2482
2751
  #fileResult;
2483
2752
  #hasOnly;
2484
2753
  #position;
2485
2754
  #resolvedConfig;
2486
- constructor(resolvedConfig, compiler, expect, options) {
2755
+ constructor(resolvedConfig, compiler, typeChecker, options) {
2487
2756
  this.#resolvedConfig = resolvedConfig;
2488
2757
  this.#compiler = compiler;
2489
- this.#expect = expect;
2490
2758
  this.#cancellationToken = options.cancellationToken;
2491
2759
  this.#fileResult = options.fileResult;
2492
2760
  this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
2493
2761
  this.#position = options.position;
2762
+ this.#expectService = new ExpectService(compiler, typeChecker);
2494
2763
  }
2495
2764
  #resolveRunMode(mode, member) {
2496
2765
  if (member.flags & 1) {
@@ -2520,13 +2789,7 @@ class TestTreeWorker {
2520
2789
  }
2521
2790
  const validationError = member.validate();
2522
2791
  if (validationError.length > 0) {
2523
- EventEmitter.dispatch([
2524
- "file:error",
2525
- {
2526
- diagnostics: validationError,
2527
- result: this.#fileResult,
2528
- },
2529
- ]);
2792
+ EventEmitter.dispatch(["file:error", { diagnostics: validationError, result: this.#fileResult }]);
2530
2793
  break;
2531
2794
  }
2532
2795
  switch (member.brand) {
@@ -2554,28 +2817,25 @@ class TestTreeWorker {
2554
2817
  EventEmitter.dispatch(["expect:skip", { result: expectResult }]);
2555
2818
  return;
2556
2819
  }
2557
- if (assertion.diagnostics.size > 0 && assertion.matcherName.getText() !== "toRaiseError") {
2820
+ const onExpectDiagnostics = (diagnostics) => {
2558
2821
  EventEmitter.dispatch([
2559
2822
  "expect:error",
2560
- {
2561
- diagnostics: Diagnostic.fromDiagnostics([...assertion.diagnostics], this.#compiler),
2562
- result: expectResult,
2563
- },
2823
+ { diagnostics: Array.isArray(diagnostics) ? diagnostics : [diagnostics], result: expectResult },
2564
2824
  ]);
2825
+ };
2826
+ if (assertion.diagnostics.size > 0 && assertion.matcherName.getText() !== "toRaiseError") {
2827
+ onExpectDiagnostics(Diagnostic.fromDiagnostics([...assertion.diagnostics], this.#compiler));
2565
2828
  return;
2566
2829
  }
2567
- const matchResult = this.#expect.match(assertion, expectResult);
2568
- if (matchResult == null) {
2830
+ const matchResult = this.#expectService.match(assertion, onExpectDiagnostics);
2831
+ if (!matchResult) {
2569
2832
  return;
2570
2833
  }
2571
2834
  if (assertion.isNot ? !matchResult.isMatch : matchResult.isMatch) {
2572
2835
  if (runMode & 1) {
2573
2836
  const text = ["The assertion was supposed to fail, but it passed.", "Consider removing the '.fail' flag."];
2574
- const origin = DiagnosticOrigin.fromNode(assertion.node);
2575
- EventEmitter.dispatch([
2576
- "expect:error",
2577
- { diagnostics: [Diagnostic.error(text, origin)], result: expectResult },
2578
- ]);
2837
+ const origin = DiagnosticOrigin.fromNode(assertion.node.expression.name);
2838
+ onExpectDiagnostics(Diagnostic.error(text, origin));
2579
2839
  }
2580
2840
  else {
2581
2841
  EventEmitter.dispatch(["expect:pass", { result: expectResult }]);
@@ -2585,14 +2845,7 @@ class TestTreeWorker {
2585
2845
  EventEmitter.dispatch(["expect:pass", { result: expectResult }]);
2586
2846
  }
2587
2847
  else {
2588
- const origin = DiagnosticOrigin.fromNode(assertion.matcherName, assertion.ancestorNames);
2589
- const diagnostics = matchResult.explain().map((diagnostic) => {
2590
- if (diagnostic.origin == null) {
2591
- return diagnostic.add({ origin });
2592
- }
2593
- return diagnostic;
2594
- });
2595
- EventEmitter.dispatch(["expect:fail", { diagnostics, result: expectResult }]);
2848
+ EventEmitter.dispatch(["expect:fail", { diagnostics: matchResult.explain(), result: expectResult }]);
2596
2849
  }
2597
2850
  }
2598
2851
  #visitDescribe(describe, runMode, parentResult) {
@@ -2705,13 +2958,12 @@ class TestFileRunner {
2705
2958
  return;
2706
2959
  }
2707
2960
  const typeChecker = program.getTypeChecker();
2708
- if (!Expect.assertTypeChecker(typeChecker)) {
2961
+ if (!ExpectService.assertTypeChecker(typeChecker)) {
2709
2962
  const text = "The required 'isTypeRelatedTo()' method is missing in the provided type checker.";
2710
2963
  EventEmitter.dispatch(["file:error", { diagnostics: [Diagnostic.error(text)], result: fileResult }]);
2711
2964
  return;
2712
2965
  }
2713
- const expect = new Expect(this.#compiler, typeChecker);
2714
- const testTreeWorker = new TestTreeWorker(this.#resolvedConfig, this.#compiler, expect, {
2966
+ const testTreeWorker = new TestTreeWorker(this.#resolvedConfig, this.#compiler, typeChecker, {
2715
2967
  cancellationToken,
2716
2968
  fileResult,
2717
2969
  hasOnly: testTree.hasOnly,
@@ -2742,6 +2994,7 @@ class TaskRunner {
2742
2994
  this.#eventEmitter.addHandler(cancellationHandler);
2743
2995
  }
2744
2996
  if (this.#resolvedConfig.watch === true) {
2997
+ await this.#run(testFiles, cancellationToken);
2745
2998
  await this.#watch(testFiles, cancellationToken);
2746
2999
  }
2747
3000
  else {
@@ -2767,22 +3020,15 @@ class TaskRunner {
2767
3020
  EventEmitter.dispatch(["target:end", { result: targetResult }]);
2768
3021
  }
2769
3022
  EventEmitter.dispatch(["run:end", { result }]);
2770
- if (cancellationToken?.reason === "failFast") {
3023
+ if (cancellationToken.reason === "failFast") {
2771
3024
  cancellationToken.reset();
2772
3025
  }
2773
3026
  }
2774
3027
  async #watch(testFiles, cancellationToken) {
2775
- await this.#run(testFiles, cancellationToken);
2776
- const runCallback = async (testFiles) => {
3028
+ const watchService = new WatchService(this.#resolvedConfig, this.#selectService, testFiles);
3029
+ for await (const testFiles of watchService.watch(cancellationToken)) {
2777
3030
  await this.#run(testFiles, cancellationToken);
2778
- };
2779
- const watchModeManager = new WatchService(this.#resolvedConfig, runCallback, this.#selectService, testFiles);
2780
- cancellationToken?.onCancellationRequested((reason) => {
2781
- if (reason !== "failFast") {
2782
- watchModeManager.close();
2783
- }
2784
- });
2785
- await watchModeManager.watch(cancellationToken);
3031
+ }
2786
3032
  }
2787
3033
  }
2788
3034
 
@@ -2793,7 +3039,7 @@ class TSTyche {
2793
3039
  #selectService;
2794
3040
  #storeService;
2795
3041
  #taskRunner;
2796
- static version = "2.0.0";
3042
+ static version = "2.1.1";
2797
3043
  constructor(resolvedConfig, outputService, selectService, storeService) {
2798
3044
  this.#resolvedConfig = resolvedConfig;
2799
3045
  this.#outputService = outputService;
@@ -3009,14 +3255,14 @@ class OptionUsageText {
3009
3255
  }
3010
3256
 
3011
3257
  class OptionValidator {
3012
- #onDiagnostic;
3258
+ #onDiagnostics;
3013
3259
  #optionGroup;
3014
3260
  #optionUsageText;
3015
3261
  #storeService;
3016
- constructor(optionGroup, storeService, onDiagnostic) {
3262
+ constructor(optionGroup, storeService, onDiagnostics) {
3017
3263
  this.#optionGroup = optionGroup;
3018
3264
  this.#storeService = storeService;
3019
- this.#onDiagnostic = onDiagnostic;
3265
+ this.#onDiagnostics = onDiagnostics;
3020
3266
  this.#optionUsageText = new OptionUsageText(this.#optionGroup, this.#storeService);
3021
3267
  }
3022
3268
  async check(optionName, optionValue, optionBrand, origin) {
@@ -3024,13 +3270,13 @@ class OptionValidator {
3024
3270
  case "config":
3025
3271
  case "rootPath": {
3026
3272
  if (!existsSync(optionValue)) {
3027
- this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.fileDoesNotExist(optionValue), origin));
3273
+ this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.fileDoesNotExist(optionValue), origin));
3028
3274
  }
3029
3275
  break;
3030
3276
  }
3031
3277
  case "target": {
3032
3278
  if ((await this.#storeService.validateTag(optionValue)) === false) {
3033
- this.#onDiagnostic(Diagnostic.error([
3279
+ this.#onDiagnostics(Diagnostic.error([
3034
3280
  ConfigDiagnosticText.versionIsNotSupported(optionValue),
3035
3281
  ...(await this.#optionUsageText.get(optionName, optionBrand)),
3036
3282
  ], origin));
@@ -3040,14 +3286,14 @@ class OptionValidator {
3040
3286
  case "testFileMatch": {
3041
3287
  for (const segment of ["/", "../"]) {
3042
3288
  if (optionValue.startsWith(segment)) {
3043
- this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.testFileMatchCannotStartWith(segment), origin));
3289
+ this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.testFileMatchCannotStartWith(segment), origin));
3044
3290
  }
3045
3291
  }
3046
3292
  break;
3047
3293
  }
3048
3294
  case "watch": {
3049
3295
  if (Environment.isCi) {
3050
- this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.watchCannotBeEnabled(), origin));
3296
+ this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.watchCannotBeEnabled(), origin));
3051
3297
  }
3052
3298
  break;
3053
3299
  }
@@ -3058,27 +3304,27 @@ class OptionValidator {
3058
3304
  class CommandLineOptionsWorker {
3059
3305
  #commandLineOptionDefinitions;
3060
3306
  #commandLineOptions;
3061
- #onDiagnostic;
3307
+ #onDiagnostics;
3062
3308
  #optionGroup = 2;
3063
3309
  #optionUsageText;
3064
3310
  #optionValidator;
3065
3311
  #pathMatch;
3066
3312
  #storeService;
3067
- constructor(commandLineOptions, pathMatch, storeService, onDiagnostic) {
3313
+ constructor(commandLineOptions, pathMatch, storeService, onDiagnostics) {
3068
3314
  this.#commandLineOptions = commandLineOptions;
3069
3315
  this.#pathMatch = pathMatch;
3070
3316
  this.#storeService = storeService;
3071
- this.#onDiagnostic = onDiagnostic;
3317
+ this.#onDiagnostics = onDiagnostics;
3072
3318
  this.#commandLineOptionDefinitions = OptionDefinitionsMap.for(this.#optionGroup);
3073
3319
  this.#optionUsageText = new OptionUsageText(this.#optionGroup, this.#storeService);
3074
- this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostic);
3320
+ this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostics);
3075
3321
  }
3076
3322
  async #onExpectsValue(optionDefinition) {
3077
3323
  const text = [
3078
3324
  ConfigDiagnosticText.expectsValue(optionDefinition.name, this.#optionGroup),
3079
3325
  ...(await this.#optionUsageText.get(optionDefinition.name, optionDefinition.brand)),
3080
3326
  ];
3081
- this.#onDiagnostic(Diagnostic.error(text));
3327
+ this.#onDiagnostics(Diagnostic.error(text));
3082
3328
  }
3083
3329
  async parse(commandLineArgs) {
3084
3330
  let index = 0;
@@ -3092,11 +3338,11 @@ class CommandLineOptionsWorker {
3092
3338
  index = await this.#parseOptionValue(commandLineArgs, index, optionDefinition);
3093
3339
  }
3094
3340
  else {
3095
- this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.unknownOption(arg)));
3341
+ this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.unknownOption(arg)));
3096
3342
  }
3097
3343
  }
3098
3344
  else if (arg.startsWith("-")) {
3099
- this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.unknownOption(arg)));
3345
+ this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.unknownOption(arg)));
3100
3346
  }
3101
3347
  else {
3102
3348
  this.#pathMatch.push(Path.normalizeSlashes(arg));
@@ -3162,18 +3408,18 @@ class ConfigFileOptionsWorker {
3162
3408
  #configFileOptionDefinitions;
3163
3409
  #configFileOptions;
3164
3410
  #configFilePath;
3165
- #onDiagnostic;
3411
+ #onDiagnostics;
3166
3412
  #optionGroup = 4;
3167
3413
  #optionValidator;
3168
3414
  #storeService;
3169
- constructor(compiler, configFileOptions, configFilePath, storeService, onDiagnostic) {
3415
+ constructor(compiler, configFileOptions, configFilePath, storeService, onDiagnostics) {
3170
3416
  this.#compiler = compiler;
3171
3417
  this.#configFileOptions = configFileOptions;
3172
3418
  this.#configFilePath = configFilePath;
3173
3419
  this.#storeService = storeService;
3174
- this.#onDiagnostic = onDiagnostic;
3420
+ this.#onDiagnostics = onDiagnostics;
3175
3421
  this.#configFileOptionDefinitions = OptionDefinitionsMap.for(this.#optionGroup);
3176
- this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostic);
3422
+ this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostics);
3177
3423
  }
3178
3424
  #isDoubleQuotedString(node, sourceFile) {
3179
3425
  return (node.kind === this.#compiler.SyntaxKind.StringLiteral &&
@@ -3182,25 +3428,26 @@ class ConfigFileOptionsWorker {
3182
3428
  async parse(sourceText) {
3183
3429
  const sourceFile = this.#compiler.parseJsonText(this.#configFilePath, sourceText);
3184
3430
  if (sourceFile.parseDiagnostics.length > 0) {
3185
- for (const diagnostic of Diagnostic.fromDiagnostics(sourceFile.parseDiagnostics, this.#compiler)) {
3186
- this.#onDiagnostic(diagnostic);
3187
- }
3431
+ this.#onDiagnostics(Diagnostic.fromDiagnostics(sourceFile.parseDiagnostics, this.#compiler));
3188
3432
  return;
3189
3433
  }
3190
3434
  const rootExpression = sourceFile.statements[0]?.expression;
3191
- if (rootExpression == null || !this.#compiler.isObjectLiteralExpression(rootExpression)) {
3192
- const origin = new DiagnosticOrigin(0, 0, sourceFile);
3193
- this.#onDiagnostic(Diagnostic.error("The root value of a configuration file must be an object literal.", origin));
3435
+ if (!rootExpression) {
3436
+ return;
3437
+ }
3438
+ if (!this.#compiler.isObjectLiteralExpression(rootExpression)) {
3439
+ const origin = DiagnosticOrigin.fromJsonNode(rootExpression, sourceFile, this.#skipTrivia);
3440
+ this.#onDiagnostics(Diagnostic.error("The root value of a configuration file must be an object literal.", origin));
3194
3441
  return;
3195
3442
  }
3196
3443
  for (const property of rootExpression.properties) {
3197
3444
  if (this.#compiler.isPropertyAssignment(property)) {
3198
3445
  if (!this.#isDoubleQuotedString(property.name, sourceFile)) {
3199
- const origin = DiagnosticOrigin.fromJsonNode(property, sourceFile, this.#skipTrivia);
3200
- this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.doubleQuotesExpected(), origin));
3446
+ const origin = DiagnosticOrigin.fromJsonNode(property.name, sourceFile, this.#skipTrivia);
3447
+ this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.doubleQuotesExpected(), origin));
3201
3448
  continue;
3202
3449
  }
3203
- const optionName = this.#resolvePropertyName(property);
3450
+ const optionName = property.name.text;
3204
3451
  if (optionName === "$schema") {
3205
3452
  continue;
3206
3453
  }
@@ -3209,8 +3456,8 @@ class ConfigFileOptionsWorker {
3209
3456
  this.#configFileOptions[optionDefinition.name] = await this.#parseOptionValue(sourceFile, property.initializer, optionDefinition);
3210
3457
  }
3211
3458
  else {
3212
- const origin = DiagnosticOrigin.fromJsonNode(property, sourceFile, this.#skipTrivia);
3213
- this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.unknownOption(optionName), origin));
3459
+ const origin = DiagnosticOrigin.fromJsonNode(property.name, sourceFile, this.#skipTrivia);
3460
+ this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.unknownOption(optionName), origin));
3214
3461
  }
3215
3462
  }
3216
3463
  }
@@ -3233,7 +3480,7 @@ class ConfigFileOptionsWorker {
3233
3480
  case this.#compiler.SyntaxKind.StringLiteral: {
3234
3481
  if (!this.#isDoubleQuotedString(valueExpression, sourceFile)) {
3235
3482
  const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
3236
- this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.doubleQuotesExpected(), origin));
3483
+ this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.doubleQuotesExpected(), origin));
3237
3484
  return;
3238
3485
  }
3239
3486
  if (optionDefinition.brand === "string") {
@@ -3262,15 +3509,9 @@ class ConfigFileOptionsWorker {
3262
3509
  ? ConfigDiagnosticText.expectsListItemType(optionDefinition.name, optionDefinition.brand)
3263
3510
  : ConfigDiagnosticText.requiresValueType(optionDefinition.name, optionDefinition.brand, this.#optionGroup);
3264
3511
  const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
3265
- this.#onDiagnostic(Diagnostic.error(text, origin));
3512
+ this.#onDiagnostics(Diagnostic.error(text, origin));
3266
3513
  return;
3267
3514
  }
3268
- #resolvePropertyName({ name }) {
3269
- if ("text" in name) {
3270
- return name.text;
3271
- }
3272
- return "";
3273
- }
3274
3515
  #skipTrivia(position, sourceFile) {
3275
3516
  const { text } = sourceFile.getSourceFile();
3276
3517
  while (position < text.length) {
@@ -3312,7 +3553,7 @@ class ConfigFileOptionsWorker {
3312
3553
  const defaultOptions = {
3313
3554
  failFast: false,
3314
3555
  rootPath: "./",
3315
- target: [Environment.typescriptPath == null ? "latest" : "current"],
3556
+ target: [Environment.typescriptPath != null ? "current" : "latest"],
3316
3557
  testFileMatch: ["**/*.tst.*", "**/__typetests__/*.test.*", "**/typetests/*.test.*"],
3317
3558
  };
3318
3559
  class ConfigService {
@@ -3326,13 +3567,13 @@ class ConfigService {
3326
3567
  this.#compiler = compiler;
3327
3568
  this.#storeService = storeService;
3328
3569
  }
3329
- #onDiagnostic(diagnostic) {
3330
- EventEmitter.dispatch(["config:error", { diagnostics: [diagnostic] }]);
3570
+ #onDiagnostics(diagnostics) {
3571
+ EventEmitter.dispatch(["config:error", { diagnostics: Array.isArray(diagnostics) ? diagnostics : [diagnostics] }]);
3331
3572
  }
3332
3573
  async parseCommandLine(commandLineArgs) {
3333
3574
  this.#commandLineOptions = {};
3334
3575
  this.#pathMatch = [];
3335
- const commandLineWorker = new CommandLineOptionsWorker(this.#commandLineOptions, this.#pathMatch, this.#storeService, this.#onDiagnostic);
3576
+ const commandLineWorker = new CommandLineOptionsWorker(this.#commandLineOptions, this.#pathMatch, this.#storeService, this.#onDiagnostics);
3336
3577
  await commandLineWorker.parse(commandLineArgs);
3337
3578
  if (this.#commandLineOptions.config != null) {
3338
3579
  this.#configFilePath = this.#commandLineOptions.config;
@@ -3349,7 +3590,7 @@ class ConfigService {
3349
3590
  const configFileText = await fs.readFile(this.#configFilePath, {
3350
3591
  encoding: "utf8",
3351
3592
  });
3352
- const configFileWorker = new ConfigFileOptionsWorker(this.#compiler, this.#configFileOptions, this.#configFilePath, this.#storeService, this.#onDiagnostic);
3593
+ const configFileWorker = new ConfigFileOptionsWorker(this.#compiler, this.#configFileOptions, this.#configFilePath, this.#storeService, this.#onDiagnostics);
3353
3594
  await configFileWorker.parse(configFileText);
3354
3595
  }
3355
3596
  resolveConfig() {
@@ -3382,27 +3623,27 @@ class StoreDiagnosticText {
3382
3623
  return `Failed to fetch metadata of the 'typescript' package from '${registryUrl.toString()}'.`;
3383
3624
  }
3384
3625
  static failedWithStatusCode(code) {
3385
- return `Request failed with status code ${String(code)}.`;
3626
+ return `Request failed with status code ${code}.`;
3386
3627
  }
3387
3628
  static maybeNetworkConnectionIssue() {
3388
3629
  return "Might be there is an issue with the registry or the network connection.";
3389
3630
  }
3390
3631
  static setupTimeoutExceeded(timeout) {
3391
- return `Setup timeout of ${String(timeout / 1000)}s was exceeded.`;
3632
+ return `Setup timeout of ${timeout / 1000}s was exceeded.`;
3392
3633
  }
3393
3634
  }
3394
3635
 
3395
3636
  class ManifestWorker {
3396
3637
  #manifestFileName = "store-manifest.json";
3397
3638
  #manifestFilePath;
3398
- #onDiagnostic;
3639
+ #onDiagnostics;
3399
3640
  #registryUrl = new URL("https://registry.npmjs.org");
3400
3641
  #storePath;
3401
3642
  #timeout = Environment.timeout * 1000;
3402
3643
  #version = "1";
3403
- constructor(storePath, onDiagnostic) {
3644
+ constructor(storePath, onDiagnostics) {
3404
3645
  this.#storePath = storePath;
3405
- this.#onDiagnostic = onDiagnostic;
3646
+ this.#onDiagnostics = onDiagnostics;
3406
3647
  this.#manifestFilePath = Path.join(storePath, this.#manifestFileName);
3407
3648
  }
3408
3649
  async #create() {
@@ -3432,7 +3673,7 @@ class ManifestWorker {
3432
3673
  signal: AbortSignal.timeout(this.#timeout),
3433
3674
  });
3434
3675
  if (!response.ok) {
3435
- this.#onDiagnostic(Diagnostic.error([
3676
+ this.#onDiagnostics(Diagnostic.error([
3436
3677
  StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
3437
3678
  StoreDiagnosticText.failedWithStatusCode(response.status),
3438
3679
  ]));
@@ -3445,13 +3686,13 @@ class ManifestWorker {
3445
3686
  return;
3446
3687
  }
3447
3688
  if (error instanceof Error && error.name === "TimeoutError") {
3448
- this.#onDiagnostic(Diagnostic.error([
3689
+ this.#onDiagnostics(Diagnostic.error([
3449
3690
  StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
3450
3691
  StoreDiagnosticText.setupTimeoutExceeded(this.#timeout),
3451
3692
  ]));
3452
3693
  }
3453
3694
  else {
3454
- this.#onDiagnostic(Diagnostic.error([
3695
+ this.#onDiagnostics(Diagnostic.error([
3455
3696
  StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
3456
3697
  StoreDiagnosticText.maybeNetworkConnectionIssue(),
3457
3698
  ]));
@@ -3486,9 +3727,9 @@ class ManifestWorker {
3486
3727
  manifestText = await fs.readFile(this.#manifestFilePath, { encoding: "utf8" });
3487
3728
  }
3488
3729
  catch (error) {
3489
- this.#onDiagnostic(Diagnostic.fromError("Failed to open store manifest.", error));
3730
+ this.#onDiagnostics(Diagnostic.fromError("Failed to open store manifest.", error));
3490
3731
  }
3491
- if (manifestText == null) {
3732
+ if (!manifestText) {
3492
3733
  return;
3493
3734
  }
3494
3735
  try {
@@ -3496,7 +3737,7 @@ class ManifestWorker {
3496
3737
  }
3497
3738
  catch {
3498
3739
  }
3499
- if (manifest == null || manifest.$version !== this.#version) {
3740
+ if (!manifest || manifest.$version !== this.#version) {
3500
3741
  await fs.rm(this.#storePath, { force: true, recursive: true });
3501
3742
  return this.#create();
3502
3743
  }
@@ -3536,7 +3777,7 @@ class Lock {
3536
3777
  if (!isLocked) {
3537
3778
  return isLocked;
3538
3779
  }
3539
- if (options?.timeout == null) {
3780
+ if (!options?.timeout) {
3540
3781
  return isLocked;
3541
3782
  }
3542
3783
  const waitStartTime = Date.now();
@@ -3545,7 +3786,7 @@ class Lock {
3545
3786
  break;
3546
3787
  }
3547
3788
  if (Date.now() - waitStartTime > options.timeout) {
3548
- options.onDiagnostic?.(`Lock wait timeout of ${String(options.timeout / 1000)}s was exceeded.`);
3789
+ options.onDiagnostics?.(`Lock wait timeout of ${options.timeout / 1000}s was exceeded.`);
3549
3790
  break;
3550
3791
  }
3551
3792
  await Lock.#sleep(1000);
@@ -3562,13 +3803,13 @@ class Lock {
3562
3803
  }
3563
3804
 
3564
3805
  class PackageInstaller {
3565
- #onDiagnostic;
3806
+ #onDiagnostics;
3566
3807
  #readyFileName = "__ready__";
3567
3808
  #storePath;
3568
3809
  #timeout = Environment.timeout * 1000;
3569
- constructor(storePath, onDiagnostic) {
3810
+ constructor(storePath, onDiagnostics) {
3570
3811
  this.#storePath = storePath;
3571
- this.#onDiagnostic = onDiagnostic;
3812
+ this.#onDiagnostics = onDiagnostics;
3572
3813
  }
3573
3814
  async ensure(compilerVersion, cancellationToken) {
3574
3815
  const installationPath = Path.join(this.#storePath, compilerVersion);
@@ -3579,8 +3820,8 @@ class PackageInstaller {
3579
3820
  }
3580
3821
  if (await Lock.isLocked(installationPath, {
3581
3822
  cancellationToken,
3582
- onDiagnostic: (text) => {
3583
- this.#onDiagnostic(Diagnostic.error([`Failed to install 'typescript@${compilerVersion}'.`, text]));
3823
+ onDiagnostics: (text) => {
3824
+ this.#onDiagnostics(Diagnostic.error([`Failed to install 'typescript@${compilerVersion}'.`, text]));
3584
3825
  },
3585
3826
  timeout: this.#timeout,
3586
3827
  })) {
@@ -3606,7 +3847,7 @@ class PackageInstaller {
3606
3847
  return modulePath;
3607
3848
  }
3608
3849
  catch (error) {
3609
- this.#onDiagnostic(Diagnostic.fromError(`Failed to install 'typescript@${compilerVersion}'.`, error));
3850
+ this.#onDiagnostics(Diagnostic.fromError(`Failed to install 'typescript@${compilerVersion}'.`, error));
3610
3851
  }
3611
3852
  finally {
3612
3853
  lock.release();
@@ -3630,9 +3871,9 @@ class PackageInstaller {
3630
3871
  resolve();
3631
3872
  }
3632
3873
  if (signal != null) {
3633
- reject(new Error(`setup timeout of ${String(this.#timeout / 1000)}s was exceeded`));
3874
+ reject(new Error(`setup timeout of ${this.#timeout / 1000}s was exceeded`));
3634
3875
  }
3635
- reject(new Error(`process exited with code ${String(code)}`));
3876
+ reject(new Error(`process exited with code ${code}`));
3636
3877
  });
3637
3878
  });
3638
3879
  }
@@ -3646,8 +3887,8 @@ class StoreService {
3646
3887
  #storePath;
3647
3888
  constructor() {
3648
3889
  this.#storePath = Environment.storePath;
3649
- this.#packageInstaller = new PackageInstaller(this.#storePath, this.#onDiagnostic);
3650
- this.#manifestWorker = new ManifestWorker(this.#storePath, this.#onDiagnostic);
3890
+ this.#packageInstaller = new PackageInstaller(this.#storePath, this.#onDiagnostics);
3891
+ this.#manifestWorker = new ManifestWorker(this.#storePath, this.#onDiagnostics);
3651
3892
  }
3652
3893
  async getSupportedTags() {
3653
3894
  await this.open();
@@ -3660,9 +3901,9 @@ class StoreService {
3660
3901
  if (tag === "current") {
3661
3902
  return;
3662
3903
  }
3663
- const version = await this.resolveTag(tag);
3664
- if (version == null) {
3665
- this.#onDiagnostic(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
3904
+ const version = await this.#resolveTag(tag);
3905
+ if (!version) {
3906
+ this.#onDiagnostics(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
3666
3907
  return;
3667
3908
  }
3668
3909
  return this.#packageInstaller.ensure(version, cancellationToken);
@@ -3677,9 +3918,9 @@ class StoreService {
3677
3918
  modulePath = Environment.typescriptPath;
3678
3919
  }
3679
3920
  else {
3680
- const version = await this.resolveTag(tag);
3681
- if (version == null) {
3682
- this.#onDiagnostic(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
3921
+ const version = await this.#resolveTag(tag);
3922
+ if (!version) {
3923
+ this.#onDiagnostics(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
3683
3924
  return;
3684
3925
  }
3685
3926
  compilerInstance = this.#compilerInstanceCache.get(version);
@@ -3705,6 +3946,7 @@ class StoreService {
3705
3946
  continue;
3706
3947
  }
3707
3948
  const toExpose = [
3949
+ "getTypeOfSymbol",
3708
3950
  "isTypeRelatedTo",
3709
3951
  "relation: { assignable: assignableRelation, identity: identityRelation, subtype: strictSubtypeRelation }",
3710
3952
  ];
@@ -3715,7 +3957,7 @@ class StoreService {
3715
3957
  }
3716
3958
  return module.exports;
3717
3959
  }
3718
- #onDiagnostic(diagnostic) {
3960
+ #onDiagnostics(diagnostic) {
3719
3961
  EventEmitter.dispatch(["store:error", { diagnostics: [diagnostic] }]);
3720
3962
  }
3721
3963
  async open() {
@@ -3724,10 +3966,7 @@ class StoreService {
3724
3966
  }
3725
3967
  this.#manifest = await this.#manifestWorker.open();
3726
3968
  }
3727
- async resolveTag(tag) {
3728
- if (tag === "current") {
3729
- return tag;
3730
- }
3969
+ async #resolveTag(tag) {
3731
3970
  await this.open();
3732
3971
  if (!this.#manifest) {
3733
3972
  return;
@@ -3752,7 +3991,7 @@ class StoreService {
3752
3991
  (!Version.isVersionTag(tag) ||
3753
3992
  (this.#manifest.resolutions["latest"] != null &&
3754
3993
  Version.isGreaterThan(tag, this.#manifest.resolutions["latest"])))) {
3755
- this.#onDiagnostic(Diagnostic.warning([
3994
+ this.#onDiagnostics(Diagnostic.warning([
3756
3995
  "Failed to update metadata of the 'typescript' package from the registry.",
3757
3996
  `The resolution of the '${tag}' tag may be outdated.`,
3758
3997
  ]));
@@ -3789,7 +4028,7 @@ class Cli {
3789
4028
  await this.#storeService.update();
3790
4029
  return;
3791
4030
  }
3792
- const compiler = await this.#storeService.load(Environment.typescriptPath == null ? "latest" : "current");
4031
+ const compiler = await this.#storeService.load(Environment.typescriptPath != null ? "current" : "latest");
3793
4032
  if (!compiler) {
3794
4033
  return;
3795
4034
  }
@@ -3855,28 +4094,33 @@ class Cli {
3855
4094
  this.#eventEmitter.removeHandlers();
3856
4095
  }
3857
4096
  #waitForChangedFiles(resolvedConfig, selectService, cancellationToken) {
3858
- const watchers = [];
3859
- cancellationToken.reset();
3860
- this.#outputService.writeMessage(waitingForFileChangesText());
3861
- const onChanged = () => {
3862
- cancellationToken.cancel("configChange");
3863
- for (const watcher of watchers) {
3864
- watcher.close();
3865
- }
3866
- };
3867
- watchers.push(new FileWatcher(resolvedConfig.configFilePath, onChanged));
3868
- if (selectService != null) {
3869
- const onChangedTestFile = (filePath) => {
3870
- if (selectService.isTestFile(filePath)) {
3871
- onChanged();
4097
+ return new Promise((resolve) => {
4098
+ const watchers = [];
4099
+ cancellationToken.reset();
4100
+ this.#outputService.writeMessage(waitingForFileChangesText());
4101
+ const onChanged = () => {
4102
+ cancellationToken.cancel("configChange");
4103
+ for (const watcher of watchers) {
4104
+ watcher.close();
3872
4105
  }
4106
+ resolve();
3873
4107
  };
3874
- const onRemoved = () => {
3875
- };
3876
- watchers.push(new Watcher(resolvedConfig.rootPath, onChangedTestFile, onRemoved, { recursive: true }));
3877
- }
3878
- return Promise.all(watchers.map((watcher) => watcher.watch()));
4108
+ watchers.push(new FileWatcher(resolvedConfig.configFilePath, onChanged));
4109
+ if (selectService != null) {
4110
+ const onChangedTestFile = (filePath) => {
4111
+ if (selectService.isTestFile(filePath)) {
4112
+ onChanged();
4113
+ }
4114
+ };
4115
+ const onRemoved = () => {
4116
+ };
4117
+ watchers.push(new Watcher(resolvedConfig.rootPath, onChangedTestFile, onRemoved, { recursive: true }));
4118
+ }
4119
+ for (const watcher of watchers) {
4120
+ watcher.watch();
4121
+ }
4122
+ });
3879
4123
  }
3880
4124
  }
3881
4125
 
3882
- export { Assertion, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, ConfigDiagnosticText, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Environment, EventEmitter, ExitCodeHandler, Expect, ExpectResult, FileResult, FileWatcher, InputService, Line, OptionBrand, OptionDefinitionsMap, OptionGroup, OutputService, Path, ProjectResult, ProjectService, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, RunReporter, Scribbler, SelectDiagnosticText, SelectService, SetupReporter, StoreService, SummaryReporter, TSTyche, TargetResult, TaskRunner, TestFile, TestMember, TestMemberBrand, TestMemberFlags, TestResult, TestTree, Text, Version, WatchReporter, WatchService, Watcher, addsPackageStepText, defaultOptions, describeNameText, diagnosticText, fileStatusText, fileViewText, formattedText, helpText, summaryText, testNameText, usesCompilerStepText, waitingForFileChangesText, watchUsageText };
4126
+ export { Assertion, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, ConfigDiagnosticText, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Environment, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileResult, FileWatcher, InputService, Line, OptionBrand, OptionDefinitionsMap, OptionGroup, OutputService, Path, ProjectResult, ProjectService, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, RunReporter, Scribbler, SelectDiagnosticText, SelectService, SetupReporter, StoreService, SummaryReporter, TSTyche, TargetResult, TaskRunner, TestFile, TestMember, TestMemberBrand, TestMemberFlags, TestResult, TestTree, Text, Version, WatchReporter, WatchService, Watcher, addsPackageStepText, defaultOptions, describeNameText, diagnosticText, fileStatusText, fileViewText, formattedText, helpText, summaryText, testNameText, usesCompilerStepText, waitingForFileChangesText, watchUsageText };