tstyche 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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';
@@ -613,12 +613,27 @@ 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.unshift(" ❭ ", ancestor.name);
620
+ ancestor = ancestor.parent;
621
+ }
622
+ return jsx(Text, { color: "90", children: text.join("") });
623
+ }
624
+ function CodeLineText({ lineText, gutterWidth, lineNumberColor = "90", lineNumberText }) {
625
+ return (jsx(Line, { children: [jsx(Text, { color: lineNumberColor, children: lineNumberText.padStart(gutterWidth) }), jsx(Text, { color: "90", children: " | " }), lineText] }));
626
+ }
627
+ function SquiggleLineText({ gutterWidth, indentWidth = 0, squiggleWidth }) {
628
+ return (jsx(Line, { children: [" ".repeat(gutterWidth), jsx(Text, { color: "90", children: " | " }), " ".repeat(indentWidth), jsx(Text, { color: "31", children: "~".repeat(squiggleWidth === 0 ? 1 : squiggleWidth) })] }));
629
+ }
630
+ function CodeSpanText({ diagnosticOrigin }) {
617
631
  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);
632
+ const { character: firstMarkedLineCharacter, line: firstMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.start);
633
+ const { character: lastMarkedLineCharacter, line: lastMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.end);
634
+ const firstLine = Math.max(firstMarkedLine - 2, 0);
620
635
  const lastLine = Math.min(firstLine + 5, lastLineInFile);
621
- const lineNumberMaxWidth = String(lastLine + 1).length;
636
+ const gutterWidth = String(lastLine + 1).length + 2;
622
637
  const codeSpan = [];
623
638
  for (let index = firstLine; index <= lastLine; index++) {
624
639
  const lineStart = diagnosticOrigin.sourceFile.getPositionOfLineAndCharacter(index, 0);
@@ -627,18 +642,26 @@ function CodeSpanText(diagnosticOrigin) {
627
642
  : diagnosticOrigin.sourceFile.getPositionOfLineAndCharacter(index + 1, 0);
628
643
  const lineNumberText = String(index + 1);
629
644
  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: "^" })] }));
645
+ if (index >= firstMarkedLine && index <= lastMarkedLine) {
646
+ codeSpan.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumberColor: "31", lineNumberText: lineNumberText, lineText: lineText }));
647
+ if (index === firstMarkedLine) {
648
+ const squiggleLength = index === lastMarkedLine
649
+ ? lastMarkedLineCharacter - firstMarkedLineCharacter
650
+ : lineText.length - firstMarkedLineCharacter;
651
+ codeSpan.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, indentWidth: firstMarkedLineCharacter, squiggleWidth: squiggleLength }));
652
+ }
653
+ else if (index === lastMarkedLine) {
654
+ codeSpan.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleWidth: lastMarkedLineCharacter }));
655
+ }
656
+ else {
657
+ codeSpan.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleWidth: lineText.length }));
658
+ }
632
659
  }
633
660
  else {
634
- codeSpan.push(jsx(Line, { children: [" ".repeat(2), jsx(Text, { color: "90", children: [lineNumberText.padStart(lineNumberMaxWidth), " | ", lineText || ""] })] }));
661
+ codeSpan.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumberText: lineNumberText, lineText: lineText }));
635
662
  }
636
663
  }
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] }));
664
+ 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: [":", String(firstMarkedLine + 1), ":", String(firstMarkedLineCharacter + 1)] }), diagnosticOrigin.assertion && jsx(BreadcrumbsText, { ancestor: diagnosticOrigin.assertion.parent })] }));
642
665
  return (jsx(Text, { children: [codeSpan, jsx(Line, {}), location] }));
643
666
  }
644
667
 
@@ -647,7 +670,7 @@ function DiagnosticText({ diagnostic }) {
647
670
  const text = Array.isArray(diagnostic.text) ? diagnostic.text : [diagnostic.text];
648
671
  const message = text.map((text, index) => (jsx(Text, { children: [index === 1 ? jsx(Line, {}) : undefined, jsx(Line, { children: [text, code] })] })));
649
672
  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;
673
+ const codeSpan = diagnostic.origin ? (jsx(Text, { children: [jsx(Line, {}), jsx(CodeSpanText, { diagnosticOrigin: diagnostic.origin })] })) : undefined;
651
674
  return (jsx(Text, { children: [message, codeSpan, jsx(Line, {}), jsx(Text, { indent: 2, children: related })] }));
652
675
  }
