tstyche 4.3.0 → 5.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/tstyche.js CHANGED
@@ -6,34 +6,117 @@ import os from 'node:os';
6
6
  import process from 'node:process';
7
7
  import { createRequire } from 'node:module';
8
8
  import vm from 'node:vm';
9
- import streamConsumers from 'node:stream/consumers';
10
9
 
11
- var DiagnosticCategory;
12
- (function (DiagnosticCategory) {
13
- DiagnosticCategory["Error"] = "error";
14
- DiagnosticCategory["Warning"] = "warning";
15
- })(DiagnosticCategory || (DiagnosticCategory = {}));
10
+ class EventEmitter {
11
+ static instanceCount = 0;
12
+ static #handlers = new Map();
13
+ static #reporters = new Map();
14
+ #scope;
15
+ constructor() {
16
+ this.#scope = EventEmitter.instanceCount++;
17
+ EventEmitter.#handlers.set(this.#scope, new Set());
18
+ EventEmitter.#reporters.set(this.#scope, new Set());
19
+ }
20
+ addHandler(handler) {
21
+ EventEmitter.#handlers.get(this.#scope)?.add(handler);
22
+ }
23
+ addReporter(reporter) {
24
+ EventEmitter.#reporters.get(this.#scope)?.add(reporter);
25
+ }
26
+ static dispatch(event) {
27
+ function forEachHandler(handlers, event) {
28
+ for (const handler of handlers) {
29
+ handler.on(event);
30
+ }
31
+ }
32
+ for (const handlers of EventEmitter.#handlers.values()) {
33
+ forEachHandler(handlers, event);
34
+ }
35
+ for (const handlers of EventEmitter.#reporters.values()) {
36
+ forEachHandler(handlers, event);
37
+ }
38
+ }
39
+ removeHandler(handler) {
40
+ EventEmitter.#handlers.get(this.#scope)?.delete(handler);
41
+ }
42
+ removeReporter(reporter) {
43
+ EventEmitter.#reporters.get(this.#scope)?.delete(reporter);
44
+ }
45
+ removeHandlers() {
46
+ EventEmitter.#handlers.get(this.#scope)?.clear();
47
+ }
48
+ removeReporters() {
49
+ EventEmitter.#reporters.get(this.#scope)?.clear();
50
+ }
51
+ }
52
+
53
+ class JsonNode {
54
+ origin;
55
+ text;
56
+ constructor(text, origin) {
57
+ this.origin = origin;
58
+ this.text = text;
59
+ }
60
+ getValue(options) {
61
+ if (this.text == null) {
62
+ return undefined;
63
+ }
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;
80
+ }
81
+ }
82
+
83
+ class SourceService {
84
+ static #files = new Map();
85
+ static delete(filePath) {
86
+ SourceService.#files.delete(filePath);
87
+ }
88
+ static get(source) {
89
+ const file = SourceService.#files.get(source.fileName);
90
+ if (file != null) {
91
+ return file;
92
+ }
93
+ return source;
94
+ }
95
+ static set(source) {
96
+ SourceService.#files.set(source.fileName, source);
97
+ }
98
+ }
16
99
 
17
100
  class DiagnosticOrigin {
18
- assertion;
101
+ assertionNode;
19
102
  end;
20
103
  sourceFile;
21
104
  start;
22
- constructor(start, end, sourceFile, assertion) {
105
+ constructor(start, end, sourceFile, assertionNode) {
23
106
  this.start = start;
24
107
  this.end = end;
25
- this.sourceFile = sourceFile;
26
- this.assertion = assertion;
108
+ this.sourceFile = SourceService.get(sourceFile);
109
+ this.assertionNode = assertionNode;
27
110
  }
28
- static fromAssertion(assertion) {
29
- const node = assertion.matcherNameNode.name;
30
- return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertion);
111
+ static fromAssertion(assertionNode) {
112
+ const node = assertionNode.matcherNameNode.name;
113
+ return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertionNode);
31
114
  }
32
- static fromNode(node, assertion) {
33
- return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertion);
115
+ static fromNode(node, assertionNode) {
116
+ return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertionNode);
34
117
  }
35
- static fromNodes(nodes, assertion) {
36
- return new DiagnosticOrigin(nodes.pos, nodes.end, nodes[0].getSourceFile(), assertion);
118
+ static fromNodes(nodes, assertionNode) {
119
+ return new DiagnosticOrigin(nodes.pos, nodes.end, nodes[0].getSourceFile(), assertionNode);
37
120
  }
38
121
  }
39
122
 
@@ -82,32 +165,141 @@ class Diagnostic {
82
165
  return this;
83
166
  }
84
167
  static error(text, origin) {
85
- return new Diagnostic(text, DiagnosticCategory.Error, origin);
168
+ return new Diagnostic(text, "error", origin);
86
169
  }
87
170
  extendWith(text, origin) {
88
171
  return new Diagnostic([this.text, text].flat(), this.category, origin ?? this.origin);
89
172
  }
90
- static fromDiagnostics(diagnostics, sourceFile) {
173
+ static fromDiagnostics(diagnostics) {
91
174
  return diagnostics.map((diagnostic) => {
92
175
  const code = `ts(${diagnostic.code})`;
93
176
  let origin;
94
177
  if (isDiagnosticWithLocation(diagnostic)) {
95
- origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceFile ?? diagnostic.file);
178
+ origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), diagnostic.file);
96
179
  }
97
180
  let related;
98
181
  if (diagnostic.relatedInformation != null) {
99
182
  related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
100
183
  }
101
184
  const text = getDiagnosticMessageText(diagnostic);
102
- return new Diagnostic(text, DiagnosticCategory.Error, origin).add({ code, related });
185
+ return new Diagnostic(text, "error", origin).add({ code, related });
103
186
  });
104
187
  }
105
188
  static warning(text, origin) {
106
- return new Diagnostic(text, DiagnosticCategory.Warning, origin);
189
+ return new Diagnostic(text, "warning", origin);
190
+ }
191
+ }
192
+
193
+ var DiagnosticCategory;
194
+ (function (DiagnosticCategory) {
195
+ DiagnosticCategory["Error"] = "error";
196
+ DiagnosticCategory["Warning"] = "warning";
197
+ })(DiagnosticCategory || (DiagnosticCategory = {}));
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;
107
299
  }
108
300
  }
109
301
 
