tstyche 7.1.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.
Files changed (3) hide show
  1. package/dist/api.d.ts +22 -5
  2. package/dist/api.js +509 -599
  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.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
+ }
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,56 @@ 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.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
+ }
3365
3474
  case "toBeApplicable":
3366
3475
  this.#nodes.push(expect);
3367
- this.#editor.replaceRanges([
3368
- [expectStart, expectExpressionEnd],
3369
- [expectEnd, matcherNameEnd],
3370
- ]);
3476
+ this.#editor.erase(expectStart, expectExpressionEnd).erase(expectEnd, matcherNameEnd);
3371
3477
  break;
3372
3478
  case "toBeCallableWith": {
3373
3479
  this.#nodes.push(expect);
@@ -3376,27 +3482,16 @@ class AbilityLayer {
3376
3482
  return;
3377
3483
  }
3378
3484
  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
- ]);
3485
+ this.#editor
3486
+ .eraseTrailingComma(expect.source)
3487
+ .update(expectStart, expectExpressionEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? ";" : "")
3488
+ .erase(expectEnd, matcherNameEnd);
3388
3489
  }
3389
3490
  else {
3390
3491
  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
- ]);
3492
+ this.#editor.update(expectStart, matcherNameEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode)
3493
+ ? `;(undefined as any as ${sourceText})`
3494
+ : `(undefined as any as ${sourceText})`);
3400
3495
  }
3401
3496
  break;
3402
3497
  }
@@ -3407,27 +3502,16 @@ class AbilityLayer {
3407
3502
  return;
3408
3503
  }
3409
3504
  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
- ]);
3505
+ this.#editor
3506
+ .eraseTrailingComma(expect.source)
3507
+ .update(expectStart, expectExpressionEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? "; new" : "new")
3508
+ .erase(expectEnd, matcherNameEnd);
3419
3509
  }
3420
3510
  else {
3421
3511
  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
- ]);
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})`);
3431
3515
  }
3432
3516
  break;
3433
3517
  }
@@ -3439,39 +3523,26 @@ class AbilityLayer {
3439
3523
  return;
3440
3524
  }
3441
3525
  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
- ]);
3526
+ this.#editor
3527
+ .eraseTrailingComma(expect.source)
3528
+ .update(expectStart, expectExpressionEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? ";" : "")
3529
+ .erase(expectEnd, matcherNodeEnd);
3451
3530
  if (this.#compiler.isExpressionWithTypeArguments(sourceNode)) {
3452
- this.#editor.replaceRanges([[sourceNode.expression.getEnd(), sourceNode.getEnd()]]);
3531
+ this.#editor.erase(sourceNode.expression.getEnd(), sourceNode.getEnd());
3453
3532
  }
3454
3533
  }
3455
3534
  else {
3456
3535
  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
- ]);
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}`);
3468
3541
  }
3469
3542
  if (targetNode != null) {
3470
3543
  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
- ]);
3544
+ if (targetText.trim().length > 0) {
3545
+ this.#editor.update(targetNode.getFullStart(), targetNode.getEnd(), `<${targetText}>`.padStart(targetNode.getFullWidth()));
3475
3546
  }
3476
3547
  }
3477
3548
  break;
@@ -3483,30 +3554,21 @@ class AbilityLayer {
3483
3554
  if (!sourceNode || !targetNode) {
3484
3555
  return;
3485
3556
  }
3486
- const sourceText = sourceNode.getFullText();
3487
- const targetText = targetNode.getFullText();
3488
3557
  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
- ]);
3558
+ this.#editor
3559
+ .eraseTrailingComma(expect.source)
3560
+ .update(expectStart, expectExpressionEnd, nodeIsChildOfExpressionStatement(this.#compiler, expect.matcherNode) ? ";" : "")
3561
+ .erase(expectEnd, matcherNodeEnd);
3499
3562
  }
3500
3563
  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
- ]);
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()));
3510
3572
  }
