tstyche 7.0.0 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.js CHANGED
@@ -77,46 +77,6 @@ class JsonNode {
77
77
  }
78
78
  }
79
79
 
80
- class SourceService {
81
- static #files = new Map();
82
- static delete(filePath) {
83
- SourceService.#files.delete(filePath);
84
- }
85
- static get(source) {
86
- const file = SourceService.#files.get(source.fileName);
87
- if (file != null) {
88
- return file;
89
- }
90
- return source;
91
- }
92
- static set(source) {
93
- SourceService.#files.set(source.fileName, source);
94
- }
95
- }
96
-
97
- class DiagnosticOrigin {
98
- assertionNode;
99
- end;
100
- sourceFile;
101
- start;
102
- constructor(start, end, sourceFile, assertionNode) {
103
- this.start = start;
104
- this.end = end;
105
- this.sourceFile = SourceService.get(sourceFile);
106
- this.assertionNode = assertionNode;
107
- }
108
- static fromAssertion(assertionNode) {
109
- const node = assertionNode.matcherNameNode.name;
110
- return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertionNode);
111
- }
112
- static fromNode(node, assertionNode) {
113
- return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertionNode);
114
- }
115
- static fromNodes(nodes, assertionNode) {
116
- return new DiagnosticOrigin(nodes.pos, nodes.end, nodes[0].getSourceFile(), assertionNode);
117
- }
118
- }
119
-
120
80
  function diagnosticBelongsToNode(diagnostic, node) {
121
81
  return diagnostic.start != null && diagnostic.start >= node.pos && diagnostic.start <= node.end;
122
82
  }
@@ -134,6 +94,16 @@ function getDiagnosticMessageText(diagnostic) {
134
94
  ? diagnostic.messageText
135
95
  : diagnosticMessageChainToText(diagnostic.messageText);
136
96
  }
97
+ function getOffset(position, offsets) {
98
+ let diff = 0;
99
+ for (const offset of offsets) {
100
+ if (offset.position > position - diff) {
101
+ break;
102
+ }
103
+ diff += offset.diff;
104
+ }
105
+ return diff;
106
+ }
137
107
  function getTextSpanEnd(span) {
138
108
  return span.start + span.length;
139
109
  }
@@ -141,6 +111,29 @@ function isDiagnosticWithLocation(diagnostic) {
141
111
  return diagnostic.file != null && diagnostic.start != null && diagnostic.length != null;
142
112
  }
143
113
 
114
+ class DiagnosticOrigin {
115
+ assertionNode;
116
+ end;
117
+ sourceFile;
118
+ start;
119
+ constructor(start, end, sourceFile, assertionNode) {
120
+ this.start = start;
121
+ this.end = end;
122
+ this.sourceFile = sourceFile;
123
+ this.assertionNode = assertionNode;
124
+ }
125
+ static fromAbilityDiagnostic(diagnostic, assertionNode) {
126
+ return new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), diagnostic.file, assertionNode);
127
+ }
128
+ static fromAssertion(assertionNode) {
129
+ const node = assertionNode.matcherNameNode.name;
130
+ return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertionNode);
131
+ }
132
+ static fromNode(node, assertionNode) {
133
+ return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertionNode);
134
+ }
135
+ }
136
+
144
137
  class Diagnostic {
145
138
  category;
146
139
  code;
@@ -193,6 +186,37 @@ var DiagnosticCategory;
193
186
  DiagnosticCategory["Warning"] = "warning";
194
187
  })(DiagnosticCategory || (DiagnosticCategory = {}));
195
188
 
