tstyche 2.0.0-beta.1 → 2.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/tstyche.js CHANGED
@@ -3,10 +3,9 @@ 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, watch, writeFileSync, rmSync } from 'node:fs';
6
+ import { existsSync, writeFileSync, rmSync } from 'node:fs';
7
7
  import fs from 'node:fs/promises';
8
8
  import vm from 'node:vm';
9
- import https from 'node:https';
10
9
  import { spawn } from 'node:child_process';
11
10
 
12
11
  class EventEmitter {
@@ -34,18 +33,21 @@ class EventEmitter {
34
33
  }
35
34
 
36
35
  class Path {
36
+ static normalizeSlashes;
37
+ static {
38
+ if (path.sep === "/") {
39
+ Path.normalizeSlashes = (filePath) => filePath;
40
+ }
41
+ else {
42
+ Path.normalizeSlashes = (filePath) => filePath.replace(/\\/g, "/");
43
+ }
44
+ }
37
45
  static dirname(filePath) {
38
46
  return Path.normalizeSlashes(path.dirname(filePath));
39
47
  }
40
48
  static join(...filePaths) {
41
49
  return Path.normalizeSlashes(path.join(...filePaths));
42
50
  }
43
- static normalizeSlashes(filePath) {
44
- if (path.sep === "/") {
45
- return filePath;
46
- }
47
- return filePath.replace(/\\/g, "/");
48
- }
49
51
  static relative(from, to) {
50
52
  let relativePath = path.relative(from, to);
51
53
  if (!relativePath.startsWith("./")) {
@@ -61,16 +63,11 @@ class Path {
61
63
  class TestFile {
62
64
  path;
63
65
  position;
64
- constructor(identifier) {
65
- this.path = Path.normalizeSlashes(this.#resolvePath(identifier));
66
- }
67
- add(options) {
68
- if (options.position != null) {
69
- this.position = options.position;
70
- }
71
- return this;
66
+ constructor(identifier, position) {
67
+ this.path = Path.normalizeSlashes(this.#toPath(identifier));
68
+ this.position = position;
72
69
  }
73
- #resolvePath(identifier) {
70
+ #toPath(identifier) {
74
71
  if (typeof identifier === "string" && !identifier.startsWith("file:")) {
75
72
  return identifier;
76
73
  }
@@ -304,21 +301,21 @@ class CodeSpanText {
304
301
  this.props = props;
305
302
  }
306
303
  render() {
307
- const lastLineInFile = this.props.file.getLineAndCharacterOfPosition(this.props.file.text.length).line;
308
- const { character: markedCharacter, line: markedLine } = this.props.file.getLineAndCharacterOfPosition(this.props.start);
304
+ const lastLineInFile = this.props.sourceFile.getLineAndCharacterOfPosition(this.props.sourceFile.text.length).line;
305
+ const { character: markedCharacter, line: markedLine } = this.props.sourceFile.getLineAndCharacterOfPosition(this.props.start);
309
306
  const firstLine = Math.max(markedLine - 2, 0);
310
307
  const lastLine = Math.min(firstLine + 5, lastLineInFile);
311
308
  const lineNumberMaxWidth = String(lastLine + 1).length;
312
309
  const codeSpan = [];
313
310
  for (let index = firstLine; index <= lastLine; index++) {
314
- const lineStart = this.props.file.getPositionOfLineAndCharacter(index, 0);
311
+ const lineStart = this.props.sourceFile.getPositionOfLineAndCharacter(index, 0);
315
312
  const lineEnd = index === lastLineInFile
316
- ? this.props.file.text.length
317
- : this.props.file.getPositionOfLineAndCharacter(index + 1, 0);
313
+ ? this.props.sourceFile.text.length
314
+ : this.props.sourceFile.getPositionOfLineAndCharacter(index + 1, 0);
318
315
  const lineNumberText = String(index + 1);
319
- const lineText = this.props.file.text.slice(lineStart, lineEnd).trimEnd().replace(/\t/g, " ");
316
+ const lineText = this.props.sourceFile.text.slice(lineStart, lineEnd).trimEnd().replace(/\t/g, " ");
320
317
  if (index === markedLine) {
321
- codeSpan.push(jsx(Line, { children: [jsx(Text, { color: "31", children: ">" }), " ", lineNumberText.padStart(lineNumberMaxWidth), " ", 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: "^" })] }));
318
+ 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: "^" })] }));
322
319
  }
323
320
  else {
324
321
  codeSpan.push(jsx(Line, { children: [" ".repeat(2), jsx(Text, { color: "90", children: [lineNumberText.padStart(lineNumberMaxWidth), " | ", lineText || ""] })] }));
@@ -328,7 +325,7 @@ class CodeSpanText {
328
325
  jsx(Text, { color: "90", children: " ❭ " }),
329
326
  jsx(Text, { children: ancestor }),
330
327
  ]);
331
- const location = (jsx(Line, { children: [" ".repeat(lineNumberMaxWidth + 5), jsx(Text, { color: "90", children: "at" }), " ", jsx(Text, { color: "36", children: Path.relative("", this.props.file.fileName) }), jsx(Text, { color: "90", children: [":", String(markedLine + 1), ":", String(markedCharacter + 1)] }), breadcrumbs] }));
328
+ const location = (jsx(Line, { children: [" ".repeat(lineNumberMaxWidth + 5), jsx(Text, { color: "90", children: "at" }), jsx(Text, { children: " " }), jsx(Text, { color: "36", children: Path.relative("", this.props.sourceFile.fileName) }), jsx(Text, { color: "90", children: [":", String(markedLine + 1), ":", String(markedCharacter + 1)] }), breadcrumbs] }));
332
329
  return (jsx(Text, { children: [codeSpan, jsx(Line, {}), location] }));
333
330
  }
334
331
  }
@@ -619,21 +616,21 @@ class RanFilesText {
619
616
  this.props = props;
620
617
  }
621
618
  render() {
622
- const testNameMatch = [];
619
+ const testNameMatchText = [];
623
620
  if (this.props.onlyMatch != null) {
624
- testNameMatch.push(jsx(Text, { children: [jsx(Text, { color: "90", children: "matching " }), jsx(MatchText, { text: this.props.onlyMatch })] }));
621
+ testNameMatchText.push(jsx(Text, { children: [jsx(Text, { color: "90", children: "matching " }), jsx(MatchText, { text: this.props.onlyMatch })] }));
625
622
  }
626
623
  if (this.props.skipMatch != null) {
627
- testNameMatch.push(jsx(Text, { children: [this.props.onlyMatch == null ? undefined : jsx(Text, { color: "90", children: " and " }), jsx(Text, { color: "90", children: "not matching " }), jsx(MatchText, { text: this.props.skipMatch })] }));
624
+ testNameMatchText.push(jsx(Text, { children: [this.props.onlyMatch == null ? undefined : jsx(Text, { color: "90", children: " and " }), jsx(Text, { color: "90", children: "not matching " }), jsx(MatchText, { text: this.props.skipMatch })] }));
628
625
  }
629
- let pathMatch;
626
+ let pathMatchText;
630
627
  if (this.props.pathMatch.length > 0) {
631
- pathMatch = (jsx(Text, { children: [jsx(Text, { color: "90", children: "test files matching " }), jsx(MatchText, { text: this.props.pathMatch }), jsx(Text, { color: "90", children: "." })] }));
628
+ pathMatchText = (jsx(Text, { children: [jsx(Text, { color: "90", children: "test files matching " }), jsx(MatchText, { text: this.props.pathMatch }), jsx(Text, { color: "90", children: "." })] }));
632
629
  }
633
630
  else {
634
- pathMatch = jsx(Text, { color: "90", children: "all test files." });
631
+ pathMatchText = jsx(Text, { color: "90", children: "all test files." });
635
632
  }
636
- return (jsx(Line, { children: [jsx(Text, { color: "90", children: "Ran " }), testNameMatch.length > 0 ? jsx(Text, { color: "90", children: "tests " }) : undefined, testNameMatch, testNameMatch.length > 0 ? jsx(Text, { color: "90", children: " in " }) : undefined, pathMatch] }));
633
+ return (jsx(Line, { children: [jsx(Text, { color: "90", children: "Ran " }), testNameMatchText.length > 0 ? jsx(Text, { color: "90", children: "tests " }) : undefined, testNameMatchText, testNameMatchText.length > 0 ? jsx(Text, { color: "90", children: " in " }) : undefined, pathMatchText] }));
637
634
  }
638
635
  }