3511
3573
  break;
3512
3574
  }
@@ -3514,65 +3576,6 @@ class AbilityLayer {
3514
3576
  }
3515
3577
  }
3516
3578
 
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
3579
  class SuppressedLayer {
3577
3580
  #compiler;
3578
3581
  #editor;
@@ -3645,7 +3648,7 @@ class SuppressedLayer {
3645
3648
  }
3646
3649
  for (const suppressedError of suppressedErrors) {
3647
3650
  const { start, end } = suppressedError.directive;
3648
- this.#editor.replaceRange(start + 2, end);
3651
+ this.#editor.erase(start, end);
3649
3652
  if (this.#suppressedErrorsMap != null) {
3650
3653
  const { line } = tree.sourceFile.getLineAndCharacterOfPosition(start);
3651
3654
  this.#suppressedErrorsMap.set(line, suppressedError);
@@ -3654,9 +3657,84 @@ class SuppressedLayer {
3654
3657
  }
3655
3658
  }
3656
3659
 
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 = [];
3670
+ }
3671
+ close() {
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;
3683
+ }
3684
+ eraseTrailingComma(node) {
3685
+ if (node.hasTrailingComma) {
3686
+ this.erase(node.end - 1, node.end);
3687
+ }
3688
+ return this;
3689
+ }
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
+
3657
3735
  class Layers {
3658
3736
  #abilityLayer;
3659
- #editor = new SourceTextEditor();
3737
+ #editor = new TextEditor();
3660
3738
  #projectService;
3661
3739
  #suppressedDiagnostics;
3662
3740
  #suppressedLayer;
@@ -3665,14 +3743,23 @@ class Layers {
3665
3743
  this.#abilityLayer = new AbilityLayer(compiler, this.#editor);
3666
3744
  this.#suppressedLayer = new SuppressedLayer(compiler, this.#editor, resolvedConfig);
3667
3745
  }
3668
- close() {
3669
- let isSeenDiagnostic;
3746
+ close(tree) {
3747
+ let seenDiagnostics = [];
3670
3748
  if (this.#suppressedDiagnostics != null) {
3671
- const seenDiagnostics = this.#suppressedDiagnostics;
3749
+ seenDiagnostics = this.#suppressedDiagnostics;
3672
3750
  this.#suppressedDiagnostics = undefined;
3673
- isSeenDiagnostic = (diagnostic) => !seenDiagnostics.some((seenDiagnostic) => compareDiagnostics(diagnostic, seenDiagnostic));
3674
3751
  }
3675
- const abilityDiagnostics = this.#projectService.getDiagnostics(this.#editor.getFilePath(), this.#editor.getText(), isSeenDiagnostic);
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
+ }
3676
3763
  this.#abilityLayer.close(abilityDiagnostics);
3677
3764
  this.#editor.close();
3678
3765
  }
@@ -3680,7 +3767,7 @@ class Layers {
3680
3767
  this.#editor.open(tree.sourceFile);
3681
3768
  this.#suppressedLayer.open(tree);
3682
3769
  this.#suppressedDiagnostics = this.#projectService.getDiagnostics(this.#editor.getFilePath(), this.#editor.getText());
3683
- this.#suppressedLayer.close(this.#suppressedDiagnostics);
3770
+ this.#suppressedLayer.close(this.#suppressedDiagnostics?.map((diagnostic) => new MappedDiagnostic(tree.sourceFile, diagnostic)));
3684
3771
  }
3685
3772
  visit(node) {
3686
3773
  this.#abilityLayer.visitExpect(node);
@@ -3927,7 +4014,7 @@ class CollectService {
3927
4014
  this.#layers.open(testTree);
3928
4015
  this.#identifierLookup.open();
3929
4016
  this.#collectTestTreeNodes(sourceFile, testTree, testTree);
3930
- this.#layers.close();
4017
+ this.#layers.close(testTree);
3931
4018
  EventEmitter.dispatch(["collect:end", { tree: testTree }]);
3932
4019
  return testTree;
3933
4020
  }
@@ -4061,7 +4148,6 @@ class ProjectService {
4061
4148
  }
4062
4149
  closeFile(filePath) {
4063
4150
  this.#service.closeClientFile(filePath);
4064
- SourceService.delete(filePath);
4065
4151
  }
4066
4152
  #getDefaultCompilerOptions() {
4067
4153
  const defaultCompilerOptions = {
@@ -4091,14 +4177,10 @@ class ProjectService {
4091
4177
  }
4092
4178
  return project;
4093
4179
  }
4094
- getDiagnostics(filePath, sourceText, shouldInclude) {
4180
+ getDiagnostics(filePath, sourceText) {
4095
4181
  this.openFile(filePath, sourceText);
4096
4182
  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;
4183
+ return languageService?.getSemanticDiagnostics(filePath);
4102
4184
  }
4103
4185
  getLanguageService(filePath) {
4104
4186
  const project = this.getDefaultProject(filePath);
@@ -4263,51 +4345,102 @@ class SuppressedService {
4263
4345
  }
4264
4346
  }
4265
4347
 
4266
- class EnsureDiagnosticText {
4267
- static argumentMustBeProvided(argumentNameText) {
4268
- 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.`;
4269
4355
  }
4270
- static argumentOrTypeArgumentMustBeProvided(argumentNameText, typeArgumentNameText) {
4271
- 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.`;
4272
4358
  }
4273
- static typeArgumentMustBeProvided(typeArgumentNameText) {
4274
- 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
+ ];
4275
4365
  }
4276
4366
  }
4277
4367
 
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;
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
+ }
4284
4381
  }
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)]);
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
+ }
4293
4405
  return false;
