tstyche 5.0.0-beta.0 → 5.0.0-beta.2

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
@@ -50,70 +50,41 @@ class EventEmitter {
50
50
  }
51
51
  }
52
52
 
53
- class Path {
54
- static normalizeSlashes;
55
- static {
56
- if (path.sep === "/") {
57
- Path.normalizeSlashes = (filePath) => filePath;
58
- }
59
- else {
60
- Path.normalizeSlashes = (filePath) => filePath.replace(/\\/g, "/");
61
- }
62
- }
63
- static dirname(filePath) {
64
- return Path.normalizeSlashes(path.dirname(filePath));
65
- }
66
- static join(...filePaths) {
67
- return Path.normalizeSlashes(path.join(...filePaths));
68
- }
69
- static relative(from, to) {
70
- const relativePath = Path.normalizeSlashes(path.relative(from, to));
71
- if (/^\.\.?\//.test(relativePath)) {
72
- return relativePath;
73
- }
74
- return `./${relativePath}`;
75
- }
76
- static resolve(...filePaths) {
77
- return Path.normalizeSlashes(path.resolve(...filePaths));
78
- }
79
- }
80
-
81
- class SourceFile {
82
- fileName;
83
- #lineMap;
53
+ class JsonNode {
54
+ origin;
84
55
  text;
85
- constructor(fileName, text) {
86
- this.fileName = fileName;
56
+ constructor(text, origin) {
57
+ this.origin = origin;
87
58
  this.text = text;
88
- this.#lineMap = this.#createLineMap();
89
59
  }
90
- #createLineMap() {
91
- const result = [0];
92
- let position = 0;
93
- while (position < this.text.length) {
94
- if (this.text.charAt(position - 1) === "\r") {
95
- position++;
96
- }
97
- if (this.text.charAt(position - 1) === "\n") {
98
- result.push(position);
99
- }
100
- position++;
60
+ getValue(options) {
61
+ if (this.text == null) {
62
+ return undefined;
101
63
  }
102
- result.push(position);
103
- return result;
104
- }
105
- getLineStarts() {
106
- return this.#lineMap;
107
- }
108
- getLineAndCharacterOfPosition(position) {
109
- const line = this.#lineMap.findLastIndex((line) => line <= position);
110
- const character = position - this.#lineMap[line];
111
- return { line, character };
64
+ if (/^['"]/.test(this.text)) {
65
+ return this.text.slice(1, -1);
66
+ }
67
+ if (options?.expectsIdentifier) {
68
+ return this.text;
69
+ }
70
+ if (this.text === "true") {
71
+ return true;
72
+ }
73
+ if (this.text === "false") {
74
+ return false;
75
+ }
76
+ if (/^\d/.test(this.text)) {
77
+ return Number.parseFloat(this.text);
78
+ }
79
+ return undefined;
112
80
  }
113
81
  }
114
82
 
115
83
  class SourceService {
116
84
  static #files = new Map();
85
+ static delete(filePath) {
86
+ SourceService.#files.delete(filePath);
87
+ }
117
88
  static get(source) {
118
89
  const file = SourceService.#files.get(source.fileName);
119
90
  if (file != null) {
@@ -225,6 +196,171 @@ var DiagnosticCategory;
225
196
  DiagnosticCategory["Warning"] = "warning";
226
197
  })(DiagnosticCategory || (DiagnosticCategory = {}));
227
198
 
199
+ class JsonScanner {
200
+ #end;
201
+ #position;
202
+ #previousPosition;
203
+ #sourceFile;
204
+ constructor(sourceFile, options) {
205
+ this.#end = options?.end ?? sourceFile.text.length;
206
+ this.#position = options?.start ?? 0;
207
+ this.#previousPosition = options?.start ?? 0;
208
+ this.#sourceFile = sourceFile;
209
+ }
210
+ #getOrigin() {
211
+ return new DiagnosticOrigin(this.#previousPosition, this.#position, this.#sourceFile);
212
+ }
213
+ isRead() {
214
+ return !(this.#position < this.#end);
215
+ }
216
+ #peekCharacter() {
217
+ return this.#sourceFile.text.charAt(this.#position);
218
+ }
219
+ #peekNextCharacter() {
220
+ return this.#sourceFile.text.charAt(this.#position + 1);
221
+ }
222
+ peekToken(token) {
223
+ this.#skipTrivia();
224
+ return this.#peekCharacter() === token;
225
+ }
226
+ read() {
227
+ this.#skipTrivia();
228
+ this.#previousPosition = this.#position;
229
+ if (/[\s,:\]}]/.test(this.#peekCharacter())) {
230
+ return new JsonNode(undefined, this.#getOrigin());
231
+ }
232
+ let text = "";
233
+ let closingTokenText = "";
234
+ if (/[[{'"]/.test(this.#peekCharacter())) {
235
+ text += this.#readCharacter();
236
+ switch (text) {
237
+ case "[":
238
+ closingTokenText = "]";
239
+ break;
240
+ case "{":
241
+ closingTokenText = "}";
242
+ break;
243
+ default:
244
+ closingTokenText = text;
245
+ }
246
+ }
247
+ while (!this.isRead()) {
248
+ text += this.#readCharacter();
249
+ if (text.slice(-1) === closingTokenText || (!closingTokenText && /[\s,:\]}]/.test(this.#peekCharacter()))) {
250
+ break;
251
+ }
252
+ }
253
+ return new JsonNode(text, this.#getOrigin());
254
+ }
255
+ #readCharacter() {
256
+ return this.#sourceFile.text.charAt(this.#position++);
257
+ }
258
+ readToken(token) {
259
+ this.#skipTrivia();
260
+ this.#previousPosition = this.#position;
261
+ const character = this.#peekCharacter();
262
+ if (typeof token === "string" ? token === character : token.test(character)) {
263
+ this.#position++;
264
+ return new JsonNode(character, this.#getOrigin());
265
+ }
266
+ return new JsonNode(undefined, this.#getOrigin());
267
+ }
268
+ #skipTrivia() {
269
+ while (!this.isRead()) {
270
+ if (/\s/.test(this.#peekCharacter())) {
271
+ this.#position++;
272
+ continue;
273
+ }
274
+ if (this.#peekCharacter() === "/") {
275
+ if (this.#peekNextCharacter() === "/") {
276
+ this.#position += 2;
277
+ while (!this.isRead()) {
278
+ if (this.#readCharacter() === "\n") {
279
+ break;
280
+ }
281
+ }
282
+ continue;
283
+ }
284
+ if (this.#peekNextCharacter() === "*") {
285
+ this.#position += 2;
286
+ while (!this.isRead()) {
287
+ if (this.#peekCharacter() === "*" && this.#peekNextCharacter() === "/") {
288
+ this.#position += 2;
289
+ break;
290
+ }
291
+ this.#position++;
292
+ }
293
+ continue;
294
+ }
295
+ }
296
+ break;
297
+ }
298
+ this.#previousPosition = this.#position;
299
+ }
300
+ }
301
+
302
+ class JsonSourceFile {
303
+ fileName;
304
+ #lineMap;
305
+ text;
306
+ constructor(fileName, text) {
307
+ this.fileName = fileName;
308
+ this.text = text;
309
+ this.#lineMap = this.#createLineMap();
310
+ }
311
+ #createLineMap() {
312
+ const result = [0];
313
+ let position = 0;
314
+ while (position < this.text.length) {
315
+ if (this.text.charAt(position - 1) === "\r") {
316
+ position++;
317
+ }
318
+ if (this.text.charAt(position - 1) === "\n") {
319
+ result.push(position);
320
+ }
321
+ position++;
322
+ }
323
+ result.push(position);
324
+ return result;
325
+ }
326
+ getLineStarts() {
327
+ return this.#lineMap;
328
+ }
329
+ getLineAndCharacterOfPosition(position) {
330
+ const line = this.#lineMap.findLastIndex((line) => line <= position);
331
+ const character = position - this.#lineMap[line];
332
+ return { line, character };
333
+ }
334
+ }
335
+
336
+ class Path {
337
+ static normalizeSlashes;
338
+ static {
339
+ if (path.sep === "/") {
340
+ Path.normalizeSlashes = (filePath) => filePath;
341
+ }
342
+ else {
343
+ Path.normalizeSlashes = (filePath) => filePath.replace(/\\/g, "/");
344
+ }
345
+ }
346
+ static dirname(filePath) {
347
+ return Path.normalizeSlashes(path.dirname(filePath));
348
+ }
349
+ static join(...filePaths) {
350
+ return Path.normalizeSlashes(path.join(...filePaths));
351
+ }
352
+ static relative(from, to) {
353
+ const relativePath = Path.normalizeSlashes(path.relative(from, to));
354
+ if (/^\.\.?\//.test(relativePath)) {
355
+ return relativePath;
356
+ }
357
+ return `./${relativePath}`;
358
+ }
359
+ static resolve(...filePaths) {
360
+ return Path.normalizeSlashes(path.resolve(...filePaths));
361
+ }
362
+ }
363
+
228
364
  class ConfigDiagnosticText {
229
365
  static expected(element) {
230
366
  return `Expected ${element}.`;
@@ -1470,139 +1606,6 @@ const defaultOptions = {
1470
1606
  tsconfig: "findup",
1471
1607
  };
1472
1608
 
1473
- class JsonNode {
1474
- origin;
1475
- text;
1476
- constructor(text, origin) {
1477
- this.origin = origin;
1478
- this.text = text;
1479
- }
1480
- getValue(options) {
1481
- if (this.text == null) {
1482
- return undefined;
1483
- }
1484
- if (/^['"]/.test(this.text)) {
1485
- return this.text.slice(1, -1);
1486
- }
1487
- if (options?.expectsIdentifier) {
1488
- return this.text;
1489
- }
1490
- if (this.text === "true") {
1491
- return true;
1492
- }
1493
- if (this.text === "false") {
1494
- return false;
1495
- }
1496
- if (/^\d/.test(this.text)) {
1497
- return Number.parseFloat(this.text);
1498
- }
1499
- return undefined;
1500
- }
1501
- }
1502
-
1503
- class JsonScanner {
1504
- #end;
1505
- #position;
1506
- #previousPosition;
1507
- #sourceFile;
1508
- constructor(sourceFile, options) {
1509
- this.#end = options?.end ?? sourceFile.text.length;
1510
- this.#position = options?.start ?? 0;
1511
- this.#previousPosition = options?.start ?? 0;
1512
- this.#sourceFile = sourceFile;
1513
- }
1514
- #getOrigin() {
1515
- return new DiagnosticOrigin(this.#previousPosition, this.#position, this.#sourceFile);
1516
- }
1517
- isRead() {
1518
- return !(this.#position < this.#end);
1519
- }
1520
- #peekCharacter() {
1521
- return this.#sourceFile.text.charAt(this.#position);
1522
- }
1523
- #peekNextCharacter() {
1524
- return this.#sourceFile.text.charAt(this.#position + 1);
1525
- }
1526
- peekToken(token) {
1527
- this.#skipTrivia();
1528
- return this.#peekCharacter() === token;
1529
- }
1530
- read() {
1531
- this.#skipTrivia();
1532
- this.#previousPosition = this.#position;
1533
- if (/[\s,:\]}]/.test(this.#peekCharacter())) {
1534
- return new JsonNode(undefined, this.#getOrigin());
1535
- }
1536
- let text = "";
1537
- let closingTokenText = "";
1538
- if (/[[{'"]/.test(this.#peekCharacter())) {
1539
- text += this.#readCharacter();
1540
- switch (text) {
1541
- case "[":
1542
- closingTokenText = "]";
1543
- break;
1544
- case "{":
1545
- closingTokenText = "}";
1546
- break;
1547
- default:
1548
- closingTokenText = text;
1549
- }
1550
- }
1551
- while (!this.isRead()) {
1552
- text += this.#readCharacter();
1553
- if (text.slice(-1) === closingTokenText || (!closingTokenText && /[\s,:\]}]/.test(this.#peekCharacter()))) {
1554
- break;
1555
- }
1556
- }
1557
- return new JsonNode(text, this.#getOrigin());
1558
- }
1559
- #readCharacter() {
1560
- return this.#sourceFile.text.charAt(this.#position++);
1561
- }
1562
- readToken(token) {
1563
- this.#skipTrivia();
1564
- this.#previousPosition = this.#position;
1565
- const character = this.#peekCharacter();
1566
- if (typeof token === "string" ? token === character : token.test(character)) {
1567
- this.#position++;
1568
- return new JsonNode(character, this.#getOrigin());
1569
- }
1570
- return new JsonNode(undefined, this.#getOrigin());
1571
- }
1572
- #skipTrivia() {
1573
- while (!this.isRead()) {
1574
- if (/\s/.test(this.#peekCharacter())) {
1575
- this.#position++;
1576
- continue;
1577
- }
1578
- if (this.#peekCharacter() === "/") {
1579
- if (this.#peekNextCharacter() === "/") {
1580
- this.#position += 2;
1581
- while (!this.isRead()) {
1582
- if (this.#readCharacter() === "\n") {
1583
- break;
1584
- }
1585
- }
1586
- continue;
1587
- }
1588
- if (this.#peekNextCharacter() === "*") {
1589
- this.#position += 2;
1590
- while (!this.isRead()) {
1591
- if (this.#peekCharacter() === "*" && this.#peekNextCharacter() === "/") {
1592
- this.#position += 2;
1593
- break;
1594
- }
1595
- this.#position++;
1596
- }
1597
- continue;
1598
- }
1599
- }
1600
- break;
1601
- }
1602
- this.#previousPosition = this.#position;
1603
- }
1604
- }
1605
-
1606
1609
  class Config {
1607
1610
  static #onDiagnostics(diagnostic) {
1608
1611
  EventEmitter.dispatch(["config:error", { diagnostics: [diagnostic] }]);
@@ -1623,7 +1626,7 @@ class Config {
1623
1626
  const configFileText = await fs.readFile(configFilePath, {
1624
1627
  encoding: "utf8",
1625
1628
  });
1626
- const sourceFile = new SourceFile(configFilePath, configFileText);
1629
+ const sourceFile = new JsonSourceFile(configFilePath, configFileText);
1627
1630
  const configFileParser = new ConfigParser(configFileOptions, 4, sourceFile, new JsonScanner(sourceFile), Config.#onDiagnostics);
1628
1631
  await configFileParser.parse();
1629
1632
  }
@@ -1827,19 +1830,33 @@ class ExitCodeHandler {
1827
1830
  }
1828
1831
  }
1829
1832
 
1830
- class ResultTiming {
1831
- end = Number.NaN;
1832
- start = Number.NaN;
1833
- get duration() {
1834
- return this.end - this.start;
1835
- }
1833
+ function createObjectFromKeys(keys, defaultValue) {
1834
+ return Object.fromEntries(keys.map((key) => [key, defaultValue]));
1835
+ }
1836
+ function createTargetCounts() {
1837
+ return createObjectFromKeys(["failed", "passed"], 0);
1838
+ }
1839
+ function createFileCounts() {
1840
+ return createObjectFromKeys(["failed", "passed"], 0);
1841
+ }
1842
+ function createTestCounts() {
1843
+ return createObjectFromKeys(["failed", "passed", "fixme", "skipped", "todo"], 0);
1844
+ }
1845
+ function createAssertionCounts() {
1846
+ return createObjectFromKeys(["failed", "passed", "fixme", "skipped", "todo"], 0);
1847
+ }
1848
+ function createSuppressedCounts() {
1849
+ return createObjectFromKeys(["failed", "matched", "ignored"], 0);
1850
+ }
1851
+ function createResultTiming() {
1852
+ return createObjectFromKeys(["start", "end"], Number.NaN);
1836
1853
  }
1837
1854
 
1838
1855
  class DescribeResult {
1839
1856
  describe;
1840
1857
  parent;
1841
1858
  results = [];
1842
- timing = new ResultTiming();
1859
+ timing = createResultTiming();
1843
1860
  constructor(describe, parent) {
1844
1861
  this.describe = describe;
1845
1862
  this.parent = parent;
@@ -1848,35 +1865,23 @@ class DescribeResult {
1848
1865
 
1849
1866
  class ExpectResult {
1850
1867
  expect;
1851
- diagnostics = [];
1852
1868
  parent;
1853
1869
  status = "runs";
1854
- timing = new ResultTiming();
1870
+ timing = createResultTiming();
1855
1871
  constructor(expect, parent) {
1856
1872
  this.expect = expect;
1857
1873
  this.parent = parent;
1858
1874
  }
1859
1875
  }
1860
1876
 
1861
- class ResultCount {
1862
- failed = 0;
1863
- passed = 0;
1864
- skipped = 0;
1865
- fixme = 0;
1866
- todo = 0;
1867
- get total() {
1868
- return this.failed + this.passed + this.skipped + this.fixme + this.todo;
1869
- }
1870
- }
1871
-
1872
1877
  class FileResult {
1873
- diagnostics = [];
1874
- expectCount = new ResultCount();
1878
+ assertionCounts = createAssertionCounts();
1875
1879
  file;
1876
1880
  results = [];
1881
+ suppressedCounts = createSuppressedCounts();
1877
1882
  status = "runs";
1878
- testCount = new ResultCount();
1879
- timing = new ResultTiming();
1883
+ testCounts = createTestCounts();
1884
+ timing = createResultTiming();
1880
1885
  constructor(file) {
1881
1886
  this.file = file;
1882
1887
  }
@@ -1884,7 +1889,6 @@ class FileResult {
1884
1889
 
1885
1890
  class ProjectResult {
1886
1891
  compilerVersion;
1887
- diagnostics = [];
1888
1892
  projectConfigFilePath;
1889
1893
  results = [];
1890
1894
  constructor(compilerVersion, projectConfigFilePath) {
@@ -1894,13 +1898,14 @@ class ProjectResult {
1894
1898
  }
1895
1899
 
1896
1900
  class Result {
1897
- expectCount = new ResultCount();
1898
- fileCount = new ResultCount();
1901
+ assertionCounts = createAssertionCounts();
1902
+ fileCounts = createFileCounts();
1899
1903
  files;
1900
1904
  results = [];
1901
- targetCount = new ResultCount();
1902
- testCount = new ResultCount();
1903
- timing = new ResultTiming();
1905
+ suppressedCounts = createSuppressedCounts();
1906
+ targetCounts = createTargetCounts();
1907
+ testCounts = createTestCounts();
1908
+ timing = createResultTiming();
1904
1909
  constructor(files) {
1905
1910
  this.files = files;
1906
1911
  }
@@ -1910,18 +1915,27 @@ var ResultStatus;
1910
1915
  (function (ResultStatus) {
1911
1916
  ResultStatus["Runs"] = "runs";
1912
1917
  ResultStatus["Passed"] = "passed";
1918
+ ResultStatus["Matched"] = "matched";
1913
1919
  ResultStatus["Failed"] = "failed";
1914
- ResultStatus["Skipped"] = "skipped";
1915
1920
  ResultStatus["Fixme"] = "fixme";
1921
+ ResultStatus["Skipped"] = "skipped";
1922
+ ResultStatus["Ignored"] = "ignored";
1916
1923
  ResultStatus["Todo"] = "todo";
1917
1924
  })(ResultStatus || (ResultStatus = {}));
1918
1925
 
1926
+ class SuppressedResult {
1927
+ suppressed;
1928
+ constructor(suppressed) {
1929
+ this.suppressed = suppressed;
1930
+ }
1931
+ }
1932
+
1919
1933
  class TargetResult {
1920
1934
  files;
1921
1935
  results = new Map();
1922
1936
  status = "runs";
1923
1937
  target;
1924
- timing = new ResultTiming();
1938
+ timing = createResultTiming();
1925
1939
  constructor(target, files) {
1926
1940
  this.target = target;
1927
1941
  this.files = files;
@@ -1929,13 +1943,12 @@ class TargetResult {
1929
1943
  }
1930
1944
 
1931
1945
  class TestResult {
1932
- diagnostics = [];
1933
- expectCount = new ResultCount();
1946
+ assertionCounts = createAssertionCounts();
1934
1947
  parent;
1935
1948
  results = [];
1936
1949
  status = "runs";
1937
1950
  test;
1938
- timing = new ResultTiming();
1951
+ timing = createResultTiming();
1939
1952
  constructor(test, parent) {
1940
1953
  this.test = test;
1941
1954
  this.parent = parent;
@@ -1967,10 +1980,10 @@ class ResultHandler {
1967
1980
  break;
1968
1981
  case "target:end":
1969
1982
  if (this.#targetResult.status === "failed") {
1970
- this.#result.targetCount.failed++;
1983
+ this.#result.targetCounts.failed++;
1971
1984
  }
1972
1985
  else {
1973
- this.#result.targetCount.passed++;
1986
+ this.#result.targetCounts.passed++;
1974
1987
  this.#targetResult.status = "passed";
1975
1988
  }
1976
1989
  this.#targetResult.timing.end = Date.now();
@@ -1992,7 +2005,6 @@ class ResultHandler {
1992
2005
  }
1993
2006
  case "project:error":
1994
2007
  this.#targetResult.status = "failed";
1995
- this.#projectResult.diagnostics.push(...payload.diagnostics);
1996
2008
  break;
1997
2009
  case "file:start":
1998
2010
  this.#projectResult.results.push(payload.result);
@@ -2004,18 +2016,17 @@ class ResultHandler {
2004
2016
  case "collect:error":
2005
2017
  this.#targetResult.status = "failed";
2006
2018
  this.#fileResult.status = "failed";
2007
- this.#fileResult.diagnostics.push(...payload.diagnostics);
2008
2019
  break;
2009
2020
  case "file:end":
2010
2021
  if (this.#fileResult.status === "failed" ||
2011
- this.#fileResult.expectCount.failed > 0 ||
2012
- this.#fileResult.testCount.failed > 0) {
2013
- this.#result.fileCount.failed++;
2022
+ this.#fileResult.assertionCounts.failed > 0 ||
2023
+ this.#fileResult.testCounts.failed > 0) {
2024
+ this.#result.fileCounts.failed++;
2014
2025
  this.#targetResult.status = "failed";
2015
2026
  this.#fileResult.status = "failed";
2016
2027
  }
2017
2028
  else {
2018
- this.#result.fileCount.passed++;
2029
+ this.#result.fileCounts.passed++;
2019
2030
  this.#fileResult.status = "passed";
2020
2031
  }
2021
2032
  this.#fileResult.timing.end = Date.now();
@@ -2046,44 +2057,37 @@ class ResultHandler {
2046
2057
  this.#testResult.timing.start = Date.now();
2047
2058
  break;
2048
2059
  case "test:error":
2049
- this.#result.testCount.failed++;
2050
- this.#fileResult.testCount.failed++;
2051
- this.#testResult.status = "failed";
2052
- this.#testResult.diagnostics.push(...payload.diagnostics);
2053
- this.#testResult.timing.end = Date.now();
2054
- this.#testResult = undefined;
2055
- break;
2056
2060
  case "test:fail":
2057
- this.#result.testCount.failed++;
2058
- this.#fileResult.testCount.failed++;
2061
+ this.#result.testCounts.failed++;
2062
+ this.#fileResult.testCounts.failed++;
2059
2063
  this.#testResult.status = "failed";
2060
2064
  this.#testResult.timing.end = Date.now();
2061
2065
  this.#testResult = undefined;
2062
2066
  break;
2063
2067
  case "test:pass":
2064
- this.#result.testCount.passed++;
2065
- this.#fileResult.testCount.passed++;
2068
+ this.#result.testCounts.passed++;
2069
+ this.#fileResult.testCounts.passed++;
2066
2070
  this.#testResult.status = "passed";
2067
2071
  this.#testResult.timing.end = Date.now();
2068
2072
  this.#testResult = undefined;
2069
2073
  break;
2070
2074
  case "test:skip":
2071
- this.#result.testCount.skipped++;
2072
- this.#fileResult.testCount.skipped++;
2075
+ this.#result.testCounts.skipped++;
2076
+ this.#fileResult.testCounts.skipped++;
2073
2077
  this.#testResult.status = "skipped";
2074
2078
  this.#testResult.timing.end = Date.now();
2075
2079
  this.#testResult = undefined;
2076
2080
  break;
2077
2081
  case "test:fixme":
2078
- this.#result.testCount.fixme++;
2079
- this.#fileResult.testCount.fixme++;
2082
+ this.#result.testCounts.fixme++;
2083
+ this.#fileResult.testCounts.fixme++;
2080
2084
  this.#testResult.status = "fixme";
2081
2085
  this.#testResult.timing.end = Date.now();
2082
2086
  this.#testResult = undefined;
2083
2087
  break;
2084
2088
  case "test:todo":
2085
- this.#result.testCount.todo++;
2086
- this.#fileResult.testCount.todo++;
2089
+ this.#result.testCounts.todo++;
2090
+ this.#fileResult.testCounts.todo++;
2087
2091
  this.#testResult.status = "todo";
2088
2092
  this.#testResult.timing.end = Date.now();
2089
2093
  this.#testResult = undefined;
@@ -2099,56 +2103,60 @@ class ResultHandler {
2099
2103
  this.#expectResult.timing.start = Date.now();
2100
2104
  break;
2101
2105
  case "expect:error":
2102
- this.#result.expectCount.failed++;
2103
- this.#fileResult.expectCount.failed++;
2104
- if (this.#testResult) {
2105
- this.#testResult.expectCount.failed++;
2106
- }
2107
- this.#expectResult.status = "failed";
2108
- this.#expectResult.diagnostics.push(...payload.diagnostics);
2109
- this.#expectResult.timing.end = Date.now();
2110
- this.#expectResult = undefined;
2111
- break;
2112
2106
  case "expect:fail":
2113
- this.#result.expectCount.failed++;
2114
- this.#fileResult.expectCount.failed++;
2107
+ this.#result.assertionCounts.failed++;
2108
+ this.#fileResult.assertionCounts.failed++;
2115
2109
  if (this.#testResult) {
2116
- this.#testResult.expectCount.failed++;
2110
+ this.#testResult.assertionCounts.failed++;
2117
2111
  }
2118
2112
  this.#expectResult.status = "failed";
2119
2113
  this.#expectResult.timing.end = Date.now();
2120
2114
  this.#expectResult = undefined;
2121
2115
  break;
2122
2116
  case "expect:pass":
2123
- this.#result.expectCount.passed++;
2124
- this.#fileResult.expectCount.passed++;
2117
+ this.#result.assertionCounts.passed++;
2118
+ this.#fileResult.assertionCounts.passed++;
2125
2119
  if (this.#testResult) {
2126
- this.#testResult.expectCount.passed++;
2120
+ this.#testResult.assertionCounts.passed++;
2127
2121
  }
2128
2122
  this.#expectResult.status = "passed";
2129
2123
  this.#expectResult.timing.end = Date.now();
2130
2124
  this.#expectResult = undefined;
2131
2125
  break;
2132
2126
  case "expect:skip":
2133
- this.#result.expectCount.skipped++;
2134
- this.#fileResult.expectCount.skipped++;
2127
+ this.#result.assertionCounts.skipped++;
2128
+ this.#fileResult.assertionCounts.skipped++;
2135
2129
  if (this.#testResult) {
2136
- this.#testResult.expectCount.skipped++;
2130
+ this.#testResult.assertionCounts.skipped++;
2137
2131
  }
2138
2132
  this.#expectResult.status = "skipped";
2139
2133
  this.#expectResult.timing.end = Date.now();
2140
2134
  this.#expectResult = undefined;
2141
2135
  break;
2142
2136
  case "expect:fixme":
2143
- this.#result.expectCount.fixme++;
2144
- this.#fileResult.expectCount.fixme++;
2137
+ this.#result.assertionCounts.fixme++;
2138
+ this.#fileResult.assertionCounts.fixme++;
2145
2139
  if (this.#testResult) {
2146
- this.#testResult.expectCount.fixme++;
2140
+ this.#testResult.assertionCounts.fixme++;
2147
2141
  }
2148
2142
  this.#expectResult.status = "fixme";
2149
2143
  this.#expectResult.timing.end = Date.now();
2150
2144
  this.#expectResult = undefined;
2151
2145
  break;
2146
+ case "suppressed:error":
2147
+ this.#result.suppressedCounts.failed++;
2148
+ this.#fileResult.suppressedCounts.failed++;
2149
+ this.#targetResult.status = "failed";
2150
+ this.#fileResult.status = "failed";
2151
+ break;
2152
+ case "suppressed:match":
2153
+ this.#result.suppressedCounts.matched++;
2154
+ this.#fileResult.suppressedCounts.matched++;
2155
+ break;
2156
+ case "suppressed:ignore":
2157
+ this.#result.suppressedCounts.ignored++;
2158
+ this.#fileResult.suppressedCounts.ignored++;
2159
+ break;
2152
2160
  }
2153
2161
  }
2154
2162
  }
@@ -2331,6 +2339,30 @@ function diagnosticText(diagnostic, codeFrameOptions = {}) {
2331
2339
  return (jsx(Text, { children: [prefix, jsx(DiagnosticText, { codeFrameOptions: codeFrameOptions, diagnostic: diagnostic })] }));
2332
2340
  }
2333
2341
 
2342
+ function getStatusColor(status) {
2343
+ switch (status) {
2344
+ case "runs":
2345
+ return "33";
2346
+ case "passed":
2347
+ case "matched":
2348
+ return "32";
2349
+ case "failed":
2350
+ return "31";
2351
+ case "fixme":
2352
+ case "skipped":
2353
+ case "ignored":
2354
+ return "33";
2355
+ case "todo":
2356
+ return "35";
2357
+ }
2358
+ }
2359
+ function duration(timing) {
2360
+ return timing.end - timing.start;
2361
+ }
2362
+ function total(counts) {
2363
+ return Object.values(counts).reduce((sum, value) => sum + value, 0);
2364
+ }
2365
+
2334
2366
  function FileNameText({ filePath }) {
2335
2367
  const relativePath = Path.relative("", filePath);
2336
2368
  const lastPathSeparator = relativePath.lastIndexOf("/");
@@ -2339,23 +2371,19 @@ function FileNameText({ filePath }) {
2339
2371
  return (jsx(Text, { children: [jsx(Text, { color: "90", children: directoryNameText }), fileNameText] }));
2340
2372
  }
2341
2373
  function fileStatusText(status, file) {
2342
- let statusColor;
2343
2374
  let statusText;
2344
2375
  switch (status) {
2345
2376
  case "runs":
2346
- statusColor = "33";
2347
2377
  statusText = "runs";
2348
2378
  break;
2349
2379
  case "passed":
2350
- statusColor = "32";
2351
2380
  statusText = "pass";
2352
2381
  break;
2353
2382
  case "failed":
2354
- statusColor = "31";
2355
2383
  statusText = "fail";
2356
2384
  break;
2357
2385
  }
2358
- return (jsx(Line, { children: [jsx(Text, { color: statusColor, children: statusText }), " ", jsx(FileNameText, { filePath: file.path })] }));
2386
+ return (jsx(Line, { children: [jsx(Text, { color: getStatusColor(status), children: statusText }), " ", jsx(FileNameText, { filePath: file.path })] }));
2359
2387
  }
2360
2388
 
2361
2389
  function fileViewText(lines, addEmptyFinalLine) {
@@ -2466,36 +2494,52 @@ class OutputService {
2466
2494
  function RowText({ label, text }) {
2467
2495
  return (jsx(Line, { children: [`${label}:`.padEnd(12), text] }));
2468
2496
  }
2469
- function CountText({ failed, fixme, passed, skipped, todo, total }) {
2470
- return (jsx(Text, { children: [failed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "31", children: [failed, " failed"] }), jsx(Text, { children: ", " })] })) : undefined, fixme > 0 ? (jsx(Text, { children: [jsx(Text, { color: "33", children: [fixme, " fixme"] }), jsx(Text, { children: ", " })] })) : undefined, skipped > 0 ? (jsx(Text, { children: [jsx(Text, { color: "33", children: [skipped, " skipped"] }), jsx(Text, { children: ", " })] })) : undefined, todo > 0 ? (jsx(Text, { children: [jsx(Text, { color: "35", children: [todo, " todo"] }), jsx(Text, { children: ", " })] })) : undefined, passed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "32", children: [passed, " passed"] }), jsx(Text, { children: ", " })] })) : undefined, jsx(Text, { children: [total, " total"] })] }));
2497
+ function CountsText({ counts, total }) {
2498
+ const countsText = Object.entries(counts).map(([status, count]) => {
2499
+ return (jsx(Text, { children: count > 0 ? (jsx(Text, { children: [jsx(Text, { color: getStatusColor(status), children: [count, " ", status] }), jsx(Text, { children: ", " })] })) : undefined }));
2500
+ });
2501
+ const totalText = (jsx(Text, { children: [total, " ", "total"] }));
2502
+ return (jsx(Text, { children: [countsText, totalText] }));
2471
2503
  }
2472
- function DurationText({ seconds }) {
2504
+ function DurationText({ timing }) {
2505
+ const seconds = duration(timing) / 1000;
2473
2506
  return jsx(Text, { children: `${Math.round(seconds * 10) / 10}s` });
2474
2507
  }
2475
- function summaryText({ duration, expectCount, fileCount, targetCount, testCount, }) {
2476
- const targetCountText = (jsx(RowText, { label: "Targets", text: jsx(CountText, { failed: targetCount.failed, fixme: targetCount.fixme, passed: targetCount.passed, skipped: targetCount.skipped, todo: targetCount.todo, total: targetCount.total }) }));
2477
- const fileCountText = (jsx(RowText, { label: "Test files", text: jsx(CountText, { failed: fileCount.failed, fixme: fileCount.fixme, passed: fileCount.passed, skipped: fileCount.skipped, todo: fileCount.todo, total: fileCount.total }) }));
2478
- const testCountText = (jsx(RowText, { label: "Tests", text: jsx(CountText, { failed: testCount.failed, fixme: testCount.fixme, passed: testCount.passed, skipped: testCount.skipped, todo: testCount.todo, total: testCount.total }) }));
2479
- const assertionCountText = (jsx(RowText, { label: "Assertions", text: jsx(CountText, { failed: expectCount.failed, fixme: expectCount.fixme, passed: expectCount.passed, skipped: expectCount.skipped, todo: expectCount.todo, total: expectCount.total }) }));
2480
- return (jsx(Text, { children: [targetCountText, fileCountText, testCount.total > 0 ? testCountText : undefined, expectCount.total > 0 ? assertionCountText : undefined, jsx(RowText, { label: "Duration", text: jsx(DurationText, { seconds: duration / 1000 }) })] }));
2508
+ function summaryText({ targetCounts, fileCounts, testCounts, assertionCounts, suppressedCounts, timing, }) {
2509
+ const targetCountsTotal = total(targetCounts);
2510
+ const targetCountsText = (jsx(RowText, { label: "Targets", text: jsx(CountsText, { counts: targetCounts, total: targetCountsTotal }) }));
2511
+ const fileCountsTotal = total(fileCounts);
2512
+ const fileCountsText = (jsx(RowText, { label: "Test files", text: jsx(CountsText, { counts: fileCounts, total: fileCountsTotal }) }));
2513
+ const testCountsTotal = total(testCounts);
2514
+ const testCountsText = testCountsTotal > 0 ? (jsx(RowText, { label: "Tests", text: jsx(CountsText, { counts: testCounts, total: testCountsTotal }) })) : undefined;
2515
+ const assertionCountsTotal = total(assertionCounts);
2516
+ const assertionCountsText = assertionCountsTotal > 0 ? (jsx(RowText, { label: "Assertions", text: jsx(CountsText, { counts: assertionCounts, total: assertionCountsTotal }) })) : undefined;
2517
+ const suppressedCountsTotal = total(suppressedCounts);
2518
+ const suppressedCountsText = suppressedCountsTotal > 0 ? (jsx(RowText, { label: "Suppressed", text: jsx(CountsText, { counts: suppressedCounts, total: suppressedCountsTotal }) })) : undefined;
2519
+ const durationText = jsx(RowText, { label: "Duration", text: jsx(DurationText, { timing: timing }) });
2520
+ return (jsx(Text, { children: [targetCountsText, fileCountsText, testCountsText, assertionCountsText, suppressedCountsText, durationText] }));
2481
2521
  }
2482
2522
 
2483
- function StatusText({ status }) {
2523
+ function testNameText(status, name, indent = 0) {
2524
+ let statusText;
2484
2525
  switch (status) {
2485
- case "fail":
2486
- return jsx(Text, { color: "31", children: "\u00D7" });
2487
- case "pass":
2488
- return jsx(Text, { color: "32", children: "+" });
2489
- case "skip":
2490
- return jsx(Text, { color: "33", children: "- skip" });
2526
+ case "passed":
2527
+ statusText = "+";
2528
+ break;
2529
+ case "failed":
2530
+ statusText = "×";
2531
+ break;
2532
+ case "skipped":
2533
+ statusText = "- skip";
2534
+ break;
2491
2535
  case "fixme":
2492
- return jsx(Text, { color: "33", children: "- fixme" });
2536
+ statusText = "- fixme";
2537
+ break;
2493
2538
  case "todo":
2494
- return jsx(Text, { color: "35", children: "- todo" });
2539
+ statusText = "- todo";
2540
+ break;
2495
2541
  }
2496
- }
2497
- function testNameText(status, name, indent = 0) {
2498
- return (jsx(Line, { indent: indent + 1, children: [jsx(StatusText, { status: status }), " ", jsx(Text, { color: "90", children: name })] }));
2542
+ return (jsx(Line, { indent: indent + 1, children: [jsx(Text, { color: getStatusColor(status), children: statusText }), " ", jsx(Text, { color: "90", children: name })] }));
2499
2543
  }
2500
2544
 
2501
2545
  function usesCompilerText(compilerVersion, projectConfigFilePath, options) {
@@ -2551,9 +2595,6 @@ class FileView {
2551
2595
  #indent = 0;
2552
2596
  #lines = [];
2553
2597
  #messages = [];
2554
- get hasErrors() {
2555
- return this.#messages.length > 0;
2556
- }
2557
2598
  addMessage(message) {
2558
2599
  this.#messages.push(message);
2559
2600
  }
@@ -2576,7 +2617,10 @@ class FileView {
2576
2617
  return this.#messages;
2577
2618
  }
2578
2619
  getViewText(options) {
2579
- return fileViewText(this.#lines, options?.appendEmptyLine || this.hasErrors);
2620
+ return fileViewText(this.#lines, options?.appendEmptyLine || this.hasErrors());
2621
+ }
2622
+ hasErrors() {
2623
+ return this.#messages.length > 0;
2580
2624
  }
2581
2625
  }
2582
2626
 
@@ -2587,7 +2631,7 @@ class ListReporter extends BaseReporter {
2587
2631
  #hasReportedError = false;
2588
2632
  #hasReportedUses = false;
2589
2633
  #isFileViewExpanded = false;
2590
- get #isLastFile() {
2634
+ #isLastFile() {
2591
2635
  return this.#fileCount === 0;
2592
2636
  }
2593
2637
  on([event, payload]) {
@@ -2630,6 +2674,7 @@ class ListReporter extends BaseReporter {
2630
2674
  case "file:error":
2631
2675
  case "directive:error":
2632
2676
  case "collect:error":
2677
+ case "suppressed:error":
2633
2678
  for (const diagnostic of payload.diagnostics) {
2634
2679
  this.#fileView.addMessage(diagnosticText(diagnostic));
2635
2680
  }
@@ -2639,8 +2684,8 @@ class ListReporter extends BaseReporter {
2639
2684
  OutputService.eraseLastLine();
2640
2685
  }
2641
2686
  OutputService.writeMessage(fileStatusText(payload.result.status, payload.result.file));
2642
- OutputService.writeMessage(this.#fileView.getViewText({ appendEmptyLine: this.#isLastFile }));
2643
- if (this.#fileView.hasErrors) {
2687
+ OutputService.writeMessage(this.#fileView.getViewText({ appendEmptyLine: this.#isLastFile() }));
2688
+ if (this.#fileView.hasErrors()) {
2644
2689
  OutputService.writeError(this.#fileView.getMessages());
2645
2690
  this.#hasReportedError = true;
2646
2691
  }
@@ -2658,7 +2703,7 @@ class ListReporter extends BaseReporter {
2658
2703
  break;
2659
2704
  case "test:skip":
2660
2705
  if (this.#isFileViewExpanded) {
2661
- this.#fileView.addTest("skip", payload.result.test.name);
2706
+ this.#fileView.addTest("skipped", payload.result.test.name);
2662
2707
  }
2663
2708
  break;
2664
2709
  case "test:fixme":
@@ -2673,7 +2718,7 @@ class ListReporter extends BaseReporter {
2673
2718
  break;
2674
2719
  case "test:error":
2675
2720
  if (this.#isFileViewExpanded) {
2676
- this.#fileView.addTest("fail", payload.result.test.name);
2721
+ this.#fileView.addTest("failed", payload.result.test.name);
2677
2722
  }
2678
2723
  for (const diagnostic of payload.diagnostics) {
2679
2724
  this.#fileView.addMessage(diagnosticText(diagnostic));
@@ -2681,12 +2726,12 @@ class ListReporter extends BaseReporter {
2681
2726
  break;
2682
2727
  case "test:fail":
2683
2728
  if (this.#isFileViewExpanded) {
2684
- this.#fileView.addTest("fail", payload.result.test.name);
2729
+ this.#fileView.addTest("failed", payload.result.test.name);
2685
2730
  }
2686
2731
  break;
2687
2732
  case "test:pass":
2688
2733
  if (this.#isFileViewExpanded) {
2689
- this.#fileView.addTest("pass", payload.result.test.name);
2734
+ this.#fileView.addTest("passed", payload.result.test.name);
2690
2735
  }
2691
2736
  break;
2692
2737
  case "expect:error":
@@ -2727,11 +2772,12 @@ class SummaryReporter extends BaseReporter {
2727
2772
  }
2728
2773
  if (event === "run:end") {
2729
2774
  OutputService.writeMessage(summaryText({
2730
- duration: payload.result.timing.duration,
2731
- expectCount: payload.result.expectCount,
2732
- fileCount: payload.result.fileCount,
2733
- targetCount: payload.result.targetCount,
2734
- testCount: payload.result.testCount,
2775
+ targetCounts: payload.result.targetCounts,
2776
+ fileCounts: payload.result.fileCounts,
2777
+ testCounts: payload.result.testCounts,
2778
+ assertionCounts: payload.result.assertionCounts,
2779
+ suppressedCounts: payload.result.suppressedCounts,
2780
+ timing: payload.result.timing,
2735
2781
  }));
2736
2782
  }
2737
2783
  }
@@ -2783,18 +2829,18 @@ var CancellationReason;
2783
2829
  class CancellationToken {
2784
2830
  #isCancelled = false;
2785
2831
  #reason;
2786
- get isCancellationRequested() {
2787
- return this.#isCancelled;
2788
- }
2789
- get reason() {
2790
- return this.#reason;
2791
- }
2792
2832
  cancel(reason) {
2793
2833
  if (!this.#isCancelled) {
2794
2834
  this.#isCancelled = true;
2795
2835
  this.#reason = reason;
2796
2836
  }
2797
2837
  }
2838
+ isCancellationRequested() {
2839
+ return this.#isCancelled;
2840
+ }
2841
+ getReason() {
2842
+ return this.#reason;
2843
+ }
2798
2844
  reset() {
2799
2845
  if (this.#isCancelled) {
2800
2846
  this.#isCancelled = false;
@@ -3179,7 +3225,7 @@ class WatchService {
3179
3225
  for (const watcher of this.#watchers) {
3180
3226
  watcher.watch();
3181
3227
  }
3182
- while (!cancellationToken.isCancellationRequested) {
3228
+ while (!cancellationToken.isCancellationRequested()) {
3183
3229
  const testFiles = await debounce.schedule();
3184
3230
  if (testFiles.length > 0) {
3185
3231
  yield testFiles;
@@ -3188,6 +3234,29 @@ class WatchService {
3188
3234
  }
3189
3235
  }
3190
3236
 
3237
+ function compareDiagnostics(a, b) {
3238
+ if (a.file?.fileName !== b.file?.fileName) {
3239
+ return false;
3240
+ }
3241
+ return deepCompareKeys(a, b, ["start", "length", "code", "messageText"]);
3242
+ }
3243
+ function deepCompareKeys(a, b, keys) {
3244
+ if (a == null || b == null) {
3245
+ return a === b;
3246
+ }
3247
+ if (typeof a !== typeof b) {
3248
+ return false;
3249
+ }
3250
+ if (typeof a !== "object") {
3251
+ return a === b;
3252
+ }
3253
+ for (const key of Object.keys(a).filter((key) => keys.includes(key))) {
3254
+ if (!(key in b) || !deepCompareKeys(a[key], b[key], keys)) {
3255
+ return false;
3256
+ }
3257
+ }
3258
+ return true;
3259
+ }
3191
3260
  function nodeBelongsToArgumentList(compiler, node) {
3192
3261
  return compiler.isCallExpression(node.parent) && node.parent.arguments.some((argument) => argument === node);
3193
3262
  }
@@ -3197,70 +3266,189 @@ function nodeIsChildOfExpressionStatement(compiler, node) {
3197
3266
 
3198
3267
  class AbilityLayer {
3199
3268
  #compiler;
3200
- #expectErrorRegex = /^(\s*)(\/\/ *@ts-expect-error)(!?)(:? *)(.*)?$/gim;
3201
- #filePath = "";
3269
+ #editor;
3202
3270
  #nodes = [];
3203
- #projectService;
3204
- #resolvedConfig;
3205
- #suppressedErrorsMap;
3206
- #text = "";
3207
- constructor(compiler, projectService, resolvedConfig) {
3271
+ constructor(compiler, editor) {
3208
3272
  this.#compiler = compiler;
3209
- this.#projectService = projectService;
3210
- this.#resolvedConfig = resolvedConfig;
3211
- }
3212
- #addRanges(node, ranges) {
3213
- this.#nodes.push(node);
3214
- for (const range of ranges) {
3215
- const rangeText = range.replacement != null
3216
- ? `${range.replacement}${this.#getErasedRangeText(range).slice(range.replacement.length)}`
3217
- : this.#getErasedRangeText(range);
3218
- this.#text = `${this.#text.slice(0, range.start)}${rangeText}${this.#text.slice(range.end)}`;
3219
- }
3273
+ this.#editor = editor;
3220
3274
  }
3221
3275
  #belongsToNode(node, diagnostic) {
3222
3276
  switch (node.brand) {
3223
3277
  case "expect":
3224
- return (diagnosticBelongsToNode(diagnostic, node.matcherNode) &&
3225
- !diagnosticBelongsToNode(diagnostic, node.source));
3278
+ return (diagnosticBelongsToNode(diagnostic, node.matcherNode) ||
3279
+ diagnosticBelongsToNode(diagnostic, node.source));
3226
3280
  case "when":
3227
3281
  return (diagnosticBelongsToNode(diagnostic, node.actionNode) &&
3228
3282
  !diagnosticBelongsToNode(diagnostic, node.target));
3229
3283
  }
3230
3284
  return false;
3231
3285
  }
3286
+ close(diagnostics) {
3287
+ if (diagnostics != null && this.#nodes.length > 0) {
3288
+ this.#nodes.reverse();
3289
+ for (const diagnostic of diagnostics) {
3290
+ this.#mapToNodes(diagnostic);
3291
+ }
3292
+ }
3293
+ this.#nodes = [];
3294
+ }
3232
3295
  #mapToNodes(diagnostic) {
3233
3296
  for (const node of this.#nodes) {
3234
3297
  if (this.#belongsToNode(node, diagnostic)) {
3235
3298
  node.abilityDiagnostics.add(diagnostic);
3236
- return true;
3299
+ break;
3237
3300
  }
3238
3301
  }
3239
- return false;
3240
3302
  }
3241
- #mapToDirectives(diagnostic) {
3242
- if (!isDiagnosticWithLocation(diagnostic)) {
3243
- return;
3244
- }
3245
- const { file, start } = diagnostic;
3246
- const lineMap = file.getLineStarts();
3247
- let line = this.#compiler.getLineAndCharacterOfPosition(file, start).line - 1;
3248
- while (line >= 0) {
3249
- const suppressedError = this.#suppressedErrorsMap?.get(line);
3250
- if (suppressedError != null) {
3251
- suppressedError.diagnostics.push(diagnostic);
3303
+ visitExpect(expect) {
3304
+ const expectStart = expect.node.getStart();
3305
+ const expectExpressionEnd = expect.node.expression.getEnd();
3306
+ const expectEnd = expect.node.getEnd();
3307
+ const matcherNameEnd = expect.matcherNameNode.getEnd();
3308
+ switch (expect.matcherNameNode.name.text) {
3309
+ case "toBeApplicable":
3310
+ this.#nodes.push(expect);
3311
+ this.#editor.replaceRanges([
3312
+ [expectStart, expectExpressionEnd],
3313
+ [expectEnd, matcherNameEnd],
3314
+ ]);
3315
+ break;
3316
+ case "toBeCallableWith": {
3317
+ this.#nodes.push(expect);
3318
+ const sourceNode = expect.source[0];
3319
+ if (!sourceNode) {
3320
+ return;
3321
+ }
3322
+ if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
3323
+ this.#editor.eraseTrailingComma(expect.source);
3324
+ this.#editor.replaceRanges([
3325
+ [
3326
+ expectStart,
3327
+ expectExpressionEnd,
3328
+ nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? ";" : "",
3329
+ ],
3330
+ [expectEnd, matcherNameEnd],
3331
+ ]);
3332
+ }
3333
+ else {
3334
+ const sourceText = sourceNode.getFullText();
3335
+ this.#editor.replaceRanges([
3336
+ [
3337
+ expectStart,
3338
+ matcherNameEnd,
3339
+ nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3340
+ ? `;(undefined as any as ${sourceText})`
3341
+ : `(undefined as any as ${sourceText})`,
3342
+ ],
3343
+ ]);
3344
+ }
3252
3345
  break;
3253
3346
  }
3254
- const lineText = file.text.slice(lineMap[line], lineMap[line + 1]).trim();
3255
- if (lineText !== "" && !lineText.startsWith("//")) {
3347
+ case "toBeConstructableWith":
3348
+ this.#nodes.push(expect);
3349
+ this.#editor.eraseTrailingComma(expect.source);
3350
+ this.#editor.replaceRanges([
3351
+ [
3352
+ expectStart,
3353
+ expectExpressionEnd,
3354
+ nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? "; new" : "new",
3355
+ ],
3356
+ [expectEnd, matcherNameEnd],
3357
+ ]);
3358
+ break;
3359
+ }
3360
+ }
3361
+ visitWhen(when) {
3362
+ const whenStart = when.node.getStart();
3363
+ const whenExpressionEnd = when.node.expression.getEnd();
3364
+ const whenEnd = when.node.getEnd();
3365
+ const actionNameEnd = when.actionNameNode.getEnd();
3366
+ switch (when.actionNameNode.name.text) {
3367
+ case "isCalledWith":
3368
+ this.#nodes.push(when);
3369
+ this.#editor.eraseTrailingComma(when.target);
3370
+ this.#editor.replaceRanges([
3371
+ [whenStart, whenExpressionEnd, nodeIsChildOfExpressionStatement(this.#compiler, when.actionNode) ? ";" : ""],
3372
+ [whenEnd, actionNameEnd],
3373
+ ]);
3256
3374
  break;
3375
+ }
3376
+ }
3377
+ }
3378
+
3379
+ class SourceTextEditor {
3380
+ #filePath = "";
3381
+ #sourceFile;
3382
+ #text = "";
3383
+ open(sourceFile) {
3384
+ this.#sourceFile = sourceFile;
3385
+ this.#filePath = sourceFile.fileName;
3386
+ this.#text = sourceFile.text;
3387
+ }
3388
+ close() {
3389
+ if (this.#sourceFile != null) {
3390
+ SourceService.set(this.#sourceFile);
3391
+ this.#sourceFile = undefined;
3392
+ }
3393
+ this.#filePath = "";
3394
+ this.#text = "";
3395
+ }
3396
+ eraseTrailingComma(node) {
3397
+ if (node.hasTrailingComma) {
3398
+ this.replaceRange(node.end - 1, node.end);
3399
+ }
3400
+ }
3401
+ #getErasedRange(start, end) {
3402
+ if (this.#text.indexOf("\n", start) >= end) {
3403
+ return " ".repeat(end - start);
3404
+ }
3405
+ const text = [];
3406
+ for (let index = start; index < end; index++) {
3407
+ const character = this.#text.charAt(index);
3408
+ switch (character) {
3409
+ case "\n":
3410
+ case "\r":
3411
+ text.push(character);
3412
+ break;
3413
+ default:
3414
+ text.push(" ");
3257
3415
  }
3258
- line--;
3259
3416
  }
3417
+ return text.join("");
3418
+ }
3419
+ getFilePath() {
3420
+ return this.#filePath;
3421
+ }
3422
+ getText() {
3423
+ return this.#text;
3424
+ }
3425
+ replaceRange(start, end, replacement) {
3426
+ const rangeText = replacement != null
3427
+ ? `${replacement}${this.#getErasedRange(start, end).slice(replacement.length)}`
3428
+ : this.#getErasedRange(start, end);
3429
+ this.#text = `${this.#text.slice(0, start)}${rangeText}${this.#text.slice(end)}`;
3430
+ }
3431
+ replaceRanges(ranges) {
3432
+ for (const [start, end, replacement] of ranges) {
3433
+ this.replaceRange(start, end, replacement);
3434
+ }
3435
+ }
3436
+ }
3437
+
3438
+ class SuppressedLayer {
3439
+ #compiler;
3440
+ #editor;
3441
+ #expectErrorRegex = /^(\s*)(\/\/ *@ts-expect-error)(!?)(:? *)(.*)?$/gim;
3442
+ #resolvedConfig;
3443
+ #suppressedErrorsMap;
3444
+ constructor(compiler, editor, resolvedConfig) {
3445
+ this.#compiler = compiler;
3446
+ this.#editor = editor;
3447
+ this.#resolvedConfig = resolvedConfig;
3260
3448
  }
3261
- #collectSuppressedErrors() {
3449
+ #collectSuppressedErrors(text) {
3262
3450
  const ranges = [];
3263
- for (const match of this.#text.matchAll(this.#expectErrorRegex)) {
3451
+ for (const match of text.matchAll(this.#expectErrorRegex)) {
3264
3452
  const offsetText = match?.[1];
3265
3453
  const directiveText = match?.[2];
3266
3454
  const ignoreText = match?.[3];
@@ -3283,126 +3471,89 @@ class AbilityLayer {
3283
3471
  }
3284
3472
  return ranges;
3285
3473
  }
3286
- close(testTree) {
3287
- if (this.#nodes.length > 0 || this.#suppressedErrorsMap != null) {
3288
- SourceService.set(testTree.sourceFile);
3289
- this.#projectService.openFile(this.#filePath, this.#text, this.#resolvedConfig.rootPath);
3290
- const languageService = this.#projectService.getLanguageService(this.#filePath);
3291
- const diagnostics = languageService?.getSemanticDiagnostics(this.#filePath);
3292
- if (diagnostics != null) {
3293
- this.#nodes.reverse();
3294
- for (const diagnostic of diagnostics) {
3295
- if (this.#mapToNodes(diagnostic)) {
3296
- continue;
3297
- }
3298
- this.#mapToDirectives(diagnostic);
3299
- }
3474
+ close(diagnostics) {
3475
+ if (diagnostics != null && this.#suppressedErrorsMap != null) {
3476
+ for (const diagnostic of diagnostics) {
3477
+ this.#mapToDirectives(diagnostic);
3300
3478
  }
3301
3479
  }
3302
- this.#filePath = "";
3303
- this.#nodes = [];
3304
3480
  this.#suppressedErrorsMap = undefined;
3305
- this.#text = "";
3306
3481
  }
3307
- #eraseTrailingComma(node, parent) {
3308
- if (node.hasTrailingComma) {
3309
- this.#addRanges(parent, [{ start: node.end - 1, end: node.end }]);
3310
- }
3311
- }
3312
- #getErasedRangeText(range) {
3313
- if (this.#text.indexOf("\n", range.start) >= range.end) {
3314
- return " ".repeat(range.end - range.start);
3315
- }
3316
- const text = [];
3317
- for (let index = range.start; index < range.end; index++) {
3318
- const character = this.#text.charAt(index);
3319
- switch (character) {
3320
- case "\n":
3321
- case "\r":
3322
- text.push(character);
3323
- break;
3324
- default:
3325
- text.push(" ");
3326
- }
3482
+ #mapToDirectives(diagnostic) {
3483
+ if (!isDiagnosticWithLocation(diagnostic)) {
3484
+ return;
3327
3485
  }
3328
- return text.join("");
3329
- }
3330
- handleExpect(expect) {
3331
- const expectStart = expect.node.getStart();
3332
- const expectExpressionEnd = expect.node.expression.getEnd();
3333
- const expectEnd = expect.node.getEnd();
3334
- const matcherNameEnd = expect.matcherNameNode.getEnd();
3335
- switch (expect.matcherNameNode.name.text) {
3336
- case "toBeApplicable":
3337
- this.#addRanges(expect, [
3338
- { start: expectStart, end: expectExpressionEnd },
3339
- { start: expectEnd, end: matcherNameEnd },
3340
- ]);
3341
- break;
3342
- case "toBeCallableWith":
3343
- this.#eraseTrailingComma(expect.source, expect);
3344
- this.#addRanges(expect, [
3345
- {
3346
- start: expectStart,
3347
- end: expectExpressionEnd,
3348
- replacement: nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? ";" : "",
3349
- },
3350
- { start: expectEnd, end: matcherNameEnd },
3351
- ]);
3486
+ const { file, start } = diagnostic;
3487
+ const lineMap = file.getLineStarts();
3488
+ let line = this.#compiler.getLineAndCharacterOfPosition(file, start).line - 1;
3489
+ while (line >= 0) {
3490
+ const suppressedError = this.#suppressedErrorsMap?.get(line);
3491
+ if (suppressedError != null) {
3492
+ suppressedError.diagnostics.push(diagnostic);
3352
3493
  break;
3353
- case "toBeConstructableWith":
3354
- this.#eraseTrailingComma(expect.source, expect);
3355
- this.#addRanges(expect, [
3356
- {
3357
- start: expectStart,
3358
- end: expectExpressionEnd,
3359
- replacement: nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? "; new" : "new",
3360
- },
3361
- { start: expectEnd, end: matcherNameEnd },
3362
- ]);
3494
+ }
3495
+ const lineText = file.text.slice(lineMap[line], lineMap[line + 1]).trim();
3496
+ if (lineText !== "" && !lineText.startsWith("//")) {
3363
3497
  break;
3498
+ }
3499
+ line--;
3364
3500
  }
3365
3501
  }
3366
- #handleSuppressedErrors(testTree) {
3367
- const suppressedErrors = this.#collectSuppressedErrors();
3502
+ open(tree) {
3503
+ const suppressedErrors = this.#collectSuppressedErrors(this.#editor.getText());
3368
3504
  if (this.#resolvedConfig.checkSuppressedErrors) {
3369
- testTree.suppressedErrors = suppressedErrors;
3505
+ tree.suppressedErrors = suppressedErrors;
3370
3506
  this.#suppressedErrorsMap = new Map();
3371
3507
  }
3372
3508
  for (const suppressedError of suppressedErrors) {
3373
3509
  const { start, end } = suppressedError.directive;
3374
- const rangeText = this.#getErasedRangeText({ start: start + 2, end });
3375
- this.#text = `${this.#text.slice(0, start + 2)}${rangeText}${this.#text.slice(end)}`;
3510
+ this.#editor.replaceRange(start + 2, end);
3376
3511
  if (this.#suppressedErrorsMap != null) {
3377
- const { line } = testTree.sourceFile.getLineAndCharacterOfPosition(start);
3512
+ const { line } = tree.sourceFile.getLineAndCharacterOfPosition(start);
3378
3513
  this.#suppressedErrorsMap.set(line, suppressedError);
3379
3514
  }
3380
3515
  }
3381
3516
  }
3382
- handleWhen(whenNode) {
3383
- const whenStart = whenNode.node.getStart();
3384
- const whenExpressionEnd = whenNode.node.expression.getEnd();
3385
- const whenEnd = whenNode.node.getEnd();
3386
- const actionNameEnd = whenNode.actionNameNode.getEnd();
3387
- switch (whenNode.actionNameNode.name.text) {
3388
- case "isCalledWith":
3389
- this.#eraseTrailingComma(whenNode.target, whenNode);
3390
- this.#addRanges(whenNode, [
3391
- {
3392
- start: whenStart,
3393
- end: whenExpressionEnd,
3394
- replacement: nodeIsChildOfExpressionStatement(this.#compiler, whenNode.actionNode) ? ";" : "",
3395
- },
3396
- { start: whenEnd, end: actionNameEnd },
3397
- ]);
3517
+ }
3518
+
3519
+ class Layers {
3520
+ #abilityLayer;
3521
+ #editor = new SourceTextEditor();
3522
+ #projectService;
3523
+ #suppressedDiagnostics;
3524
+ #suppressedLayer;
3525
+ constructor(compiler, projectService, resolvedConfig) {
3526
+ this.#projectService = projectService;
3527
+ this.#abilityLayer = new AbilityLayer(compiler, this.#editor);
3528
+ this.#suppressedLayer = new SuppressedLayer(compiler, this.#editor, resolvedConfig);
3529
+ }
3530
+ close() {
3531
+ let isSeenDiagnostic;
3532
+ if (this.#suppressedDiagnostics != null) {
3533
+ const seenDiagnostics = this.#suppressedDiagnostics;
3534
+ this.#suppressedDiagnostics = undefined;
3535
+ isSeenDiagnostic = (diagnostic) => !seenDiagnostics.some((seenDiagnostic) => compareDiagnostics(diagnostic, seenDiagnostic));
3536
+ }
3537
+ const abilityDiagnostics = this.#projectService.getDiagnostics(this.#editor.getFilePath(), this.#editor.getText(), isSeenDiagnostic);
3538
+ this.#abilityLayer.close(abilityDiagnostics);
3539
+ this.#editor.close();
3540
+ }
3541
+ open(tree) {
3542
+ this.#editor.open(tree.sourceFile);
3543
+ this.#suppressedLayer.open(tree);
3544
+ this.#suppressedDiagnostics = this.#projectService.getDiagnostics(this.#editor.getFilePath(), this.#editor.getText());
3545
+ this.#suppressedLayer.close(this.#suppressedDiagnostics);
3546
+ }
3547
+ visit(node) {
3548
+ switch (node.brand) {
3549
+ case "expect":
3550
+ this.#abilityLayer.visitExpect(node);
3551
+ break;
3552
+ case "when":
3553
+ this.#abilityLayer.visitWhen(node);
3398
3554
  break;
3399
3555
  }
3400
3556
  }
3401
- open(testTree) {
3402
- this.#filePath = testTree.sourceFile.fileName;
3403
- this.#text = testTree.sourceFile.text;
3404
- this.#handleSuppressedErrors(testTree);
3405
- }
3406
3557
  }
3407
3558
 
3408
3559
  class CollectDiagnosticText {
@@ -3546,14 +3697,15 @@ class IdentifierLookup {
3546
3697
  }
3547
3698
  switch (identifier) {
3548
3699
  case "describe":
3549
- return { brand: "describe", flags, identifier };
3700
+ return { brand: "describe", flags };
3550
3701
  case "it":
3702
+ return { brand: "it", flags };
3551
3703
  case "test":
3552
- return { brand: "test", flags, identifier };
3704
+ return { brand: "test", flags };
3553
3705
  case "expect":
3554
- return { brand: "expect", flags, identifier };
3706
+ return { brand: "expect", flags };
3555
3707
  case "when":
3556
- return { brand: "when", flags, identifier };
3708
+ return { brand: "when", flags };
3557
3709
  }
3558
3710
  return;
3559
3711
  }
@@ -3591,12 +3743,12 @@ class WhenNode extends TestTreeNode {
3591
3743
  }
3592
3744
 
3593
3745
  class CollectService {
3594
- #abilityLayer;
3746
+ #layers;
3595
3747
  #compiler;
3596
3748
  #identifierLookup;
3597
3749
  constructor(compiler, projectService, resolvedConfig) {
3598
3750
  this.#compiler = compiler;
3599
- this.#abilityLayer = new AbilityLayer(compiler, projectService, resolvedConfig);
3751
+ this.#layers = new Layers(compiler, projectService, resolvedConfig);
3600
3752
  this.#identifierLookup = new IdentifierLookup(compiler);
3601
3753
  }
3602
3754
  #collectTestTreeNodes(node, parent, testTree) {
@@ -3606,7 +3758,9 @@ class CollectService {
3606
3758
  if (!this.#checkNode(node, meta, parent)) {
3607
3759
  return;
3608
3760
  }
3609
- if (meta.brand === "describe" || meta.brand === "test") {
3761
+ if (meta.brand === "describe" ||
3762
+ meta.brand === "it" ||
3763
+ meta.brand === "test") {
3610
3764
  const testTreeNode = new TestTreeNode(this.#compiler, meta.brand, node, parent, meta.flags);
3611
3765
  this.#compiler.forEachChild(node, (node) => {
3612
3766
  this.#collectTestTreeNodes(node, testTreeNode, testTree);
@@ -3617,19 +3771,28 @@ class CollectService {
3617
3771
  if (meta.brand === "expect") {
3618
3772
  const modifierNode = this.#getChainedNode(node, "type");
3619
3773
  if (!modifierNode) {
3774
+ const text = "'expect()' must be followed by the '.type' modifier.";
3775
+ const origin = DiagnosticOrigin.fromNode(node);
3776
+ this.#onDiagnostics(Diagnostic.error(text, origin));
3620
3777
  return;
3621
3778
  }
3622
3779
  const notNode = this.#getChainedNode(modifierNode, "not");
3623
3780
  const matcherNameNode = this.#getChainedNode(notNode ?? modifierNode);
3624
3781
  if (!matcherNameNode) {
3782
+ const text = "The final element in the chain must be a matcher.";
3783
+ const origin = DiagnosticOrigin.fromNode(notNode ?? modifierNode);
3784
+ this.#onDiagnostics(Diagnostic.error(text, origin));
3625
3785
  return;
3626
3786
  }
3627
3787
  const matcherNode = this.#getMatcherNode(matcherNameNode);
3628
3788
  if (!matcherNode) {
3789
+ const text = "The matcher must be called with an argument.";
3790
+ const origin = DiagnosticOrigin.fromNode(matcherNameNode);
3791
+ this.#onDiagnostics(Diagnostic.error(text, origin));
3629
3792
  return;
3630
3793
  }
3631
3794
  const expectNode = new ExpectNode(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, matcherNameNode, modifierNode, notNode);
3632
- this.#abilityLayer.handleExpect(expectNode);
3795
+ this.#layers.visit(expectNode);
3633
3796
  this.#compiler.forEachChild(node, (node) => {
3634
3797
  this.#collectTestTreeNodes(node, expectNode, testTree);
3635
3798
  });
@@ -3639,24 +3802,32 @@ class CollectService {
3639
3802
  if (meta.brand === "when") {
3640
3803
  const actionNameNode = this.#getChainedNode(node);
3641
3804
  if (!actionNameNode) {
3805
+ const text = "The final element in the chain must be an action.";
3806
+ const origin = DiagnosticOrigin.fromNode(node);
3807
+ this.#onDiagnostics(Diagnostic.error(text, origin));
3642
3808
  return;
3643
3809
  }
3644
3810
  const actionNode = this.#getActionNode(actionNameNode);
3645
3811
  if (!actionNode) {
3812
+ const text = "The action must be called with an argument.";
3813
+ const origin = DiagnosticOrigin.fromNode(actionNameNode);
3814
+ this.#onDiagnostics(Diagnostic.error(text, origin));
3646
3815
  return;
3647
3816
  }
3648
3817
  this.#compiler.forEachChild(actionNode, (node) => {
3649
3818
  if (this.#compiler.isCallExpression(node)) {
3650
3819
  const meta = this.#identifierLookup.resolveTestTreeNodeMeta(node);
3651
- if (meta?.brand === "describe" || meta?.brand === "test") {
3652
- const text = CollectDiagnosticText.cannotBeNestedWithin(meta.identifier, "when");
3820
+ if (meta?.brand === "describe" ||
3821
+ meta?.brand === "it" ||
3822
+ meta?.brand === "test") {
3823
+ const text = CollectDiagnosticText.cannotBeNestedWithin(meta.brand, "when");
3653
3824
  const origin = DiagnosticOrigin.fromNode(node);
3654
3825
  this.#onDiagnostics(Diagnostic.error(text, origin));
3655
3826
  }
3656
3827
  }
3657
3828
  });
3658
3829
  const whenNode = new WhenNode(this.#compiler, meta.brand, node, parent, meta.flags, actionNode, actionNameNode);
3659
- this.#abilityLayer.handleWhen(whenNode);
3830
+ this.#layers.visit(whenNode);
3660
3831
  this.#onNode(whenNode, parent, testTree);
3661
3832
  return;
3662
3833
  }
@@ -3673,16 +3844,16 @@ class CollectService {
3673
3844
  createTestTree(sourceFile, semanticDiagnostics = []) {
3674
3845
  const testTree = new TestTree(new Set(semanticDiagnostics), sourceFile);
3675
3846
  EventEmitter.dispatch(["collect:start", { tree: testTree }]);
3676
- this.#abilityLayer.open(testTree);
3847
+ this.#layers.open(testTree);
3677
3848
  this.#identifierLookup.open();
3678
3849
  this.#collectTestTreeNodes(sourceFile, testTree, testTree);
3679
- this.#abilityLayer.close(testTree);
3850
+ this.#layers.close();
3680
3851
  EventEmitter.dispatch(["collect:end", { tree: testTree }]);
3681
3852
  return testTree;
3682
3853
  }
3683
3854
  #checkNode(node, meta, parent) {
3684
3855
  if ("brand" in parent && !this.#isNodeAllowed(meta, parent)) {
3685
- const text = CollectDiagnosticText.cannotBeNestedWithin(meta.identifier, parent.node.expression.getText());
3856
+ const text = CollectDiagnosticText.cannotBeNestedWithin(meta.brand, parent.brand);
3686
3857
  const origin = DiagnosticOrigin.fromNode(node);
3687
3858
  this.#onDiagnostics(Diagnostic.error(text, origin));
3688
3859
  return false;
@@ -3692,8 +3863,11 @@ class CollectService {
3692
3863
  #isNodeAllowed(meta, parent) {
3693
3864
  switch (meta.brand) {
3694
3865
  case "describe":
3866
+ case "it":
3695
3867
  case "test":
3696
- if (parent.brand === "test" || parent.brand === "expect") {
3868
+ if (parent.brand === "it" ||
3869
+ parent.brand === "test" ||
3870
+ parent.brand === "expect") {
3697
3871
  return false;
3698
3872
  }
3699
3873
  break;
@@ -3749,6 +3923,7 @@ var TestTreeNodeBrand;
3749
3923
  (function (TestTreeNodeBrand) {
3750
3924
  TestTreeNodeBrand["Describe"] = "describe";
3751
3925
  TestTreeNodeBrand["Test"] = "test";
3926
+ TestTreeNodeBrand["It"] = "it";
3752
3927
  TestTreeNodeBrand["Expect"] = "expect";
3753
3928
  TestTreeNodeBrand["When"] = "when";
3754
3929
  })(TestTreeNodeBrand || (TestTreeNodeBrand = {}));
@@ -3808,6 +3983,7 @@ class ProjectService {
3808
3983
  }
3809
3984
  closeFile(filePath) {
3810
3985
  this.#service.closeClientFile(filePath);
3986
+ SourceService.delete(filePath);
3811
3987
  }
3812
3988
  #getDefaultCompilerOptions() {
3813
3989
  const defaultCompilerOptions = {
@@ -3837,6 +4013,15 @@ class ProjectService {
3837
4013
  }
3838
4014
  return project;
3839
4015
  }
4016
+ getDiagnostics(filePath, sourceText, shouldInclude) {
4017
+ this.openFile(filePath, sourceText);
4018
+ const languageService = this.getLanguageService(filePath);
4019
+ const diagnostics = languageService?.getSemanticDiagnostics(filePath);
4020
+ if (diagnostics != null && shouldInclude != null) {
4021
+ return diagnostics.filter(shouldInclude);
4022
+ }
4023
+ return diagnostics;
4024
+ }
3840
4025
  getLanguageService(filePath) {
3841
4026
  const project = this.getDefaultProject(filePath);
3842
4027
  return project?.getLanguageService(true);
@@ -3846,7 +4031,7 @@ class ProjectService {
3846
4031
  const { fileNames } = this.#compiler.parseJsonSourceFileConfigFileContent(configSourceFile, this.#compiler.sys, Path.dirname(this.#resolvedConfig.tsconfig), undefined, this.#resolvedConfig.tsconfig);
3847
4032
  return fileNames.includes(filePath);
3848
4033
  }
3849
- openFile(filePath, sourceText, projectRootPath) {
4034
+ openFile(filePath, sourceText) {
3850
4035
  switch (this.#resolvedConfig.tsconfig) {
3851
4036
  case "findup":
3852
4037
  break;
@@ -3858,7 +4043,7 @@ class ProjectService {
3858
4043
  ? () => this.#resolvedConfig.tsconfig
3859
4044
  : () => undefined;
3860
4045
  }
3861
- const { configFileErrors, configFileName } = this.#service.openClientFile(filePath, sourceText, undefined, projectRootPath);
4046
+ const { configFileErrors, configFileName } = this.#service.openClientFile(filePath, sourceText, undefined, this.#resolvedConfig.rootPath);
3862
4047
  if (configFileName !== this.#lastSeenProject) {
3863
4048
  this.#lastSeenProject = configFileName;
3864
4049
  EventEmitter.dispatch([
@@ -3926,12 +4111,17 @@ class SuppressedDiagnosticText {
3926
4111
  }
3927
4112
 
3928
4113
  class SuppressedService {
3929
- match(testTree, onDiagnostics) {
4114
+ match(testTree) {
3930
4115
  if (!testTree.suppressedErrors) {
3931
4116
  return;
3932
4117
  }
3933
4118
  for (const suppressedError of testTree.suppressedErrors) {
3934
- if (suppressedError.diagnostics.length === 0 || suppressedError.ignore) {
4119
+ const suppressedResult = new SuppressedResult(suppressedError);
4120
+ if (suppressedError.diagnostics.length === 0) {
4121
+ continue;
4122
+ }
4123
+ if (suppressedError.ignore) {
4124
+ EventEmitter.dispatch(["suppressed:ignore", { result: suppressedResult }]);
3935
4125
  continue;
3936
4126
  }
3937
4127
  const related = [
@@ -3941,12 +4131,12 @@ class SuppressedService {
3941
4131
  const origin = new DiagnosticOrigin(suppressedError.directive.start, suppressedError.directive.end, testTree.sourceFile);
3942
4132
  if (!suppressedError.argument?.text) {
3943
4133
  const text = SuppressedDiagnosticText.directiveRequires();
3944
- onDiagnostics([Diagnostic.error(text, origin).add({ related })]);
4134
+ this.#onDiagnostics(Diagnostic.error(text, origin).add({ related }), suppressedResult);
3945
4135
  continue;
3946
4136
  }
3947
4137
  if (suppressedError.diagnostics.length > 1) {
3948
- const text = [SuppressedDiagnosticText.onlySingleError()];
3949
- onDiagnostics([Diagnostic.error(text, origin).add({ related })]);
4138
+ const text = SuppressedDiagnosticText.onlySingleError();
4139
+ this.#onDiagnostics(Diagnostic.error(text, origin).add({ related }), suppressedResult);
3950
4140
  continue;
3951
4141
  }
3952
4142
  let messageText = getDiagnosticMessageText(suppressedError.diagnostics[0]);
@@ -3954,10 +4144,12 @@ class SuppressedService {
3954
4144
  messageText = messageText.join("\n");
3955
4145
  }
3956
4146
  if (!this.#matchMessage(messageText, suppressedError.argument.text)) {
3957
- const text = [SuppressedDiagnosticText.messageDidNotMatch()];
4147
+ const text = SuppressedDiagnosticText.messageDidNotMatch();
3958
4148
  const origin = new DiagnosticOrigin(suppressedError.argument.start, suppressedError.argument.end, testTree.sourceFile);
3959
- onDiagnostics([Diagnostic.error(text, origin).add({ related })]);
4149
+ this.#onDiagnostics(Diagnostic.error(text, origin).add({ related }), suppressedResult);
4150
+ continue;
3960
4151
  }
4152
+ EventEmitter.dispatch(["suppressed:match", { result: suppressedResult }]);
3961
4153
  }
3962
4154
  }
3963
4155
  #matchMessage(source, target) {
@@ -3973,6 +4165,9 @@ class SuppressedService {
3973
4165
  }
3974
4166
  return source.includes(target);
3975
4167
  }
4168
+ #onDiagnostics(diagnostic, result) {
4169
+ EventEmitter.dispatch(["suppressed:error", { diagnostics: [diagnostic], result }]);
4170
+ }
3976
4171
  }
3977
4172
 
3978
4173
  class EnsureDiagnosticText {
@@ -4075,11 +4270,11 @@ class ExpectDiagnosticText {
4075
4270
  static isNotAssignableTo(sourceTypeText, targetTypeText) {
4076
4271
  return `Type '${sourceTypeText}' is not assignable to type '${targetTypeText}'.`;
4077
4272
  }
4078
- static isAssignableWith(sourceTypeText, targetTypeText) {
4079
- return `Type '${sourceTypeText}' is assignable with type '${targetTypeText}'.`;
4273
+ static isAssignableFrom(sourceTypeText, targetTypeText) {
4274
+ return `Type '${sourceTypeText}' is assignable from type '${targetTypeText}'.`;
4080
4275
  }
4081
- static isNotAssignableWith(sourceTypeText, targetTypeText) {
4082
- return `Type '${sourceTypeText}' is not assignable with type '${targetTypeText}'.`;
4276
+ static isNotAssignableFrom(sourceTypeText, targetTypeText) {
4277
+ return `Type '${sourceTypeText}' is not assignable from type '${targetTypeText}'.`;
4083
4278
  }
4084
4279
  static isTheSame(sourceTypeText, targetTypeText) {
4085
4280
  return `Type '${sourceTypeText}' is the same as type '${targetTypeText}'.`;
@@ -4293,7 +4488,7 @@ class ToAcceptProps {
4293
4488
  }
4294
4489
  if (this.#isOptionalProperty(targetProperty) && !this.#isOptionalProperty(sourceProperty)) {
4295
4490
  const text = [
4296
- ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
4491
+ ExpectDiagnosticText.isNotAssignableFrom(sourceTypeText, targetTypeText),
4297
4492
  ExpectDiagnosticText.requiresProperty(sourceTypeText, targetPropertyName),
4298
4493
  ];
4299
4494
  const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
@@ -4306,9 +4501,9 @@ class ToAcceptProps {
4306
4501
  const targetPropertyTypeText = this.#typeChecker.typeToString(targetPropertyType);
4307
4502
  const sourcePropertyTypeText = this.#typeChecker.typeToString(sourcePropertyType);
4308
4503
  const text = [
4309
- ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
4504
+ ExpectDiagnosticText.isNotAssignableFrom(sourceTypeText, targetTypeText),
4310
4505
  ExpectDiagnosticText.typesOfPropertyAreNotCompatible(targetPropertyName),
4311
- ExpectDiagnosticText.isNotAssignableWith(sourcePropertyTypeText, targetPropertyTypeText),
4506
+ ExpectDiagnosticText.isNotAssignableFrom(sourcePropertyTypeText, targetPropertyTypeText),
4312
4507
  ];
4313
4508
  const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
4314
4509
  diagnostics.push(diagnostic.extendWith(text, origin));
@@ -4320,7 +4515,7 @@ class ToAcceptProps {
4320
4515
  const targetProperty = targetType.getProperty(sourcePropertyName);
4321
4516
  if (!targetProperty && !this.#isOptionalProperty(sourceProperty)) {
4322
4517
  const text = [
4323
- ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
4518
+ ExpectDiagnosticText.isNotAssignableFrom(sourceTypeText, targetTypeText),
4324
4519
  ExpectDiagnosticText.requiresProperty(sourceTypeText, sourcePropertyName),
4325
4520
  ];
4326
4521
  diagnostics.push(diagnostic.extendWith(text));
@@ -4328,7 +4523,7 @@ class ToAcceptProps {
4328
4523
  }
4329
4524
  }
4330
4525
  if (diagnostics.length === 0) {
4331
- const text = ExpectDiagnosticText.isAssignableWith(sourceTypeText, targetTypeText);
4526
+ const text = ExpectDiagnosticText.isAssignableFrom(sourceTypeText, targetTypeText);
4332
4527
  diagnostics.push(diagnostic.extendWith(text));
4333
4528
  return { diagnostics, isMatch: true };
4334
4529
  }
@@ -4338,8 +4533,8 @@ class ToAcceptProps {
4338
4533
  let accumulator = [];
4339
4534
  const isMatch = sourceType.types.some((sourceType) => {
4340
4535
  const text = matchWorker.assertionNode.isNot
4341
- ? ExpectDiagnosticText.isAssignableWith(sourceTypeText, targetTypeText)
4342
- : ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText);
4536
+ ? ExpectDiagnosticText.isAssignableFrom(sourceTypeText, targetTypeText)
4537
+ : ExpectDiagnosticText.isNotAssignableFrom(sourceTypeText, targetTypeText);
4343
4538
  const { diagnostics, isMatch } = explain(sourceType, targetType, diagnostic.extendWith(text));
4344
4539
  if (isMatch) {
4345
4540
  accumulator = diagnostics;
@@ -4480,24 +4675,24 @@ class ToBeApplicable {
4480
4675
  }
4481
4676
  }
4482
4677
 
4483
- class ToBeAssignableTo extends RelationMatcherBase {
4484
- explainText = ExpectDiagnosticText.isAssignableTo;
4485
- explainNotText = ExpectDiagnosticText.isNotAssignableTo;
4678
+ class ToBeAssignableFrom extends RelationMatcherBase {
4679
+ explainText = ExpectDiagnosticText.isAssignableFrom;
4680
+ explainNotText = ExpectDiagnosticText.isNotAssignableFrom;
4486
4681
  match(matchWorker, sourceNode, targetNode) {
4487
4682
  return {
4488
4683
  explain: () => this.explain(matchWorker, sourceNode, targetNode),
4489
- isMatch: matchWorker.checkIsAssignableTo(sourceNode, targetNode),
4684
+ isMatch: matchWorker.checkIsAssignableWith(sourceNode, targetNode),
4490
4685
  };
4491
4686
  }
4492
4687
  }
4493
4688
 
4494
- class ToBeAssignableWith extends RelationMatcherBase {
4495
- explainText = ExpectDiagnosticText.isAssignableWith;
4496
- explainNotText = ExpectDiagnosticText.isNotAssignableWith;
4689
+ class ToBeAssignableTo extends RelationMatcherBase {
4690
+ explainText = ExpectDiagnosticText.isAssignableTo;
4691
+ explainNotText = ExpectDiagnosticText.isNotAssignableTo;
4497
4692
  match(matchWorker, sourceNode, targetNode) {
4498
4693
  return {
4499
4694
  explain: () => this.explain(matchWorker, sourceNode, targetNode),
4500
- isMatch: matchWorker.checkIsAssignableWith(sourceNode, targetNode),
4695
+ isMatch: matchWorker.checkIsAssignableTo(sourceNode, targetNode),
4501
4696
  };
4502
4697
  }
4503
4698
  }
@@ -4549,19 +4744,8 @@ class ToBeCallableWith extends AbilityMatcherBase {
4549
4744
  explainText = ExpectDiagnosticText.isCallable;
4550
4745
  explainNotText = ExpectDiagnosticText.isNotCallable;
4551
4746
  match(matchWorker, sourceNode, targetNodes, onDiagnostics) {
4552
- let type;
4553
- if (this.compiler.isCallExpression(sourceNode)) {
4554
- type = matchWorker.typeChecker.getResolvedSignature(sourceNode)?.getReturnType();
4555
- }
4556
- if (this.compiler.isArrowFunction(sourceNode) ||
4557
- this.compiler.isFunctionDeclaration(sourceNode) ||
4558
- this.compiler.isFunctionExpression(sourceNode) ||
4559
- this.compiler.isExpressionWithTypeArguments(sourceNode) ||
4560
- this.compiler.isIdentifier(sourceNode) ||
4561
- this.compiler.isPropertyAccessExpression(sourceNode)) {
4562
- type = matchWorker.getType(sourceNode);
4563
- }
4564
- if (!type || type.getCallSignatures().length === 0) {
4747
+ const sourceType = matchWorker.getType(sourceNode);
4748
+ if (sourceType.getCallSignatures().length === 0) {
4565
4749
  const text = [];
4566
4750
  if (nodeBelongsToArgumentList(this.compiler, sourceNode)) {
4567
4751
  text.push(ExpectDiagnosticText.argumentMustBe("source", "a callable expression"));
@@ -4569,7 +4753,7 @@ class ToBeCallableWith extends AbilityMatcherBase {
4569
4753
  else {
4570
4754
  text.push(ExpectDiagnosticText.typeArgumentMustBe("Source", "a callable type"));
4571
4755
  }
4572
- if (type != null && type.getConstructSignatures().length > 0) {
4756
+ if (sourceType.getConstructSignatures().length > 0) {
4573
4757
  text.push(ExpectDiagnosticText.didYouMeanToUse("the '.toBeConstructableWith()' matcher"));
4574
4758
  }
4575
4759
  const origin = DiagnosticOrigin.fromNode(sourceNode);
@@ -4768,8 +4952,8 @@ class ExpectService {
4768
4952
  toAcceptProps;
4769
4953
  toBe;
4770
4954
  toBeApplicable;
4955
+ toBeAssignableFrom;
4771
4956
  toBeAssignableTo;
4772
- toBeAssignableWith;
4773
4957
  toBeCallableWith;
4774
4958
  toBeConstructableWith;
4775
4959
  toHaveProperty;
@@ -4781,8 +4965,8 @@ class ExpectService {
4781
4965
  this.toAcceptProps = new ToAcceptProps(compiler, typeChecker);
4782
4966
  this.toBe = new ToBe();
4783
4967
  this.toBeApplicable = new ToBeApplicable(compiler);
4968
+ this.toBeAssignableFrom = new ToBeAssignableFrom();
4784
4969
  this.toBeAssignableTo = new ToBeAssignableTo();
4785
- this.toBeAssignableWith = new ToBeAssignableWith();
4786
4970
  this.toBeCallableWith = new ToBeCallableWith(compiler);
4787
4971
  this.toBeConstructableWith = new ToBeConstructableWith(compiler);
4788
4972
  this.toHaveProperty = new ToHaveProperty(compiler);
@@ -4804,8 +4988,8 @@ class ExpectService {
4804
4988
  switch (matcherNameText) {
4805
4989
  case "toAcceptProps":
4806
4990
  case "toBe":
4991
+ case "toBeAssignableFrom":
4807
4992
  case "toBeAssignableTo":
4808
- case "toBeAssignableWith":
4809
4993
  if (!argumentOrTypeArgumentIsProvided("target", "Target", assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
4810
4994
  return;
4811
4995
  }
@@ -5000,8 +5184,7 @@ class FixmeService {
5000
5184
  FixmeService.#range = FixmeService.#range?.previous;
5001
5185
  }
5002
5186
  if (isFail === false) {
5003
- const targetText = owner.node.expression.getText();
5004
- const text = [FixmeDiagnosticText.wasSupposedToFail(targetText), FixmeDiagnosticText.considerRemoving()];
5187
+ const text = [FixmeDiagnosticText.wasSupposedToFail(owner.brand), FixmeDiagnosticText.considerRemoving()];
5005
5188
  const origin = new DiagnosticOrigin(directive.namespace.start, directive.directive.end, directive.sourceFile);
5006
5189
  onFileDiagnostics([Diagnostic.error(text, origin)]);
5007
5190
  }
@@ -5053,7 +5236,7 @@ class TestTreeWalker {
5053
5236
  }
5054
5237
  async visit(nodes, runModeFlags, parentResult) {
5055
5238
  for (const node of nodes) {
5056
- if (this.#cancellationToken?.isCancellationRequested) {
5239
+ if (this.#cancellationToken?.isCancellationRequested()) {
5057
5240
  break;
5058
5241
  }
5059
5242
  const fixmeDirective = Directive.getDirectiveRange(this.#compiler, node, "fixme");
@@ -5064,6 +5247,7 @@ class TestTreeWalker {
5064
5247
  case "describe":
5065
5248
  await this.#visitDescribe(node, runModeFlags, parentResult);
5066
5249
  break;
5250
+ case "it":
5067
5251
  case "test":
5068
5252
  await this.#visitTest(node, runModeFlags, parentResult);
5069
5253
  break;
@@ -5162,7 +5346,7 @@ class TestTreeWalker {
5162
5346
  EventEmitter.dispatch(["test:skip", { result: testResult }]);
5163
5347
  return;
5164
5348
  }
5165
- const isPass = testResult.expectCount.failed === 0;
5349
+ const isPass = testResult.assertionCounts.failed === 0;
5166
5350
  if (FixmeService.isFixme(test, isPass)) {
5167
5351
  EventEmitter.dispatch(["test:fixme", { result: testResult }]);
5168
5352
  return;
@@ -5195,10 +5379,10 @@ class FileRunner {
5195
5379
  EventEmitter.dispatch(["file:error", { diagnostics, result }]);
5196
5380
  }
5197
5381
  async run(file, cancellationToken) {
5198
- if (cancellationToken.isCancellationRequested) {
5382
+ if (cancellationToken.isCancellationRequested()) {
5199
5383
  return;
5200
5384
  }
5201
- this.#projectService.openFile(file.path, undefined, this.#resolvedConfig.rootPath);
5385
+ this.#projectService.openFile(file.path);
5202
5386
  const fileResult = new FileResult(file);
5203
5387
  EventEmitter.dispatch(["file:start", { result: fileResult }]);
5204
5388
  await this.#run(file, fileResult, cancellationToken);
@@ -5235,13 +5419,11 @@ class FileRunner {
5235
5419
  this.#onDiagnostics([Diagnostic.error("A template test file must export a string.")], fileResult);
5236
5420
  return;
5237
5421
  }
5238
- this.#projectService.openFile(file.path, testText, this.#resolvedConfig.rootPath);
5422
+ this.#projectService.openFile(file.path, testText);
5239
5423
  return this.#resolveFileFacts(file, fileResult, runModeFlags);
5240
5424
  }
5241
5425
  const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics);
5242
- this.#suppressedService.match(testTree, (diagnostics) => {
5243
- this.#onDiagnostics(diagnostics, fileResult);
5244
- });
5426
+ this.#suppressedService.match(testTree);
5245
5427
  return { runModeFlags, testTree, typeChecker };
5246
5428
  }
5247
5429
  async #run(file, fileResult, cancellationToken) {
@@ -5272,7 +5454,7 @@ class FileRunner {
5272
5454
  class Runner {
5273
5455
  #eventEmitter = new EventEmitter();
5274
5456
  #resolvedConfig;
5275
- static version = "5.0.0-beta.0";
5457
+ static version = "5.0.0-beta.2";
5276
5458
  constructor(resolvedConfig) {
5277
5459
  this.#resolvedConfig = resolvedConfig;
5278
5460
  }
@@ -5336,7 +5518,7 @@ class Runner {
5336
5518
  EventEmitter.dispatch(["target:end", { result: targetResult }]);
5337
5519
  }
5338
5520
  EventEmitter.dispatch(["run:end", { result }]);
5339
- if (cancellationToken.reason === "failFast") {
5521
+ if (cancellationToken.getReason() === "failFast") {
5340
5522
  cancellationToken.reset();
5341
5523
  }
5342
5524
  }
@@ -5382,11 +5564,11 @@ class Cli {
5382
5564
  return;
5383
5565
  }
5384
5566
  const { commandLineOptions, pathMatch } = await Config.parseCommandLine(commandLine);
5385
- if (cancellationToken.isCancellationRequested) {
5567
+ if (cancellationToken.isCancellationRequested()) {
5386
5568
  return;
5387
5569
  }
5388
5570
  do {
5389
- if (cancellationToken.reason === "configChange") {
5571
+ if (cancellationToken.getReason() === "configChange") {
5390
5572
  cancellationToken.reset();
5391
5573
  exitCodeHandler.resetCode();
5392
5574
  OutputService.clearTerminal();
@@ -5400,7 +5582,7 @@ class Cli {
5400
5582
  commandLineOptions,
5401
5583
  pathMatch,
5402
5584
  });
5403
- if (cancellationToken.isCancellationRequested) {
5585
+ if (cancellationToken.isCancellationRequested()) {
5404
5586
  if (commandLine.includes("--watch")) {
5405
5587
  await this.#waitForChangedFiles(resolvedConfig, cancellationToken);
5406
5588
  }
@@ -5441,7 +5623,7 @@ class Cli {
5441
5623
  const runner = new Runner(resolvedConfig);
5442
5624
  await runner.run(testFiles, cancellationToken);
5443
5625
  PluginService.removeHandlers();
5444
- } while (cancellationToken.reason === "configChange");
5626
+ } while (cancellationToken.getReason() === "configChange");
5445
5627
  this.#eventEmitter.removeHandlers();
5446
5628
  }
5447
5629
  #waitForChangedFiles(resolvedConfig, cancellationToken) {
@@ -5472,4 +5654,4 @@ class Cli {
5472
5654
  }
5473
5655
  }
5474
5656
 
5475
- export { BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Directive, EventEmitter, ExitCodeHandler, ExpectNode, ExpectResult, ExpectService, FileLocation, FileResult, FileWatcher, Glob, InputService, Line, ListReporter, OptionBrand, OptionGroup, Options, OutputService, Path, PluginService, ProjectResult, ProjectService, Reject, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, Runner, Scribbler, Select, SelectDiagnosticText, SetupReporter, SourceFile, SourceService, Store, SummaryReporter, SuppressedService, TargetResult, TestResult, TestTree, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, WhenNode, WhenService, addsPackageText, argumentIsProvided, argumentOrTypeArgumentIsProvided, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, environmentOptions, fileStatusText, fileViewText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, nodeBelongsToArgumentList, summaryText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
5657
+ export { BaseReporter, CancellationReason, CancellationToken, Cli, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Directive, EventEmitter, ExpectResult, FileLocation, FileResult, Line, ListReporter, OptionBrand, OptionGroup, Options, OutputService, Path, PluginService, ProjectResult, Result, ResultStatus, Runner, Scribbler, Select, SelectDiagnosticText, SetupReporter, Store, SummaryReporter, SuppressedResult, TargetResult, TestResult, Text, Version, WatchReporter, addsPackageText, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, environmentOptions, fileStatusText, fileViewText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, summaryText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };