tstyche 7.1.0 → 7.2.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.
Files changed (3) hide show
  1. package/dist/api.d.ts +22 -5
  2. package/dist/api.js +578 -636
  3. package/package.json +7 -3
package/dist/api.js CHANGED
@@ -77,43 +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
- }
116
-
117
80
  function diagnosticBelongsToNode(diagnostic, node) {
118
81
  return diagnostic.start != null && diagnostic.start >= node.pos && diagnostic.start <= node.end;
119
82
  }
@@ -131,6 +94,16 @@ function getDiagnosticMessageText(diagnostic) {
131
94
  ? diagnostic.messageText
132
95
  : diagnosticMessageChainToText(diagnostic.messageText);
133
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
+ }
134
107
  function getTextSpanEnd(span) {
135
108
  return span.start + span.length;
136
109
  }
@@ -138,6 +111,29 @@ function isDiagnosticWithLocation(diagnostic) {
138
111
  return diagnostic.file != null && diagnostic.start != null && diagnostic.length != null;
139
112
  }
140
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
+
141
137
  class Diagnostic {
142
138
  category;
143
139
  code;
@@ -190,6 +186,37 @@ var DiagnosticCategory;
190
186
  DiagnosticCategory["Warning"] = "warning";
191
187
  })(DiagnosticCategory || (DiagnosticCategory = {}));