4294
4406
  }
4295
- return true;
4296
4407
  }
4297
4408
 
4298
- function typeArgumentIsProvided(compiler, typeArgumentNameText, node, enclosingNode, onDiagnostics) {
4299
- if (!node || nodeBelongsToArgumentList(compiler, node)) {
4300
- 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) {
4301
4436
  const origin = DiagnosticOrigin.fromNode(enclosingNode);
4302
4437
  onDiagnostics([Diagnostic.error(text, origin)]);
4303
- return false;
4304
4438
  }
4305
- return true;
4306
4439
  }
4307
4440
 
4308
4441
  class ExpectDiagnosticText {
4309
- static argumentMustBe(argumentNameText, expectedText) {
4310
- return `An argument for '${argumentNameText}' must be ${expectedText}.`;
4442
+ static argumentMustBe(expectedText) {
4443
+ return `The argument must be ${expectedText}.`;
4311
4444
  }
4312
4445
  static typeArgumentMustBe(expectedText) {
4313
4446
  return `The type argument must be ${expectedText}.`;
@@ -4331,10 +4464,10 @@ class ExpectDiagnosticText {
4331
4464
  return `${isExpression ? "Expression" : "Type"} is not instantiable ${targetText}.`;
4332
4465
  }
4333
4466
  static acceptsProps(isExpression) {
4334
- return `${isExpression ? "Component" : "Component type"} accepts props of the given type.`;
4467
+ return `${isExpression ? "Component" : "Type"} accepts props of the given type.`;
4335
4468
  }
4336
4469
  static doesNotAcceptProps(isExpression) {
4337
- 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.`;
4338
4471
  }
4339
4472
  static canBeApplied(targetText) {
4340
4473
  return `The decorator function can be applied${targetText}.`;
@@ -4354,9 +4487,6 @@ class ExpectDiagnosticText {
4354
4487
  static matcherIsNotSupported(matcherNameText) {
4355
4488
  return `The '.${matcherNameText}()' matcher is not supported.`;
4356
4489
  }
4357
- static overloadGaveTheFollowingError(index, count, signatureText) {
4358
- return `Overload ${index} of ${count}, '${signatureText}', gave the following error.`;
4359
- }
4360
4490
  static raisedTypeError(count = 1) {
4361
4491
  return `The raised type error${count === 1 ? "" : "s"}:`;
4362
4492
  }
@@ -4394,15 +4524,6 @@ class ExpectDiagnosticText {
4394
4524
  static isNotTheSame(sourceTypeText, targetTypeText) {
4395
4525
  return `Type '${sourceTypeText}' is not the same as type '${targetTypeText}'.`;
4396
4526
  }
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
4527
  }
4407
4528
 
4408
4529
  class MatchWorker {
@@ -4432,13 +4553,6 @@ class MatchWorker {
4432
4553
  : { flags: this.#compiler.TypeFlags.NonPrimitive };
4433
4554
  return this.typeChecker.isTypeAssignableTo(type, nonPrimitiveType);
4434
4555
  }
4435
- getParameterType(signature, index) {
4436
- const parameter = signature.getDeclaration().parameters[index];
4437
- if (!parameter) {
4438
- return;
4439
- }
4440
- return this.getType(parameter);
4441
- }
4442
4556
  getSignatures(node) {
4443
4557
  let signatures = this.#signatureCache.get(node);
4444
4558
  if (!signatures) {
@@ -4456,195 +4570,100 @@ class MatchWorker {
4456
4570
  getType(node) {
4457
4571
  return this.typeChecker.getTypeAtLocation(node);
4458
4572
  }
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
- }
4471
-
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
4573
  }
4481
4574
 
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
- }, []);
4575
+ class AbilityMatcherBase {
4576
+ compiler;
4577
+ constructor(compiler) {
4578
+ this.compiler = compiler;
4512
4579
  }
4513
- #isOptionalProperty(symbol) {
4514
- 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"}`;
4515
4588
  }
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));
4589
+ getTypeArgumentCountText(targetNode) {
4590
+ if (targetNode.elements.length === 0) {
4591
+ return "without type arguments";
4543
4592
  }
4544
- return check(sourceType, targetType);
4593
+ return `with the given type argument${targetNode.elements.length === 1 ? "" : "s"}`;
4545
4594
  }
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;
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);
4565
4605
  }
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));
4606
+ else {
4607
+ origin = DiagnosticOrigin.fromAssertion(matchWorker.assertionNode);
4578
4608
  }
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
- }
4609
+ let related;
4610
+ if (diagnostic.relatedInformation != null) {
4611
+ related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
4591
4612
  }
