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