189
+ class MappedDiagnostic {
190
+ category;
191
+ code;
192
+ file;
193
+ length;
194
+ messageText;
195
+ relatedInformation;
196
+ start;
197
+ constructor(sourceFile, diagnostic, offsets = []) {
198
+ this.file = sourceFile;
199
+ if (diagnostic.start != null) {
200
+ this.start = diagnostic.start - getOffset(diagnostic.start, offsets);
201
+ }
202
+ this.category = diagnostic.category;
203
+ this.code = diagnostic.code;
204
+ this.length = diagnostic.length;
205
+ this.messageText = diagnostic.messageText;
206
+ if ("relatedInformation" in diagnostic && Array.isArray(diagnostic.relatedInformation)) {
207
+ this.relatedInformation = [];
208
+ for (const related of diagnostic.relatedInformation) {
209
+ if (related.file?.fileName === sourceFile.fileName) {
210
+ this.relatedInformation.push(new MappedDiagnostic(sourceFile, related, offsets));
211
+ }
212
+ else {
213
+ this.relatedInformation.push(related);
214
+ }
215
+ }
216
+ }
217
+ }
218
+ }
219
+
196
220
  class JsonScanner {
197
221
  #end;
198
222
  #position;
@@ -310,15 +334,20 @@ class JsonSourceFile {
310
334
  const result = [0];
311
335
  let position = 0;
312
336
  while (position < this.text.length) {
313
- if (this.text.charAt(position - 1) === "\r") {
314
- position++;
315
- }
316
- if (this.text.charAt(position - 1) === "\n") {
317
- result.push(position);
337
+ const character = this.text.charAt(position);
338
+ switch (character) {
339
+ case "\n":
340
+ result.push(position + 1);
341
+ break;
342
+ case "\r":
343
+ if (this.text.charAt(position + 1) === "\n") {
344
+ result.push(position + 2);
345
+ position++;
346
+ }
347
+ break;
318
348
  }
319
349
  position++;
320
350
  }
321
- result.push(position);
322
351
  return result;
323
352
  }
324
353
  getLineStarts() {
@@ -420,15 +449,31 @@ class ConfigDiagnosticText {
420
449
  class Environment {
421
450
  static resolve() {
422
451
  return {
452
+ fetchRetries: Environment.#resolveFetchRetries(),
453
+ fetchTimeout: Environment.#resolveFetchTimeout(),
423
454
  isCi: Environment.#resolveIsCi(),
424
455
  noColor: Environment.#resolveNoColor(),
425
456
  noInteractive: Environment.#resolveNoInteractive(),
426
457
  npmRegistry: Environment.#resolveNpmRegistry(),
427
458
  storePath: Environment.#resolveStorePath(),
428
- timeout: Environment.#resolveTimeout(),
429
459
  typescriptModule: Environment.#resolveTypeScriptModule(),
430
460
  };
431
461
  }
462
+ static #resolveFetchRetries() {
463
+ if (process.env["TSTYCHE_FETCH_RETRIES"] != null) {
464
+ return Number(process.env["TSTYCHE_FETCH_RETRIES"]);
465
+ }
466
+ return 2;
467
+ }
468
+ static #resolveFetchTimeout() {
469
+ if (process.env["TSTYCHE_FETCH_TIMEOUT"] != null) {
470
+ return Number.parseFloat(process.env["TSTYCHE_FETCH_TIMEOUT"]);
471
+ }
472
+ if (process.env["TSTYCHE_TIMEOUT"] != null) {
473
+ return Number.parseFloat(process.env["TSTYCHE_TIMEOUT"]);
474
+ }
475
+ return 30;
476
+ }
432
477
  static #resolveIsCi() {
433
478
  if (process.env["CI"] != null) {
434
479
  return process.env["CI"] !== "";
@@ -471,12 +516,6 @@ class Environment {
471
516
  }
472
517
  return Path.resolve(os.homedir(), ".local", "share", "TSTyche");
473
518
  }
474
- static #resolveTimeout() {
475
- if (process.env["TSTYCHE_TIMEOUT"] != null) {
476
- return Number.parseFloat(process.env["TSTYCHE_TIMEOUT"]);
477
- }
478
- return 30;
479
- }
480
519
  static #resolveTypeScriptModule() {
481
520
  let specifier = "typescript";
482
521
  if (process.env["TSTYCHE_TYPESCRIPT_MODULE"] != null) {
@@ -525,6 +564,10 @@ class Version {
525
564
  }
526
565
  }
527
566
 
567
+ function sleep(delay) {
568
+ return new Promise((resolve) => setTimeout(resolve, delay));
569
+ }
570
+
528
571
  class StoreDiagnosticText {
529
572
  static cannotAddTypeScriptPackage(tag) {
530
573
  return `Cannot add the 'typescript' package for the '${tag}' tag.`;
@@ -538,13 +581,13 @@ class StoreDiagnosticText {
538
581
  static failedToUpdateMetadata(registry) {
539
582
  return `Failed to update metadata of the 'typescript' package from '${registry}'.`;
540
583
  }
541
- static maybeNetworkConnectionIssue() {
542
- return "Might be there is an issue with the registry or the network connection.";
543
- }
544
584
  static maybeOutdatedResolution(tag) {
545
585
  return `The resolution of the '${tag}' tag may be outdated.`;
546
586
  }
547
- static requestFailedWithStatusCode(code) {
587
+ static networkFailure(retries) {
588
+ return `The network connection failed after ${retries + 1} attempts.`;
589
+ }
590
+ static requestFailed(code) {
548
591
  return `The request failed with status code ${code}.`;
549
592
  }
550
593
  static requestTimeoutWasExceeded(timeout) {
@@ -557,29 +600,48 @@ class StoreDiagnosticText {
557
600
 
558
601
  class Fetcher {
559
602
  #onDiagnostics;
603
+ #retries;
560
604
  #timeout;
561
- constructor(onDiagnostics, timeout) {
605
+ constructor(onDiagnostics, retries, timeout) {
562
606
  this.#onDiagnostics = onDiagnostics;
607
+ this.#retries = retries;
563
608
  this.#timeout = timeout;
564
609
  }
565
610
  async get(request, diagnostic, options) {
566
- try {
567
- const response = await fetch(request, { signal: AbortSignal.timeout(this.#timeout) });
568
- if (!response.ok) {
569
- !options?.suppressErrors &&
570
- this.#onDiagnostics(diagnostic.extendWith(StoreDiagnosticText.requestFailedWithStatusCode(response.status)));
571
- return;
611
+ let delay = 3000;
612
+ request.headers.set("User-Agent", `tstyche/${"7.2.0"} ${process.platform} ${process.arch}`);
613
+ for (let attempt = 0; attempt <= this.#retries; attempt++) {
614
+ const isLastAttempt = attempt === this.#retries;
615
+ const suppressErrors = options?.suppressErrors || !isLastAttempt;
616
+ try {
617
+ const response = await fetch(request, { signal: AbortSignal.timeout(this.#timeout) });
618
+ if (response.ok) {
619
+ return response;
620
+ }
621
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
622
+ if (options?.suppressErrors) {
623
+ return;
624
+ }
625
+ this.#onDiagnostics(diagnostic().extendWith(StoreDiagnosticText.requestFailed(response.status)));
626
+ return;
627
+ }
628
+ if (!suppressErrors) {
629
+ this.#onDiagnostics(diagnostic().extendWith(StoreDiagnosticText.requestFailed(response.status)));
630
+ }
572
631
  }
573
- return response;
574
- }
575
- catch (error) {
576
- if (error instanceof Error && error.name === "TimeoutError") {
577
- !options?.suppressErrors &&
578
- this.#onDiagnostics(diagnostic.extendWith(StoreDiagnosticText.requestTimeoutWasExceeded(this.#timeout)));
632
+ catch (error) {
633
+ if (!suppressErrors) {
634
+ if (error instanceof Error && error.name === "TimeoutError") {
635
+ this.#onDiagnostics(diagnostic().extendWith(StoreDiagnosticText.requestTimeoutWasExceeded(this.#timeout)));
636
+ }
637
+ else {
638
+ this.#onDiagnostics(diagnostic().extendWith(StoreDiagnosticText.networkFailure(this.#retries)));
639
+ }
640
+ }
579
641
  }
580
- else {
581
- !options?.suppressErrors &&
582
- this.#onDiagnostics(diagnostic.extendWith(StoreDiagnosticText.maybeNetworkConnectionIssue()));
642
+ if (!isLastAttempt) {
643
+ await sleep(delay);
644
+ delay *= 2;
583
645
  }
584
646
  }
585
647
  return;
@@ -623,17 +685,14 @@ class LockService {
623
685
  const waitStartTime = Date.now();
624
686
  while (isLocked) {
625
687
  if (Date.now() - waitStartTime > this.#timeout) {
626
- this.#onDiagnostics(diagnostic.extendWith(StoreDiagnosticText.lockWaitTimeoutWasExceeded(this.#timeout)));
688
+ this.#onDiagnostics(diagnostic().extendWith(StoreDiagnosticText.lockWaitTimeoutWasExceeded(this.#timeout)));
627
689
  break;
628
690
  }
629
- await this.#sleep(1000);
691
+ await sleep(1000);
630
692
  isLocked = existsSync(lockFilePath);
631
693
  }
632
694
  return isLocked;
633
695
  }
634
- #sleep(delay) {
635
- return new Promise((resolve) => setTimeout(resolve, delay));
636
- }
637
696
  }
638
697
 
639
698
  class Manifest {
@@ -714,7 +773,7 @@ class ManifestService {
714
773
  return manifest;
715
774
  }
716
775
  async #load(options) {
717
- const diagnostic = Diagnostic.error(StoreDiagnosticText.failedToFetchMetadata(this.#npmRegistry));
776
+ const diagnostic = () => Diagnostic.error(StoreDiagnosticText.failedToFetchMetadata(this.#npmRegistry));
718
777
  const request = new Request(new URL("typescript", this.#npmRegistry), {
719
778
  headers: {
720
779
  ["Accept"]: "application/vnd.npm.install-v1+json;q=1.0, application/json;q=0.8, */*",
@@ -776,9 +835,7 @@ class ManifestService {
776
835
  return manifest;
777
836
  }
778
837
  async #persist(manifest) {
779
- if (!existsSync(this.#storePath)) {
780
- await fs.mkdir(this.#storePath, { recursive: true });
781
- }
838
+ await fs.mkdir(this.#storePath, { recursive: true });
782
839
  await fs.writeFile(this.#manifestFilePath, manifest.stringify());
783
840
  }
784
841
  async prune() {
@@ -851,29 +908,9 @@ class PackageService {
851
908
  this.#fetcher = fetcher;
852
909
  this.#lockService = lockService;
853
910
  }
854
- async #add(packagePath, resource, diagnostic) {
855
- const request = new Request(resource.tarball, { integrity: resource.integrity });
856
- const response = await this.#fetcher.get(request, diagnostic);
857
- if (response?.body != null) {
858
- const targetPath = `${packagePath}-${Math.random().toString(32).slice(2)}`;
859
- const stream = response.body.pipeThrough(new DecompressionStream("gzip"));
860
- const tarReader = new TarReader(stream);
861
- for await (const file of tarReader.extract()) {
862
- const filePath = Path.join(targetPath, file.name.replace(/^package\//, ""));
863
- const directoryPath = Path.dirname(filePath);
864
- if (!existsSync(directoryPath)) {
865
- await fs.mkdir(directoryPath, { recursive: true });
866
- }
867
- await fs.writeFile(filePath, file.content);
868
- }
869
- await fs.rename(targetPath, packagePath);
870
- return packagePath;
871
- }
872
- return;
873
- }
874
911
  async ensure(packageVersion, manifest) {
875
- let packagePath = Path.join(this.#storePath, `typescript@${packageVersion}`);
876
- const diagnostic = Diagnostic.error(StoreDiagnosticText.failedToFetchPackage(packageVersion));
912
+ const packagePath = Path.join(this.#storePath, `typescript@${packageVersion}`);
913
+ const diagnostic = () => Diagnostic.error(StoreDiagnosticText.failedToFetchPackage(packageVersion));
877
914
  if (await this.#lockService.isLocked(packagePath, diagnostic)) {
878
915
  return;
879
916
  }
@@ -881,18 +918,29 @@ class PackageService {
881
918
  return packagePath;
882
919
  }
883
920
  EventEmitter.dispatch(["store:adds", { packagePath, packageVersion }]);
884
- const resource = manifest?.packages[packageVersion];
885
- if (resource != null) {
886
- const lock = this.#lockService.getLock(packagePath);
887
- try {
888
- packagePath = await this.#add(packagePath, resource, diagnostic);
921
+ const resource = manifest.packages[packageVersion];
922
+ const lock = this.#lockService.getLock(packagePath);
923
+ try {
924
+ const request = new Request(resource.tarball, { integrity: resource.integrity });
925
+ const response = await this.#fetcher.get(request, diagnostic);
926
+ if (!response?.body) {
927
+ return;
889
928
  }
890
- finally {
891
- lock.release();
929
+ const targetPath = await fs.mkdtemp(`${packagePath}-`);
930
+ const stream = response.body.pipeThrough(new DecompressionStream("gzip"));
931
+ const tarReader = new TarReader(stream);
932
+ for await (const file of tarReader.extract()) {
933
+ const filePath = Path.join(targetPath, file.name.replace(/^package\//, ""));
934
+ const directoryPath = Path.dirname(filePath);
935
+ await fs.mkdir(directoryPath, { recursive: true });
936
+ await fs.writeFile(filePath, file.content);
892
937
  }
938
+ await fs.rename(targetPath, packagePath);
893
939
  return packagePath;
894
940
  }
895
- return;
941
+ finally {
942
+ lock.release();
943
+ }
896
944
  }
897
945
  }
898
946
 
@@ -903,26 +951,30 @@ class Store {
903
951
  static #manifestService;
904
952
  static #packageService;
905
953
  static #npmRegistry = environmentOptions.npmRegistry;
954
+ static #fetchRetries = environmentOptions.fetchRetries;
955
+ static #fetchTimeout = environmentOptions.fetchTimeout * 1000;
906
956
  static #storePath = environmentOptions.storePath;
907
957
  static #supportedTags;
908
- static #timeout = environmentOptions.timeout * 1000;
909
958
  static {
910
- Store.#fetcher = new Fetcher(Store.#onDiagnostics, Store.#timeout);
911
- Store.#lockService = new LockService(Store.#onDiagnostics, Store.#timeout);
959
+ Store.#fetcher = new Fetcher(Store.#onDiagnostics, Store.#fetchRetries, Store.#fetchTimeout);
960
+ Store.#lockService = new LockService(Store.#onDiagnostics, Store.#fetchTimeout);
912
961
  Store.#packageService = new PackageService(Store.#storePath, Store.#fetcher, Store.#lockService);
913
962
  Store.#manifestService = new ManifestService(Store.#storePath, Store.#npmRegistry, Store.#fetcher);
914
963
  }
915
- static async fetch(tag) {
916
- if (tag === "*" && environmentOptions.typescriptModule != null) {
917
- return;
918
- }
964
+ static async #ensure(tag) {
919
965
  await Store.open();
920
966
  const version = Store.manifest?.resolve(tag);
921
967
  if (!version) {
922
968
  Store.#onDiagnostics(Diagnostic.error(StoreDiagnosticText.cannotAddTypeScriptPackage(tag)));
923
969
  return;
924
970
  }
925
- await Store.#packageService.ensure(version, Store.manifest);
971
+ return await Store.#packageService.ensure(version, Store.manifest);
972
+ }
973
+ static async fetch(tag) {
974
+ if (tag === "*" && environmentOptions.typescriptModule != null) {
975
+ return;
976
+ }
977
+ await Store.#ensure(tag);
926
978
  }
927
979
  static async load(tag) {
928
980
  let resolvedModule;
@@ -930,13 +982,7 @@ class Store {
930
982
  resolvedModule = environmentOptions.typescriptModule;
931
983
  }
932
984
  else {
933
- await Store.open();
934
- const version = Store.manifest?.resolve(tag);
935
- if (!version) {
936
- Store.#onDiagnostics(Diagnostic.error(StoreDiagnosticText.cannotAddTypeScriptPackage(tag)));
937
- return;
938
- }
939
- const packagePath = await Store.#packageService.ensure(version, Store.manifest);
985
+ const packagePath = await Store.#ensure(tag);
940
986
  if (packagePath != null) {
941
987
  resolvedModule = pathToFileURL(`${packagePath}/lib/typescript.js`).toString();
942
988
  }
@@ -950,7 +996,9 @@ class Store {
950
996
  EventEmitter.dispatch(["store:error", { diagnostics: [diagnostic] }]);
951
997
  }
952
998
  static async open() {
953
- Store.open = () => Promise.resolve();
999
+ if (Store.manifest != null) {
1000
+ return;
1001
+ }
954
1002
  Store.manifest = await Store.#manifestService.open();
955
1003
  if (Store.manifest != null) {
956
1004
  Store.#supportedTags = [...Object.keys(Store.manifest.resolutions), ...Store.manifest.versions];
@@ -1040,7 +1088,7 @@ class Options {
1040
1088
  },
1041
1089
  {
1042
1090
  brand: "boolean",
1043
- description: "Check errors silenced by '@ts-expect-error' directives.",
1091
+ description: "Check errors suppressed by '@ts-expect-error' directives.",
1044
1092
  group: 4,
1045
1093
  name: "checkSuppressedErrors",
1046
1094
  },
@@ -1092,13 +1140,13 @@ class Options {
1092
1140
  },
1093
1141
  {
1094
1142
  brand: "string",
1095
- description: "Only run tests with matching name.",
1143
+ description: "Only run tests with a matching name.",
1096
1144
  group: 2,
1097
1145
  name: "only",
1098
1146
  },
1099
1147
  {
1100
1148
  brand: "true",
1101
- description: "Remove all installed versions of the 'typescript' package and exit.",
1149
+ description: "Remove all fetched versions of the 'typescript' package and exit.",
1102
1150
  group: 2,
1103
1151
  name: "prune",
1104
1152
  },
@@ -1132,7 +1180,7 @@ class Options {
1132
1180
  },
1133
1181
  {
1134
1182
  brand: "string",
1135
- description: "The path to a directory containing files of a test project.",
1183
+ description: "The path to the root directory of a test project.",
1136
1184
  group: 2,
1137
1185
  name: "root",
1138
1186
  },
@@ -1144,13 +1192,13 @@ class Options {
1144
1192
  },
1145
1193
  {
1146
1194
  brand: "string",
1147
- description: "Skip tests with matching name.",
1195
+ description: "Skip tests with a matching name.",
1148
1196
  group: 2,
1149
1197
  name: "skip",
1150
1198
  },
1151
1199
  {
1152
1200
  brand: "range",
1153
- description: "The range of TypeScript versions to test against.",
1201
+ description: "The TypeScript version or range of versions to test against.",
1154
1202
  group: 2 | 4 | 8,
1155
1203
  name: "target",
1156
1204
  },
@@ -2270,8 +2318,8 @@ function CodeFrameText({ diagnosticCategory, diagnosticOrigin, options }) {
2270
2318
  const linesBelow = options?.linesBelow ?? 3;
2271
2319
  const showBreadcrumbs = options?.showBreadcrumbs ?? true;
2272
2320
  const lineMap = diagnosticOrigin.sourceFile.getLineStarts();
2273
- const { character: firstMarkedLineCharacter, line: firstMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.start);
2274
- const { character: lastMarkedLineCharacter, line: lastMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.end);
2321
+ const { character: firstMarkedCharacter, line: firstMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.start);
2322
+ const { character: lastMarkedCharacter, line: lastMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.end);
2275
2323
  const firstLine = Math.max(firstMarkedLine - linesAbove, 0);
2276
2324
  const lastLine = Math.min(lastMarkedLine + linesBelow, lineMap.length - 1);
2277
2325
  const gutterWidth = (lastLine + 1).toString().length + 2;
@@ -2293,12 +2341,12 @@ function CodeFrameText({ diagnosticCategory, diagnosticOrigin, options }) {
2293
2341
  codeFrame.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumber: index + 1, lineNumberColor: highlightColor, lineText: lineText }));
2294
2342
  if (index === firstMarkedLine) {
2295
2343
  const squiggleLength = index === lastMarkedLine
2296
- ? lastMarkedLineCharacter - firstMarkedLineCharacter
2297
- : lineText.length - firstMarkedLineCharacter;
2298
- codeFrame.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, indentWidth: firstMarkedLineCharacter, squiggleColor: highlightColor, squiggleWidth: squiggleLength }));
2344
+ ? lastMarkedCharacter - firstMarkedCharacter
2345
+ : lineText.length - firstMarkedCharacter;
2346
+ codeFrame.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, indentWidth: firstMarkedCharacter, squiggleColor: highlightColor, squiggleWidth: squiggleLength }));
2299
2347
  }
2300
2348
  else if (index === lastMarkedLine) {
2301
- codeFrame.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleColor: highlightColor, squiggleWidth: lastMarkedLineCharacter }));
2349
+ codeFrame.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleColor: highlightColor, squiggleWidth: lastMarkedCharacter }));
2302
2350
  }
2303
2351
  else {
2304
2352
  codeFrame.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleColor: highlightColor, squiggleWidth: lineText.length }));
@@ -2312,7 +2360,7 @@ function CodeFrameText({ diagnosticCategory, diagnosticOrigin, options }) {
2312
2360
  if (showBreadcrumbs && diagnosticOrigin.assertionNode != null) {
2313
2361
  breadcrumbs = jsx(BreadcrumbsText, { ancestor: diagnosticOrigin.assertionNode.parent });
2314
2362
  }
2315
- 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] }));
2363
+ 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}:${firstMarkedCharacter + 1}` }), breadcrumbs] }));
2316
2364
  return (jsx(Text, { children: [codeFrame, jsx(Line, {}), location] }));
2317
2365
  }
2318
2366
 
@@ -3175,7 +3223,7 @@ class Select {
3175
3223
  if (testFilePaths.length === 0) {
3176
3224
  Select.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereSelected(resolvedConfig)));
3177
3225
  }
3178
- return testFilePaths.sort();
3226
+ return testFilePaths.sort((a, b) => a.localeCompare(b));
3179
3227
  }
3180
3228
  static async #visitDirectory(currentPath, testFilePaths, matchPatterns, resolvedConfig) {
3181
3229
  const targetPath = Path.join(resolvedConfig.rootPath, currentPath);
@@ -3305,7 +3353,7 @@ class WatchService {
3305
3353
  while (!cancellationToken.isCancellationRequested()) {
3306
3354
  const testFiles = await debounce.schedule();
3307
3355
  if (testFiles.length > 0) {
3308
- yield testFiles;
3356
+ yield testFiles.sort((a, b) => a.path.localeCompare(b.path));
3309
3357
  }
3310
3358
  }
3311
3359
  }
@@ -3350,18 +3398,10 @@ class AbilityLayer {
3350
3398
  this.#editor = editor;
3351
3399
  }
3352
3400
  #belongsToNode(node, diagnostic) {
3353
- switch (node.brand) {
3354
- case "expect":
3355
- return (diagnosticBelongsToNode(diagnostic, node.matcherNode) ||
3356
- diagnosticBelongsToNode(diagnostic, node.source));
3357
- case "when":
3358
- return (diagnosticBelongsToNode(diagnostic, node.actionNode) &&
3359
- !diagnosticBelongsToNode(diagnostic, node.target));
3360
- }
3361
- return false;
3401
+ return diagnosticBelongsToNode(diagnostic, node.matcherNode) || diagnosticBelongsToNode(diagnostic, node.source);
3362
3402
  }
3363
3403
  close(diagnostics) {
3364
- if (diagnostics != null && this.#nodes.length > 0) {
3404
+ if (diagnostics.length > 0 && this.#nodes.length > 0) {
3365
3405
  this.#nodes.reverse();
3366
3406
  for (const diagnostic of diagnostics) {
3367
3407
  this.#mapToNodes(diagnostic);
@@ -3384,12 +3424,56 @@ class AbilityLayer {
3384
3424
  const matcherNameEnd = expect.matcherNameNode.getEnd();
3385
3425
  const matcherNodeEnd = expect.matcherNode.getEnd();
3386
3426
  switch (expect.matcherNameNode.name.text) {
3427
+ case "toAcceptProps": {
3428
+ this.#nodes.push(expect);
3429
+ const sourceNode = expect.source[0];
3430
+ const targetNode = expect.target?.[0];
3431
+ if (!sourceNode ||
3432
+ !targetNode ||
3433
+ !this.#compiler.isObjectLiteralExpression(targetNode) ||
3434
+ !targetNode.properties.every((property) => (this.#compiler.isPropertyAssignment(property) &&
3435
+ (this.#compiler.isIdentifier(property.name) || this.#compiler.isStringLiteral(property.name))) ||
3436
+ this.#compiler.isSpreadAssignment(property))) {
3437
+ return;
3438
+ }
3439
+ const sourceText = sourceNode.getText();
3440
+ if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
3441
+ this.#editor
3442
+ .eraseTrailingComma(expect.source)
3443
+ .erase(expectStart, matcherNodeEnd)
3444
+ .update(expectStart, matcherNameEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? ";" : "")
3445
+ .update(sourceNode.getStart() - 1, sourceNode.getEnd(), `<${sourceText}`);
3446
+ }
3447
+ else {
3448
+ const id = ["SC", expectStart, Date.now().toString(36)].join("_");
3449
+ this.#editor
3450
+ .erase(expectStart, matcherNodeEnd)
3451
+ .update(expectStart, matcherNameEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3452
+ ? `;const ${id} = undefined as any as ${sourceText};<${id}`
3453
+ : `const ${id} = undefined as any as ${sourceText};<${id}`);
3454
+ }
3455
+ for (const property of targetNode.properties) {
3456
+ if (this.#compiler.isPropertyAssignment(property)) {
3457
+ this.#editor
3458
+ .update(property.name.getStart(), property.name.getEnd() + 1, this.#compiler.isStringLiteral(property.name)
3459
+ ? ` ${property.name.getText().slice(1, -1)} =`
3460
+ : property.name.getText() + "=")
3461
+ .update(property.initializer.getStart(), property.initializer.getStart(), "{")
3462
+ .update(property.initializer.getStart(), property.initializer.getEnd(), property.initializer.getText() + "}");
3463
+ continue;
3464
+ }
3465
+ if (this.#compiler.isSpreadAssignment(property)) {
3466
+ this.#editor
3467
+ .update(property.getStart(), property.getStart(), "{")
3468
+ .update(property.getStart(), property.getEnd(), property.getText() + "}");
3469
+ }
3470
+ }
3471
+ this.#editor.update(matcherNodeEnd, matcherNodeEnd, "/>");
3472
+ break;
3473
+ }
3387
3474
  case "toBeApplicable":
3388
3475
  this.#nodes.push(expect);
3389
- this.#editor.replaceRanges([
3390
- [expectStart, expectExpressionEnd],
3391
- [expectEnd, matcherNameEnd],
3392
- ]);
3476
+ this.#editor.erase(expectStart, expectExpressionEnd).erase(expectEnd, matcherNameEnd);
3393
3477
  break;
3394
3478
  case "toBeCallableWith": {
3395
3479
  this.#nodes.push(expect);
@@ -3398,27 +3482,16 @@ class AbilityLayer {
3398
3482
  return;
3399
3483
  }
3400
3484
  if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
3401
- this.#editor.eraseTrailingComma(expect.source);
3402
- this.#editor.replaceRanges([
3403
- [
3404
- expectStart,
3405
- expectExpressionEnd,
3406
- nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? ";" : "",
3407
- ],
3408
- [expectEnd, matcherNameEnd],
3409
- ]);
3485
+ this.#editor
3486
+ .eraseTrailingComma(expect.source)
3487
+ .update(expectStart, expectExpressionEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? ";" : "")
3488
+ .erase(expectEnd, matcherNameEnd);
3410
3489
  }
3411
3490
  else {
3412
3491
  const sourceText = sourceNode.getFullText();
3413
- this.#editor.replaceRanges([
3414
- [
3415
- expectStart,
3416
- matcherNameEnd,
3417
- nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3418
- ? `;(undefined as any as ${sourceText})`
3419
- : `(undefined as any as ${sourceText})`,
3420
- ],
3421
- ]);
3492
+ this.#editor.update(expectStart, matcherNameEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3493
+ ? `;(undefined as any as ${sourceText})`
3494
+ : `(undefined as any as ${sourceText})`);
3422
3495
  }
3423
3496
  break;
3424
3497
  }
@@ -3429,27 +3502,16 @@ class AbilityLayer {
3429
3502
  return;
3430
3503
  }
3431
3504
  if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
3432
- this.#editor.eraseTrailingComma(expect.source);
3433
- this.#editor.replaceRanges([
3434
- [
3435
- expectStart,
3436
- expectExpressionEnd,
3437
- nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? "; new" : "new",
3438
- ],
3439
- [expectEnd, matcherNameEnd],
3440
- ]);
3505
+ this.#editor
3506
+ .eraseTrailingComma(expect.source)
3507
+ .update(expectStart, expectExpressionEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? "; new" : "new")
3508
+ .erase(expectEnd, matcherNameEnd);
3441
3509
  }
3442
3510
  else {
3443
3511
  const sourceText = sourceNode.getFullText();
3444
- this.#editor.replaceRanges([
3445
- [
3446
- expectStart,
3447
- matcherNameEnd,
3448
- nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3449
- ? `;new (undefined as any as ${sourceText})`
3450
- : `new (undefined as any as ${sourceText})`,
3451
- ],
3452
- ]);
3512
+ this.#editor.update(expectStart, matcherNameEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3513
+ ? `;new (undefined as any as ${sourceText})`
3514
+ : `new (undefined as any as ${sourceText})`);
3453
3515
  }
3454
3516
  break;
3455
3517
  }
@@ -3461,39 +3523,26 @@ class AbilityLayer {
3461
3523
  return;
3462
3524
  }
3463
3525
  if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
3464
- this.#editor.eraseTrailingComma(expect.source);
3465
- this.#editor.replaceRanges([
3466
- [
3467
- expectStart,
3468
- expectExpressionEnd,
3469
- nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? ";" : "",
3470
- ],
3471
- [expectEnd, matcherNodeEnd],
3472
- ]);
3526
+ this.#editor
3527
+ .eraseTrailingComma(expect.source)
3528
+ .update(expectStart, expectExpressionEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? ";" : "")
3529
+ .erase(expectEnd, matcherNodeEnd);
3473
3530
  if (this.#compiler.isExpressionWithTypeArguments(sourceNode)) {
3474
- this.#editor.replaceRanges([[sourceNode.expression.getEnd(), sourceNode.getEnd()]]);
3531
+ this.#editor.erase(sourceNode.expression.getEnd(), sourceNode.getEnd());
3475
3532
  }
3476
3533
  }
3477
3534
  else {
3478
3535
  const sourceText = this.#compiler.isTypeReferenceNode(sourceNode)
3479
- ? sourceNode.typeName.getText()
3480
- : sourceNode.getText();
3481
- this.#editor.replaceRanges([
3482
- [
3483
- expectStart,
3484
- matcherNodeEnd,
3485
- nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3486
- ? `;undefined as any as ${sourceText}`
3487
- : `undefined as any as ${sourceText}`,
3488
- ],
3489
- ]);
3536
+ ? sourceNode.typeName.getFullText()
3537
+ : sourceNode.getFullText();
3538
+ this.#editor.update(expectStart, matcherNodeEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3539
+ ? `;undefined as any as ${sourceText}`
3540
+ : `undefined as any as ${sourceText}`);
3490
3541
  }
3491
3542
  if (targetNode != null) {
3492
3543
  const targetText = targetNode.getText().slice(1, -1);
3493
- if (targetText.trim().length > 1) {
3494
- this.#editor.replaceRanges([
3495
- [targetNode.getFullStart(), targetNode.getEnd(), `<${targetText}>`.padStart(targetNode.getFullWidth())],
3496
- ]);
3544
+ if (targetText.trim().length > 0) {
3545
+ this.#editor.update(targetNode.getFullStart(), targetNode.getEnd(), `<${targetText}>`.padStart(targetNode.getFullWidth()));
3497
3546
  }
3498
3547
  }
3499
3548
  break;
@@ -3505,110 +3554,26 @@ class AbilityLayer {
3505
3554
  if (!sourceNode || !targetNode) {
3506
3555
  return;
3507
3556
  }
3508
- const sourceText = sourceNode.getFullText();
3509
- const targetText = targetNode.getFullText();
3510
3557
  if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
3511
- this.#editor.eraseTrailingComma(expect.source);
3512
- this.#editor.replaceRanges([
3513
- [
3514
- expectStart,
3515
- matcherNodeEnd,
3516
- nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3517
- ? `;(${sourceText})[${targetText}]`
3518
- : `(${sourceText})[${targetText}]`,
3519
- ],
3520
- ]);
3558
+ this.#editor
3559
+ .eraseTrailingComma(expect.source)
3560
+ .update(expectStart, expectExpressionEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? ";" : "")
3561
+ .erase(expectEnd, matcherNodeEnd);
3521
3562
  }
3522
3563
  else {
3523
- this.#editor.replaceRanges([
3524
- [
3525
- expectStart,
3526
- matcherNodeEnd,
3527
- nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3528
- ? `;(undefined as any as ${sourceText})[${targetText}]`
3529
- : `(undefined as any as ${sourceText})[${targetText}]`,
3530
- ],
3531
- ]);
3564
+ const sourceText = sourceNode.getFullText();
3565
+ this.#editor.update(expectStart, matcherNodeEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3566
+ ? `;(undefined as any as ${sourceText})`
3567
+ : `(undefined as any as ${sourceText})`);
3568
+ }
3569
+ const targetText = targetNode.getText();
3570
+ if (targetText.trim().length > 0) {
3571
+ this.#editor.update(targetNode.getFullStart() - 1, targetNode.getEnd() + 1, `[${targetText}]`.padStart(targetNode.getFullWidth()));
3532
3572
  }
3533
3573
  break;
3534
3574
  }
3535
3575
  }
3536
3576
  }
3537
- visitWhen(when) {
3538
- const whenStart = when.node.getStart();
3539
- const whenExpressionEnd = when.node.expression.getEnd();
3540
- const whenEnd = when.node.getEnd();
3541
- const actionNameEnd = when.actionNameNode.getEnd();
3542
- switch (when.actionNameNode.name.text) {
3543
- case "isCalledWith":
3544
- this.#nodes.push(when);
3545
- this.#editor.eraseTrailingComma(when.target);
3546
- this.#editor.replaceRanges([
3547
- [whenStart, whenExpressionEnd, nodeIsChildOfExpressionStatement(this.#compiler, when.actionNode) ? ";" : ""],
3548
- [whenEnd, actionNameEnd],
3549
- ]);
3550
- break;
3551
- }
3552
- }
3553
- }
3554
-
3555
- class SourceTextEditor {
3556
- #filePath = "";
3557
- #sourceFile;
3558
- #text = "";
3559
- open(sourceFile) {
3560
- this.#sourceFile = sourceFile;
3561
- this.#filePath = sourceFile.fileName;
3562
- this.#text = sourceFile.text;
3563
- }
3564
- close() {
3565
- if (this.#sourceFile != null) {
3566
- SourceService.set(this.#sourceFile);
3567
- this.#sourceFile = undefined;
3568
- }
3569
- this.#filePath = "";
3570
- this.#text = "";
3571
- }
3572
- eraseTrailingComma(node) {
3573
- if (node.hasTrailingComma) {
3574
- this.replaceRange(node.end - 1, node.end);
3575
- }
3576
- }
3577
- #getErasedRange(start, end) {
3578
- if (this.#text.indexOf("\n", start) >= end) {
3579
- return " ".repeat(end - start);
3580
- }
3581
- const text = [];
3582
- for (let index = start; index < end; index++) {
3583
- const character = this.#text.charAt(index);
3584
- switch (character) {
3585
- case "\n":
3586
- case "\r":
3587
- text.push(character);
3588
- break;
3589
- default:
3590
- text.push(" ");
3591
- }
3592
- }
3593
- return text.join("");
3594
- }
3595
- getFilePath() {
3596
- return this.#filePath;
3597
- }
3598
- getText() {
3599
- return this.#text;
3600
- }
3601
- replaceRange(start, end, replacement) {
3602
- const rangeText = replacement != null
3603
- ? `${replacement}${this.#getErasedRange(start, end).slice(replacement.length)}`
3604
- : this.#getErasedRange(start, end);
3605
- this.#text = `${this.#text.slice(0, start)}${rangeText}${this.#text.slice(end)}`;
3606
- }
3607
- replaceRanges(ranges) {
3608
- for (const [start, end, replacement] of ranges) {
3609
- this.replaceRange(start, end, replacement);
3610
- }
3611
- }
3612
3577
  }
3613
3578
 
3614
3579
  class SuppressedLayer {
@@ -3683,7 +3648,7 @@ class SuppressedLayer {
3683
3648
  }
3684
3649
  for (const suppressedError of suppressedErrors) {
3685
3650
  const { start, end } = suppressedError.directive;
3686
- this.#editor.replaceRange(start + 2, end);
3651
+ this.#editor.erase(start, end);
3687
3652
  if (this.#suppressedErrorsMap != null) {
3688
3653
  const { line } = tree.sourceFile.getLineAndCharacterOfPosition(start);
3689
3654
  this.#suppressedErrorsMap.set(line, suppressedError);
@@ -3692,52 +3657,129 @@ class SuppressedLayer {
3692
3657
  }
3693
3658
  }
3694
3659
 
3695
- class Layers {
3696
- #abilityLayer;
3697
- #editor = new SourceTextEditor();
3698
- #projectService;
3699
- #suppressedDiagnostics;
3700
- #suppressedLayer;
3701
- constructor(compiler, projectService, resolvedConfig) {
3702
- this.#projectService = projectService;
3703
- this.#abilityLayer = new AbilityLayer(compiler, this.#editor);
3704
- this.#suppressedLayer = new SuppressedLayer(compiler, this.#editor, resolvedConfig);
3660
+ class TextEditor {
3661
+ #filePath = "";
3662
+ #offset = 0;
3663
+ #offsets = [];
3664
+ #text = "";
3665
+ open(sourceFile) {
3666
+ this.#filePath = sourceFile.fileName;
3667
+ this.#text = sourceFile.text;
3668
+ this.#offset = 0;
3669
+ this.#offsets = [];
3705
3670
  }
3706
3671
  close() {
3707
- let isSeenDiagnostic;
3708
- if (this.#suppressedDiagnostics != null) {
3709
- const seenDiagnostics = this.#suppressedDiagnostics;
3710
- this.#suppressedDiagnostics = undefined;
3711
- isSeenDiagnostic = (diagnostic) => !seenDiagnostics.some((seenDiagnostic) => compareDiagnostics(diagnostic, seenDiagnostic));
3712
- }
3713
- const abilityDiagnostics = this.#projectService.getDiagnostics(this.#editor.getFilePath(), this.#editor.getText(), isSeenDiagnostic);
3714
- this.#abilityLayer.close(abilityDiagnostics);
3715
- this.#editor.close();
3716
- }
3717
- open(tree) {
3718
- this.#editor.open(tree.sourceFile);
3719
- this.#suppressedLayer.open(tree);
3720
- this.#suppressedDiagnostics = this.#projectService.getDiagnostics(this.#editor.getFilePath(), this.#editor.getText());
3721
- this.#suppressedLayer.close(this.#suppressedDiagnostics);
3672
+ this.#filePath = "";
3673
+ this.#text = "";
3674
+ this.#offset = 0;
3675
+ this.#offsets = [];
3676
+ }
3677
+ erase(start, end) {
3678
+ this.#text =
3679
+ this.#text.slice(0, start + this.#offset) +
3680
+ this.#getErased(start + this.#offset, end + this.#offset) +
3681
+ this.#text.slice(end + this.#offset);
3682
+ return this;
3722
3683
  }
3723
- visit(node) {
3724
- switch (node.brand) {
3725
- case "expect":
3726
- this.#abilityLayer.visitExpect(node);
3727
- break;
3728
- case "when":
3729
- this.#abilityLayer.visitWhen(node);
3730
- break;
3684
+ eraseTrailingComma(node) {
3685
+ if (node.hasTrailingComma) {
3686
+ this.erase(node.end - 1, node.end);
3731
3687
  }
3688
+ return this;
3732
3689
  }
3733
- }
3734
-
3735
- class CollectDiagnosticText {
3736
- static cannotBeNestedWithin(source, target) {
3737
- return `'${source}()' cannot be nested within '${target}()'.`;
3738
- }
3739
- }
3740
-
3690
+ #getErased(start, end) {
3691
+ if (this.#text.indexOf("\n", start) >= end) {
3692
+ return " ".repeat(end - start);
3693
+ }
3694
+ const text = [];
3695
+ for (let index = start; index < end; index++) {
3696
+ const character = this.#text.charAt(index);
3697
+ switch (character) {
3698
+ case "\n":
3699
+ case "\r":
3700
+ text.push(character);
3701
+ break;
3702
+ default:
3703
+ text.push(" ");
3704
+ }
3705
+ }
3706
+ return text.join("");
3707
+ }
3708
+ getFilePath() {
3709
+ return this.#filePath;
3710
+ }
3711
+ getOffsets() {
3712
+ return this.#offsets;
3713
+ }
3714
+ getText() {
3715
+ return this.#text;
3716
+ }
3717
+ #setOffset(start, end, text) {
3718
+ const diff = text.length - (end - start);
3719
+ if (diff > 0) {
3720
+ this.#offset += diff;
3721
+ this.#offsets.push({ position: end, diff });
3722
+ }
3723
+ }
3724
+ update(start, end, text) {
3725
+ this.#text =
3726
+ this.#text.slice(0, start + this.#offset) +
3727
+ text +
3728
+ this.#getErased(start + this.#offset, end + this.#offset).slice(text.length) +
3729
+ this.#text.slice(end + this.#offset);
3730
+ this.#setOffset(start, end, text);
3731
+ return this;
3732
+ }
3733
+ }
3734
+
3735
+ class Layers {
3736
+ #abilityLayer;
3737
+ #editor = new TextEditor();
3738
+ #projectService;
3739
+ #suppressedDiagnostics;
3740
+ #suppressedLayer;
3741
+ constructor(compiler, projectService, resolvedConfig) {
3742
+ this.#projectService = projectService;
3743
+ this.#abilityLayer = new AbilityLayer(compiler, this.#editor);
3744
+ this.#suppressedLayer = new SuppressedLayer(compiler, this.#editor, resolvedConfig);
3745
+ }
3746
+ close(tree) {
3747
+ let seenDiagnostics = [];
3748
+ if (this.#suppressedDiagnostics != null) {
3749
+ seenDiagnostics = this.#suppressedDiagnostics;
3750
+ this.#suppressedDiagnostics = undefined;
3751
+ }
3752
+ const diagnostics = this.#projectService.getDiagnostics(this.#editor.getFilePath(), this.#editor.getText());
3753
+ const offsets = this.#editor.getOffsets();
3754
+ const abilityDiagnostics = [];
3755
+ if (diagnostics != null) {
3756
+ for (const diagnostic of diagnostics) {
3757
+ const mappedDiagnostic = new MappedDiagnostic(tree.sourceFile, diagnostic, offsets);
3758
+ if (!seenDiagnostics.some((seenDiagnostic) => compareDiagnostics(mappedDiagnostic, seenDiagnostic))) {
3759
+ abilityDiagnostics.push(mappedDiagnostic);
3760
+ }
3761
+ }
3762
+ }
3763
+ this.#abilityLayer.close(abilityDiagnostics);
3764
+ this.#editor.close();
3765
+ }
3766
+ open(tree) {
3767
+ this.#editor.open(tree.sourceFile);
3768
+ this.#suppressedLayer.open(tree);
3769
+ this.#suppressedDiagnostics = this.#projectService.getDiagnostics(this.#editor.getFilePath(), this.#editor.getText());
3770
+ this.#suppressedLayer.close(this.#suppressedDiagnostics?.map((diagnostic) => new MappedDiagnostic(tree.sourceFile, diagnostic)));
3771
+ }
3772
+ visit(node) {
3773
+ this.#abilityLayer.visitExpect(node);
3774
+ }
3775
+ }
3776
+
3777
+ class CollectDiagnosticText {
3778
+ static cannotBeNestedWithin(source, target) {
3779
+ return `'${source}()' cannot be nested within '${target}()'.`;
3780
+ }
3781
+ }
3782
+
3741
3783
  class TestTreeNode {
3742
3784
  brand;
3743
3785
  children = [];
@@ -3880,8 +3922,6 @@ class IdentifierLookup {
3880
3922
  return { brand: "test", flags };
3881
3923
  case "expect":
3882
3924
  return { brand: "expect", flags };
3883
- case "when":
3884
- return { brand: "when", flags };
3885
3925
  }
3886
3926
  return;
3887
3927
  }
@@ -3899,25 +3939,6 @@ class TestTree {
3899
3939
  }
3900
3940
  }
3901
3941
 
3902
- class WhenNode extends TestTreeNode {
3903
- actionNode;
3904
- actionNameNode;
3905
- abilityDiagnostics = new Set();
3906
- target;
3907
- constructor(compiler, brand, node, parent, flags, actionNode, actionNameNode) {
3908
- super(compiler, brand, node, parent, flags);
3909
- this.actionNode = actionNode;
3910
- this.actionNameNode = actionNameNode;
3911
- this.target = this.node.typeArguments ?? this.node.arguments;
3912
- for (const diagnostic of parent.diagnostics) {
3913
- if (diagnosticBelongsToNode(diagnostic, node)) {
3914
- this.diagnostics.add(diagnostic);
3915
- parent.diagnostics.delete(diagnostic);
3916
- }
3917
- }
3918
- }
3919
- }
3920
-
3921
3942
  class CollectService {
3922
3943
  #layers;
3923
3944
  #compiler;
@@ -3977,38 +3998,6 @@ class CollectService {
3977
3998
  this.#onNode(expectNode, parent, testTree);
3978
3999
  return;
3979
4000
  }
3980
- if (meta.brand === "when") {
3981
- const actionNameNode = this.#getChainedNode(node);
3982
- if (!actionNameNode) {
3983
- const text = "The final element in the chain must be an action.";
3984
- const origin = DiagnosticOrigin.fromNode(node);
3985
- this.#onDiagnostics(Diagnostic.error(text, origin));
3986
- return;
3987
- }
3988
- const actionNode = this.#getActionNode(actionNameNode);
3989
- if (!actionNode) {
3990
- const text = "The action must be called with an argument.";
3991
- const origin = DiagnosticOrigin.fromNode(actionNameNode);
3992
- this.#onDiagnostics(Diagnostic.error(text, origin));
3993
- return;
3994
- }
3995
- this.#compiler.forEachChild(actionNode, (node) => {
3996
- if (this.#compiler.isCallExpression(node)) {
3997
- const meta = this.#identifierLookup.resolveTestTreeNodeMeta(node);
3998
- if (meta?.brand === "describe" ||
3999
- meta?.brand === "it" ||
4000
- meta?.brand === "test") {
4001
- const text = CollectDiagnosticText.cannotBeNestedWithin(meta.brand, "when");
4002
- const origin = DiagnosticOrigin.fromNode(node);
4003
- this.#onDiagnostics(Diagnostic.error(text, origin));
4004
- }
4005
- }
4006
- });
4007
- const whenNode = new WhenNode(this.#compiler, meta.brand, node, parent, meta.flags, actionNode, actionNameNode);
4008
- this.#layers.visit(whenNode);
4009
- this.#onNode(whenNode, parent, testTree);
4010
- return;
4011
- }
4012
4001
  }
4013
4002
  }
4014
4003
  if (this.#compiler.isImportDeclaration(node)) {
@@ -4025,7 +4014,7 @@ class CollectService {
4025
4014
  this.#layers.open(testTree);
4026
4015
  this.#identifierLookup.open();
4027
4016
  this.#collectTestTreeNodes(sourceFile, testTree, testTree);
4028
- this.#layers.close();
4017
+ this.#layers.close(testTree);
4029
4018
  EventEmitter.dispatch(["collect:end", { tree: testTree }]);
4030
4019
  return testTree;
4031
4020
  }
@@ -4050,7 +4039,6 @@ class CollectService {
4050
4039
  }
4051
4040
  break;
4052
4041
  case "expect":
4053
- case "when":
4054
4042
  if (parent.brand === "describe") {
4055
4043
  return false;
4056
4044
  }
@@ -4079,12 +4067,6 @@ class CollectService {
4079
4067
  }
4080
4068
  return;
4081
4069
  }
4082
- #getActionNode(node) {
4083
- if (this.#compiler.isCallExpression(node.parent)) {
4084
- return node.parent;
4085
- }
4086
- return;
4087
- }
4088
4070
  #onDiagnostics(diagnostic) {
4089
4071
  EventEmitter.dispatch(["collect:error", { diagnostics: [diagnostic] }]);
4090
4072
  }
@@ -4103,7 +4085,6 @@ var TestTreeNodeBrand;
4103
4085
  TestTreeNodeBrand["Test"] = "test";
4104
4086
  TestTreeNodeBrand["It"] = "it";
4105
4087
  TestTreeNodeBrand["Expect"] = "expect";
4106
- TestTreeNodeBrand["When"] = "when";
4107
4088
  })(TestTreeNodeBrand || (TestTreeNodeBrand = {}));
4108
4089
 
4109
4090
  var TestTreeNodeFlags;
@@ -4117,7 +4098,7 @@ var TestTreeNodeFlags;
4117
4098
  class ProjectService {
4118
4099
  #compiler;
4119
4100
  #host;
4120
- #lastSeenProject = "";
4101
+ #lastSeenProject = "none";
4121
4102
  #projectConfig;
4122
4103
  #resolvedConfig;
4123
4104
  #seenProjects = new Set();
@@ -4167,7 +4148,6 @@ class ProjectService {
4167
4148
  }
4168
4149
  closeFile(filePath) {
4169
4150
  this.#service.closeClientFile(filePath);
4170
- SourceService.delete(filePath);
4171
4151
  }
4172
4152
  #getDefaultCompilerOptions() {
4173
4153
  const defaultCompilerOptions = {
@@ -4197,14 +4177,10 @@ class ProjectService {
4197
4177
  }
4198
4178
  return project;
4199
4179
  }
4200
- getDiagnostics(filePath, sourceText, shouldInclude) {
4180
+ getDiagnostics(filePath, sourceText) {
4201
4181
  this.openFile(filePath, sourceText);
4202
4182
  const languageService = this.getLanguageService(filePath);
4203
- const diagnostics = languageService?.getSemanticDiagnostics(filePath);
4204
- if (diagnostics != null && shouldInclude != null) {
4205
- return diagnostics.filter(shouldInclude);
4206
- }
4207
- return diagnostics;
4183
+ return languageService?.getSemanticDiagnostics(filePath);
4208
4184
  }
4209
4185
  getLanguageService(filePath) {
4210
4186
  const project = this.getDefaultProject(filePath);
@@ -4225,7 +4201,7 @@ class ProjectService {
4225
4201
  if (Options.isJsonString(specifier)) {
4226
4202
  return {
4227
4203
  kind: 3,
4228
- specifier: Path.resolve(this.#resolvedConfig.rootPath, `${Math.random().toString(32).slice(2)}.tsconfig.json`),
4204
+ specifier: Path.resolve(this.#resolvedConfig.rootPath, `${Date.now().toString(36)}.tsconfig.json`),
4229
4205
  };
4230
4206
  }
4231
4207
  return { kind: 2, specifier };
@@ -4369,51 +4345,102 @@ class SuppressedService {
4369
4345
  }
4370
4346
  }
4371
4347
 
4372
- class EnsureDiagnosticText {
4373
- static argumentMustBeProvided(argumentNameText) {
4374
- return `An argument for '${argumentNameText}' must be provided.`;
4348
+ function capitalize(text) {
4349
+ return text.replace(/^./, text.charAt(0).toUpperCase());
4350
+ }
4351
+
4352
+ class RejectDiagnosticText {
4353
+ static argumentCannotBeOfType(typeText) {
4354
+ return `The argument cannot be of the '${typeText}' type.`;
4375
4355
  }
4376
- static argumentOrTypeArgumentMustBeProvided(argumentNameText, typeArgumentNameText) {
4377
- return `An argument for '${argumentNameText}' or type argument for '${typeArgumentNameText}' must be provided.`;
4356
+ static typeArgumentCannotBeOfType(typeText) {
4357
+ return `The type argument cannot be of the '${typeText}' type.`;
4378
4358
  }
4379
- static typeArgumentMustBeProvided(typeArgumentNameText) {
4380
- return `Type argument for '${typeArgumentNameText}' must be provided.`;
4359
+ static typeWasRejected(typeText) {
4360
+ const optionNameText = `reject${capitalize(typeText)}Type`;
4361
+ return [
4362
+ `The '${typeText}' type was rejected because the '${optionNameText}' option is enabled.`,
4363
+ `If this check is necessary, pass '${typeText}' as the type argument explicitly.`,
4364
+ ];
4381
4365
  }
4382
4366
  }
4383
4367
 
4384
- function argumentIsProvided(compiler, argumentNameText, node, enclosingNode, onDiagnostics) {
4385
- if (!node || !nodeBelongsToArgumentList(compiler, node)) {
4386
- const text = EnsureDiagnosticText.argumentMustBeProvided(argumentNameText);
4387
- const origin = DiagnosticOrigin.fromNode(enclosingNode);
4388
- onDiagnostics([Diagnostic.error(text, origin)]);
4389
- return false;
4368
+ class Reject {
4369
+ #compiler;
4370
+ #rejectedArgumentTypes = new Set();
4371
+ #typeChecker;
4372
+ constructor(compiler, program, resolvedConfig) {
4373
+ this.#compiler = compiler;
4374
+ this.#typeChecker = program.getTypeChecker();
4375
+ if (resolvedConfig.rejectAnyType) {
4376
+ this.#rejectedArgumentTypes.add("any");
4377
+ }
4378
+ if (resolvedConfig.rejectNeverType) {
4379
+ this.#rejectedArgumentTypes.add("never");
4380
+ }
4390
4381
  }
4391
- return true;
4392
- }
4393
-
4394
- function argumentOrTypeArgumentIsProvided(argumentNameText, typeArgumentNameText, node, enclosingNode, onDiagnostics) {
4395
- if (!node) {
4396
- const text = EnsureDiagnosticText.argumentOrTypeArgumentMustBeProvided(argumentNameText, typeArgumentNameText);
4397
- const origin = DiagnosticOrigin.fromNode(enclosingNode);
4398
- onDiagnostics([Diagnostic.error(text, origin)]);
4382
+ argumentType(target, onDiagnostics) {
4383
+ for (const rejectedType of this.#rejectedArgumentTypes) {
4384
+ const allowedKeyword = this.#compiler.SyntaxKind[`${capitalize(rejectedType)}Keyword`];
4385
+ if (target.some((node) => node?.kind === allowedKeyword)) {
4386
+ continue;
4387
+ }
4388
+ for (const node of target) {
4389
+ if (!node) {
4390
+ continue;
4391
+ }
4392
+ if (this.#typeChecker.getTypeAtLocation(node).flags & this.#compiler.TypeFlags[capitalize(rejectedType)]) {
4393
+ const text = [
4394
+ nodeBelongsToArgumentList(this.#compiler, node)
4395
+ ? RejectDiagnosticText.argumentCannotBeOfType(rejectedType)
4396
+ : RejectDiagnosticText.typeArgumentCannotBeOfType(rejectedType),
4397
+ ...RejectDiagnosticText.typeWasRejected(rejectedType),
4398
+ ];
4399
+ const origin = DiagnosticOrigin.fromNode(node);
4400
+ onDiagnostics([Diagnostic.error(text, origin)]);
4401
+ return true;
4402
+ }
4403
+ }
4404
+ }
4399
4405
  return false;
4400
4406
  }
4401
- return true;
4402
4407
  }
4403
4408
 
4404
- function typeArgumentIsProvided(compiler, typeArgumentNameText, node, enclosingNode, onDiagnostics) {
4405
- if (!node || nodeBelongsToArgumentList(compiler, node)) {
4406
- const text = EnsureDiagnosticText.typeArgumentMustBeProvided(typeArgumentNameText);
4409
+ class Ensure {
4410
+ #compiler;
4411
+ constructor(compiler) {
4412
+ this.#compiler = compiler;
4413
+ }
4414
+ argument(node, enclosingNode, onDiagnostics) {
4415
+ if (!node || !nodeBelongsToArgumentList(this.#compiler, node)) {
4416
+ this.#emitDiagnostic("An argument must be provided.", enclosingNode, onDiagnostics);
4417
+ return false;
4418
+ }
4419
+ return true;
4420
+ }
4421
+ argumentOrTypeArgument(node, enclosingNode, onDiagnostics) {
4422
+ if (!node) {
4423
+ this.#emitDiagnostic("An argument or type argument must be provided.", enclosingNode, onDiagnostics);
4424
+ return false;
4425
+ }
4426
+ return true;
4427
+ }
4428
+ typeArgument(node, enclosingNode, onDiagnostics) {
4429
+ if (!node || nodeBelongsToArgumentList(this.#compiler, node)) {
4430
+ this.#emitDiagnostic("A type argument must be provided.", enclosingNode, onDiagnostics);
4431
+ return false;
4432
+ }
4433
+ return true;
4434
+ }
4435
+ #emitDiagnostic(text, enclosingNode, onDiagnostics) {
4407
4436
  const origin = DiagnosticOrigin.fromNode(enclosingNode);
4408
4437
  onDiagnostics([Diagnostic.error(text, origin)]);
4409
- return false;
4410
4438
  }
4411
- return true;
4412
4439
  }
4413
4440
 
4414
4441
  class ExpectDiagnosticText {
4415
- static argumentMustBe(argumentNameText, expectedText) {
4416
- return `An argument for '${argumentNameText}' must be ${expectedText}.`;
4442
+ static argumentMustBe(expectedText) {
4443
+ return `The argument must be ${expectedText}.`;
4417
4444
  }
4418
4445
  static typeArgumentMustBe(expectedText) {
4419
4446
  return `The type argument must be ${expectedText}.`;
@@ -4437,10 +4464,10 @@ class ExpectDiagnosticText {
4437
4464
  return `${isExpression ? "Expression" : "Type"} is not instantiable ${targetText}.`;
4438
4465
  }
4439
4466
  static acceptsProps(isExpression) {
4440
- return `${isExpression ? "Component" : "Component type"} accepts props of the given type.`;
4467
+ return `${isExpression ? "Component" : "Type"} accepts props of the given type.`;
4441
4468
  }
4442
4469
  static doesNotAcceptProps(isExpression) {
4443
- return `${isExpression ? "Component" : "Component type"} does not accept props of the given type.`;
4470
+ return `${isExpression ? "Component" : "Type"} does not accept props of the given type.`;
4444
4471
  }
4445
4472
  static canBeApplied(targetText) {
4446
4473
  return `The decorator function can be applied${targetText}.`;
@@ -4460,9 +4487,6 @@ class ExpectDiagnosticText {
4460
4487
  static matcherIsNotSupported(matcherNameText) {
4461
4488
  return `The '.${matcherNameText}()' matcher is not supported.`;
4462
4489
  }
4463
- static overloadGaveTheFollowingError(index, count, signatureText) {
4464
- return `Overload ${index} of ${count}, '${signatureText}', gave the following error.`;
4465
- }
4466
4490
  static raisedTypeError(count = 1) {
4467
4491
  return `The raised type error${count === 1 ? "" : "s"}:`;
4468
4492
  }
@@ -4500,15 +4524,6 @@ class ExpectDiagnosticText {
4500
4524
  static isNotTheSame(sourceTypeText, targetTypeText) {
4501
4525
  return `Type '${sourceTypeText}' is not the same as type '${targetTypeText}'.`;
4502
4526
  }
4503
- static isNotCompatibleWith(sourceTypeText, targetTypeText) {
4504
- return `Type '${sourceTypeText}' is not compatible with type '${targetTypeText}'.`;
4505
- }
4506
- static requiresProperty(typeText, propertyNameText) {
4507
- return `Type '${typeText}' requires property '${propertyNameText}'.`;
4508
- }
4509
- static typesOfPropertyAreNotCompatible(propertyNameText) {
4510
- return `Types of property '${propertyNameText}' are not compatible.`;
4511
- }
4512
4527
  }
4513
4528
 
4514
4529
  class MatchWorker {
@@ -4538,13 +4553,6 @@ class MatchWorker {
4538
4553
  : { flags: this.#compiler.TypeFlags.NonPrimitive };
4539
4554
  return this.typeChecker.isTypeAssignableTo(type, nonPrimitiveType);
4540
4555
  }
4541
- getParameterType(signature, index) {
4542
- const parameter = signature.getDeclaration().parameters[index];
4543
- if (!parameter) {
4544
- return;
4545
- }
4546
- return this.getType(parameter);
4547
- }
4548
4556
  getSignatures(node) {
4549
4557
  let signatures = this.#signatureCache.get(node);
4550
4558
  if (!signatures) {
@@ -4562,195 +4570,100 @@ class MatchWorker {
4562
4570
  getType(node) {
4563
4571
  return this.typeChecker.getTypeAtLocation(node);
4564
4572
  }
4565
- resolveDiagnosticOrigin(symbol, enclosingNode) {
4566
- if (symbol.valueDeclaration != null &&
4567
- (this.#compiler.isPropertySignature(symbol.valueDeclaration) ||
4568
- this.#compiler.isPropertyAssignment(symbol.valueDeclaration) ||
4569
- this.#compiler.isShorthandPropertyAssignment(symbol.valueDeclaration)) &&
4570
- symbol.valueDeclaration.getStart() >= enclosingNode.getStart() &&
4571
- symbol.valueDeclaration.getEnd() <= enclosingNode.getEnd()) {
4572
- return DiagnosticOrigin.fromNode(symbol.valueDeclaration.name, this.assertionNode);
4573
- }
4574
- return DiagnosticOrigin.fromNode(enclosingNode, this.assertionNode);
4575
- }
4576
- }
4577
-
4578
- function isStringOrNumberLiteralType(compiler, type) {
4579
- return !!(type.flags & compiler.TypeFlags.StringOrNumberLiteral);
4580
- }
4581
- function isUnionType(compiler, type) {
4582
- return !!(type.flags & compiler.TypeFlags.Union);
4583
- }
4584
- function isUniqueSymbolType(compiler, type) {
4585
- return !!(type.flags & compiler.TypeFlags.UniqueESSymbol);
4586
4573
  }
4587
4574
 
4588
- class ToAcceptProps {
4589
- #compiler;
4590
- #typeChecker;
4591
- constructor(compiler, program) {
4592
- this.#compiler = compiler;
4593
- this.#typeChecker = program.getTypeChecker();
4594
- }
4595
- #explain(matchWorker, sourceNode, targetNode) {
4596
- const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
4597
- const signatures = matchWorker.getSignatures(sourceNode);
4598
- return signatures.reduce((accumulator, signature, index) => {
4599
- let diagnostic;
4600
- const introText = matchWorker.assertionNode.isNot
4601
- ? ExpectDiagnosticText.acceptsProps(isExpression)
4602
- : ExpectDiagnosticText.doesNotAcceptProps(isExpression);
4603
- const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertionNode);
4604
- if (signatures.length > 1) {
4605
- const signatureText = this.#typeChecker.signatureToString(signature, sourceNode);
4606
- const overloadText = ExpectDiagnosticText.overloadGaveTheFollowingError(index + 1, signatures.length, signatureText);
4607
- diagnostic = Diagnostic.error([introText, overloadText], origin);
4608
- }
4609
- else {
4610
- diagnostic = Diagnostic.error([introText], origin);
4611
- }
4612
- const { diagnostics, isMatch } = this.#explainProperties(matchWorker, signature, targetNode, diagnostic);
4613
- if (matchWorker.assertionNode.isNot ? isMatch : !isMatch) {
4614
- accumulator.push(...diagnostics);
4615
- }
4616
- return accumulator;
4617
- }, []);
4575
+ class AbilityMatcherBase {
4576
+ compiler;
4577
+ constructor(compiler) {
4578
+ this.compiler = compiler;
4618
4579
  }
4619
- #isOptionalProperty(symbol) {
4620
- return symbol.declarations?.every((declaration) => this.#compiler.isPropertySignature(declaration) && declaration.questionToken != null);
4580
+ getArgumentCountText(nodes) {
4581
+ if (nodes.length === 0) {
4582
+ return "without arguments";
4583
+ }
4584
+ if (nodes.length === 1 && nodes[0]?.kind === this.compiler.SyntaxKind.SpreadElement) {
4585
+ return "with the given arguments";
4586
+ }
4587
+ return `with the given argument${nodes.length === 1 ? "" : "s"}`;
4621
4588
  }
4622
- #checkProperties(sourceType, targetType) {
4623
- const check = (sourceType, targetType) => {
4624
- for (const targetProperty of targetType.getProperties()) {
4625
- const targetPropertyName = targetProperty.getName();
4626
- const sourceProperty = sourceType?.getProperty(targetPropertyName);
4627
- if (!sourceProperty) {
4628
- return false;
4629
- }
4630
- const targetPropertyType = this.#typeChecker.getTypeOfSymbol(targetProperty);
4631
- const sourcePropertyType = this.#typeChecker.getTypeOfSymbol(sourceProperty);
4632
- if (!this.#typeChecker.isTypeAssignableTo(targetPropertyType, sourcePropertyType)) {
4633
- return false;
4634
- }
4635
- }
4636
- if (sourceType != null) {
4637
- const sourceProperties = sourceType.getProperties();
4638
- for (const sourceProperty of sourceProperties) {
4639
- const targetProperty = targetType.getProperty(sourceProperty.getName());
4640
- if (!targetProperty && !this.#isOptionalProperty(sourceProperty)) {
4641
- return false;
4642
- }
4643
- }
4644
- }
4645
- return true;
4646
- };
4647
- if (sourceType != null && isUnionType(this.#compiler, sourceType)) {
4648
- return sourceType.types.some((sourceType) => check(sourceType, targetType));
4589
+ getTypeArgumentCountText(targetNode) {
4590
+ if (targetNode.elements.length === 0) {
4591
+ return "without type arguments";
4649
4592
  }
4650
- return check(sourceType, targetType);
4593
+ return `with the given type argument${targetNode.elements.length === 1 ? "" : "s"}`;
4651
4594
  }
4652
- #explainProperties(matchWorker, signature, targetNode, diagnostic) {
4653
- const sourceType = matchWorker.getParameterType(signature, 0);
4654
- const sourceTypeText = sourceType != null ? this.#typeChecker.typeToString(sourceType) : "{}";
4655
- const targetType = matchWorker.getType(targetNode);
4656
- const targetTypeText = this.#typeChecker.typeToString(targetType);
4657
- const explain = (sourceType, targetType, diagnostic) => {
4658
- const sourceTypeText = sourceType != null ? this.#typeChecker.typeToString(sourceType) : "{}";
4659
- const diagnostics = [];
4660
- for (const targetProperty of targetType.getProperties()) {
4661
- const targetPropertyName = targetProperty.getName();
4662
- const sourceProperty = sourceType?.getProperty(targetPropertyName);
4663
- if (!sourceProperty) {
4664
- const text = [
4665
- ExpectDiagnosticText.isNotCompatibleWith(sourceTypeText, targetTypeText),
4666
- ExpectDiagnosticText.doesNotHaveProperty(sourceTypeText, targetPropertyName),
4667
- ];
4668
- const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
4669
- diagnostics.push(diagnostic.extendWith(text, origin));
4670
- continue;
4595
+ explain(matchWorker, sourceNode, targetNode, getArgumentCountText) {
4596
+ const isExpression = nodeBelongsToArgumentList(this.compiler, sourceNode);
4597
+ const argumentCountText = getArgumentCountText?.();
4598
+ const diagnostics = [];
4599
+ if (matchWorker.assertionNode.abilityDiagnostics.size > 0) {
4600
+ for (const diagnostic of matchWorker.assertionNode.abilityDiagnostics) {
4601
+ const text = [this.explainNotText(isExpression, argumentCountText), getDiagnosticMessageText(diagnostic)];
4602
+ let origin;
4603
+ if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, targetNode)) {
4604
+ origin = DiagnosticOrigin.fromAbilityDiagnostic(diagnostic, matchWorker.assertionNode);
4671
4605
  }
4672
- const targetPropertyType = this.#typeChecker.getTypeOfSymbol(targetProperty);
4673
- const sourcePropertyType = this.#typeChecker.getTypeOfSymbol(sourceProperty);
4674
- if (!this.#typeChecker.isTypeAssignableTo(targetPropertyType, sourcePropertyType)) {
4675
- const targetPropertyTypeText = this.#typeChecker.typeToString(targetPropertyType);
4676
- const sourcePropertyTypeText = this.#typeChecker.typeToString(sourcePropertyType);
4677
- const text = [
4678
- ExpectDiagnosticText.isNotAssignableFrom(sourceTypeText, targetTypeText),
4679
- ExpectDiagnosticText.typesOfPropertyAreNotCompatible(targetPropertyName),
4680
- ExpectDiagnosticText.isNotAssignableFrom(sourcePropertyTypeText, targetPropertyTypeText),
4681
- ];
4682
- const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
4683
- diagnostics.push(diagnostic.extendWith(text, origin));
4606
+ else {
4607
+ origin = DiagnosticOrigin.fromAssertion(matchWorker.assertionNode);
4684
4608
  }
4685
- }
4686
- if (sourceType != null) {
4687
- for (const sourceProperty of sourceType.getProperties()) {
4688
- const sourcePropertyName = sourceProperty.getName();
4689
- const targetProperty = targetType.getProperty(sourcePropertyName);
4690
- if (!targetProperty && !this.#isOptionalProperty(sourceProperty)) {
4691
- const text = [
4692
- ExpectDiagnosticText.isNotAssignableFrom(sourceTypeText, targetTypeText),
4693
- ExpectDiagnosticText.requiresProperty(sourceTypeText, sourcePropertyName),
4694
- ];
4695
- diagnostics.push(diagnostic.extendWith(text));
4696
- }
4609
+ let related;
4610
+ if (diagnostic.relatedInformation != null) {
4611
+ related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
4697
4612
  }
4613
+ diagnostics.push(Diagnostic.error(text.flat(), origin).add({ related }));
4698
4614
  }
4699
- if (diagnostics.length === 0) {
4700
- const text = ExpectDiagnosticText.isAssignableFrom(sourceTypeText, targetTypeText);
4701
- diagnostics.push(diagnostic.extendWith(text));
4702
- return { diagnostics, isMatch: true };
4703
- }
4704
- return { diagnostics, isMatch: false };
4705
- };
4706
- if (sourceType != null && isUnionType(this.#compiler, sourceType)) {
4707
- let accumulator = [];
4708
- const isMatch = sourceType.types.some((sourceType) => {
4709
- const text = matchWorker.assertionNode.isNot
4710
- ? ExpectDiagnosticText.isAssignableFrom(sourceTypeText, targetTypeText)
4711
- : ExpectDiagnosticText.isNotAssignableFrom(sourceTypeText, targetTypeText);
4712
- const { diagnostics, isMatch } = explain(sourceType, targetType, diagnostic.extendWith(text));
4713
- if (isMatch) {
4714
- accumulator = diagnostics;
4715
- }
4716
- else {
4717
- accumulator.push(...diagnostics);
4718
- }
4719
- return isMatch;
4720
- });
4721
- return { diagnostics: accumulator, isMatch };
4722
4615
  }
4723
- return explain(sourceType, targetType, diagnostic);
4616
+ else {
4617
+ const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertionNode);
4618
+ diagnostics.push(Diagnostic.error(this.explainText(isExpression, argumentCountText), origin));
4619
+ }
4620
+ return diagnostics;
4724
4621
  }
4622
+ }
4623
+
4624
+ class ToAcceptProps extends AbilityMatcherBase {
4625
+ explainText = ExpectDiagnosticText.acceptsProps;
4626
+ explainNotText = ExpectDiagnosticText.doesNotAcceptProps;
4725
4627
  match(matchWorker, sourceNode, targetNode, onDiagnostics) {
4726
4628
  const diagnostics = [];
4727
4629
  const signatures = matchWorker.getSignatures(sourceNode);
4728
4630
  if (signatures.length === 0) {
4729
4631
  const expectedText = "of a function or class type";
4730
- const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
4731
- ? ExpectDiagnosticText.argumentMustBe("source", expectedText)
4632
+ const text = nodeBelongsToArgumentList(this.compiler, sourceNode)
4633
+ ? ExpectDiagnosticText.argumentMustBe(expectedText)
4732
4634
  : ExpectDiagnosticText.typeArgumentMustBe(expectedText);
4733
4635
  const origin = DiagnosticOrigin.fromNode(sourceNode);
4734
4636
  diagnostics.push(Diagnostic.error(text, origin));
4735
4637
  }
4736
- const targetType = matchWorker.getType(targetNode);
4737
- if (!(targetType.flags & this.#compiler.TypeFlags.Object)) {
4738
- const expectedText = "of an object type";
4739
- const text = ExpectDiagnosticText.argumentMustBe("props", expectedText);
4638
+ if (!this.compiler.isObjectLiteralExpression(targetNode)) {
4639
+ const expectedText = "an object literal with key-value pairs";
4640
+ const text = ExpectDiagnosticText.argumentMustBe(expectedText);
4740
4641
  const origin = DiagnosticOrigin.fromNode(targetNode);
4741
4642
  diagnostics.push(Diagnostic.error(text, origin));
4742
4643
  }
4644
+ else {
4645
+ for (const property of targetNode.properties) {
4646
+ if (!(this.compiler.isPropertyAssignment(property) || this.compiler.isSpreadAssignment(property))) {
4647
+ const text = "Each property must be a key-value pair or a spread element.";
4648
+ const origin = DiagnosticOrigin.fromNode(property);
4649
+ diagnostics.push(Diagnostic.error(text, origin));
4650
+ continue;
4651
+ }
4652
+ if (this.compiler.isPropertyAssignment(property) &&
4653
+ !(this.compiler.isIdentifier(property.name) || this.compiler.isStringLiteral(property.name))) {
4654
+ const text = "Property keys must be static identifiers or string literals.";
4655
+ const origin = DiagnosticOrigin.fromNode(property.name);
4656
+ diagnostics.push(Diagnostic.error(text, origin));
4657
+ }
4658
+ }
4659
+ }
4743
4660
  if (diagnostics.length > 0) {
4744
4661
  onDiagnostics(diagnostics);
4745
4662
  return;
4746
4663
  }
4747
- const isMatch = signatures.some((signature) => {
4748
- const sourceType = matchWorker.getParameterType(signature, 0);
4749
- return this.#checkProperties(sourceType, targetType);
4750
- });
4751
4664
  return {
4752
- explain: () => this.#explain(matchWorker, sourceNode, targetNode),
4753
- isMatch,
4665
+ explain: () => this.explain(matchWorker, sourceNode, targetNode),
4666
+ isMatch: matchWorker.assertionNode.abilityDiagnostics.size === 0,
4754
4667
  };
4755
4668
  }
4756
4669
  }
@@ -5351,7 +5264,7 @@ class ToBeApplicable {
5351
5264
  if (type.getCallSignatures().length === 0) {
5352
5265
  const expectedText = "of a function type";
5353
5266
  const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
5354
- ? ExpectDiagnosticText.argumentMustBe("source", expectedText)
5267
+ ? ExpectDiagnosticText.argumentMustBe(expectedText)
5355
5268
  : ExpectDiagnosticText.typeArgumentMustBe(expectedText);
5356
5269
  const origin = DiagnosticOrigin.fromNode(sourceNode);
5357
5270
  onDiagnostics([Diagnostic.error(text, origin)]);
@@ -5386,55 +5299,6 @@ class ToBeAssignableTo extends RelationMatcherBase {
5386
5299
  }
5387
5300
  }
5388
5301
 
5389
- class AbilityMatcherBase {
5390
- compiler;
5391
- constructor(compiler) {
5392
- this.compiler = compiler;
5393
- }
5394
- getArgumentCountText(nodes) {
5395
- if (nodes.length === 0) {
5396
- return "without arguments";
5397
- }
5398
- if (nodes.length === 1 && nodes[0]?.kind === this.compiler.SyntaxKind.SpreadElement) {
5399
- return "with the given arguments";
5400
- }
5401
- return `with the given argument${nodes.length === 1 ? "" : "s"}`;
5402
- }
5403
- getTypeArgumentCountText(targetNode) {
5404
- if (targetNode.elements.length === 0) {
5405
- return "without type arguments";
5406
- }
5407
- return `with the given type argument${targetNode.elements.length === 1 ? "" : "s"}`;
5408
- }
5409
- explain(matchWorker, sourceNode, targetNode, getArgumentCountText) {
5410
- const isExpression = nodeBelongsToArgumentList(this.compiler, sourceNode);
5411
- const argumentCountText = getArgumentCountText();
5412
- const diagnostics = [];
5413
- if (matchWorker.assertionNode.abilityDiagnostics.size > 0) {
5414
- for (const diagnostic of matchWorker.assertionNode.abilityDiagnostics) {
5415
- const text = [this.explainNotText(isExpression, argumentCountText), getDiagnosticMessageText(diagnostic)];
5416
- let origin;
5417
- if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, targetNode)) {
5418
- origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceNode.getSourceFile(), matchWorker.assertionNode);
5419
- }
5420
- else {
5421
- origin = DiagnosticOrigin.fromAssertion(matchWorker.assertionNode);
5422
- }
5423
- let related;
5424
- if (diagnostic.relatedInformation != null) {
5425
- related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
5426
- }
5427
- diagnostics.push(Diagnostic.error(text.flat(), origin).add({ related }));
5428
- }
5429
- }
5430
- else {
5431
- const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertionNode);
5432
- diagnostics.push(Diagnostic.error(this.explainText(isExpression, argumentCountText), origin));
5433
- }
5434
- return diagnostics;
5435
- }
5436
- }
5437
-
5438
5302
  class ToBeCallableWith extends AbilityMatcherBase {
5439
5303
  explainText = ExpectDiagnosticText.isCallable;
5440
5304
  explainNotText = ExpectDiagnosticText.isNotCallable;
@@ -5443,7 +5307,7 @@ class ToBeCallableWith extends AbilityMatcherBase {
5443
5307
  if (sourceType.getCallSignatures().length === 0) {
5444
5308
  const text = [];
5445
5309
  if (nodeBelongsToArgumentList(this.compiler, sourceNode)) {
5446
- text.push(ExpectDiagnosticText.argumentMustBe("source", "a callable expression"));
5310
+ text.push(ExpectDiagnosticText.argumentMustBe("a callable expression"));
5447
5311
  }
5448
5312
  else {
5449
5313
  text.push(ExpectDiagnosticText.typeArgumentMustBe("a callable type"));
@@ -5470,7 +5334,7 @@ class ToBeConstructableWith extends AbilityMatcherBase {
5470
5334
  if (sourceType.getConstructSignatures().length === 0) {
5471
5335
  const text = [];
5472
5336
  if (nodeBelongsToArgumentList(this.compiler, sourceNode)) {
5473
- text.push(ExpectDiagnosticText.argumentMustBe("source", "a constructable expression"));
5337
+ text.push(ExpectDiagnosticText.argumentMustBe("a constructable expression"));
5474
5338
  }
5475
5339
  else {
5476
5340
  text.push(ExpectDiagnosticText.typeArgumentMustBe("a constructable type"));
@@ -5499,7 +5363,7 @@ class ToBeInstantiableWith extends AbilityMatcherBase {
5499
5363
  this.compiler.isExpressionWithTypeArguments(sourceNode))) {
5500
5364
  let text;
5501
5365
  if (nodeBelongsToArgumentList(this.compiler, sourceNode)) {
5502
- text = ExpectDiagnosticText.argumentMustBe("source", "an instantiable expression");
5366
+ text = ExpectDiagnosticText.argumentMustBe("an instantiable expression");
5503
5367
  }
5504
5368
  else {
5505
5369
  text = ExpectDiagnosticText.typeArgumentMustBe("an instantiable type");
@@ -5530,7 +5394,7 @@ class ToHaveProperty {
5530
5394
  const sourceTypeText = matchWorker.getTypeText(sourceNode);
5531
5395
  const targetType = matchWorker.getType(targetNode);
5532
5396
  let propertyNameText;
5533
- if (isStringOrNumberLiteralType(this.#compiler, targetType)) {
5397
+ if (targetType.flags & (this.#compiler.TypeFlags.StringLiteral | this.#compiler.TypeFlags.NumberLiteral)) {
5534
5398
  propertyNameText = targetType.value.toString();
5535
5399
  }
5536
5400
  else {
@@ -5548,15 +5412,18 @@ class ToHaveProperty {
5548
5412
  !matchWorker.extendsObjectType(sourceType)) {
5549
5413
  const expectedText = "of an object type";
5550
5414
  const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
5551
- ? ExpectDiagnosticText.argumentMustBe("source", expectedText)
5415
+ ? ExpectDiagnosticText.argumentMustBe(expectedText)
5552
5416
  : ExpectDiagnosticText.typeArgumentMustBe(expectedText);
5553
5417
  const origin = DiagnosticOrigin.fromNode(sourceNode);
5554
5418
  diagnostics.push(Diagnostic.error(text, origin));
5555
5419
  }
5556
5420
  const targetType = matchWorker.getType(targetNode);
5557
- if (!(isStringOrNumberLiteralType(this.#compiler, targetType) || isUniqueSymbolType(this.#compiler, targetType))) {
5558
- const expectedText = "of type 'string | number | symbol'";
5559
- const text = ExpectDiagnosticText.argumentMustBe("key", expectedText);
5421
+ if (!(targetType.flags &
5422
+ (this.#compiler.TypeFlags.StringLiteral |
5423
+ this.#compiler.TypeFlags.NumberLiteral |
5424
+ this.#compiler.TypeFlags.UniqueESSymbol))) {
5425
+ const expectedText = "a string, number or symbol";
5426
+ const text = ExpectDiagnosticText.argumentMustBe(expectedText);
5560
5427
  const origin = DiagnosticOrigin.fromNode(targetNode);
5561
5428
  diagnostics.push(Diagnostic.error(text, origin));
5562
5429
  }
@@ -5615,8 +5482,8 @@ class ToRaiseError {
5615
5482
  if (!(this.#compiler.isStringLiteralLike(targetNode) ||
5616
5483
  this.#compiler.isNumericLiteral(targetNode) ||
5617
5484
  this.#compiler.isRegularExpressionLiteral(targetNode))) {
5618
- const expectedText = "a string, number or regular expression literal";
5619
- const text = ExpectDiagnosticText.argumentMustBe("target", expectedText);
5485
+ const expectedText = "a string, number or regular expression";
5486
+ const text = ExpectDiagnosticText.argumentMustBe(expectedText);
5620
5487
  const origin = DiagnosticOrigin.fromNode(targetNode);
5621
5488
  diagnostics.push(Diagnostic.error(text, origin));
5622
5489
  }
@@ -5657,6 +5524,7 @@ class ToRaiseError {
5657
5524
 
5658
5525
  class ExpectService {
5659
5526
  #compiler;
5527
+ #ensure;
5660
5528
  #program;
5661
5529
  #reject;
5662
5530
  toAcceptProps;
@@ -5669,11 +5537,12 @@ class ExpectService {
5669
5537
  toBeInstantiableWith;
5670
5538
  toHaveProperty;
5671
5539
  toRaiseError;
5672
- constructor(compiler, program, reject) {
5540
+ constructor(compiler, program, resolvedConfig) {
5673
5541
  this.#compiler = compiler;
5674
5542
  this.#program = program;
5675
- this.#reject = reject;
5676
- this.toAcceptProps = new ToAcceptProps(compiler, program);
5543
+ this.#ensure = new Ensure(compiler);
5544
+ this.#reject = new Reject(compiler, program, resolvedConfig);
5545
+ this.toAcceptProps = new ToAcceptProps(compiler);
5677
5546
  this.toBe = new ToBe(compiler, program);
5678
5547
  this.toBeApplicable = new ToBeApplicable(compiler);
5679
5548
  this.toBeAssignableFrom = new ToBeAssignableFrom();
@@ -5686,15 +5555,12 @@ class ExpectService {
5686
5555
  }
5687
5556
  match(assertionNode, onDiagnostics) {
5688
5557
  const matcherNameText = assertionNode.matcherNameNode.name.text;
5689
- if (!argumentOrTypeArgumentIsProvided("source", "Source", assertionNode.source[0], assertionNode.node.expression, onDiagnostics)) {
5558
+ if (!this.#ensure.argumentOrTypeArgument(assertionNode.source[0], assertionNode.node.expression, onDiagnostics)) {
5690
5559
  return;
5691
5560
  }
5692
5561
  const matchWorker = new MatchWorker(this.#compiler, this.#program, assertionNode);
5693
5562
  if (!(matcherNameText === "toBeInstantiableWith" || (matcherNameText === "toRaiseError" && !assertionNode.isNot)) &&
5694
- this.#reject.argumentType([
5695
- ["source", assertionNode.source[0]],
5696
- ["target", assertionNode.target?.[0]],
5697
- ], onDiagnostics)) {
5563
+ this.#reject.argumentType([assertionNode.source[0], assertionNode.target?.[0]], onDiagnostics)) {
5698
5564
  return;
5699
5565
  }
5700
5566
  switch (matcherNameText) {
@@ -5702,7 +5568,7 @@ class ExpectService {
5702
5568
  case "toBe":
5703
5569
  case "toBeAssignableFrom":
5704
5570
  case "toBeAssignableTo":
5705
- if (!argumentOrTypeArgumentIsProvided("target", "Target", assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
5571
+ if (!this.#ensure.argumentOrTypeArgument(assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
5706
5572
  return;
5707
5573
  }
5708
5574
  return this[matcherNameText].match(matchWorker, assertionNode.source[0], assertionNode.target[0], onDiagnostics);
@@ -5713,13 +5579,13 @@ class ExpectService {
5713
5579
  case "toRaiseError":
5714
5580
  return this[matcherNameText].match(matchWorker, assertionNode.source[0], assertionNode.target, onDiagnostics);
5715
5581
  case "toBeInstantiableWith": {
5716
- if (!typeArgumentIsProvided(this.#compiler, "Target", assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
5582
+ if (!this.#ensure.typeArgument(assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
5717
5583
  return;
5718
5584
  }
5719
5585
  return this.toBeInstantiableWith.match(matchWorker, assertionNode.source[0], assertionNode.target[0], onDiagnostics);
5720
5586
  }
5721
5587
  case "toHaveProperty":
5722
- if (!argumentIsProvided(this.#compiler, "key", assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
5588
+ if (!this.#ensure.argument(assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
5723
5589
  return;
5724
5590
  }
5725
5591
  return this.toHaveProperty.match(matchWorker, assertionNode.source[0], assertionNode.target[0], onDiagnostics);
@@ -5735,124 +5601,6 @@ class ExpectService {
5735
5601
  }
5736
5602
  }
5737
5603
 
5738
- function capitalize(text) {
5739
- return text.replace(/^./, text.charAt(0).toUpperCase());
5740
- }
5741
-
5742
- class RejectDiagnosticText {
5743
- static argumentCannotBeOfType(argumentNameText, typeText) {
5744
- return `An argument for '${argumentNameText}' cannot be of the '${typeText}' type.`;
5745
- }
5746
- static typeArgumentCannotBeOfType(argumentNameText, typeText) {
5747
- return `A type argument for '${argumentNameText}' cannot be of the '${typeText}' type.`;
5748
- }
5749
- static typeWasRejected(typeText) {
5750
- const optionNameText = `reject${capitalize(typeText)}Type`;
5751
- return [
5752
- `The '${typeText}' type was rejected because the '${optionNameText}' option is enabled.`,
5753
- `If this check is necessary, pass '${typeText}' as the type argument explicitly.`,
5754
- ];
5755
- }
5756
- }
5757
-
5758
- class Reject {
5759
- #compiler;
5760
- #rejectedArgumentTypes = new Set();
5761
- #typeChecker;
5762
- constructor(compiler, program, resolvedConfig) {
5763
- this.#compiler = compiler;
5764
- this.#typeChecker = program.getTypeChecker();
5765
- if (resolvedConfig.rejectAnyType) {
5766
- this.#rejectedArgumentTypes.add("any");
5767
- }
5768
- if (resolvedConfig.rejectNeverType) {
5769
- this.#rejectedArgumentTypes.add("never");
5770
- }
5771
- }
5772
- argumentType(target, onDiagnostics) {
5773
- for (const rejectedType of this.#rejectedArgumentTypes) {
5774
- const allowedKeyword = this.#compiler.SyntaxKind[`${capitalize(rejectedType)}Keyword`];
5775
- if (target.some(([, node]) => node?.kind === allowedKeyword)) {
5776
- continue;
5777
- }
5778
- for (const [name, node] of target) {
5779
- if (!node) {
5780
- continue;
5781
- }
5782
- if (this.#typeChecker.getTypeAtLocation(node).flags & this.#compiler.TypeFlags[capitalize(rejectedType)]) {
5783
- const text = [
5784
- nodeBelongsToArgumentList(this.#compiler, node)
5785
- ? RejectDiagnosticText.argumentCannotBeOfType(name, rejectedType)
5786
- : RejectDiagnosticText.typeArgumentCannotBeOfType(capitalize(name), rejectedType),
5787
- ...RejectDiagnosticText.typeWasRejected(rejectedType),
5788
- ];
5789
- const origin = DiagnosticOrigin.fromNode(node);
5790
- onDiagnostics([Diagnostic.error(text, origin)]);
5791
- return true;
5792
- }
5793
- }
5794
- }
5795
- return false;
5796
- }
5797
- }
5798
-
5799
- class WhenDiagnosticText {
5800
- static actionIsNotSupported(actionNameText) {
5801
- return `The '.${actionNameText}()' action is not supported.`;
5802
- }
5803
- }
5804
-
5805
- class WhenService {
5806
- #compiler;
5807
- #onDiagnostics;
5808
- #reject;
5809
- constructor(compiler, reject, onDiagnostics) {
5810
- this.#compiler = compiler;
5811
- this.#reject = reject;
5812
- this.#onDiagnostics = onDiagnostics;
5813
- }
5814
- action(when) {
5815
- if (!argumentIsProvided(this.#compiler, "target", when.target[0], when.node.expression, this.#onDiagnostics) ||
5816
- this.#reject.argumentType([["target", when.target[0]]], this.#onDiagnostics)) {
5817
- return;
5818
- }
5819
- const actionNameText = when.actionNameNode.name.getText();
5820
- switch (actionNameText) {
5821
- case "isCalledWith":
5822
- break;
5823
- default:
5824
- this.#onActionIsNotSupported(actionNameText, when, this.#onDiagnostics);
5825
- return;
5826
- }
5827
- if (when.abilityDiagnostics.size > 0) {
5828
- const diagnostics = [];
5829
- for (const diagnostic of when.abilityDiagnostics) {
5830
- if (isDiagnosticWithLocation(diagnostic)) {
5831
- const text = getDiagnosticMessageText(diagnostic);
5832
- let origin;
5833
- if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, when.node)) {
5834
- origin = DiagnosticOrigin.fromNodes(when.target);
5835
- }
5836
- else {
5837
- origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), when.node.getSourceFile());
5838
- }
5839
- let related;
5840
- if (diagnostic.relatedInformation != null) {
5841
- related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
5842
- }
5843
- diagnostics.push(Diagnostic.error(text, origin).add({ related }));
5844
- }
5845
- }
5846
- this.#onDiagnostics(diagnostics);
5847
- }
5848
- }
5849
- #onActionIsNotSupported(actionNameText, when, onDiagnostics) {
5850
- const text = WhenDiagnosticText.actionIsNotSupported(actionNameText);
5851
- const origin = DiagnosticOrigin.fromNode(when.actionNameNode.name);
5852
- onDiagnostics([Diagnostic.error(text, origin)]);
5853
- }
5854
- }
5855
-
5856
5604
  class FixmeDiagnosticText {
5857
5605
  static considerRemoving() {
5858
5606
  return "Consider removing the '// @tstyche fixme' directive.";
@@ -5919,7 +5667,6 @@ class TestTreeWalker {
5919
5667
  #onFileDiagnostics;
5920
5668
  #position;
5921
5669
  #resolvedConfig;
5922
- #whenService;
5923
5670
  constructor(compiler, program, resolvedConfig, onFileDiagnostics, options) {
5924
5671
  this.#compiler = compiler;
5925
5672
  this.#resolvedConfig = resolvedConfig;
@@ -5927,9 +5674,7 @@ class TestTreeWalker {
5927
5674
  this.#cancellationToken = options.cancellationToken;
5928
5675
  this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
5929
5676
  this.#position = options.position;
5930
- const reject = new Reject(compiler, program, resolvedConfig);
5931
- this.#expectService = new ExpectService(compiler, program, reject);
5932
- this.#whenService = new WhenService(compiler, reject, onFileDiagnostics);
5677
+ this.#expectService = new ExpectService(compiler, program, resolvedConfig);
5933
5678
  }
5934
5679
  async #resolveRunMode(flags, node) {
5935
5680
  const ifDirective = Directive.getDirectiveRange(this.#compiler, node, "if");
@@ -5974,9 +5719,6 @@ class TestTreeWalker {
5974
5719
  case "expect":
5975
5720
  await this.#visitExpect(node, runModeFlags, parentResult);
5976
5721
  break;
5977
- case "when":
5978
- this.#visitWhen(node);
5979
- break;
5980
5722
  }
5981
5723
  if (fixmeDirective) {
5982
5724
  FixmeService.end(fixmeDirective, node, this.#onFileDiagnostics);
@@ -6078,9 +5820,6 @@ class TestTreeWalker {
6078
5820
  EventEmitter.dispatch(["test:fail", { result: testResult }]);
6079
5821
  }
6080
5822
  }
6081
- #visitWhen(when) {
6082
- this.#whenService.action(when);
6083
- }
6084
5823
  }
6085
5824
 
6086
5825
  class FileRunner {
@@ -6169,7 +5908,7 @@ class FileRunner {
6169
5908
  class Runner {
6170
5909
  #eventEmitter = new EventEmitter();
6171
5910
  #resolvedConfig;
6172
- static version = "7.0.0";
5911
+ static version = "7.2.0";
6173
5912
  constructor(resolvedConfig) {
6174
5913
  this.#resolvedConfig = resolvedConfig;
6175
5914
  }
@@ -6394,4 +6133,4 @@ class Cli {
6394
6133
  }
6395
6134
  }
6396
6135
 
6397
- export { BaseReporter, CancellationReason, CancellationToken, Cli, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Directive, DotReporter, EventEmitter, ExpectResult, FileLocation, FileResult, Line, ListReporter, OptionBrand, OptionGroup, Options, OutputService, Path, ProjectConfigKind, ProjectResult, Result, ResultStatus, Runner, Scribbler, Select, SelectDiagnosticText, SetupReporter, Store, StreamController, SummaryReporter, SuppressedResult, TargetResult, TestResult, Text, Version, WatchReporter, addsText, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, dotText, environmentOptions, fileStatusText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, prologueText, summaryText, testNameText, usesText, waitingForFileChangesText, watchUsageText };
6136
+ export { BaseReporter, CancellationReason, CancellationToken, Cli, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Directive, DotReporter, EventEmitter, ExpectResult, FileLocation, FileResult, Line, ListReporter, MappedDiagnostic, OptionBrand, OptionGroup, Options, OutputService, Path, ProjectConfigKind, ProjectResult, Result, ResultStatus, Runner, Scribbler, Select, SelectDiagnosticText, SetupReporter, Store, StreamController, SummaryReporter, SuppressedResult, TargetResult, TestResult, Text, Version, WatchReporter, addsText, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, dotText, environmentOptions, fileStatusText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, prologueText, summaryText, testNameText, usesText, waitingForFileChangesText, watchUsageText };