192
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
+
193
220
  class JsonScanner {
194
221
  #end;
195
222
  #position;
@@ -307,15 +334,20 @@ class JsonSourceFile {
307
334
  const result = [0];
308
335
  let position = 0;
309
336
  while (position < this.text.length) {
310
- if (this.text.charAt(position - 1) === "\r") {
311
- position++;
312
- }
313
- if (this.text.charAt(position - 1) === "\n") {
314
- 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;
315
348
  }
316
349
  position++;
317
350
  }
318
- result.push(position);
319
351
  return result;
320
352
  }
321
353
  getLineStarts() {
@@ -417,15 +449,31 @@ class ConfigDiagnosticText {
417
449
  class Environment {
418
450
  static resolve() {
419
451
  return {
452
+ fetchRetries: Environment.#resolveFetchRetries(),
453
+ fetchTimeout: Environment.#resolveFetchTimeout(),
420
454
  isCi: Environment.#resolveIsCi(),
421
455
  noColor: Environment.#resolveNoColor(),
422
456
  noInteractive: Environment.#resolveNoInteractive(),
423
457
  npmRegistry: Environment.#resolveNpmRegistry(),
424
458
  storePath: Environment.#resolveStorePath(),
425
- timeout: Environment.#resolveTimeout(),
426
459
  typescriptModule: Environment.#resolveTypeScriptModule(),
427
460
  };
428
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
+ }
429
477
  static #resolveIsCi() {
430
478
  if (process.env["CI"] != null) {
431
479
  return process.env["CI"] !== "";
@@ -468,12 +516,6 @@ class Environment {
468
516
  }
469
517
  return Path.resolve(os.homedir(), ".local", "share", "TSTyche");
470
518
  }
471
- static #resolveTimeout() {
472
- if (process.env["TSTYCHE_TIMEOUT"] != null) {
473
- return Number.parseFloat(process.env["TSTYCHE_TIMEOUT"]);
474
- }
475
- return 30;
476
- }
477
519
  static #resolveTypeScriptModule() {
478
520
  let specifier = "typescript";
479
521
  if (process.env["TSTYCHE_TYPESCRIPT_MODULE"] != null) {
@@ -522,6 +564,10 @@ class Version {
522
564
  }
523
565
  }
524
566
 
567
+ function sleep(delay) {
568
+ return new Promise((resolve) => setTimeout(resolve, delay));
569
+ }
570
+
525
571
  class StoreDiagnosticText {
526
572
  static cannotAddTypeScriptPackage(tag) {
527
573
  return `Cannot add the 'typescript' package for the '${tag}' tag.`;
@@ -535,13 +581,13 @@ class StoreDiagnosticText {
535
581
  static failedToUpdateMetadata(registry) {
536
582
  return `Failed to update metadata of the 'typescript' package from '${registry}'.`;
537
583
  }
538
- static maybeNetworkConnectionIssue() {
539
- return "Might be there is an issue with the registry or the network connection.";
540
- }
541
584
  static maybeOutdatedResolution(tag) {
542
585
  return `The resolution of the '${tag}' tag may be outdated.`;
543
586
  }
544
- static requestFailedWithStatusCode(code) {
587
+ static networkFailure(retries) {
588
+ return `The network connection failed after ${retries + 1} attempts.`;
589
+ }
590
+ static requestFailed(code) {
545
591
  return `The request failed with status code ${code}.`;
546
592
  }
547
593
  static requestTimeoutWasExceeded(timeout) {
@@ -554,29 +600,48 @@ class StoreDiagnosticText {
554
600
 
555
601
  class Fetcher {
556
602
  #onDiagnostics;
603
+ #retries;
557
604
  #timeout;
558
- constructor(onDiagnostics, timeout) {
605
+ constructor(onDiagnostics, retries, timeout) {
559
606
  this.#onDiagnostics = onDiagnostics;
607
+ this.#retries = retries;
560
608
  this.#timeout = timeout;
561
609
  }
562
610
  async get(request, diagnostic, options) {
563
- try {
564
- const response = await fetch(request, { signal: AbortSignal.timeout(this.#timeout) });
565
- if (!response.ok) {
566
- !options?.suppressErrors &&
567
- this.#onDiagnostics(diagnostic.extendWith(StoreDiagnosticText.requestFailedWithStatusCode(response.status)));
568
- return;
611
+ let delay = 3000;
612
+ request.headers.set("User-Agent", `tstyche/${"7.2.1"} ${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
+ }
569
631
  }
570
- return response;
571
- }
572
- catch (error) {
573
- if (error instanceof Error && error.name === "TimeoutError") {
574
- !options?.suppressErrors &&
575
- 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
+ }
576
641
  }
577
- else {
578
- !options?.suppressErrors &&
579
- this.#onDiagnostics(diagnostic.extendWith(StoreDiagnosticText.maybeNetworkConnectionIssue()));
642
+ if (!isLastAttempt) {
643
+ await sleep(delay);
644
+ delay *= 2;
580
645
  }
581
646
  }
582
647
  return;
@@ -620,17 +685,14 @@ class LockService {
620
685
  const waitStartTime = Date.now();
621
686
  while (isLocked) {
622
687
  if (Date.now() - waitStartTime > this.#timeout) {
623
- this.#onDiagnostics(diagnostic.extendWith(StoreDiagnosticText.lockWaitTimeoutWasExceeded(this.#timeout)));
688
+ this.#onDiagnostics(diagnostic().extendWith(StoreDiagnosticText.lockWaitTimeoutWasExceeded(this.#timeout)));
624
689
  break;
625
690
  }
626
- await this.#sleep(1000);
691
+ await sleep(1000);
627
692
  isLocked = existsSync(lockFilePath);
628
693
  }
629
694
  return isLocked;
630
695
  }
631
- #sleep(delay) {
632
- return new Promise((resolve) => setTimeout(resolve, delay));
633
- }
634
696
  }
635
697
 
636
698
  class Manifest {
@@ -711,7 +773,7 @@ class ManifestService {
711
773
  return manifest;
712
774
  }
713
775
  async #load(options) {
714
- const diagnostic = Diagnostic.error(StoreDiagnosticText.failedToFetchMetadata(this.#npmRegistry));
776
+ const diagnostic = () => Diagnostic.error(StoreDiagnosticText.failedToFetchMetadata(this.#npmRegistry));
715
777
  const request = new Request(new URL("typescript", this.#npmRegistry), {
716
778
  headers: {
717
779
  ["Accept"]: "application/vnd.npm.install-v1+json;q=1.0, application/json;q=0.8, */*",
@@ -848,7 +910,7 @@ class PackageService {
848
910
  }
849
911
  async ensure(packageVersion, manifest) {
850
912
  const packagePath = Path.join(this.#storePath, `typescript@${packageVersion}`);
851
- const diagnostic = Diagnostic.error(StoreDiagnosticText.failedToFetchPackage(packageVersion));
913
+ const diagnostic = () => Diagnostic.error(StoreDiagnosticText.failedToFetchPackage(packageVersion));
852
914
  if (await this.#lockService.isLocked(packagePath, diagnostic)) {
853
915
  return;
854
916
  }
@@ -889,12 +951,13 @@ class Store {
889
951
  static #manifestService;
890
952
  static #packageService;
891
953
  static #npmRegistry = environmentOptions.npmRegistry;
954
+ static #fetchRetries = environmentOptions.fetchRetries;
955
+ static #fetchTimeout = environmentOptions.fetchTimeout * 1000;
892
956
  static #storePath = environmentOptions.storePath;
893
957
  static #supportedTags;
894
- static #timeout = environmentOptions.timeout * 1000;
895
958
  static {
896
- Store.#fetcher = new Fetcher(Store.#onDiagnostics, Store.#timeout);
897
- 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);
898
961
  Store.#packageService = new PackageService(Store.#storePath, Store.#fetcher, Store.#lockService);
899
962
  Store.#manifestService = new ManifestService(Store.#storePath, Store.#npmRegistry, Store.#fetcher);
900
963
  }
@@ -2255,8 +2318,8 @@ function CodeFrameText({ diagnosticCategory, diagnosticOrigin, options }) {
2255
2318
  const linesBelow = options?.linesBelow ?? 3;
2256
2319
  const showBreadcrumbs = options?.showBreadcrumbs ?? true;
2257
2320
  const lineMap = diagnosticOrigin.sourceFile.getLineStarts();
2258
- const { character: firstMarkedLineCharacter, line: firstMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.start);
2259
- 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);
2260
2323
  const firstLine = Math.max(firstMarkedLine - linesAbove, 0);
2261
2324
  const lastLine = Math.min(lastMarkedLine + linesBelow, lineMap.length - 1);
2262
2325
  const gutterWidth = (lastLine + 1).toString().length + 2;
@@ -2278,12 +2341,12 @@ function CodeFrameText({ diagnosticCategory, diagnosticOrigin, options }) {
2278
2341
  codeFrame.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumber: index + 1, lineNumberColor: highlightColor, lineText: lineText }));
2279
2342
  if (index === firstMarkedLine) {
2280
2343
  const squiggleLength = index === lastMarkedLine
2281
- ? lastMarkedLineCharacter - firstMarkedLineCharacter
2282
- : lineText.length - firstMarkedLineCharacter;
2283
- 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 }));
2284
2347
  }
2285
2348
  else if (index === lastMarkedLine) {
2286
- codeFrame.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleColor: highlightColor, squiggleWidth: lastMarkedLineCharacter }));
2349
+ codeFrame.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleColor: highlightColor, squiggleWidth: lastMarkedCharacter }));
2287
2350
  }
2288
2351
  else {
2289
2352
  codeFrame.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleColor: highlightColor, squiggleWidth: lineText.length }));
@@ -2297,7 +2360,7 @@ function CodeFrameText({ diagnosticCategory, diagnosticOrigin, options }) {
2297
2360
  if (showBreadcrumbs && diagnosticOrigin.assertionNode != null) {
2298
2361
  breadcrumbs = jsx(BreadcrumbsText, { ancestor: diagnosticOrigin.assertionNode.parent });
2299
2362
  }
2300
- 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] }));
2301
2364
  return (jsx(Text, { children: [codeFrame, jsx(Line, {}), location] }));
2302
2365
  }
2303
2366
 
@@ -3160,7 +3223,7 @@ class Select {
3160
3223
  if (testFilePaths.length === 0) {
3161
3224
  Select.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereSelected(resolvedConfig)));
3162
3225
  }
3163
- return testFilePaths.sort();
3226
+ return testFilePaths.sort((a, b) => a.localeCompare(b));
3164
3227
  }
3165
3228
  static async #visitDirectory(currentPath, testFilePaths, matchPatterns, resolvedConfig) {
3166
3229
  const targetPath = Path.join(resolvedConfig.rootPath, currentPath);
@@ -3290,7 +3353,7 @@ class WatchService {
3290
3353
  while (!cancellationToken.isCancellationRequested()) {
3291
3354
  const testFiles = await debounce.schedule();
3292
3355
  if (testFiles.length > 0) {
3293
- yield testFiles;
3356
+ yield testFiles.sort((a, b) => a.path.localeCompare(b.path));
3294
3357
  }
3295
3358
  }
3296
3359
  }
@@ -3335,11 +3398,10 @@ class AbilityLayer {
3335
3398
  this.#editor = editor;
3336
3399
  }
3337
3400
  #belongsToNode(node, diagnostic) {
3338
- return (diagnosticBelongsToNode(diagnostic, node.matcherNode) ||
3339
- diagnosticBelongsToNode(diagnostic, node.source));
3401
+ return diagnosticBelongsToNode(diagnostic, node.matcherNode) || diagnosticBelongsToNode(diagnostic, node.source);
3340
3402
  }
3341
3403
  close(diagnostics) {
3342
- if (diagnostics != null && this.#nodes.length > 0) {
3404
+ if (diagnostics.length > 0 && this.#nodes.length > 0) {
3343
3405
  this.#nodes.reverse();
3344
3406
  for (const diagnostic of diagnostics) {
3345
3407
  this.#mapToNodes(diagnostic);
@@ -3362,12 +3424,63 @@ class AbilityLayer {
3362
3424
  const matcherNameEnd = expect.matcherNameNode.getEnd();
3363
3425
  const matcherNodeEnd = expect.matcherNode.getEnd();
3364
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.isIdentifier(sourceNode) ||
3434
+ this.#compiler.isPropertyAccessExpression(sourceNode) ||
3435
+ this.#compiler.isTypeReferenceNode(sourceNode) ||
3436
+ this.#compiler.isExpressionWithTypeArguments(sourceNode)) ||
3437
+ !/^[A-Z_$]/.test(sourceNode.getText()[0]) ||
3438
+ !this.#compiler.isObjectLiteralExpression(targetNode) ||
3439
+ !targetNode.properties.every((property) => (this.#compiler.isPropertyAssignment(property) &&
3440
+ (this.#compiler.isIdentifier(property.name) || this.#compiler.isStringLiteral(property.name))) ||
3441
+ this.#compiler.isSpreadAssignment(property))) {
3442
+ return;
3443
+ }
3444
+ const sourceText = sourceNode.getText();
3445
+ if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
3446
+ this.#editor
3447
+ .eraseTrailingComma(expect.source)
3448
+ .erase(expectStart, matcherNodeEnd)
3449
+ .update(expectStart, matcherNameEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? ";" : "")
3450
+ .update(sourceNode.getStart() - 1, sourceNode.getEnd(), `<${sourceText}`);
3451
+ }
3452
+ else {
3453
+ const id = ["SC", expectStart, Date.now().toString(36)].join("_");
3454
+ this.#editor
3455
+ .erase(expectStart, matcherNodeEnd)
3456
+ .update(expectStart, matcherNameEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3457
+ ? `;const ${id} = undefined as any as ${sourceText};<${id}`
3458
+ : `const ${id} = undefined as any as ${sourceText};<${id}`);
3459
+ }
3460
+ for (const property of targetNode.properties) {
3461
+ if (this.#compiler.isPropertyAssignment(property)) {
3462
+ this.#editor
3463
+ .update(property.name.getStart(), property.name.getEnd(), this.#compiler.isStringLiteral(property.name)
3464
+ ? ` ${property.name.getText().slice(1, -1)} `
3465
+ : property.name.getText())
3466
+ .insert(property.initializer.getStart(), "={")
3467
+ .update(property.initializer.getStart(), property.initializer.getEnd(), property.initializer.getText())
3468
+ .insert(property.initializer.getEnd(), "}");
3469
+ continue;
3470
+ }
3471
+ if (this.#compiler.isSpreadAssignment(property)) {
3472
+ this.#editor
3473
+ .insert(property.getStart(), "{")
3474
+ .update(property.getStart(), property.getEnd(), property.getText())
3475
+ .insert(property.getEnd(), "}");
3476
+ }
3477
+ }
3478
+ this.#editor.insert(matcherNodeEnd, "/>");
3479
+ break;
3480
+ }
3365
3481
  case "toBeApplicable":
3366
3482
  this.#nodes.push(expect);
3367
- this.#editor.replaceRanges([
3368
- [expectStart, expectExpressionEnd],
3369
- [expectEnd, matcherNameEnd],
3370
- ]);
3483
+ this.#editor.erase(expectStart, expectExpressionEnd).erase(expectEnd, matcherNameEnd);
3371
3484
  break;
3372
3485
  case "toBeCallableWith": {
3373
3486
  this.#nodes.push(expect);
@@ -3376,27 +3489,16 @@ class AbilityLayer {
3376
3489
  return;
3377
3490
  }
3378
3491
  if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
3379
- this.#editor.eraseTrailingComma(expect.source);
3380
- this.#editor.replaceRanges([
3381
- [
3382
- expectStart,
3383
- expectExpressionEnd,
3384
- nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? ";" : "",
3385
- ],
3386
- [expectEnd, matcherNameEnd],
3387
- ]);
3492
+ this.#editor
3493
+ .eraseTrailingComma(expect.source)
3494
+ .update(expectStart, expectExpressionEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? ";" : "")
3495
+ .erase(expectEnd, matcherNameEnd);
3388
3496
  }
3389
3497
  else {
3390
3498
  const sourceText = sourceNode.getFullText();
3391
- this.#editor.replaceRanges([
3392
- [
3393
- expectStart,
3394
- matcherNameEnd,
3395
- nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3396
- ? `;(undefined as any as ${sourceText})`
3397
- : `(undefined as any as ${sourceText})`,
3398
- ],
3399
- ]);
3499
+ this.#editor.update(expectStart, matcherNameEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3500
+ ? `;(undefined as any as ${sourceText})`
3501
+ : `(undefined as any as ${sourceText})`);
3400
3502
  }
3401
3503
  break;
3402
3504
  }
@@ -3407,27 +3509,16 @@ class AbilityLayer {
3407
3509
  return;
3408
3510
  }
3409
3511
  if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
3410
- this.#editor.eraseTrailingComma(expect.source);
3411
- this.#editor.replaceRanges([
3412
- [
3413
- expectStart,
3414
- expectExpressionEnd,
3415
- nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? "; new" : "new",
3416
- ],
3417
- [expectEnd, matcherNameEnd],
3418
- ]);
3512
+ this.#editor
3513
+ .eraseTrailingComma(expect.source)
3514
+ .update(expectStart, expectExpressionEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? "; new" : "new")
3515
+ .erase(expectEnd, matcherNameEnd);
3419
3516
  }
3420
3517
  else {
3421
3518
  const sourceText = sourceNode.getFullText();
3422
- this.#editor.replaceRanges([
3423
- [
3424
- expectStart,
3425
- matcherNameEnd,
3426
- nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3427
- ? `;new (undefined as any as ${sourceText})`
3428
- : `new (undefined as any as ${sourceText})`,
3429
- ],
3430
- ]);
3519
+ this.#editor.update(expectStart, matcherNameEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3520
+ ? `;new (undefined as any as ${sourceText})`
3521
+ : `new (undefined as any as ${sourceText})`);
3431
3522
  }
3432
3523
  break;
3433
3524
  }
@@ -3439,39 +3530,26 @@ class AbilityLayer {
3439
3530
  return;
3440
3531
  }
3441
3532
  if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
3442
- this.#editor.eraseTrailingComma(expect.source);
3443
- this.#editor.replaceRanges([
3444
- [
3445
- expectStart,
3446
- expectExpressionEnd,
3447
- nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? ";" : "",
3448
- ],
3449
- [expectEnd, matcherNodeEnd],
3450
- ]);
3533
+ this.#editor
3534
+ .eraseTrailingComma(expect.source)
3535
+ .update(expectStart, expectExpressionEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? ";" : "")
3536
+ .erase(expectEnd, matcherNodeEnd);
3451
3537
  if (this.#compiler.isExpressionWithTypeArguments(sourceNode)) {
3452
- this.#editor.replaceRanges([[sourceNode.expression.getEnd(), sourceNode.getEnd()]]);
3538
+ this.#editor.erase(sourceNode.expression.getEnd(), sourceNode.getEnd());
3453
3539
  }
3454
3540
  }
3455
3541
  else {
3456
3542
  const sourceText = this.#compiler.isTypeReferenceNode(sourceNode)
3457
- ? sourceNode.typeName.getText()
3458
- : sourceNode.getText();
3459
- this.#editor.replaceRanges([
3460
- [
3461
- expectStart,
3462
- matcherNodeEnd,
3463
- nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3464
- ? `;undefined as any as ${sourceText}`
3465
- : `undefined as any as ${sourceText}`,
3466
- ],
3467
- ]);
3543
+ ? sourceNode.typeName.getFullText()
3544
+ : sourceNode.getFullText();
3545
+ this.#editor.update(expectStart, matcherNodeEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3546
+ ? `;undefined as any as ${sourceText}`
3547
+ : `undefined as any as ${sourceText}`);
3468
3548
  }
3469
3549
  if (targetNode != null) {
3470
3550
  const targetText = targetNode.getText().slice(1, -1);
3471
- if (targetText.trim().length > 1) {
3472
- this.#editor.replaceRanges([
3473
- [targetNode.getFullStart(), targetNode.getEnd(), `<${targetText}>`.padStart(targetNode.getFullWidth())],
3474
- ]);
3551
+ if (targetText.trim().length > 0) {
3552
+ this.#editor.update(targetNode.getFullStart(), targetNode.getEnd(), `<${targetText}>`.padStart(targetNode.getFullWidth()));
3475
3553
  }
3476
3554
  }
3477
3555
  break;
@@ -3483,30 +3561,21 @@ class AbilityLayer {
3483
3561
  if (!sourceNode || !targetNode) {
3484
3562
  return;
3485
3563
  }
3486
- const sourceText = sourceNode.getFullText();
3487
- const targetText = targetNode.getFullText();
3488
3564
  if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
3489
- this.#editor.eraseTrailingComma(expect.source);
3490
- this.#editor.replaceRanges([
3491
- [
3492
- expectStart,
3493
- matcherNodeEnd,
3494
- nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3495
- ? `;(${sourceText})[${targetText}]`
3496
- : `(${sourceText})[${targetText}]`,
3497
- ],
3498
- ]);
3565
+ this.#editor
3566
+ .eraseTrailingComma(expect.source)
3567
+ .update(expectStart, expectExpressionEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? ";" : "")
3568
+ .erase(expectEnd, matcherNodeEnd);
3499
3569
  }
3500
3570
  else {
3501
- this.#editor.replaceRanges([
3502
- [
3503
- expectStart,
3504
- matcherNodeEnd,
3505
- nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3506
- ? `;(undefined as any as ${sourceText})[${targetText}]`
3507
- : `(undefined as any as ${sourceText})[${targetText}]`,
3508
- ],
3509
- ]);
3571
+ const sourceText = sourceNode.getFullText();
3572
+ this.#editor.update(expectStart, matcherNodeEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3573
+ ? `;(undefined as any as ${sourceText})`
3574
+ : `(undefined as any as ${sourceText})`);
3575
+ }
3576
+ const targetText = targetNode.getText();
3577
+ if (targetText.trim().length > 0) {
3578
+ this.#editor.update(targetNode.getFullStart() - 1, targetNode.getEnd() + 1, `[${targetText}]`.padStart(targetNode.getFullWidth()));
3510
3579
  }
3511
3580
  break;
3512
3581
  }
@@ -3514,65 +3583,6 @@ class AbilityLayer {
3514
3583
  }
3515
3584
  }
3516
3585
 
3517
- class SourceTextEditor {
3518
- #filePath = "";
3519
- #sourceFile;
3520
- #text = "";
3521
- open(sourceFile) {
3522
- this.#sourceFile = sourceFile;
3523
- this.#filePath = sourceFile.fileName;
3524
- this.#text = sourceFile.text;
3525
- }
3526
- close() {
3527
- if (this.#sourceFile != null) {
3528
- SourceService.set(this.#sourceFile);
3529
- this.#sourceFile = undefined;
3530
- }
3531
- this.#filePath = "";
3532
- this.#text = "";
3533
- }
3534
- eraseTrailingComma(node) {
3535
- if (node.hasTrailingComma) {
3536
- this.replaceRange(node.end - 1, node.end);
3537
- }
3538
- }
3539
- #getErasedRange(start, end) {
3540
- if (this.#text.indexOf("\n", start) >= end) {
3541
- return " ".repeat(end - start);
3542
- }
3543
- const text = [];
3544
- for (let index = start; index < end; index++) {
3545
- const character = this.#text.charAt(index);
3546
- switch (character) {
3547
- case "\n":
3548
- case "\r":
3549
- text.push(character);
3550
- break;
3551
- default:
3552
- text.push(" ");
3553
- }
3554
- }
3555
- return text.join("");
3556
- }
3557
- getFilePath() {
3558
- return this.#filePath;
3559
- }
3560
- getText() {
3561
- return this.#text;
3562
- }
3563
- replaceRange(start, end, replacement) {
3564
- const rangeText = replacement != null
3565
- ? `${replacement}${this.#getErasedRange(start, end).slice(replacement.length)}`
3566
- : this.#getErasedRange(start, end);
3567
- this.#text = `${this.#text.slice(0, start)}${rangeText}${this.#text.slice(end)}`;
3568
- }
3569
- replaceRanges(ranges) {
3570
- for (const [start, end, replacement] of ranges) {
3571
- this.replaceRange(start, end, replacement);
3572
- }
3573
- }
3574
- }
3575
-
3576
3586
  class SuppressedLayer {
3577
3587
  #compiler;
3578
3588
  #editor;
@@ -3645,7 +3655,7 @@ class SuppressedLayer {
3645
3655
  }
3646
3656
  for (const suppressedError of suppressedErrors) {
3647
3657
  const { start, end } = suppressedError.directive;
3648
- this.#editor.replaceRange(start + 2, end);
3658
+ this.#editor.erase(start, end);
3649
3659
  if (this.#suppressedErrorsMap != null) {
3650
3660
  const { line } = tree.sourceFile.getLineAndCharacterOfPosition(start);
3651
3661
  this.#suppressedErrorsMap.set(line, suppressedError);
@@ -3654,9 +3664,88 @@ class SuppressedLayer {
3654
3664
  }
3655
3665
  }
3656
3666
 
3667
+ class TextEditor {
3668
+ #filePath = "";
3669
+ #offset = 0;
3670
+ #offsets = [];
3671
+ #text = "";
3672
+ open(sourceFile) {
3673
+ this.#filePath = sourceFile.fileName;
3674
+ this.#text = sourceFile.text;
3675
+ this.#offset = 0;
3676
+ this.#offsets = [];
3677
+ }
3678
+ close() {
3679
+ this.#filePath = "";
3680
+ this.#text = "";
3681
+ this.#offset = 0;
3682
+ this.#offsets = [];
3683
+ }
3684
+ erase(start, end) {
3685
+ this.#text =
3686
+ this.#text.slice(0, start + this.#offset) +
3687
+ this.#getErased(start + this.#offset, end + this.#offset) +
3688
+ this.#text.slice(end + this.#offset);
3689
+ return this;
3690
+ }
3691
+ eraseTrailingComma(node) {
3692
+ if (node.hasTrailingComma) {
3693
+ this.erase(node.end - 1, node.end);
3694
+ }
3695
+ return this;
3696
+ }
3697
+ insert(position, text) {
3698
+ this.update(position, position, text);
3699
+ return this;
3700
+ }
3701
+ #getErased(start, end) {
3702
+ if (this.#text.indexOf("\n", start) >= end) {
3703
+ return " ".repeat(end - start);
3704
+ }
3705
+ const text = [];
3706
+ for (let index = start; index < end; index++) {
3707
+ const character = this.#text.charAt(index);
3708
+ switch (character) {
3709
+ case "\n":
3710
+ case "\r":
3711
+ text.push(character);
3712
+ break;
3713
+ default:
3714
+ text.push(" ");
3715
+ }
3716
+ }
3717
+ return text.join("");
3718
+ }
3719
+ getFilePath() {
3720
+ return this.#filePath;
3721
+ }
3722
+ getOffsets() {
3723
+ return this.#offsets;
3724
+ }
3725
+ getText() {
3726
+ return this.#text;
3727
+ }
3728
+ #setOffset(start, end, text) {
3729
+ const diff = text.length - (end - start);
3730
+ if (diff > 0) {
3731
+ this.#offset += diff;
3732
+ this.#offsets.push({ position: end, diff });
3733
+ }
3734
+ }
3735
+ update(start, end, text) {
3736
+ this.#text =
3737
+ this.#text.slice(0, start + this.#offset) +
3738
+ text +
3739
+ this.#getErased(start + this.#offset, end + this.#offset).slice(text.length) +
3740
+ this.#text.slice(end + this.#offset);
3741
+ this.#setOffset(start, end, text);
3742
+ return this;
3743
+ }
3744
+ }
3745
+
3657
3746
  class Layers {
3658
3747
  #abilityLayer;
3659
- #editor = new SourceTextEditor();
3748
+ #editor = new TextEditor();
3660
3749
  #projectService;
3661
3750
  #suppressedDiagnostics;
3662
3751
  #suppressedLayer;
@@ -3665,14 +3754,23 @@ class Layers {
3665
3754
  this.#abilityLayer = new AbilityLayer(compiler, this.#editor);
3666
3755
  this.#suppressedLayer = new SuppressedLayer(compiler, this.#editor, resolvedConfig);
3667
3756
  }
3668
- close() {
3669
- let isSeenDiagnostic;
3757
+ close(tree) {
3758
+ let seenDiagnostics = [];
3670
3759
  if (this.#suppressedDiagnostics != null) {
3671
- const seenDiagnostics = this.#suppressedDiagnostics;
3760
+ seenDiagnostics = this.#suppressedDiagnostics;
3672
3761
  this.#suppressedDiagnostics = undefined;
3673
- isSeenDiagnostic = (diagnostic) => !seenDiagnostics.some((seenDiagnostic) => compareDiagnostics(diagnostic, seenDiagnostic));
3674
3762
  }
3675
- const abilityDiagnostics = this.#projectService.getDiagnostics(this.#editor.getFilePath(), this.#editor.getText(), isSeenDiagnostic);
3763
+ const diagnostics = this.#projectService.getDiagnostics(this.#editor.getFilePath(), this.#editor.getText());
3764
+ const offsets = this.#editor.getOffsets();
3765
+ const abilityDiagnostics = [];
3766
+ if (diagnostics != null) {
3767
+ for (const diagnostic of diagnostics) {
3768
+ const mappedDiagnostic = new MappedDiagnostic(tree.sourceFile, diagnostic, offsets);
3769
+ if (!seenDiagnostics.some((seenDiagnostic) => compareDiagnostics(mappedDiagnostic, seenDiagnostic))) {
3770
+ abilityDiagnostics.push(mappedDiagnostic);
3771
+ }
3772
+ }
3773
+ }
3676
3774
  this.#abilityLayer.close(abilityDiagnostics);
3677
3775
  this.#editor.close();
3678
3776
  }
@@ -3680,7 +3778,7 @@ class Layers {
3680
3778
  this.#editor.open(tree.sourceFile);
3681
3779
  this.#suppressedLayer.open(tree);
3682
3780
  this.#suppressedDiagnostics = this.#projectService.getDiagnostics(this.#editor.getFilePath(), this.#editor.getText());
3683
- this.#suppressedLayer.close(this.#suppressedDiagnostics);
3781
+ this.#suppressedLayer.close(this.#suppressedDiagnostics?.map((diagnostic) => new MappedDiagnostic(tree.sourceFile, diagnostic)));
3684
3782
  }
3685
3783
  visit(node) {
3686
3784
  this.#abilityLayer.visitExpect(node);
@@ -3927,7 +4025,7 @@ class CollectService {
3927
4025
  this.#layers.open(testTree);
3928
4026
  this.#identifierLookup.open();
3929
4027
  this.#collectTestTreeNodes(sourceFile, testTree, testTree);
3930
- this.#layers.close();
4028
+ this.#layers.close(testTree);
3931
4029
  EventEmitter.dispatch(["collect:end", { tree: testTree }]);
3932
4030
  return testTree;
3933
4031
  }
@@ -4061,7 +4159,6 @@ class ProjectService {
4061
4159
  }
4062
4160
  closeFile(filePath) {
4063
4161
  this.#service.closeClientFile(filePath);
4064
- SourceService.delete(filePath);
4065
4162
  }
4066
4163
  #getDefaultCompilerOptions() {
4067
4164
  const defaultCompilerOptions = {
@@ -4091,14 +4188,10 @@ class ProjectService {
4091
4188
  }
4092
4189
  return project;
4093
4190
  }
4094
- getDiagnostics(filePath, sourceText, shouldInclude) {
4191
+ getDiagnostics(filePath, sourceText) {
4095
4192
  this.openFile(filePath, sourceText);
4096
4193
  const languageService = this.getLanguageService(filePath);
4097
- const diagnostics = languageService?.getSemanticDiagnostics(filePath);
4098
- if (diagnostics != null && shouldInclude != null) {
4099
- return diagnostics.filter(shouldInclude);
4100
- }
4101
- return diagnostics;
4194
+ return languageService?.getSemanticDiagnostics(filePath);
4102
4195
  }
4103
4196
  getLanguageService(filePath) {
4104
4197
  const project = this.getDefaultProject(filePath);
@@ -4263,63 +4356,131 @@ class SuppressedService {
4263
4356
  }
4264
4357
  }
4265
4358
 
4266
- class EnsureDiagnosticText {
4267
- static argumentMustBeProvided(argumentNameText) {
4268
- return `An argument for '${argumentNameText}' must be provided.`;
4269
- }
4270
- static argumentOrTypeArgumentMustBeProvided(argumentNameText, typeArgumentNameText) {
4271
- return `An argument for '${argumentNameText}' or type argument for '${typeArgumentNameText}' must be provided.`;
4272
- }
4273
- static typeArgumentMustBeProvided(typeArgumentNameText) {
4274
- return `Type argument for '${typeArgumentNameText}' must be provided.`;
4275
- }
4359
+ function capitalize(text) {
4360
+ return text.replace(/^./, text.charAt(0).toUpperCase());
4276
4361
  }
4277
4362
 
4278
- function argumentIsProvided(compiler, argumentNameText, node, enclosingNode, onDiagnostics) {
4279
- if (!node || !nodeBelongsToArgumentList(compiler, node)) {
4280
- const text = EnsureDiagnosticText.argumentMustBeProvided(argumentNameText);
4281
- const origin = DiagnosticOrigin.fromNode(enclosingNode);
4282
- onDiagnostics([Diagnostic.error(text, origin)]);
4283
- return false;
4363
+ class RejectDiagnosticText {
4364
+ static argumentCannotBeOfType(typeText) {
4365
+ return `The argument cannot be of the '${typeText}' type.`;
4284
4366
  }
4285
- return true;
4286
- }
4287
-
4288
- function argumentOrTypeArgumentIsProvided(argumentNameText, typeArgumentNameText, node, enclosingNode, onDiagnostics) {
4289
- if (!node) {
4290
- const text = EnsureDiagnosticText.argumentOrTypeArgumentMustBeProvided(argumentNameText, typeArgumentNameText);
4291
- const origin = DiagnosticOrigin.fromNode(enclosingNode);
4292
- onDiagnostics([Diagnostic.error(text, origin)]);
4293
- return false;
4367
+ static typeArgumentCannotBeOfType(typeText) {
4368
+ return `The type argument cannot be of the '${typeText}' type.`;
4294
4369
  }
4295
- return true;
4296
- }
4297
-
4298
- function typeArgumentIsProvided(compiler, typeArgumentNameText, node, enclosingNode, onDiagnostics) {
4299
- if (!node || nodeBelongsToArgumentList(compiler, node)) {
4300
- const text = EnsureDiagnosticText.typeArgumentMustBeProvided(typeArgumentNameText);
4301
- const origin = DiagnosticOrigin.fromNode(enclosingNode);
4302
- onDiagnostics([Diagnostic.error(text, origin)]);
4303
- return false;
4370
+ static typeWasRejected(typeText) {
4371
+ const optionNameText = `reject${capitalize(typeText)}Type`;
4372
+ return [
4373
+ `The '${typeText}' type was rejected because the '${optionNameText}' option is enabled.`,
4374
+ `If this check is necessary, pass '${typeText}' as the type argument explicitly.`,
4375
+ ];
4304
4376
  }
4305
- return true;
4306
4377
  }
4307
4378
 
4308
- class ExpectDiagnosticText {
4309
- static argumentMustBe(argumentNameText, expectedText) {
4310
- return `An argument for '${argumentNameText}' must be ${expectedText}.`;
4311
- }
4312
- static typeArgumentMustBe(expectedText) {
4313
- return `The type argument must be ${expectedText}.`;
4314
- }
4315
- static isCallable(isExpression, targetText) {
4316
- return `${isExpression ? "Expression" : "Type"} is callable ${targetText}.`;
4317
- }
4318
- static isNotCallable(isExpression, targetText) {
4319
- return `${isExpression ? "Expression" : "Type"} is not callable ${targetText}.`;
4320
- }
4321
- static isConstructable(isExpression, targetText) {
4322
- return `${isExpression ? "Expression" : "Type"} is constructable ${targetText}.`;
4379
+ class Reject {
4380
+ #compiler;
4381
+ #rejectedArgumentTypes = new Set();
4382
+ #typeChecker;
4383
+ constructor(compiler, program, resolvedConfig) {
4384
+ this.#compiler = compiler;
4385
+ this.#typeChecker = program.getTypeChecker();
4386
+ if (resolvedConfig.rejectAnyType) {
4387
+ this.#rejectedArgumentTypes.add("any");
4388
+ }
4389
+ if (resolvedConfig.rejectNeverType) {
4390
+ this.#rejectedArgumentTypes.add("never");
4391
+ }
4392
+ }
4393
+ argumentType(target, onDiagnostics) {
4394
+ for (const rejectedType of this.#rejectedArgumentTypes) {
4395
+ const allowedKeyword = this.#compiler.SyntaxKind[`${capitalize(rejectedType)}Keyword`];
4396
+ if (target.some((node) => node?.kind === allowedKeyword)) {
4397
+ continue;
4398
+ }
4399
+ for (const node of target) {
4400
+ if (!node) {
4401
+ continue;
4402
+ }
4403
+ if (this.#typeChecker.getTypeAtLocation(node).flags & this.#compiler.TypeFlags[capitalize(rejectedType)]) {
4404
+ const text = [
4405
+ nodeBelongsToArgumentList(this.#compiler, node)
4406
+ ? RejectDiagnosticText.argumentCannotBeOfType(rejectedType)
4407
+ : RejectDiagnosticText.typeArgumentCannotBeOfType(rejectedType),
4408
+ ...RejectDiagnosticText.typeWasRejected(rejectedType),
4409
+ ];
4410
+ const origin = DiagnosticOrigin.fromNode(node);
4411
+ onDiagnostics([Diagnostic.error(text, origin)]);
4412
+ return true;
4413
+ }
4414
+ }
4415
+ }
4416
+ return false;
4417
+ }
4418
+ }
4419
+
4420
+ class Ensure {
4421
+ #compiler;
4422
+ constructor(compiler) {
4423
+ this.#compiler = compiler;
4424
+ }
4425
+ argument(node, enclosingNode, onDiagnostics) {
4426
+ if (!node || !nodeBelongsToArgumentList(this.#compiler, node)) {
4427
+ this.#emitDiagnostic("An argument must be provided.", enclosingNode, onDiagnostics);
4428
+ return false;
4429
+ }
4430
+ return true;
4431
+ }
4432
+ argumentOrTypeArgument(node, enclosingNode, onDiagnostics) {
4433
+ if (!node) {
4434
+ this.#emitDiagnostic("An argument or type argument must be provided.", enclosingNode, onDiagnostics);
4435
+ return false;
4436
+ }
4437
+ return true;
4438
+ }
4439
+ jsxSetup(program, node, onDiagnostics) {
4440
+ const diagnosticText = [];
4441
+ if (!program.getCompilerOptions().jsx) {
4442
+ diagnosticText.push("The matcher requires the 'jsx' compiler option to be configured.");
4443
+ }
4444
+ if (node.getSourceFile().languageVariant !== this.#compiler.LanguageVariant.JSX) {
4445
+ diagnosticText.push("The matcher requires a '.tsx' file extension.");
4446
+ }
4447
+ if (diagnosticText.length > 0) {
4448
+ this.#emitDiagnostic(diagnosticText, node, onDiagnostics);
4449
+ return false;
4450
+ }
4451
+ return true;
4452
+ }
4453
+ typeArgument(node, enclosingNode, onDiagnostics) {
4454
+ if (!node || nodeBelongsToArgumentList(this.#compiler, node)) {
4455
+ this.#emitDiagnostic("A type argument must be provided.", enclosingNode, onDiagnostics);
4456
+ return false;
4457
+ }
4458
+ return true;
4459
+ }
4460
+ #emitDiagnostic(text, enclosingNode, onDiagnostics) {
4461
+ if (!Array.isArray(text)) {
4462
+ text = [text];
4463
+ }
4464
+ const origin = DiagnosticOrigin.fromNode(enclosingNode);
4465
+ onDiagnostics(text.map((text) => Diagnostic.error(text, origin)));
4466
+ }
4467
+ }
4468
+
4469
+ class ExpectDiagnosticText {
4470
+ static argumentMustBe(expectedText) {
4471
+ return `The argument must be ${expectedText}.`;
4472
+ }
4473
+ static typeArgumentMustBe(expectedText) {
4474
+ return `The type argument must be ${expectedText}.`;
4475
+ }
4476
+ static isCallable(isExpression, targetText) {
4477
+ return `${isExpression ? "Expression" : "Type"} is callable ${targetText}.`;
4478
+ }
4479
+ static isNotCallable(isExpression, targetText) {
4480
+ return `${isExpression ? "Expression" : "Type"} is not callable ${targetText}.`;
4481
+ }
4482
+ static isConstructable(isExpression, targetText) {
4483
+ return `${isExpression ? "Expression" : "Type"} is constructable ${targetText}.`;
4323
4484
  }
4324
4485
  static isNotConstructable(isExpression, targetText) {
4325
4486
  return `${isExpression ? "Expression" : "Type"} is not constructable ${targetText}.`;
@@ -4331,10 +4492,10 @@ class ExpectDiagnosticText {
4331
4492
  return `${isExpression ? "Expression" : "Type"} is not instantiable ${targetText}.`;
4332
4493
  }
4333
4494
  static acceptsProps(isExpression) {
4334
- return `${isExpression ? "Component" : "Component type"} accepts props of the given type.`;
4495
+ return `${isExpression ? "Component" : "Type"} accepts props of the given type.`;
4335
4496
  }
4336
4497
  static doesNotAcceptProps(isExpression) {
4337
- return `${isExpression ? "Component" : "Component type"} does not accept props of the given type.`;
4498
+ return `${isExpression ? "Component" : "Type"} does not accept props of the given type.`;
4338
4499
  }
4339
4500
  static canBeApplied(targetText) {
4340
4501
  return `The decorator function can be applied${targetText}.`;
@@ -4354,9 +4515,6 @@ class ExpectDiagnosticText {
4354
4515
  static matcherIsNotSupported(matcherNameText) {
4355
4516
  return `The '.${matcherNameText}()' matcher is not supported.`;
4356
4517
  }
4357
- static overloadGaveTheFollowingError(index, count, signatureText) {
4358
- return `Overload ${index} of ${count}, '${signatureText}', gave the following error.`;
4359
- }
4360
4518
  static raisedTypeError(count = 1) {
4361
4519
  return `The raised type error${count === 1 ? "" : "s"}:`;
4362
4520
  }
@@ -4394,21 +4552,11 @@ class ExpectDiagnosticText {
4394
4552
  static isNotTheSame(sourceTypeText, targetTypeText) {
4395
4553
  return `Type '${sourceTypeText}' is not the same as type '${targetTypeText}'.`;
4396
4554
  }
4397
- static isNotCompatibleWith(sourceTypeText, targetTypeText) {
4398
- return `Type '${sourceTypeText}' is not compatible with type '${targetTypeText}'.`;
4399
- }
4400
- static requiresProperty(typeText, propertyNameText) {
4401
- return `Type '${typeText}' requires property '${propertyNameText}'.`;
4402
- }
4403
- static typesOfPropertyAreNotCompatible(propertyNameText) {
4404
- return `Types of property '${propertyNameText}' are not compatible.`;
4405
- }
4406
4555
  }
4407
4556
 
4408
4557
  class MatchWorker {
4409
4558
  assertionNode;
4410
4559
  #compiler;
4411
- #signatureCache = new Map();
4412
4560
  typeChecker;
4413
4561
  constructor(compiler, program, assertionNode) {
4414
4562
  this.#compiler = compiler;
@@ -4432,219 +4580,118 @@ class MatchWorker {
4432
4580
  : { flags: this.#compiler.TypeFlags.NonPrimitive };
4433
4581
  return this.typeChecker.isTypeAssignableTo(type, nonPrimitiveType);
4434
4582
  }
4435
- getParameterType(signature, index) {
4436
- const parameter = signature.getDeclaration().parameters[index];
4437
- if (!parameter) {
4438
- return;
4439
- }
4440
- return this.getType(parameter);
4441
- }
4442
- getSignatures(node) {
4443
- let signatures = this.#signatureCache.get(node);
4444
- if (!signatures) {
4445
- const type = this.getType(node);
4446
- signatures = type.getCallSignatures();
4447
- if (signatures.length === 0) {
4448
- signatures = type.getConstructSignatures();
4449
- }
4450
- }
4451
- return signatures;
4452
- }
4453
4583
  getTypeText(node) {
4454
4584
  return this.typeChecker.typeToString(this.getType(node));
4455
4585
  }
4456
4586
  getType(node) {
4457
4587
  return this.typeChecker.getTypeAtLocation(node);
4458
4588
  }
4459
- resolveDiagnosticOrigin(symbol, enclosingNode) {
4460
- if (symbol.valueDeclaration != null &&
4461
- (this.#compiler.isPropertySignature(symbol.valueDeclaration) ||
4462
- this.#compiler.isPropertyAssignment(symbol.valueDeclaration) ||
4463
- this.#compiler.isShorthandPropertyAssignment(symbol.valueDeclaration)) &&
4464
- symbol.valueDeclaration.getStart() >= enclosingNode.getStart() &&
4465
- symbol.valueDeclaration.getEnd() <= enclosingNode.getEnd()) {
4466
- return DiagnosticOrigin.fromNode(symbol.valueDeclaration.name, this.assertionNode);
4467
- }
4468
- return DiagnosticOrigin.fromNode(enclosingNode, this.assertionNode);
4469
- }
4470
4589
  }
4471
4590
 
4472
- function isStringOrNumberLiteralType(compiler, type) {
4473
- return !!(type.flags & compiler.TypeFlags.StringOrNumberLiteral);
4474
- }
4475
- function isUnionType(compiler, type) {
4476
- return !!(type.flags & compiler.TypeFlags.Union);
4477
- }
4478
- function isUniqueSymbolType(compiler, type) {
4479
- return !!(type.flags & compiler.TypeFlags.UniqueESSymbol);
4480
- }
4481
-
4482
- class ToAcceptProps {
4483
- #compiler;
4484
- #typeChecker;
4485
- constructor(compiler, program) {
4486
- this.#compiler = compiler;
4487
- this.#typeChecker = program.getTypeChecker();
4488
- }
4489
- #explain(matchWorker, sourceNode, targetNode) {
4490
- const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
4491
- const signatures = matchWorker.getSignatures(sourceNode);
4492
- return signatures.reduce((accumulator, signature, index) => {
4493
- let diagnostic;
4494
- const introText = matchWorker.assertionNode.isNot
4495
- ? ExpectDiagnosticText.acceptsProps(isExpression)
4496
- : ExpectDiagnosticText.doesNotAcceptProps(isExpression);
4497
- const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertionNode);
4498
- if (signatures.length > 1) {
4499
- const signatureText = this.#typeChecker.signatureToString(signature, sourceNode);
4500
- const overloadText = ExpectDiagnosticText.overloadGaveTheFollowingError(index + 1, signatures.length, signatureText);
4501
- diagnostic = Diagnostic.error([introText, overloadText], origin);
4502
- }
4503
- else {
4504
- diagnostic = Diagnostic.error([introText], origin);
4505
- }
4506
- const { diagnostics, isMatch } = this.#explainProperties(matchWorker, signature, targetNode, diagnostic);
4507
- if (matchWorker.assertionNode.isNot ? isMatch : !isMatch) {
4508
- accumulator.push(...diagnostics);
4509
- }
4510
- return accumulator;
4511
- }, []);
4591
+ class AbilityMatcherBase {
4592
+ compiler;
4593
+ constructor(compiler) {
4594
+ this.compiler = compiler;
4512
4595
  }
4513
- #isOptionalProperty(symbol) {
4514
- return symbol.declarations?.every((declaration) => this.#compiler.isPropertySignature(declaration) && declaration.questionToken != null);
4596
+ getArgumentCountText(nodes) {
4597
+ if (nodes.length === 0) {
4598
+ return "without arguments";
4599
+ }
4600
+ if (nodes.length === 1 && nodes[0]?.kind === this.compiler.SyntaxKind.SpreadElement) {
4601
+ return "with the given arguments";
4602
+ }
4603
+ return `with the given argument${nodes.length === 1 ? "" : "s"}`;
4515
4604
  }
4516
- #checkProperties(sourceType, targetType) {
4517
- const check = (sourceType, targetType) => {
4518
- for (const targetProperty of targetType.getProperties()) {
4519
- const targetPropertyName = targetProperty.getName();
4520
- const sourceProperty = sourceType?.getProperty(targetPropertyName);
4521
- if (!sourceProperty) {
4522
- return false;
4523
- }
4524
- const targetPropertyType = this.#typeChecker.getTypeOfSymbol(targetProperty);
4525
- const sourcePropertyType = this.#typeChecker.getTypeOfSymbol(sourceProperty);
4526
- if (!this.#typeChecker.isTypeAssignableTo(targetPropertyType, sourcePropertyType)) {
4527
- return false;
4528
- }
4529
- }
4530
- if (sourceType != null) {
4531
- const sourceProperties = sourceType.getProperties();
4532
- for (const sourceProperty of sourceProperties) {
4533
- const targetProperty = targetType.getProperty(sourceProperty.getName());
4534
- if (!targetProperty && !this.#isOptionalProperty(sourceProperty)) {
4535
- return false;
4536
- }
4537
- }
4538
- }
4539
- return true;
4540
- };
4541
- if (sourceType != null && isUnionType(this.#compiler, sourceType)) {
4542
- return sourceType.types.some((sourceType) => check(sourceType, targetType));
4605
+ getTypeArgumentCountText(targetNode) {
4606
+ if (targetNode.elements.length === 0) {
4607
+ return "without type arguments";
4543
4608
  }
4544
- return check(sourceType, targetType);
4609
+ return `with the given type argument${targetNode.elements.length === 1 ? "" : "s"}`;
4545
4610
  }
4546
- #explainProperties(matchWorker, signature, targetNode, diagnostic) {
4547
- const sourceType = matchWorker.getParameterType(signature, 0);
4548
- const sourceTypeText = sourceType != null ? this.#typeChecker.typeToString(sourceType) : "{}";
4549
- const targetType = matchWorker.getType(targetNode);
4550
- const targetTypeText = this.#typeChecker.typeToString(targetType);
4551
- const explain = (sourceType, targetType, diagnostic) => {
4552
- const sourceTypeText = sourceType != null ? this.#typeChecker.typeToString(sourceType) : "{}";
4553
- const diagnostics = [];
4554
- for (const targetProperty of targetType.getProperties()) {
4555
- const targetPropertyName = targetProperty.getName();
4556
- const sourceProperty = sourceType?.getProperty(targetPropertyName);
4557
- if (!sourceProperty) {
4558
- const text = [
4559
- ExpectDiagnosticText.isNotCompatibleWith(sourceTypeText, targetTypeText),
4560
- ExpectDiagnosticText.doesNotHaveProperty(sourceTypeText, targetPropertyName),
4561
- ];
4562
- const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
4563
- diagnostics.push(diagnostic.extendWith(text, origin));
4564
- continue;
4611
+ explain(matchWorker, sourceNode, targetNode, getArgumentCountText) {
4612
+ const isExpression = nodeBelongsToArgumentList(this.compiler, sourceNode);
4613
+ const argumentCountText = getArgumentCountText?.();
4614
+ const diagnostics = [];
4615
+ if (matchWorker.assertionNode.abilityDiagnostics.size > 0) {
4616
+ for (const diagnostic of matchWorker.assertionNode.abilityDiagnostics) {
4617
+ const text = [this.explainNotText(isExpression, argumentCountText), getDiagnosticMessageText(diagnostic)];
4618
+ let origin;
4619
+ if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, targetNode)) {
4620
+ origin = DiagnosticOrigin.fromAbilityDiagnostic(diagnostic, matchWorker.assertionNode);
4565
4621
  }
4566
- const targetPropertyType = this.#typeChecker.getTypeOfSymbol(targetProperty);
4567
- const sourcePropertyType = this.#typeChecker.getTypeOfSymbol(sourceProperty);
4568
- if (!this.#typeChecker.isTypeAssignableTo(targetPropertyType, sourcePropertyType)) {
4569
- const targetPropertyTypeText = this.#typeChecker.typeToString(targetPropertyType);
4570
- const sourcePropertyTypeText = this.#typeChecker.typeToString(sourcePropertyType);
4571
- const text = [
4572
- ExpectDiagnosticText.isNotAssignableFrom(sourceTypeText, targetTypeText),
4573
- ExpectDiagnosticText.typesOfPropertyAreNotCompatible(targetPropertyName),
4574
- ExpectDiagnosticText.isNotAssignableFrom(sourcePropertyTypeText, targetPropertyTypeText),
4575
- ];
4576
- const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
4577
- diagnostics.push(diagnostic.extendWith(text, origin));
4622
+ else {
4623
+ origin = DiagnosticOrigin.fromAssertion(matchWorker.assertionNode);
4578
4624
  }
4579
- }
4580
- if (sourceType != null) {
4581
- for (const sourceProperty of sourceType.getProperties()) {
4582
- const sourcePropertyName = sourceProperty.getName();
4583
- const targetProperty = targetType.getProperty(sourcePropertyName);
4584
- if (!targetProperty && !this.#isOptionalProperty(sourceProperty)) {
4585
- const text = [
4586
- ExpectDiagnosticText.isNotAssignableFrom(sourceTypeText, targetTypeText),
4587
- ExpectDiagnosticText.requiresProperty(sourceTypeText, sourcePropertyName),
4588
- ];
4589
- diagnostics.push(diagnostic.extendWith(text));
4590
- }
4625
+ let related;
4626
+ if (diagnostic.relatedInformation != null) {
4627
+ related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
4591
4628
  }
4629
+ diagnostics.push(Diagnostic.error(text.flat(), origin).add({ related }));
4592
4630
  }
4593
- if (diagnostics.length === 0) {
4594
- const text = ExpectDiagnosticText.isAssignableFrom(sourceTypeText, targetTypeText);
4595
- diagnostics.push(diagnostic.extendWith(text));
4596
- return { diagnostics, isMatch: true };
4597
- }
4598
- return { diagnostics, isMatch: false };
4599
- };
4600
- if (sourceType != null && isUnionType(this.#compiler, sourceType)) {
4601
- let accumulator = [];
4602
- const isMatch = sourceType.types.some((sourceType) => {
4603
- const text = matchWorker.assertionNode.isNot
4604
- ? ExpectDiagnosticText.isAssignableFrom(sourceTypeText, targetTypeText)
4605
- : ExpectDiagnosticText.isNotAssignableFrom(sourceTypeText, targetTypeText);
4606
- const { diagnostics, isMatch } = explain(sourceType, targetType, diagnostic.extendWith(text));
4607
- if (isMatch) {
4608
- accumulator = diagnostics;
4609
- }
4610
- else {
4611
- accumulator.push(...diagnostics);
4612
- }
4613
- return isMatch;
4614
- });
4615
- return { diagnostics: accumulator, isMatch };
4616
4631
  }
4617
- return explain(sourceType, targetType, diagnostic);
4632
+ else {
4633
+ const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertionNode);
4634
+ diagnostics.push(Diagnostic.error(this.explainText(isExpression, argumentCountText), origin));
4635
+ }
4636
+ return diagnostics;
4618
4637
  }
4638
+ }
4639
+
4640
+ class ToAcceptProps extends AbilityMatcherBase {
4641
+ explainText = ExpectDiagnosticText.acceptsProps;
4642
+ explainNotText = ExpectDiagnosticText.doesNotAcceptProps;
4619
4643
  match(matchWorker, sourceNode, targetNode, onDiagnostics) {
4620
4644
  const diagnostics = [];
4621
- const signatures = matchWorker.getSignatures(sourceNode);
4622
- if (signatures.length === 0) {
4623
- const expectedText = "of a function or class type";
4624
- const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
4625
- ? ExpectDiagnosticText.argumentMustBe("source", expectedText)
4645
+ const sourceType = matchWorker.getType(sourceNode);
4646
+ if (!(this.compiler.isIdentifier(sourceNode) ||
4647
+ this.compiler.isPropertyAccessExpression(sourceNode) ||
4648
+ this.compiler.isTypeReferenceNode(sourceNode) ||
4649
+ this.compiler.isExpressionWithTypeArguments(sourceNode)) ||
4650
+ !(sourceType.getCallSignatures().length > 0 || sourceType.getConstructSignatures().length > 0)) {
4651
+ const expectedText = "an identifier of a JSX component";
4652
+ const text = nodeBelongsToArgumentList(this.compiler, sourceNode)
4653
+ ? ExpectDiagnosticText.argumentMustBe(expectedText)
4626
4654
  : ExpectDiagnosticText.typeArgumentMustBe(expectedText);
4627
4655
  const origin = DiagnosticOrigin.fromNode(sourceNode);
4628
4656
  diagnostics.push(Diagnostic.error(text, origin));
4629
4657
  }
4630
- const targetType = matchWorker.getType(targetNode);
4631
- if (!(targetType.flags & this.#compiler.TypeFlags.Object)) {
4632
- const expectedText = "of an object type";
4633
- const text = ExpectDiagnosticText.argumentMustBe("props", expectedText);
4658
+ else if (!/^[A-Z_$]/.test(sourceNode.getText()[0])) {
4659
+ const expectedText = "an identifier that begins with an uppercase letter";
4660
+ const text = nodeBelongsToArgumentList(this.compiler, sourceNode)
4661
+ ? ExpectDiagnosticText.argumentMustBe(expectedText)
4662
+ : ExpectDiagnosticText.typeArgumentMustBe(expectedText);
4663
+ const origin = DiagnosticOrigin.fromNode(sourceNode);
4664
+ diagnostics.push(Diagnostic.error(text, origin));
4665
+ }
4666
+ if (!this.compiler.isObjectLiteralExpression(targetNode)) {
4667
+ const expectedText = "an object literal with key-value pairs";
4668
+ const text = ExpectDiagnosticText.argumentMustBe(expectedText);
4634
4669
  const origin = DiagnosticOrigin.fromNode(targetNode);
4635
4670
  diagnostics.push(Diagnostic.error(text, origin));
4636
4671
  }
4672
+ else {
4673
+ for (const property of targetNode.properties) {
4674
+ if (!(this.compiler.isPropertyAssignment(property) || this.compiler.isSpreadAssignment(property))) {
4675
+ const text = "Each property must be a key-value pair or a spread element.";
4676
+ const origin = DiagnosticOrigin.fromNode(property);
4677
+ diagnostics.push(Diagnostic.error(text, origin));
4678
+ continue;
4679
+ }
4680
+ if (this.compiler.isPropertyAssignment(property) &&
4681
+ !(this.compiler.isIdentifier(property.name) || this.compiler.isStringLiteral(property.name))) {
4682
+ const text = "Property keys must be static identifiers or string literals.";
4683
+ const origin = DiagnosticOrigin.fromNode(property.name);
4684
+ diagnostics.push(Diagnostic.error(text, origin));
4685
+ }
4686
+ }
4687
+ }
4637
4688
  if (diagnostics.length > 0) {
4638
4689
  onDiagnostics(diagnostics);
4639
4690
  return;
4640
4691
  }
4641
- const isMatch = signatures.some((signature) => {
4642
- const sourceType = matchWorker.getParameterType(signature, 0);
4643
- return this.#checkProperties(sourceType, targetType);
4644
- });
4645
4692
  return {
4646
- explain: () => this.#explain(matchWorker, sourceNode, targetNode),
4647
- isMatch,
4693
+ explain: () => this.explain(matchWorker, sourceNode, targetNode),
4694
+ isMatch: matchWorker.assertionNode.abilityDiagnostics.size === 0,
4648
4695
  };
4649
4696
  }
4650
4697
  }
@@ -5245,7 +5292,7 @@ class ToBeApplicable {
5245
5292
  if (type.getCallSignatures().length === 0) {
5246
5293
  const expectedText = "of a function type";
5247
5294
  const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
5248
- ? ExpectDiagnosticText.argumentMustBe("source", expectedText)
5295
+ ? ExpectDiagnosticText.argumentMustBe(expectedText)
5249
5296
  : ExpectDiagnosticText.typeArgumentMustBe(expectedText);
5250
5297
  const origin = DiagnosticOrigin.fromNode(sourceNode);
5251
5298
  onDiagnostics([Diagnostic.error(text, origin)]);
@@ -5280,55 +5327,6 @@ class ToBeAssignableTo extends RelationMatcherBase {
5280
5327
  }
5281
5328
  }
5282
5329
 
5283
- class AbilityMatcherBase {
5284
- compiler;
5285
- constructor(compiler) {
5286
- this.compiler = compiler;
5287
- }
5288
- getArgumentCountText(nodes) {
5289
- if (nodes.length === 0) {
5290
- return "without arguments";
5291
- }
5292
- if (nodes.length === 1 && nodes[0]?.kind === this.compiler.SyntaxKind.SpreadElement) {
5293
- return "with the given arguments";
5294
- }
5295
- return `with the given argument${nodes.length === 1 ? "" : "s"}`;
5296
- }
5297
- getTypeArgumentCountText(targetNode) {
5298
- if (targetNode.elements.length === 0) {
5299
- return "without type arguments";
5300
- }
5301
- return `with the given type argument${targetNode.elements.length === 1 ? "" : "s"}`;
5302
- }
5303
- explain(matchWorker, sourceNode, targetNode, getArgumentCountText) {
5304
- const isExpression = nodeBelongsToArgumentList(this.compiler, sourceNode);
5305
- const argumentCountText = getArgumentCountText();
5306
- const diagnostics = [];
5307
- if (matchWorker.assertionNode.abilityDiagnostics.size > 0) {
5308
- for (const diagnostic of matchWorker.assertionNode.abilityDiagnostics) {
5309
- const text = [this.explainNotText(isExpression, argumentCountText), getDiagnosticMessageText(diagnostic)];
5310
- let origin;
5311
- if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, targetNode)) {
5312
- origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceNode.getSourceFile(), matchWorker.assertionNode);
5313
- }
5314
- else {
5315
- origin = DiagnosticOrigin.fromAssertion(matchWorker.assertionNode);
5316
- }
5317
- let related;
5318
- if (diagnostic.relatedInformation != null) {
5319
- related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
5320
- }
5321
- diagnostics.push(Diagnostic.error(text.flat(), origin).add({ related }));
5322
- }
5323
- }
5324
- else {
5325
- const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertionNode);
5326
- diagnostics.push(Diagnostic.error(this.explainText(isExpression, argumentCountText), origin));
5327
- }
5328
- return diagnostics;
5329
- }
5330
- }
5331
-
5332
5330
  class ToBeCallableWith extends AbilityMatcherBase {
5333
5331
  explainText = ExpectDiagnosticText.isCallable;
5334
5332
  explainNotText = ExpectDiagnosticText.isNotCallable;
@@ -5337,7 +5335,7 @@ class ToBeCallableWith extends AbilityMatcherBase {
5337
5335
  if (sourceType.getCallSignatures().length === 0) {
5338
5336
  const text = [];
5339
5337
  if (nodeBelongsToArgumentList(this.compiler, sourceNode)) {
5340
- text.push(ExpectDiagnosticText.argumentMustBe("source", "a callable expression"));
5338
+ text.push(ExpectDiagnosticText.argumentMustBe("a callable expression"));
5341
5339
  }
5342
5340
  else {
5343
5341
  text.push(ExpectDiagnosticText.typeArgumentMustBe("a callable type"));
@@ -5364,7 +5362,7 @@ class ToBeConstructableWith extends AbilityMatcherBase {
5364
5362
  if (sourceType.getConstructSignatures().length === 0) {
5365
5363
  const text = [];
5366
5364
  if (nodeBelongsToArgumentList(this.compiler, sourceNode)) {
5367
- text.push(ExpectDiagnosticText.argumentMustBe("source", "a constructable expression"));
5365
+ text.push(ExpectDiagnosticText.argumentMustBe("a constructable expression"));
5368
5366
  }
5369
5367
  else {
5370
5368
  text.push(ExpectDiagnosticText.typeArgumentMustBe("a constructable type"));
@@ -5393,7 +5391,7 @@ class ToBeInstantiableWith extends AbilityMatcherBase {
5393
5391
  this.compiler.isExpressionWithTypeArguments(sourceNode))) {
5394
5392
  let text;
5395
5393
  if (nodeBelongsToArgumentList(this.compiler, sourceNode)) {
5396
- text = ExpectDiagnosticText.argumentMustBe("source", "an instantiable expression");
5394
+ text = ExpectDiagnosticText.argumentMustBe("an instantiable expression");
5397
5395
  }
5398
5396
  else {
5399
5397
  text = ExpectDiagnosticText.typeArgumentMustBe("an instantiable type");
@@ -5424,7 +5422,7 @@ class ToHaveProperty {
5424
5422
  const sourceTypeText = matchWorker.getTypeText(sourceNode);
5425
5423
  const targetType = matchWorker.getType(targetNode);
5426
5424
  let propertyNameText;
5427
- if (isStringOrNumberLiteralType(this.#compiler, targetType)) {
5425
+ if (targetType.flags & (this.#compiler.TypeFlags.StringLiteral | this.#compiler.TypeFlags.NumberLiteral)) {
5428
5426
  propertyNameText = targetType.value.toString();
5429
5427
  }
5430
5428
  else {
@@ -5442,15 +5440,18 @@ class ToHaveProperty {
5442
5440
  !matchWorker.extendsObjectType(sourceType)) {
5443
5441
  const expectedText = "of an object type";
5444
5442
  const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
5445
- ? ExpectDiagnosticText.argumentMustBe("source", expectedText)
5443
+ ? ExpectDiagnosticText.argumentMustBe(expectedText)
5446
5444
  : ExpectDiagnosticText.typeArgumentMustBe(expectedText);
5447
5445
  const origin = DiagnosticOrigin.fromNode(sourceNode);
5448
5446
  diagnostics.push(Diagnostic.error(text, origin));
5449
5447
  }
5450
5448
  const targetType = matchWorker.getType(targetNode);
5451
- if (!(isStringOrNumberLiteralType(this.#compiler, targetType) || isUniqueSymbolType(this.#compiler, targetType))) {
5452
- const expectedText = "of type 'string | number | symbol'";
5453
- const text = ExpectDiagnosticText.argumentMustBe("key", expectedText);
5449
+ if (!(targetType.flags &
5450
+ (this.#compiler.TypeFlags.StringLiteral |
5451
+ this.#compiler.TypeFlags.NumberLiteral |
5452
+ this.#compiler.TypeFlags.UniqueESSymbol))) {
5453
+ const expectedText = "a string, number or symbol";
5454
+ const text = ExpectDiagnosticText.argumentMustBe(expectedText);
5454
5455
  const origin = DiagnosticOrigin.fromNode(targetNode);
5455
5456
  diagnostics.push(Diagnostic.error(text, origin));
5456
5457
  }
@@ -5509,8 +5510,8 @@ class ToRaiseError {
5509
5510
  if (!(this.#compiler.isStringLiteralLike(targetNode) ||
5510
5511
  this.#compiler.isNumericLiteral(targetNode) ||
5511
5512
  this.#compiler.isRegularExpressionLiteral(targetNode))) {
5512
- const expectedText = "a string, number or regular expression literal";
5513
- const text = ExpectDiagnosticText.argumentMustBe("target", expectedText);
5513
+ const expectedText = "a string, number or regular expression";
5514
+ const text = ExpectDiagnosticText.argumentMustBe(expectedText);
5514
5515
  const origin = DiagnosticOrigin.fromNode(targetNode);
5515
5516
  diagnostics.push(Diagnostic.error(text, origin));
5516
5517
  }
@@ -5551,6 +5552,7 @@ class ToRaiseError {
5551
5552
 
5552
5553
  class ExpectService {
5553
5554
  #compiler;
5555
+ #ensure;
5554
5556
  #program;
5555
5557
  #reject;
5556
5558
  toAcceptProps;
@@ -5563,11 +5565,12 @@ class ExpectService {
5563
5565
  toBeInstantiableWith;
5564
5566
  toHaveProperty;
5565
5567
  toRaiseError;
5566
- constructor(compiler, program, reject) {
5568
+ constructor(compiler, program, resolvedConfig) {
5567
5569
  this.#compiler = compiler;
5568
5570
  this.#program = program;
5569
- this.#reject = reject;
5570
- this.toAcceptProps = new ToAcceptProps(compiler, program);
5571
+ this.#ensure = new Ensure(compiler);
5572
+ this.#reject = new Reject(compiler, program, resolvedConfig);
5573
+ this.toAcceptProps = new ToAcceptProps(compiler);
5571
5574
  this.toBe = new ToBe(compiler, program);
5572
5575
  this.toBeApplicable = new ToBeApplicable(compiler);
5573
5576
  this.toBeAssignableFrom = new ToBeAssignableFrom();
@@ -5580,23 +5583,24 @@ class ExpectService {
5580
5583
  }
5581
5584
  match(assertionNode, onDiagnostics) {
5582
5585
  const matcherNameText = assertionNode.matcherNameNode.name.text;
5583
- if (!argumentOrTypeArgumentIsProvided("source", "Source", assertionNode.source[0], assertionNode.node.expression, onDiagnostics)) {
5586
+ if (matcherNameText === "toAcceptProps" &&
5587
+ !this.#ensure.jsxSetup(this.#program, assertionNode.matcherNameNode.name, onDiagnostics)) {
5588
+ return;
5589
+ }
5590
+ if (!this.#ensure.argumentOrTypeArgument(assertionNode.source[0], assertionNode.node.expression, onDiagnostics)) {
5584
5591
  return;
5585
5592
  }
5586
- const matchWorker = new MatchWorker(this.#compiler, this.#program, assertionNode);
5587
5593
  if (!(matcherNameText === "toBeInstantiableWith" || (matcherNameText === "toRaiseError" && !assertionNode.isNot)) &&
5588
- this.#reject.argumentType([
5589
- ["source", assertionNode.source[0]],
5590
- ["target", assertionNode.target?.[0]],
5591
- ], onDiagnostics)) {
5594
+ this.#reject.argumentType([assertionNode.source[0], assertionNode.target?.[0]], onDiagnostics)) {
5592
5595
  return;
5593
5596
  }
5597
+ const matchWorker = new MatchWorker(this.#compiler, this.#program, assertionNode);
5594
5598
  switch (matcherNameText) {
5595
5599
  case "toAcceptProps":
5596
5600
  case "toBe":
5597
5601
  case "toBeAssignableFrom":
5598
5602
  case "toBeAssignableTo":
5599
- if (!argumentOrTypeArgumentIsProvided("target", "Target", assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
5603
+ if (!this.#ensure.argumentOrTypeArgument(assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
5600
5604
  return;
5601
5605
  }
5602
5606
  return this[matcherNameText].match(matchWorker, assertionNode.source[0], assertionNode.target[0], onDiagnostics);
@@ -5607,13 +5611,13 @@ class ExpectService {
5607
5611
  case "toRaiseError":
5608
5612
  return this[matcherNameText].match(matchWorker, assertionNode.source[0], assertionNode.target, onDiagnostics);
5609
5613
  case "toBeInstantiableWith": {
5610
- if (!typeArgumentIsProvided(this.#compiler, "Target", assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
5614
+ if (!this.#ensure.typeArgument(assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
5611
5615
  return;
5612
5616
  }
5613
5617
  return this.toBeInstantiableWith.match(matchWorker, assertionNode.source[0], assertionNode.target[0], onDiagnostics);
5614
5618
  }
5615
5619
  case "toHaveProperty":
5616
- if (!argumentIsProvided(this.#compiler, "key", assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
5620
+ if (!this.#ensure.argument(assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
5617
5621
  return;
5618
5622
  }
5619
5623
  return this.toHaveProperty.match(matchWorker, assertionNode.source[0], assertionNode.target[0], onDiagnostics);
@@ -5629,67 +5633,6 @@ class ExpectService {
5629
5633
  }
5630
5634
  }
5631
5635
 
5632
- function capitalize(text) {
5633
- return text.replace(/^./, text.charAt(0).toUpperCase());
5634
- }
5635
-
5636
- class RejectDiagnosticText {
5637
- static argumentCannotBeOfType(argumentNameText, typeText) {
5638
- return `An argument for '${argumentNameText}' cannot be of the '${typeText}' type.`;
5639
- }
5640
- static typeArgumentCannotBeOfType(argumentNameText, typeText) {
5641
- return `A type argument for '${argumentNameText}' cannot be of the '${typeText}' type.`;
5642
- }
5643
- static typeWasRejected(typeText) {
5644
- const optionNameText = `reject${capitalize(typeText)}Type`;
5645
- return [
5646
- `The '${typeText}' type was rejected because the '${optionNameText}' option is enabled.`,
5647
- `If this check is necessary, pass '${typeText}' as the type argument explicitly.`,
5648
- ];
5649
- }
5650
- }
5651
-
5652
- class Reject {
5653
- #compiler;
5654
- #rejectedArgumentTypes = new Set();
5655
- #typeChecker;
5656
- constructor(compiler, program, resolvedConfig) {
5657
- this.#compiler = compiler;
5658
- this.#typeChecker = program.getTypeChecker();
5659
- if (resolvedConfig.rejectAnyType) {
5660
- this.#rejectedArgumentTypes.add("any");
5661
- }
5662
- if (resolvedConfig.rejectNeverType) {
5663
- this.#rejectedArgumentTypes.add("never");
5664
- }
5665
- }
5666
- argumentType(target, onDiagnostics) {
5667
- for (const rejectedType of this.#rejectedArgumentTypes) {
5668
- const allowedKeyword = this.#compiler.SyntaxKind[`${capitalize(rejectedType)}Keyword`];
5669
- if (target.some(([, node]) => node?.kind === allowedKeyword)) {
5670
- continue;
5671
- }
5672
- for (const [name, node] of target) {
5673
- if (!node) {
5674
- continue;
5675
- }
5676
- if (this.#typeChecker.getTypeAtLocation(node).flags & this.#compiler.TypeFlags[capitalize(rejectedType)]) {
5677
- const text = [
5678
- nodeBelongsToArgumentList(this.#compiler, node)
5679
- ? RejectDiagnosticText.argumentCannotBeOfType(name, rejectedType)
5680
- : RejectDiagnosticText.typeArgumentCannotBeOfType(capitalize(name), rejectedType),
5681
- ...RejectDiagnosticText.typeWasRejected(rejectedType),
5682
- ];
5683
- const origin = DiagnosticOrigin.fromNode(node);
5684
- onDiagnostics([Diagnostic.error(text, origin)]);
5685
- return true;
5686
- }
5687
- }
5688
- }
5689
- return false;
5690
- }
5691
- }
5692
-
5693
5636
  class FixmeDiagnosticText {
5694
5637
  static considerRemoving() {
5695
5638
  return "Consider removing the '// @tstyche fixme' directive.";
@@ -5763,8 +5706,7 @@ class TestTreeWalker {
5763
5706
  this.#cancellationToken = options.cancellationToken;
5764
5707
  this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
5765
5708
  this.#position = options.position;
5766
- const reject = new Reject(compiler, program, resolvedConfig);
5767
- this.#expectService = new ExpectService(compiler, program, reject);
5709
+ this.#expectService = new ExpectService(compiler, program, resolvedConfig);
5768
5710
  }
5769
5711
  async #resolveRunMode(flags, node) {
5770
5712
  const ifDirective = Directive.getDirectiveRange(this.#compiler, node, "if");
@@ -5998,7 +5940,7 @@ class FileRunner {
5998
5940
  class Runner {
5999
5941
  #eventEmitter = new EventEmitter();
6000
5942
  #resolvedConfig;
6001
- static version = "7.1.0";
5943
+ static version = "7.2.1";
6002
5944
  constructor(resolvedConfig) {
6003
5945
  this.#resolvedConfig = resolvedConfig;
6004
5946
  }
@@ -6223,4 +6165,4 @@ class Cli {
6223
6165
  }
6224
6166
  }
6225
6167
 
6226
- 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 };
6168
+ 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 };