4613
+ diagnostics.push(Diagnostic.error(text.flat(), origin).add({ related }));
4592
4614
  }
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
4615
  }
4617
- 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;
4618
4621
  }
4622
+ }
4623
+
4624
+ class ToAcceptProps extends AbilityMatcherBase {
4625
+ explainText = ExpectDiagnosticText.acceptsProps;
4626
+ explainNotText = ExpectDiagnosticText.doesNotAcceptProps;
4619
4627
  match(matchWorker, sourceNode, targetNode, onDiagnostics) {
4620
4628
  const diagnostics = [];
4621
4629
  const signatures = matchWorker.getSignatures(sourceNode);
4622
4630
  if (signatures.length === 0) {
4623
4631
  const expectedText = "of a function or class type";
4624
- const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
4625
- ? ExpectDiagnosticText.argumentMustBe("source", expectedText)
4632
+ const text = nodeBelongsToArgumentList(this.compiler, sourceNode)
4633
+ ? ExpectDiagnosticText.argumentMustBe(expectedText)
4626
4634
  : ExpectDiagnosticText.typeArgumentMustBe(expectedText);
4627
4635
  const origin = DiagnosticOrigin.fromNode(sourceNode);
4628
4636
  diagnostics.push(Diagnostic.error(text, origin));
4629
4637
  }
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);
4638
+ if (!this.compiler.isObjectLiteralExpression(targetNode)) {
4639
+ const expectedText = "an object literal with key-value pairs";
4640
+ const text = ExpectDiagnosticText.argumentMustBe(expectedText);
4634
4641
  const origin = DiagnosticOrigin.fromNode(targetNode);
4635
4642
  diagnostics.push(Diagnostic.error(text, origin));
4636
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
+ }
4637
4660
  if (diagnostics.length > 0) {
4638
4661
  onDiagnostics(diagnostics);
4639
4662
  return;
4640
4663
  }
4641
- const isMatch = signatures.some((signature) => {
4642
- const sourceType = matchWorker.getParameterType(signature, 0);
4643
- return this.#checkProperties(sourceType, targetType);
4644
- });
4645
4664
  return {
4646
- explain: () => this.#explain(matchWorker, sourceNode, targetNode),
4647
- isMatch,
4665
+ explain: () => this.explain(matchWorker, sourceNode, targetNode),
4666
+ isMatch: matchWorker.assertionNode.abilityDiagnostics.size === 0,
4648
4667
  };
4649
4668
  }