639
636
  function summaryText({ duration, expectCount, fileCount, onlyMatch, pathMatch, skipMatch, targetCount, testCount, }) {
@@ -711,6 +708,11 @@ class FileViewService {
711
708
  this.#lines.push(describeNameText(name, this.#indent));
712
709
  this.#indent++;
713
710
  }
711
+ clear() {
712
+ this.#indent = 0;
713
+ this.#lines = [];
714
+ this.#messages = [];
715
+ }
714
716
  endDescribe() {
715
717
  this.#indent--;
716
718
  }
@@ -720,15 +722,9 @@ class FileViewService {
720
722
  getViewText(options) {
721
723
  return fileViewText(this.#lines, options?.appendEmptyLine === true || this.hasErrors);
722
724
  }
723
- reset() {
724
- this.#indent = 0;
725
- this.#lines = [];
726
- this.#messages = [];
727
- }
728
725
  }
729
726
 
730
727
  class RuntimeReporter {
731
- resolvedConfig;
732
728
  #currentCompilerVersion;
733
729
  #currentProjectConfigFilePath;
734
730
  #fileCount = 0;
@@ -736,10 +732,11 @@ class RuntimeReporter {
736
732
  #hasReportedAdds = false;
737
733
  #hasReportedError = false;
738
734
  #isFileViewExpanded = false;
735
+ #resolvedConfig;
739
736
  #outputService;
740
737
  #seenDeprecations = new Set();
741
738
  constructor(resolvedConfig, outputService) {
742
- this.resolvedConfig = resolvedConfig;
739
+ this.#resolvedConfig = resolvedConfig;
743
740
  this.#outputService = outputService;
744
741
  }
745
742
  get #isLastFile() {
@@ -757,7 +754,7 @@ class RuntimeReporter {
757
754
  break;
758
755
  }
759
756
  case "run:start": {
760
- this.#isFileViewExpanded = payload.result.testFiles.length === 1 && this.resolvedConfig.watch !== true;
757
+ this.#isFileViewExpanded = payload.result.testFiles.length === 1 && this.#resolvedConfig.watch !== true;
761
758
  break;
762
759
  }
763
760
  case "store:info": {
@@ -822,7 +819,8 @@ class RuntimeReporter {
822
819
  this.#outputService.writeError(this.#fileView.getMessages());
823
820
  this.#hasReportedError = true;
824
821
  }
825
- this.#fileView.reset();
822
+ this.#fileView.clear();
823
+ this.#seenDeprecations.clear();
826
824
  break;
827
825
  }
828
826
  case "describe:start": {
@@ -960,8 +958,8 @@ class WatchReporter {
960
958
  }
961
959
 
962
960
  class ResultTiming {
963
- end = Date.now();
964
- start = Date.now();
961
+ end = Number.NaN;
962
+ start = Number.NaN;
965
963
  get duration() {
966
964
  return this.end - this.start;
967
965
  }
@@ -989,8 +987,8 @@ var ResultStatus;
989
987
 
990
988
  class ExpectResult {
991
989
  assertion;
992
- parent;
993
990
  diagnostics = [];
991
+ parent;
994
992
  status = "runs";
995
993
  timing = new ResultTiming();
996
994
  constructor(assertion, parent) {
@@ -1010,12 +1008,12 @@ class ResultCount {
1010
1008
  }
1011
1009
 
1012
1010
  class FileResult {
1013
- testFile;
1014
1011
  diagnostics = [];
1015
1012
  expectCount = new ResultCount();
1016
1013
  results = [];
1017
1014
  status = "runs";
1018
1015
  testCount = new ResultCount();
1016
+ testFile;
1019
1017
  timing = new ResultTiming();
1020
1018
  constructor(testFile) {
1021
1019
  this.testFile = testFile;
@@ -1024,8 +1022,8 @@ class FileResult {
1024
1022
 
1025
1023
  class ProjectResult {
1026
1024
  compilerVersion;
1027
- projectConfigFilePath;
1028
1025
  diagnostics = [];
1026
+ projectConfigFilePath;
1029
1027
  results = [];
1030
1028
  constructor(compilerVersion, projectConfigFilePath) {
1031
1029
  this.compilerVersion = compilerVersion;
@@ -1034,13 +1032,13 @@ class ProjectResult {
1034
1032
  }
1035
1033
 
1036
1034
  class Result {
1037
- resolvedConfig;
1038
- testFiles;
1039
1035
  expectCount = new ResultCount();
1040
1036
  fileCount = new ResultCount();
1037
+ resolvedConfig;
1041
1038
  results = [];
1042
1039
  targetCount = new ResultCount();
1043
1040
  testCount = new ResultCount();
1041
+ testFiles;
1044
1042
  timing = new ResultTiming();
1045
1043
  constructor(resolvedConfig, testFiles) {
1046
1044
  this.resolvedConfig = resolvedConfig;
@@ -1265,11 +1263,11 @@ class ResultHandler {
1265
1263
  }
1266
1264
 
1267
1265
  class TargetResult {
1268
- versionTag;
1269
- testFiles;
1270
1266
  results = new Map();
1271
1267
  status = "runs";
1268
+ testFiles;
1272
1269
  timing = new ResultTiming();
1270
+ versionTag;
1273
1271
  constructor(versionTag, testFiles) {
1274
1272
  this.versionTag = versionTag;
1275
1273
  this.testFiles = testFiles;
@@ -1277,12 +1275,12 @@ class TargetResult {
1277
1275
  }
1278
1276
 
1279
1277
  class TestResult {
1280
- test;
1281
- parent;
1282
1278
  diagnostics = [];
1283
1279
  expectCount = new ResultCount();
1280
+ parent;
1284
1281
  results = [];
1285
1282
  status = "runs";
1283
+ test;
1286
1284
  timing = new ResultTiming();
1287
1285
  constructor(test, parent) {
1288
1286
  this.test = test;
@@ -1328,27 +1326,27 @@ var CancellationReason;
1328
1326
  })(CancellationReason || (CancellationReason = {}));
1329
1327
 
1330
1328
  class Watcher {
1331
- targetPath;
1332
1329
  #abortController = new AbortController();
1333
1330
  #onChanged;
1334
1331
  #onRemoved;
1335
1332
  #recursive;
1333
+ #targetPath;
1336
1334
  #watcher;
1337
- constructor(targetPath, onChanged, onRemoved, recursive) {
1338
- this.targetPath = targetPath;
1335
+ constructor(targetPath, onChanged, onRemoved, options) {
1336
+ this.#targetPath = targetPath;
1339
1337
  this.#onChanged = onChanged;
1340
1338
  this.#onRemoved = onRemoved ?? onChanged;
1341
- this.#recursive = recursive;
1339
+ this.#recursive = options?.recursive;
1342
1340
  }
1343
1341
  close() {
1344
1342
  this.#abortController.abort();
1345
1343
  }
1346
1344
  async watch() {
1347
- this.#watcher = fs.watch(this.targetPath, { recursive: this.#recursive, signal: this.#abortController.signal });
1345
+ this.#watcher = fs.watch(this.#targetPath, { recursive: this.#recursive, signal: this.#abortController.signal });
1348
1346
  try {
1349
1347
  for await (const event of this.#watcher) {
1350
1348
  if (event.filename != null) {
1351
- const filePath = Path.resolve(this.targetPath, event.filename);
1349
+ const filePath = Path.resolve(this.#targetPath, event.filename);
1352
1350
  if (existsSync(filePath)) {
1353
1351
  await this.#onChanged(filePath);
1354
1352
  }
@@ -1375,12 +1373,31 @@ class FileWatcher extends Watcher {
1375
1373
  }
1376
1374
  }
1377
1375
 
1376
+ class DiagnosticOrigin {
1377
+ breadcrumbs;
1378
+ end;
1379
+ sourceFile;
1380
+ start;
1381
+ constructor(start, end, sourceFile, breadcrumbs) {
1382
+ this.start = start;
1383
+ this.end = end;
1384
+ this.sourceFile = sourceFile;
1385
+ this.breadcrumbs = breadcrumbs;
1386
+ }
1387
+ static fromJsonNode(node, sourceFile, skipTrivia) {
1388
+ return new DiagnosticOrigin(skipTrivia(node.pos, sourceFile), node.end, sourceFile);
1389
+ }
1390
+ static fromNode(node, breadcrumbs) {
1391
+ return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), breadcrumbs);
1392
+ }
1393
+ }
1394
+
1378
1395
  class Diagnostic {
1379
- text;
1380
1396
  category;
1381
- origin;
1382
1397
  code;
1383
1398
  related;
1399
+ origin;
1400
+ text;
1384
1401
  constructor(text, category, origin) {
1385
1402
  this.text = text;
1386
1403
  this.category = category;
@@ -1408,11 +1425,7 @@ class Diagnostic {
1408
1425
  let origin;
1409
1426
  const text = compiler.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
1410
1427
  if (Diagnostic.isTsDiagnosticWithLocation(diagnostic)) {
1411
- origin = {
1412
- end: diagnostic.start + diagnostic.length,
1413
- file: diagnostic.file,
1414
- start: diagnostic.start,
1415
- };
1428
+ origin = new DiagnosticOrigin(diagnostic.start, diagnostic.start + diagnostic.length, diagnostic.file);
1416
1429
  }
1417
1430
  return new Diagnostic(text, category, origin).add({ code });
1418
1431
  });
@@ -1599,6 +1612,12 @@ class OptionDiagnosticText {
1599
1612
  return optionName;
1600
1613
  }
1601
1614
  }
1615
+ static testFileMatchCannotStartWith(segment) {
1616
+ return [
1617
+ `A test file match pattern cannot start with '${segment}'.`,
1618
+ "The test files are only collected within the 'rootPath' directory.",
1619
+ ];
1620
+ }
1602
1621
  static requiresValueType(optionName, optionBrand, optionGroup) {
1603
1622
  optionName = OptionDiagnosticText.#optionName(optionName, optionGroup);
1604
1623
  return `Option '${optionName}' requires a value of type ${optionBrand}.`;
@@ -1681,30 +1700,22 @@ class OptionValidator {
1681
1700
  }
1682
1701
  break;
1683
1702
  }
1703
+ case "testFileMatch": {
1704
+ for (const segment of ["/", "../"]) {
1705
+ if (optionValue.startsWith(segment)) {
1706
+ this.#onDiagnostic(Diagnostic.error(OptionDiagnosticText.testFileMatchCannotStartWith(segment), origin));
1707
+ }
1708
+ }
1709
+ break;
1710
+ }
1684
1711
  case "watch": {
1685
1712
  if (Environment.isCi) {
1686
1713
  this.#onDiagnostic(Diagnostic.error(OptionDiagnosticText.watchCannotBeEnabledInCiEnvironment(), origin));
1687
- break;
1688
- }
1689
- if (!this.#isWatchSupported()) {
1690
- this.#onDiagnostic(Diagnostic.error(OptionDiagnosticText.watchIsNotAvailable(), origin));
1691
1714
  }
1692
1715
  break;
1693
1716
  }
1694
1717
  }
1695
1718
  }
1696
- #isWatchSupported() {
1697
- let isRecursiveWatchAvailable;
1698
- try {
1699
- const watcher = watch(Path.resolve("./"), { persistent: false, recursive: true });
1700
- watcher.close();
1701
- isRecursiveWatchAvailable = true;
1702
- }
1703
- catch {
1704
- isRecursiveWatchAvailable = false;
1705
- }
1706
- return isRecursiveWatchAvailable;
1707
- }
1708
1719
  }
1709
1720
 
1710
1721
  class CommandLineOptionsWorker {
@@ -1810,7 +1821,7 @@ class CommandLineOptionsWorker {
1810
1821
  }
1811
1822
 
1812
1823
  class ConfigFileOptionsWorker {
1813
- compiler;
1824
+ #compiler;
1814
1825
  #configFileOptionDefinitions;
1815
1826
  #configFileOptions;
1816
1827
  #configFilePath;
@@ -1819,7 +1830,7 @@ class ConfigFileOptionsWorker {
1819
1830
  #optionValidator;
1820
1831
  #storeService;
1821
1832
  constructor(compiler, configFileOptions, configFilePath, storeService, onDiagnostic) {
1822
- this.compiler = compiler;
1833
+ this.#compiler = compiler;
1823
1834
  this.#configFileOptions = configFileOptions;
1824
1835
  this.#configFilePath = configFilePath;
1825
1836
  this.#storeService = storeService;
@@ -1828,31 +1839,27 @@ class ConfigFileOptionsWorker {
1828
1839
  this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostic);
1829
1840
  }
1830
1841
  #isDoubleQuotedString(node, sourceFile) {
1831
- return (node.kind === this.compiler.SyntaxKind.StringLiteral &&
1842
+ return (node.kind === this.#compiler.SyntaxKind.StringLiteral &&
1832
1843
  sourceFile.text.slice(this.#skipTrivia(node.pos, sourceFile), node.end).startsWith('"'));
1833
1844
  }
1834
1845
  async parse(sourceText) {
1835
- const configSourceFile = this.compiler.parseJsonText(this.#configFilePath, sourceText);
1836
- if (configSourceFile.parseDiagnostics.length > 0) {
1837
- for (const diagnostic of Diagnostic.fromDiagnostics(configSourceFile.parseDiagnostics, this.compiler)) {
1846
+ const sourceFile = this.#compiler.parseJsonText(this.#configFilePath, sourceText);
1847
+ if (sourceFile.parseDiagnostics.length > 0) {
1848
+ for (const diagnostic of Diagnostic.fromDiagnostics(sourceFile.parseDiagnostics, this.#compiler)) {
1838
1849
  this.#onDiagnostic(diagnostic);
1839
1850
  }
1840
1851
  return;
1841
1852
  }
1842
- const rootExpression = configSourceFile.statements[0]?.expression;
1843
- if (rootExpression == null || !this.compiler.isObjectLiteralExpression(rootExpression)) {
1844
- const origin = { end: 0, file: configSourceFile, start: 0 };
1853
+ const rootExpression = sourceFile.statements[0]?.expression;
1854
+ if (rootExpression == null || !this.#compiler.isObjectLiteralExpression(rootExpression)) {
1855
+ const origin = new DiagnosticOrigin(0, 0, sourceFile);
1845
1856
  this.#onDiagnostic(Diagnostic.error("The root value of a configuration file must be an object literal.", origin));
1846
1857
  return;
1847
1858
  }
1848
1859
  for (const property of rootExpression.properties) {
1849
- if (this.compiler.isPropertyAssignment(property)) {
1850
- if (!this.#isDoubleQuotedString(property.name, configSourceFile)) {
1851
- const origin = {
1852
- end: property.end,
1853
- file: configSourceFile,
1854
- start: this.#skipTrivia(property.pos, configSourceFile),
1855
- };
1860
+ if (this.#compiler.isPropertyAssignment(property)) {
1861
+ if (!this.#isDoubleQuotedString(property.name, sourceFile)) {
1862
+ const origin = DiagnosticOrigin.fromJsonNode(property, sourceFile, this.#skipTrivia);
1856
1863
  this.#onDiagnostic(Diagnostic.error(OptionDiagnosticText.doubleQuotesExpected(), origin));
1857
1864
  continue;
1858
1865
  }
@@ -1862,14 +1869,10 @@ class ConfigFileOptionsWorker {
1862
1869
  }
1863
1870
  const optionDefinition = this.#configFileOptionDefinitions.get(optionName);
1864
1871
  if (optionDefinition) {
1865
- this.#configFileOptions[optionDefinition.name] = await this.#parseOptionValue(configSourceFile, property.initializer, optionDefinition);
1872
+ this.#configFileOptions[optionDefinition.name] = await this.#parseOptionValue(sourceFile, property.initializer, optionDefinition);
1866
1873
  }
1867
1874
  else {
1868
- const origin = {
1869
- end: property.end,
1870
- file: configSourceFile,
1871
- start: this.#skipTrivia(property.pos, configSourceFile),
1872
- };
1875
+ const origin = DiagnosticOrigin.fromJsonNode(property, sourceFile, this.#skipTrivia);
1873
1876
  this.#onDiagnostic(Diagnostic.error(OptionDiagnosticText.unknownOption(optionName), origin));
1874
1877
  }
1875
1878
  }
@@ -1878,25 +1881,21 @@ class ConfigFileOptionsWorker {
1878
1881
  }
1879
1882
  async #parseOptionValue(sourceFile, valueExpression, optionDefinition, isListItem = false) {
1880
1883
  switch (valueExpression.kind) {
1881
- case this.compiler.SyntaxKind.TrueKeyword: {
1884
+ case this.#compiler.SyntaxKind.TrueKeyword: {
1882
1885
  if (optionDefinition.brand === "boolean") {
1883
1886
  return true;
1884
1887
  }
1885
1888
  break;
1886
1889
  }
1887
- case this.compiler.SyntaxKind.FalseKeyword: {
1890
+ case this.#compiler.SyntaxKind.FalseKeyword: {
1888
1891
  if (optionDefinition.brand === "boolean") {
1889
1892
  return false;
1890
1893
  }
1891
1894
  break;
1892
1895
  }
1893
- case this.compiler.SyntaxKind.StringLiteral: {
1896
+ case this.#compiler.SyntaxKind.StringLiteral: {
1894
1897
  if (!this.#isDoubleQuotedString(valueExpression, sourceFile)) {
1895
- const origin = {
1896
- end: valueExpression.end,
1897
- file: sourceFile,
1898
- start: this.#skipTrivia(valueExpression.pos, sourceFile),
1899
- };
1898
+ const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
1900
1899
  this.#onDiagnostic(Diagnostic.error(OptionDiagnosticText.doubleQuotesExpected(), origin));
1901
1900
  return;
1902
1901
  }
@@ -1905,17 +1904,13 @@ class ConfigFileOptionsWorker {
1905
1904
  if (optionDefinition.name === "rootPath") {
1906
1905
  value = Path.resolve(Path.dirname(this.#configFilePath), value);
1907
1906
  }
1908
- const origin = {
1909
- end: valueExpression.end,
1910
- file: sourceFile,
1911
- start: this.#skipTrivia(valueExpression.pos, sourceFile),
1912
- };
1907
+ const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
1913
1908
  await this.#optionValidator.check(optionDefinition.name, value, optionDefinition.brand, origin);
1914
1909
  return value;
1915
1910
  }
1916
1911
  break;
1917
1912
  }
1918
- case this.compiler.SyntaxKind.ArrayLiteralExpression: {
1913
+ case this.#compiler.SyntaxKind.ArrayLiteralExpression: {
1919
1914
  if (optionDefinition.brand === "list") {
1920
1915
  const value = [];
1921
1916
  for (const element of valueExpression.elements) {
@@ -1926,14 +1921,10 @@ class ConfigFileOptionsWorker {
1926
1921
  break;
1927
1922
  }
1928
1923
  }
1929
- const origin = {
1930
- end: valueExpression.end,
1931
- file: sourceFile,
1932
- start: this.#skipTrivia(valueExpression.pos, sourceFile),
1933
- };
1934
1924
  const text = isListItem
1935
1925
  ? OptionDiagnosticText.expectsListItemType(optionDefinition.name, optionDefinition.brand)
1936
1926
  : OptionDiagnosticText.requiresValueType(optionDefinition.name, optionDefinition.brand, this.#optionGroup);
1927
+ const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
1937
1928
  this.#onDiagnostic(Diagnostic.error(text, origin));
1938
1929
  return;
1939
1930
  }
@@ -1988,14 +1979,14 @@ const defaultOptions = {
1988
1979
  testFileMatch: ["**/*.tst.*", "**/__typetests__/*.test.*", "**/typetests/*.test.*"],
1989
1980
  };
1990
1981
  class ConfigService {
1991
- compiler;
1992
1982
  #commandLineOptions = {};
1983
+ #compiler;
1993
1984
  #configFileOptions = {};
1994
1985
  #configFilePath = Path.resolve(defaultOptions.rootPath, "./tstyche.config.json");
1995
1986
  #pathMatch = [];
1996
1987
  #storeService;
1997
1988
  constructor(compiler, storeService) {
1998
- this.compiler = compiler;
1989
+ this.#compiler = compiler;
1999
1990
  this.#storeService = storeService;
2000
1991
  }
2001
1992
  #onDiagnostic(diagnostic) {
@@ -2021,7 +2012,7 @@ class ConfigService {
2021
2012
  const configFileText = await fs.readFile(this.#configFilePath, {
2022
2013
  encoding: "utf8",
2023
2014
  });
2024
- const configFileWorker = new ConfigFileOptionsWorker(this.compiler, this.#configFileOptions, this.#configFilePath, this.#storeService, this.#onDiagnostic);
2015
+ const configFileWorker = new ConfigFileOptionsWorker(this.#compiler, this.#configFileOptions, this.#configFilePath, this.#storeService, this.#onDiagnostic);
2025
2016
  await configFileWorker.parse(configFileText);
2026
2017
  }
2027
2018
  resolveConfig() {
@@ -2080,16 +2071,16 @@ class Timer {
2080
2071
  }
2081
2072
 
2082
2073
  class WatchService {
2083
- resolvedConfig;
2084
2074
  #changedTestFiles = new Map();
2085
2075
  #inputService;
2076
+ #resolvedConfig;
2086
2077
  #runCallback;
2087
2078
  #selectService;
2088
2079
  #timer = new Timer();
2089
2080
  #watchers = [];
2090
2081
  #watchedTestFiles;
2091
2082
  constructor(resolvedConfig, runCallback, selectService, testFiles) {
2092
- this.resolvedConfig = resolvedConfig;
2083
+ this.#resolvedConfig = resolvedConfig;
2093
2084
  this.#runCallback = runCallback;
2094
2085
  this.#selectService = selectService;
2095
2086
  this.#watchedTestFiles = new Map(testFiles.map((testFile) => [testFile.path, testFile]));
@@ -2157,39 +2148,37 @@ class WatchService {
2157
2148
  this.#changedTestFiles.delete(filePath);
2158
2149
  this.#watchedTestFiles.delete(filePath);
2159
2150
  if (this.#watchedTestFiles.size === 0) {
2160
- this.#onDiagnostic(Diagnostic.error(OptionDiagnosticText.noTestFilesWereLeft(this.resolvedConfig)));
2151
+ this.#onDiagnostic(Diagnostic.error(OptionDiagnosticText.noTestFilesWereLeft(this.#resolvedConfig)));
2161
2152
  }
2162
2153
  };
2163
- this.#watchers.push(new Watcher(this.resolvedConfig.rootPath, onChangedFile, onRemovedFile, true));
2154
+ this.#watchers.push(new Watcher(this.#resolvedConfig.rootPath, onChangedFile, onRemovedFile, { recursive: true }));
2164
2155
  const onChangedConfigFile = () => {
2165
2156
  cancellationToken?.cancel("configChange");
2166
2157
  };
2167
- this.#watchers.push(new FileWatcher(this.resolvedConfig.configFilePath, onChangedConfigFile));
2158
+ this.#watchers.push(new FileWatcher(this.#resolvedConfig.configFilePath, onChangedConfigFile));
2168
2159
  return Promise.all(this.#watchers.map((watcher) => watcher.watch()));
2169
2160
  }
2170
2161
  }
2171
2162
 
2172
2163
  class TestMember {
2173
2164
  brand;
2174
- node;
2175
- parent;
2176
- flags;
2177
- compiler;
2178
2165
  diagnostics = new Set();
2166
+ flags;
2179
2167
  members = [];
2180
2168
  name = "";
2181
- constructor(brand, node, parent, flags) {
2169
+ node;
2170
+ parent;
2171
+ constructor(compiler, brand, node, parent, flags) {
2182
2172
  this.brand = brand;
2183
2173
  this.node = node;
2184
2174
  this.parent = parent;
2185
2175
  this.flags = flags;
2186
- this.compiler = parent.compiler;
2187
- if (node.arguments[0] != null && this.compiler.isStringLiteralLike(node.arguments[0])) {
2176
+ if (node.arguments[0] != null && compiler.isStringLiteralLike(node.arguments[0])) {
2188
2177
  this.name = node.arguments[0].text;
2189
2178
  }
2190
2179
  if (node.arguments[1] != null &&
2191
- parent.compiler.isFunctionLike(node.arguments[1]) &&
2192
- parent.compiler.isBlock(node.arguments[1].body)) {
2180
+ compiler.isFunctionLike(node.arguments[1]) &&
2181
+ compiler.isBlock(node.arguments[1].body)) {
2193
2182
  const blockStart = node.arguments[1].body.getStart();
2194
2183
  const blockEnd = node.arguments[1].body.getEnd();
2195
2184
  for (const diagnostic of parent.diagnostics) {
@@ -2216,11 +2205,7 @@ class TestMember {
2216
2205
  case "describe": {
2217
2206
  for (const member of this.members) {
2218
2207
  if (member.brand === "expect") {
2219
- diagnostics.push(Diagnostic.error(getText(member.node), {
2220
- end: member.node.getEnd(),
2221
- file: member.node.getSourceFile(),
2222
- start: member.node.getStart(),
2223
- }));
2208
+ diagnostics.push(Diagnostic.error(getText(member.node), DiagnosticOrigin.fromNode(member.node)));
2224
2209
  }
2225
2210
  }
2226
2211
  break;
@@ -2229,11 +2214,7 @@ class TestMember {
2229
2214
  case "expect": {
2230
2215
  for (const member of this.members) {
2231
2216
  if (member.brand !== "expect") {
2232
- diagnostics.push(Diagnostic.error(getText(member.node), {
2233
- end: member.node.getEnd(),
2234
- file: member.node.getSourceFile(),
2235
- start: member.node.getStart(),
2236
- }));
2217
+ diagnostics.push(Diagnostic.error(getText(member.node), DiagnosticOrigin.fromNode(member.node)));
2237
2218
  }
2238
2219
  }
2239
2220
  break;
@@ -2244,16 +2225,15 @@ class TestMember {
2244
2225
  }
2245
2226
 
2246
2227
  class Assertion extends TestMember {
2228
+ isNot;
2247
2229
  matcherNode;
2248
2230
  modifierNode;
2249
2231
  notNode;
2250
- isNot;
2251
- constructor(brand, node, parent, flags, matcherNode, modifierNode, notNode) {
2252
- super(brand, node, parent, flags);
2232
+ constructor(compiler, brand, node, parent, flags, matcherNode, modifierNode, notNode) {
2233
+ super(compiler, brand, node, parent, flags);
2234
+ this.isNot = notNode != null;
2253
2235
  this.matcherNode = matcherNode;
2254
2236
  this.modifierNode = modifierNode;
2255
- this.notNode = notNode;
2256
- this.isNot = notNode != null;
2257
2237
  const argStart = this.source[0]?.getStart();
2258
2238
  const argEnd = this.source[0]?.getEnd();
2259
2239
  for (const diagnostic of parent.diagnostics) {
@@ -2295,11 +2275,11 @@ class Assertion extends TestMember {
2295
2275
  }
2296
2276
 
2297
2277
  class IdentifierLookup {
2298
- compiler;
2278
+ #compiler;
2299
2279
  #identifiers;
2300
2280
  #moduleSpecifiers = ['"tstyche"', "'tstyche'"];
2301
2281
  constructor(compiler, identifiers) {
2302
- this.compiler = compiler;
2282
+ this.#compiler = compiler;
2303
2283
  this.#identifiers = identifiers ?? {
2304
2284
  namedImports: {
2305
2285
  describe: undefined,
@@ -2321,7 +2301,7 @@ class IdentifierLookup {
2321
2301
  if (this.#moduleSpecifiers.includes(node.moduleSpecifier.getText()) &&
2322
2302
  node.importClause?.isTypeOnly !== true &&
2323
2303
  node.importClause?.namedBindings != null) {
2324
- if (this.compiler.isNamedImports(node.importClause.namedBindings)) {
2304
+ if (this.#compiler.isNamedImports(node.importClause.namedBindings)) {
2325
2305
  for (const element of node.importClause.namedBindings.elements) {
2326
2306
  if (element.isTypeOnly) {
2327
2307
  continue;
@@ -2338,7 +2318,7 @@ class IdentifierLookup {
2338
2318
  }
2339
2319
  }
2340
2320
  }
2341
- if (this.compiler.isNamespaceImport(node.importClause.namedBindings)) {
2321
+ if (this.#compiler.isNamespaceImport(node.importClause.namedBindings)) {
2342
2322
  this.#identifiers.namespace = node.importClause.namedBindings.name.getText();
2343
2323
  }
2344
2324
  }
@@ -2346,7 +2326,7 @@ class IdentifierLookup {
2346
2326
  resolveTestMemberMeta(node) {
2347
2327
  let flags = 0;
2348
2328
  let expression = node.expression;
2349
- while (this.compiler.isPropertyAccessExpression(expression)) {
2329
+ while (this.#compiler.isPropertyAccessExpression(expression)) {
2350
2330
  if (expression.expression.getText() === this.#identifiers.namespace) {
2351
2331
  break;
2352
2332
  }
@@ -2371,7 +2351,7 @@ class IdentifierLookup {
2371
2351
  expression = expression.expression;
2372
2352
  }
2373
2353
  let identifierName;
2374
- if (this.compiler.isPropertyAccessExpression(expression) &&
2354
+ if (this.#compiler.isPropertyAccessExpression(expression) &&
2375
2355
  expression.expression.getText() === this.#identifiers.namespace) {
2376
2356
  identifierName = expression.name.getText();
2377
2357
  }
@@ -2395,12 +2375,10 @@ class IdentifierLookup {
2395
2375
  }
2396
2376
 
2397
2377
  class TestTree {
2398
- compiler;
2399
2378
  diagnostics;
2400
- sourceFile;
2401
2379
  members = [];
2402
- constructor(compiler, diagnostics, sourceFile) {
2403
- this.compiler = compiler;
2380
+ sourceFile;
2381
+ constructor(diagnostics, sourceFile) {
2404
2382
  this.diagnostics = diagnostics;
2405
2383
  this.sourceFile = sourceFile;
2406
2384
  }
@@ -2413,8 +2391,8 @@ class TestTree {
2413
2391
  }
2414
2392
 
2415
2393
  class CollectService {
2416
- compiler;
2417
- matcherIdentifiers = [
2394
+ #compiler;
2395
+ #matcherIdentifiers = [
2418
2396
  "toBe",
2419
2397
  "toBeAny",
2420
2398
  "toBeAssignable",
@@ -2436,69 +2414,69 @@ class CollectService {
2436
2414
  "toMatch",
2437
2415
  "toRaiseError",
2438
2416
  ];
2439
- modifierIdentifiers = ["type"];
2440
- notIdentifier = "not";
2417
+ #modifierIdentifiers = ["type"];
2418
+ #notIdentifier = "not";
2441
2419
  constructor(compiler) {
2442
- this.compiler = compiler;
2420
+ this.#compiler = compiler;
2443
2421
  }
2444
2422
  #collectTestMembers(node, identifiers, parent) {
2445
- if (this.compiler.isBlock(node)) {
2446
- this.compiler.forEachChild(node, (node) => {
2447
- this.#collectTestMembers(node, new IdentifierLookup(this.compiler, identifiers.clone()), parent);
2423
+ if (this.#compiler.isBlock(node)) {
2424
+ this.#compiler.forEachChild(node, (node) => {
2425
+ this.#collectTestMembers(node, new IdentifierLookup(this.#compiler, identifiers.clone()), parent);
2448
2426
  });
2449
2427
  return;
2450
2428
  }
2451
- if (this.compiler.isCallExpression(node)) {
2429
+ if (this.#compiler.isCallExpression(node)) {
2452
2430
  const meta = identifiers.resolveTestMemberMeta(node);
2453
2431
  if (meta != null && (meta.brand === "describe" || meta.brand === "test")) {
2454
- const testMember = new TestMember(meta.brand, node, parent, meta.flags);
2432
+ const testMember = new TestMember(this.#compiler, meta.brand, node, parent, meta.flags);
2455
2433
  parent.members.push(testMember);
2456
- this.compiler.forEachChild(node, (node) => {
2434
+ this.#compiler.forEachChild(node, (node) => {
2457
2435
  this.#collectTestMembers(node, identifiers, testMember);
2458
2436
  });
2459
2437
  return;
2460
2438
  }
2461
2439
  if (meta != null && meta.brand === "expect") {
2462
- const modifierNode = this.#getMatchingChainNode(node, this.modifierIdentifiers);
2440
+ const modifierNode = this.#getMatchingChainNode(node, this.#modifierIdentifiers);
2463
2441
  if (!modifierNode) {
2464
2442
  return;
2465
2443
  }
2466
- const notNode = this.#getMatchingChainNode(modifierNode, [this.notIdentifier]);
2467
- const matcherNode = this.#getMatchingChainNode(notNode ?? modifierNode, this.matcherIdentifiers)?.parent;
2444
+ const notNode = this.#getMatchingChainNode(modifierNode, [this.#notIdentifier]);
2445
+ const matcherNode = this.#getMatchingChainNode(notNode ?? modifierNode, this.#matcherIdentifiers)?.parent;
2468
2446
  if (matcherNode == null || !this.#isMatcherNode(matcherNode)) {
2469
2447
  return;
2470
2448
  }
2471
- const assertion = new Assertion(meta.brand, node, parent, meta.flags, matcherNode, modifierNode, notNode);
2449
+ const assertion = new Assertion(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, modifierNode, notNode);
2472
2450
  parent.members.push(assertion);
2473
- this.compiler.forEachChild(node, (node) => {
2451
+ this.#compiler.forEachChild(node, (node) => {
2474
2452
  this.#collectTestMembers(node, identifiers, assertion);
2475
2453
  });
2476
2454
  return;
2477
2455
  }
2478
2456
  }
2479
- if (this.compiler.isImportDeclaration(node)) {
2457
+ if (this.#compiler.isImportDeclaration(node)) {
2480
2458
  identifiers.handleImportDeclaration(node);
2481
2459
  return;
2482
2460
  }
2483
- if (this.compiler.isVariableDeclaration(node)) ;
2484
- if (this.compiler.isBinaryExpression(node)) ;
2485
- this.compiler.forEachChild(node, (node) => {
2461
+ if (this.#compiler.isVariableDeclaration(node)) ;
2462
+ if (this.#compiler.isBinaryExpression(node)) ;
2463
+ this.#compiler.forEachChild(node, (node) => {
2486
2464
  this.#collectTestMembers(node, identifiers, parent);
2487
2465
  });
2488
2466
  }
2489
2467
  createTestTree(sourceFile, semanticDiagnostics = []) {
2490
- const testTree = new TestTree(this.compiler, new Set(semanticDiagnostics), sourceFile);
2491
- this.#collectTestMembers(sourceFile, new IdentifierLookup(this.compiler), testTree);
2468
+ const testTree = new TestTree(new Set(semanticDiagnostics), sourceFile);
2469
+ this.#collectTestMembers(sourceFile, new IdentifierLookup(this.#compiler), testTree);
2492
2470
  return testTree;
2493
2471
  }
2494
2472
  #getMatchingChainNode({ parent }, name) {
2495
- if (this.compiler.isPropertyAccessExpression(parent) && name.includes(parent.name.getText())) {
2473
+ if (this.#compiler.isPropertyAccessExpression(parent) && name.includes(parent.name.getText())) {
2496
2474
  return parent;
2497
2475
  }
2498
2476
  return;
2499
2477
  }
2500
2478
  #isMatcherNode(node) {
2501
- return this.compiler.isCallExpression(node) && this.compiler.isPropertyAccessExpression(node.expression);
2479
+ return this.#compiler.isCallExpression(node) && this.#compiler.isPropertyAccessExpression(node.expression);
2502
2480
  }
2503
2481
  }
2504
2482
 
@@ -2518,8 +2496,8 @@ var TestMemberFlags;
2518
2496
  })(TestMemberFlags || (TestMemberFlags = {}));
2519
2497
 
2520
2498
  class PrimitiveTypeMatcher {
2521
- typeChecker;
2522
2499
  #targetTypeFlag;
2500
+ typeChecker;
2523
2501
  constructor(typeChecker, targetTypeFlag) {
2524
2502
  this.typeChecker = typeChecker;
2525
2503
  this.#targetTypeFlag = targetTypeFlag;
@@ -2538,8 +2516,8 @@ class PrimitiveTypeMatcher {
2538
2516
  }
2539
2517
 
2540
2518
  class RelationMatcherBase {
2541
- typeChecker;
2542
2519
  relationExplanationVerb = "is";
2520
+ typeChecker;
2543
2521
  constructor(typeChecker) {
2544
2522
  this.typeChecker = typeChecker;
2545
2523
  }
@@ -2729,8 +2707,8 @@ class ToRaiseError {
2729
2707
  }
2730
2708
 
2731
2709
  class Expect {
2732
- compiler;
2733
- typeChecker;
2710
+ #compiler;
2711
+ #typeChecker;
2734
2712
  toBe;
2735
2713
  toBeAny;
2736
2714
  toBeAssignable;
@@ -2752,36 +2730,36 @@ class Expect {
2752
2730
  toMatch;
2753
2731
  toRaiseError;
2754
2732
  constructor(compiler, typeChecker) {
2755
- this.compiler = compiler;
2756
- this.typeChecker = typeChecker;
2757
- this.toBe = new ToBe(this.typeChecker);
2758
- this.toBeAny = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Any);
2759
- this.toBeAssignable = new ToBeAssignableWith(this.typeChecker);
2760
- this.toBeAssignableTo = new ToBeAssignableTo(this.typeChecker);
2761
- this.toBeAssignableWith = new ToBeAssignableWith(this.typeChecker);
2762
- this.toBeBigInt = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.BigInt);
2763
- this.toBeBoolean = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Boolean);
2764
- this.toBeNever = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Never);
2765
- this.toBeNull = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Null);
2766
- this.toBeNumber = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Number);
2767
- this.toBeString = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.String);
2768
- this.toBeSymbol = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.ESSymbol);
2769
- this.toBeUndefined = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Undefined);
2770
- this.toBeUniqueSymbol = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.UniqueESSymbol);
2771
- this.toBeUnknown = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Unknown);
2772
- this.toBeVoid = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Void);
2773
- this.toEqual = new ToBe(this.typeChecker);
2774
- this.toHaveProperty = new ToHaveProperty(this.compiler, this.typeChecker);
2775
- this.toMatch = new ToMatch(this.typeChecker);
2776
- this.toRaiseError = new ToRaiseError(this.compiler, this.typeChecker);
2733
+ this.#compiler = compiler;
2734
+ this.#typeChecker = typeChecker;
2735
+ this.toBe = new ToBe(typeChecker);
2736
+ this.toBeAny = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.Any);
2737
+ this.toBeAssignable = new ToBeAssignableWith(typeChecker);
2738
+ this.toBeAssignableTo = new ToBeAssignableTo(typeChecker);
2739
+ this.toBeAssignableWith = new ToBeAssignableWith(typeChecker);
2740
+ this.toBeBigInt = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.BigInt);
2741
+ this.toBeBoolean = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.Boolean);
2742
+ this.toBeNever = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.Never);
2743
+ this.toBeNull = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.Null);
2744
+ this.toBeNumber = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.Number);
2745
+ this.toBeString = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.String);
2746
+ this.toBeSymbol = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.ESSymbol);
2747
+ this.toBeUndefined = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.Undefined);
2748
+ this.toBeUniqueSymbol = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.UniqueESSymbol);
2749
+ this.toBeUnknown = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.Unknown);
2750
+ this.toBeVoid = new PrimitiveTypeMatcher(typeChecker, compiler.TypeFlags.Void);
2751
+ this.toEqual = new ToBe(typeChecker);
2752
+ this.toHaveProperty = new ToHaveProperty(compiler, typeChecker);
2753
+ this.toMatch = new ToMatch(typeChecker);
2754
+ this.toRaiseError = new ToRaiseError(compiler, typeChecker);
2777
2755
  }
2778
2756
  static assertTypeChecker(typeChecker) {
2779
2757
  return "isTypeRelatedTo" in typeChecker && "relation" in typeChecker;
2780
2758
  }
2781
2759
  #getType(node) {
2782
- return this.compiler.isExpression(node)
2783
- ? this.typeChecker.getTypeAtLocation(node)
2784
- : this.typeChecker.getTypeFromTypeNode(node);
2760
+ return this.#compiler.isExpression(node)
2761
+ ? this.#typeChecker.getTypeAtLocation(node)
2762
+ : this.#typeChecker.getTypeFromTypeNode(node);
2785
2763
  }
2786
2764
  #getTypes(nodes) {
2787
2765
  return nodes.map((node) => this.#getType(node));
@@ -2790,10 +2768,10 @@ class Expect {
2790
2768
  return types.every((type) => this.#isStringOrNumberLiteralType(type));
2791
2769
  }
2792
2770
  #isStringOrNumberLiteralType(type) {
2793
- return Boolean(type.flags & this.compiler.TypeFlags.StringOrNumberLiteral);
2771
+ return Boolean(type.flags & this.#compiler.TypeFlags.StringOrNumberLiteral);
2794
2772
  }
2795
2773
  #isUniqueSymbolType(type) {
2796
- return Boolean(type.flags & this.compiler.TypeFlags.UniqueESSymbol);
2774
+ return Boolean(type.flags & this.#compiler.TypeFlags.UniqueESSymbol);
2797
2775
  }
2798
2776
  match(assertion, expectResult) {
2799
2777
  const matcherNameText = assertion.matcherName.getText();
@@ -2839,9 +2817,9 @@ class Expect {
2839
2817
  return;
2840
2818
  }
2841
2819
  const sourceType = this.#getType(assertion.source[0]);
2842
- const nonPrimitiveType = { flags: this.compiler.TypeFlags.NonPrimitive };
2843
- if (sourceType.flags & (this.compiler.TypeFlags.Any | this.compiler.TypeFlags.Never) ||
2844
- !this.typeChecker.isTypeRelatedTo(sourceType, nonPrimitiveType, this.typeChecker.relation.assignable)) {
2820
+ const nonPrimitiveType = { flags: this.#compiler.TypeFlags.NonPrimitive };
2821
+ if (sourceType.flags & (this.#compiler.TypeFlags.Any | this.#compiler.TypeFlags.Never) ||
2822
+ !this.#typeChecker.isTypeRelatedTo(sourceType, nonPrimitiveType, this.#typeChecker.relation.assignable)) {
2845
2823
  this.#onSourceArgumentMustBeObjectType(assertion.source[0], expectResult);
2846
2824
  return;
2847
2825
  }
@@ -2880,29 +2858,17 @@ class Expect {
2880
2858
  `'.${matcherNameText}()' is deprecated and will be removed in TSTyche 3.`,
2881
2859
  "To learn more, visit https://tstyche.org/release-notes/tstyche-2",
2882
2860
  ];
2883
- const origin = {
2884
- end: assertion.matcherName.getEnd(),
2885
- file: assertion.matcherName.getSourceFile(),
2886
- start: assertion.matcherName.getStart(),
2887
- };
2861
+ const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
2888
2862
  EventEmitter.dispatch(["deprecation:info", { diagnostics: [Diagnostic.warning(text, origin)] }]);
2889
2863
  }
2890
2864
  #onKeyArgumentMustBeOfType(node, expectResult) {
2891
- const receivedTypeText = this.typeChecker.typeToString(this.#getType(node));
2865
+ const receivedTypeText = this.#typeChecker.typeToString(this.#getType(node));
2892
2866
  const text = `An argument for 'key' must be of type 'string | number | symbol', received: '${receivedTypeText}'.`;
2893
- const origin = {
2894
- end: node.getEnd(),
2895
- file: node.getSourceFile(),
2896
- start: node.getStart(),
2897
- };
2867
+ const origin = DiagnosticOrigin.fromNode(node);
2898
2868
  EventEmitter.dispatch(["expect:error", { diagnostics: [Diagnostic.error(text, origin)], result: expectResult }]);
2899
2869
  }
2900
2870
  #onKeyArgumentMustBeProvided(assertion, expectResult) {
2901
- const origin = {
2902
- end: assertion.matcherName.getEnd(),
2903
- file: assertion.matcherName.getSourceFile(),
2904
- start: assertion.matcherName.getStart(),
2905
- };
2871
+ const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
2906
2872
  EventEmitter.dispatch([
2907
2873
  "expect:error",
2908
2874
  {
@@ -2913,11 +2879,7 @@ class Expect {
2913
2879
  }
2914
2880
  #onNotSupportedMatcherName(assertion, expectResult) {
2915
2881
  const matcherNameText = assertion.matcherName.getText();
2916
- const origin = {
2917
- end: assertion.matcherName.getEnd(),
2918
- file: assertion.matcherName.getSourceFile(),
2919
- start: assertion.matcherName.getStart(),
2920
- };
2882
+ const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
2921
2883
  EventEmitter.dispatch([
2922
2884
  "expect:error",
2923
2885
  {
@@ -2927,22 +2889,14 @@ class Expect {
2927
2889
  ]);
2928
2890
  }
2929
2891
  #onSourceArgumentMustBeObjectType(node, expectResult) {
2930
- const sourceText = this.compiler.isTypeNode(node) ? "A type argument for 'Source'" : "An argument for 'source'";
2931
- const receivedTypeText = this.typeChecker.typeToString(this.#getType(node));
2892
+ const sourceText = this.#compiler.isTypeNode(node) ? "A type argument for 'Source'" : "An argument for 'source'";
2893
+ const receivedTypeText = this.#typeChecker.typeToString(this.#getType(node));
2932
2894
  const text = `${sourceText} must be of an object type, received: '${receivedTypeText}'.`;
2933
- const origin = {
2934
- end: node.getEnd(),
2935
- file: node.getSourceFile(),
2936
- start: node.getStart(),
2937
- };
2895
+ const origin = DiagnosticOrigin.fromNode(node);
2938
2896
  EventEmitter.dispatch(["expect:error", { diagnostics: [Diagnostic.error(text, origin)], result: expectResult }]);
2939
2897
  }
2940
2898
  #onSourceArgumentMustBeProvided(assertion, expectResult) {
2941
- const origin = {
2942
- end: assertion.node.getEnd(),
2943
- file: assertion.node.getSourceFile(),
2944
- start: assertion.node.getStart(),
2945
- };
2899
+ const origin = DiagnosticOrigin.fromNode(assertion.node);
2946
2900
  EventEmitter.dispatch([
2947
2901
  "expect:error",
2948
2902
  {
@@ -2954,11 +2908,7 @@ class Expect {
2954
2908
  ]);
2955
2909
  }
2956
2910
  #onTargetArgumentMustBeProvided(assertion, expectResult) {
2957
- const origin = {
2958
- end: assertion.matcherName.getEnd(),
2959
- file: assertion.matcherName.getSourceFile(),
2960
- start: assertion.matcherName.getStart(),
2961
- };
2911
+ const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
2962
2912
  EventEmitter.dispatch([
2963
2913
  "expect:error",
2964
2914
  {
@@ -2974,12 +2924,8 @@ class Expect {
2974
2924
  for (const node of nodes) {
2975
2925
  const receivedType = this.#getType(node);
2976
2926
  if (!this.#isStringOrNumberLiteralType(receivedType)) {
2977
- const receivedTypeText = this.typeChecker.typeToString(this.#getType(node));
2978
- const origin = {
2979
- end: node.getEnd(),
2980
- file: node.getSourceFile(),
2981
- start: node.getStart(),
2982
- };
2927
+ const receivedTypeText = this.#typeChecker.typeToString(this.#getType(node));
2928
+ const origin = DiagnosticOrigin.fromNode(node);
2983
2929
  diagnostics.push(Diagnostic.error(`An argument for 'target' must be of type 'string | number', received: '${receivedTypeText}'.`, origin));
2984
2930
  }
2985
2931
  }
@@ -3019,10 +2965,10 @@ class Version {
3019
2965
  }
3020
2966
 
3021
2967
  class ProjectService {
3022
- compiler;
2968
+ #compiler;
3023
2969
  #service;
3024
2970
  constructor(compiler) {
3025
- this.compiler = compiler;
2971
+ this.#compiler = compiler;
3026
2972
  function doNothing() {
3027
2973
  }
3028
2974
  function returnFalse() {
@@ -3044,7 +2990,7 @@ class ProjectService {
3044
2990
  startGroup: doNothing,
3045
2991
  };
3046
2992
  const host = {
3047
- ...this.compiler.sys,
2993
+ ...this.#compiler.sys,
3048
2994
  clearImmediate,
3049
2995
  clearTimeout,
3050
2996
  setImmediate,
@@ -3052,9 +2998,9 @@ class ProjectService {
3052
2998
  watchDirectory: () => noopWatcher,
3053
2999
  watchFile: () => noopWatcher,
3054
3000
  };
3055
- this.#service = new this.compiler.server.ProjectService({
3001
+ this.#service = new this.#compiler.server.ProjectService({
3056
3002
  allowLocalPluginLoads: true,
3057
- cancellationToken: this.compiler.server.nullCancellationToken,
3003
+ cancellationToken: this.#compiler.server.nullCancellationToken,
3058
3004
  host,
3059
3005
  logger: noopLogger,
3060
3006
  session: undefined,
@@ -3079,17 +3025,17 @@ class ProjectService {
3079
3025
  strictNullChecks: true,
3080
3026
  target: "esnext",
3081
3027
  };
3082
- if (Version.isSatisfiedWith(this.compiler.version, "5.4")) {
3028
+ if (Version.isSatisfiedWith(this.#compiler.version, "5.4")) {
3083
3029
  defaultCompilerOptions.module = "preserve";
3084
3030
  }
3085
- if (Version.isSatisfiedWith(this.compiler.version, "5.0")) {
3031
+ if (Version.isSatisfiedWith(this.#compiler.version, "5.0")) {
3086
3032
  defaultCompilerOptions["allowImportingTsExtensions"] = true;
3087
3033
  defaultCompilerOptions.moduleResolution = "bundler";
3088
3034
  }
3089
3035
  return defaultCompilerOptions;
3090
3036
  }
3091
3037
  getDefaultProject(filePath) {
3092
- return this.#service.getDefaultProjectForFile(this.compiler.server.toNormalizedPath(filePath), true);
3038
+ return this.#service.getDefaultProjectForFile(this.#compiler.server.toNormalizedPath(filePath), true);
3093
3039
  }
3094
3040
  getLanguageService(filePath) {
3095
3041
  const project = this.getDefaultProject(filePath);
@@ -3102,30 +3048,30 @@ class ProjectService {
3102
3048
  const { configFileErrors, configFileName } = this.#service.openClientFile(filePath, sourceText, undefined, projectRootPath);
3103
3049
  EventEmitter.dispatch([
3104
3050
  "project:info",
3105
- { compilerVersion: this.compiler.version, projectConfigFilePath: configFileName },
3051
+ { compilerVersion: this.#compiler.version, projectConfigFilePath: configFileName },
3106
3052
  ]);
3107
3053
  if (configFileErrors && configFileErrors.length > 0) {
3108
3054
  EventEmitter.dispatch([
3109
3055
  "project:error",
3110
- { diagnostics: Diagnostic.fromDiagnostics(configFileErrors, this.compiler) },
3056
+ { diagnostics: Diagnostic.fromDiagnostics(configFileErrors, this.#compiler) },
3111
3057
  ]);
3112
3058
  }
3113
3059
  }
3114
3060
  }
3115
3061
 
3116
3062
  class TestTreeWorker {
3117
- resolvedConfig;
3118
- compiler;
3063
+ #compiler;
3119
3064
  #cancellationToken;
3120
3065
  #expect;
3121
3066
  #fileResult;
3122
3067
  #hasOnly;
3123
3068
  #position;
3069
+ #resolvedConfig;
3124
3070
  constructor(resolvedConfig, compiler, expect, options) {
3125
- this.resolvedConfig = resolvedConfig;
3126
- this.compiler = compiler;
3127
- this.#cancellationToken = options.cancellationToken;
3071
+ this.#resolvedConfig = resolvedConfig;
3072
+ this.#compiler = compiler;
3128
3073
  this.#expect = expect;
3074
+ this.#cancellationToken = options.cancellationToken;
3129
3075
  this.#fileResult = options.fileResult;
3130
3076
  this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
3131
3077
  this.#position = options.position;
@@ -3135,11 +3081,11 @@ class TestTreeWorker {
3135
3081
  mode |= 1;
3136
3082
  }
3137
3083
  if (member.flags & 2 ||
3138
- (this.resolvedConfig.only != null && member.name.toLowerCase().includes(this.resolvedConfig.only.toLowerCase()))) {
3084
+ (this.#resolvedConfig.only != null && member.name.toLowerCase().includes(this.#resolvedConfig.only.toLowerCase()))) {
3139
3085
  mode |= 2;
3140
3086
  }
3141
3087
  if (member.flags & 4 ||
3142
- (this.resolvedConfig.skip != null && member.name.toLowerCase().includes(this.resolvedConfig.skip.toLowerCase()))) {
3088
+ (this.#resolvedConfig.skip != null && member.name.toLowerCase().includes(this.#resolvedConfig.skip.toLowerCase()))) {
3143
3089
  mode |= 4;
3144
3090
  }
3145
3091
  if (member.flags & 8) {
@@ -3196,7 +3142,7 @@ class TestTreeWorker {
3196
3142
  EventEmitter.dispatch([
3197
3143
  "expect:error",
3198
3144
  {
3199
- diagnostics: Diagnostic.fromDiagnostics([...assertion.diagnostics], this.compiler),
3145
+ diagnostics: Diagnostic.fromDiagnostics([...assertion.diagnostics], this.#compiler),
3200
3146
  result: expectResult,
3201
3147
  },
3202
3148
  ]);
@@ -3209,11 +3155,7 @@ class TestTreeWorker {
3209
3155
  if (assertion.isNot ? !matchResult.isMatch : matchResult.isMatch) {
3210
3156
  if (runMode & 1) {
3211
3157
  const text = ["The assertion was supposed to fail, but it passed.", "Consider removing the '.fail' flag."];
3212
- const origin = {
3213
- end: assertion.node.getEnd(),
3214
- file: assertion.node.getSourceFile(),
3215
- start: assertion.node.getStart(),
3216
- };
3158
+ const origin = DiagnosticOrigin.fromNode(assertion.node);
3217
3159
  EventEmitter.dispatch([
3218
3160
  "expect:error",
3219
3161
  { diagnostics: [Diagnostic.error(text, origin)], result: expectResult },
@@ -3227,19 +3169,13 @@ class TestTreeWorker {
3227
3169
  EventEmitter.dispatch(["expect:pass", { result: expectResult }]);
3228
3170
  }
3229
3171
  else {
3230
- const origin = {
3231
- breadcrumbs: assertion.ancestorNames,
3232
- end: assertion.matcherName.getEnd(),
3233
- file: assertion.matcherName.getSourceFile(),
3234
- start: assertion.matcherName.getStart(),
3235
- };
3236
- const diagnostics = [];
3237
- for (const diagnostic of matchResult.explain()) {
3172
+ const origin = DiagnosticOrigin.fromNode(assertion.matcherName, assertion.ancestorNames);
3173
+ const diagnostics = matchResult.explain().map((diagnostic) => {
3238
3174
  if (diagnostic.origin == null) {
3239
- diagnostic.add({ origin });
3175
+ return diagnostic.add({ origin });
3240
3176
  }
3241
- diagnostics.push(diagnostic);
3242
- }
3177
+ return diagnostic;
3178
+ });
3243
3179
  EventEmitter.dispatch(["expect:fail", { diagnostics, result: expectResult }]);
3244
3180
  }
3245
3181
  }
@@ -3252,7 +3188,7 @@ class TestTreeWorker {
3252
3188
  EventEmitter.dispatch([
3253
3189
  "file:error",
3254
3190
  {
3255
- diagnostics: Diagnostic.fromDiagnostics([...describe.diagnostics], this.compiler),
3191
+ diagnostics: Diagnostic.fromDiagnostics([...describe.diagnostics], this.#compiler),
3256
3192
  result: this.#fileResult,
3257
3193
  },
3258
3194
  ]);
@@ -3274,7 +3210,7 @@ class TestTreeWorker {
3274
3210
  EventEmitter.dispatch([
3275
3211
  "test:error",
3276
3212
  {
3277
- diagnostics: Diagnostic.fromDiagnostics([...test.diagnostics], this.compiler),
3213
+ diagnostics: Diagnostic.fromDiagnostics([...test.diagnostics], this.#compiler),
3278
3214
  result: testResult,
3279
3215
  },
3280
3216
  ]);
@@ -3295,13 +3231,13 @@ class TestTreeWorker {
3295
3231
  }
3296
3232
 
3297
3233
  class TestFileRunner {
3298
- resolvedConfig;
3299
- compiler;
3234
+ #compiler;
3300
3235
  #collectService;
3236
+ #resolvedConfig;
3301
3237
  #projectService;
3302
3238
  constructor(resolvedConfig, compiler) {
3303
- this.resolvedConfig = resolvedConfig;
3304
- this.compiler = compiler;
3239
+ this.#resolvedConfig = resolvedConfig;
3240
+ this.#compiler = compiler;
3305
3241
  this.#collectService = new CollectService(compiler);
3306
3242
  this.#projectService = new ProjectService(compiler);
3307
3243
  }
@@ -3309,7 +3245,7 @@ class TestFileRunner {
3309
3245
  if (cancellationToken?.isCancellationRequested === true) {
3310
3246
  return;
3311
3247
  }
3312
- this.#projectService.openFile(testFile.path, undefined, this.resolvedConfig.rootPath);
3248
+ this.#projectService.openFile(testFile.path, undefined, this.#resolvedConfig.rootPath);
3313
3249
  const fileResult = new FileResult(testFile);
3314
3250
  EventEmitter.dispatch(["file:start", { result: fileResult }]);
3315
3251
  this.#runFile(testFile, fileResult, cancellationToken);
@@ -3326,7 +3262,7 @@ class TestFileRunner {
3326
3262
  EventEmitter.dispatch([
3327
3263
  "file:error",
3328
3264
  {
3329
- diagnostics: Diagnostic.fromDiagnostics(syntacticDiagnostics, this.compiler),
3265
+ diagnostics: Diagnostic.fromDiagnostics(syntacticDiagnostics, this.#compiler),
3330
3266
  result: fileResult,
3331
3267
  },
3332
3268
  ]);
@@ -3346,7 +3282,7 @@ class TestFileRunner {
3346
3282
  EventEmitter.dispatch([
3347
3283
  "file:error",
3348
3284
  {
3349
- diagnostics: Diagnostic.fromDiagnostics([...testTree.diagnostics], this.compiler),
3285
+ diagnostics: Diagnostic.fromDiagnostics([...testTree.diagnostics], this.#compiler),
3350
3286
  result: fileResult,
3351
3287
  },
3352
3288
  ]);
@@ -3358,8 +3294,8 @@ class TestFileRunner {
3358
3294
  EventEmitter.dispatch(["file:error", { diagnostics: [Diagnostic.error(text)], result: fileResult }]);
3359
3295
  return;
3360
3296
  }
3361
- const expect = new Expect(this.compiler, typeChecker);
3362
- const testTreeWorker = new TestTreeWorker(this.resolvedConfig, this.compiler, expect, {
3297
+ const expect = new Expect(this.#compiler, typeChecker);
3298
+ const testTreeWorker = new TestTreeWorker(this.#resolvedConfig, this.#compiler, expect, {
3363
3299
  cancellationToken,
3364
3300
  fileResult,
3365
3301
  hasOnly: testTree.hasOnly,
@@ -3370,12 +3306,12 @@ class TestFileRunner {
3370
3306
  }
3371
3307
 
3372
3308
  class TaskRunner {
3373
- resolvedConfig;
3374
3309
  #eventEmitter = new EventEmitter();
3310
+ #resolvedConfig;
3375
3311
  #selectService;
3376
3312
  #storeService;
3377
3313
  constructor(resolvedConfig, selectService, storeService) {
3378
- this.resolvedConfig = resolvedConfig;
3314
+ this.#resolvedConfig = resolvedConfig;
3379
3315
  this.#selectService = selectService;
3380
3316
  this.#storeService = storeService;
3381
3317
  this.#eventEmitter.addHandler(new ResultHandler());
@@ -3385,11 +3321,11 @@ class TaskRunner {
3385
3321
  }
3386
3322
  async run(testFiles, cancellationToken = new CancellationToken()) {
3387
3323
  let cancellationHandler;
3388
- if (this.resolvedConfig.failFast) {
3324
+ if (this.#resolvedConfig.failFast) {
3389
3325
  cancellationHandler = new CancellationHandler(cancellationToken, "failFast");
3390
3326
  this.#eventEmitter.addHandler(cancellationHandler);
3391
3327
  }
3392
- if (this.resolvedConfig.watch === true) {
3328
+ if (this.#resolvedConfig.watch === true) {
3393
3329
  await this.#watch(testFiles, cancellationToken);
3394
3330
  }
3395
3331
  else {
@@ -3400,14 +3336,14 @@ class TaskRunner {
3400
3336
  }
3401
3337
  }
3402
3338
  async #run(testFiles, cancellationToken) {
3403
- const result = new Result(this.resolvedConfig, testFiles);
3339
+ const result = new Result(this.#resolvedConfig, testFiles);
3404
3340
  EventEmitter.dispatch(["run:start", { result }]);
3405
- for (const versionTag of this.resolvedConfig.target) {
3341
+ for (const versionTag of this.#resolvedConfig.target) {
3406
3342
  const targetResult = new TargetResult(versionTag, testFiles);
3407
3343
  EventEmitter.dispatch(["target:start", { result: targetResult }]);
3408
3344
  const compiler = await this.#storeService.load(versionTag, cancellationToken);
3409
3345
  if (compiler) {
3410
- const testFileRunner = new TestFileRunner(this.resolvedConfig, compiler);
3346
+ const testFileRunner = new TestFileRunner(this.#resolvedConfig, compiler);
3411
3347
  for (const testFile of testFiles) {
3412
3348
  testFileRunner.run(testFile, cancellationToken);
3413
3349
  }
@@ -3424,7 +3360,7 @@ class TaskRunner {
3424
3360
  const runCallback = async (testFiles) => {
3425
3361
  await this.#run(testFiles, cancellationToken);
3426
3362
  };
3427
- const watchModeManager = new WatchService(this.resolvedConfig, runCallback, this.#selectService, testFiles);
3363
+ const watchModeManager = new WatchService(this.#resolvedConfig, runCallback, this.#selectService, testFiles);
3428
3364
  cancellationToken?.onCancellationRequested((reason) => {
3429
3365
  if (reason !== "failFast") {
3430
3366
  watchModeManager.close();
@@ -3435,26 +3371,26 @@ class TaskRunner {
3435
3371
  }
3436
3372
 
3437
3373
  class TSTyche {
3438
- resolvedConfig;
3439
3374
  #eventEmitter = new EventEmitter();
3440
3375
  #outputService;
3376
+ #resolvedConfig;
3441
3377
  #selectService;
3442
3378
  #storeService;
3443
3379
  #taskRunner;
3444
- static version = "2.0.0-beta.1";
3380
+ static version = "2.0.0-rc.1";
3445
3381
  constructor(resolvedConfig, outputService, selectService, storeService) {
3446
- this.resolvedConfig = resolvedConfig;
3382
+ this.#resolvedConfig = resolvedConfig;
3447
3383
  this.#outputService = outputService;
3448
3384
  this.#selectService = selectService;
3449
3385
  this.#storeService = storeService;
3450
- this.#taskRunner = new TaskRunner(this.resolvedConfig, this.#selectService, this.#storeService);
3386
+ this.#taskRunner = new TaskRunner(this.#resolvedConfig, this.#selectService, this.#storeService);
3451
3387
  }
3452
3388
  close() {
3453
3389
  this.#taskRunner.close();
3454
3390
  }
3455
3391
  async run(testFiles, cancellationToken = new CancellationToken()) {
3456
- this.#eventEmitter.addHandler(new RuntimeReporter(this.resolvedConfig, this.#outputService));
3457
- if (this.resolvedConfig.watch === true) {
3392
+ this.#eventEmitter.addHandler(new RuntimeReporter(this.#resolvedConfig, this.#outputService));
3393
+ if (this.#resolvedConfig.watch === true) {
3458
3394
  this.#eventEmitter.addHandler(new WatchReporter(this.#outputService));
3459
3395
  }
3460
3396
  else {
@@ -3472,6 +3408,9 @@ class GlobPattern {
3472
3408
  let resultPattern = "\\.";
3473
3409
  let optionalSegmentCount = 0;
3474
3410
  for (const segment of segments) {
3411
+ if (segment === ".") {
3412
+ continue;
3413
+ }
3475
3414
  if (segment === "**") {
3476
3415
  resultPattern += "(\\/(?!(node_modules)(\\/|$))[^./][^/]*)*?";
3477
3416
  continue;
@@ -3507,11 +3446,11 @@ class GlobPattern {
3507
3446
  }
3508
3447
 
3509
3448
  class SelectService {
3510
- resolvedConfig;
3511
3449
  #includeDirectoryRegex;
3512
3450
  #includeFileRegex;
3451
+ #resolvedConfig;
3513
3452
  constructor(resolvedConfig) {
3514
- this.resolvedConfig = resolvedConfig;
3453
+ this.#resolvedConfig = resolvedConfig;
3515
3454
  this.#includeDirectoryRegex = GlobPattern.toRegex(resolvedConfig.testFileMatch, "directories");
3516
3455
  this.#includeFileRegex = GlobPattern.toRegex(resolvedConfig.testFileMatch, "files");
3517
3456
  }
@@ -3519,14 +3458,14 @@ class SelectService {
3519
3458
  return this.#includeDirectoryRegex.test(directoryPath);
3520
3459
  }
3521
3460
  #isFileIncluded(filePath) {
3522
- if (this.resolvedConfig.pathMatch.length > 0 &&
3523
- !this.resolvedConfig.pathMatch.some((match) => filePath.toLowerCase().includes(match.toLowerCase()))) {
3461
+ if (this.#resolvedConfig.pathMatch.length > 0 &&
3462
+ !this.#resolvedConfig.pathMatch.some((match) => filePath.toLowerCase().includes(match.toLowerCase()))) {
3524
3463
  return false;
3525
3464
  }
3526
3465
  return this.#includeFileRegex.test(filePath);
3527
3466
  }
3528
3467
  isTestFile(filePath) {
3529
- return this.#isFileIncluded(Path.relative(this.resolvedConfig.rootPath, filePath));
3468
+ return this.#isFileIncluded(Path.relative(this.#resolvedConfig.rootPath, filePath));
3530
3469
  }
3531
3470
  #onDiagnostic(diagnostic) {
3532
3471
  EventEmitter.dispatch(["select:error", { diagnostics: [diagnostic] }]);
@@ -3536,12 +3475,12 @@ class SelectService {
3536
3475
  const testFilePaths = [];
3537
3476
  await this.#visitDirectory(currentPath, testFilePaths);
3538
3477
  if (testFilePaths.length === 0) {
3539
- this.#onDiagnostic(Diagnostic.error(OptionDiagnosticText.noTestFilesWereSelected(this.resolvedConfig)));
3478
+ this.#onDiagnostic(Diagnostic.error(OptionDiagnosticText.noTestFilesWereSelected(this.#resolvedConfig)));
3540
3479
  }
3541
3480
  return testFilePaths.sort();
3542
3481
  }
3543
3482
  async #visitDirectory(currentPath, testFilePaths) {
3544
- const targetPath = Path.join(this.resolvedConfig.rootPath, currentPath);
3483
+ const targetPath = Path.join(this.#resolvedConfig.rootPath, currentPath);
3545
3484
  let entries = [];
3546
3485
  try {
3547
3486
  entries = await fs.readdir(targetPath, { withFileTypes: true });
@@ -3573,6 +3512,21 @@ class SelectService {
3573
3512
  }
3574
3513
  }
3575
3514
 
3515
+ class StoreDiagnosticText {
3516
+ static failedToFetchMetadata(registryUrl) {
3517
+ return `Failed to fetch metadata of the 'typescript' package from '${registryUrl.toString()}'.`;
3518
+ }
3519
+ static failedWithStatusCode(code) {
3520
+ return `Request failed with status code ${String(code)}.`;
3521
+ }
3522
+ static maybeNetworkConnectionIssue() {
3523
+ return "Might be there is an issue with the registry or the network connection.";
3524
+ }
3525
+ static setupTimeoutExceeded(timeout) {
3526
+ return `Setup timeout of ${String(timeout / 1000)}s was exceeded.`;
3527
+ }
3528
+ }
3529
+
3576
3530
  class ManifestWorker {
3577
3531
  #manifestFileName = "store-manifest.json";
3578
3532
  #manifestFilePath;
@@ -3593,35 +3547,6 @@ class ManifestWorker {
3593
3547
  }
3594
3548
  return manifest;
3595
3549
  }
3596
- async #fetch() {
3597
- return new Promise((resolve, reject) => {
3598
- const request = https.get(new URL("typescript", this.#registryUrl), {
3599
- headers: { accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*" },
3600
- timeout: this.#timeout,
3601
- }, (response) => {
3602
- if (response.statusCode !== 200) {
3603
- reject(new Error(`Request failed with status code ${String(response.statusCode)}.`));
3604
- response.resume();
3605
- return;
3606
- }
3607
- response.setEncoding("utf8");
3608
- let rawData = "";
3609
- response.on("data", (chunk) => {
3610
- rawData += chunk;
3611
- });
3612
- response.on("end", () => {
3613
- const packageMetadata = JSON.parse(rawData);
3614
- resolve(packageMetadata);
3615
- });
3616
- });
3617
- request.on("error", (error) => {
3618
- reject(error);
3619
- });
3620
- request.on("timeout", () => {
3621
- request.destroy();
3622
- });
3623
- });
3624
- }
3625
3550
  isOutdated(manifest, ageTolerance = 0) {
3626
3551
  if (Date.now() - manifest.lastUpdated > 2 * 60 * 60 * 1000 + ageTolerance * 1000) {
3627
3552
  return true;
@@ -3637,20 +3562,35 @@ class ManifestWorker {
3637
3562
  };
3638
3563
  let packageMetadata;
3639
3564
  try {
3640
- packageMetadata = await this.#fetch();
3565
+ const response = await fetch(new URL("typescript", this.#registryUrl), {
3566
+ headers: { accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*" },
3567
+ signal: AbortSignal.timeout(this.#timeout),
3568
+ });
3569
+ if (!response.ok) {
3570
+ this.#onDiagnostic(Diagnostic.error([
3571
+ StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
3572
+ StoreDiagnosticText.failedWithStatusCode(response.status),
3573
+ ]));
3574
+ return;
3575
+ }
3576
+ packageMetadata = (await response.json());
3641
3577
  }
3642
3578
  catch (error) {
3643
3579
  if (options?.quite === true) {
3644
3580
  return;
3645
3581
  }
3646
- const text = [`Failed to fetch metadata of the 'typescript' package from '${this.#registryUrl.toString()}'.`];
3647
- if (error instanceof Error && "code" in error && error.code === "ECONNRESET") {
3648
- text.push(`Setup timeout of ${String(this.#timeout / 1000)}s was exceeded.`);
3582
+ if (error instanceof Error && error.name === "TimeoutError") {
3583
+ this.#onDiagnostic(Diagnostic.error([
3584
+ StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
3585
+ StoreDiagnosticText.setupTimeoutExceeded(this.#timeout),
3586
+ ]));
3649
3587
  }
3650
3588
  else {
3651
- text.push("Might be there is an issue with the registry or the network connection.");
3589
+ this.#onDiagnostic(Diagnostic.error([
3590
+ StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
3591
+ StoreDiagnosticText.maybeNetworkConnectionIssue(),
3592
+ ]));
3652
3593
  }
3653
- this.#onDiagnostic(Diagnostic.fromError(text, error));
3654
3594
  return;
3655
3595
  }
3656
3596
  manifest.versions = Object.keys(packageMetadata.versions)
@@ -4068,10 +4008,10 @@ class Cli {
4068
4008
  };
4069
4009
  const onRemoved = () => {
4070
4010
  };
4071
- watchers.push(new Watcher(resolvedConfig.rootPath, onChangedTestFile, onRemoved, true));
4011
+ watchers.push(new Watcher(resolvedConfig.rootPath, onChangedTestFile, onRemoved, { recursive: true }));
4072
4012
  }
4073
4013
  return Promise.all(watchers.map((watcher) => watcher.watch()));
4074
4014
  }
4075
4015
  }
4076
4016
 
4077
- export { Assertion, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, Environment, EventEmitter, ExitCodeHandler, Expect, ExpectResult, FileResult, FileWatcher, InputService, Line, OptionBrand, OptionDefinitionsMap, OptionDiagnosticText, OptionGroup, OutputService, Path, ProjectResult, ProjectService, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, RuntimeReporter, Scribbler, 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 };
4017
+ export { Assertion, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Environment, EventEmitter, ExitCodeHandler, Expect, ExpectResult, FileResult, FileWatcher, InputService, Line, OptionBrand, OptionDefinitionsMap, OptionDiagnosticText, OptionGroup, OutputService, Path, ProjectResult, ProjectService, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, RuntimeReporter, Scribbler, 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 };