653
676
  function diagnosticText(diagnostic) {
@@ -790,21 +813,21 @@ class OutputService {
790
813
  eraseLastLine() {
791
814
  this.#stdout.write("\u001B[1A\u001B[0K");
792
815
  }
793
- #write(stream, body) {
794
- const elements = Array.isArray(body) ? body : [body];
816
+ #writeTo(stream, element) {
817
+ const elements = Array.isArray(element) ? element : [element];
795
818
  for (const element of elements) {
796
819
  stream.write(this.#scribbler.render(element));
797
820
  }
798
821
  this.#isClear = false;
799
822
  }
800
- writeError(body) {
801
- this.#write(this.#stderr, body);
823
+ writeError(element) {
824
+ this.#writeTo(this.#stderr, element);
802
825
  }
803
- writeMessage(body) {
804
- this.#write(this.#stdout, body);
826
+ writeMessage(element) {
827
+ this.#writeTo(this.#stdout, element);
805
828
  }
806
- writeWarning(body) {
807
- this.#write(this.#stderr, body);
829
+ writeWarning(element) {
830
+ this.#writeTo(this.#stderr, element);
808
831
  }
809
832
  }
810
833
 
@@ -814,10 +837,8 @@ function RowText({ label, text }) {
814
837
  function CountText({ failed, passed, skipped, todo, total }) {
815
838
  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" })] })] }));
816
839
  }
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`] }));
840
+ function DurationText({ seconds }) {
841
+ return jsx(Text, { children: `${String(Math.round(seconds * 10) / 10)}s` });
821
842
  }
822
843
  function MatchText({ text }) {
823
844
  if (typeof text === "string") {
@@ -835,7 +856,7 @@ function RanFilesText({ onlyMatch, pathMatch, skipMatch }) {
835
856
  testNameMatchText.push(jsx(Text, { children: [jsx(Text, { color: "90", children: "matching " }), jsx(MatchText, { text: onlyMatch })] }));
836
857
  }
837
858
  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 })] }));
859
+ testNameMatchText.push(jsx(Text, { children: [onlyMatch != null ? jsx(Text, { color: "90", children: " and " }) : undefined, jsx(Text, { color: "90", children: "not matching " }), jsx(MatchText, { text: skipMatch })] }));
839
860
  }
840
861
  let pathMatchText;
841
862
  if (pathMatch.length > 0) {
@@ -851,7 +872,7 @@ function summaryText({ duration, expectCount, fileCount, onlyMatch, pathMatch, s
851
872
  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
873
  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
874
  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 })] }));
875
+ 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
876
  }
856
877
 
857
878
  function StatusText({ status }) {
@@ -1155,7 +1176,6 @@ class WatchReporter extends Reporter {
1155
1176
 
1156
1177
  class CancellationToken {
1157
1178
  #isCancelled = false;
1158
- #handlers = new Set();
1159
1179
  #reason;
1160
1180
  get isCancellationRequested() {
1161
1181
  return this.#isCancelled;
@@ -1165,16 +1185,10 @@ class CancellationToken {
1165
1185
  }
1166
1186
  cancel(reason) {
1167
1187
  if (!this.#isCancelled) {
1168
- for (const handler of this.#handlers) {
1169
- handler(reason);
1170
- }
1171
1188
  this.#isCancelled = true;
1172
1189
  this.#reason = reason;
1173
1190
  }
1174
1191
  }
1175
- onCancellationRequested(handler) {
1176
- this.#handlers.add(handler);
1177
- }
1178
1192
  reset() {
1179
1193
  if (this.#isCancelled) {
1180
1194
  this.#isCancelled = false;
@@ -1188,10 +1202,10 @@ var CancellationReason;
1188
1202
  CancellationReason["ConfigChange"] = "configChange";
1189
1203
  CancellationReason["ConfigError"] = "configError";
1190
1204
  CancellationReason["FailFast"] = "failFast";
1205
+ CancellationReason["WatchClose"] = "watchClose";
1191
1206
  })(CancellationReason || (CancellationReason = {}));
1192
1207
 
1193
1208
  class Watcher {
1194
- #abortController = new AbortController();
1195
1209
  #onChanged;
1196
1210
  #onRemoved;
1197
1211
  #recursive;
@@ -1204,34 +1218,28 @@ class Watcher {
1204
1218
  this.#recursive = options?.recursive;
1205
1219
  }
1206
1220
  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
- }
1221
+ this.#watcher?.close();
1222
+ }
1223
+ watch() {
1224
+ this.#watcher = watch(this.#targetPath, { recursive: this.#recursive }, (_eventType, fileName) => {
1225
+ if (fileName != null) {
1226
+ const filePath = Path.resolve(this.#targetPath, fileName);
1227
+ if (existsSync(filePath)) {
1228
+ this.#onChanged(filePath);
1229
+ }
1230
+ else {
1231
+ this.#onRemoved(filePath);
1221
1232
  }
1222
1233
  }
1223
- }
1224
- catch (error) {
1225
- if (error instanceof Error && error.name === "AbortError") ;
1226
- }
1234
+ });
1227
1235
  }
1228
1236
  }
1229
1237
 
1230
1238
  class FileWatcher extends Watcher {
1231
1239
  constructor(targetPath, onChanged) {
1232
- const onChangedFile = async (filePath) => {
1240
+ const onChangedFile = (filePath) => {
1233
1241
  if (filePath === targetPath) {
1234
- await onChanged();
1242
+ onChanged();
1235
1243
  }
1236
1244
  };
1237
1245
  super(Path.dirname(targetPath), onChangedFile);
@@ -1239,29 +1247,33 @@ class FileWatcher extends Watcher {
1239
1247
  }
1240
1248
 
1241
1249
  class DiagnosticOrigin {
1242
- breadcrumbs;
1250
+ assertion;
1243
1251
  end;
1244
1252
  sourceFile;
1245
1253
  start;
1246
- constructor(start, end, sourceFile, breadcrumbs) {
1254
+ constructor(start, end, sourceFile, assertion) {
1247
1255
  this.start = start;
1248
1256
  this.end = end;
1249
1257
  this.sourceFile = sourceFile;
1250
- this.breadcrumbs = breadcrumbs;
1258
+ this.assertion = assertion;
1259
+ }
1260
+ static fromAssertion(assertion) {
1261
+ const node = assertion.matcherName;
1262
+ return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertion);
1251
1263
  }
1252
1264
  static fromJsonNode(node, sourceFile, skipTrivia) {
1253
1265
  return new DiagnosticOrigin(skipTrivia(node.pos, sourceFile), node.end, sourceFile);
1254
1266
  }
1255
- static fromNode(node, breadcrumbs) {
1256
- return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), breadcrumbs);
1267
+ static fromNode(node, assertion) {
1268
+ return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertion);
1257
1269
  }
1258
1270
  }
1259
1271
 
1260
1272
  class Diagnostic {
1261
1273
  category;
1262
1274
  code;
1263
- related;
1264
1275
  origin;
1276
+ related;
1265
1277
  text;
1266
1278
  constructor(text, category, origin) {
1267
1279
  this.text = text;
@@ -1272,9 +1284,6 @@ class Diagnostic {
1272
1284
  if (options.code != null) {
1273
1285
  this.code = options.code;
1274
1286
  }
1275
- if (options.origin != null) {
1276
- this.origin = options.origin;
1277
- }
1278
1287
  if (options.related != null) {
1279
1288
  this.related = options.related;
1280
1289
  }
@@ -1283,16 +1292,22 @@ class Diagnostic {
1283
1292
  static error(text, origin) {
1284
1293
  return new Diagnostic(text, "error", origin);
1285
1294
  }
1295
+ extendWith(text, origin) {
1296
+ return new Diagnostic([this.text, text].flat(), this.category, origin ?? this.origin);
1297
+ }
1286
1298
  static fromDiagnostics(diagnostics, compiler) {
1287
1299
  return diagnostics.map((diagnostic) => {
1288
- const category = "error";
1289
1300
  const code = `ts(${String(diagnostic.code)})`;
1290
1301
  let origin;
1291
- const text = compiler.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
1292
- if (Diagnostic.isTsDiagnosticWithLocation(diagnostic)) {
1302
+ if (Diagnostic.#isTsDiagnosticWithLocation(diagnostic)) {
1293
1303
  origin = new DiagnosticOrigin(diagnostic.start, diagnostic.start + diagnostic.length, diagnostic.file);
1294
1304
  }
1295
- return new Diagnostic(text, category, origin).add({ code });
1305
+ let related;
1306
+ if (diagnostic.relatedInformation != null) {
1307
+ related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation, compiler);
1308
+ }
1309
+ const text = compiler.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
1310
+ return new Diagnostic(text, "error", origin).add({ code, related });
1296
1311
  });
1297
1312
  }
1298
1313
  static fromError(text, error) {
@@ -1306,7 +1321,7 @@ class Diagnostic {
1306
1321
  }
1307
1322
  return Diagnostic.error(messageText);
1308
1323
  }
1309
- static isTsDiagnosticWithLocation(diagnostic) {
1324
+ static #isTsDiagnosticWithLocation(diagnostic) {
1310
1325
  return diagnostic.file != null && diagnostic.start != null && diagnostic.length != null;
1311
1326
  }
1312
1327
  static warning(text, origin) {
@@ -1428,7 +1443,7 @@ class SelectService {
1428
1443
  isTestFile(filePath) {
1429
1444
  return this.#isFileIncluded(Path.relative(this.#resolvedConfig.rootPath, filePath));
1430
1445
  }
1431
- #onDiagnostic(diagnostic) {
1446
+ #onDiagnostics(diagnostic) {
1432
1447
  EventEmitter.dispatch(["select:error", { diagnostics: [diagnostic] }]);
1433
1448
  }
1434
1449
  async selectFiles() {
@@ -1436,18 +1451,21 @@ class SelectService {
1436
1451
  const testFilePaths = [];
1437
1452
  await this.#visitDirectory(currentPath, testFilePaths);
1438
1453
  if (testFilePaths.length === 0) {
1439
- this.#onDiagnostic(Diagnostic.error(SelectDiagnosticText.noTestFilesWereSelected(this.#resolvedConfig)));
1454
+ this.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereSelected(this.#resolvedConfig)));
1440
1455
  }
1441
1456
  return testFilePaths.sort();
1442
1457
  }
1443
1458
  async #visitDirectory(currentPath, testFilePaths) {
1444
1459
  const targetPath = Path.join(this.#resolvedConfig.rootPath, currentPath);
1445
- let entries = [];
1460
+ let entries;
1446
1461
  try {
1447
1462
  entries = await fs.readdir(targetPath, { withFileTypes: true });
1448
1463
  }
1449
1464
  catch {
1450
1465
  }
1466
+ if (!entries) {
1467
+ return;
1468
+ }
1451
1469
  for (const entry of entries) {
1452
1470
  let entryMeta;
1453
1471
  if (entry.isSymbolicLink()) {
@@ -1455,12 +1473,14 @@ class SelectService {
1455
1473
  entryMeta = await fs.stat([targetPath, entry.name].join("/"));
1456
1474
  }
1457
1475
  catch {
1458
- continue;
1459
1476
  }
1460
1477
  }
1461
1478
  else {
1462
1479
  entryMeta = entry;
1463
1480
  }
1481
+ if (!entryMeta) {
1482
+ continue;
1483
+ }
1464
1484
  const entryPath = [currentPath, entry.name].join("/");
1465
1485
  if (entryMeta.isDirectory() && this.#isDirectoryIncluded(entryPath)) {
1466
1486
  await this.#visitDirectory(entryPath, testFilePaths);
@@ -1473,16 +1493,30 @@ class SelectService {
1473
1493
  }
1474
1494
  }
1475
1495
 
1476
- class Timer {
1496
+ class Debounce {
1497
+ #delay;
1498
+ #onResolve;
1499
+ #resolve;
1477
1500
  #timeout;
1478
- clear() {
1501
+ constructor(delay, onResolve) {
1502
+ this.#delay = delay;
1503
+ this.#onResolve = onResolve;
1504
+ }
1505
+ clearTimeout() {
1479
1506
  clearTimeout(this.#timeout);
1480
1507
  }
1481
- set(callback, delay, arg) {
1482
- new Promise((resolve) => {
1483
- this.#timeout = setTimeout(resolve, delay, arg);
1484
- }).then(async (arg) => {
1485
- await callback(arg);
1508
+ refreshTimeout() {
1509
+ this.clearTimeout();
1510
+ this.#timeout = setTimeout(() => {
1511
+ this.#resolve?.(this.#onResolve());
1512
+ }, this.#delay);
1513
+ }
1514
+ resolveWith(value) {
1515
+ this.#resolve?.(value);
1516
+ }
1517
+ setup() {
1518
+ return new Promise((resolve) => {
1519
+ this.#resolve = resolve;
1486
1520
  });
1487
1521
  }
1488
1522
  }
@@ -1491,16 +1525,33 @@ class WatchService {
1491
1525
  #changedTestFiles = new Map();
1492
1526
  #inputService;
1493
1527
  #resolvedConfig;
1494
- #runCallback;
1495
1528
  #selectService;
1496
- #timer = new Timer();
1497
1529
  #watchers = [];
1498
1530
  #watchedTestFiles;
1499
- constructor(resolvedConfig, runCallback, selectService, testFiles) {
1531
+ constructor(resolvedConfig, selectService, testFiles) {
1500
1532
  this.#resolvedConfig = resolvedConfig;
1501
- this.#runCallback = runCallback;
1502
1533
  this.#selectService = selectService;
1503
1534
  this.#watchedTestFiles = new Map(testFiles.map((testFile) => [testFile.path, testFile]));
1535
+ }
1536
+ #onDiagnostics(diagnostic) {
1537
+ EventEmitter.dispatch(["watch:error", { diagnostics: [diagnostic] }]);
1538
+ }
1539
+ async *watch(cancellationToken) {
1540
+ const onResolve = () => {
1541
+ const testFiles = [...this.#changedTestFiles.values()];
1542
+ this.#changedTestFiles.clear();
1543
+ return testFiles;
1544
+ };
1545
+ const debounce = new Debounce(100, onResolve);
1546
+ const onClose = (reason) => {
1547
+ debounce.clearTimeout();
1548
+ this.#inputService?.close();
1549
+ for (const watcher of this.#watchers) {
1550
+ watcher.close();
1551
+ }
1552
+ cancellationToken.cancel(reason);
1553
+ debounce.resolveWith([]);
1554
+ };
1504
1555
  const onInput = (chunk) => {
1505
1556
  switch (chunk.toLowerCase()) {
1506
1557
  case "\u0003":
@@ -1508,47 +1559,23 @@ class WatchService {
1508
1559
  case "\u001B":
1509
1560
  case "q":
1510
1561
  case "x": {
1511
- this.close();
1562
+ onClose("watchClose");
1512
1563
  break;
1513
1564
  }
1514
1565
  case "\u000D":
1515
1566
  case "\u0020":
1516
1567
  case "a": {
1517
- this.#runAll();
1568
+ debounce.clearTimeout();
1569
+ if (this.#watchedTestFiles.size !== 0) {
1570
+ debounce.resolveWith([...this.#watchedTestFiles.values()]);
1571
+ }
1518
1572
  break;
1519
1573
  }
1520
1574
  }
1521
1575
  };
1522
1576
  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
1577
  const onChangedFile = (filePath) => {
1578
+ debounce.refreshTimeout();
1552
1579
  let testFile = this.#watchedTestFiles.get(filePath);
1553
1580
  if (testFile != null) {
1554
1581
  this.#changedTestFiles.set(filePath, testFile);
@@ -1558,26 +1585,35 @@ class WatchService {
1558
1585
  this.#changedTestFiles.set(filePath, testFile);
1559
1586
  this.#watchedTestFiles.set(filePath, testFile);
1560
1587
  }
1561
- this.#runChanged();
1562
1588
  };
1563
1589
  const onRemovedFile = (filePath) => {
1564
1590
  this.#changedTestFiles.delete(filePath);
1565
1591
  this.#watchedTestFiles.delete(filePath);
1566
1592
  if (this.#watchedTestFiles.size === 0) {
1567
- this.#onDiagnostic(Diagnostic.error(SelectDiagnosticText.noTestFilesWereLeft(this.#resolvedConfig)));
1593
+ debounce.clearTimeout();
1594
+ this.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereLeft(this.#resolvedConfig)));
1568
1595
  }
1569
1596
  };
1570
1597
  this.#watchers.push(new Watcher(this.#resolvedConfig.rootPath, onChangedFile, onRemovedFile, { recursive: true }));
1571
1598
  const onChangedConfigFile = () => {
1572
- cancellationToken?.cancel("configChange");
1599
+ onClose("configChange");
1573
1600
  };
1574
1601
  this.#watchers.push(new FileWatcher(this.#resolvedConfig.configFilePath, onChangedConfigFile));
1575
- return Promise.all(this.#watchers.map((watcher) => watcher.watch()));
1602
+ for (const watcher of this.#watchers) {
1603
+ watcher.watch();
1604
+ }
1605
+ while (!cancellationToken.isCancellationRequested) {
1606
+ const testFiles = await debounce.setup();
1607
+ if (testFiles.length > 0) {
1608
+ yield testFiles;
1609
+ }
1610
+ }
1576
1611
  }
1577
1612
  }
1578
1613
 
1579
1614
  class TestMember {
1580
1615
  brand;
1616
+ #compiler;
1581
1617
  diagnostics = new Set();
1582
1618
  flags;
1583
1619
  members = [];
@@ -1586,6 +1622,7 @@ class TestMember {
1586
1622
  parent;
1587
1623
  constructor(compiler, brand, node, parent, flags) {
1588
1624
  this.brand = brand;
1625
+ this.#compiler = compiler;
1589
1626
  this.node = node;
1590
1627
  this.parent = parent;
1591
1628
  this.flags = flags;
@@ -1605,23 +1642,20 @@ class TestMember {
1605
1642
  }
1606
1643
  }
1607
1644
  }
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
1645
  validate() {
1618
1646
  const diagnostics = [];
1619
1647
  const getText = (node) => `'${node.expression.getText()}()' cannot be nested within '${this.node.expression.getText()}()'.`;
1648
+ const getParentCallExpression = (node) => {
1649
+ while (!this.#compiler.isCallExpression(node.parent)) {
1650
+ node = node.parent;
1651
+ }
1652
+ return node.parent;
1653
+ };
1620
1654
  switch (this.brand) {
1621
1655
  case "describe": {
1622
1656
  for (const member of this.members) {
1623
1657
  if (member.brand === "expect") {
1624
- diagnostics.push(Diagnostic.error(getText(member.node), DiagnosticOrigin.fromNode(member.node)));
1658
+ diagnostics.push(Diagnostic.error(getText(member.node), DiagnosticOrigin.fromNode(getParentCallExpression(member.node))));
1625
1659
  }
1626
1660
  }
1627
1661
  break;
@@ -1650,43 +1684,21 @@ class Assertion extends TestMember {
1650
1684
  this.isNot = notNode != null;
1651
1685
  this.matcherNode = matcherNode;
1652
1686
  this.modifierNode = modifierNode;
1653
- const argStart = this.source[0]?.getStart();
1654
- const argEnd = this.source[0]?.getEnd();
1655
1687
  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) {
1688
+ if (diagnostic.start != null && diagnostic.start >= this.source.pos && diagnostic.start <= this.source.end) {
1661
1689
  this.diagnostics.add(diagnostic);
1662
1690
  parent.diagnostics.delete(diagnostic);
1663
1691
  }
1664
1692
  }
1665
1693
  }
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
1694
  get matcherName() {
1677
1695
  return this.matcherNode.expression.name;
1678
1696
  }
1679
1697
  get source() {
1680
- if (this.node.typeArguments != null) {
1681
- return this.node.typeArguments;
1682
- }
1683
- return this.node.arguments;
1698
+ return this.node.typeArguments ?? this.node.arguments;
1684
1699
  }
1685
1700
  get target() {
1686
- if (this.matcherNode.typeArguments != null) {
1687
- return this.matcherNode.typeArguments;
1688
- }
1689
- return this.matcherNode.arguments;
1701
+ return this.matcherNode.typeArguments ?? this.matcherNode.arguments;
1690
1702
  }
1691
1703
  }
1692
1704
 
@@ -1707,12 +1719,6 @@ class IdentifierLookup {
1707
1719
  namespace: undefined,
1708
1720
  };
1709
1721
  }
1710
- clone() {
1711
- return {
1712
- namedImports: { ...this.#identifiers.namedImports },
1713
- namespace: this.#identifiers.namespace,
1714
- };
1715
- }
1716
1722
  handleImportDeclaration(node) {
1717
1723
  if (this.#moduleSpecifiers.includes(node.moduleSpecifier.getText()) &&
1718
1724
  node.importClause?.isTypeOnly !== true &&
@@ -1774,7 +1780,7 @@ class IdentifierLookup {
1774
1780
  else {
1775
1781
  identifierName = Object.keys(this.#identifiers.namedImports).find((key) => this.#identifiers.namedImports[key] === expression.getText());
1776
1782
  }
1777
- if (identifierName == null) {
1783
+ if (!identifierName) {
1778
1784
  return;
1779
1785
  }
1780
1786
  switch (identifierName) {
@@ -1808,40 +1814,10 @@ class TestTree {
1808
1814
 
1809
1815
  class CollectService {
1810
1816
  #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
1817
  constructor(compiler) {
1836
1818
  this.#compiler = compiler;
1837
1819
  }
1838
1820
  #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
1821
  if (this.#compiler.isCallExpression(node)) {
1846
1822
  const meta = identifiers.resolveTestMemberMeta(node);
1847
1823
  if (meta != null && (meta.brand === "describe" || meta.brand === "test")) {
@@ -1853,13 +1829,13 @@ class CollectService {
1853
1829
  return;
1854
1830
  }
1855
1831
  if (meta != null && meta.brand === "expect") {
1856
- const modifierNode = this.#getMatchingChainNode(node, this.#modifierIdentifiers);
1832
+ const modifierNode = this.#getChainedNode(node, "type");
1857
1833
  if (!modifierNode) {
1858
1834
  return;
1859
1835
  }
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)) {
1836
+ const notNode = this.#getChainedNode(modifierNode, "not");
1837
+ const matcherNode = this.#getChainedNode(notNode ?? modifierNode)?.parent;
1838
+ if (!matcherNode || !this.#isMatcherNode(matcherNode)) {
1863
1839
  return;
1864
1840
  }
1865
1841
  const assertion = new Assertion(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, modifierNode, notNode);
@@ -1874,8 +1850,6 @@ class CollectService {
1874
1850
  identifiers.handleImportDeclaration(node);
1875
1851
  return;
1876
1852
  }
1877
- if (this.#compiler.isVariableDeclaration(node)) ;
1878
- if (this.#compiler.isBinaryExpression(node)) ;
1879
1853
  this.#compiler.forEachChild(node, (node) => {
1880
1854
  this.#collectTestMembers(node, identifiers, parent);
1881
1855
  });
@@ -1885,11 +1859,14 @@ class CollectService {
1885
1859
  this.#collectTestMembers(sourceFile, new IdentifierLookup(this.#compiler), testTree);
1886
1860
  return testTree;
1887
1861
  }
1888
- #getMatchingChainNode({ parent }, name) {
1889
- if (this.#compiler.isPropertyAccessExpression(parent) && name.includes(parent.name.getText())) {
1890
- return parent;
1862
+ #getChainedNode({ parent }, name) {
1863
+ if (!this.#compiler.isPropertyAccessExpression(parent)) {
1864
+ return;
1891
1865
  }
1892
- return;
1866
+ if (name != null && name !== parent.name.getText()) {
1867
+ return;
1868
+ }
1869
+ return parent;
1893
1870
  }
1894
1871
  #isMatcherNode(node) {
1895
1872
  return this.#compiler.isCallExpression(node) && this.#compiler.isPropertyAccessExpression(node.expression);
@@ -1911,220 +1888,599 @@ var TestMemberFlags;
1911
1888
  TestMemberFlags[TestMemberFlags["Todo"] = 8] = "Todo";
1912
1889
  })(TestMemberFlags || (TestMemberFlags = {}));
1913
1890
 
1891
+ class ExpectDiagnosticText {
1892
+ static argumentOrTypeArgumentMustBeProvided(argumentNameText, typeArgumentNameText) {
1893
+ return `An argument for '${argumentNameText}' or type argument for '${typeArgumentNameText}' must be provided.`;
1894
+ }
1895
+ static argumentMustBe(argumentNameText, expectedText) {
1896
+ return `An argument for '${argumentNameText}' must be ${expectedText}.`;
1897
+ }
1898
+ static argumentMustBeProvided(argumentNameText) {
1899
+ return `An argument for '${argumentNameText}' must be provided.`;
1900
+ }
1901
+ static componentAcceptsProps(isTypeNode) {
1902
+ return `${isTypeNode ? "Component type" : "Component"} accepts props of the given type.`;
1903
+ }
1904
+ static componentDoesNotAcceptProps(isTypeNode) {
1905
+ return `${isTypeNode ? "Component type" : "Component"} does not accept props of the given type.`;
1906
+ }
1907
+ static matcherIsDeprecated(matcherNameText) {
1908
+ return [
1909
+ `The '.${matcherNameText}()' matcher is deprecated and will be removed in TSTyche 3.`,
1910
+ "To learn more, visit https://tstyche.org/releases/tstyche-2",
1911
+ ];
1912
+ }
1913
+ static matcherIsNotSupported(matcherNameText) {
1914
+ return `The '.${matcherNameText}()' matcher is not supported.`;
1915
+ }
1916
+ static overloadGaveTheFollowingError(indexText, countText, signatureText) {
1917
+ return `Overload ${indexText} of ${countText}, '${signatureText}', gave the following error.`;
1918
+ }
1919
+ static raisedTypeError(count = 1) {
1920
+ return `The raised type error${count === 1 ? "" : "s"}:`;
1921
+ }
1922
+ static typeArgumentMustBe(argumentNameText, expectedText) {
1923
+ return `A type argument for '${argumentNameText}' must be ${expectedText}.`;
1924
+ }
1925
+ static typeDidNotRaiseError(isTypeNode) {
1926
+ return `${isTypeNode ? "Type" : "Expression type"} did not raise a type error.`;
1927
+ }
1928
+ static typeDidNotRaiseMatchingError(isTypeNode) {
1929
+ return `${isTypeNode ? "Type" : "Expression type"} did not raise a matching type error.`;
1930
+ }
1931
+ static typeDoesNotHaveProperty(typeText, propertyNameText) {
1932
+ return `Type '${typeText}' does not have property '${propertyNameText}'.`;
1933
+ }
1934
+ static typeDoesMatch(sourceTypeText, targetTypeText) {
1935
+ return `Type '${sourceTypeText}' does match type '${targetTypeText}'.`;
1936
+ }
1937
+ static typeDoesNotMatch(sourceTypeText, targetTypeText) {
1938
+ return `Type '${sourceTypeText}' does not match type '${targetTypeText}'.`;
1939
+ }
1940
+ static typeHasProperty(typeText, propertyNameText) {
1941
+ return `Type '${typeText}' has property '${propertyNameText}'.`;
1942
+ }
1943
+ static typeIs(typeText) {
1944
+ return `Type is '${typeText}'.`;
1945
+ }
1946
+ static typeIsAssignableTo(sourceTypeText, targetTypeText) {
1947
+ return `Type '${sourceTypeText}' is assignable to type '${targetTypeText}'.`;
1948
+ }
1949
+ static typeIsAssignableWith(sourceTypeText, targetTypeText) {
1950
+ return `Type '${sourceTypeText}' is assignable with type '${targetTypeText}'.`;
1951
+ }
1952
+ static typeIsIdenticalTo(sourceTypeText, targetTypeText) {
1953
+ return `Type '${sourceTypeText}' is identical to type '${targetTypeText}'.`;
1954
+ }
1955
+ static typeIsNotAssignableTo(sourceTypeText, targetTypeText) {
1956
+ return `Type '${sourceTypeText}' is not assignable to type '${targetTypeText}'.`;
1957
+ }
1958
+ static typeIsNotAssignableWith(sourceTypeText, targetTypeText) {
1959
+ return `Type '${sourceTypeText}' is not assignable with type '${targetTypeText}'.`;
1960
+ }
1961
+ static typeIsNotCompatibleWith(sourceTypeText, targetTypeText) {
1962
+ return `Type '${sourceTypeText}' is not compatible with type '${targetTypeText}'.`;
1963
+ }
1964
+ static typeIsNotIdenticalTo(sourceTypeText, targetTypeText) {
1965
+ return `Type '${sourceTypeText}' is not identical to type '${targetTypeText}'.`;
1966
+ }
1967
+ static typeRaisedError(isTypeNode, count, targetCount) {
1968
+ let countText = "a";
1969
+ if (count > 1 || targetCount > 1) {
1970
+ countText = count > targetCount ? String(count) : `only ${String(count)}`;
1971
+ }
1972
+ return `${isTypeNode ? "Type" : "Expression type"} raised ${countText} type error${count === 1 ? "" : "s"}.`;
1973
+ }
1974
+ static typeRaisedMatchingError(isTypeNode) {
1975
+ return `${isTypeNode ? "Type" : "Expression type"} raised a matching type error.`;
1976
+ }
1977
+ static typeRequiresProperty(typeText, propertyNameText) {
1978
+ return `Type '${typeText}' requires property '${propertyNameText}'.`;
1979
+ }
1980
+ static typesOfPropertyAreNotCompatible(propertyNameText) {
1981
+ return `Types of property '${propertyNameText}' are not compatible.`;
1982
+ }
1983
+ }
1984
+
1985
+ class MatchWorker {
1986
+ assertion;
1987
+ #compiler;
1988
+ #signatureCache = new Map();
1989
+ #typeCache = new Map();
1990
+ #typeChecker;
1991
+ constructor(compiler, typeChecker, assertion) {
1992
+ this.#compiler = compiler;
1993
+ this.#typeChecker = typeChecker;
1994
+ this.assertion = assertion;
1995
+ }
1996
+ checkIsAssignableTo(sourceNode, targetNode) {
1997
+ const relation = this.#typeChecker.relation.assignable;
1998
+ return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
1999
+ }
2000
+ checkIsAssignableWith(sourceNode, targetNode) {
2001
+ const relation = this.#typeChecker.relation.assignable;
2002
+ return this.#checkIsRelatedTo(targetNode, sourceNode, relation);
2003
+ }
2004
+ checkIsIdenticalTo(sourceNode, targetNode) {
2005
+ const relation = this.#typeChecker.relation.identity;
2006
+ return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
2007
+ }
2008
+ checkIsSubtype(sourceNode, targetNode) {
2009
+ const relation = this.#typeChecker.relation.subtype;
2010
+ return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
2011
+ }
2012
+ #checkIsRelatedTo(sourceNode, targetNode, relation) {
2013
+ const sourceType = this.getType(sourceNode);
2014
+ const targetType = this.getType(targetNode);
2015
+ return this.#typeChecker.isTypeRelatedTo(sourceType, targetType, relation);
2016
+ }
2017
+ extendsObjectType(type) {
2018
+ const nonPrimitiveType = { flags: this.#compiler.TypeFlags.NonPrimitive };
2019
+ return this.#typeChecker.isTypeAssignableTo(type, nonPrimitiveType);
2020
+ }
2021
+ getParameterType(signature, index) {
2022
+ const parameter = signature.getDeclaration().parameters[index];
2023
+ if (!parameter) {
2024
+ return;
2025
+ }
2026
+ return this.#getTypeOfNode(parameter);
2027
+ }
2028
+ getSignatures(node) {
2029
+ let signatures = this.#signatureCache.get(node);
2030
+ if (!signatures) {
2031
+ const type = this.getType(node);
2032
+ signatures = type.getCallSignatures();
2033
+ if (signatures.length === 0) {
2034
+ signatures = type.getConstructSignatures();
2035
+ }
2036
+ }
2037
+ return signatures;
2038
+ }
2039
+ getTypeText(node) {
2040
+ const type = this.getType(node);
2041
+ return this.#typeChecker.typeToString(type);
2042
+ }
2043
+ getType(node) {
2044
+ return this.#compiler.isExpression(node) ? this.#getTypeOfNode(node) : this.#getTypeOfTypeNode(node);
2045
+ }
2046
+ #getTypeOfNode(node) {
2047
+ let type = this.#typeCache.get(node);
2048
+ if (!type) {
2049
+ type = this.#typeChecker.getTypeAtLocation(node);
2050
+ }
2051
+ return type;
2052
+ }
2053
+ #getTypeOfTypeNode(node) {
2054
+ let type = this.#typeCache.get(node);
2055
+ if (!type) {
2056
+ type = this.#typeChecker.getTypeFromTypeNode(node);
2057
+ }
2058
+ return type;
2059
+ }
2060
+ isAnyOrNeverType(type) {
2061
+ return Boolean(type.flags & (this.#compiler.TypeFlags.Any | this.#compiler.TypeFlags.Never));
2062
+ }
2063
+ isStringOrNumberLiteralType(type) {
2064
+ return Boolean(type.flags & this.#compiler.TypeFlags.StringOrNumberLiteral);
2065
+ }
2066
+ isObjectType(type) {
2067
+ return Boolean(type.flags & this.#compiler.TypeFlags.Object);
2068
+ }
2069
+ isUnionType(type) {
2070
+ return Boolean(type.flags & this.#compiler.TypeFlags.Union);
2071
+ }
2072
+ isUniqueSymbolType(type) {
2073
+ return Boolean(type.flags & this.#compiler.TypeFlags.UniqueESSymbol);
2074
+ }
2075
+ resolveDiagnosticOrigin(symbol, enclosingNode) {
2076
+ if (symbol.valueDeclaration != null &&
2077
+ (this.#compiler.isPropertySignature(symbol.valueDeclaration) ||
2078
+ this.#compiler.isPropertyAssignment(symbol.valueDeclaration) ||
2079
+ this.#compiler.isShorthandPropertyAssignment(symbol.valueDeclaration)) &&
2080
+ symbol.valueDeclaration.getStart() >= enclosingNode.getStart() &&
2081
+ symbol.valueDeclaration.getEnd() <= enclosingNode.getEnd()) {
2082
+ return DiagnosticOrigin.fromNode(symbol.valueDeclaration.name, this.assertion);
2083
+ }
2084
+ return DiagnosticOrigin.fromNode(enclosingNode, this.assertion);
2085
+ }
2086
+ }
2087
+
1914
2088
  class PrimitiveTypeMatcher {
1915
2089
  #targetTypeFlag;
1916
- typeChecker;
1917
- constructor(typeChecker, targetTypeFlag) {
1918
- this.typeChecker = typeChecker;
2090
+ constructor(targetTypeFlag) {
1919
2091
  this.#targetTypeFlag = targetTypeFlag;
1920
2092
  }
1921
- #explain(sourceType) {
1922
- const sourceTypeText = this.typeChecker.typeToString(sourceType);
1923
- return [Diagnostic.error(`The source type is '${sourceTypeText}'.`)];
2093
+ #explain(matchWorker, sourceNode) {
2094
+ const sourceTypeText = matchWorker.getTypeText(sourceNode);
2095
+ const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
2096
+ return [Diagnostic.error(ExpectDiagnosticText.typeIs(sourceTypeText), origin)];
1924
2097
  }
1925
- match(sourceType) {
2098
+ match(matchWorker, sourceNode) {
2099
+ const sourceType = matchWorker.getType(sourceNode);
1926
2100
  const isMatch = Boolean(sourceType.flags & this.#targetTypeFlag);
1927
2101
  return {
1928
- explain: () => this.#explain(sourceType),
2102
+ explain: () => this.#explain(matchWorker, sourceNode),
1929
2103
  isMatch,
1930
2104
  };
1931
2105
  }
1932
2106
  }
1933
2107
 
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
- ];
2108
+ class ToAcceptProps {
2109
+ #compiler;
2110
+ #typeChecker;
2111
+ constructor(compiler, typeChecker) {
2112
+ this.#compiler = compiler;
2113
+ this.#typeChecker = typeChecker;
2114
+ }
2115
+ #explain(matchWorker, sourceNode, targetNode) {
2116
+ const signatures = matchWorker.getSignatures(sourceNode);
2117
+ return signatures.reduce((accumulator, signature, index) => {
2118
+ let diagnostic;
2119
+ const introText = matchWorker.assertion.isNot
2120
+ ? ExpectDiagnosticText.componentAcceptsProps(this.#compiler.isTypeNode(sourceNode))
2121
+ : ExpectDiagnosticText.componentDoesNotAcceptProps(this.#compiler.isTypeNode(sourceNode));
2122
+ const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
2123
+ if (signatures.length > 1) {
2124
+ const signatureText = this.#typeChecker.signatureToString(signature, sourceNode);
2125
+ const overloadText = ExpectDiagnosticText.overloadGaveTheFollowingError(String(index + 1), String(signatures.length), signatureText);
2126
+ diagnostic = Diagnostic.error([introText, overloadText], origin);
2127
+ }
2128
+ else {
2129
+ diagnostic = Diagnostic.error([introText], origin);
2130
+ }
2131
+ const { diagnostics, isMatch } = this.#explainProperties(matchWorker, signature, targetNode, diagnostic);
2132
+ if (matchWorker.assertion.isNot ? isMatch : !isMatch) {
2133
+ accumulator.push(...diagnostics);
2134
+ }
2135
+ return accumulator;
2136
+ }, []);
2137
+ }
2138
+ #isOptionalProperty(symbol) {
2139
+ return symbol.declarations?.every((declaration) => this.#compiler.isPropertySignature(declaration) && declaration.questionToken != null);
2140
+ }
2141
+ #checkProperties(matchWorker, sourceType, targetType) {
2142
+ const check = (sourceType, targetType) => {
2143
+ for (const targetProperty of targetType.getProperties()) {
2144
+ const targetPropertyName = targetProperty.getName();
2145
+ const sourceProperty = sourceType?.getProperty(targetPropertyName);
2146
+ if (!sourceProperty) {
2147
+ return false;
2148
+ }
2149
+ if (this.#isOptionalProperty(targetProperty) && !this.#isOptionalProperty(sourceProperty)) {
2150
+ return false;
2151
+ }
2152
+ const targetPropertyType = this.#typeChecker.getTypeOfSymbol(targetProperty);
2153
+ const sourcePropertyType = this.#typeChecker.getTypeOfSymbol(sourceProperty);
2154
+ if (!this.#typeChecker.isTypeAssignableTo(targetPropertyType, sourcePropertyType)) {
2155
+ return false;
2156
+ }
2157
+ }
2158
+ if (sourceType != null) {
2159
+ const sourceProperties = sourceType.getProperties();
2160
+ for (const sourceProperty of sourceProperties) {
2161
+ const targetProperty = targetType.getProperty(sourceProperty.getName());
2162
+ if (!targetProperty && !this.#isOptionalProperty(sourceProperty)) {
2163
+ return false;
2164
+ }
2165
+ }
2166
+ }
2167
+ return true;
2168
+ };
2169
+ if (sourceType != null && matchWorker.isUnionType(sourceType)) {
2170
+ return sourceType.types.some((sourceType) => check(sourceType, targetType));
2171
+ }
2172
+ return check(sourceType, targetType);
2173
+ }
2174
+ #explainProperties(matchWorker, signature, targetNode, diagnostic) {
2175
+ const sourceType = matchWorker.getParameterType(signature, 0);
2176
+ const sourceTypeText = sourceType != null ? this.#typeChecker.typeToString(sourceType) : "{}";
2177
+ const targetType = matchWorker.getType(targetNode);
2178
+ const targetTypeText = this.#typeChecker.typeToString(targetType);
2179
+ const explain = (sourceType, targetType, diagnostic) => {
2180
+ const sourceTypeText = sourceType != null ? this.#typeChecker.typeToString(sourceType) : "{}";
2181
+ const diagnostics = [];
2182
+ for (const targetProperty of targetType.getProperties()) {
2183
+ const targetPropertyName = targetProperty.getName();
2184
+ const sourceProperty = sourceType?.getProperty(targetPropertyName);
2185
+ if (!sourceProperty) {
2186
+ const text = [
2187
+ ExpectDiagnosticText.typeIsNotCompatibleWith(sourceTypeText, targetTypeText),
2188
+ ExpectDiagnosticText.typeDoesNotHaveProperty(sourceTypeText, targetPropertyName),
2189
+ ];
2190
+ const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
2191
+ diagnostics.push(diagnostic.extendWith(text, origin));
2192
+ continue;
2193
+ }
2194
+ if (this.#isOptionalProperty(targetProperty) && !this.#isOptionalProperty(sourceProperty)) {
2195
+ const text = [
2196
+ ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText),
2197
+ ExpectDiagnosticText.typeRequiresProperty(sourceTypeText, targetPropertyName),
2198
+ ];
2199
+ const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
2200
+ diagnostics.push(diagnostic.extendWith(text, origin));
2201
+ continue;
2202
+ }
2203
+ const targetPropertyType = this.#typeChecker.getTypeOfSymbol(targetProperty);
2204
+ const sourcePropertyType = this.#typeChecker.getTypeOfSymbol(sourceProperty);
2205
+ if (!this.#typeChecker.isTypeAssignableTo(targetPropertyType, sourcePropertyType)) {
2206
+ const targetPropertyTypeText = this.#typeChecker.typeToString(targetPropertyType);
2207
+ const sourcePropertyTypeText = this.#typeChecker.typeToString(sourcePropertyType);
2208
+ const text = [
2209
+ ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText),
2210
+ ExpectDiagnosticText.typesOfPropertyAreNotCompatible(targetPropertyName),
2211
+ ExpectDiagnosticText.typeIsNotAssignableWith(sourcePropertyTypeText, targetPropertyTypeText),
2212
+ ];
2213
+ const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
2214
+ diagnostics.push(diagnostic.extendWith(text, origin));
2215
+ }
2216
+ }
2217
+ if (sourceType != null) {
2218
+ for (const sourceProperty of sourceType.getProperties()) {
2219
+ const sourcePropertyName = sourceProperty.getName();
2220
+ const targetProperty = targetType.getProperty(sourcePropertyName);
2221
+ if (!targetProperty && !this.#isOptionalProperty(sourceProperty)) {
2222
+ const text = [
2223
+ ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText),
2224
+ ExpectDiagnosticText.typeRequiresProperty(sourceTypeText, sourcePropertyName),
2225
+ ];
2226
+ diagnostics.push(diagnostic.extendWith(text));
2227
+ }
2228
+ }
2229
+ }
2230
+ if (diagnostics.length === 0) {
2231
+ const text = ExpectDiagnosticText.typeIsAssignableWith(sourceTypeText, targetTypeText);
2232
+ diagnostics.push(diagnostic.extendWith(text));
2233
+ return { diagnostics, isMatch: true };
2234
+ }
2235
+ return { diagnostics, isMatch: false };
2236
+ };
2237
+ if (sourceType != null && matchWorker.isUnionType(sourceType)) {
2238
+ let accumulator = [];
2239
+ const isMatch = sourceType.types.some((sourceType) => {
2240
+ const text = matchWorker.assertion.isNot
2241
+ ? ExpectDiagnosticText.typeIsAssignableWith(sourceTypeText, targetTypeText)
2242
+ : ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText);
2243
+ const { diagnostics, isMatch } = explain(sourceType, targetType, diagnostic.extendWith(text));
2244
+ if (isMatch) {
2245
+ accumulator = diagnostics;
2246
+ }
2247
+ else {
2248
+ accumulator.push(...diagnostics);
2249
+ }
2250
+ return isMatch;
2251
+ });
2252
+ return { diagnostics: accumulator, isMatch };
2253
+ }
2254
+ return explain(sourceType, targetType, diagnostic);
1950
2255
  }
1951
- match(sourceType, targetType, isNot) {
1952
- const isMatch = this.typeChecker.isTypeRelatedTo(sourceType, targetType, this.relation);
2256
+ match(matchWorker, sourceNode, targetNode, onDiagnostics) {
2257
+ const diagnostics = [];
2258
+ const signatures = matchWorker.getSignatures(sourceNode);
2259
+ if (signatures.length === 0) {
2260
+ const expectedText = "of a function or class type";
2261
+ const text = this.#compiler.isTypeNode(sourceNode)
2262
+ ? ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText)
2263
+ : ExpectDiagnosticText.argumentMustBe("source", expectedText);
2264
+ const origin = DiagnosticOrigin.fromNode(sourceNode);
2265
+ diagnostics.push(Diagnostic.error(text, origin));
2266
+ }
2267
+ const targetType = matchWorker.getType(targetNode);
2268
+ if (!matchWorker.isObjectType(targetType)) {
2269
+ const expectedText = "of an object type";
2270
+ const text = this.#compiler.isTypeNode(targetNode)
2271
+ ? ExpectDiagnosticText.typeArgumentMustBe("Target", expectedText)
2272
+ : ExpectDiagnosticText.argumentMustBe("target", expectedText);
2273
+ const origin = DiagnosticOrigin.fromNode(targetNode);
2274
+ diagnostics.push(Diagnostic.error(text, origin));
2275
+ }
2276
+ if (diagnostics.length > 0) {
2277
+ onDiagnostics(diagnostics);
2278
+ return;
2279
+ }
2280
+ const isMatch = signatures.some((signature) => {
2281
+ const sourceType = matchWorker.getParameterType(signature, 0);
2282
+ return this.#checkProperties(matchWorker, sourceType, targetType);
2283
+ });
1953
2284
  return {
1954
- explain: () => this.explain(sourceType, targetType, isNot),
2285
+ explain: () => this.#explain(matchWorker, sourceNode, targetNode),
1955
2286
  isMatch,
1956
2287
  };
1957
2288
  }
1958
2289
  }
1959
2290
 
2291
+ class RelationMatcherBase {
2292
+ explain(matchWorker, sourceNode, targetNode) {
2293
+ const sourceTypeText = matchWorker.getTypeText(sourceNode);
2294
+ const targetTypeText = matchWorker.getTypeText(targetNode);
2295
+ const text = matchWorker.assertion.isNot
2296
+ ? this.explainText(sourceTypeText, targetTypeText)
2297
+ : this.explainNotText(sourceTypeText, targetTypeText);
2298
+ const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
2299
+ return [Diagnostic.error(text, origin)];
2300
+ }
2301
+ }
2302
+
1960
2303
  class ToBe extends RelationMatcherBase {
1961
- relation = this.typeChecker.relation.identity;
1962
- relationExplanationText = "identical to";
2304
+ explainText = ExpectDiagnosticText.typeIsIdenticalTo;
2305
+ explainNotText = ExpectDiagnosticText.typeIsNotIdenticalTo;
2306
+ match(matchWorker, sourceNode, targetNode) {
2307
+ return {
2308
+ explain: () => this.explain(matchWorker, sourceNode, targetNode),
2309
+ isMatch: matchWorker.checkIsIdenticalTo(sourceNode, targetNode),
2310
+ };
2311
+ }
1963
2312
  }
1964
2313
 
1965
2314
  class ToBeAssignableTo extends RelationMatcherBase {
1966
- relation = this.typeChecker.relation.assignable;
1967
- relationExplanationText = "assignable to";
2315
+ explainText = ExpectDiagnosticText.typeIsAssignableTo;
2316
+ explainNotText = ExpectDiagnosticText.typeIsNotAssignableTo;
2317
+ match(matchWorker, sourceNode, targetNode) {
2318
+ return {
2319
+ explain: () => this.explain(matchWorker, sourceNode, targetNode),
2320
+ isMatch: matchWorker.checkIsAssignableTo(sourceNode, targetNode),
2321
+ };
2322
+ }
1968
2323
  }
1969
2324
 
1970
2325
  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);
2326
+ explainText = ExpectDiagnosticText.typeIsAssignableWith;
2327
+ explainNotText = ExpectDiagnosticText.typeIsNotAssignableWith;
2328
+ match(matchWorker, sourceNode, targetNode) {
1975
2329
  return {
1976
- explain: () => this.explain(sourceType, targetType, isNot),
1977
- isMatch,
2330
+ explain: () => this.explain(matchWorker, sourceNode, targetNode),
2331
+ isMatch: matchWorker.checkIsAssignableWith(sourceNode, targetNode),
1978
2332
  };
1979
2333
  }
1980
2334
  }
1981
2335
 
1982
2336
  class ToHaveProperty {
1983
- compiler;
1984
- typeChecker;
1985
- constructor(compiler, typeChecker) {
1986
- this.compiler = compiler;
1987
- this.typeChecker = typeChecker;
2337
+ #compiler;
2338
+ constructor(compiler) {
2339
+ this.#compiler = compiler;
1988
2340
  }
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);
2341
+ #explain(matchWorker, sourceNode, targetNode) {
2342
+ const sourceTypeText = matchWorker.getTypeText(sourceNode);
2343
+ const targetType = matchWorker.getType(targetNode);
2344
+ let propertyNameText;
2345
+ if (matchWorker.isStringOrNumberLiteralType(targetType)) {
2346
+ propertyNameText = String(targetType.value);
1994
2347
  }
1995
2348
  else {
1996
- targetArgumentText = `[${this.compiler.unescapeLeadingUnderscores(targetType.symbol.escapedName)}]`;
2349
+ propertyNameText = `[${this.#compiler.unescapeLeadingUnderscores(targetType.symbol.escapedName)}]`;
1997
2350
  }
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);
2351
+ const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
2352
+ return matchWorker.assertion.isNot
2353
+ ? [Diagnostic.error(ExpectDiagnosticText.typeHasProperty(sourceTypeText, propertyNameText), origin)]
2354
+ : [Diagnostic.error(ExpectDiagnosticText.typeDoesNotHaveProperty(sourceTypeText, propertyNameText), origin)];
2004
2355
  }
2005
- match(sourceType, targetType, isNot) {
2006
- let targetArgumentText;
2007
- if (this.#isStringOrNumberLiteralType(targetType)) {
2008
- targetArgumentText = String(targetType.value);
2356
+ match(matchWorker, sourceNode, targetNode, onDiagnostics) {
2357
+ const diagnostics = [];
2358
+ const sourceType = matchWorker.getType(sourceNode);
2359
+ if (matchWorker.isAnyOrNeverType(sourceType) || !matchWorker.extendsObjectType(sourceType)) {
2360
+ const expectedText = "of an object type";
2361
+ const text = this.#compiler.isTypeNode(sourceNode)
2362
+ ? ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText)
2363
+ : ExpectDiagnosticText.argumentMustBe("source", expectedText);
2364
+ const origin = DiagnosticOrigin.fromNode(sourceNode);
2365
+ diagnostics.push(Diagnostic.error(text, origin));
2366
+ }
2367
+ const targetType = matchWorker.getType(targetNode);
2368
+ let propertyNameText;
2369
+ if (matchWorker.isStringOrNumberLiteralType(targetType)) {
2370
+ propertyNameText = String(targetType.value);
2371
+ }
2372
+ else if (matchWorker.isUniqueSymbolType(targetType)) {
2373
+ propertyNameText = this.#compiler.unescapeLeadingUnderscores(targetType.escapedName);
2009
2374
  }
2010
2375
  else {
2011
- targetArgumentText = this.compiler.unescapeLeadingUnderscores(targetType.escapedName);
2376
+ const expectedText = "of type 'string | number | symbol'";
2377
+ const text = ExpectDiagnosticText.argumentMustBe("key", expectedText);
2378
+ const origin = DiagnosticOrigin.fromNode(targetNode);
2379
+ diagnostics.push(Diagnostic.error(text, origin));
2380
+ }
2381
+ if (diagnostics.length > 0) {
2382
+ onDiagnostics(diagnostics);
2383
+ return;
2012
2384
  }
2013
2385
  const isMatch = sourceType.getProperties().some((property) => {
2014
- return this.compiler.unescapeLeadingUnderscores(property.escapedName) === targetArgumentText;
2386
+ return this.#compiler.unescapeLeadingUnderscores(property.escapedName) === propertyNameText;
2015
2387
  });
2016
2388
  return {
2017
- explain: () => this.#explain(sourceType, targetType, isNot),
2389
+ explain: () => this.#explain(matchWorker, sourceNode, targetNode),
2018
2390
  isMatch,
2019
2391
  };
2020
2392
  }
2021
2393
  }
2022
2394
 
2023
2395
  class ToMatch extends RelationMatcherBase {
2024
- relation = this.typeChecker.relation.subtype;
2025
- relationExplanationText = "match";
2026
- relationExplanationVerb = "does";
2396
+ explainText = ExpectDiagnosticText.typeDoesMatch;
2397
+ explainNotText = ExpectDiagnosticText.typeDoesNotMatch;
2398
+ match(matchWorker, sourceNode, targetNode) {
2399
+ return {
2400
+ explain: () => this.explain(matchWorker, sourceNode, targetNode),
2401
+ isMatch: matchWorker.checkIsSubtype(sourceNode, targetNode),
2402
+ };
2403
+ }
2027
2404
  }
2028
2405
 
2029
2406
  class ToRaiseError {
2030
- compiler;
2031
- typeChecker;
2032
- constructor(compiler, typeChecker) {
2033
- this.compiler = compiler;
2034
- this.typeChecker = typeChecker;
2407
+ #compiler;
2408
+ constructor(compiler) {
2409
+ this.#compiler = compiler;
2035
2410
  }
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.`)];
2411
+ #explain(matchWorker, sourceNode, targetNodes) {
2412
+ const isTypeNode = this.#compiler.isTypeNode(sourceNode);
2413
+ const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
2414
+ if (matchWorker.assertion.diagnostics.size === 0) {
2415
+ const text = ExpectDiagnosticText.typeDidNotRaiseError(isTypeNode);
2416
+ return [Diagnostic.error(text, origin)];
2040
2417
  }
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)}`;
2418
+ if (matchWorker.assertion.diagnostics.size !== targetNodes.length) {
2419
+ const count = matchWorker.assertion.diagnostics.size;
2420
+ const text = ExpectDiagnosticText.typeRaisedError(isTypeNode, count, targetNodes.length);
2056
2421
  const related = [
2057
- Diagnostic.error(`The raised type error${source.diagnostics.length === 1 ? "" : "s"}:`),
2058
- ...Diagnostic.fromDiagnostics(source.diagnostics, this.compiler),
2422
+ Diagnostic.error(ExpectDiagnosticText.raisedTypeError(count)),
2423
+ ...Diagnostic.fromDiagnostics([...matchWorker.assertion.diagnostics], this.#compiler),
2059
2424
  ];
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)}`;
2425
+ return [Diagnostic.error(text, origin).add({ related })];
2426
+ }
2427
+ return [...matchWorker.assertion.diagnostics].reduce((accumulator, diagnostic, index) => {
2428
+ const targetNode = targetNodes[index];
2429
+ const isMatch = this.#matchExpectedError(diagnostic, targetNode);
2430
+ if (matchWorker.assertion.isNot ? isMatch : !isMatch) {
2431
+ const text = matchWorker.assertion.isNot
2432
+ ? ExpectDiagnosticText.typeRaisedMatchingError(isTypeNode)
2433
+ : ExpectDiagnosticText.typeDidNotRaiseMatchingError(isTypeNode);
2434
+ const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
2074
2435
  const related = [
2075
- Diagnostic.error("The raised type error:"),
2076
- ...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
2436
+ Diagnostic.error(ExpectDiagnosticText.raisedTypeError()),
2437
+ ...Diagnostic.fromDiagnostics([diagnostic], this.#compiler),
2077
2438
  ];
2078
- const text = `${sourceText} did not raise a type error ${expectedText}.`;
2079
- diagnostics.push(Diagnostic.error(text).add({ related }));
2439
+ accumulator.push(Diagnostic.error(text, origin).add({ related }));
2080
2440
  }
2081
- if (isNot && isMatch) {
2082
- const expectedText = this.#isStringLiteralType(argument)
2083
- ? `matching substring '${argument.value}'`
2084
- : `with code ${String(argument.value)}`;
2085
- const related = [
2086
- Diagnostic.error("The raised type error:"),
2087
- ...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
2088
- ];
2089
- const text = `${sourceText} raised a type error ${expectedText}.`;
2090
- diagnostics.push(Diagnostic.error(text).add({ related }));
2091
- }
2092
- });
2093
- return diagnostics;
2094
- }
2095
- #isStringLiteralType(type) {
2096
- return Boolean(type.flags & this.compiler.TypeFlags.StringLiteral);
2441
+ return accumulator;
2442
+ }, []);
2097
2443
  }
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
- };
2444
+ match(matchWorker, sourceNode, targetNodes, onDiagnostics) {
2445
+ const diagnostics = [];
2446
+ for (const targetNode of targetNodes) {
2447
+ if (!(this.#compiler.isStringLiteralLike(targetNode) || this.#compiler.isNumericLiteral(targetNode))) {
2448
+ const expectedText = "a string or number literal";
2449
+ const text = ExpectDiagnosticText.argumentMustBe("target", expectedText);
2450
+ const origin = DiagnosticOrigin.fromNode(targetNode);
2451
+ diagnostics.push(Diagnostic.error(text, origin));
2452
+ }
2105
2453
  }
2106
- if (source.diagnostics.length !== targetTypes.length) {
2107
- return {
2108
- explain,
2109
- isMatch: false,
2110
- };
2454
+ if (diagnostics.length > 0) {
2455
+ onDiagnostics(diagnostics);
2456
+ return;
2457
+ }
2458
+ let isMatch;
2459
+ if (targetNodes.length === 0) {
2460
+ isMatch = matchWorker.assertion.diagnostics.size > 0;
2461
+ }
2462
+ else {
2463
+ isMatch =
2464
+ matchWorker.assertion.diagnostics.size === targetNodes.length &&
2465
+ [...matchWorker.assertion.diagnostics].every((diagnostic, index) => this.#matchExpectedError(diagnostic, targetNodes[index]));
2111
2466
  }
2112
2467
  return {
2113
- explain,
2114
- isMatch: targetTypes.every((type, index) => this.#matchExpectedError(source.diagnostics[index], type)),
2468
+ explain: () => this.#explain(matchWorker, sourceNode, targetNodes),
2469
+ isMatch,
2115
2470
  };
2116
2471
  }
2117
- #matchExpectedError(diagnostic, type) {
2118
- if (this.#isStringLiteralType(type)) {
2119
- return this.compiler.flattenDiagnosticMessageText(diagnostic?.messageText, " ", 0).includes(type.value);
2472
+ #matchExpectedError(diagnostic, targetNode) {
2473
+ if (this.#compiler.isStringLiteralLike(targetNode)) {
2474
+ return this.#compiler.flattenDiagnosticMessageText(diagnostic.messageText, " ", 0).includes(targetNode.text);
2120
2475
  }
2121
- return type.value === diagnostic?.code;
2476
+ return Number(targetNode.text) === diagnostic.code;
2122
2477
  }
2123
2478
  }
2124
2479
 
2125
- class Expect {
2480
+ class ExpectService {
2126
2481
  #compiler;
2127
2482
  #typeChecker;
2483
+ toAcceptProps;
2128
2484
  toBe;
2129
2485
  toBeAny;
2130
2486
  toBeAssignable;
@@ -2148,66 +2504,63 @@ class Expect {
2148
2504
  constructor(compiler, typeChecker) {
2149
2505
  this.#compiler = compiler;
2150
2506
  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);
2507
+ this.toAcceptProps = new ToAcceptProps(compiler, typeChecker);
2508
+ this.toBe = new ToBe();
2509
+ this.toBeAny = new PrimitiveTypeMatcher(compiler.TypeFlags.Any);
2510
+ this.toBeAssignable = new ToBeAssignableWith();
2511
+ this.toBeAssignableTo = new ToBeAssignableTo();
2512
+ this.toBeAssignableWith = new ToBeAssignableWith();
2513
+ this.toBeBigInt = new PrimitiveTypeMatcher(compiler.TypeFlags.BigInt);
2514
+ this.toBeBoolean = new PrimitiveTypeMatcher(compiler.TypeFlags.Boolean);
2515
+ this.toBeNever = new PrimitiveTypeMatcher(compiler.TypeFlags.Never);
2516
+ this.toBeNull = new PrimitiveTypeMatcher(compiler.TypeFlags.Null);
2517
+ this.toBeNumber = new PrimitiveTypeMatcher(compiler.TypeFlags.Number);
2518
+ this.toBeString = new PrimitiveTypeMatcher(compiler.TypeFlags.String);
2519
+ this.toBeSymbol = new PrimitiveTypeMatcher(compiler.TypeFlags.ESSymbol);
2520
+ this.toBeUndefined = new PrimitiveTypeMatcher(compiler.TypeFlags.Undefined);
2521
+ this.toBeUniqueSymbol = new PrimitiveTypeMatcher(compiler.TypeFlags.UniqueESSymbol);
2522
+ this.toBeUnknown = new PrimitiveTypeMatcher(compiler.TypeFlags.Unknown);
2523
+ this.toBeVoid = new PrimitiveTypeMatcher(compiler.TypeFlags.Void);
2524
+ this.toEqual = new ToBe();
2525
+ this.toHaveProperty = new ToHaveProperty(compiler);
2526
+ this.toMatch = new ToMatch();
2527
+ this.toRaiseError = new ToRaiseError(compiler);
2171
2528
  }
2172
2529
  static assertTypeChecker(typeChecker) {
2173
2530
  return "isTypeRelatedTo" in typeChecker && "relation" in typeChecker;
2174
2531
  }
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);
2532
+ #handleDeprecated(matcherNameText, assertion) {
2533
+ switch (matcherNameText) {
2534
+ case "toBeAssignable":
2535
+ case "toEqual": {
2536
+ const text = ExpectDiagnosticText.matcherIsDeprecated(matcherNameText);
2537
+ const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
2538
+ EventEmitter.dispatch(["deprecation:info", { diagnostics: [Diagnostic.warning(text, origin)] }]);
2539
+ break;
2540
+ }
2541
+ }
2191
2542
  }
2192
- match(assertion, expectResult) {
2543
+ match(assertion, onDiagnostics) {
2193
2544
  const matcherNameText = assertion.matcherName.getText();
2545
+ this.#handleDeprecated(matcherNameText, assertion);
2546
+ if (!assertion.source[0]) {
2547
+ this.#onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
2548
+ return;
2549
+ }
2550
+ const matchWorker = new MatchWorker(this.#compiler, this.#typeChecker, assertion);
2194
2551
  switch (matcherNameText) {
2195
- case "toBeAssignable":
2196
- case "toEqual":
2197
- this.#onDeprecatedMatcher(assertion);
2552
+ case "toAcceptProps":
2198
2553
  case "toBe":
2554
+ case "toBeAssignable":
2199
2555
  case "toBeAssignableTo":
2200
2556
  case "toBeAssignableWith":
2557
+ case "toEqual":
2201
2558
  case "toMatch": {
2202
- if (assertion.source[0] == null) {
2203
- this.#onSourceArgumentMustBeProvided(assertion, expectResult);
2204
- return;
2205
- }
2206
- if (assertion.target[0] == null) {
2207
- this.#onTargetArgumentMustBeProvided(assertion, expectResult);
2559
+ if (!assertion.target[0]) {
2560
+ this.#onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
2208
2561
  return;
2209
2562
  }
2210
- return this[matcherNameText].match(this.#getType(assertion.source[0]), this.#getType(assertion.target[0]), assertion.isNot);
2563
+ return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
2211
2564
  }
2212
2565
  case "toBeAny":
2213
2566
  case "toBeBigInt":
@@ -2221,131 +2574,40 @@ class Expect {
2221
2574
  case "toBeUniqueSymbol":
2222
2575
  case "toBeUnknown":
2223
2576
  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]));
2577
+ return this[matcherNameText].match(matchWorker, assertion.source[0]);
2229
2578
  }
2230
2579
  case "toHaveProperty": {
2231
- if (assertion.source[0] == null) {
2232
- this.#onSourceArgumentMustBeProvided(assertion, expectResult);
2580
+ if (!assertion.target[0]) {
2581
+ this.#onTargetArgumentMustBeProvided("key", assertion, onDiagnostics);
2233
2582
  return;
2234
2583
  }
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);
2240
- return;
2241
- }
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);
2584
+ return this.toHaveProperty.match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
2252
2585
  }
2253
2586
  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);
2587
+ return this.toRaiseError.match(matchWorker, assertion.source[0], [...assertion.target], onDiagnostics);
2264
2588
  }
2265
2589
  default: {
2266
- this.#onNotSupportedMatcherName(assertion, expectResult);
2267
- return;
2590
+ const text = ExpectDiagnosticText.matcherIsNotSupported(matcherNameText);
2591
+ const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
2592
+ onDiagnostics(Diagnostic.error(text, origin));
2268
2593
  }
2269
2594
  }
2595
+ return;
2270
2596
  }
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 }]);
2597
+ #onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics) {
2598
+ const text = ExpectDiagnosticText.argumentOrTypeArgumentMustBeProvided("source", "Source");
2599
+ const origin = DiagnosticOrigin.fromNode(assertion.node.expression);
2600
+ onDiagnostics(Diagnostic.error(text, origin));
2285
2601
  }
2286
- #onKeyArgumentMustBeProvided(assertion, expectResult) {
2602
+ #onTargetArgumentMustBeProvided(argumentNameText, assertion, onDiagnostics) {
2603
+ const text = ExpectDiagnosticText.argumentMustBeProvided(argumentNameText);
2287
2604
  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
- ]);
2605
+ onDiagnostics(Diagnostic.error(text, origin));
2295
2606
  }
2296
- #onNotSupportedMatcherName(assertion, expectResult) {
2297
- const matcherNameText = assertion.matcherName.getText();
2607
+ #onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics) {
2608
+ const text = ExpectDiagnosticText.argumentOrTypeArgumentMustBeProvided("target", "Target");
2298
2609
  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
- ]);
2325
- }
2326
- #onTargetArgumentMustBeProvided(assertion, expectResult) {
2327
- 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 }]);
2610
+ onDiagnostics(Diagnostic.error(text, origin));
2349
2611
  }
2350
2612
  }
2351
2613
 
@@ -2445,7 +2707,7 @@ class ProjectService {
2445
2707
  defaultCompilerOptions.module = "preserve";
2446
2708
  }
2447
2709
  if (Version.isSatisfiedWith(this.#compiler.version, "5.0")) {
2448
- defaultCompilerOptions["allowImportingTsExtensions"] = true;
2710
+ defaultCompilerOptions.allowImportingTsExtensions = true;
2449
2711
  defaultCompilerOptions.moduleResolution = "bundler";
2450
2712
  }
2451
2713
  return defaultCompilerOptions;
@@ -2478,19 +2740,19 @@ class ProjectService {
2478
2740
  class TestTreeWorker {
2479
2741
  #compiler;
2480
2742
  #cancellationToken;
2481
- #expect;
2743
+ #expectService;
2482
2744
  #fileResult;
2483
2745
  #hasOnly;
2484
2746
  #position;
2485
2747
  #resolvedConfig;
2486
- constructor(resolvedConfig, compiler, expect, options) {
2748
+ constructor(resolvedConfig, compiler, typeChecker, options) {
2487
2749
  this.#resolvedConfig = resolvedConfig;
2488
2750
  this.#compiler = compiler;
2489
- this.#expect = expect;
2490
2751
  this.#cancellationToken = options.cancellationToken;
2491
2752
  this.#fileResult = options.fileResult;
2492
2753
  this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
2493
2754
  this.#position = options.position;
2755
+ this.#expectService = new ExpectService(compiler, typeChecker);
2494
2756
  }
2495
2757
  #resolveRunMode(mode, member) {
2496
2758
  if (member.flags & 1) {
@@ -2520,13 +2782,7 @@ class TestTreeWorker {
2520
2782
  }
2521
2783
  const validationError = member.validate();
2522
2784
  if (validationError.length > 0) {
2523
- EventEmitter.dispatch([
2524
- "file:error",
2525
- {
2526
- diagnostics: validationError,
2527
- result: this.#fileResult,
2528
- },
2529
- ]);
2785
+ EventEmitter.dispatch(["file:error", { diagnostics: validationError, result: this.#fileResult }]);
2530
2786
  break;
2531
2787
  }
2532
2788
  switch (member.brand) {
@@ -2554,28 +2810,25 @@ class TestTreeWorker {
2554
2810
  EventEmitter.dispatch(["expect:skip", { result: expectResult }]);
2555
2811
  return;
2556
2812
  }
2557
- if (assertion.diagnostics.size > 0 && assertion.matcherName.getText() !== "toRaiseError") {
2813
+ const onExpectDiagnostics = (diagnostics) => {
2558
2814
  EventEmitter.dispatch([
2559
2815
  "expect:error",
2560
- {
2561
- diagnostics: Diagnostic.fromDiagnostics([...assertion.diagnostics], this.#compiler),
2562
- result: expectResult,
2563
- },
2816
+ { diagnostics: Array.isArray(diagnostics) ? diagnostics : [diagnostics], result: expectResult },
2564
2817
  ]);
2818
+ };
2819
+ if (assertion.diagnostics.size > 0 && assertion.matcherName.getText() !== "toRaiseError") {
2820
+ onExpectDiagnostics(Diagnostic.fromDiagnostics([...assertion.diagnostics], this.#compiler));
2565
2821
  return;
2566
2822
  }
2567
- const matchResult = this.#expect.match(assertion, expectResult);
2568
- if (matchResult == null) {
2823
+ const matchResult = this.#expectService.match(assertion, onExpectDiagnostics);
2824
+ if (!matchResult) {
2569
2825
  return;
2570
2826
  }
2571
2827
  if (assertion.isNot ? !matchResult.isMatch : matchResult.isMatch) {
2572
2828
  if (runMode & 1) {
2573
2829
  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
- ]);
2830
+ const origin = DiagnosticOrigin.fromNode(assertion.node.expression.name);
2831
+ onExpectDiagnostics(Diagnostic.error(text, origin));
2579
2832
  }
2580
2833
  else {
2581
2834
  EventEmitter.dispatch(["expect:pass", { result: expectResult }]);
@@ -2585,14 +2838,7 @@ class TestTreeWorker {
2585
2838
  EventEmitter.dispatch(["expect:pass", { result: expectResult }]);
2586
2839
  }
2587
2840
  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 }]);
2841
+ EventEmitter.dispatch(["expect:fail", { diagnostics: matchResult.explain(), result: expectResult }]);
2596
2842
  }
2597
2843
  }
2598
2844
  #visitDescribe(describe, runMode, parentResult) {
@@ -2705,13 +2951,12 @@ class TestFileRunner {
2705
2951
  return;
2706
2952
  }
2707
2953
  const typeChecker = program.getTypeChecker();
2708
- if (!Expect.assertTypeChecker(typeChecker)) {
2954
+ if (!ExpectService.assertTypeChecker(typeChecker)) {
2709
2955
  const text = "The required 'isTypeRelatedTo()' method is missing in the provided type checker.";
2710
2956
  EventEmitter.dispatch(["file:error", { diagnostics: [Diagnostic.error(text)], result: fileResult }]);
2711
2957
  return;
2712
2958
  }
2713
- const expect = new Expect(this.#compiler, typeChecker);
2714
- const testTreeWorker = new TestTreeWorker(this.#resolvedConfig, this.#compiler, expect, {
2959
+ const testTreeWorker = new TestTreeWorker(this.#resolvedConfig, this.#compiler, typeChecker, {
2715
2960
  cancellationToken,
2716
2961
  fileResult,
2717
2962
  hasOnly: testTree.hasOnly,
@@ -2742,6 +2987,7 @@ class TaskRunner {
2742
2987
  this.#eventEmitter.addHandler(cancellationHandler);
2743
2988
  }
2744
2989
  if (this.#resolvedConfig.watch === true) {
2990
+ await this.#run(testFiles, cancellationToken);
2745
2991
  await this.#watch(testFiles, cancellationToken);
2746
2992
  }
2747
2993
  else {
@@ -2767,22 +3013,15 @@ class TaskRunner {
2767
3013
  EventEmitter.dispatch(["target:end", { result: targetResult }]);
2768
3014
  }
2769
3015
  EventEmitter.dispatch(["run:end", { result }]);
2770
- if (cancellationToken?.reason === "failFast") {
3016
+ if (cancellationToken.reason === "failFast") {
2771
3017
  cancellationToken.reset();
2772
3018
  }
2773
3019
  }
2774
3020
  async #watch(testFiles, cancellationToken) {
2775
- await this.#run(testFiles, cancellationToken);
2776
- const runCallback = async (testFiles) => {
3021
+ const watchService = new WatchService(this.#resolvedConfig, this.#selectService, testFiles);
3022
+ for await (const testFiles of watchService.watch(cancellationToken)) {
2777
3023
  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);
3024
+ }
2786
3025
  }
2787
3026
  }
2788
3027
 
@@ -2793,7 +3032,7 @@ class TSTyche {
2793
3032
  #selectService;
2794
3033
  #storeService;
2795
3034
  #taskRunner;
2796
- static version = "2.0.0";
3035
+ static version = "2.1.0";
2797
3036
  constructor(resolvedConfig, outputService, selectService, storeService) {
2798
3037
  this.#resolvedConfig = resolvedConfig;
2799
3038
  this.#outputService = outputService;
@@ -3009,14 +3248,14 @@ class OptionUsageText {
3009
3248
  }
3010
3249
 
3011
3250
  class OptionValidator {
3012
- #onDiagnostic;
3251
+ #onDiagnostics;
3013
3252
  #optionGroup;
3014
3253
  #optionUsageText;
3015
3254
  #storeService;
3016
- constructor(optionGroup, storeService, onDiagnostic) {
3255
+ constructor(optionGroup, storeService, onDiagnostics) {
3017
3256
  this.#optionGroup = optionGroup;
3018
3257
  this.#storeService = storeService;
3019
- this.#onDiagnostic = onDiagnostic;
3258
+ this.#onDiagnostics = onDiagnostics;
3020
3259
  this.#optionUsageText = new OptionUsageText(this.#optionGroup, this.#storeService);
3021
3260
  }
3022
3261
  async check(optionName, optionValue, optionBrand, origin) {
@@ -3024,13 +3263,13 @@ class OptionValidator {
3024
3263
  case "config":
3025
3264
  case "rootPath": {
3026
3265
  if (!existsSync(optionValue)) {
3027
- this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.fileDoesNotExist(optionValue), origin));
3266
+ this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.fileDoesNotExist(optionValue), origin));
3028
3267
  }
3029
3268
  break;
3030
3269
  }
3031
3270
  case "target": {
3032
3271
  if ((await this.#storeService.validateTag(optionValue)) === false) {
3033
- this.#onDiagnostic(Diagnostic.error([
3272
+ this.#onDiagnostics(Diagnostic.error([
3034
3273
  ConfigDiagnosticText.versionIsNotSupported(optionValue),
3035
3274
  ...(await this.#optionUsageText.get(optionName, optionBrand)),
3036
3275
  ], origin));
@@ -3040,14 +3279,14 @@ class OptionValidator {
3040
3279
  case "testFileMatch": {
3041
3280
  for (const segment of ["/", "../"]) {
3042
3281
  if (optionValue.startsWith(segment)) {
3043
- this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.testFileMatchCannotStartWith(segment), origin));
3282
+ this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.testFileMatchCannotStartWith(segment), origin));
3044
3283
  }
3045
3284
  }
3046
3285
  break;
3047
3286
  }
3048
3287
  case "watch": {
3049
3288
  if (Environment.isCi) {
3050
- this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.watchCannotBeEnabled(), origin));
3289
+ this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.watchCannotBeEnabled(), origin));
3051
3290
  }
3052
3291
  break;
3053
3292
  }
@@ -3058,27 +3297,27 @@ class OptionValidator {
3058
3297
  class CommandLineOptionsWorker {
3059
3298
  #commandLineOptionDefinitions;
3060
3299
  #commandLineOptions;
3061
- #onDiagnostic;
3300
+ #onDiagnostics;
3062
3301
  #optionGroup = 2;
3063
3302
  #optionUsageText;
3064
3303
  #optionValidator;
3065
3304
  #pathMatch;
3066
3305
  #storeService;
3067
- constructor(commandLineOptions, pathMatch, storeService, onDiagnostic) {
3306
+ constructor(commandLineOptions, pathMatch, storeService, onDiagnostics) {
3068
3307
  this.#commandLineOptions = commandLineOptions;
3069
3308
  this.#pathMatch = pathMatch;
3070
3309
  this.#storeService = storeService;
3071
- this.#onDiagnostic = onDiagnostic;
3310
+ this.#onDiagnostics = onDiagnostics;
3072
3311
  this.#commandLineOptionDefinitions = OptionDefinitionsMap.for(this.#optionGroup);
3073
3312
  this.#optionUsageText = new OptionUsageText(this.#optionGroup, this.#storeService);
3074
- this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostic);
3313
+ this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostics);
3075
3314
  }
3076
3315
  async #onExpectsValue(optionDefinition) {
3077
3316
  const text = [
3078
3317
  ConfigDiagnosticText.expectsValue(optionDefinition.name, this.#optionGroup),
3079
3318
  ...(await this.#optionUsageText.get(optionDefinition.name, optionDefinition.brand)),
3080
3319
  ];
3081
- this.#onDiagnostic(Diagnostic.error(text));
3320
+ this.#onDiagnostics(Diagnostic.error(text));
3082
3321
  }
3083
3322
  async parse(commandLineArgs) {
3084
3323
  let index = 0;
@@ -3092,11 +3331,11 @@ class CommandLineOptionsWorker {
3092
3331
  index = await this.#parseOptionValue(commandLineArgs, index, optionDefinition);
3093
3332
  }
3094
3333
  else {
3095
- this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.unknownOption(arg)));
3334
+ this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.unknownOption(arg)));
3096
3335
  }
3097
3336
  }
3098
3337
  else if (arg.startsWith("-")) {
3099
- this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.unknownOption(arg)));
3338
+ this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.unknownOption(arg)));
3100
3339
  }
3101
3340
  else {
3102
3341
  this.#pathMatch.push(Path.normalizeSlashes(arg));
@@ -3162,18 +3401,18 @@ class ConfigFileOptionsWorker {
3162
3401
  #configFileOptionDefinitions;
3163
3402
  #configFileOptions;
3164
3403
  #configFilePath;
3165
- #onDiagnostic;
3404
+ #onDiagnostics;
3166
3405
  #optionGroup = 4;
3167
3406
  #optionValidator;
3168
3407
  #storeService;
3169
- constructor(compiler, configFileOptions, configFilePath, storeService, onDiagnostic) {
3408
+ constructor(compiler, configFileOptions, configFilePath, storeService, onDiagnostics) {
3170
3409
  this.#compiler = compiler;
3171
3410
  this.#configFileOptions = configFileOptions;
3172
3411
  this.#configFilePath = configFilePath;
3173
3412
  this.#storeService = storeService;
3174
- this.#onDiagnostic = onDiagnostic;
3413
+ this.#onDiagnostics = onDiagnostics;
3175
3414
  this.#configFileOptionDefinitions = OptionDefinitionsMap.for(this.#optionGroup);
3176
- this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostic);
3415
+ this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostics);
3177
3416
  }
3178
3417
  #isDoubleQuotedString(node, sourceFile) {
3179
3418
  return (node.kind === this.#compiler.SyntaxKind.StringLiteral &&
@@ -3182,25 +3421,26 @@ class ConfigFileOptionsWorker {
3182
3421
  async parse(sourceText) {
3183
3422
  const sourceFile = this.#compiler.parseJsonText(this.#configFilePath, sourceText);
3184
3423
  if (sourceFile.parseDiagnostics.length > 0) {
3185
- for (const diagnostic of Diagnostic.fromDiagnostics(sourceFile.parseDiagnostics, this.#compiler)) {
3186
- this.#onDiagnostic(diagnostic);
3187
- }
3424
+ this.#onDiagnostics(Diagnostic.fromDiagnostics(sourceFile.parseDiagnostics, this.#compiler));
3188
3425
  return;
3189
3426
  }
3190
3427
  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));
3428
+ if (!rootExpression) {
3429
+ return;
3430
+ }
3431
+ if (!this.#compiler.isObjectLiteralExpression(rootExpression)) {
3432
+ const origin = DiagnosticOrigin.fromJsonNode(rootExpression, sourceFile, this.#skipTrivia);
3433
+ this.#onDiagnostics(Diagnostic.error("The root value of a configuration file must be an object literal.", origin));
3194
3434
  return;
3195
3435
  }
3196
3436
  for (const property of rootExpression.properties) {
3197
3437
  if (this.#compiler.isPropertyAssignment(property)) {
3198
3438
  if (!this.#isDoubleQuotedString(property.name, sourceFile)) {
3199
- const origin = DiagnosticOrigin.fromJsonNode(property, sourceFile, this.#skipTrivia);
3200
- this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.doubleQuotesExpected(), origin));
3439
+ const origin = DiagnosticOrigin.fromJsonNode(property.name, sourceFile, this.#skipTrivia);
3440
+ this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.doubleQuotesExpected(), origin));
3201
3441
  continue;
3202
3442
  }
3203
- const optionName = this.#resolvePropertyName(property);
3443
+ const optionName = property.name.text;
3204
3444
  if (optionName === "$schema") {
3205
3445
  continue;
3206
3446
  }
@@ -3209,8 +3449,8 @@ class ConfigFileOptionsWorker {
3209
3449
  this.#configFileOptions[optionDefinition.name] = await this.#parseOptionValue(sourceFile, property.initializer, optionDefinition);
3210
3450
  }
3211
3451
  else {
3212
- const origin = DiagnosticOrigin.fromJsonNode(property, sourceFile, this.#skipTrivia);
3213
- this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.unknownOption(optionName), origin));
3452
+ const origin = DiagnosticOrigin.fromJsonNode(property.name, sourceFile, this.#skipTrivia);
3453
+ this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.unknownOption(optionName), origin));
3214
3454
  }
3215
3455
  }
3216
3456
  }
@@ -3233,7 +3473,7 @@ class ConfigFileOptionsWorker {
3233
3473
  case this.#compiler.SyntaxKind.StringLiteral: {
3234
3474
  if (!this.#isDoubleQuotedString(valueExpression, sourceFile)) {
3235
3475
  const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
3236
- this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.doubleQuotesExpected(), origin));
3476
+ this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.doubleQuotesExpected(), origin));
3237
3477
  return;
3238
3478
  }
3239
3479
  if (optionDefinition.brand === "string") {
@@ -3262,15 +3502,9 @@ class ConfigFileOptionsWorker {
3262
3502
  ? ConfigDiagnosticText.expectsListItemType(optionDefinition.name, optionDefinition.brand)
3263
3503
  : ConfigDiagnosticText.requiresValueType(optionDefinition.name, optionDefinition.brand, this.#optionGroup);
3264
3504
  const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
3265
- this.#onDiagnostic(Diagnostic.error(text, origin));
3505
+ this.#onDiagnostics(Diagnostic.error(text, origin));
3266
3506
  return;
3267
3507
  }
3268
- #resolvePropertyName({ name }) {
3269
- if ("text" in name) {
3270
- return name.text;
3271
- }
3272
- return "";
3273
- }
3274
3508
  #skipTrivia(position, sourceFile) {
3275
3509
  const { text } = sourceFile.getSourceFile();
3276
3510
  while (position < text.length) {
@@ -3312,7 +3546,7 @@ class ConfigFileOptionsWorker {
3312
3546
  const defaultOptions = {
3313
3547
  failFast: false,
3314
3548
  rootPath: "./",
3315
- target: [Environment.typescriptPath == null ? "latest" : "current"],
3549
+ target: [Environment.typescriptPath != null ? "current" : "latest"],
3316
3550
  testFileMatch: ["**/*.tst.*", "**/__typetests__/*.test.*", "**/typetests/*.test.*"],
3317
3551
  };
3318
3552
  class ConfigService {
@@ -3326,13 +3560,13 @@ class ConfigService {
3326
3560
  this.#compiler = compiler;
3327
3561
  this.#storeService = storeService;
3328
3562
  }
3329
- #onDiagnostic(diagnostic) {
3330
- EventEmitter.dispatch(["config:error", { diagnostics: [diagnostic] }]);
3563
+ #onDiagnostics(diagnostics) {
3564
+ EventEmitter.dispatch(["config:error", { diagnostics: Array.isArray(diagnostics) ? diagnostics : [diagnostics] }]);
3331
3565
  }
3332
3566
  async parseCommandLine(commandLineArgs) {
3333
3567
  this.#commandLineOptions = {};
3334
3568
  this.#pathMatch = [];
3335
- const commandLineWorker = new CommandLineOptionsWorker(this.#commandLineOptions, this.#pathMatch, this.#storeService, this.#onDiagnostic);
3569
+ const commandLineWorker = new CommandLineOptionsWorker(this.#commandLineOptions, this.#pathMatch, this.#storeService, this.#onDiagnostics);
3336
3570
  await commandLineWorker.parse(commandLineArgs);
3337
3571
  if (this.#commandLineOptions.config != null) {
3338
3572
  this.#configFilePath = this.#commandLineOptions.config;
@@ -3349,7 +3583,7 @@ class ConfigService {
3349
3583
  const configFileText = await fs.readFile(this.#configFilePath, {
3350
3584
  encoding: "utf8",
3351
3585
  });
3352
- const configFileWorker = new ConfigFileOptionsWorker(this.#compiler, this.#configFileOptions, this.#configFilePath, this.#storeService, this.#onDiagnostic);
3586
+ const configFileWorker = new ConfigFileOptionsWorker(this.#compiler, this.#configFileOptions, this.#configFilePath, this.#storeService, this.#onDiagnostics);
3353
3587
  await configFileWorker.parse(configFileText);
3354
3588
  }
3355
3589
  resolveConfig() {
@@ -3395,14 +3629,14 @@ class StoreDiagnosticText {
3395
3629
  class ManifestWorker {
3396
3630
  #manifestFileName = "store-manifest.json";
3397
3631
  #manifestFilePath;
3398
- #onDiagnostic;
3632
+ #onDiagnostics;
3399
3633
  #registryUrl = new URL("https://registry.npmjs.org");
3400
3634
  #storePath;
3401
3635
  #timeout = Environment.timeout * 1000;
3402
3636
  #version = "1";
3403
- constructor(storePath, onDiagnostic) {
3637
+ constructor(storePath, onDiagnostics) {
3404
3638
  this.#storePath = storePath;
3405
- this.#onDiagnostic = onDiagnostic;
3639
+ this.#onDiagnostics = onDiagnostics;
3406
3640
  this.#manifestFilePath = Path.join(storePath, this.#manifestFileName);
3407
3641
  }
3408
3642
  async #create() {
@@ -3432,7 +3666,7 @@ class ManifestWorker {
3432
3666
  signal: AbortSignal.timeout(this.#timeout),
3433
3667
  });
3434
3668
  if (!response.ok) {
3435
- this.#onDiagnostic(Diagnostic.error([
3669
+ this.#onDiagnostics(Diagnostic.error([
3436
3670
  StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
3437
3671
  StoreDiagnosticText.failedWithStatusCode(response.status),
3438
3672
  ]));
@@ -3445,13 +3679,13 @@ class ManifestWorker {
3445
3679
  return;
3446
3680
  }
3447
3681
  if (error instanceof Error && error.name === "TimeoutError") {
3448
- this.#onDiagnostic(Diagnostic.error([
3682
+ this.#onDiagnostics(Diagnostic.error([
3449
3683
  StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
3450
3684
  StoreDiagnosticText.setupTimeoutExceeded(this.#timeout),
3451
3685
  ]));
3452
3686
  }
3453
3687
  else {
3454
- this.#onDiagnostic(Diagnostic.error([
3688
+ this.#onDiagnostics(Diagnostic.error([
3455
3689
  StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
3456
3690
  StoreDiagnosticText.maybeNetworkConnectionIssue(),
3457
3691
  ]));
@@ -3486,9 +3720,9 @@ class ManifestWorker {
3486
3720
  manifestText = await fs.readFile(this.#manifestFilePath, { encoding: "utf8" });
3487
3721
  }
3488
3722
  catch (error) {
3489
- this.#onDiagnostic(Diagnostic.fromError("Failed to open store manifest.", error));
3723
+ this.#onDiagnostics(Diagnostic.fromError("Failed to open store manifest.", error));
3490
3724
  }
3491
- if (manifestText == null) {
3725
+ if (!manifestText) {
3492
3726
  return;
3493
3727
  }
3494
3728
  try {
@@ -3496,7 +3730,7 @@ class ManifestWorker {
3496
3730
  }
3497
3731
  catch {
3498
3732
  }
3499
- if (manifest == null || manifest.$version !== this.#version) {
3733
+ if (!manifest || manifest.$version !== this.#version) {
3500
3734
  await fs.rm(this.#storePath, { force: true, recursive: true });
3501
3735
  return this.#create();
3502
3736
  }
@@ -3536,7 +3770,7 @@ class Lock {
3536
3770
  if (!isLocked) {
3537
3771
  return isLocked;
3538
3772
  }
3539
- if (options?.timeout == null) {
3773
+ if (!options?.timeout) {
3540
3774
  return isLocked;
3541
3775
  }
3542
3776
  const waitStartTime = Date.now();
@@ -3545,7 +3779,7 @@ class Lock {
3545
3779
  break;
3546
3780
  }
3547
3781
  if (Date.now() - waitStartTime > options.timeout) {
3548
- options.onDiagnostic?.(`Lock wait timeout of ${String(options.timeout / 1000)}s was exceeded.`);
3782
+ options.onDiagnostics?.(`Lock wait timeout of ${String(options.timeout / 1000)}s was exceeded.`);
3549
3783
  break;
3550
3784
  }
3551
3785
  await Lock.#sleep(1000);
@@ -3562,13 +3796,13 @@ class Lock {
3562
3796
  }
3563
3797
 
3564
3798
  class PackageInstaller {
3565
- #onDiagnostic;
3799
+ #onDiagnostics;
3566
3800
  #readyFileName = "__ready__";
3567
3801
  #storePath;
3568
3802
  #timeout = Environment.timeout * 1000;
3569
- constructor(storePath, onDiagnostic) {
3803
+ constructor(storePath, onDiagnostics) {
3570
3804
  this.#storePath = storePath;
3571
- this.#onDiagnostic = onDiagnostic;
3805
+ this.#onDiagnostics = onDiagnostics;
3572
3806
  }
3573
3807
  async ensure(compilerVersion, cancellationToken) {
3574
3808
  const installationPath = Path.join(this.#storePath, compilerVersion);
@@ -3579,8 +3813,8 @@ class PackageInstaller {
3579
3813
  }
3580
3814
  if (await Lock.isLocked(installationPath, {
3581
3815
  cancellationToken,
3582
- onDiagnostic: (text) => {
3583
- this.#onDiagnostic(Diagnostic.error([`Failed to install 'typescript@${compilerVersion}'.`, text]));
3816
+ onDiagnostics: (text) => {
3817
+ this.#onDiagnostics(Diagnostic.error([`Failed to install 'typescript@${compilerVersion}'.`, text]));
3584
3818
  },
3585
3819
  timeout: this.#timeout,
3586
3820
  })) {
@@ -3606,7 +3840,7 @@ class PackageInstaller {
3606
3840
  return modulePath;
3607
3841
  }
3608
3842
  catch (error) {
3609
- this.#onDiagnostic(Diagnostic.fromError(`Failed to install 'typescript@${compilerVersion}'.`, error));
3843
+ this.#onDiagnostics(Diagnostic.fromError(`Failed to install 'typescript@${compilerVersion}'.`, error));
3610
3844
  }
3611
3845
  finally {
3612
3846
  lock.release();
@@ -3646,8 +3880,8 @@ class StoreService {
3646
3880
  #storePath;
3647
3881
  constructor() {
3648
3882
  this.#storePath = Environment.storePath;
3649
- this.#packageInstaller = new PackageInstaller(this.#storePath, this.#onDiagnostic);
3650
- this.#manifestWorker = new ManifestWorker(this.#storePath, this.#onDiagnostic);
3883
+ this.#packageInstaller = new PackageInstaller(this.#storePath, this.#onDiagnostics);
3884
+ this.#manifestWorker = new ManifestWorker(this.#storePath, this.#onDiagnostics);
3651
3885
  }
3652
3886
  async getSupportedTags() {
3653
3887
  await this.open();
@@ -3660,9 +3894,9 @@ class StoreService {
3660
3894
  if (tag === "current") {
3661
3895
  return;
3662
3896
  }
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.`));
3897
+ const version = await this.#resolveTag(tag);
3898
+ if (!version) {
3899
+ this.#onDiagnostics(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
3666
3900
  return;
3667
3901
  }
3668
3902
  return this.#packageInstaller.ensure(version, cancellationToken);
@@ -3677,9 +3911,9 @@ class StoreService {
3677
3911
  modulePath = Environment.typescriptPath;
3678
3912
  }
3679
3913
  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.`));
3914
+ const version = await this.#resolveTag(tag);
3915
+ if (!version) {
3916
+ this.#onDiagnostics(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
3683
3917
  return;
3684
3918
  }
3685
3919
  compilerInstance = this.#compilerInstanceCache.get(version);
@@ -3715,7 +3949,7 @@ class StoreService {
3715
3949
  }
3716
3950
  return module.exports;
3717
3951
  }
3718
- #onDiagnostic(diagnostic) {
3952
+ #onDiagnostics(diagnostic) {
3719
3953
  EventEmitter.dispatch(["store:error", { diagnostics: [diagnostic] }]);
3720
3954
  }
3721
3955
  async open() {
@@ -3724,10 +3958,7 @@ class StoreService {
3724
3958
  }
3725
3959
  this.#manifest = await this.#manifestWorker.open();
3726
3960
  }
3727
- async resolveTag(tag) {
3728
- if (tag === "current") {
3729
- return tag;
3730
- }
3961
+ async #resolveTag(tag) {
3731
3962
  await this.open();
3732
3963
  if (!this.#manifest) {
3733
3964
  return;
@@ -3752,7 +3983,7 @@ class StoreService {
3752
3983
  (!Version.isVersionTag(tag) ||
3753
3984
  (this.#manifest.resolutions["latest"] != null &&
3754
3985
  Version.isGreaterThan(tag, this.#manifest.resolutions["latest"])))) {
3755
- this.#onDiagnostic(Diagnostic.warning([
3986
+ this.#onDiagnostics(Diagnostic.warning([
3756
3987
  "Failed to update metadata of the 'typescript' package from the registry.",
3757
3988
  `The resolution of the '${tag}' tag may be outdated.`,
3758
3989
  ]));
@@ -3789,7 +4020,7 @@ class Cli {
3789
4020
  await this.#storeService.update();
3790
4021
  return;
3791
4022
  }
3792
- const compiler = await this.#storeService.load(Environment.typescriptPath == null ? "latest" : "current");
4023
+ const compiler = await this.#storeService.load(Environment.typescriptPath != null ? "current" : "latest");
3793
4024
  if (!compiler) {
3794
4025
  return;
3795
4026
  }
@@ -3855,28 +4086,33 @@ class Cli {
3855
4086
  this.#eventEmitter.removeHandlers();
3856
4087
  }
3857
4088
  #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();
4089
+ return new Promise((resolve) => {
4090
+ const watchers = [];
4091
+ cancellationToken.reset();
4092
+ this.#outputService.writeMessage(waitingForFileChangesText());
4093
+ const onChanged = () => {
4094
+ cancellationToken.cancel("configChange");
4095
+ for (const watcher of watchers) {
4096
+ watcher.close();
3872
4097
  }
4098
+ resolve();
3873
4099
  };
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()));
4100
+ watchers.push(new FileWatcher(resolvedConfig.configFilePath, onChanged));
4101
+ if (selectService != null) {
4102
+ const onChangedTestFile = (filePath) => {
4103
+ if (selectService.isTestFile(filePath)) {
4104
+ onChanged();
4105
+ }
4106
+ };
4107
+ const onRemoved = () => {
4108
+ };
4109
+ watchers.push(new Watcher(resolvedConfig.rootPath, onChangedTestFile, onRemoved, { recursive: true }));
4110
+ }
4111
+ for (const watcher of watchers) {
4112
+ watcher.watch();
4113
+ }
4114
+ });
3879
4115
  }
3880
4116
  }
3881
4117
 
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 };
4118
+ 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 };