110
- class SourceFile {
302
+ class JsonSourceFile {
111
303
  fileName;
112
304
  #lineMap;
113
305
  text;
@@ -141,49 +333,6 @@ class SourceFile {
141
333
  }
142
334
  }
143
335
 
144
- class EventEmitter {
145
- static instanceCount = 0;
146
- static #handlers = new Map();
147
- static #reporters = new Map();
148
- #scope;
149
- constructor() {
150
- this.#scope = EventEmitter.instanceCount++;
151
- EventEmitter.#handlers.set(this.#scope, new Set());
152
- EventEmitter.#reporters.set(this.#scope, new Set());
153
- }
154
- addHandler(handler) {
155
- EventEmitter.#handlers.get(this.#scope)?.add(handler);
156
- }
157
- addReporter(reporter) {
158
- EventEmitter.#reporters.get(this.#scope)?.add(reporter);
159
- }
160
- static dispatch(event) {
161
- function forEachHandler(handlers, event) {
162
- for (const handler of handlers) {
163
- handler.on(event);
164
- }
165
- }
166
- for (const handlers of EventEmitter.#handlers.values()) {
167
- forEachHandler(handlers, event);
168
- }
169
- for (const handlers of EventEmitter.#reporters.values()) {
170
- forEachHandler(handlers, event);
171
- }
172
- }
173
- removeHandler(handler) {
174
- EventEmitter.#handlers.get(this.#scope)?.delete(handler);
175
- }
176
- removeReporter(reporter) {
177
- EventEmitter.#reporters.get(this.#scope)?.delete(reporter);
178
- }
179
- removeHandlers() {
180
- EventEmitter.#handlers.get(this.#scope)?.clear();
181
- }
182
- removeReporters() {
183
- EventEmitter.#reporters.get(this.#scope)?.clear();
184
- }
185
- }
186
-
187
336
  class Path {
188
337
  static normalizeSlashes;
189
338
  static {
@@ -237,19 +386,22 @@ class ConfigDiagnosticText {
237
386
  static moduleWasNotFound(specifier) {
238
387
  return `The specified module '${specifier}' was not found.`;
239
388
  }
389
+ static optionValueMustBe(optionName, optionBrand) {
390
+ return `Value for the '${optionName}' option must be a ${optionBrand}.`;
391
+ }
240
392
  static rangeIsNotValid(value) {
241
393
  return `The specified range '${value}' is not valid.`;
242
394
  }
395
+ static rangeDoesNotMatchSupported(value) {
396
+ return `The specified range '${value}' does not match any supported TypeScript versions.`;
397
+ }
243
398
  static rangeUsage() {
244
399
  return [
245
- "A range must be specified using an operator and a minor version.",
246
- "To set an upper bound, the intersection of two ranges can be used.",
247
- "Examples: '>=5.5', '>=5.0 <5.3'.",
400
+ "A range must be specified using an operator and a minor version: '>=5.5'.",
401
+ "To set an upper bound, use the intersection of two ranges: '>=5.0 <5.3'.",
402
+ "Use the '||' operator to join ranges into a union: '>=5.2 <=5.3 || 5.4.2 || >5.5'.",
248
403
  ];
249
404
  }
250
- static requiresValueType(optionName, optionBrand) {
251
- return `Option '${optionName}' requires a value of type ${optionBrand}.`;
252
- }
253
405
  static seen(element) {
254
406
  return `The ${element} was seen here.`;
255
407
  }
@@ -259,46 +411,14 @@ class ConfigDiagnosticText {
259
411
  static unknownOption(optionName) {
260
412
  return `Unknown option '${optionName}'.`;
261
413
  }
262
- static usage(optionName, optionBrand) {
263
- switch (optionName.startsWith("--") ? optionName.slice(2) : optionName) {
264
- case "target": {
265
- const text = [];
266
- if (optionName.startsWith("--")) {
267
- text.push("Value for the '--target' option must be a string or a comma separated list.", "Examples: '--target 5.2', '--target next', '--target '>=5.0 <5.3, 5.4.2, >=5.5''.");
268
- }
269
- return text;
270
- }
271
- }
272
- return [ConfigDiagnosticText.requiresValueType(optionName, optionBrand)];
273
- }
274
414
  static versionIsNotSupported(value) {
275
- if (value === "current") {
276
- return "Cannot use 'current' as a target. Failed to resolve the installed TypeScript module.";
277
- }
278
- return `TypeScript version '${value}' is not supported.`;
415
+ return `The TypeScript version '${value}' is not supported.`;
279
416
  }
280
417
  static watchCannotBeEnabled() {
281
418
  return "Watch mode cannot be enabled in continuous integration environment.";
282
419
  }
283
420
  }
284
421
 
285
- var OptionBrand;
286
- (function (OptionBrand) {
287
- OptionBrand["String"] = "string";
288
- OptionBrand["Number"] = "number";
289
- OptionBrand["Boolean"] = "boolean";
290
- OptionBrand["BareTrue"] = "bareTrue";
291
- OptionBrand["List"] = "list";
292
- })(OptionBrand || (OptionBrand = {}));
293
-
294
- var OptionGroup;
295
- (function (OptionGroup) {
296
- OptionGroup[OptionGroup["CommandLine"] = 2] = "CommandLine";
297
- OptionGroup[OptionGroup["ConfigFile"] = 4] = "ConfigFile";
298
- OptionGroup[OptionGroup["InlineConditions"] = 8] = "InlineConditions";
299
- OptionGroup[OptionGroup["ResolvedConfig"] = 6] = "ResolvedConfig";
300
- })(OptionGroup || (OptionGroup = {}));
301
-
302
422
  class Environment {
303
423
  static resolve() {
304
424
  return {
@@ -555,6 +675,9 @@ class Manifest {
555
675
  return;
556
676
  }
557
677
  resolve(tag) {
678
+ if (tag === "*") {
679
+ return this.resolutions["latest"];
680
+ }
558
681
  if (this.versions.includes(tag)) {
559
682
  return tag;
560
683
  }
@@ -663,32 +786,58 @@ class ManifestService {
663
786
  }
664
787
 
665
788
  class TarReader {
666
- static #textDecoder = new TextDecoder();
667
- static async *extract(stream) {
668
- const decompressedStream = stream.pipeThrough(new DecompressionStream("gzip"));
669
- const buffer = await streamConsumers.arrayBuffer(decompressedStream);
670
- let offset = 0;
671
- while (offset < buffer.byteLength - 512) {
672
- const name = TarReader.#read(buffer, offset, 100);
673
- if (name.length === 0) {
789
+ #leftover = new Uint8Array(0);
790
+ #reader;
791
+ #textDecoder = new TextDecoder();
792
+ constructor(stream) {
793
+ this.#reader = stream.getReader();
794
+ }
795
+ async *extract() {
796
+ while (true) {
797
+ const header = await this.#read(512);
798
+ if (this.#isEndOfArchive(header)) {
674
799
  break;
675
800
  }
676
- const size = Number.parseInt(TarReader.#read(buffer, offset + 124, 12), 8);
677
- const contents = new Uint8Array(buffer, offset + 512, size);
678
- yield { name, contents };
679
- offset += 512 + 512 * Math.trunc(size / 512);
680
- if (size % 512) {
681
- offset += 512;
801
+ const name = this.#textDecoder.decode(header.subarray(0, 100)).replace(/\0.*$/, "");
802
+ const sizeOctal = this.#textDecoder.decode(header.subarray(124, 136)).replace(/\0.*$/, "").trim();
803
+ const size = Number.parseInt(sizeOctal, 8);
804
+ const content = await this.#read(size);
805
+ yield { name, content };
806
+ if (size % 512 !== 0) {
807
+ const toSkip = 512 - (size % 512);
808
+ await this.#read(toSkip);
682
809
  }
683
810
  }
684
811
  }
685
- static #read(buffer, byteOffset, length) {
686
- let view = new Uint8Array(buffer, byteOffset, length);
687
- const zeroIndex = view.indexOf(0);
688
- if (zeroIndex !== -1) {
689
- view = view.subarray(0, zeroIndex);
812
+ #isEndOfArchive(entry) {
813
+ return entry.every((byte) => byte === 0);
814
+ }
815
+ async #read(n) {
816
+ const result = new Uint8Array(n);
817
+ let filled = 0;
818
+ if (this.#leftover.length > 0) {
819
+ const toCopy = Math.min(this.#leftover.length, n);
820
+ result.set(this.#leftover.subarray(0, toCopy), filled);
821
+ filled += toCopy;
822
+ this.#leftover = this.#leftover.subarray(toCopy);
823
+ if (filled === n) {
824
+ return result;
825
+ }
690
826
  }
691
- return TarReader.#textDecoder.decode(view);
827
+ while (filled < n) {
828
+ const { value, done } = await this.#reader.read();
829
+ if (done) {
830
+ break;
831
+ }
832
+ const toCopy = Math.min(value.length, n - filled);
833
+ result.set(value.subarray(0, toCopy), filled);
834
+ filled += toCopy;
835
+ if (toCopy < value.length) {
836
+ this.#leftover = value.subarray(toCopy);
837
+ break;
838
+ }
839
+ }
840
+ return result.subarray(0, filled);
692
841
  }
693
842
  }
694
843
 
@@ -706,16 +855,18 @@ class PackageService {
706
855
  const response = await this.#fetcher.get(request, diagnostic);
707
856
  if (response?.body != null) {
708
857
  const targetPath = `${packagePath}-${Math.random().toString(32).slice(2)}`;
709
- for await (const file of TarReader.extract(response.body)) {
858
+ const stream = response.body.pipeThrough(new DecompressionStream("gzip"));
859
+ const tarReader = new TarReader(stream);
860
+ for await (const file of tarReader.extract()) {
710
861
  if (!file.name.startsWith("package/")) {
711
862
  continue;
712
863
  }
713
- const filePath = Path.join(targetPath, file.name.replace("package/", ""));
864
+ const filePath = Path.join(targetPath, file.name.replace(/^package\//, ""));
714
865
  const directoryPath = Path.dirname(filePath);
715
866
  if (!existsSync(directoryPath)) {
716
867
  await fs.mkdir(directoryPath, { recursive: true });
717
868
  }
718
- await fs.writeFile(filePath, file.contents);
869
+ await fs.writeFile(filePath, file.content);
719
870
  }
720
871
  await fs.rename(targetPath, packagePath);
721
872
  return packagePath;
@@ -765,7 +916,7 @@ class Store {
765
916
  Store.#manifestService = new ManifestService(Store.#storePath, Store.#npmRegistry, Store.#fetcher);
766
917
  }
767
918
  static async fetch(tag) {
768
- if (tag === "current") {
919
+ if (tag === "*" && environmentOptions.typescriptModule != null) {
769
920
  return;
770
921
  }
771
922
  await Store.open();
@@ -782,7 +933,7 @@ class Store {
782
933
  return compilerInstance;
783
934
  }
784
935
  let modulePath;
785
- if (tag === "current" && environmentOptions.typescriptModule != null) {
936
+ if (tag === "*" && environmentOptions.typescriptModule != null) {
786
937
  modulePath = fileURLToPath(environmentOptions.typescriptModule);
787
938
  }
788
939
  else {
@@ -830,7 +981,7 @@ class Store {
830
981
  Store.open = () => Promise.resolve();
831
982
  Store.manifest = await Store.#manifestService.open();
832
983
  if (Store.manifest != null) {
833
- Store.#supportedTags = [...Object.keys(Store.manifest.resolutions), ...Store.manifest.versions, "current"].sort();
984
+ Store.#supportedTags = [...Object.keys(Store.manifest.resolutions), ...Store.manifest.versions];
834
985
  }
835
986
  }
836
987
  static async prune() {
@@ -840,8 +991,8 @@ class Store {
840
991
  await Store.#manifestService.open({ refresh: true });
841
992
  }
842
993
  static async validateTag(tag) {
843
- if (tag === "current") {
844
- return environmentOptions.typescriptModule != null;
994
+ if (tag === "*") {
995
+ return true;
845
996
  }
846
997
  await Store.open();
847
998
  if (Store.manifest?.isOutdated({ ageTolerance: 60 }) &&
@@ -859,23 +1010,25 @@ class Store {
859
1010
 
860
1011
  class Target {
861
1012
  static #rangeRegex = /^[<>]=?\d\.\d( [<>]=?\d\.\d)?$/;
862
- static async expand(queries) {
863
- const include = [];
864
- for (const query of queries) {
865
- if (!Target.isRange(query)) {
866
- include.push(query);
867
- continue;
868
- }
1013
+ static async expand(range, onDiagnostics, origin) {
1014
+ if (Target.isRange(range)) {
869
1015
  await Store.open();
870
1016
  if (Store.manifest != null) {
871
1017
  let versions = [...Store.manifest.minorVersions];
872
- for (const comparator of query.split(" ")) {
873
- versions = Target.#filter(comparator, versions);
1018
+ for (const comparator of range.split(" ")) {
1019
+ versions = Target.#filter(comparator.trim(), versions);
1020
+ if (versions.length === 0) {
1021
+ const text = [
1022
+ ConfigDiagnosticText.rangeDoesNotMatchSupported(range),
1023
+ ConfigDiagnosticText.inspectSupportedVersions(),
1024
+ ];
1025
+ onDiagnostics(Diagnostic.error(text, origin));
1026
+ }
874
1027
  }
875
- include.push(...versions);
1028
+ return versions;
876
1029
  }
877
1030
  }
878
- return include;
1031
+ return [range];
879
1032
  }
880
1033
  static #filter(comparator, versions) {
881
1034
  const targetVersion = comparator.replace(/^[<>]=?/, "");
@@ -894,178 +1047,177 @@ class Target {
894
1047
  static isRange(query) {
895
1048
  return Target.#rangeRegex.test(query);
896
1049
  }
1050
+ static split(range) {
1051
+ return range.split(/ *\|\| */);
1052
+ }
897
1053
  }
898
1054
 
899
1055
  class Options {
900
1056
  static #definitions = [
901
1057
  {
902
- brand: OptionBrand.String,
1058
+ brand: "string",
903
1059
  description: "The Url to the config file validation schema.",
904
- group: OptionGroup.ConfigFile,
1060
+ group: 4,
905
1061
  name: "$schema",
906
1062
  },
907
1063
  {
908
- brand: OptionBrand.Boolean,
909
- description: "Enable type error reporting for source files.",
910
- group: OptionGroup.ConfigFile,
911
- name: "checkSourceFiles",
1064
+ brand: "boolean",
1065
+ description: "Check declaration files for type errors.",
1066
+ group: 4,
1067
+ name: "checkDeclarationFiles",
912
1068
  },
913
1069
  {
914
- brand: OptionBrand.Boolean,
1070
+ brand: "boolean",
915
1071
  description: "Check errors silenced by '// @ts-expect-error' directives.",
916
- group: OptionGroup.ConfigFile,
1072
+ group: 4,
917
1073
  name: "checkSuppressedErrors",
918
1074
  },
919
1075
  {
920
- brand: OptionBrand.String,
1076
+ brand: "string",
921
1077
  description: "The path to a TSTyche configuration file.",
922
- group: OptionGroup.CommandLine,
1078
+ group: 2,
923
1079
  name: "config",
924
1080
  },
925
1081
  {
926
- brand: OptionBrand.Boolean,
1082
+ brand: "boolean",
927
1083
  description: "Stop running tests after the first failed assertion.",
928
- group: OptionGroup.ConfigFile | OptionGroup.CommandLine,
1084
+ group: 4 | 2,
929
1085
  name: "failFast",
930
1086
  },
931
1087
  {
932
- brand: OptionBrand.BareTrue,
1088
+ brand: "true",
933
1089
  description: "Fetch the specified versions of the 'typescript' package and exit.",
934
- group: OptionGroup.CommandLine,
1090
+ group: 2,
935
1091
  name: "fetch",
936
1092
  },
937
1093
  {
938
- brand: OptionBrand.List,
1094
+ brand: "list",
939
1095
  description: "The list of glob patterns matching the fixture files.",
940
- group: OptionGroup.ConfigFile,
1096
+ group: 4,
941
1097
  items: {
942
- brand: OptionBrand.String,
1098
+ brand: "string",
943
1099
  name: "fixtureFileMatch",
944
1100
  },
945
1101
  name: "fixtureFileMatch",
946
1102
  },
947
1103
  {
948
- brand: OptionBrand.BareTrue,
1104
+ brand: "true",
949
1105
  description: "Print the list of command line options with brief descriptions and exit.",
950
- group: OptionGroup.CommandLine,
1106
+ group: 2,
951
1107
  name: "help",
952
1108
  },
953
1109
  {
954
- brand: OptionBrand.BareTrue,
1110
+ brand: "true",
955
1111
  description: "Print the list of supported versions of the 'typescript' package and exit.",
956
- group: OptionGroup.CommandLine,
1112
+ group: 2,
957
1113
  name: "list",
958
1114
  },
959
1115
  {
960
- brand: OptionBrand.BareTrue,
1116
+ brand: "true",
961
1117
  description: "Print the list of the selected test files and exit.",
962
- group: OptionGroup.CommandLine,
1118
+ group: 2,
963
1119
  name: "listFiles",
964
1120
  },
965
1121
  {
966
- brand: OptionBrand.String,
1122
+ brand: "string",
967
1123
  description: "Only run tests with matching name.",
968
- group: OptionGroup.CommandLine,
1124
+ group: 2,
969
1125
  name: "only",
970
1126
  },
971
1127
  {
972
- brand: OptionBrand.List,
1128
+ brand: "list",
973
1129
  description: "The list of plugins to use.",
974
- group: OptionGroup.CommandLine | OptionGroup.ConfigFile,
1130
+ group: 2 | 4,
975
1131
  items: {
976
- brand: OptionBrand.String,
1132
+ brand: "string",
977
1133
  name: "plugins",
978
1134
  },
979
1135
  name: "plugins",
980
1136
  },
981
1137
  {
982
- brand: OptionBrand.BareTrue,
1138
+ brand: "true",
983
1139
  description: "Remove all installed versions of the 'typescript' package and exit.",
984
- group: OptionGroup.CommandLine,
1140
+ group: 2,
985
1141
  name: "prune",
986
1142
  },
987
1143
  {
988
- brand: OptionBrand.Boolean,
1144
+ brand: "boolean",
989
1145
  description: "Reject the 'any' type passed as an argument to the 'expect()' function or a matcher.",
990
- group: OptionGroup.ConfigFile,
1146
+ group: 4,
991
1147
  name: "rejectAnyType",
992
1148
  },
993
1149
  {
994
- brand: OptionBrand.Boolean,
1150
+ brand: "boolean",
995
1151
  description: "Reject the 'never' type passed as an argument to the 'expect()' function or a matcher.",
996
- group: OptionGroup.ConfigFile,
1152
+ group: 4,
997
1153
  name: "rejectNeverType",
998
1154
  },
999
1155
  {
1000
- brand: OptionBrand.List,
1156
+ brand: "list",
1001
1157
  description: "The list of reporters to use.",
1002
- group: OptionGroup.CommandLine | OptionGroup.ConfigFile,
1158
+ group: 2 | 4,
1003
1159
  items: {
1004
- brand: OptionBrand.String,
1160
+ brand: "string",
1005
1161
  name: "reporters",
1006
1162
  },
1007
1163
  name: "reporters",
1008
1164
  },
1009
1165
  {
1010
- brand: OptionBrand.String,
1166
+ brand: "string",
1011
1167
  description: "The path to a directory containing files of a test project.",
1012
- group: OptionGroup.ConfigFile,
1168
+ group: 4,
1013
1169
  name: "rootPath",
1014
1170
  },
1015
1171
  {
1016
- brand: OptionBrand.BareTrue,
1172
+ brand: "true",
1017
1173
  description: "Print the resolved configuration and exit.",
1018
- group: OptionGroup.CommandLine,
1174
+ group: 2,
1019
1175
  name: "showConfig",
1020
1176
  },
1021
1177
  {
1022
- brand: OptionBrand.String,
1178
+ brand: "string",
1023
1179
  description: "Skip tests with matching name.",
1024
- group: OptionGroup.CommandLine,
1180
+ group: 2,
1025
1181
  name: "skip",
1026
1182
  },
1027
1183
  {
1028
- brand: OptionBrand.List,
1029
- description: "The list of TypeScript versions to be tested on.",
1030
- group: OptionGroup.CommandLine | OptionGroup.ConfigFile | OptionGroup.InlineConditions,
1031
- items: {
1032
- brand: OptionBrand.String,
1033
- name: "target",
1034
- },
1184
+ brand: "range",
1185
+ description: "The range of TypeScript versions to be tested against.",
1186
+ group: 2 | 4 | 8,
1035
1187
  name: "target",
1036
1188
  },
1037
1189
  {
1038
- brand: OptionBrand.List,
1190
+ brand: "list",
1039
1191
  description: "The list of glob patterns matching the test files.",
1040
- group: OptionGroup.ConfigFile,
1192
+ group: 4,
1041
1193
  items: {
1042
- brand: OptionBrand.String,
1194
+ brand: "string",
1043
1195
  name: "testFileMatch",
1044
1196
  },
1045
1197
  name: "testFileMatch",
1046
1198
  },
1047
1199
  {
1048
- brand: OptionBrand.String,
1200
+ brand: "string",
1049
1201
  description: "The look up strategy to be used to find the TSConfig file.",
1050
- group: OptionGroup.CommandLine | OptionGroup.ConfigFile,
1202
+ group: 2 | 4,
1051
1203
  name: "tsconfig",
1052
1204
  },
1053
1205
  {
1054
- brand: OptionBrand.BareTrue,
1206
+ brand: "true",
1055
1207
  description: "Fetch the 'typescript' package metadata from the registry and exit.",
1056
- group: OptionGroup.CommandLine,
1208
+ group: 2,
1057
1209
  name: "update",
1058
1210
  },
1059
1211
  {
1060
- brand: OptionBrand.BareTrue,
1212
+ brand: "true",
1061
1213
  description: "Print the version number and exit.",
1062
- group: OptionGroup.CommandLine,
1214
+ group: 2,
1063
1215
  name: "version",
1064
1216
  },
1065
1217
  {
1066
- brand: OptionBrand.BareTrue,
1218
+ brand: "true",
1067
1219
  description: "Watch for changes and rerun related test files.",
1068
- group: OptionGroup.CommandLine,
1220
+ group: 2,
1069
1221
  name: "watch",
1070
1222
  },
1071
1223
  ];
@@ -1117,7 +1269,7 @@ class Options {
1117
1269
  }
1118
1270
  return optionValue;
1119
1271
  }
1120
- static async validate(optionName, optionValue, optionBrand, onDiagnostics, origin) {
1272
+ static async validate(optionName, optionValue, onDiagnostics, origin) {
1121
1273
  const canonicalOptionName = Options.#getCanonicalOptionName(optionName);
1122
1274
  switch (canonicalOptionName) {
1123
1275
  case "config":
@@ -1144,16 +1296,17 @@ class Options {
1144
1296
  case "target": {
1145
1297
  if (/[<>=]/.test(optionValue)) {
1146
1298
  if (!Target.isRange(optionValue)) {
1147
- onDiagnostics(Diagnostic.error([ConfigDiagnosticText.rangeIsNotValid(optionValue), ...ConfigDiagnosticText.rangeUsage()], origin));
1299
+ const text = [ConfigDiagnosticText.rangeIsNotValid(optionValue), ...ConfigDiagnosticText.rangeUsage()];
1300
+ onDiagnostics(Diagnostic.error(text, origin));
1148
1301
  }
1149
1302
  break;
1150
1303
  }
1151
1304
  if ((await Store.validateTag(optionValue)) === false) {
1152
- onDiagnostics(Diagnostic.error([
1305
+ const text = [
1153
1306
  ConfigDiagnosticText.versionIsNotSupported(optionValue),
1154
- ...ConfigDiagnosticText.usage(optionName, optionBrand),
1155
1307
  ConfigDiagnosticText.inspectSupportedVersions(),
1156
- ], origin));
1308
+ ];
1309
+ onDiagnostics(Diagnostic.error(text, origin));
1157
1310
  }
1158
1311
  break;
1159
1312
  }
@@ -1174,7 +1327,7 @@ class Options {
1174
1327
  }
1175
1328
  }
1176
1329
 
1177
- class CommandLineParser {
1330
+ class CommandParser {
1178
1331
  #commandLineOptions;
1179
1332
  #onDiagnostics;
1180
1333
  #options;
@@ -1183,12 +1336,12 @@ class CommandLineParser {
1183
1336
  this.#commandLineOptions = commandLine;
1184
1337
  this.#pathMatch = pathMatch;
1185
1338
  this.#onDiagnostics = onDiagnostics;
1186
- this.#options = Options.for(OptionGroup.CommandLine);
1339
+ this.#options = Options.for(2);
1187
1340
  }
1188
1341
  #onExpectsValue(optionName, optionBrand) {
1189
1342
  const text = [
1190
1343
  ConfigDiagnosticText.expectsValue(optionName),
1191
- ...ConfigDiagnosticText.usage(optionName, optionBrand),
1344
+ ConfigDiagnosticText.optionValueMustBe(optionName, optionBrand),
1192
1345
  ];
1193
1346
  this.#onDiagnostics(Diagnostic.error(text));
1194
1347
  }
@@ -1218,18 +1371,18 @@ class CommandLineParser {
1218
1371
  async #parseOptionValue(commandLineArgs, index, optionName, optionDefinition) {
1219
1372
  let optionValue = this.#resolveOptionValue(commandLineArgs[index]);
1220
1373
  switch (optionDefinition.brand) {
1221
- case OptionBrand.BareTrue:
1222
- await Options.validate(optionName, optionValue, optionDefinition.brand, this.#onDiagnostics);
1374
+ case "true":
1375
+ await Options.validate(optionName, optionValue, this.#onDiagnostics);
1223
1376
  this.#commandLineOptions[optionDefinition.name] = true;
1224
1377
  break;
1225
- case OptionBrand.Boolean:
1226
- await Options.validate(optionName, optionValue, optionDefinition.brand, this.#onDiagnostics);
1378
+ case "boolean":
1379
+ await Options.validate(optionName, optionValue, this.#onDiagnostics);
1227
1380
  this.#commandLineOptions[optionDefinition.name] = optionValue !== "false";
1228
1381
  if (optionValue === "false" || optionValue === "true") {
1229
1382
  index++;
1230
1383
  }
1231
1384
  break;
1232
- case OptionBrand.List:
1385
+ case "list":
1233
1386
  if (optionValue !== "") {
1234
1387
  const optionValues = optionValue
1235
1388
  .split(",")
@@ -1237,7 +1390,7 @@ class CommandLineParser {
1237
1390
  .filter((value) => value !== "")
1238
1391
  .map((value) => Options.resolve(optionName, value));
1239
1392
  for (const optionValue of optionValues) {
1240
- await Options.validate(optionName, optionValue, optionDefinition.brand, this.#onDiagnostics);
1393
+ await Options.validate(optionName, optionValue, this.#onDiagnostics);
1241
1394
  }
1242
1395
  this.#commandLineOptions[optionDefinition.name] = optionValues;
1243
1396
  index++;
@@ -1245,16 +1398,30 @@ class CommandLineParser {
1245
1398
  }
1246
1399
  this.#onExpectsValue(optionName, optionDefinition.brand);
1247
1400
  break;
1248
- case OptionBrand.String:
1401
+ case "string":
1249
1402
  if (optionValue !== "") {
1250
1403
  optionValue = Options.resolve(optionName, optionValue);
1251
- await Options.validate(optionName, optionValue, optionDefinition.brand, this.#onDiagnostics);
1404
+ await Options.validate(optionName, optionValue, this.#onDiagnostics);
1252
1405
  this.#commandLineOptions[optionDefinition.name] = optionValue;
1253
1406
  index++;
1254
1407
  break;
1255
1408
  }
1256
1409
  this.#onExpectsValue(optionName, optionDefinition.brand);
1257
1410
  break;
1411
+ case "range":
1412
+ if (optionValue !== "") {
1413
+ const optionValues = [];
1414
+ for (const range of Target.split(optionValue)) {
1415
+ await Options.validate(optionName, range, this.#onDiagnostics);
1416
+ const versions = await Target.expand(range, this.#onDiagnostics);
1417
+ optionValues.push(...versions);
1418
+ }
1419
+ this.#commandLineOptions[optionDefinition.name] = optionValues;
1420
+ index++;
1421
+ break;
1422
+ }
1423
+ this.#onExpectsValue(optionName, "string");
1424
+ break;
1258
1425
  }
1259
1426
  return index;
1260
1427
  }
@@ -1276,43 +1443,58 @@ class ConfigParser {
1276
1443
  this.#sourceFile = sourceFile;
1277
1444
  this.#options = Options.for(optionGroup);
1278
1445
  }
1279
- #onRequiresValue(optionDefinition, jsonNode, isListItem) {
1446
+ #onRequiresValue(optionName, optionBrand, jsonNode, isListItem) {
1280
1447
  const text = isListItem
1281
- ? ConfigDiagnosticText.expectsListItemType(optionDefinition.name, optionDefinition.brand)
1282
- : ConfigDiagnosticText.requiresValueType(optionDefinition.name, optionDefinition.brand);
1448
+ ? ConfigDiagnosticText.expectsListItemType(optionName, optionBrand)
1449
+ : ConfigDiagnosticText.optionValueMustBe(optionName, optionBrand);
1283
1450
  this.#onDiagnostics(Diagnostic.error(text, jsonNode.origin));
1284
1451
  }
1285
1452
  async #parseValue(optionDefinition, isListItem = false) {
1286
1453
  let jsonNode;
1287
1454
  let optionValue;
1288
1455
  switch (optionDefinition.brand) {
1289
- case OptionBrand.Boolean: {
1456
+ case "boolean": {
1290
1457
  jsonNode = this.#jsonScanner.read();
1291
1458
  optionValue = jsonNode.getValue();
1292
1459
  if (typeof optionValue !== "boolean") {
1293
- this.#onRequiresValue(optionDefinition, jsonNode, isListItem);
1460
+ this.#onRequiresValue(optionDefinition.name, optionDefinition.brand, jsonNode, isListItem);
1294
1461
  break;
1295
1462
  }
1296
1463
  break;
1297
1464
  }
1298
- case OptionBrand.String: {
1465
+ case "string": {
1299
1466
  jsonNode = this.#jsonScanner.read();
1300
1467
  optionValue = jsonNode.getValue();
1301
1468
  if (typeof optionValue !== "string") {
1302
- this.#onRequiresValue(optionDefinition, jsonNode, isListItem);
1469
+ this.#onRequiresValue(optionDefinition.name, optionDefinition.brand, jsonNode, isListItem);
1303
1470
  break;
1304
1471
  }
1305
1472
  const rootPath = Path.dirname(this.#sourceFile.fileName);
1306
1473
  optionValue = Options.resolve(optionDefinition.name, optionValue, rootPath);
1307
- await Options.validate(optionDefinition.name, optionValue, optionDefinition.brand, this.#onDiagnostics, jsonNode.origin);
1474
+ await Options.validate(optionDefinition.name, optionValue, this.#onDiagnostics, jsonNode.origin);
1308
1475
  break;
1309
1476
  }
1310
- case OptionBrand.List: {
1477
+ case "range": {
1478
+ optionValue = [];
1479
+ jsonNode = this.#jsonScanner.read();
1480
+ const ranges = jsonNode.getValue();
1481
+ if (typeof ranges !== "string") {
1482
+ this.#onRequiresValue(optionDefinition.name, "string", jsonNode, isListItem);
1483
+ break;
1484
+ }
1485
+ for (const range of Target.split(ranges)) {
1486
+ await Options.validate(optionDefinition.name, range, this.#onDiagnostics, jsonNode.origin);
1487
+ const versions = await Target.expand(range, this.#onDiagnostics, jsonNode.origin);
1488
+ optionValue.push(...versions);
1489
+ }
1490
+ break;
1491
+ }
1492
+ case "list": {
1311
1493
  optionValue = [];
1312
1494
  const leftBracketToken = this.#jsonScanner.readToken("[");
1313
1495
  if (!leftBracketToken.text) {
1314
1496
  jsonNode = this.#jsonScanner.read();
1315
- this.#onRequiresValue(optionDefinition, jsonNode, isListItem);
1497
+ this.#onRequiresValue(optionDefinition.name, optionDefinition.brand, jsonNode, isListItem);
1316
1498
  break;
1317
1499
  }
1318
1500
  while (!this.#jsonScanner.isRead()) {
@@ -1410,152 +1592,19 @@ class ConfigParser {
1410
1592
  }
1411
1593
 
1412
1594
  const defaultOptions = {
1413
- checkSourceFiles: true,
1414
- checkSuppressedErrors: false,
1595
+ checkDeclarationFiles: true,
1596
+ checkSuppressedErrors: true,
1415
1597
  failFast: false,
1416
- fixtureFileMatch: ["**/__fixtures__/*.{ts,tsx}", "**/fixtures/*.{ts,tsx}"],
1417
- plugins: [],
1418
- rejectAnyType: true,
1419
- rejectNeverType: true,
1420
- reporters: ["list", "summary"],
1421
- rootPath: Path.resolve("./"),
1422
- target: environmentOptions.typescriptModule != null ? ["current"] : ["latest"],
1423
- testFileMatch: ["**/*.tst.*", "**/__typetests__/*.test.*", "**/typetests/*.test.*"],
1424
- tsconfig: "findup",
1425
- };
1426
-
1427
- class JsonNode {
1428
- origin;
1429
- text;
1430
- constructor(text, origin) {
1431
- this.origin = origin;
1432
- this.text = text;
1433
- }
1434
- getValue(options) {
1435
- if (this.text == null) {
1436
- return undefined;
1437
- }
1438
- if (/^['"]/.test(this.text)) {
1439
- return this.text.slice(1, -1);
1440
- }
1441
- if (options?.expectsIdentifier) {
1442
- return this.text;
1443
- }
1444
- if (this.text === "true") {
1445
- return true;
1446
- }
1447
- if (this.text === "false") {
1448
- return false;
1449
- }
1450
- if (/^\d/.test(this.text)) {
1451
- return Number.parseFloat(this.text);
1452
- }
1453
- return undefined;
1454
- }
1455
- }
1456
-
1457
- class JsonScanner {
1458
- #end;
1459
- #position;
1460
- #previousPosition;
1461
- #sourceFile;
1462
- constructor(sourceFile, options) {
1463
- this.#end = options?.end ?? sourceFile.text.length;
1464
- this.#position = options?.start ?? 0;
1465
- this.#previousPosition = options?.start ?? 0;
1466
- this.#sourceFile = sourceFile;
1467
- }
1468
- #getOrigin() {
1469
- return new DiagnosticOrigin(this.#previousPosition, this.#position, this.#sourceFile);
1470
- }
1471
- isRead() {
1472
- return !(this.#position < this.#end);
1473
- }
1474
- #peekCharacter() {
1475
- return this.#sourceFile.text.charAt(this.#position);
1476
- }
1477
- #peekNextCharacter() {
1478
- return this.#sourceFile.text.charAt(this.#position + 1);
1479
- }
1480
- peekToken(token) {
1481
- this.#skipTrivia();
1482
- return this.#peekCharacter() === token;
1483
- }
1484
- read() {
1485
- this.#skipTrivia();
1486
- this.#previousPosition = this.#position;
1487
- if (/[\s,:\]}]/.test(this.#peekCharacter())) {
1488
- return new JsonNode(undefined, this.#getOrigin());
1489
- }
1490
- let text = "";
1491
- let closingTokenText = "";
1492
- if (/[[{'"]/.test(this.#peekCharacter())) {
1493
- text += this.#readCharacter();
1494
- switch (text) {
1495
- case "[":
1496
- closingTokenText = "]";
1497
- break;
1498
- case "{":
1499
- closingTokenText = "}";
1500
- break;
1501
- default:
1502
- closingTokenText = text;
1503
- }
1504
- }
1505
- while (!this.isRead()) {
1506
- text += this.#readCharacter();
1507
- if (text.slice(-1) === closingTokenText || (!closingTokenText && /[\s,:\]}]/.test(this.#peekCharacter()))) {
1508
- break;
1509
- }
1510
- }
1511
- return new JsonNode(text, this.#getOrigin());
1512
- }
1513
- #readCharacter() {
1514
- return this.#sourceFile.text.charAt(this.#position++);
1515
- }
1516
- readToken(token) {
1517
- this.#skipTrivia();
1518
- this.#previousPosition = this.#position;
1519
- const character = this.#peekCharacter();
1520
- if (typeof token === "string" ? token === character : token.test(character)) {
1521
- this.#position++;
1522
- return new JsonNode(character, this.#getOrigin());
1523
- }
1524
- return new JsonNode(undefined, this.#getOrigin());
1525
- }
1526
- #skipTrivia() {
1527
- while (!this.isRead()) {
1528
- if (/\s/.test(this.#peekCharacter())) {
1529
- this.#position++;
1530
- continue;
1531
- }
1532
- if (this.#peekCharacter() === "/") {
1533
- if (this.#peekNextCharacter() === "/") {
1534
- this.#position += 2;
1535
- while (!this.isRead()) {
1536
- if (this.#readCharacter() === "\n") {
1537
- break;
1538
- }
1539
- }
1540
- continue;
1541
- }
1542
- if (this.#peekNextCharacter() === "*") {
1543
- this.#position += 2;
1544
- while (!this.isRead()) {
1545
- if (this.#peekCharacter() === "*" && this.#peekNextCharacter() === "/") {
1546
- this.#position += 2;
1547
- break;
1548
- }
1549
- this.#position++;
1550
- }
1551
- continue;
1552
- }
1553
- }
1554
- break;
1555
- }
1556
- this.#previousPosition = this.#position;
1557
- }
1558
- }
1598
+ fixtureFileMatch: ["**/__fixtures__/*.{ts,tsx}", "**/fixtures/*.{ts,tsx}"],
1599
+ plugins: [],
1600
+ rejectAnyType: true,
1601
+ rejectNeverType: true,
1602
+ reporters: ["list", "summary"],
1603
+ rootPath: Path.resolve("./"),
1604
+ target: ["*"],
1605
+ testFileMatch: ["**/*.tst.*", "**/__typetests__/*.test.*", "**/typetests/*.test.*"],
1606
+ tsconfig: "findup",
1607
+ };
1559
1608
 
1560
1609
  class Config {
1561
1610
  static #onDiagnostics(diagnostic) {
@@ -1564,11 +1613,8 @@ class Config {
1564
1613
  static async parseCommandLine(commandLine) {
1565
1614
  const commandLineOptions = {};
1566
1615
  const pathMatch = [];
1567
- const commandLineParser = new CommandLineParser(commandLineOptions, pathMatch, Config.#onDiagnostics);
1616
+ const commandLineParser = new CommandParser(commandLineOptions, pathMatch, Config.#onDiagnostics);
1568
1617
  await commandLineParser.parse(commandLine);
1569
- if (commandLineOptions.target != null) {
1570
- commandLineOptions.target = await Target.expand(commandLineOptions.target);
1571
- }
1572
1618
  return { commandLineOptions, pathMatch };
1573
1619
  }
1574
1620
  static async parseConfigFile(filePath) {
@@ -1580,12 +1626,9 @@ class Config {
1580
1626
  const configFileText = await fs.readFile(configFilePath, {
1581
1627
  encoding: "utf8",
1582
1628
  });
1583
- const sourceFile = new SourceFile(configFilePath, configFileText);
1584
- const configFileParser = new ConfigParser(configFileOptions, OptionGroup.ConfigFile, sourceFile, new JsonScanner(sourceFile), Config.#onDiagnostics);
1629
+ const sourceFile = new JsonSourceFile(configFilePath, configFileText);
1630
+ const configFileParser = new ConfigParser(configFileOptions, 4, sourceFile, new JsonScanner(sourceFile), Config.#onDiagnostics);
1585
1631
  await configFileParser.parse();
1586
- if (configFileOptions.target != null) {
1587
- configFileOptions.target = await Target.expand(configFileOptions.target);
1588
- }
1589
1632
  }
1590
1633
  return { configFileOptions, configFilePath };
1591
1634
  }
@@ -1621,12 +1664,30 @@ class DirectiveDiagnosticText {
1621
1664
 
1622
1665
  class Directive {
1623
1666
  static #directiveRegex = /^(\/\/ *@tstyche)( *|-)?(\S*)?( *)?(.*)?/i;
1624
- static getDirectiveRanges(compiler, sourceFile, position = 0) {
1667
+ static #rangeCache = new WeakMap();
1668
+ static getDirectiveRange(compiler, owner, directiveText) {
1669
+ const directiveRanges = Directive.getDirectiveRanges(compiler, owner.node);
1670
+ return directiveRanges?.find((range) => range.directive?.text === directiveText);
1671
+ }
1672
+ static getDirectiveRanges(compiler, node) {
1673
+ let ranges = Directive.#rangeCache.get(node);
1674
+ if (ranges != null) {
1675
+ return ranges;
1676
+ }
1677
+ let sourceFile;
1678
+ let position = 0;
1679
+ if (compiler.isSourceFile(node)) {
1680
+ sourceFile = node;
1681
+ }
1682
+ else {
1683
+ sourceFile = node.getSourceFile();
1684
+ position = node.getFullStart();
1685
+ }
1625
1686
  const comments = compiler.getLeadingCommentRanges(sourceFile.text, position);
1626
1687
  if (!comments || comments.length === 0) {
1627
1688
  return;
1628
1689
  }
1629
- const ranges = Object.assign([], { sourceFile });
1690
+ ranges = [];
1630
1691
  for (const comment of comments) {
1631
1692
  if (comment.kind !== compiler.SyntaxKind.SingleLineCommentTrivia) {
1632
1693
  continue;
@@ -1636,15 +1697,17 @@ class Directive {
1636
1697
  ranges.push(range);
1637
1698
  }
1638
1699
  }
1700
+ Directive.#rangeCache.set(node, ranges);
1639
1701
  return ranges;
1640
1702
  }
1641
1703
  static async getInlineConfig(ranges) {
1642
1704
  if (!ranges) {
1643
1705
  return;
1644
1706
  }
1707
+ ranges = Array.isArray(ranges) ? ranges : [ranges];
1645
1708
  const inlineConfig = {};
1646
1709
  for (const range of ranges) {
1647
- await Directive.#parse(inlineConfig, ranges.sourceFile, range);
1710
+ await Directive.#parse(inlineConfig, range);
1648
1711
  }
1649
1712
  return inlineConfig;
1650
1713
  }
@@ -1656,6 +1719,7 @@ class Directive {
1656
1719
  return;
1657
1720
  }
1658
1721
  const range = {
1722
+ sourceFile,
1659
1723
  namespace: { start: comment.pos, end: comment.pos + namespaceText.length, text: namespaceText },
1660
1724
  };
1661
1725
  const directiveSeparatorText = match?.[2];
@@ -1675,45 +1739,61 @@ class Directive {
1675
1739
  static #onDiagnostics(diagnostic) {
1676
1740
  EventEmitter.dispatch(["directive:error", { diagnostics: [diagnostic] }]);
1677
1741
  }
1678
- static async #parse(inlineConfig, sourceFile, ranges) {
1679
- switch (ranges.directive?.text) {
1742
+ static async #parse(inlineConfig, range) {
1743
+ switch (range.directive?.text) {
1680
1744
  case "if":
1681
1745
  {
1682
- if (!ranges.argument?.text) {
1746
+ if (!range.argument?.text) {
1683
1747
  const text = DirectiveDiagnosticText.requiresArgument();
1684
- const origin = new DiagnosticOrigin(ranges.namespace.start, ranges.directive.end, sourceFile);
1748
+ const origin = new DiagnosticOrigin(range.namespace.start, range.directive.end, range.sourceFile);
1685
1749
  Directive.#onDiagnostics(Diagnostic.error(text, origin));
1686
1750
  return;
1687
1751
  }
1688
- const value = await Directive.#parseJson(sourceFile, ranges.argument.start, ranges.argument.end);
1752
+ const value = await Directive.#parseJson(range.sourceFile, range.argument.start, range.argument.end);
1689
1753
  inlineConfig.if = value;
1690
1754
  }
1691
1755
  return;
1756
+ case "fixme":
1692
1757
  case "template":
1693
- if (ranges.argument?.text != null) {
1758
+ if (range.argument?.text != null) {
1694
1759
  const text = DirectiveDiagnosticText.doesNotTakeArgument();
1695
- const origin = new DiagnosticOrigin(ranges.argument.start, ranges.argument.end, sourceFile);
1760
+ const origin = new DiagnosticOrigin(range.argument.start, range.argument.end, range.sourceFile);
1696
1761
  Directive.#onDiagnostics(Diagnostic.error(text, origin));
1697
1762
  }
1698
- inlineConfig.template = true;
1763
+ inlineConfig[range.directive?.text] = true;
1699
1764
  return;
1700
1765
  }
1701
- const target = ranges?.directive ?? ranges.namespace;
1766
+ const target = range?.directive ?? range.namespace;
1702
1767
  const text = DirectiveDiagnosticText.isNotSupported(target.text);
1703
- const origin = new DiagnosticOrigin(target.start, target.end, sourceFile);
1768
+ const origin = new DiagnosticOrigin(target.start, target.end, range.sourceFile);
1704
1769
  Directive.#onDiagnostics(Diagnostic.error(text, origin));
1705
1770
  }
1706
1771
  static async #parseJson(sourceFile, start, end) {
1707
1772
  const inlineOptions = {};
1708
- const configParser = new ConfigParser(inlineOptions, OptionGroup.InlineConditions, sourceFile, new JsonScanner(sourceFile, { start, end }), Directive.#onDiagnostics);
1773
+ const configParser = new ConfigParser(inlineOptions, 8, sourceFile, new JsonScanner(sourceFile, { start, end }), Directive.#onDiagnostics);
1709
1774
  await configParser.parse();
1710
- if ("target" in inlineOptions) {
1711
- inlineOptions["target"] = await Target.expand(inlineOptions["target"]);
1712
- }
1713
1775
  return inlineOptions;
1714
1776
  }
1715
1777
  }
1716
1778
 
1779
+ var OptionBrand;
1780
+ (function (OptionBrand) {
1781
+ OptionBrand["String"] = "string";
1782
+ OptionBrand["SemverRange"] = "range";
1783
+ OptionBrand["Number"] = "number";
1784
+ OptionBrand["Boolean"] = "boolean";
1785
+ OptionBrand["True"] = "true";
1786
+ OptionBrand["List"] = "list";
1787
+ })(OptionBrand || (OptionBrand = {}));
1788
+
1789
+ var OptionGroup;
1790
+ (function (OptionGroup) {
1791
+ OptionGroup[OptionGroup["CommandLine"] = 2] = "CommandLine";
1792
+ OptionGroup[OptionGroup["ConfigFile"] = 4] = "ConfigFile";
1793
+ OptionGroup[OptionGroup["InlineConditions"] = 8] = "InlineConditions";
1794
+ OptionGroup[OptionGroup["ResolvedConfig"] = 6] = "ResolvedConfig";
1795
+ })(OptionGroup || (OptionGroup = {}));
1796
+
1717
1797
  class CancellationHandler {
1718
1798
  #cancellationToken;
1719
1799
  #cancellationReason;
@@ -1723,7 +1803,7 @@ class CancellationHandler {
1723
1803
  }
1724
1804
  on([, payload]) {
1725
1805
  if ("diagnostics" in payload) {
1726
- if (payload.diagnostics.some((diagnostic) => diagnostic.category === DiagnosticCategory.Error)) {
1806
+ if (payload.diagnostics.some((diagnostic) => diagnostic.category === "error")) {
1727
1807
  this.#cancellationToken.cancel(this.#cancellationReason);
1728
1808
  }
1729
1809
  }
@@ -1737,7 +1817,7 @@ class ExitCodeHandler {
1737
1817
  return;
1738
1818
  }
1739
1819
  if ("diagnostics" in payload) {
1740
- if (payload.diagnostics.some((diagnostic) => diagnostic.category === DiagnosticCategory.Error)) {
1820
+ if (payload.diagnostics.some((diagnostic) => diagnostic.category === "error")) {
1741
1821
  this.#setCode(1);
1742
1822
  }
1743
1823
  }
@@ -1750,49 +1830,65 @@ class ExitCodeHandler {
1750
1830
  }
1751
1831
  }
1752
1832
 
1753
- class ResultTiming {
1754
- end = Number.NaN;
1755
- start = Number.NaN;
1756
- get duration() {
1757
- return this.end - this.start;
1758
- }
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);
1759
1853
  }
1760
1854
 
1761
1855
  class DescribeResult {
1762
1856
  describe;
1763
1857
  parent;
1764
1858
  results = [];
1765
- timing = new ResultTiming();
1859
+ timing = createResultTiming();
1766
1860
  constructor(describe, parent) {
1767
1861
  this.describe = describe;
1768
1862
  this.parent = parent;
1769
1863
  }
1770
1864
  }
1771
1865
 
1772
- var ResultStatus;
1773
- (function (ResultStatus) {
1774
- ResultStatus["Runs"] = "runs";
1775
- ResultStatus["Passed"] = "passed";
1776
- ResultStatus["Failed"] = "failed";
1777
- ResultStatus["Skipped"] = "skipped";
1778
- ResultStatus["Todo"] = "todo";
1779
- })(ResultStatus || (ResultStatus = {}));
1780
-
1781
1866
  class ExpectResult {
1782
- assertion;
1783
- diagnostics = [];
1867
+ expect;
1784
1868
  parent;
1785
- status = ResultStatus.Runs;
1786
- timing = new ResultTiming();
1787
- constructor(assertion, parent) {
1788
- this.assertion = assertion;
1869
+ status = "runs";
1870
+ timing = createResultTiming();
1871
+ constructor(expect, parent) {
1872
+ this.expect = expect;
1789
1873
  this.parent = parent;
1790
1874
  }
1791
1875
  }
1792
1876
 
1877
+ class FileResult {
1878
+ assertionCounts = createAssertionCounts();
1879
+ file;
1880
+ results = [];
1881
+ suppressedCounts = createSuppressedCounts();
1882
+ status = "runs";
1883
+ testCounts = createTestCounts();
1884
+ timing = createResultTiming();
1885
+ constructor(file) {
1886
+ this.file = file;
1887
+ }
1888
+ }
1889
+
1793
1890
  class ProjectResult {
1794
1891
  compilerVersion;
1795
- diagnostics = [];
1796
1892
  projectConfigFilePath;
1797
1893
  results = [];
1798
1894
  constructor(compilerVersion, projectConfigFilePath) {
@@ -1801,62 +1897,58 @@ class ProjectResult {
1801
1897
  }
1802
1898
  }
1803
1899
 
1804
- class ResultCount {
1805
- failed = 0;
1806
- passed = 0;
1807
- skipped = 0;
1808
- todo = 0;
1809
- get total() {
1810
- return this.failed + this.passed + this.skipped + this.todo;
1900
+ class Result {
1901
+ assertionCounts = createAssertionCounts();
1902
+ fileCounts = createFileCounts();
1903
+ files;
1904
+ results = [];
1905
+ suppressedCounts = createSuppressedCounts();
1906
+ targetCounts = createTargetCounts();
1907
+ testCounts = createTestCounts();
1908
+ timing = createResultTiming();
1909
+ constructor(files) {
1910
+ this.files = files;
1811
1911
  }
1812
1912
  }
1813
1913
 
1814
- class Result {
1815
- expectCount = new ResultCount();
1816
- fileCount = new ResultCount();
1817
- results = [];
1818
- targetCount = new ResultCount();
1819
- tasks;
1820
- testCount = new ResultCount();
1821
- timing = new ResultTiming();
1822
- constructor(tasks) {
1823
- this.tasks = tasks;
1914
+ var ResultStatus;
1915
+ (function (ResultStatus) {
1916
+ ResultStatus["Runs"] = "runs";
1917
+ ResultStatus["Passed"] = "passed";
1918
+ ResultStatus["Matched"] = "matched";
1919
+ ResultStatus["Failed"] = "failed";
1920
+ ResultStatus["Fixme"] = "fixme";
1921
+ ResultStatus["Skipped"] = "skipped";
1922
+ ResultStatus["Ignored"] = "ignored";
1923
+ ResultStatus["Todo"] = "todo";
1924
+ })(ResultStatus || (ResultStatus = {}));
1925
+
1926
+ class SuppressedResult {
1927
+ suppressed;
1928
+ constructor(suppressed) {
1929
+ this.suppressed = suppressed;
1824
1930
  }
1825
1931
  }
1826
1932
 
1827
1933
  class TargetResult {
1934
+ files;
1828
1935
  results = new Map();
1829
- status = ResultStatus.Runs;
1936
+ status = "runs";
1830
1937
  target;
1831
- tasks;
1832
- timing = new ResultTiming();
1833
- constructor(target, tasks) {
1938
+ timing = createResultTiming();
1939
+ constructor(target, files) {
1834
1940
  this.target = target;
1835
- this.tasks = tasks;
1836
- }
1837
- }
1838
-
1839
- class TaskResult {
1840
- diagnostics = [];
1841
- expectCount = new ResultCount();
1842
- results = [];
1843
- status = ResultStatus.Runs;
1844
- task;
1845
- testCount = new ResultCount();
1846
- timing = new ResultTiming();
1847
- constructor(task) {
1848
- this.task = task;
1941
+ this.files = files;
1849
1942
  }
1850
1943
  }
1851
1944
 
1852
1945
  class TestResult {
1853
- diagnostics = [];
1854
- expectCount = new ResultCount();
1946
+ assertionCounts = createAssertionCounts();
1855
1947
  parent;
1856
1948
  results = [];
1857
- status = ResultStatus.Runs;
1949
+ status = "runs";
1858
1950
  test;
1859
- timing = new ResultTiming();
1951
+ timing = createResultTiming();
1860
1952
  constructor(test, parent) {
1861
1953
  this.test = test;
1862
1954
  this.parent = parent;
@@ -1866,10 +1958,10 @@ class TestResult {
1866
1958
  class ResultHandler {
1867
1959
  #describeResult;
1868
1960
  #expectResult;
1961
+ #fileResult;
1869
1962
  #projectResult;
1870
1963
  #result;
1871
1964
  #targetResult;
1872
- #taskResult;
1873
1965
  #testResult;
1874
1966
  on([event, payload]) {
1875
1967
  switch (event) {
@@ -1887,19 +1979,19 @@ class ResultHandler {
1887
1979
  this.#targetResult.timing.start = Date.now();
1888
1980
  break;
1889
1981
  case "target:end":
1890
- if (this.#targetResult.status === ResultStatus.Failed) {
1891
- this.#result.targetCount.failed++;
1982
+ if (this.#targetResult.status === "failed") {
1983
+ this.#result.targetCounts.failed++;
1892
1984
  }
1893
1985
  else {
1894
- this.#result.targetCount.passed++;
1895
- this.#targetResult.status = ResultStatus.Passed;
1986
+ this.#result.targetCounts.passed++;
1987
+ this.#targetResult.status = "passed";
1896
1988
  }
1897
1989
  this.#targetResult.timing.end = Date.now();
1898
1990
  this.#targetResult = undefined;
1899
1991
  break;
1900
1992
  case "store:error":
1901
- if (payload.diagnostics.some(({ category }) => category === DiagnosticCategory.Error)) {
1902
- this.#targetResult.status = ResultStatus.Failed;
1993
+ if (payload.diagnostics.some(({ category }) => category === "error")) {
1994
+ this.#targetResult.status = "failed";
1903
1995
  }
1904
1996
  break;
1905
1997
  case "project:uses": {
@@ -1912,42 +2004,40 @@ class ResultHandler {
1912
2004
  break;
1913
2005
  }
1914
2006
  case "project:error":
1915
- this.#targetResult.status = ResultStatus.Failed;
1916
- this.#projectResult.diagnostics.push(...payload.diagnostics);
2007
+ this.#targetResult.status = "failed";
1917
2008
  break;
1918
- case "task:start":
2009
+ case "file:start":
1919
2010
  this.#projectResult.results.push(payload.result);
1920
- this.#taskResult = payload.result;
1921
- this.#taskResult.timing.start = Date.now();
2011
+ this.#fileResult = payload.result;
2012
+ this.#fileResult.timing.start = Date.now();
1922
2013
  break;
1923
- case "task:error":
2014
+ case "file:error":
1924
2015
  case "directive:error":
1925
2016
  case "collect:error":
1926
- this.#targetResult.status = ResultStatus.Failed;
1927
- this.#taskResult.status = ResultStatus.Failed;
1928
- this.#taskResult.diagnostics.push(...payload.diagnostics);
2017
+ this.#targetResult.status = "failed";
2018
+ this.#fileResult.status = "failed";
1929
2019
  break;
1930
- case "task:end":
1931
- if (this.#taskResult.status === ResultStatus.Failed ||
1932
- this.#taskResult.expectCount.failed > 0 ||
1933
- this.#taskResult.testCount.failed > 0) {
1934
- this.#result.fileCount.failed++;
1935
- this.#targetResult.status = ResultStatus.Failed;
1936
- this.#taskResult.status = ResultStatus.Failed;
2020
+ case "file:end":
2021
+ if (this.#fileResult.status === "failed" ||
2022
+ this.#fileResult.assertionCounts.failed > 0 ||
2023
+ this.#fileResult.testCounts.failed > 0) {
2024
+ this.#result.fileCounts.failed++;
2025
+ this.#targetResult.status = "failed";
2026
+ this.#fileResult.status = "failed";
1937
2027
  }
1938
2028
  else {
1939
- this.#result.fileCount.passed++;
1940
- this.#taskResult.status = ResultStatus.Passed;
2029
+ this.#result.fileCounts.passed++;
2030
+ this.#fileResult.status = "passed";
1941
2031
  }
1942
- this.#taskResult.timing.end = Date.now();
1943
- this.#taskResult = undefined;
2032
+ this.#fileResult.timing.end = Date.now();
2033
+ this.#fileResult = undefined;
1944
2034
  break;
1945
2035
  case "describe:start":
1946
2036
  if (this.#describeResult) {
1947
2037
  this.#describeResult.results.push(payload.result);
1948
2038
  }
1949
2039
  else {
1950
- this.#taskResult.results.push(payload.result);
2040
+ this.#fileResult.results.push(payload.result);
1951
2041
  }
1952
2042
  this.#describeResult = payload.result;
1953
2043
  this.#describeResult.timing.start = Date.now();
@@ -1961,44 +2051,44 @@ class ResultHandler {
1961
2051
  this.#describeResult.results.push(payload.result);
1962
2052
  }
1963
2053
  else {
1964
- this.#taskResult.results.push(payload.result);
2054
+ this.#fileResult.results.push(payload.result);
1965
2055
  }
1966
2056
  this.#testResult = payload.result;
1967
2057
  this.#testResult.timing.start = Date.now();
1968
2058
  break;
1969
2059
  case "test:error":
1970
- this.#result.testCount.failed++;
1971
- this.#taskResult.testCount.failed++;
1972
- this.#testResult.status = ResultStatus.Failed;
1973
- this.#testResult.diagnostics.push(...payload.diagnostics);
1974
- this.#testResult.timing.end = Date.now();
1975
- this.#testResult = undefined;
1976
- break;
1977
2060
  case "test:fail":
1978
- this.#result.testCount.failed++;
1979
- this.#taskResult.testCount.failed++;
1980
- this.#testResult.status = ResultStatus.Failed;
2061
+ this.#result.testCounts.failed++;
2062
+ this.#fileResult.testCounts.failed++;
2063
+ this.#testResult.status = "failed";
1981
2064
  this.#testResult.timing.end = Date.now();
1982
2065
  this.#testResult = undefined;
1983
2066
  break;
1984
2067
  case "test:pass":
1985
- this.#result.testCount.passed++;
1986
- this.#taskResult.testCount.passed++;
1987
- this.#testResult.status = ResultStatus.Passed;
2068
+ this.#result.testCounts.passed++;
2069
+ this.#fileResult.testCounts.passed++;
2070
+ this.#testResult.status = "passed";
1988
2071
  this.#testResult.timing.end = Date.now();
1989
2072
  this.#testResult = undefined;
1990
2073
  break;
1991
2074
  case "test:skip":
1992
- this.#result.testCount.skipped++;
1993
- this.#taskResult.testCount.skipped++;
1994
- this.#testResult.status = ResultStatus.Skipped;
2075
+ this.#result.testCounts.skipped++;
2076
+ this.#fileResult.testCounts.skipped++;
2077
+ this.#testResult.status = "skipped";
2078
+ this.#testResult.timing.end = Date.now();
2079
+ this.#testResult = undefined;
2080
+ break;
2081
+ case "test:fixme":
2082
+ this.#result.testCounts.fixme++;
2083
+ this.#fileResult.testCounts.fixme++;
2084
+ this.#testResult.status = "fixme";
1995
2085
  this.#testResult.timing.end = Date.now();
1996
2086
  this.#testResult = undefined;
1997
2087
  break;
1998
2088
  case "test:todo":
1999
- this.#result.testCount.todo++;
2000
- this.#taskResult.testCount.todo++;
2001
- this.#testResult.status = ResultStatus.Todo;
2089
+ this.#result.testCounts.todo++;
2090
+ this.#fileResult.testCounts.todo++;
2091
+ this.#testResult.status = "todo";
2002
2092
  this.#testResult.timing.end = Date.now();
2003
2093
  this.#testResult = undefined;
2004
2094
  break;
@@ -2007,52 +2097,66 @@ class ResultHandler {
2007
2097
  this.#testResult.results.push(payload.result);
2008
2098
  }
2009
2099
  else {
2010
- this.#taskResult.results.push(payload.result);
2100
+ this.#fileResult.results.push(payload.result);
2011
2101
  }
2012
2102
  this.#expectResult = payload.result;
2013
2103
  this.#expectResult.timing.start = Date.now();
2014
2104
  break;
2015
2105
  case "expect:error":
2016
- this.#result.expectCount.failed++;
2017
- this.#taskResult.expectCount.failed++;
2106
+ case "expect:fail":
2107
+ this.#result.assertionCounts.failed++;
2108
+ this.#fileResult.assertionCounts.failed++;
2018
2109
  if (this.#testResult) {
2019
- this.#testResult.expectCount.failed++;
2110
+ this.#testResult.assertionCounts.failed++;
2020
2111
  }
2021
- this.#expectResult.status = ResultStatus.Failed;
2022
- this.#expectResult.diagnostics.push(...payload.diagnostics);
2112
+ this.#expectResult.status = "failed";
2023
2113
  this.#expectResult.timing.end = Date.now();
2024
2114
  this.#expectResult = undefined;
2025
2115
  break;
2026
- case "expect:fail":
2027
- this.#result.expectCount.failed++;
2028
- this.#taskResult.expectCount.failed++;
2116
+ case "expect:pass":
2117
+ this.#result.assertionCounts.passed++;
2118
+ this.#fileResult.assertionCounts.passed++;
2029
2119
  if (this.#testResult) {
2030
- this.#testResult.expectCount.failed++;
2120
+ this.#testResult.assertionCounts.passed++;
2031
2121
  }
2032
- this.#expectResult.status = ResultStatus.Failed;
2122
+ this.#expectResult.status = "passed";
2033
2123
  this.#expectResult.timing.end = Date.now();
2034
2124
  this.#expectResult = undefined;
2035
2125
  break;
2036
- case "expect:pass":
2037
- this.#result.expectCount.passed++;
2038
- this.#taskResult.expectCount.passed++;
2126
+ case "expect:skip":
2127
+ this.#result.assertionCounts.skipped++;
2128
+ this.#fileResult.assertionCounts.skipped++;
2039
2129
  if (this.#testResult) {
2040
- this.#testResult.expectCount.passed++;
2130
+ this.#testResult.assertionCounts.skipped++;
2041
2131
  }
2042
- this.#expectResult.status = ResultStatus.Passed;
2132
+ this.#expectResult.status = "skipped";
2043
2133
  this.#expectResult.timing.end = Date.now();
2044
2134
  this.#expectResult = undefined;
2045
2135
  break;
2046
- case "expect:skip":
2047
- this.#result.expectCount.skipped++;
2048
- this.#taskResult.expectCount.skipped++;
2136
+ case "expect:fixme":
2137
+ this.#result.assertionCounts.fixme++;
2138
+ this.#fileResult.assertionCounts.fixme++;
2049
2139
  if (this.#testResult) {
2050
- this.#testResult.expectCount.skipped++;
2140
+ this.#testResult.assertionCounts.fixme++;
2051
2141
  }
2052
- this.#expectResult.status = ResultStatus.Skipped;
2142
+ this.#expectResult.status = "fixme";
2053
2143
  this.#expectResult.timing.end = Date.now();
2054
2144
  this.#expectResult = undefined;
2055
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;
2056
2160
  }
2057
2161
  }
2058
2162
  }
@@ -2078,7 +2182,7 @@ function Text({ children, color, indent }) {
2078
2182
  if (color != null) {
2079
2183
  ansiEscapes.push(color);
2080
2184
  }
2081
- return (jsx("text", { indent: indent ?? 0, children: [ansiEscapes.length > 0 ? jsx("ansi", { escapes: ansiEscapes }) : undefined, children, ansiEscapes.length > 0 ? jsx("ansi", { escapes: Color.Reset }) : undefined] }));
2185
+ return (jsx("text", { indent: indent ?? 0, children: [ansiEscapes.length > 0 ? jsx("ansi", { escapes: ansiEscapes }) : undefined, children, ansiEscapes.length > 0 ? jsx("ansi", { escapes: "0" }) : undefined] }));
2082
2186
  }
2083
2187
 
2084
2188
  function Line({ children, color, indent }) {
@@ -2139,7 +2243,7 @@ class Scribbler {
2139
2243
  }
2140
2244
 
2141
2245
  function addsPackageText(packageVersion, packagePath) {
2142
- return (jsx(Line, { children: [jsx(Text, { color: Color.Gray, children: "adds" }), " TypeScript ", packageVersion, jsx(Text, { color: Color.Gray, children: [" to ", packagePath] })] }));
2246
+ return (jsx(Line, { children: [jsx(Text, { color: "90", children: "adds" }), " TypeScript ", packageVersion, jsx(Text, { color: "90", children: [" to ", packagePath] })] }));
2143
2247
  }
2144
2248
 
2145
2249
  function describeNameText(name, indent = 0) {
@@ -2155,13 +2259,13 @@ function BreadcrumbsText({ ancestor }) {
2155
2259
  ancestor = ancestor.parent;
2156
2260
  }
2157
2261
  text.push("");
2158
- return jsx(Text, { color: Color.Gray, children: text.reverse().join(" ❭ ") });
2262
+ return jsx(Text, { color: "90", children: text.reverse().join(" ❭ ") });
2159
2263
  }
2160
- function CodeLineText({ gutterWidth, lineNumber, lineNumberColor = Color.Gray, lineText }) {
2161
- return (jsx(Line, { children: [jsx(Text, { color: lineNumberColor, children: lineNumber.toString().padStart(gutterWidth) }), jsx(Text, { color: Color.Gray, children: " | " }), lineText] }));
2264
+ function CodeLineText({ gutterWidth, lineNumber, lineNumberColor = "90", lineText }) {
2265
+ return (jsx(Line, { children: [jsx(Text, { color: lineNumberColor, children: lineNumber.toString().padStart(gutterWidth) }), jsx(Text, { color: "90", children: " | " }), lineText] }));
2162
2266
  }
2163
2267
  function SquiggleLineText({ gutterWidth, indentWidth = 0, squiggleColor, squiggleWidth }) {
2164
- return (jsx(Line, { children: [" ".repeat(gutterWidth), jsx(Text, { color: Color.Gray, children: " | " }), " ".repeat(indentWidth), jsx(Text, { color: squiggleColor, children: "~".repeat(squiggleWidth === 0 ? 1 : squiggleWidth) })] }));
2268
+ return (jsx(Line, { children: [" ".repeat(gutterWidth), jsx(Text, { color: "90", children: " | " }), " ".repeat(indentWidth), jsx(Text, { color: squiggleColor, children: "~".repeat(squiggleWidth === 0 ? 1 : squiggleWidth) })] }));
2165
2269
  }
2166
2270
  function CodeFrameText({ diagnosticCategory, diagnosticOrigin, options }) {
2167
2271
  const linesAbove = options?.linesAbove ?? 2;
@@ -2175,11 +2279,11 @@ function CodeFrameText({ diagnosticCategory, diagnosticOrigin, options }) {
2175
2279
  const gutterWidth = (lastLine + 1).toString().length + 2;
2176
2280
  let highlightColor;
2177
2281
  switch (diagnosticCategory) {
2178
- case DiagnosticCategory.Error:
2179
- highlightColor = Color.Red;
2282
+ case "error":
2283
+ highlightColor = "31";
2180
2284
  break;
2181
- case DiagnosticCategory.Warning:
2182
- highlightColor = Color.Yellow;
2285
+ case "warning":
2286
+ highlightColor = "33";
2183
2287
  break;
2184
2288
  }
2185
2289
  const codeFrame = [];
@@ -2207,15 +2311,15 @@ function CodeFrameText({ diagnosticCategory, diagnosticOrigin, options }) {
2207
2311
  }
2208
2312
  }
2209
2313
  let breadcrumbs;
2210
- if (showBreadcrumbs && diagnosticOrigin.assertion != null) {
2211
- breadcrumbs = jsx(BreadcrumbsText, { ancestor: diagnosticOrigin.assertion.parent });
2314
+ if (showBreadcrumbs && diagnosticOrigin.assertionNode != null) {
2315
+ breadcrumbs = jsx(BreadcrumbsText, { ancestor: diagnosticOrigin.assertionNode.parent });
2212
2316
  }
2213
- const location = (jsx(Line, { children: [" ".repeat(gutterWidth + 2), jsx(Text, { color: Color.Gray, children: " at " }), jsx(Text, { color: Color.Cyan, children: Path.relative("", diagnosticOrigin.sourceFile.fileName) }), jsx(Text, { color: Color.Gray, children: `:${firstMarkedLine + 1}:${firstMarkedLineCharacter + 1}` }), breadcrumbs] }));
2317
+ const location = (jsx(Line, { children: [" ".repeat(gutterWidth + 2), jsx(Text, { color: "90", children: " at " }), jsx(Text, { color: "36", children: Path.relative("", diagnosticOrigin.sourceFile.fileName) }), jsx(Text, { color: "90", children: `:${firstMarkedLine + 1}:${firstMarkedLineCharacter + 1}` }), breadcrumbs] }));
2214
2318
  return (jsx(Text, { children: [codeFrame, jsx(Line, {}), location] }));
2215
2319
  }
2216
2320
 
2217
2321
  function DiagnosticText({ codeFrameOptions, diagnostic }) {
2218
- const code = diagnostic.code ? jsx(Text, { color: Color.Gray, children: [" ", diagnostic.code] }) : undefined;
2322
+ const code = diagnostic.code ? jsx(Text, { color: "90", children: [" ", diagnostic.code] }) : undefined;
2219
2323
  const text = Array.isArray(diagnostic.text) ? diagnostic.text : [diagnostic.text];
2220
2324
  const message = text.map((text, index) => (jsx(Text, { children: [index === 1 ? jsx(Line, {}) : undefined, jsx(Line, { children: [text, index === 0 ? code : undefined] })] })));
2221
2325
  const related = diagnostic.related?.map((relatedDiagnostic) => jsx(DiagnosticText, { diagnostic: relatedDiagnostic }));
@@ -2225,16 +2329,63 @@ function DiagnosticText({ codeFrameOptions, diagnostic }) {
2225
2329
  function diagnosticText(diagnostic, codeFrameOptions = {}) {
2226
2330
  let prefix;
2227
2331
  switch (diagnostic.category) {
2228
- case DiagnosticCategory.Error:
2229
- prefix = jsx(Text, { color: Color.Red, children: "Error: " });
2332
+ case "error":
2333
+ prefix = jsx(Text, { color: "31", children: "Error: " });
2230
2334
  break;
2231
- case DiagnosticCategory.Warning:
2232
- prefix = jsx(Text, { color: Color.Yellow, children: "Warning: " });
2335
+ case "warning":
2336
+ prefix = jsx(Text, { color: "33", children: "Warning: " });
2233
2337
  break;
2234
2338
  }
2235
2339
  return (jsx(Text, { children: [prefix, jsx(DiagnosticText, { codeFrameOptions: codeFrameOptions, diagnostic: diagnostic })] }));
2236
2340
  }
2237
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
+
2366
+ function FileNameText({ filePath }) {
2367
+ const relativePath = Path.relative("", filePath);
2368
+ const lastPathSeparator = relativePath.lastIndexOf("/");
2369
+ const directoryNameText = relativePath.slice(0, lastPathSeparator + 1);
2370
+ const fileNameText = relativePath.slice(lastPathSeparator + 1);
2371
+ return (jsx(Text, { children: [jsx(Text, { color: "90", children: directoryNameText }), fileNameText] }));
2372
+ }
2373
+ function fileStatusText(status, file) {
2374
+ let statusText;
2375
+ switch (status) {
2376
+ case "runs":
2377
+ statusText = "runs";
2378
+ break;
2379
+ case "passed":
2380
+ statusText = "pass";
2381
+ break;
2382
+ case "failed":
2383
+ statusText = "fail";
2384
+ break;
2385
+ }
2386
+ return (jsx(Line, { children: [jsx(Text, { color: getStatusColor(status), children: statusText }), " ", jsx(FileNameText, { filePath: file.path })] }));
2387
+ }
2388
+
2238
2389
  function fileViewText(lines, addEmptyFinalLine) {
2239
2390
  return (jsx(Text, { children: [[...lines], addEmptyFinalLine ? jsx(Line, {}) : undefined] }));
2240
2391
  }
@@ -2258,13 +2409,13 @@ function formattedText(input) {
2258
2409
  }
2259
2410
 
2260
2411
  function HintText({ children }) {
2261
- return (jsx(Text, { indent: 1, color: Color.Gray, children: children }));
2412
+ return (jsx(Text, { indent: 1, color: "90", children: children }));
2262
2413
  }
2263
2414
  function HelpHeaderText({ tstycheVersion }) {
2264
2415
  return (jsx(Line, { children: ["The TSTyche Type Test Runner", jsx(HintText, { children: tstycheVersion })] }));
2265
2416
  }
2266
2417
  function CommandText({ hint, text }) {
2267
- return (jsx(Line, { indent: 1, children: [jsx(Text, { color: Color.Blue, children: text }), hint && jsx(HintText, { children: hint })] }));
2418
+ return (jsx(Line, { indent: 1, children: [jsx(Text, { color: "34", children: text }), hint && jsx(HintText, { children: hint })] }));
2268
2419
  }
2269
2420
  function OptionDescriptionText({ text }) {
2270
2421
  return jsx(Line, { indent: 1, children: text });
@@ -2272,8 +2423,8 @@ function OptionDescriptionText({ text }) {
2272
2423
  function CommandLineUsageText() {
2273
2424
  const usage = [
2274
2425
  ["tstyche", "Run all tests."],
2275
- ["tstyche path/to/first.test.ts", "Only run the test files with matching path."],
2276
- ["tstyche --target 5.3,5.6.2,current", "Test on all specified versions of TypeScript."],
2426
+ ["tstyche query-params", "Only run the matching test file."],
2427
+ ["tstyche --target '5.3 || 5.5.2 || >=5.7'", "Test against specific versions of TypeScript."],
2277
2428
  ];
2278
2429
  const usageText = usage.map(([commandText, descriptionText]) => (jsx(Line, { children: [jsx(CommandText, { text: commandText }), jsx(OptionDescriptionText, { text: descriptionText })] })));
2279
2430
  return jsx(Text, { children: usageText });
@@ -2282,16 +2433,19 @@ function CommandLineOptionNameText({ text }) {
2282
2433
  return jsx(Text, { children: `--${text}` });
2283
2434
  }
2284
2435
  function CommandLineOptionHintText({ definition }) {
2285
- if (definition.brand === OptionBrand.List) {
2436
+ if (definition.brand === "list") {
2286
2437
  return jsx(Text, { children: `${definition.brand} of ${definition.items.brand}s` });
2287
2438
  }
2439
+ if (definition.brand === "range") {
2440
+ return jsx(Text, { children: "string" });
2441
+ }
2288
2442
  return jsx(Text, { children: definition.brand });
2289
2443
  }
2290
2444
  function CommandLineOptionsText({ optionDefinitions }) {
2291
2445
  const definitions = [...optionDefinitions.values()];
2292
2446
  const optionsText = definitions.map((definition) => {
2293
2447
  let hint;
2294
- if (definition.brand !== OptionBrand.BareTrue) {
2448
+ if (definition.brand !== "true") {
2295
2449
  hint = jsx(CommandLineOptionHintText, { definition: definition });
2296
2450
  }
2297
2451
  return (jsx(Text, { children: [jsx(CommandText, { text: jsx(CommandLineOptionNameText, { text: definition.name }), hint: hint }), jsx(OptionDescriptionText, { text: definition.description }), jsx(Line, {})] }));
@@ -2340,69 +2494,60 @@ class OutputService {
2340
2494
  function RowText({ label, text }) {
2341
2495
  return (jsx(Line, { children: [`${label}:`.padEnd(12), text] }));
2342
2496
  }
2343
- function CountText({ failed, passed, skipped, todo, total }) {
2344
- return (jsx(Text, { children: [failed > 0 ? (jsx(Text, { children: [jsx(Text, { color: Color.Red, children: [failed, " failed"] }), jsx(Text, { children: ", " })] })) : undefined, skipped > 0 ? (jsx(Text, { children: [jsx(Text, { color: Color.Yellow, children: [skipped, " skipped"] }), jsx(Text, { children: ", " })] })) : undefined, todo > 0 ? (jsx(Text, { children: [jsx(Text, { color: Color.Magenta, children: [todo, " todo"] }), jsx(Text, { children: ", " })] })) : undefined, passed > 0 ? (jsx(Text, { children: [jsx(Text, { color: Color.Green, 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] }));
2345
2503
  }
2346
- function DurationText({ seconds }) {
2504
+ function DurationText({ timing }) {
2505
+ const seconds = duration(timing) / 1000;
2347
2506
  return jsx(Text, { children: `${Math.round(seconds * 10) / 10}s` });
2348
2507
  }
2349
- function summaryText({ duration, expectCount, fileCount, targetCount, testCount, }) {
2350
- const targetCountText = (jsx(RowText, { label: "Targets", text: jsx(CountText, { failed: targetCount.failed, passed: targetCount.passed, skipped: targetCount.skipped, todo: targetCount.todo, total: targetCount.total }) }));
2351
- const fileCountText = (jsx(RowText, { label: "Test files", text: jsx(CountText, { failed: fileCount.failed, passed: fileCount.passed, skipped: fileCount.skipped, todo: fileCount.todo, total: fileCount.total }) }));
2352
- const testCountText = (jsx(RowText, { label: "Tests", text: jsx(CountText, { failed: testCount.failed, passed: testCount.passed, skipped: testCount.skipped, todo: testCount.todo, total: testCount.total }) }));
2353
- const assertionCountText = (jsx(RowText, { label: "Assertions", text: jsx(CountText, { failed: expectCount.failed, passed: expectCount.passed, skipped: expectCount.skipped, todo: expectCount.todo, total: expectCount.total }) }));
2354
- 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] }));
2355
2521
  }
2356
2522
 
2357
- function FileNameText({ filePath }) {
2358
- const relativePath = Path.relative("", filePath);
2359
- const lastPathSeparator = relativePath.lastIndexOf("/");
2360
- const directoryNameText = relativePath.slice(0, lastPathSeparator + 1);
2361
- const fileNameText = relativePath.slice(lastPathSeparator + 1);
2362
- return (jsx(Text, { children: [jsx(Text, { color: Color.Gray, children: directoryNameText }), fileNameText] }));
2363
- }
2364
- function taskStatusText(status, task) {
2365
- let statusColor;
2523
+ function testNameText(status, name, indent = 0) {
2366
2524
  let statusText;
2367
2525
  switch (status) {
2368
- case ResultStatus.Runs:
2369
- statusColor = Color.Yellow;
2370
- statusText = "runs";
2526
+ case "passed":
2527
+ statusText = "+";
2371
2528
  break;
2372
- case ResultStatus.Passed:
2373
- statusColor = Color.Green;
2374
- statusText = "pass";
2529
+ case "failed":
2530
+ statusText = "×";
2375
2531
  break;
2376
- case ResultStatus.Failed:
2377
- statusColor = Color.Red;
2378
- statusText = "fail";
2532
+ case "skipped":
2533
+ statusText = "- skip";
2534
+ break;
2535
+ case "fixme":
2536
+ statusText = "- fixme";
2379
2537
  break;
2380
- }
2381
- return (jsx(Line, { children: [jsx(Text, { color: statusColor, children: statusText }), " ", jsx(FileNameText, { filePath: task.filePath })] }));
2382
- }
2383
-
2384
- function StatusText({ status }) {
2385
- switch (status) {
2386
- case "fail":
2387
- return jsx(Text, { color: Color.Red, children: "\u00D7" });
2388
- case "pass":
2389
- return jsx(Text, { color: Color.Green, children: "+" });
2390
- case "skip":
2391
- return jsx(Text, { color: Color.Yellow, children: "- skip" });
2392
2538
  case "todo":
2393
- return jsx(Text, { color: Color.Magenta, children: "- todo" });
2539
+ statusText = "- todo";
2540
+ break;
2394
2541
  }
2395
- }
2396
- function testNameText(status, name, indent = 0) {
2397
- return (jsx(Line, { indent: indent + 1, children: [jsx(StatusText, { status: status }), " ", jsx(Text, { color: Color.Gray, children: name })] }));
2542
+ return (jsx(Line, { indent: indent + 1, children: [jsx(Text, { color: getStatusColor(status), children: statusText }), " ", jsx(Text, { color: "90", children: name })] }));
2398
2543
  }
2399
2544
 
2400
2545
  function usesCompilerText(compilerVersion, projectConfigFilePath, options) {
2401
2546
  let projectConfigPathText;
2402
2547
  if (projectConfigFilePath != null) {
2403
- projectConfigPathText = (jsx(Text, { color: Color.Gray, children: [" with ", Path.relative("", projectConfigFilePath)] }));
2548
+ projectConfigPathText = (jsx(Text, { color: "90", children: [" with ", Path.relative("", projectConfigFilePath)] }));
2404
2549
  }
2405
- return (jsx(Text, { children: [options?.prependEmptyLine ? jsx(Line, {}) : undefined, jsx(Line, { children: [jsx(Text, { color: Color.Blue, children: "uses" }), " TypeScript ", compilerVersion, projectConfigPathText] }), jsx(Line, {})] }));
2550
+ return (jsx(Text, { children: [options?.prependEmptyLine ? jsx(Line, {}) : undefined, jsx(Line, { children: [jsx(Text, { color: "34", children: "uses" }), " TypeScript ", compilerVersion, projectConfigPathText] }), jsx(Line, {})] }));
2406
2551
  }
2407
2552
 
2408
2553
  function waitingForFileChangesText() {
@@ -2415,7 +2560,7 @@ function watchUsageText() {
2415
2560
  ["x", "to exit."],
2416
2561
  ];
2417
2562
  const usageText = usage.map(([keyText, actionText]) => {
2418
- return (jsx(Line, { children: [jsx(Text, { color: Color.Gray, children: "Press" }), jsx(Text, { children: ` ${keyText} ` }), jsx(Text, { color: Color.Gray, children: actionText })] }));
2563
+ return (jsx(Line, { children: [jsx(Text, { color: "90", children: "Press" }), jsx(Text, { children: ` ${keyText} ` }), jsx(Text, { color: "90", children: actionText })] }));
2419
2564
  });
2420
2565
  return jsx(Text, { children: usageText });
2421
2566
  }
@@ -2492,7 +2637,7 @@ class ListReporter extends BaseReporter {
2492
2637
  on([event, payload]) {
2493
2638
  switch (event) {
2494
2639
  case "run:start":
2495
- this.#isFileViewExpanded = payload.result.tasks.length === 1 && this.resolvedConfig.watch !== true;
2640
+ this.#isFileViewExpanded = payload.result.files.length === 1 && this.resolvedConfig.watch !== true;
2496
2641
  break;
2497
2642
  case "store:adds":
2498
2643
  OutputService.writeMessage(addsPackageText(payload.packageVersion, payload.packagePath));
@@ -2504,7 +2649,7 @@ class ListReporter extends BaseReporter {
2504
2649
  }
2505
2650
  break;
2506
2651
  case "target:start":
2507
- this.#fileCount = payload.result.tasks.length;
2652
+ this.#fileCount = payload.result.files.length;
2508
2653
  this.#hasReportedUses = false;
2509
2654
  break;
2510
2655
  case "project:uses":
@@ -2519,25 +2664,26 @@ class ListReporter extends BaseReporter {
2519
2664
  OutputService.writeError(diagnosticText(diagnostic));
2520
2665
  }
2521
2666
  break;
2522
- case "task:start":
2667
+ case "file:start":
2523
2668
  if (!environmentOptions.noInteractive) {
2524
- OutputService.writeMessage(taskStatusText(payload.result.status, payload.result.task));
2669
+ OutputService.writeMessage(fileStatusText(payload.result.status, payload.result.file));
2525
2670
  }
2526
2671
  this.#fileCount--;
2527
2672
  this.#hasReportedError = false;
2528
2673
  break;
2529
- case "task:error":
2674
+ case "file:error":
2530
2675
  case "directive:error":
2531
2676
  case "collect:error":
2677
+ case "suppressed:error":
2532
2678
  for (const diagnostic of payload.diagnostics) {
2533
2679
  this.#fileView.addMessage(diagnosticText(diagnostic));
2534
2680
  }
2535
2681
  break;
2536
- case "task:end":
2682
+ case "file:end":
2537
2683
  if (!environmentOptions.noInteractive) {
2538
2684
  OutputService.eraseLastLine();
2539
2685
  }
2540
- OutputService.writeMessage(taskStatusText(payload.result.status, payload.result.task));
2686
+ OutputService.writeMessage(fileStatusText(payload.result.status, payload.result.file));
2541
2687
  OutputService.writeMessage(this.#fileView.getViewText({ appendEmptyLine: this.#isLastFile }));
2542
2688
  if (this.#fileView.hasErrors) {
2543
2689
  OutputService.writeError(this.#fileView.getMessages());
@@ -2557,7 +2703,12 @@ class ListReporter extends BaseReporter {
2557
2703
  break;
2558
2704
  case "test:skip":
2559
2705
  if (this.#isFileViewExpanded) {
2560
- this.#fileView.addTest("skip", payload.result.test.name);
2706
+ this.#fileView.addTest("skipped", payload.result.test.name);
2707
+ }
2708
+ break;
2709
+ case "test:fixme":
2710
+ if (this.#isFileViewExpanded) {
2711
+ this.#fileView.addTest("fixme", payload.result.test.name);
2561
2712
  }
2562
2713
  break;
2563
2714
  case "test:todo":
@@ -2567,7 +2718,7 @@ class ListReporter extends BaseReporter {
2567
2718
  break;
2568
2719
  case "test:error":
2569
2720
  if (this.#isFileViewExpanded) {
2570
- this.#fileView.addTest("fail", payload.result.test.name);
2721
+ this.#fileView.addTest("failed", payload.result.test.name);
2571
2722
  }
2572
2723
  for (const diagnostic of payload.diagnostics) {
2573
2724
  this.#fileView.addMessage(diagnosticText(diagnostic));
@@ -2575,12 +2726,12 @@ class ListReporter extends BaseReporter {
2575
2726
  break;
2576
2727
  case "test:fail":
2577
2728
  if (this.#isFileViewExpanded) {
2578
- this.#fileView.addTest("fail", payload.result.test.name);
2729
+ this.#fileView.addTest("failed", payload.result.test.name);
2579
2730
  }
2580
2731
  break;
2581
2732
  case "test:pass":
2582
2733
  if (this.#isFileViewExpanded) {
2583
- this.#fileView.addTest("pass", payload.result.test.name);
2734
+ this.#fileView.addTest("passed", payload.result.test.name);
2584
2735
  }
2585
2736
  break;
2586
2737
  case "expect:error":
@@ -2602,10 +2753,10 @@ class SetupReporter {
2602
2753
  if ("diagnostics" in payload) {
2603
2754
  for (const diagnostic of payload.diagnostics) {
2604
2755
  switch (diagnostic.category) {
2605
- case DiagnosticCategory.Error:
2756
+ case "error":
2606
2757
  OutputService.writeError(diagnosticText(diagnostic));
2607
2758
  break;
2608
- case DiagnosticCategory.Warning:
2759
+ case "warning":
2609
2760
  OutputService.writeWarning(diagnosticText(diagnostic));
2610
2761
  break;
2611
2762
  }
@@ -2621,11 +2772,12 @@ class SummaryReporter extends BaseReporter {
2621
2772
  }
2622
2773
  if (event === "run:end") {
2623
2774
  OutputService.writeMessage(summaryText({
2624
- duration: payload.result.timing.duration,
2625
- expectCount: payload.result.expectCount,
2626
- fileCount: payload.result.fileCount,
2627
- targetCount: payload.result.targetCount,
2628
- 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,
2629
2781
  }));
2630
2782
  }
2631
2783
  }
@@ -2651,18 +2803,18 @@ class WatchReporter extends BaseReporter {
2651
2803
  }
2652
2804
  }
2653
2805
 
2654
- class Task {
2655
- filePath;
2806
+ class FileLocation {
2807
+ path;
2656
2808
  position;
2657
- constructor(filePath, position) {
2658
- this.filePath = Path.resolve(this.#toPath(filePath));
2809
+ constructor(file, position) {
2810
+ this.path = Path.resolve(this.#toPath(file));
2659
2811
  this.position = position;
2660
2812
  }
2661
- #toPath(filePath) {
2662
- if (typeof filePath === "string" && !filePath.startsWith("file:")) {
2663
- return filePath;
2813
+ #toPath(file) {
2814
+ if (typeof file === "string" && !file.startsWith("file:")) {
2815
+ return file;
2664
2816
  }
2665
- return fileURLToPath(filePath);
2817
+ return fileURLToPath(file);
2666
2818
  }
2667
2819
  }
2668
2820
 
@@ -3000,9 +3152,9 @@ class WatchService {
3000
3152
  #resolvedConfig;
3001
3153
  #watchedTestFiles;
3002
3154
  #watchers = [];
3003
- constructor(resolvedConfig, tasks) {
3155
+ constructor(resolvedConfig, files) {
3004
3156
  this.#resolvedConfig = resolvedConfig;
3005
- this.#watchedTestFiles = new Map(tasks.map((task) => [task.filePath, task]));
3157
+ this.#watchedTestFiles = new Map(files.map((file) => [file.path, file]));
3006
3158
  }
3007
3159
  #onDiagnostics(diagnostic) {
3008
3160
  EventEmitter.dispatch(["watch:error", { diagnostics: [diagnostic] }]);
@@ -3031,7 +3183,7 @@ class WatchService {
3031
3183
  case "\u001B":
3032
3184
  case "q":
3033
3185
  case "x":
3034
- onClose(CancellationReason.WatchClose);
3186
+ onClose("watchClose");
3035
3187
  break;
3036
3188
  case "\u000D":
3037
3189
  case "\u0020":
@@ -3047,14 +3199,14 @@ class WatchService {
3047
3199
  }
3048
3200
  const onChangedFile = (filePath) => {
3049
3201
  debounce.refresh();
3050
- let task = this.#watchedTestFiles.get(filePath);
3051
- if (task != null) {
3052
- this.#changedTestFiles.set(filePath, task);
3202
+ let file = this.#watchedTestFiles.get(filePath);
3203
+ if (file != null) {
3204
+ this.#changedTestFiles.set(filePath, file);
3053
3205
  }
3054
3206
  else if (Select.isTestFile(filePath, this.#resolvedConfig)) {
3055
- task = new Task(filePath);
3056
- this.#changedTestFiles.set(filePath, task);
3057
- this.#watchedTestFiles.set(filePath, task);
3207
+ file = new FileLocation(filePath);
3208
+ this.#changedTestFiles.set(filePath, file);
3209
+ this.#watchedTestFiles.set(filePath, file);
3058
3210
  }
3059
3211
  };
3060
3212
  const onRemovedFile = (filePath) => {
@@ -3067,7 +3219,7 @@ class WatchService {
3067
3219
  };
3068
3220
  this.#watchers.push(new Watcher(this.#resolvedConfig.rootPath, onChangedFile, onRemovedFile, { recursive: true }));
3069
3221
  const onChangedConfigFile = () => {
3070
- onClose(CancellationReason.ConfigChange);
3222
+ onClose("configChange");
3071
3223
  };
3072
3224
  this.#watchers.push(new FileWatcher(this.#resolvedConfig.configFilePath, onChangedConfigFile));
3073
3225
  for (const watcher of this.#watchers) {
@@ -3082,67 +3234,28 @@ class WatchService {
3082
3234
  }
3083
3235
  }
3084
3236
 
3085
- class TestTreeNode {
3086
- brand;
3087
- children = [];
3088
- diagnostics = new Set();
3089
- flags;
3090
- name = "";
3091
- node;
3092
- parent;
3093
- constructor(compiler, brand, node, parent, flags) {
3094
- this.brand = brand;
3095
- this.node = node;
3096
- this.parent = parent;
3097
- this.flags = flags;
3098
- if (node.arguments[0] != null && compiler.isStringLiteralLike(node.arguments[0])) {
3099
- this.name = node.arguments[0].text;
3100
- }
3101
- if (node.arguments[1] != null && compiler.isFunctionLike(node.arguments[1])) {
3102
- for (const diagnostic of parent.diagnostics) {
3103
- if (diagnosticBelongsToNode(diagnostic, node.arguments[1].body)) {
3104
- this.diagnostics.add(diagnostic);
3105
- parent.diagnostics.delete(diagnostic);
3106
- }
3107
- }
3108
- }
3109
- }
3110
- getDirectiveRanges(compiler) {
3111
- return Directive.getDirectiveRanges(compiler, this.node.getSourceFile(), this.node.getFullStart());
3237
+ function compareDiagnostics(a, b) {
3238
+ if (a.file?.fileName !== b.file?.fileName) {
3239
+ return false;
3112
3240
  }
3241
+ return deepCompareKeys(a, b, ["start", "length", "code", "messageText"]);
3113
3242
  }
3114
-
3115
- class AssertionNode extends TestTreeNode {
3116
- abilityDiagnostics = new Set();
3117
- isNot;
3118
- matcherNode;
3119
- matcherNameNode;
3120
- modifierNode;
3121
- notNode;
3122
- source;
3123
- target;
3124
- constructor(compiler, brand, node, parent, flags, matcherNode, matcherNameNode, modifierNode, notNode) {
3125
- super(compiler, brand, node, parent, flags);
3126
- this.isNot = notNode != null;
3127
- this.matcherNode = matcherNode;
3128
- this.matcherNameNode = matcherNameNode;
3129
- this.modifierNode = modifierNode;
3130
- this.source = this.node.typeArguments ?? this.node.arguments;
3131
- if (compiler.isCallExpression(this.matcherNode)) {
3132
- this.target = this.matcherNode.typeArguments ?? this.matcherNode.arguments;
3133
- }
3134
- for (const diagnostic of parent.diagnostics) {
3135
- if (diagnosticBelongsToNode(diagnostic, this.source) ||
3136
- (this.target != null && diagnosticBelongsToNode(diagnostic, this.target))) {
3137
- this.diagnostics.add(diagnostic);
3138
- parent.diagnostics.delete(diagnostic);
3139
- }
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;
3140
3256
  }
3141
3257
  }
3142
- }
3143
-
3144
- function nodeBelongsToArgumentList(compiler, node) {
3145
- return compiler.isCallExpression(node.parent) && node.parent.arguments.some((argument) => argument === node);
3258
+ return true;
3146
3259
  }
3147
3260
  function nodeIsChildOfExpressionStatement(compiler, node) {
3148
3261
  return compiler.isExpressionStatement(node.parent);
@@ -3150,59 +3263,170 @@ function nodeIsChildOfExpressionStatement(compiler, node) {
3150
3263
 
3151
3264
  class AbilityLayer {
3152
3265
  #compiler;
3153
- #expectErrorRegex = /^( *)(\/\/ *@ts-expect-error)(!?)(:? *)(.*)?$/gim;
3154
- #filePath = "";
3266
+ #editor;
3155
3267
  #nodes = [];
3156
- #projectService;
3157
- #resolvedConfig;
3158
- #suppressedErrorsMap;
3159
- #text = "";
3160
- constructor(compiler, projectService, resolvedConfig) {
3268
+ constructor(compiler, editor) {
3161
3269
  this.#compiler = compiler;
3162
- this.#projectService = projectService;
3163
- this.#resolvedConfig = resolvedConfig;
3270
+ this.#editor = editor;
3164
3271
  }
3165
- #addRanges(node, ranges) {
3166
- this.#nodes.push(node);
3167
- for (const range of ranges) {
3168
- const rangeText = range.replacement != null
3169
- ? `${range.replacement}${this.#getErasedRangeText(range).slice(range.replacement.length)}`
3170
- : this.#getErasedRangeText(range);
3171
- this.#text = `${this.#text.slice(0, range.start)}${rangeText}${this.#text.slice(range.end)}`;
3272
+ #belongsToNode(node, diagnostic) {
3273
+ switch (node.brand) {
3274
+ case "expect":
3275
+ return (diagnosticBelongsToNode(diagnostic, node.matcherNode) ||
3276
+ diagnosticBelongsToNode(diagnostic, node.source));
3277
+ case "when":
3278
+ return (diagnosticBelongsToNode(diagnostic, node.actionNode) &&
3279
+ !diagnosticBelongsToNode(diagnostic, node.target));
3280
+ }
3281
+ return false;
3282
+ }
3283
+ close(diagnostics) {
3284
+ if (diagnostics != null && this.#nodes.length > 0) {
3285
+ this.#nodes.reverse();
3286
+ for (const diagnostic of diagnostics) {
3287
+ this.#mapToNodes(diagnostic);
3288
+ }
3172
3289
  }
3290
+ this.#nodes = [];
3173
3291
  }
3174
- #belongsToNode(diagnostic) {
3292
+ #mapToNodes(diagnostic) {
3175
3293
  for (const node of this.#nodes) {
3176
- if (diagnosticBelongsToNode(diagnostic, "matcherNode" in node ? node.matcherNode : node.actionNode)) {
3294
+ if (this.#belongsToNode(node, diagnostic)) {
3177
3295
  node.abilityDiagnostics.add(diagnostic);
3178
- return true;
3296
+ break;
3179
3297
  }
3180
3298
  }
3181
- return false;
3182
3299
  }
3183
- #belongsToDirective(diagnostic) {
3184
- if (!isDiagnosticWithLocation(diagnostic)) {
3185
- return;
3186
- }
3187
- const { file, start } = diagnostic;
3188
- const lineMap = file.getLineStarts();
3189
- let line = this.#compiler.getLineAndCharacterOfPosition(file, start).line - 1;
3190
- while (line >= 0) {
3191
- const suppressedError = this.#suppressedErrorsMap?.get(line);
3192
- if (suppressedError != null) {
3193
- suppressedError.diagnostics.push(diagnostic);
3300
+ visitExpect(expect) {
3301
+ const expectStart = expect.node.getStart();
3302
+ const expectExpressionEnd = expect.node.expression.getEnd();
3303
+ const expectEnd = expect.node.getEnd();
3304
+ const matcherNameEnd = expect.matcherNameNode.getEnd();
3305
+ switch (expect.matcherNameNode.name.text) {
3306
+ case "toBeApplicable":
3307
+ this.#nodes.push(expect);
3308
+ this.#editor.replaceRanges([
3309
+ [expectStart, expectExpressionEnd],
3310
+ [expectEnd, matcherNameEnd],
3311
+ ]);
3194
3312
  break;
3195
- }
3196
- const lineText = file.text.slice(lineMap[line], lineMap[line + 1]).trim();
3197
- if (lineText !== "" && !lineText.startsWith("//")) {
3313
+ case "toBeCallableWith":
3314
+ this.#nodes.push(expect);
3315
+ this.#editor.eraseTrailingComma(expect.source);
3316
+ this.#editor.replaceRanges([
3317
+ [
3318
+ expectStart,
3319
+ expectExpressionEnd,
3320
+ nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? ";" : "",
3321
+ ],
3322
+ [expectEnd, matcherNameEnd],
3323
+ ]);
3324
+ break;
3325
+ case "toBeConstructableWith":
3326
+ this.#nodes.push(expect);
3327
+ this.#editor.eraseTrailingComma(expect.source);
3328
+ this.#editor.replaceRanges([
3329
+ [
3330
+ expectStart,
3331
+ expectExpressionEnd,
3332
+ nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? "; new" : "new",
3333
+ ],
3334
+ [expectEnd, matcherNameEnd],
3335
+ ]);
3336
+ break;
3337
+ }
3338
+ }
3339
+ visitWhen(when) {
3340
+ const whenStart = when.node.getStart();
3341
+ const whenExpressionEnd = when.node.expression.getEnd();
3342
+ const whenEnd = when.node.getEnd();
3343
+ const actionNameEnd = when.actionNameNode.getEnd();
3344
+ switch (when.actionNameNode.name.text) {
3345
+ case "isCalledWith":
3346
+ this.#nodes.push(when);
3347
+ this.#editor.eraseTrailingComma(when.target);
3348
+ this.#editor.replaceRanges([
3349
+ [whenStart, whenExpressionEnd, nodeIsChildOfExpressionStatement(this.#compiler, when.actionNode) ? ";" : ""],
3350
+ [whenEnd, actionNameEnd],
3351
+ ]);
3198
3352
  break;
3353
+ }
3354
+ }
3355
+ }
3356
+
3357
+ class SourceTextEditor {
3358
+ #filePath = "";
3359
+ #sourceFile;
3360
+ #text = "";
3361
+ open(sourceFile) {
3362
+ this.#sourceFile = sourceFile;
3363
+ this.#filePath = sourceFile.fileName;
3364
+ this.#text = sourceFile.text;
3365
+ }
3366
+ close() {
3367
+ if (this.#sourceFile != null) {
3368
+ SourceService.set(this.#sourceFile);
3369
+ this.#sourceFile = undefined;
3370
+ }
3371
+ this.#filePath = "";
3372
+ this.#text = "";
3373
+ }
3374
+ eraseTrailingComma(node) {
3375
+ if (node.hasTrailingComma) {
3376
+ this.replaceRange(node.end - 1, node.end);
3377
+ }
3378
+ }
3379
+ #getErasedRange(start, end) {
3380
+ if (this.#text.indexOf("\n", start) >= end) {
3381
+ return " ".repeat(end - start);
3382
+ }
3383
+ const text = [];
3384
+ for (let index = start; index < end; index++) {
3385
+ const character = this.#text.charAt(index);
3386
+ switch (character) {
3387
+ case "\n":
3388
+ case "\r":
3389
+ text.push(character);
3390
+ break;
3391
+ default:
3392
+ text.push(" ");
3199
3393
  }
3200
- line--;
3394
+ }
3395
+ return text.join("");
3396
+ }
3397
+ getFilePath() {
3398
+ return this.#filePath;
3399
+ }
3400
+ getText() {
3401
+ return this.#text;
3402
+ }
3403
+ replaceRange(start, end, replacement) {
3404
+ const rangeText = replacement != null
3405
+ ? `${replacement}${this.#getErasedRange(start, end).slice(replacement.length)}`
3406
+ : this.#getErasedRange(start, end);
3407
+ this.#text = `${this.#text.slice(0, start)}${rangeText}${this.#text.slice(end)}`;
3408
+ }
3409
+ replaceRanges(ranges) {
3410
+ for (const [start, end, replacement] of ranges) {
3411
+ this.replaceRange(start, end, replacement);
3201
3412
  }
3202
3413
  }
3203
- #collectSuppressedErrors() {
3414
+ }
3415
+
3416
+ class SuppressedLayer {
3417
+ #compiler;
3418
+ #editor;
3419
+ #expectErrorRegex = /^(\s*)(\/\/ *@ts-expect-error)(!?)(:? *)(.*)?$/gim;
3420
+ #resolvedConfig;
3421
+ #suppressedErrorsMap;
3422
+ constructor(compiler, editor, resolvedConfig) {
3423
+ this.#compiler = compiler;
3424
+ this.#editor = editor;
3425
+ this.#resolvedConfig = resolvedConfig;
3426
+ }
3427
+ #collectSuppressedErrors(text) {
3204
3428
  const ranges = [];
3205
- for (const match of this.#text.matchAll(this.#expectErrorRegex)) {
3429
+ for (const match of text.matchAll(this.#expectErrorRegex)) {
3206
3430
  const offsetText = match?.[1];
3207
3431
  const directiveText = match?.[2];
3208
3432
  const ignoreText = match?.[3];
@@ -3225,125 +3449,89 @@ class AbilityLayer {
3225
3449
  }
3226
3450
  return ranges;
3227
3451
  }
3228
- close() {
3229
- if (this.#nodes.length > 0 || this.#suppressedErrorsMap != null) {
3230
- this.#projectService.openFile(this.#filePath, this.#text, this.#resolvedConfig.rootPath);
3231
- const languageService = this.#projectService.getLanguageService(this.#filePath);
3232
- const diagnostics = languageService?.getSemanticDiagnostics(this.#filePath);
3233
- if (diagnostics != null) {
3234
- this.#nodes.reverse();
3235
- for (const diagnostic of diagnostics) {
3236
- if (this.#belongsToNode(diagnostic)) {
3237
- continue;
3238
- }
3239
- this.#belongsToDirective(diagnostic);
3240
- }
3452
+ close(diagnostics) {
3453
+ if (diagnostics != null && this.#suppressedErrorsMap != null) {
3454
+ for (const diagnostic of diagnostics) {
3455
+ this.#mapToDirectives(diagnostic);
3241
3456
  }
3242
3457
  }
3243
- this.#filePath = "";
3244
- this.#nodes = [];
3245
3458
  this.#suppressedErrorsMap = undefined;
3246
- this.#text = "";
3247
- }
3248
- #eraseTrailingComma(node, parent) {
3249
- if (node.hasTrailingComma) {
3250
- this.#addRanges(parent, [{ start: node.end - 1, end: node.end }]);
3251
- }
3252
3459
  }
3253
- #getErasedRangeText(range) {
3254
- if (this.#text.indexOf("\n", range.start) >= range.end) {
3255
- return " ".repeat(range.end - range.start);
3256
- }
3257
- const text = [];
3258
- for (let index = range.start; index < range.end; index++) {
3259
- const character = this.#text.charAt(index);
3260
- switch (character) {
3261
- case "\n":
3262
- case "\r":
3263
- text.push(character);
3264
- break;
3265
- default:
3266
- text.push(" ");
3267
- }
3460
+ #mapToDirectives(diagnostic) {
3461
+ if (!isDiagnosticWithLocation(diagnostic)) {
3462
+ return;
3268
3463
  }
3269
- return text.join("");
3270
- }
3271
- handleAssertion(assertionNode) {
3272
- const expectStart = assertionNode.node.getStart();
3273
- const expectExpressionEnd = assertionNode.node.expression.getEnd();
3274
- const expectEnd = assertionNode.node.getEnd();
3275
- const matcherNameEnd = assertionNode.matcherNameNode.getEnd();
3276
- switch (assertionNode.matcherNameNode.name.text) {
3277
- case "toBeApplicable":
3278
- this.#addRanges(assertionNode, [
3279
- { start: expectStart, end: expectExpressionEnd },
3280
- { start: expectEnd, end: matcherNameEnd },
3281
- ]);
3282
- break;
3283
- case "toBeCallableWith":
3284
- this.#eraseTrailingComma(assertionNode.source, assertionNode);
3285
- this.#addRanges(assertionNode, [
3286
- {
3287
- start: expectStart,
3288
- end: expectExpressionEnd,
3289
- replacement: nodeIsChildOfExpressionStatement(this.#compiler, assertionNode.matcherNode) ? ";" : "",
3290
- },
3291
- { start: expectEnd, end: matcherNameEnd },
3292
- ]);
3464
+ const { file, start } = diagnostic;
3465
+ const lineMap = file.getLineStarts();
3466
+ let line = this.#compiler.getLineAndCharacterOfPosition(file, start).line - 1;
3467
+ while (line >= 0) {
3468
+ const suppressedError = this.#suppressedErrorsMap?.get(line);
3469
+ if (suppressedError != null) {
3470
+ suppressedError.diagnostics.push(diagnostic);
3293
3471
  break;
3294
- case "toBeConstructableWith":
3295
- this.#eraseTrailingComma(assertionNode.source, assertionNode);
3296
- this.#addRanges(assertionNode, [
3297
- {
3298
- start: expectStart,
3299
- end: expectExpressionEnd,
3300
- replacement: nodeIsChildOfExpressionStatement(this.#compiler, assertionNode.matcherNode) ? "; new" : "new",
3301
- },
3302
- { start: expectEnd, end: matcherNameEnd },
3303
- ]);
3472
+ }
3473
+ const lineText = file.text.slice(lineMap[line], lineMap[line + 1]).trim();
3474
+ if (lineText !== "" && !lineText.startsWith("//")) {
3304
3475
  break;
3476
+ }
3477
+ line--;
3305
3478
  }
3306
3479
  }
3307
- #handleSuppressedErrors(testTree) {
3308
- const suppressedErrors = this.#collectSuppressedErrors();
3480
+ open(tree) {
3481
+ const suppressedErrors = this.#collectSuppressedErrors(this.#editor.getText());
3309
3482
  if (this.#resolvedConfig.checkSuppressedErrors) {
3310
- testTree.suppressedErrors = Object.assign(suppressedErrors, { sourceFile: testTree.sourceFile });
3483
+ tree.suppressedErrors = suppressedErrors;
3311
3484
  this.#suppressedErrorsMap = new Map();
3312
3485
  }
3313
3486
  for (const suppressedError of suppressedErrors) {
3314
3487
  const { start, end } = suppressedError.directive;
3315
- const rangeText = this.#getErasedRangeText({ start: start + 2, end });
3316
- this.#text = `${this.#text.slice(0, start + 2)}${rangeText}${this.#text.slice(end)}`;
3488
+ this.#editor.replaceRange(start + 2, end);
3317
3489
  if (this.#suppressedErrorsMap != null) {
3318
- const { line } = testTree.sourceFile.getLineAndCharacterOfPosition(start);
3490
+ const { line } = tree.sourceFile.getLineAndCharacterOfPosition(start);
3319
3491
  this.#suppressedErrorsMap.set(line, suppressedError);
3320
3492
  }
3321
3493
  }
3322
3494
  }
3323
- handleWhen(whenNode) {
3324
- const whenStart = whenNode.node.getStart();
3325
- const whenExpressionEnd = whenNode.node.expression.getEnd();
3326
- const whenEnd = whenNode.node.getEnd();
3327
- const actionNameEnd = whenNode.actionNameNode.getEnd();
3328
- switch (whenNode.actionNameNode.name.text) {
3329
- case "isCalledWith":
3330
- this.#eraseTrailingComma(whenNode.target, whenNode);
3331
- this.#addRanges(whenNode, [
3332
- {
3333
- start: whenStart,
3334
- end: whenExpressionEnd,
3335
- replacement: nodeIsChildOfExpressionStatement(this.#compiler, whenNode.actionNode) ? ";" : "",
3336
- },
3337
- { start: whenEnd, end: actionNameEnd },
3338
- ]);
3495
+ }
3496
+
3497
+ class Layers {
3498
+ #abilityLayer;
3499
+ #editor = new SourceTextEditor();
3500
+ #projectService;
3501
+ #suppressedDiagnostics;
3502
+ #suppressedLayer;
3503
+ constructor(compiler, projectService, resolvedConfig) {
3504
+ this.#projectService = projectService;
3505
+ this.#abilityLayer = new AbilityLayer(compiler, this.#editor);
3506
+ this.#suppressedLayer = new SuppressedLayer(compiler, this.#editor, resolvedConfig);
3507
+ }
3508
+ close() {
3509
+ let isSeenDiagnostic;
3510
+ if (this.#suppressedDiagnostics != null) {
3511
+ const seenDiagnostics = this.#suppressedDiagnostics;
3512
+ this.#suppressedDiagnostics = undefined;
3513
+ isSeenDiagnostic = (diagnostic) => !seenDiagnostics.some((seenDiagnostic) => compareDiagnostics(diagnostic, seenDiagnostic));
3514
+ }
3515
+ const abilityDiagnostics = this.#projectService.getDiagnostics(this.#editor.getFilePath(), this.#editor.getText(), isSeenDiagnostic);
3516
+ this.#abilityLayer.close(abilityDiagnostics);
3517
+ this.#editor.close();
3518
+ }
3519
+ open(tree) {
3520
+ this.#editor.open(tree.sourceFile);
3521
+ this.#suppressedLayer.open(tree);
3522
+ this.#suppressedDiagnostics = this.#projectService.getDiagnostics(this.#editor.getFilePath(), this.#editor.getText());
3523
+ this.#suppressedLayer.close(this.#suppressedDiagnostics);
3524
+ }
3525
+ visit(node) {
3526
+ switch (node.brand) {
3527
+ case "expect":
3528
+ this.#abilityLayer.visitExpect(node);
3529
+ break;
3530
+ case "when":
3531
+ this.#abilityLayer.visitWhen(node);
3339
3532
  break;
3340
3533
  }
3341
3534
  }
3342
- open(testTree) {
3343
- this.#filePath = testTree.sourceFile.fileName;
3344
- this.#text = testTree.sourceFile.text;
3345
- this.#handleSuppressedErrors(testTree);
3346
- }
3347
3535
  }
3348
3536
 
3349
3537
  class CollectDiagnosticText {
@@ -3352,22 +3540,61 @@ class CollectDiagnosticText {
3352
3540
  }
3353
3541
  }
3354
3542
 
3355
- var TestTreeNodeBrand;
3356
- (function (TestTreeNodeBrand) {
3357
- TestTreeNodeBrand["Describe"] = "describe";
3358
- TestTreeNodeBrand["Test"] = "test";
3359
- TestTreeNodeBrand["Expect"] = "expect";
3360
- TestTreeNodeBrand["When"] = "when";
3361
- })(TestTreeNodeBrand || (TestTreeNodeBrand = {}));
3543
+ class TestTreeNode {
3544
+ brand;
3545
+ children = [];
3546
+ diagnostics = new Set();
3547
+ flags;
3548
+ name = "";
3549
+ node;
3550
+ parent;
3551
+ constructor(compiler, brand, node, parent, flags) {
3552
+ this.brand = brand;
3553
+ this.node = node;
3554
+ this.parent = parent;
3555
+ this.flags = flags;
3556
+ if (node.arguments[0] != null && compiler.isStringLiteralLike(node.arguments[0])) {
3557
+ this.name = node.arguments[0].text;
3558
+ }
3559
+ if (node.arguments[1] != null && compiler.isFunctionLike(node.arguments[1])) {
3560
+ for (const diagnostic of parent.diagnostics) {
3561
+ if (diagnosticBelongsToNode(diagnostic, node.arguments[1].body)) {
3562
+ this.diagnostics.add(diagnostic);
3563
+ parent.diagnostics.delete(diagnostic);
3564
+ }
3565
+ }
3566
+ }
3567
+ }
3568
+ }
3362
3569
 
3363
- var TestTreeNodeFlags;
3364
- (function (TestTreeNodeFlags) {
3365
- TestTreeNodeFlags[TestTreeNodeFlags["None"] = 0] = "None";
3366
- TestTreeNodeFlags[TestTreeNodeFlags["Fail"] = 1] = "Fail";
3367
- TestTreeNodeFlags[TestTreeNodeFlags["Only"] = 2] = "Only";
3368
- TestTreeNodeFlags[TestTreeNodeFlags["Skip"] = 4] = "Skip";
3369
- TestTreeNodeFlags[TestTreeNodeFlags["Todo"] = 8] = "Todo";
3370
- })(TestTreeNodeFlags || (TestTreeNodeFlags = {}));
3570
+ class ExpectNode extends TestTreeNode {
3571
+ abilityDiagnostics = new Set();
3572
+ isNot;
3573
+ matcherNode;
3574
+ matcherNameNode;
3575
+ modifierNode;
3576
+ notNode;
3577
+ source;
3578
+ target;
3579
+ constructor(compiler, brand, node, parent, flags, matcherNode, matcherNameNode, modifierNode, notNode) {
3580
+ super(compiler, brand, node, parent, flags);
3581
+ this.isNot = notNode != null;
3582
+ this.matcherNode = matcherNode;
3583
+ this.matcherNameNode = matcherNameNode;
3584
+ this.modifierNode = modifierNode;
3585
+ this.source = this.node.typeArguments ?? this.node.arguments;
3586
+ if (compiler.isCallExpression(this.matcherNode)) {
3587
+ this.target = this.matcherNode.typeArguments ?? this.matcherNode.arguments;
3588
+ }
3589
+ for (const diagnostic of parent.diagnostics) {
3590
+ if (diagnosticBelongsToNode(diagnostic, this.source) ||
3591
+ (this.target != null && diagnosticBelongsToNode(diagnostic, this.target))) {
3592
+ this.diagnostics.add(diagnostic);
3593
+ parent.diagnostics.delete(diagnostic);
3594
+ }
3595
+ }
3596
+ }
3597
+ }
3371
3598
 
3372
3599
  class IdentifierLookup {
3373
3600
  #compiler;
@@ -3416,24 +3643,21 @@ class IdentifierLookup {
3416
3643
  };
3417
3644
  }
3418
3645
  resolveTestTreeNodeMeta(node) {
3419
- let flags = TestTreeNodeFlags.None;
3646
+ let flags = 0;
3420
3647
  let expression = node.expression;
3421
3648
  while (this.#compiler.isPropertyAccessExpression(expression)) {
3422
3649
  if (expression.expression.getText() === this.#identifiers.namespace) {
3423
3650
  break;
3424
3651
  }
3425
3652
  switch (expression.name.getText()) {
3426
- case "fail":
3427
- flags |= TestTreeNodeFlags.Fail;
3428
- break;
3429
3653
  case "only":
3430
- flags |= TestTreeNodeFlags.Only;
3654
+ flags |= 1;
3431
3655
  break;
3432
3656
  case "skip":
3433
- flags |= TestTreeNodeFlags.Skip;
3657
+ flags |= 2;
3434
3658
  break;
3435
3659
  case "todo":
3436
- flags |= TestTreeNodeFlags.Todo;
3660
+ flags |= 4;
3437
3661
  break;
3438
3662
  }
3439
3663
  expression = expression.expression;
@@ -3451,14 +3675,14 @@ class IdentifierLookup {
3451
3675
  }
3452
3676
  switch (identifier) {
3453
3677
  case "describe":
3454
- return { brand: TestTreeNodeBrand.Describe, flags, identifier };
3678
+ return { brand: "describe", flags, identifier };
3455
3679
  case "it":
3456
3680
  case "test":
3457
- return { brand: TestTreeNodeBrand.Test, flags, identifier };
3681
+ return { brand: "test", flags, identifier };
3458
3682
  case "expect":
3459
- return { brand: TestTreeNodeBrand.Expect, flags, identifier };
3683
+ return { brand: "expect", flags, identifier };
3460
3684
  case "when":
3461
- return { brand: TestTreeNodeBrand.When, flags, identifier };
3685
+ return { brand: "when", flags, identifier };
3462
3686
  }
3463
3687
  return;
3464
3688
  }
@@ -3474,9 +3698,6 @@ class TestTree {
3474
3698
  this.diagnostics = diagnostics;
3475
3699
  this.sourceFile = sourceFile;
3476
3700
  }
3477
- getDirectiveRanges(compiler) {
3478
- return Directive.getDirectiveRanges(compiler, this.sourceFile);
3479
- }
3480
3701
  }
3481
3702
 
3482
3703
  class WhenNode extends TestTreeNode {
@@ -3499,12 +3720,12 @@ class WhenNode extends TestTreeNode {
3499
3720
  }
3500
3721
 
3501
3722
  class CollectService {
3502
- #abilityLayer;
3723
+ #layers;
3503
3724
  #compiler;
3504
3725
  #identifierLookup;
3505
3726
  constructor(compiler, projectService, resolvedConfig) {
3506
3727
  this.#compiler = compiler;
3507
- this.#abilityLayer = new AbilityLayer(compiler, projectService, resolvedConfig);
3728
+ this.#layers = new Layers(compiler, projectService, resolvedConfig);
3508
3729
  this.#identifierLookup = new IdentifierLookup(compiler);
3509
3730
  }
3510
3731
  #collectTestTreeNodes(node, parent, testTree) {
@@ -3514,7 +3735,7 @@ class CollectService {
3514
3735
  if (!this.#checkNode(node, meta, parent)) {
3515
3736
  return;
3516
3737
  }
3517
- if (meta.brand === TestTreeNodeBrand.Describe || meta.brand === TestTreeNodeBrand.Test) {
3738
+ if (meta.brand === "describe" || meta.brand === "test") {
3518
3739
  const testTreeNode = new TestTreeNode(this.#compiler, meta.brand, node, parent, meta.flags);
3519
3740
  this.#compiler.forEachChild(node, (node) => {
3520
3741
  this.#collectTestTreeNodes(node, testTreeNode, testTree);
@@ -3522,7 +3743,7 @@ class CollectService {
3522
3743
  this.#onNode(testTreeNode, parent, testTree);
3523
3744
  return;
3524
3745
  }
3525
- if (meta.brand === TestTreeNodeBrand.Expect) {
3746
+ if (meta.brand === "expect") {
3526
3747
  const modifierNode = this.#getChainedNode(node, "type");
3527
3748
  if (!modifierNode) {
3528
3749
  return;
@@ -3536,15 +3757,15 @@ class CollectService {
3536
3757
  if (!matcherNode) {
3537
3758
  return;
3538
3759
  }
3539
- const assertionNode = new AssertionNode(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, matcherNameNode, modifierNode, notNode);
3540
- this.#abilityLayer.handleAssertion(assertionNode);
3760
+ const expectNode = new ExpectNode(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, matcherNameNode, modifierNode, notNode);
3761
+ this.#layers.visit(expectNode);
3541
3762
  this.#compiler.forEachChild(node, (node) => {
3542
- this.#collectTestTreeNodes(node, assertionNode, testTree);
3763
+ this.#collectTestTreeNodes(node, expectNode, testTree);
3543
3764
  });
3544
- this.#onNode(assertionNode, parent, testTree);
3765
+ this.#onNode(expectNode, parent, testTree);
3545
3766
  return;
3546
3767
  }
3547
- if (meta.brand === TestTreeNodeBrand.When) {
3768
+ if (meta.brand === "when") {
3548
3769
  const actionNameNode = this.#getChainedNode(node);
3549
3770
  if (!actionNameNode) {
3550
3771
  return;
@@ -3556,7 +3777,7 @@ class CollectService {
3556
3777
  this.#compiler.forEachChild(actionNode, (node) => {
3557
3778
  if (this.#compiler.isCallExpression(node)) {
3558
3779
  const meta = this.#identifierLookup.resolveTestTreeNodeMeta(node);
3559
- if (meta?.brand === TestTreeNodeBrand.Describe || meta?.brand === TestTreeNodeBrand.Test) {
3780
+ if (meta?.brand === "describe" || meta?.brand === "test") {
3560
3781
  const text = CollectDiagnosticText.cannotBeNestedWithin(meta.identifier, "when");
3561
3782
  const origin = DiagnosticOrigin.fromNode(node);
3562
3783
  this.#onDiagnostics(Diagnostic.error(text, origin));
@@ -3564,7 +3785,7 @@ class CollectService {
3564
3785
  }
3565
3786
  });
3566
3787
  const whenNode = new WhenNode(this.#compiler, meta.brand, node, parent, meta.flags, actionNode, actionNameNode);
3567
- this.#abilityLayer.handleWhen(whenNode);
3788
+ this.#layers.visit(whenNode);
3568
3789
  this.#onNode(whenNode, parent, testTree);
3569
3790
  return;
3570
3791
  }
@@ -3581,10 +3802,10 @@ class CollectService {
3581
3802
  createTestTree(sourceFile, semanticDiagnostics = []) {
3582
3803
  const testTree = new TestTree(new Set(semanticDiagnostics), sourceFile);
3583
3804
  EventEmitter.dispatch(["collect:start", { tree: testTree }]);
3584
- this.#abilityLayer.open(testTree);
3805
+ this.#layers.open(testTree);
3585
3806
  this.#identifierLookup.open();
3586
3807
  this.#collectTestTreeNodes(sourceFile, testTree, testTree);
3587
- this.#abilityLayer.close();
3808
+ this.#layers.close();
3588
3809
  EventEmitter.dispatch(["collect:end", { tree: testTree }]);
3589
3810
  return testTree;
3590
3811
  }
@@ -3599,15 +3820,15 @@ class CollectService {
3599
3820
  }
3600
3821
  #isNodeAllowed(meta, parent) {
3601
3822
  switch (meta.brand) {
3602
- case TestTreeNodeBrand.Describe:
3603
- case TestTreeNodeBrand.Test:
3604
- if (parent.brand === TestTreeNodeBrand.Test || parent.brand === TestTreeNodeBrand.Expect) {
3823
+ case "describe":
3824
+ case "test":
3825
+ if (parent.brand === "test" || parent.brand === "expect") {
3605
3826
  return false;
3606
3827
  }
3607
3828
  break;
3608
- case TestTreeNodeBrand.Expect:
3609
- case TestTreeNodeBrand.When:
3610
- if (parent.brand === TestTreeNodeBrand.Describe) {
3829
+ case "expect":
3830
+ case "when":
3831
+ if (parent.brand === "describe") {
3611
3832
  return false;
3612
3833
  }
3613
3834
  break;
@@ -3646,13 +3867,33 @@ class CollectService {
3646
3867
  }
3647
3868
  #onNode(node, parent, testTree) {
3648
3869
  parent.children.push(node);
3649
- if (node.flags & TestTreeNodeFlags.Only) {
3870
+ if (node.flags & 1) {
3650
3871
  testTree.hasOnly = true;
3651
3872
  }
3652
3873
  EventEmitter.dispatch(["collect:node", { node }]);
3653
3874
  }
3654
3875
  }
3655
3876
 
3877
+ function nodeBelongsToArgumentList(compiler, node) {
3878
+ return compiler.isCallExpression(node.parent) && node.parent.arguments.some((argument) => argument === node);
3879
+ }
3880
+
3881
+ var TestTreeNodeBrand;
3882
+ (function (TestTreeNodeBrand) {
3883
+ TestTreeNodeBrand["Describe"] = "describe";
3884
+ TestTreeNodeBrand["Test"] = "test";
3885
+ TestTreeNodeBrand["Expect"] = "expect";
3886
+ TestTreeNodeBrand["When"] = "when";
3887
+ })(TestTreeNodeBrand || (TestTreeNodeBrand = {}));
3888
+
3889
+ var TestTreeNodeFlags;
3890
+ (function (TestTreeNodeFlags) {
3891
+ TestTreeNodeFlags[TestTreeNodeFlags["None"] = 0] = "None";
3892
+ TestTreeNodeFlags[TestTreeNodeFlags["Only"] = 1] = "Only";
3893
+ TestTreeNodeFlags[TestTreeNodeFlags["Skip"] = 2] = "Skip";
3894
+ TestTreeNodeFlags[TestTreeNodeFlags["Todo"] = 4] = "Todo";
3895
+ })(TestTreeNodeFlags || (TestTreeNodeFlags = {}));
3896
+
3656
3897
  class ProjectService {
3657
3898
  #compiler;
3658
3899
  #lastSeenProject = "";
@@ -3700,11 +3941,10 @@ class ProjectService {
3700
3941
  }
3701
3942
  closeFile(filePath) {
3702
3943
  this.#service.closeClientFile(filePath);
3944
+ SourceService.delete(filePath);
3703
3945
  }
3704
3946
  #getDefaultCompilerOptions() {
3705
3947
  const defaultCompilerOptions = {
3706
- allowJs: true,
3707
- checkJs: true,
3708
3948
  exactOptionalPropertyTypes: true,
3709
3949
  jsx: this.#compiler.JsxEmit.Preserve,
3710
3950
  module: this.#compiler.ModuleKind.NodeNext,
@@ -3726,11 +3966,20 @@ class ProjectService {
3726
3966
  getDefaultProject(filePath) {
3727
3967
  const project = this.#service.getDefaultProjectForFile(this.#compiler.server.toNormalizedPath(filePath), true);
3728
3968
  const compilerOptions = project?.getCompilerOptions();
3729
- if (this.#resolvedConfig.checkSourceFiles && compilerOptions?.skipLibCheck) {
3969
+ if (this.#resolvedConfig.checkDeclarationFiles && compilerOptions?.skipLibCheck) {
3730
3970
  project?.setCompilerOptions({ ...compilerOptions, skipLibCheck: false });
3731
3971
  }
3732
3972
  return project;
3733
3973
  }
3974
+ getDiagnostics(filePath, sourceText, shouldInclude) {
3975
+ this.openFile(filePath, sourceText);
3976
+ const languageService = this.getLanguageService(filePath);
3977
+ const diagnostics = languageService?.getSemanticDiagnostics(filePath);
3978
+ if (diagnostics != null && shouldInclude != null) {
3979
+ return diagnostics.filter(shouldInclude);
3980
+ }
3981
+ return diagnostics;
3982
+ }
3734
3983
  getLanguageService(filePath) {
3735
3984
  const project = this.getDefaultProject(filePath);
3736
3985
  return project?.getLanguageService(true);
@@ -3740,7 +3989,7 @@ class ProjectService {
3740
3989
  const { fileNames } = this.#compiler.parseJsonSourceFileConfigFileContent(configSourceFile, this.#compiler.sys, Path.dirname(this.#resolvedConfig.tsconfig), undefined, this.#resolvedConfig.tsconfig);
3741
3990
  return fileNames.includes(filePath);
3742
3991
  }
3743
- openFile(filePath, sourceText, projectRootPath) {
3992
+ openFile(filePath, sourceText) {
3744
3993
  switch (this.#resolvedConfig.tsconfig) {
3745
3994
  case "findup":
3746
3995
  break;
@@ -3752,19 +4001,19 @@ class ProjectService {
3752
4001
  ? () => this.#resolvedConfig.tsconfig
3753
4002
  : () => undefined;
3754
4003
  }
3755
- const { configFileErrors, configFileName } = this.#service.openClientFile(filePath, sourceText, undefined, projectRootPath);
4004
+ const { configFileErrors, configFileName } = this.#service.openClientFile(filePath, sourceText, undefined, this.#resolvedConfig.rootPath);
3756
4005
  if (configFileName !== this.#lastSeenProject) {
3757
4006
  this.#lastSeenProject = configFileName;
3758
4007
  EventEmitter.dispatch([
3759
4008
  "project:uses",
3760
4009
  { compilerVersion: this.#compiler.version, projectConfigFilePath: configFileName },
3761
4010
  ]);
3762
- }
3763
- if (configFileErrors && configFileErrors.length > 0) {
3764
- EventEmitter.dispatch([
3765
- "project:error",
3766
- { diagnostics: Diagnostic.fromDiagnostics(configFileErrors) },
3767
- ]);
4011
+ if (configFileErrors && configFileErrors.length > 0) {
4012
+ EventEmitter.dispatch([
4013
+ "project:error",
4014
+ { diagnostics: Diagnostic.fromDiagnostics(configFileErrors) },
4015
+ ]);
4016
+ }
3768
4017
  }
3769
4018
  if (!this.#seenTestFiles.has(filePath)) {
3770
4019
  this.#seenTestFiles.add(filePath);
@@ -3778,13 +4027,16 @@ class ProjectService {
3778
4027
  if (program.isSourceFileFromExternalLibrary(sourceFile) || program.isSourceFileDefaultLibrary(sourceFile)) {
3779
4028
  return false;
3780
4029
  }
4030
+ if (this.#resolvedConfig.checkDeclarationFiles && sourceFile.isDeclarationFile) {
4031
+ return true;
4032
+ }
3781
4033
  if (Select.isFixtureFile(sourceFile.fileName, { ...this.#resolvedConfig, pathMatch: [] })) {
3782
4034
  return true;
3783
4035
  }
3784
4036
  if (Select.isTestFile(sourceFile.fileName, { ...this.#resolvedConfig, pathMatch: [] })) {
3785
4037
  return false;
3786
4038
  }
3787
- return this.#resolvedConfig.checkSourceFiles;
4039
+ return false;
3788
4040
  });
3789
4041
  const diagnostics = [];
3790
4042
  for (const sourceFile of sourceFilesToCheck) {
@@ -3817,50 +4069,65 @@ class SuppressedDiagnosticText {
3817
4069
  }
3818
4070
 
3819
4071
  class SuppressedService {
3820
- match(suppressedErrors, onDiagnostics) {
3821
- for (const suppressedError of suppressedErrors) {
3822
- if (suppressedError.diagnostics.length === 0 || suppressedError.ignore) {
4072
+ match(testTree) {
4073
+ if (!testTree.suppressedErrors) {
4074
+ return;
4075
+ }
4076
+ for (const suppressedError of testTree.suppressedErrors) {
4077
+ const suppressedResult = new SuppressedResult(suppressedError);
4078
+ if (suppressedError.diagnostics.length === 0) {
3823
4079
  continue;
3824
4080
  }
3825
- if (!suppressedError.argument?.text) {
3826
- const text = SuppressedDiagnosticText.directiveRequires();
3827
- const origin = new DiagnosticOrigin(suppressedError.directive.start, suppressedError.directive.end, suppressedErrors.sourceFile);
3828
- onDiagnostics([Diagnostic.error(text, origin)]);
4081
+ if (suppressedError.ignore) {
4082
+ EventEmitter.dispatch(["suppressed:ignore", { result: suppressedResult }]);
3829
4083
  continue;
3830
4084
  }
3831
4085
  const related = [
3832
4086
  Diagnostic.error(SuppressedDiagnosticText.suppressedError(suppressedError.diagnostics.length)),
3833
- ...Diagnostic.fromDiagnostics(suppressedError.diagnostics, suppressedErrors.sourceFile),
4087
+ ...Diagnostic.fromDiagnostics(suppressedError.diagnostics),
3834
4088
  ];
4089
+ const origin = new DiagnosticOrigin(suppressedError.directive.start, suppressedError.directive.end, testTree.sourceFile);
4090
+ if (!suppressedError.argument?.text) {
4091
+ const text = SuppressedDiagnosticText.directiveRequires();
4092
+ this.#onDiagnostics(Diagnostic.error(text, origin).add({ related }), suppressedResult);
4093
+ continue;
4094
+ }
3835
4095
  if (suppressedError.diagnostics.length > 1) {
3836
- const text = [SuppressedDiagnosticText.onlySingleError()];
3837
- const origin = new DiagnosticOrigin(suppressedError.directive.start, suppressedError.directive.end, suppressedErrors.sourceFile);
3838
- onDiagnostics([Diagnostic.error(text, origin).add({ related })]);
4096
+ const text = SuppressedDiagnosticText.onlySingleError();
4097
+ this.#onDiagnostics(Diagnostic.error(text, origin).add({ related }), suppressedResult);
3839
4098
  continue;
3840
4099
  }
3841
4100
  let messageText = getDiagnosticMessageText(suppressedError.diagnostics[0]);
3842
4101
  if (Array.isArray(messageText)) {
3843
4102
  messageText = messageText.join("\n");
3844
4103
  }
3845
- if (!messageText.includes(suppressedError.argument.text)) {
3846
- const text = [SuppressedDiagnosticText.messageDidNotMatch()];
3847
- const origin = new DiagnosticOrigin(suppressedError.argument.start, suppressedError.argument.end, suppressedErrors.sourceFile);
3848
- onDiagnostics([Diagnostic.error(text, origin).add({ related })]);
4104
+ if (!this.#matchMessage(messageText, suppressedError.argument.text)) {
4105
+ const text = SuppressedDiagnosticText.messageDidNotMatch();
4106
+ const origin = new DiagnosticOrigin(suppressedError.argument.start, suppressedError.argument.end, testTree.sourceFile);
4107
+ this.#onDiagnostics(Diagnostic.error(text, origin).add({ related }), suppressedResult);
4108
+ continue;
4109
+ }
4110
+ EventEmitter.dispatch(["suppressed:match", { result: suppressedResult }]);
4111
+ }
4112
+ }
4113
+ #matchMessage(source, target) {
4114
+ if (target.includes("...")) {
4115
+ let position = 0;
4116
+ for (const segment of target.split("...")) {
4117
+ position = source.indexOf(segment, position);
4118
+ if (position === -1) {
4119
+ break;
4120
+ }
3849
4121
  }
4122
+ return position > 0;
3850
4123
  }
4124
+ return source.includes(target);
4125
+ }
4126
+ #onDiagnostics(diagnostic, result) {
4127
+ EventEmitter.dispatch(["suppressed:error", { diagnostics: [diagnostic], result }]);
3851
4128
  }
3852
4129
  }
3853
4130
 
3854
- var RunMode;
3855
- (function (RunMode) {
3856
- RunMode[RunMode["Normal"] = 0] = "Normal";
3857
- RunMode[RunMode["Fail"] = 1] = "Fail";
3858
- RunMode[RunMode["Only"] = 2] = "Only";
3859
- RunMode[RunMode["Skip"] = 4] = "Skip";
3860
- RunMode[RunMode["Todo"] = 8] = "Todo";
3861
- RunMode[RunMode["Void"] = 16] = "Void";
3862
- })(RunMode || (RunMode = {}));
3863
-
3864
4131
  class EnsureDiagnosticText {
3865
4132
  static argumentMustBeProvided(argumentNameText) {
3866
4133
  return `An argument for '${argumentNameText}' must be provided.`;
@@ -3961,11 +4228,11 @@ class ExpectDiagnosticText {
3961
4228
  static isNotAssignableTo(sourceTypeText, targetTypeText) {
3962
4229
  return `Type '${sourceTypeText}' is not assignable to type '${targetTypeText}'.`;
3963
4230
  }
3964
- static isAssignableWith(sourceTypeText, targetTypeText) {
3965
- return `Type '${sourceTypeText}' is assignable with type '${targetTypeText}'.`;
4231
+ static isAssignableFrom(sourceTypeText, targetTypeText) {
4232
+ return `Type '${sourceTypeText}' is assignable from type '${targetTypeText}'.`;
3966
4233
  }
3967
- static isNotAssignableWith(sourceTypeText, targetTypeText) {
3968
- return `Type '${sourceTypeText}' is not assignable with type '${targetTypeText}'.`;
4234
+ static isNotAssignableFrom(sourceTypeText, targetTypeText) {
4235
+ return `Type '${sourceTypeText}' is not assignable from type '${targetTypeText}'.`;
3969
4236
  }
3970
4237
  static isTheSame(sourceTypeText, targetTypeText) {
3971
4238
  return `Type '${sourceTypeText}' is the same as type '${targetTypeText}'.`;
@@ -3984,21 +4251,15 @@ class ExpectDiagnosticText {
3984
4251
  }
3985
4252
  }
3986
4253
 
3987
- var Relation;
3988
- (function (Relation) {
3989
- Relation["Assignable"] = "assignable";
3990
- Relation["Identical"] = "identical";
3991
- })(Relation || (Relation = {}));
3992
-
3993
4254
  class MatchWorker {
3994
- assertion;
4255
+ assertionNode;
3995
4256
  #compiler;
3996
4257
  #signatureCache = new Map();
3997
4258
  typeChecker;
3998
- constructor(compiler, typeChecker, assertion) {
4259
+ constructor(compiler, typeChecker, assertionNode) {
3999
4260
  this.#compiler = compiler;
4000
4261
  this.typeChecker = typeChecker;
4001
- this.assertion = assertion;
4262
+ this.assertionNode = assertionNode;
4002
4263
  }
4003
4264
  checkHasApplicableIndexType(sourceNode, targetNode) {
4004
4265
  const sourceType = this.getType(sourceNode);
@@ -4014,13 +4275,13 @@ class MatchWorker {
4014
4275
  .some((property) => this.#compiler.unescapeLeadingUnderscores(property.escapedName) === propertyNameText);
4015
4276
  }
4016
4277
  checkIsAssignableTo(sourceNode, targetNode) {
4017
- return this.#checkIsRelatedTo(sourceNode, targetNode, Relation.Assignable);
4278
+ return this.#checkIsRelatedTo(sourceNode, targetNode, "assignable");
4018
4279
  }
4019
4280
  checkIsAssignableWith(sourceNode, targetNode) {
4020
- return this.#checkIsRelatedTo(targetNode, sourceNode, Relation.Assignable);
4281
+ return this.#checkIsRelatedTo(targetNode, sourceNode, "assignable");
4021
4282
  }
4022
4283
  checkIsIdenticalTo(sourceNode, targetNode) {
4023
- return (this.#checkIsRelatedTo(sourceNode, targetNode, Relation.Identical) &&
4284
+ return (this.#checkIsRelatedTo(sourceNode, targetNode, "identical") &&
4024
4285
  this.checkIsAssignableTo(sourceNode, targetNode) &&
4025
4286
  this.checkIsAssignableWith(sourceNode, targetNode));
4026
4287
  }
@@ -4028,9 +4289,9 @@ class MatchWorker {
4028
4289
  const sourceType = relation === "identical" ? this.#simplifyType(this.getType(sourceNode)) : this.getType(sourceNode);
4029
4290
  const targetType = relation === "identical" ? this.#simplifyType(this.getType(targetNode)) : this.getType(targetNode);
4030
4291
  switch (relation) {
4031
- case Relation.Assignable:
4292
+ case "assignable":
4032
4293
  return this.typeChecker.isTypeAssignableTo(sourceType, targetType);
4033
- case Relation.Identical:
4294
+ case "identical":
4034
4295
  return this.typeChecker.isTypeIdenticalTo(sourceType, targetType);
4035
4296
  }
4036
4297
  }
@@ -4071,9 +4332,9 @@ class MatchWorker {
4071
4332
  this.#compiler.isShorthandPropertyAssignment(symbol.valueDeclaration)) &&
4072
4333
  symbol.valueDeclaration.getStart() >= enclosingNode.getStart() &&
4073
4334
  symbol.valueDeclaration.getEnd() <= enclosingNode.getEnd()) {
4074
- return DiagnosticOrigin.fromNode(symbol.valueDeclaration.name, this.assertion);
4335
+ return DiagnosticOrigin.fromNode(symbol.valueDeclaration.name, this.assertionNode);
4075
4336
  }
4076
- return DiagnosticOrigin.fromNode(enclosingNode, this.assertion);
4337
+ return DiagnosticOrigin.fromNode(enclosingNode, this.assertionNode);
4077
4338
  }
4078
4339
  #simplifyType(type) {
4079
4340
  if (type.isUnionOrIntersection()) {
@@ -4108,10 +4369,10 @@ class ToAcceptProps {
4108
4369
  const signatures = matchWorker.getSignatures(sourceNode);
4109
4370
  return signatures.reduce((accumulator, signature, index) => {
4110
4371
  let diagnostic;
4111
- const introText = matchWorker.assertion.isNot
4372
+ const introText = matchWorker.assertionNode.isNot
4112
4373
  ? ExpectDiagnosticText.acceptsProps(isExpression)
4113
4374
  : ExpectDiagnosticText.doesNotAcceptProps(isExpression);
4114
- const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
4375
+ const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertionNode);
4115
4376
  if (signatures.length > 1) {
4116
4377
  const signatureText = this.#typeChecker.signatureToString(signature, sourceNode);
4117
4378
  const overloadText = ExpectDiagnosticText.overloadGaveTheFollowingError(index + 1, signatures.length, signatureText);
@@ -4121,7 +4382,7 @@ class ToAcceptProps {
4121
4382
  diagnostic = Diagnostic.error([introText], origin);
4122
4383
  }
4123
4384
  const { diagnostics, isMatch } = this.#explainProperties(matchWorker, signature, targetNode, diagnostic);
4124
- if (matchWorker.assertion.isNot ? isMatch : !isMatch) {
4385
+ if (matchWorker.assertionNode.isNot ? isMatch : !isMatch) {
4125
4386
  accumulator.push(...diagnostics);
4126
4387
  }
4127
4388
  return accumulator;
@@ -4185,7 +4446,7 @@ class ToAcceptProps {
4185
4446
  }
4186
4447
  if (this.#isOptionalProperty(targetProperty) && !this.#isOptionalProperty(sourceProperty)) {
4187
4448
  const text = [
4188
- ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
4449
+ ExpectDiagnosticText.isNotAssignableFrom(sourceTypeText, targetTypeText),
4189
4450
  ExpectDiagnosticText.requiresProperty(sourceTypeText, targetPropertyName),
4190
4451
  ];
4191
4452
  const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
@@ -4198,9 +4459,9 @@ class ToAcceptProps {
4198
4459
  const targetPropertyTypeText = this.#typeChecker.typeToString(targetPropertyType);
4199
4460
  const sourcePropertyTypeText = this.#typeChecker.typeToString(sourcePropertyType);
4200
4461
  const text = [
4201
- ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
4462
+ ExpectDiagnosticText.isNotAssignableFrom(sourceTypeText, targetTypeText),
4202
4463
  ExpectDiagnosticText.typesOfPropertyAreNotCompatible(targetPropertyName),
4203
- ExpectDiagnosticText.isNotAssignableWith(sourcePropertyTypeText, targetPropertyTypeText),
4464
+ ExpectDiagnosticText.isNotAssignableFrom(sourcePropertyTypeText, targetPropertyTypeText),
4204
4465
  ];
4205
4466
  const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
4206
4467
  diagnostics.push(diagnostic.extendWith(text, origin));
@@ -4212,7 +4473,7 @@ class ToAcceptProps {
4212
4473
  const targetProperty = targetType.getProperty(sourcePropertyName);
4213
4474
  if (!targetProperty && !this.#isOptionalProperty(sourceProperty)) {
4214
4475
  const text = [
4215
- ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
4476
+ ExpectDiagnosticText.isNotAssignableFrom(sourceTypeText, targetTypeText),
4216
4477
  ExpectDiagnosticText.requiresProperty(sourceTypeText, sourcePropertyName),
4217
4478
  ];
4218
4479
  diagnostics.push(diagnostic.extendWith(text));
@@ -4220,7 +4481,7 @@ class ToAcceptProps {
4220
4481
  }
4221
4482
  }
4222
4483
  if (diagnostics.length === 0) {
4223
- const text = ExpectDiagnosticText.isAssignableWith(sourceTypeText, targetTypeText);
4484
+ const text = ExpectDiagnosticText.isAssignableFrom(sourceTypeText, targetTypeText);
4224
4485
  diagnostics.push(diagnostic.extendWith(text));
4225
4486
  return { diagnostics, isMatch: true };
4226
4487
  }
@@ -4229,9 +4490,9 @@ class ToAcceptProps {
4229
4490
  if (sourceType != null && isUnionType(this.#compiler, sourceType)) {
4230
4491
  let accumulator = [];
4231
4492
  const isMatch = sourceType.types.some((sourceType) => {
4232
- const text = matchWorker.assertion.isNot
4233
- ? ExpectDiagnosticText.isAssignableWith(sourceTypeText, targetTypeText)
4234
- : ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText);
4493
+ const text = matchWorker.assertionNode.isNot
4494
+ ? ExpectDiagnosticText.isAssignableFrom(sourceTypeText, targetTypeText)
4495
+ : ExpectDiagnosticText.isNotAssignableFrom(sourceTypeText, targetTypeText);
4235
4496
  const { diagnostics, isMatch } = explain(sourceType, targetType, diagnostic.extendWith(text));
4236
4497
  if (isMatch) {
4237
4498
  accumulator = diagnostics;
@@ -4284,10 +4545,10 @@ class RelationMatcherBase {
4284
4545
  explain(matchWorker, sourceNode, targetNode) {
4285
4546
  const sourceTypeText = matchWorker.getTypeText(sourceNode);
4286
4547
  const targetTypeText = matchWorker.getTypeText(targetNode);
4287
- const text = matchWorker.assertion.isNot
4548
+ const text = matchWorker.assertionNode.isNot
4288
4549
  ? this.explainText(sourceTypeText, targetTypeText)
4289
4550
  : this.explainNotText(sourceTypeText, targetTypeText);
4290
- const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
4551
+ const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertionNode);
4291
4552
  return [Diagnostic.error(text, origin)];
4292
4553
  }
4293
4554
  }
@@ -4334,18 +4595,22 @@ class ToBeApplicable {
4334
4595
  }
4335
4596
  return text;
4336
4597
  }
4337
- #explain(matchWorker, sourceNode) {
4338
- const targetText = this.#resolveTargetText(matchWorker.assertion.matcherNode.parent);
4598
+ #explain(matchWorker) {
4599
+ const targetText = this.#resolveTargetText(matchWorker.assertionNode.matcherNode.parent);
4339
4600
  const diagnostics = [];
4340
- if (matchWorker.assertion.abilityDiagnostics.size > 0) {
4341
- for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
4601
+ if (matchWorker.assertionNode.abilityDiagnostics.size > 0) {
4602
+ const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertionNode);
4603
+ for (const diagnostic of matchWorker.assertionNode.abilityDiagnostics) {
4342
4604
  const text = [ExpectDiagnosticText.cannotBeApplied(targetText), getDiagnosticMessageText(diagnostic)];
4343
- const origin = DiagnosticOrigin.fromNode(sourceNode);
4344
- diagnostics.push(Diagnostic.error(text.flat(), origin));
4605
+ let related;
4606
+ if (diagnostic.relatedInformation != null) {
4607
+ related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
4608
+ }
4609
+ diagnostics.push(Diagnostic.error(text.flat(), origin).add({ related }));
4345
4610
  }
4346
4611
  }
4347
4612
  else {
4348
- const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
4613
+ const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertionNode);
4349
4614
  diagnostics.push(Diagnostic.error(ExpectDiagnosticText.canBeApplied(targetText), origin));
4350
4615
  }
4351
4616
  return diagnostics;
@@ -4362,30 +4627,30 @@ class ToBeApplicable {
4362
4627
  return;
4363
4628
  }
4364
4629
  return {
4365
- explain: () => this.#explain(matchWorker, sourceNode),
4366
- isMatch: matchWorker.assertion.abilityDiagnostics.size === 0,
4630
+ explain: () => this.#explain(matchWorker),
4631
+ isMatch: matchWorker.assertionNode.abilityDiagnostics.size === 0,
4367
4632
  };
4368
4633
  }
4369
4634
  }
4370
4635
 
4371
- class ToBeAssignableTo extends RelationMatcherBase {
4372
- explainText = ExpectDiagnosticText.isAssignableTo;
4373
- explainNotText = ExpectDiagnosticText.isNotAssignableTo;
4636
+ class ToBeAssignableFrom extends RelationMatcherBase {
4637
+ explainText = ExpectDiagnosticText.isAssignableFrom;
4638
+ explainNotText = ExpectDiagnosticText.isNotAssignableFrom;
4374
4639
  match(matchWorker, sourceNode, targetNode) {
4375
4640
  return {
4376
4641
  explain: () => this.explain(matchWorker, sourceNode, targetNode),
4377
- isMatch: matchWorker.checkIsAssignableTo(sourceNode, targetNode),
4642
+ isMatch: matchWorker.checkIsAssignableWith(sourceNode, targetNode),
4378
4643
  };
4379
4644
  }
4380
4645
  }
4381
4646
 
4382
- class ToBeAssignableWith extends RelationMatcherBase {
4383
- explainText = ExpectDiagnosticText.isAssignableWith;
4384
- explainNotText = ExpectDiagnosticText.isNotAssignableWith;
4647
+ class ToBeAssignableTo extends RelationMatcherBase {
4648
+ explainText = ExpectDiagnosticText.isAssignableTo;
4649
+ explainNotText = ExpectDiagnosticText.isNotAssignableTo;
4385
4650
  match(matchWorker, sourceNode, targetNode) {
4386
4651
  return {
4387
4652
  explain: () => this.explain(matchWorker, sourceNode, targetNode),
4388
- isMatch: matchWorker.checkIsAssignableWith(sourceNode, targetNode),
4653
+ isMatch: matchWorker.checkIsAssignableTo(sourceNode, targetNode),
4389
4654
  };
4390
4655
  }
4391
4656
  }
@@ -4408,32 +4673,25 @@ class AbilityMatcherBase {
4408
4673
  const isExpression = nodeBelongsToArgumentList(this.compiler, sourceNode);
4409
4674
  const targetText = this.#resolveTargetText(targetNodes);
4410
4675
  const diagnostics = [];
4411
- if (matchWorker.assertion.abilityDiagnostics.size > 0) {
4412
- for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
4676
+ if (matchWorker.assertionNode.abilityDiagnostics.size > 0) {
4677
+ for (const diagnostic of matchWorker.assertionNode.abilityDiagnostics) {
4678
+ const text = [this.explainNotText(isExpression, targetText), getDiagnosticMessageText(diagnostic)];
4413
4679
  let origin;
4414
- const text = [];
4415
- if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, sourceNode)) {
4416
- origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceNode.getSourceFile(), matchWorker.assertion);
4417
- text.push(getDiagnosticMessageText(diagnostic));
4680
+ if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, targetNodes)) {
4681
+ origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceNode.getSourceFile(), matchWorker.assertionNode);
4418
4682
  }
4419
4683
  else {
4420
- if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, targetNodes)) {
4421
- origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceNode.getSourceFile(), matchWorker.assertion);
4422
- }
4423
- else {
4424
- origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
4425
- }
4426
- text.push(this.explainNotText(isExpression, targetText), getDiagnosticMessageText(diagnostic));
4684
+ origin = DiagnosticOrigin.fromAssertion(matchWorker.assertionNode);
4427
4685
  }
4428
4686
  let related;
4429
4687
  if (diagnostic.relatedInformation != null) {
4430
- related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation, sourceNode.getSourceFile());
4688
+ related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
4431
4689
  }
4432
4690
  diagnostics.push(Diagnostic.error(text.flat(), origin).add({ related }));
4433
4691
  }
4434
4692
  }
4435
4693
  else {
4436
- const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
4694
+ const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertionNode);
4437
4695
  diagnostics.push(Diagnostic.error(this.explainText(isExpression, targetText), origin));
4438
4696
  }
4439
4697
  return diagnostics;
@@ -4473,7 +4731,7 @@ class ToBeCallableWith extends AbilityMatcherBase {
4473
4731
  }
4474
4732
  return {
4475
4733
  explain: () => this.explain(matchWorker, sourceNode, targetNodes),
4476
- isMatch: matchWorker.assertion.abilityDiagnostics.size === 0,
4734
+ isMatch: matchWorker.assertionNode.abilityDiagnostics.size === 0,
4477
4735
  };
4478
4736
  }
4479
4737
  }
@@ -4508,7 +4766,7 @@ class ToBeConstructableWith extends AbilityMatcherBase {
4508
4766
  }
4509
4767
  return {
4510
4768
  explain: () => this.explain(matchWorker, sourceNode, targetNodes),
4511
- isMatch: matchWorker.assertion.abilityDiagnostics.size === 0,
4769
+ isMatch: matchWorker.assertionNode.abilityDiagnostics.size === 0,
4512
4770
  };
4513
4771
  }
4514
4772
  }
@@ -4528,8 +4786,8 @@ class ToHaveProperty {
4528
4786
  else {
4529
4787
  propertyNameText = `[${this.#compiler.unescapeLeadingUnderscores(targetType.symbol.escapedName)}]`;
4530
4788
  }
4531
- const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
4532
- return matchWorker.assertion.isNot
4789
+ const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertionNode);
4790
+ return matchWorker.assertionNode.isNot
4533
4791
  ? [Diagnostic.error(ExpectDiagnosticText.hasProperty(sourceTypeText, propertyNameText), origin)]
4534
4792
  : [Diagnostic.error(ExpectDiagnosticText.doesNotHaveProperty(sourceTypeText, propertyNameText), origin)];
4535
4793
  }
@@ -4579,28 +4837,28 @@ class ToRaiseError {
4579
4837
  }
4580
4838
  #explain(matchWorker, sourceNode, targetNodes) {
4581
4839
  const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
4582
- const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
4583
- if (matchWorker.assertion.diagnostics.size === 0) {
4840
+ const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertionNode);
4841
+ if (matchWorker.assertionNode.diagnostics.size === 0) {
4584
4842
  const text = ExpectDiagnosticText.didNotRaiseError(isExpression);
4585
4843
  return [Diagnostic.error(text, origin)];
4586
4844
  }
4587
- if (matchWorker.assertion.diagnostics.size !== targetNodes.length) {
4588
- const count = matchWorker.assertion.diagnostics.size;
4845
+ if (matchWorker.assertionNode.diagnostics.size !== targetNodes.length) {
4846
+ const count = matchWorker.assertionNode.diagnostics.size;
4589
4847
  const text = ExpectDiagnosticText.raisedError(isExpression, count, targetNodes.length);
4590
4848
  const related = [
4591
4849
  Diagnostic.error(ExpectDiagnosticText.raisedTypeError(count)),
4592
- ...Diagnostic.fromDiagnostics([...matchWorker.assertion.diagnostics]),
4850
+ ...Diagnostic.fromDiagnostics([...matchWorker.assertionNode.diagnostics]),
4593
4851
  ];
4594
4852
  return [Diagnostic.error(text, origin).add({ related })];
4595
4853
  }
4596
- return [...matchWorker.assertion.diagnostics].reduce((accumulator, diagnostic, index) => {
4854
+ return [...matchWorker.assertionNode.diagnostics].reduce((accumulator, diagnostic, index) => {
4597
4855
  const targetNode = targetNodes[index];
4598
4856
  const isMatch = this.#matchExpectedError(diagnostic, targetNode);
4599
- if (matchWorker.assertion.isNot ? isMatch : !isMatch) {
4600
- const text = matchWorker.assertion.isNot
4857
+ if (matchWorker.assertionNode.isNot ? isMatch : !isMatch) {
4858
+ const text = matchWorker.assertionNode.isNot
4601
4859
  ? ExpectDiagnosticText.raisedMatchingError(isExpression)
4602
4860
  : ExpectDiagnosticText.didNotRaiseMatchingError(isExpression);
4603
- const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
4861
+ const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertionNode);
4604
4862
  const related = [
4605
4863
  Diagnostic.error(ExpectDiagnosticText.raisedTypeError()),
4606
4864
  ...Diagnostic.fromDiagnostics([diagnostic]),
@@ -4628,12 +4886,12 @@ class ToRaiseError {
4628
4886
  }
4629
4887
  let isMatch;
4630
4888
  if (targetNodes.length === 0) {
4631
- isMatch = matchWorker.assertion.diagnostics.size > 0;
4889
+ isMatch = matchWorker.assertionNode.diagnostics.size > 0;
4632
4890
  }
4633
4891
  else {
4634
4892
  isMatch =
4635
- matchWorker.assertion.diagnostics.size === targetNodes.length &&
4636
- [...matchWorker.assertion.diagnostics].every((diagnostic, index) => this.#matchExpectedError(diagnostic, targetNodes[index]));
4893
+ matchWorker.assertionNode.diagnostics.size === targetNodes.length &&
4894
+ [...matchWorker.assertionNode.diagnostics].every((diagnostic, index) => this.#matchExpectedError(diagnostic, targetNodes[index]));
4637
4895
  }
4638
4896
  return {
4639
4897
  explain: () => this.#explain(matchWorker, sourceNode, targetNodes),
@@ -4663,8 +4921,8 @@ class ExpectService {
4663
4921
  toAcceptProps;
4664
4922
  toBe;
4665
4923
  toBeApplicable;
4924
+ toBeAssignableFrom;
4666
4925
  toBeAssignableTo;
4667
- toBeAssignableWith;
4668
4926
  toBeCallableWith;
4669
4927
  toBeConstructableWith;
4670
4928
  toHaveProperty;
@@ -4676,54 +4934,54 @@ class ExpectService {
4676
4934
  this.toAcceptProps = new ToAcceptProps(compiler, typeChecker);
4677
4935
  this.toBe = new ToBe();
4678
4936
  this.toBeApplicable = new ToBeApplicable(compiler);
4937
+ this.toBeAssignableFrom = new ToBeAssignableFrom();
4679
4938
  this.toBeAssignableTo = new ToBeAssignableTo();
4680
- this.toBeAssignableWith = new ToBeAssignableWith();
4681
4939
  this.toBeCallableWith = new ToBeCallableWith(compiler);
4682
4940
  this.toBeConstructableWith = new ToBeConstructableWith(compiler);
4683
4941
  this.toHaveProperty = new ToHaveProperty(compiler);
4684
4942
  this.toRaiseError = new ToRaiseError(compiler);
4685
4943
  }
4686
- match(assertion, onDiagnostics) {
4687
- const matcherNameText = assertion.matcherNameNode.name.text;
4688
- if (!argumentOrTypeArgumentIsProvided("source", "Source", assertion.source[0], assertion.node.expression, onDiagnostics)) {
4944
+ match(assertionNode, onDiagnostics) {
4945
+ const matcherNameText = assertionNode.matcherNameNode.name.text;
4946
+ if (!argumentOrTypeArgumentIsProvided("source", "Source", assertionNode.source[0], assertionNode.node.expression, onDiagnostics)) {
4689
4947
  return;
4690
4948
  }
4691
- const matchWorker = new MatchWorker(this.#compiler, this.#typeChecker, assertion);
4692
- if (!(matcherNameText === "toRaiseError" && assertion.isNot === false) &&
4949
+ const matchWorker = new MatchWorker(this.#compiler, this.#typeChecker, assertionNode);
4950
+ if (!(matcherNameText === "toRaiseError" && assertionNode.isNot === false) &&
4693
4951
  this.#reject.argumentType([
4694
- ["source", assertion.source[0]],
4695
- ["target", assertion.target?.[0]],
4952
+ ["source", assertionNode.source[0]],
4953
+ ["target", assertionNode.target?.[0]],
4696
4954
  ], onDiagnostics)) {
4697
4955
  return;
4698
4956
  }
4699
4957
  switch (matcherNameText) {
4700
4958
  case "toAcceptProps":
4701
4959
  case "toBe":
4960
+ case "toBeAssignableFrom":
4702
4961
  case "toBeAssignableTo":
4703
- case "toBeAssignableWith":
4704
- if (!argumentOrTypeArgumentIsProvided("target", "Target", assertion.target?.[0], assertion.matcherNameNode.name, onDiagnostics)) {
4962
+ if (!argumentOrTypeArgumentIsProvided("target", "Target", assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
4705
4963
  return;
4706
4964
  }
4707
- return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
4965
+ return this[matcherNameText].match(matchWorker, assertionNode.source[0], assertionNode.target[0], onDiagnostics);
4708
4966
  case "toBeApplicable":
4709
- return this.toBeApplicable.match(matchWorker, assertion.source[0], onDiagnostics);
4967
+ return this.toBeApplicable.match(matchWorker, assertionNode.source[0], onDiagnostics);
4710
4968
  case "toBeCallableWith":
4711
4969
  case "toBeConstructableWith":
4712
4970
  case "toRaiseError":
4713
- return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target, onDiagnostics);
4971
+ return this[matcherNameText].match(matchWorker, assertionNode.source[0], assertionNode.target, onDiagnostics);
4714
4972
  case "toHaveProperty":
4715
- if (!argumentIsProvided("key", assertion.target?.[0], assertion.matcherNameNode.name, onDiagnostics)) {
4973
+ if (!argumentIsProvided("key", assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
4716
4974
  return;
4717
4975
  }
4718
- return this.toHaveProperty.match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
4976
+ return this.toHaveProperty.match(matchWorker, assertionNode.source[0], assertionNode.target[0], onDiagnostics);
4719
4977
  default:
4720
- this.#onMatcherIsNotSupported(matcherNameText, assertion, onDiagnostics);
4978
+ this.#onMatcherIsNotSupported(matcherNameText, assertionNode, onDiagnostics);
4721
4979
  }
4722
4980
  return;
4723
4981
  }
4724
- #onMatcherIsNotSupported(matcherNameText, assertion, onDiagnostics) {
4982
+ #onMatcherIsNotSupported(matcherNameText, assertionNode, onDiagnostics) {
4725
4983
  const text = ExpectDiagnosticText.matcherIsNotSupported(matcherNameText);
4726
- const origin = DiagnosticOrigin.fromNode(assertion.matcherNameNode.name);
4984
+ const origin = DiagnosticOrigin.fromNode(assertionNode.matcherNameNode.name);
4727
4985
  onDiagnostics(Diagnostic.error(text, origin));
4728
4986
  }
4729
4987
  }
@@ -4844,82 +5102,145 @@ class WhenService {
4844
5102
  }
4845
5103
  }
4846
5104
 
5105
+ class FixmeDiagnosticText {
5106
+ static considerRemoving() {
5107
+ return "Consider removing the '// @tstyche fixme' directive.";
5108
+ }
5109
+ static wasSupposedToFail(target) {
5110
+ return `The '${target}()' was supposed to fail, but it passed.`;
5111
+ }
5112
+ }
5113
+
5114
+ class FixmeService {
5115
+ static #expectRange;
5116
+ static #range;
5117
+ static async start(directive, owner) {
5118
+ const inlineConfig = await Directive.getInlineConfig(directive);
5119
+ if (inlineConfig?.fixme === true) {
5120
+ if (owner.brand === "expect") {
5121
+ FixmeService.#expectRange = { directive, owner, previous: FixmeService.#expectRange };
5122
+ }
5123
+ else {
5124
+ FixmeService.#range = { directive, owner, previous: FixmeService.#range };
5125
+ }
5126
+ }
5127
+ }
5128
+ static isFixme(owner, isPass) {
5129
+ if (owner.brand !== "expect") {
5130
+ return owner === FixmeService.#range?.owner && FixmeService.#range.isFail === true;
5131
+ }
5132
+ if (owner === FixmeService.#expectRange?.owner) {
5133
+ FixmeService.#expectRange.isFail = !isPass;
5134
+ return !isPass;
5135
+ }
5136
+ if (FixmeService.#range != null) {
5137
+ if (!isPass) {
5138
+ FixmeService.#range.isFail = true;
5139
+ return true;
5140
+ }
5141
+ FixmeService.#range.isFail ??= false;
5142
+ }
5143
+ return false;
5144
+ }
5145
+ static end(directive, owner, onFileDiagnostics) {
5146
+ let isFail;
5147
+ if (owner === FixmeService.#expectRange?.owner) {
5148
+ isFail = FixmeService.#expectRange.isFail;
5149
+ FixmeService.#expectRange = FixmeService.#expectRange?.previous;
5150
+ }
5151
+ if (owner === FixmeService.#range?.owner) {
5152
+ isFail = FixmeService.#range?.isFail;
5153
+ FixmeService.#range = FixmeService.#range?.previous;
5154
+ }
5155
+ if (isFail === false) {
5156
+ const targetText = owner.node.expression.getText();
5157
+ const text = [FixmeDiagnosticText.wasSupposedToFail(targetText), FixmeDiagnosticText.considerRemoving()];
5158
+ const origin = new DiagnosticOrigin(directive.namespace.start, directive.directive.end, directive.sourceFile);
5159
+ onFileDiagnostics([Diagnostic.error(text, origin)]);
5160
+ }
5161
+ }
5162
+ }
5163
+
4847
5164
  class TestTreeWalker {
4848
5165
  #cancellationToken;
4849
5166
  #compiler;
4850
5167
  #expectService;
4851
5168
  #hasOnly;
4852
- #onTaskDiagnostics;
5169
+ #onFileDiagnostics;
4853
5170
  #position;
4854
5171
  #resolvedConfig;
4855
5172
  #whenService;
4856
- constructor(compiler, typeChecker, resolvedConfig, onTaskDiagnostics, options) {
5173
+ constructor(compiler, typeChecker, resolvedConfig, onFileDiagnostics, options) {
4857
5174
  this.#compiler = compiler;
4858
5175
  this.#resolvedConfig = resolvedConfig;
4859
- this.#onTaskDiagnostics = onTaskDiagnostics;
5176
+ this.#onFileDiagnostics = onFileDiagnostics;
4860
5177
  this.#cancellationToken = options.cancellationToken;
4861
5178
  this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
4862
5179
  this.#position = options.position;
4863
5180
  const reject = new Reject(compiler, typeChecker, resolvedConfig);
4864
5181
  this.#expectService = new ExpectService(compiler, typeChecker, reject);
4865
- this.#whenService = new WhenService(reject, onTaskDiagnostics);
5182
+ this.#whenService = new WhenService(reject, onFileDiagnostics);
4866
5183
  }
4867
- async #resolveRunMode(mode, node) {
4868
- const directiveRanges = node.getDirectiveRanges(this.#compiler);
4869
- const inlineConfig = await Directive.getInlineConfig(directiveRanges);
5184
+ async #resolveRunMode(flags, node) {
5185
+ const ifDirective = Directive.getDirectiveRange(this.#compiler, node, "if");
5186
+ const inlineConfig = await Directive.getInlineConfig(ifDirective);
4870
5187
  if (inlineConfig?.if?.target != null && !Version.isIncluded(this.#compiler.version, inlineConfig.if.target)) {
4871
- mode |= RunMode.Void;
4872
- }
4873
- if (node.flags & TestTreeNodeFlags.Fail) {
4874
- mode |= RunMode.Fail;
5188
+ flags |= 8;
4875
5189
  }
4876
- if (node.flags & TestTreeNodeFlags.Only ||
5190
+ if (node.flags & 1 ||
4877
5191
  (this.#resolvedConfig.only != null && node.name.toLowerCase().includes(this.#resolvedConfig.only.toLowerCase()))) {
4878
- mode |= RunMode.Only;
5192
+ flags |= 1;
4879
5193
  }
4880
- if (node.flags & TestTreeNodeFlags.Skip ||
5194
+ if (node.flags & 2 ||
4881
5195
  (this.#resolvedConfig.skip != null && node.name.toLowerCase().includes(this.#resolvedConfig.skip.toLowerCase()))) {
4882
- mode |= RunMode.Skip;
5196
+ flags |= 2;
4883
5197
  }
4884
- if (node.flags & TestTreeNodeFlags.Todo) {
4885
- mode |= RunMode.Todo;
5198
+ if (node.flags & 4) {
5199
+ flags |= 4;
4886
5200
  }
4887
5201
  if (this.#position != null && node.node.getStart() === this.#position) {
4888
- mode |= RunMode.Only;
4889
- mode &= ~RunMode.Skip;
5202
+ flags |= 1;
5203
+ flags &= -3;
4890
5204
  }
4891
- return mode;
5205
+ return flags;
4892
5206
  }
4893
- async visit(nodes, runMode, parentResult) {
5207
+ async visit(nodes, runModeFlags, parentResult) {
4894
5208
  for (const node of nodes) {
4895
5209
  if (this.#cancellationToken?.isCancellationRequested) {
4896
5210
  break;
4897
5211
  }
5212
+ const fixmeDirective = Directive.getDirectiveRange(this.#compiler, node, "fixme");
5213
+ if (fixmeDirective) {
5214
+ FixmeService.start(fixmeDirective, node);
5215
+ }
4898
5216
  switch (node.brand) {
4899
- case TestTreeNodeBrand.Describe:
4900
- await this.#visitDescribe(node, runMode, parentResult);
5217
+ case "describe":
5218
+ await this.#visitDescribe(node, runModeFlags, parentResult);
4901
5219
  break;
4902
- case TestTreeNodeBrand.Test:
4903
- await this.#visitTest(node, runMode, parentResult);
5220
+ case "test":
5221
+ await this.#visitTest(node, runModeFlags, parentResult);
4904
5222
  break;
4905
- case TestTreeNodeBrand.Expect:
4906
- await this.#visitAssertion(node, runMode, parentResult);
5223
+ case "expect":
5224
+ await this.#visitExpect(node, runModeFlags, parentResult);
4907
5225
  break;
4908
- case TestTreeNodeBrand.When:
5226
+ case "when":
4909
5227
  this.#visitWhen(node);
4910
5228
  break;
4911
5229
  }
5230
+ if (fixmeDirective) {
5231
+ FixmeService.end(fixmeDirective, node, this.#onFileDiagnostics);
5232
+ }
4912
5233
  }
4913
5234
  }
4914
- async #visitAssertion(assertion, runMode, parentResult) {
4915
- await this.visit(assertion.children, runMode, parentResult);
4916
- runMode = await this.#resolveRunMode(runMode, assertion);
4917
- if (runMode & RunMode.Void) {
5235
+ async #visitExpect(expect, runModeFlags, parentResult) {
5236
+ await this.visit(expect.children, runModeFlags, parentResult);
5237
+ runModeFlags = await this.#resolveRunMode(runModeFlags, expect);
5238
+ if (runModeFlags & 8) {
4918
5239
  return;
4919
5240
  }
4920
- const expectResult = new ExpectResult(assertion, parentResult);
5241
+ const expectResult = new ExpectResult(expect, parentResult);
4921
5242
  EventEmitter.dispatch(["expect:start", { result: expectResult }]);
4922
- if (runMode & RunMode.Skip || (this.#hasOnly && !(runMode & RunMode.Only))) {
5243
+ if (runModeFlags & 2 || (this.#hasOnly && !(runModeFlags & 1))) {
4923
5244
  EventEmitter.dispatch(["expect:skip", { result: expectResult }]);
4924
5245
  return;
4925
5246
  }
@@ -4929,59 +5250,57 @@ class TestTreeWalker {
4929
5250
  { diagnostics: Array.isArray(diagnostics) ? diagnostics : [diagnostics], result: expectResult },
4930
5251
  ]);
4931
5252
  };
4932
- if (assertion.diagnostics.size > 0 && assertion.matcherNameNode.name.text !== "toRaiseError") {
4933
- onExpectDiagnostics(Diagnostic.fromDiagnostics([...assertion.diagnostics]));
5253
+ if (expect.diagnostics.size > 0 && expect.matcherNameNode.name.text !== "toRaiseError") {
5254
+ onExpectDiagnostics(Diagnostic.fromDiagnostics([...expect.diagnostics]));
4934
5255
  return;
4935
5256
  }
4936
- const matchResult = this.#expectService.match(assertion, onExpectDiagnostics);
5257
+ const matchResult = this.#expectService.match(expect, onExpectDiagnostics);
4937
5258
  if (!matchResult) {
4938
5259
  return;
4939
5260
  }
4940
- if (assertion.isNot ? !matchResult.isMatch : matchResult.isMatch) {
4941
- if (runMode & RunMode.Fail) {
4942
- const text = ["The assertion was supposed to fail, but it passed.", "Consider removing the '.fail' flag."];
4943
- const origin = DiagnosticOrigin.fromNode(assertion.node.expression.name);
4944
- onExpectDiagnostics(Diagnostic.error(text, origin));
4945
- }
4946
- else {
4947
- EventEmitter.dispatch(["expect:pass", { result: expectResult }]);
4948
- }
5261
+ const isPass = expect.isNot ? !matchResult.isMatch : matchResult.isMatch;
5262
+ if (FixmeService.isFixme(expect, isPass)) {
5263
+ EventEmitter.dispatch(["expect:fixme", { result: expectResult }]);
5264
+ return;
4949
5265
  }
4950
- else if (runMode & RunMode.Fail) {
5266
+ if (isPass) {
4951
5267
  EventEmitter.dispatch(["expect:pass", { result: expectResult }]);
4952
5268
  }
4953
5269
  else {
4954
5270
  EventEmitter.dispatch(["expect:fail", { diagnostics: matchResult.explain(), result: expectResult }]);
4955
5271
  }
4956
5272
  }
4957
- async #visitDescribe(describe, runMode, parentResult) {
4958
- runMode = await this.#resolveRunMode(runMode, describe);
4959
- if (runMode & RunMode.Void) {
5273
+ async #visitDescribe(describe, runModeFlags, parentResult) {
5274
+ runModeFlags = await this.#resolveRunMode(runModeFlags, describe);
5275
+ if (runModeFlags & 8) {
4960
5276
  return;
4961
5277
  }
4962
5278
  const describeResult = new DescribeResult(describe, parentResult);
4963
5279
  EventEmitter.dispatch(["describe:start", { result: describeResult }]);
4964
- if (!(runMode & RunMode.Skip || (this.#hasOnly && !(runMode & RunMode.Only)) || runMode & RunMode.Todo) &&
5280
+ if (!(runModeFlags & 2 ||
5281
+ (this.#hasOnly && !(runModeFlags & 1)) ||
5282
+ runModeFlags & 4) &&
4965
5283
  describe.diagnostics.size > 0) {
4966
- this.#onTaskDiagnostics(Diagnostic.fromDiagnostics([...describe.diagnostics]));
5284
+ this.#onFileDiagnostics(Diagnostic.fromDiagnostics([...describe.diagnostics]));
4967
5285
  }
4968
5286
  else {
4969
- await this.visit(describe.children, runMode, describeResult);
5287
+ await this.visit(describe.children, runModeFlags, describeResult);
4970
5288
  }
4971
5289
  EventEmitter.dispatch(["describe:end", { result: describeResult }]);
4972
5290
  }
4973
- async #visitTest(test, runMode, parentResult) {
4974
- runMode = await this.#resolveRunMode(runMode, test);
4975
- if (runMode & RunMode.Void) {
5291
+ async #visitTest(test, runModeFlags, parentResult) {
5292
+ runModeFlags = await this.#resolveRunMode(runModeFlags, test);
5293
+ if (runModeFlags & 8) {
4976
5294
  return;
4977
5295
  }
4978
5296
  const testResult = new TestResult(test, parentResult);
4979
5297
  EventEmitter.dispatch(["test:start", { result: testResult }]);
4980
- if (runMode & RunMode.Todo) {
5298
+ if (runModeFlags & 4) {
4981
5299
  EventEmitter.dispatch(["test:todo", { result: testResult }]);
4982
5300
  return;
4983
5301
  }
4984
- if (!(runMode & RunMode.Skip || (this.#hasOnly && !(runMode & RunMode.Only))) && test.diagnostics.size > 0) {
5302
+ if (!(runModeFlags & 2 || (this.#hasOnly && !(runModeFlags & 1))) &&
5303
+ test.diagnostics.size > 0) {
4985
5304
  EventEmitter.dispatch([
4986
5305
  "test:error",
4987
5306
  {
@@ -4991,24 +5310,29 @@ class TestTreeWalker {
4991
5310
  ]);
4992
5311
  return;
4993
5312
  }
4994
- await this.visit(test.children, runMode, testResult);
4995
- if (runMode & RunMode.Skip || (this.#hasOnly && !(runMode & RunMode.Only))) {
5313
+ await this.visit(test.children, runModeFlags, testResult);
5314
+ if (runModeFlags & 2 || (this.#hasOnly && !(runModeFlags & 1))) {
4996
5315
  EventEmitter.dispatch(["test:skip", { result: testResult }]);
4997
5316
  return;
4998
5317
  }
4999
- if (testResult.expectCount.failed > 0) {
5000
- EventEmitter.dispatch(["test:fail", { result: testResult }]);
5318
+ const isPass = testResult.assertionCounts.failed === 0;
5319
+ if (FixmeService.isFixme(test, isPass)) {
5320
+ EventEmitter.dispatch(["test:fixme", { result: testResult }]);
5321
+ return;
5001
5322
  }
5002
- else {
5323
+ if (isPass) {
5003
5324
  EventEmitter.dispatch(["test:pass", { result: testResult }]);
5004
5325
  }
5326
+ else {
5327
+ EventEmitter.dispatch(["test:fail", { result: testResult }]);
5328
+ }
5005
5329
  }
5006
5330
  #visitWhen(when) {
5007
5331
  this.#whenService.action(when);
5008
5332
  }
5009
5333
  }
5010
5334
 
5011
- class TaskRunner {
5335
+ class FileRunner {
5012
5336
  #collectService;
5013
5337
  #compiler;
5014
5338
  #projectService;
@@ -5021,89 +5345,85 @@ class TaskRunner {
5021
5345
  this.#collectService = new CollectService(compiler, this.#projectService, this.#resolvedConfig);
5022
5346
  }
5023
5347
  #onDiagnostics(diagnostics, result) {
5024
- EventEmitter.dispatch(["task:error", { diagnostics, result }]);
5348
+ EventEmitter.dispatch(["file:error", { diagnostics, result }]);
5025
5349
  }
5026
- async run(task, cancellationToken) {
5350
+ async run(file, cancellationToken) {
5027
5351
  if (cancellationToken.isCancellationRequested) {
5028
5352
  return;
5029
5353
  }
5030
- this.#projectService.openFile(task.filePath, undefined, this.#resolvedConfig.rootPath);
5031
- const taskResult = new TaskResult(task);
5032
- EventEmitter.dispatch(["task:start", { result: taskResult }]);
5033
- await this.#run(task, taskResult, cancellationToken);
5034
- EventEmitter.dispatch(["task:end", { result: taskResult }]);
5035
- this.#projectService.closeFile(task.filePath);
5354
+ this.#projectService.openFile(file.path);
5355
+ const fileResult = new FileResult(file);
5356
+ EventEmitter.dispatch(["file:start", { result: fileResult }]);
5357
+ await this.#run(file, fileResult, cancellationToken);
5358
+ EventEmitter.dispatch(["file:end", { result: fileResult }]);
5359
+ this.#projectService.closeFile(file.path);
5036
5360
  }
5037
- async #resolveTaskFacts(task, taskResult, runMode = RunMode.Normal) {
5038
- const languageService = this.#projectService.getLanguageService(task.filePath);
5039
- const syntacticDiagnostics = languageService?.getSyntacticDiagnostics(task.filePath);
5361
+ async #resolveFileFacts(file, fileResult, runModeFlags) {
5362
+ const languageService = this.#projectService.getLanguageService(file.path);
5363
+ const syntacticDiagnostics = languageService?.getSyntacticDiagnostics(file.path);
5040
5364
  if (syntacticDiagnostics != null && syntacticDiagnostics.length > 0) {
5041
- this.#onDiagnostics(Diagnostic.fromDiagnostics(syntacticDiagnostics), taskResult);
5365
+ this.#onDiagnostics(Diagnostic.fromDiagnostics(syntacticDiagnostics), fileResult);
5042
5366
  return;
5043
5367
  }
5044
- const semanticDiagnostics = languageService?.getSemanticDiagnostics(task.filePath);
5368
+ const semanticDiagnostics = languageService?.getSemanticDiagnostics(file.path);
5045
5369
  const program = languageService?.getProgram();
5046
5370
  const typeChecker = program?.getTypeChecker();
5047
- const sourceFile = program?.getSourceFile(task.filePath);
5371
+ const sourceFile = program?.getSourceFile(file.path);
5048
5372
  if (!sourceFile) {
5049
5373
  return;
5050
5374
  }
5051
- const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics);
5052
- const directiveRanges = testTree.getDirectiveRanges(this.#compiler);
5375
+ const directiveRanges = Directive.getDirectiveRanges(this.#compiler, sourceFile);
5053
5376
  const inlineConfig = await Directive.getInlineConfig(directiveRanges);
5054
5377
  if (inlineConfig?.if?.target != null && !Version.isIncluded(this.#compiler.version, inlineConfig.if.target)) {
5055
- runMode |= RunMode.Skip;
5056
- }
5057
- if (testTree.suppressedErrors != null) {
5058
- this.#suppressedService.match(testTree.suppressedErrors, (diagnostics) => {
5059
- this.#onDiagnostics(diagnostics, taskResult);
5060
- });
5378
+ runModeFlags |= 2;
5061
5379
  }
5062
5380
  if (inlineConfig?.template) {
5063
5381
  if (semanticDiagnostics != null && semanticDiagnostics.length > 0) {
5064
- this.#onDiagnostics(Diagnostic.fromDiagnostics(semanticDiagnostics), taskResult);
5382
+ this.#onDiagnostics(Diagnostic.fromDiagnostics(semanticDiagnostics), fileResult);
5065
5383
  return;
5066
5384
  }
5067
- const moduleSpecifier = pathToFileURL(task.filePath).toString();
5385
+ const moduleSpecifier = pathToFileURL(file.path).toString();
5068
5386
  const testText = (await import(moduleSpecifier))?.default;
5069
5387
  if (typeof testText !== "string") {
5070
- this.#onDiagnostics([Diagnostic.error("A template test file must export a string.")], taskResult);
5388
+ this.#onDiagnostics([Diagnostic.error("A template test file must export a string.")], fileResult);
5071
5389
  return;
5072
5390
  }
5073
- this.#projectService.openFile(task.filePath, testText, this.#resolvedConfig.rootPath);
5074
- return this.#resolveTaskFacts(task, taskResult, runMode);
5391
+ this.#projectService.openFile(file.path, testText);
5392
+ return this.#resolveFileFacts(file, fileResult, runModeFlags);
5075
5393
  }
5076
- return { runMode, testTree, typeChecker };
5394
+ const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics);
5395
+ this.#suppressedService.match(testTree);
5396
+ return { runModeFlags, testTree, typeChecker };
5077
5397
  }
5078
- async #run(task, taskResult, cancellationToken) {
5079
- if (!existsSync(task.filePath)) {
5080
- this.#onDiagnostics([Diagnostic.error(`Test file '${task.filePath}' does not exist.`)], taskResult);
5398
+ async #run(file, fileResult, cancellationToken) {
5399
+ if (!existsSync(file.path)) {
5400
+ this.#onDiagnostics([Diagnostic.error(`Test file '${file.path}' does not exist.`)], fileResult);
5081
5401
  return;
5082
5402
  }
5083
- const facts = await this.#resolveTaskFacts(task, taskResult);
5403
+ const facts = await this.#resolveFileFacts(file, fileResult, 0);
5084
5404
  if (!facts) {
5085
5405
  return;
5086
5406
  }
5087
5407
  if (facts.testTree.diagnostics.size > 0) {
5088
- this.#onDiagnostics(Diagnostic.fromDiagnostics([...facts.testTree.diagnostics]), taskResult);
5408
+ this.#onDiagnostics(Diagnostic.fromDiagnostics([...facts.testTree.diagnostics]), fileResult);
5089
5409
  return;
5090
5410
  }
5091
- const onTaskDiagnostics = (diagnostics) => {
5092
- this.#onDiagnostics(diagnostics, taskResult);
5411
+ const onFileDiagnostics = (diagnostics) => {
5412
+ this.#onDiagnostics(diagnostics, fileResult);
5093
5413
  };
5094
- const testTreeWalker = new TestTreeWalker(this.#compiler, facts.typeChecker, this.#resolvedConfig, onTaskDiagnostics, {
5414
+ const testTreeWalker = new TestTreeWalker(this.#compiler, facts.typeChecker, this.#resolvedConfig, onFileDiagnostics, {
5095
5415
  cancellationToken,
5096
5416
  hasOnly: facts.testTree.hasOnly,
5097
- position: task.position,
5417
+ position: file.position,
5098
5418
  });
5099
- await testTreeWalker.visit(facts.testTree.children, facts.runMode, undefined);
5419
+ await testTreeWalker.visit(facts.testTree.children, facts.runModeFlags, undefined);
5100
5420
  }
5101
5421
  }
5102
5422
 
5103
5423
  class Runner {
5104
5424
  #eventEmitter = new EventEmitter();
5105
5425
  #resolvedConfig;
5106
- static version = "4.3.0";
5426
+ static version = "5.0.0-beta.1";
5107
5427
  constructor(resolvedConfig) {
5108
5428
  this.#resolvedConfig = resolvedConfig;
5109
5429
  }
@@ -5111,7 +5431,7 @@ class Runner {
5111
5431
  const resultHandler = new ResultHandler();
5112
5432
  this.#eventEmitter.addHandler(resultHandler);
5113
5433
  if (this.#resolvedConfig.failFast) {
5114
- const cancellationHandler = new CancellationHandler(cancellationToken, CancellationReason.FailFast);
5434
+ const cancellationHandler = new CancellationHandler(cancellationToken, "failFast");
5115
5435
  this.#eventEmitter.addHandler(cancellationHandler);
5116
5436
  }
5117
5437
  }
@@ -5140,39 +5460,39 @@ class Runner {
5140
5460
  }
5141
5461
  }
5142
5462
  }
5143
- async run(testFiles, cancellationToken = new CancellationToken()) {
5144
- const tasks = testFiles.map((testFile) => (testFile instanceof Task ? testFile : new Task(testFile)));
5463
+ async run(files, cancellationToken = new CancellationToken()) {
5464
+ const fileLocations = files.map((file) => (file instanceof FileLocation ? file : new FileLocation(file)));
5145
5465
  this.#addHandlers(cancellationToken);
5146
5466
  await this.#addReporters();
5147
- await this.#run(tasks, cancellationToken);
5467
+ await this.#run(fileLocations, cancellationToken);
5148
5468
  if (this.#resolvedConfig.watch) {
5149
- await this.#watch(tasks, cancellationToken);
5469
+ await this.#watch(fileLocations, cancellationToken);
5150
5470
  }
5151
5471
  this.#eventEmitter.removeReporters();
5152
5472
  this.#eventEmitter.removeHandlers();
5153
5473
  }
5154
- async #run(tasks, cancellationToken) {
5155
- const result = new Result(tasks);
5474
+ async #run(files, cancellationToken) {
5475
+ const result = new Result(files);
5156
5476
  EventEmitter.dispatch(["run:start", { result }]);
5157
5477
  for (const target of this.#resolvedConfig.target) {
5158
- const targetResult = new TargetResult(target, tasks);
5478
+ const targetResult = new TargetResult(target, files);
5159
5479
  EventEmitter.dispatch(["target:start", { result: targetResult }]);
5160
5480
  const compiler = await Store.load(target);
5161
5481
  if (compiler) {
5162
- const taskRunner = new TaskRunner(compiler, this.#resolvedConfig);
5163
- for (const task of tasks) {
5164
- await taskRunner.run(task, cancellationToken);
5482
+ const fileRunner = new FileRunner(compiler, this.#resolvedConfig);
5483
+ for (const file of files) {
5484
+ await fileRunner.run(file, cancellationToken);
5165
5485
  }
5166
5486
  }
5167
5487
  EventEmitter.dispatch(["target:end", { result: targetResult }]);
5168
5488
  }
5169
5489
  EventEmitter.dispatch(["run:end", { result }]);
5170
- if (cancellationToken.reason === CancellationReason.FailFast) {
5490
+ if (cancellationToken.reason === "failFast") {
5171
5491
  cancellationToken.reset();
5172
5492
  }
5173
5493
  }
5174
- async #watch(testFiles, cancellationToken) {
5175
- const watchService = new WatchService(this.#resolvedConfig, testFiles);
5494
+ async #watch(files, cancellationToken) {
5495
+ const watchService = new WatchService(this.#resolvedConfig, files);
5176
5496
  for await (const testFiles of watchService.watch(cancellationToken)) {
5177
5497
  await this.#run(testFiles, cancellationToken);
5178
5498
  }
@@ -5182,14 +5502,14 @@ class Runner {
5182
5502
  class Cli {
5183
5503
  #eventEmitter = new EventEmitter();
5184
5504
  async run(commandLine, cancellationToken = new CancellationToken()) {
5185
- const cancellationHandler = new CancellationHandler(cancellationToken, CancellationReason.ConfigError);
5505
+ const cancellationHandler = new CancellationHandler(cancellationToken, "configError");
5186
5506
  this.#eventEmitter.addHandler(cancellationHandler);
5187
5507
  const exitCodeHandler = new ExitCodeHandler();
5188
5508
  this.#eventEmitter.addHandler(exitCodeHandler);
5189
5509
  const setupReporter = new SetupReporter();
5190
5510
  this.#eventEmitter.addReporter(setupReporter);
5191
5511
  if (commandLine.includes("--help")) {
5192
- const options = Options.for(OptionGroup.CommandLine);
5512
+ const options = Options.for(2);
5193
5513
  OutputService.writeMessage(helpText(options, Runner.version));
5194
5514
  return;
5195
5515
  }
@@ -5217,7 +5537,7 @@ class Cli {
5217
5537
  return;
5218
5538
  }
5219
5539
  do {
5220
- if (cancellationToken.reason === CancellationReason.ConfigChange) {
5540
+ if (cancellationToken.reason === "configChange") {
5221
5541
  cancellationToken.reset();
5222
5542
  exitCodeHandler.resetCode();
5223
5543
  OutputService.clearTerminal();
@@ -5272,7 +5592,7 @@ class Cli {
5272
5592
  const runner = new Runner(resolvedConfig);
5273
5593
  await runner.run(testFiles, cancellationToken);
5274
5594
  PluginService.removeHandlers();
5275
- } while (cancellationToken.reason === CancellationReason.ConfigChange);
5595
+ } while (cancellationToken.reason === "configChange");
5276
5596
  this.#eventEmitter.removeHandlers();
5277
5597
  }
5278
5598
  #waitForChangedFiles(resolvedConfig, cancellationToken) {
@@ -5281,7 +5601,7 @@ class Cli {
5281
5601
  cancellationToken.reset();
5282
5602
  OutputService.writeMessage(waitingForFileChangesText());
5283
5603
  const onChanged = () => {
5284
- cancellationToken.cancel(CancellationReason.ConfigChange);
5604
+ cancellationToken.cancel("configChange");
5285
5605
  for (const watcher of watchers) {
5286
5606
  watcher.close();
5287
5607
  }
@@ -5303,4 +5623,4 @@ class Cli {
5303
5623
  }
5304
5624
  }
5305
5625
 
5306
- export { AssertionNode, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Directive, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, 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, Store, SummaryReporter, SuppressedService, TargetResult, Task, TaskResult, TestResult, TestTree, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, WhenNode, WhenService, addsPackageText, argumentIsProvided, argumentOrTypeArgumentIsProvided, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, environmentOptions, fileViewText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, nodeBelongsToArgumentList, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
5626
+ 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 };