4650
4669
  }
@@ -5245,7 +5264,7 @@ class ToBeApplicable {
5245
5264
  if (type.getCallSignatures().length === 0) {
5246
5265
  const expectedText = "of a function type";
5247
5266
  const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
5248
- ? ExpectDiagnosticText.argumentMustBe("source", expectedText)
5267
+ ? ExpectDiagnosticText.argumentMustBe(expectedText)
5249
5268
  : ExpectDiagnosticText.typeArgumentMustBe(expectedText);
5250
5269
  const origin = DiagnosticOrigin.fromNode(sourceNode);
5251
5270
  onDiagnostics([Diagnostic.error(text, origin)]);
@@ -5280,55 +5299,6 @@ class ToBeAssignableTo extends RelationMatcherBase {
5280
5299
  }
5281
5300
  }
5282
5301
 
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
5302
  class ToBeCallableWith extends AbilityMatcherBase {
5333
5303
  explainText = ExpectDiagnosticText.isCallable;
5334
5304
  explainNotText = ExpectDiagnosticText.isNotCallable;
@@ -5337,7 +5307,7 @@ class ToBeCallableWith extends AbilityMatcherBase {
5337
5307
  if (sourceType.getCallSignatures().length === 0) {
5338
5308
  const text = [];
5339
5309
  if (nodeBelongsToArgumentList(this.compiler, sourceNode)) {
5340
- text.push(ExpectDiagnosticText.argumentMustBe("source", "a callable expression"));
5310
+ text.push(ExpectDiagnosticText.argumentMustBe("a callable expression"));
5341
5311
  }
5342
5312
  else {
5343
5313
  text.push(ExpectDiagnosticText.typeArgumentMustBe("a callable type"));
@@ -5364,7 +5334,7 @@ class ToBeConstructableWith extends AbilityMatcherBase {
5364
5334
  if (sourceType.getConstructSignatures().length === 0) {
5365
5335
  const text = [];
5366
5336
  if (nodeBelongsToArgumentList(this.compiler, sourceNode)) {
5367
- text.push(ExpectDiagnosticText.argumentMustBe("source", "a constructable expression"));
5337
+ text.push(ExpectDiagnosticText.argumentMustBe("a constructable expression"));
5368
5338
  }
5369
5339
  else {
5370
5340
  text.push(ExpectDiagnosticText.typeArgumentMustBe("a constructable type"));
@@ -5393,7 +5363,7 @@ class ToBeInstantiableWith extends AbilityMatcherBase {
5393
5363
  this.compiler.isExpressionWithTypeArguments(sourceNode))) {
5394
5364
  let text;
5395
5365
  if (nodeBelongsToArgumentList(this.compiler, sourceNode)) {
5396
- text = ExpectDiagnosticText.argumentMustBe("source", "an instantiable expression");
5366
+ text = ExpectDiagnosticText.argumentMustBe("an instantiable expression");
5397
5367
  }
5398
5368
  else {
5399
5369
  text = ExpectDiagnosticText.typeArgumentMustBe("an instantiable type");
@@ -5424,7 +5394,7 @@ class ToHaveProperty {
5424
5394
  const sourceTypeText = matchWorker.getTypeText(sourceNode);
5425
5395
  const targetType = matchWorker.getType(targetNode);
5426
5396
  let propertyNameText;
5427
- if (isStringOrNumberLiteralType(this.#compiler, targetType)) {
5397
+ if (targetType.flags & (this.#compiler.TypeFlags.StringLiteral | this.#compiler.TypeFlags.NumberLiteral)) {
5428
5398
  propertyNameText = targetType.value.toString();
5429
5399
  }
5430
5400
  else {
@@ -5442,15 +5412,18 @@ class ToHaveProperty {
5442
5412
  !matchWorker.extendsObjectType(sourceType)) {
5443
5413
  const expectedText = "of an object type";
5444
5414
  const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
5445
- ? ExpectDiagnosticText.argumentMustBe("source", expectedText)
5415
+ ? ExpectDiagnosticText.argumentMustBe(expectedText)
5446
5416
  : ExpectDiagnosticText.typeArgumentMustBe(expectedText);
5447
5417
  const origin = DiagnosticOrigin.fromNode(sourceNode);
5448
5418
  diagnostics.push(Diagnostic.error(text, origin));
5449
5419
  }
5450
5420
  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);
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);
5454
5427
  const origin = DiagnosticOrigin.fromNode(targetNode);
5455
5428
  diagnostics.push(Diagnostic.error(text, origin));
5456
5429
  }
@@ -5509,8 +5482,8 @@ class ToRaiseError {
5509
5482
  if (!(this.#compiler.isStringLiteralLike(targetNode) ||
5510
5483
  this.#compiler.isNumericLiteral(targetNode) ||
5511
5484
  this.#compiler.isRegularExpressionLiteral(targetNode))) {
5512
- const expectedText = "a string, number or regular expression literal";
5513
- const text = ExpectDiagnosticText.argumentMustBe("target", expectedText);
5485
+ const expectedText = "a string, number or regular expression";
5486
+ const text = ExpectDiagnosticText.argumentMustBe(expectedText);
5514
5487
  const origin = DiagnosticOrigin.fromNode(targetNode);
5515
5488
  diagnostics.push(Diagnostic.error(text, origin));
5516
5489
  }
@@ -5551,6 +5524,7 @@ class ToRaiseError {
5551
5524
 
5552
5525
  class ExpectService {
5553
5526
  #compiler;
5527
+ #ensure;
5554
5528
  #program;
5555
5529
  #reject;
5556
5530
  toAcceptProps;
@@ -5563,11 +5537,12 @@ class ExpectService {
5563
5537
  toBeInstantiableWith;
5564
5538
  toHaveProperty;
5565
5539
  toRaiseError;
5566
- constructor(compiler, program, reject) {
5540
+ constructor(compiler, program, resolvedConfig) {
5567
5541
  this.#compiler = compiler;
5568
5542
  this.#program = program;
5569
- this.#reject = reject;
5570
- 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);
5571
5546
  this.toBe = new ToBe(compiler, program);
5572
5547
  this.toBeApplicable = new ToBeApplicable(compiler);
5573
5548
  this.toBeAssignableFrom = new ToBeAssignableFrom();
@@ -5580,15 +5555,12 @@ class ExpectService {
5580
5555
  }
5581
5556
  match(assertionNode, onDiagnostics) {
5582
5557
  const matcherNameText = assertionNode.matcherNameNode.name.text;
5583
- if (!argumentOrTypeArgumentIsProvided("source", "Source", assertionNode.source[0], assertionNode.node.expression, onDiagnostics)) {
5558
+ if (!this.#ensure.argumentOrTypeArgument(assertionNode.source[0], assertionNode.node.expression, onDiagnostics)) {
5584
5559
  return;
5585
5560
  }
5586
5561
  const matchWorker = new MatchWorker(this.#compiler, this.#program, assertionNode);
5587
5562
  if (!(matcherNameText === "toBeInstantiableWith" || (matcherNameText === "toRaiseError" && !assertionNode.isNot)) &&
5588
- this.#reject.argumentType([
5589
- ["source", assertionNode.source[0]],
5590
- ["target", assertionNode.target?.[0]],
5591
- ], onDiagnostics)) {
5563
+ this.#reject.argumentType([assertionNode.source[0], assertionNode.target?.[0]], onDiagnostics)) {
5592
5564
  return;
5593
5565
  }
5594
5566
  switch (matcherNameText) {
@@ -5596,7 +5568,7 @@ class ExpectService {
5596
5568
  case "toBe":
5597
5569
  case "toBeAssignableFrom":
5598
5570
  case "toBeAssignableTo":
5599
- if (!argumentOrTypeArgumentIsProvided("target", "Target", assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
5571
+ if (!this.#ensure.argumentOrTypeArgument(assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
5600
5572
  return;
5601
5573
  }
5602
5574
  return this[matcherNameText].match(matchWorker, assertionNode.source[0], assertionNode.target[0], onDiagnostics);
@@ -5607,13 +5579,13 @@ class ExpectService {
5607
5579
  case "toRaiseError":
5608
5580
  return this[matcherNameText].match(matchWorker, assertionNode.source[0], assertionNode.target, onDiagnostics);
5609
5581
  case "toBeInstantiableWith": {
5610
- if (!typeArgumentIsProvided(this.#compiler, "Target", assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
5582
+ if (!this.#ensure.typeArgument(assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
5611
5583
  return;
5612
5584
  }
5613
5585
  return this.toBeInstantiableWith.match(matchWorker, assertionNode.source[0], assertionNode.target[0], onDiagnostics);
5614
5586
  }
5615
5587
  case "toHaveProperty":
5616
- if (!argumentIsProvided(this.#compiler, "key", assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
5588
+ if (!this.#ensure.argument(assertionNode.target?.[0], assertionNode.matcherNameNode.name, onDiagnostics)) {
5617
5589
  return;
5618
5590
  }
5619
5591
  return this.toHaveProperty.match(matchWorker, assertionNode.source[0], assertionNode.target[0], onDiagnostics);
@@ -5629,67 +5601,6 @@ class ExpectService {
5629
5601
  }
5630
5602
  }
5631
5603
 
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
5604
  class FixmeDiagnosticText {
5694
5605
  static considerRemoving() {
5695
5606
  return "Consider removing the '// @tstyche fixme' directive.";
@@ -5763,8 +5674,7 @@ class TestTreeWalker {
5763
5674
  this.#cancellationToken = options.cancellationToken;
5764
5675
  this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
5765
5676
  this.#position = options.position;
5766
- const reject = new Reject(compiler, program, resolvedConfig);
5767
- this.#expectService = new ExpectService(compiler, program, reject);
5677
+ this.#expectService = new ExpectService(compiler, program, resolvedConfig);
5768
5678
  }
5769
5679
  async #resolveRunMode(flags, node) {
5770
5680
  const ifDirective = Directive.getDirectiveRange(this.#compiler, node, "if");
@@ -5998,7 +5908,7 @@ class FileRunner {
5998
5908
  class Runner {
5999
5909
  #eventEmitter = new EventEmitter();
6000
5910
  #resolvedConfig;
6001
- static version = "7.1.0";
5911
+ static version = "7.2.0";
6002
5912
  constructor(resolvedConfig) {
6003
5913
  this.#resolvedConfig = resolvedConfig;
6004
5914
  }
@@ -6223,4 +6133,4 @@ class Cli {
6223
6133
  }
6224
6134
  }
6225
6135
 
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 };
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 };