tstyche 4.0.0-beta.1 → 4.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,14 +18,18 @@ If you are used to test JavaScript, a simple type test file should look familiar
18
18
  ```ts
19
19
  import { expect, test } from "tstyche";
20
20
 
21
- function firstItem<T>(target: Array<T>): T | undefined {
22
- return target[0];
21
+ function pickLonger<T extends { length: number }>(a: T, b: T) {
22
+ return a.length >= b.length ? a : b;
23
23
  }
24
24
 
25
- test("firstItem", () => {
26
- expect(firstItem(["a", "b", "c"])).type.toBe<string | undefined>();
25
+ test("pickLonger()", () => {
26
+ expect(pickLonger([1, 2], [1, 2, 3])).type.toBe<Array<number>>();
27
+ expect(pickLonger("two", "three")).type.toBe<"two" | "three">();
27
28
 
28
- expect(firstItem()).type.toRaiseError("Expected 1 argument");
29
+ expect(pickLonger).type.not.toBeCallableWith(1, 2);
30
+
31
+ expect(pickLonger).type.not.toBeCallableWith("zero", [123]);
32
+ expect(pickLonger<string | Array<number>>).type.toBeCallableWith("zero", [123]);
29
33
  });
30
34
  ```
31
35
 
@@ -59,6 +63,7 @@ Here is the list of all matchers:
59
63
  - `.toBe()`, `.toBeAssignableTo()`, `.toBeAssignableWith()` compare types or types of expression,
60
64
  - `.toAcceptProps()` checks the type of JSX component props,
61
65
  - `.toBeApplicable` ensures that the decorator function can be applied,
66
+ - `.toBeCallableWith()` checks whether a function can be called with the given arguments,
62
67
  - `.toHaveProperty()` looks up keys on an object type,
63
68
  - `.toRaiseError()` captures the message or code of a type error.
64
69
 
package/build/index.d.cts CHANGED
@@ -127,6 +127,10 @@ interface Matchers {
127
127
  */
128
128
  (target: unknown): void;
129
129
  };
130
+ /**
131
+ * Checks if the source type can be called with the target arguments.
132
+ */
133
+ toBeCallableWith: (...target: Array<unknown>) => void;
130
134
  /**
131
135
  * Checks if a property key exists on the source type.
132
136
  */
package/build/index.d.ts CHANGED
@@ -127,6 +127,10 @@ interface Matchers {
127
127
  */
128
128
  (target: unknown): void;
129
129
  };
130
+ /**
131
+ * Checks if the source type can be called with the target arguments.
132
+ */
133
+ toBeCallableWith: (...target: Array<unknown>) => void;
130
134
  /**
131
135
  * Checks if a property key exists on the source type.
132
136
  */
@@ -45,6 +45,7 @@ declare class DiagnosticOrigin {
45
45
  constructor(start: number, end: number, sourceFile: SourceFile | ts.SourceFile, assertion?: AssertionNode);
46
46
  static fromAssertion(assertion: AssertionNode): DiagnosticOrigin;
47
47
  static fromNode(node: ts.Node, assertion?: AssertionNode): DiagnosticOrigin;
48
+ static fromNodes(nodes: ts.NodeArray<ts.Node>, assertion?: AssertionNode): DiagnosticOrigin;
48
49
  }
49
50
 
50
51
  declare class Diagnostic {
@@ -324,7 +325,7 @@ declare const defaultOptions: Required<ConfigFileOptions>;
324
325
 
325
326
  declare class ProjectService {
326
327
  #private;
327
- constructor(resolvedConfig: ResolvedConfig, compiler: typeof ts);
328
+ constructor(compiler: typeof ts, resolvedConfig: ResolvedConfig);
328
329
  closeFile(filePath: string): void;
329
330
  getDefaultProject(filePath: string): ts.server.Project | undefined;
330
331
  getLanguageService(filePath: string): ts.LanguageService | undefined;
@@ -602,6 +603,7 @@ declare class ExpectService {
602
603
  private toBeApplicable;
603
604
  private toBeAssignableTo;
604
605
  private toBeAssignableWith;
606
+ private toBeCallableWith;
605
607
  private toHaveProperty;
606
608
  private toRaiseError;
607
609
  constructor(compiler: typeof ts, typeChecker: TypeChecker, resolvedConfig: ResolvedConfig);
package/build/tstyche.js CHANGED
@@ -99,6 +99,9 @@ class DiagnosticOrigin {
99
99
  static fromNode(node, assertion) {
100
100
  return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertion);
101
101
  }
102
+ static fromNodes(nodes, assertion) {
103
+ return new DiagnosticOrigin(nodes.pos, nodes.end, nodes[0].getSourceFile(), assertion);
104
+ }
102
105
  }
103
106
 
104
107
  class Diagnostic {
@@ -2010,11 +2013,12 @@ class AbilityLayer {
2010
2013
  return " ".repeat(range.end - range.start);
2011
2014
  }
2012
2015
  const text = [];
2013
- for (let i = range.start; i < range.end; i++) {
2014
- switch (this.#text.charAt(i)) {
2016
+ for (let index = range.start; index < range.end; index++) {
2017
+ const character = this.#text.charAt(index);
2018
+ switch (character) {
2015
2019
  case "\n":
2016
2020
  case "\r":
2017
- text.push(this.#text.charAt(i));
2021
+ text.push(character);
2018
2022
  break;
2019
2023
  default:
2020
2024
  text.push(" ");
@@ -2033,19 +2037,18 @@ class AbilityLayer {
2033
2037
  if (this.#nodes.length > 0) {
2034
2038
  this.#projectService.openFile(this.#filePath, this.#text, this.#resolvedConfig.rootPath);
2035
2039
  const languageService = this.#projectService.getLanguageService(this.#filePath);
2036
- const diagnostics = new Set(languageService?.getSemanticDiagnostics(this.#filePath)?.toReversed());
2037
- if (diagnostics.size > 0) {
2038
- for (const node of this.#nodes.toReversed()) {
2039
- for (const diagnostic of diagnostics) {
2040
- if (diagnostic.start != null &&
2041
- diagnostic.start >= node.matcherNode.pos &&
2042
- diagnostic.start <= node.matcherNode.end) {
2043
- if (!node.abilityDiagnostics) {
2044
- node.abilityDiagnostics = new Set();
2045
- }
2046
- node.abilityDiagnostics.add(diagnostic);
2047
- diagnostics.delete(diagnostic);
2040
+ const diagnostics = new Set(languageService?.getSemanticDiagnostics(this.#filePath));
2041
+ const nodes = this.#nodes.reverse();
2042
+ for (const diagnostic of diagnostics) {
2043
+ for (const node of nodes) {
2044
+ if (diagnostic.start != null &&
2045
+ diagnostic.start >= node.matcherNode.pos &&
2046
+ diagnostic.start <= node.matcherNode.end) {
2047
+ if (!node.abilityDiagnostics) {
2048
+ node.abilityDiagnostics = new Set();
2048
2049
  }
2050
+ node.abilityDiagnostics.add(diagnostic);
2051
+ diagnostics.delete(diagnostic);
2049
2052
  }
2050
2053
  }
2051
2054
  }
@@ -2067,6 +2070,17 @@ class AbilityLayer {
2067
2070
  ]);
2068
2071
  break;
2069
2072
  }
2073
+ case "toBeCallableWith": {
2074
+ const expectStart = assertionNode.node.pos;
2075
+ const expectExpressionEnd = assertionNode.node.expression.end;
2076
+ const expectEnd = assertionNode.node.end;
2077
+ const matcherNameEnd = assertionNode.matcherNameNode.end;
2078
+ this.#addRanges(assertionNode, [
2079
+ { end: expectExpressionEnd + 1, start: expectStart },
2080
+ { end: matcherNameEnd, start: expectEnd - 1 },
2081
+ ]);
2082
+ break;
2083
+ }
2070
2084
  }
2071
2085
  }
2072
2086
  open(sourceFile) {
@@ -3284,9 +3298,9 @@ class ProjectService {
3284
3298
  #resolvedConfig;
3285
3299
  #seenPrograms = new WeakSet();
3286
3300
  #service;
3287
- constructor(resolvedConfig, compiler) {
3288
- this.#resolvedConfig = resolvedConfig;
3301
+ constructor(compiler, resolvedConfig) {
3289
3302
  this.#compiler = compiler;
3303
+ this.#resolvedConfig = resolvedConfig;
3290
3304
  const noop = () => undefined;
3291
3305
  const noopLogger = {
3292
3306
  close: noop,
@@ -3434,17 +3448,29 @@ class ExpectDiagnosticText {
3434
3448
  static argumentMustBeProvided(argumentNameText) {
3435
3449
  return `An argument for '${argumentNameText}' must be provided.`;
3436
3450
  }
3437
- static componentAcceptsProps(isTypeNode) {
3451
+ static canBeCalled(isTypeNode, targetText) {
3452
+ return `${isTypeNode ? "Type" : "Expression"} can be called ${targetText}.`;
3453
+ }
3454
+ static cannotBeCalled(isTypeNode, targetText) {
3455
+ return `${isTypeNode ? "Type" : "Expression"} cannot be called ${targetText}.`;
3456
+ }
3457
+ static acceptsProps(isTypeNode) {
3438
3458
  return `${isTypeNode ? "Component type" : "Component"} accepts props of the given type.`;
3439
3459
  }
3440
- static componentDoesNotAcceptProps(isTypeNode) {
3460
+ static doesNotAcceptProps(isTypeNode) {
3441
3461
  return `${isTypeNode ? "Component type" : "Component"} does not accept props of the given type.`;
3442
3462
  }
3443
- static decoratorCanBeApplied(targetText) {
3463
+ static canBeApplied(targetText) {
3444
3464
  return `The decorator function can be applied${targetText}.`;
3445
3465
  }
3446
- static decoratorCanNotBeApplied(targetText) {
3447
- return `The decorator function can not be applied${targetText}.`;
3466
+ static cannotBeApplied(targetText) {
3467
+ return `The decorator function cannot be applied${targetText}.`;
3468
+ }
3469
+ static doesNotHaveProperty(typeText, propertyNameText) {
3470
+ return `Type '${typeText}' does not have property '${propertyNameText}'.`;
3471
+ }
3472
+ static hasProperty(typeText, propertyNameText) {
3473
+ return `Type '${typeText}' has property '${propertyNameText}'.`;
3448
3474
  }
3449
3475
  static matcherIsNotSupported(matcherNameText) {
3450
3476
  return `The '.${matcherNameText}()' matcher is not supported.`;
@@ -3461,50 +3487,44 @@ class ExpectDiagnosticText {
3461
3487
  static typeArgumentMustBe(argumentNameText, expectedText) {
3462
3488
  return `A type argument for '${argumentNameText}' must be ${expectedText}.`;
3463
3489
  }
3464
- static typeDidNotRaiseError(isTypeNode) {
3465
- return `${isTypeNode ? "Type" : "Expression type"} did not raise a type error.`;
3490
+ static raisedError(isTypeNode, count, targetCount) {
3491
+ let countText = "a";
3492
+ if (count > 1 || targetCount > 1) {
3493
+ countText = count > targetCount ? `${count}` : `only ${count}`;
3494
+ }
3495
+ return `${isTypeNode ? "Type" : "Expression type"} raised ${countText} type error${count === 1 ? "" : "s"}.`;
3466
3496
  }
3467
- static typeDidNotRaiseMatchingError(isTypeNode) {
3468
- return `${isTypeNode ? "Type" : "Expression type"} did not raise a matching type error.`;
3497
+ static didNotRaiseError(isTypeNode) {
3498
+ return `${isTypeNode ? "Type" : "Expression type"} did not raise a type error.`;
3469
3499
  }
3470
- static typeDoesNotHaveProperty(typeText, propertyNameText) {
3471
- return `Type '${typeText}' does not have property '${propertyNameText}'.`;
3500
+ static raisedMatchingError(isTypeNode) {
3501
+ return `${isTypeNode ? "Type" : "Expression type"} raised a matching type error.`;
3472
3502
  }
3473
- static typeHasProperty(typeText, propertyNameText) {
3474
- return `Type '${typeText}' has property '${propertyNameText}'.`;
3503
+ static didNotRaiseMatchingError(isTypeNode) {
3504
+ return `${isTypeNode ? "Type" : "Expression type"} did not raise a matching type error.`;
3475
3505
  }
3476
- static typeIsAssignableTo(sourceTypeText, targetTypeText) {
3506
+ static isAssignableTo(sourceTypeText, targetTypeText) {
3477
3507
  return `Type '${sourceTypeText}' is assignable to type '${targetTypeText}'.`;
3478
3508
  }
3479
- static typeIsAssignableWith(sourceTypeText, targetTypeText) {
3480
- return `Type '${sourceTypeText}' is assignable with type '${targetTypeText}'.`;
3481
- }
3482
- static typeIsIdenticalTo(sourceTypeText, targetTypeText) {
3483
- return `Type '${sourceTypeText}' is identical to type '${targetTypeText}'.`;
3484
- }
3485
- static typeIsNotAssignableTo(sourceTypeText, targetTypeText) {
3509
+ static isNotAssignableTo(sourceTypeText, targetTypeText) {
3486
3510
  return `Type '${sourceTypeText}' is not assignable to type '${targetTypeText}'.`;
3487
3511
  }
3488
- static typeIsNotAssignableWith(sourceTypeText, targetTypeText) {
3512
+ static isAssignableWith(sourceTypeText, targetTypeText) {
3513
+ return `Type '${sourceTypeText}' is assignable with type '${targetTypeText}'.`;
3514
+ }
3515
+ static isNotAssignableWith(sourceTypeText, targetTypeText) {
3489
3516
  return `Type '${sourceTypeText}' is not assignable with type '${targetTypeText}'.`;
3490
3517
  }
3491
- static typeIsNotCompatibleWith(sourceTypeText, targetTypeText) {
3492
- return `Type '${sourceTypeText}' is not compatible with type '${targetTypeText}'.`;
3518
+ static isIdenticalTo(sourceTypeText, targetTypeText) {
3519
+ return `Type '${sourceTypeText}' is identical to type '${targetTypeText}'.`;
3493
3520
  }
3494
- static typeIsNotIdenticalTo(sourceTypeText, targetTypeText) {
3521
+ static isNotIdenticalTo(sourceTypeText, targetTypeText) {
3495
3522
  return `Type '${sourceTypeText}' is not identical to type '${targetTypeText}'.`;
3496
3523
  }
3497
- static typeRaisedError(isTypeNode, count, targetCount) {
3498
- let countText = "a";
3499
- if (count > 1 || targetCount > 1) {
3500
- countText = count > targetCount ? `${count}` : `only ${count}`;
3501
- }
3502
- return `${isTypeNode ? "Type" : "Expression type"} raised ${countText} type error${count === 1 ? "" : "s"}.`;
3503
- }
3504
- static typeRaisedMatchingError(isTypeNode) {
3505
- return `${isTypeNode ? "Type" : "Expression type"} raised a matching type error.`;
3524
+ static isNotCompatibleWith(sourceTypeText, targetTypeText) {
3525
+ return `Type '${sourceTypeText}' is not compatible with type '${targetTypeText}'.`;
3506
3526
  }
3507
- static typeRequiresProperty(typeText, propertyNameText) {
3527
+ static requiresProperty(typeText, propertyNameText) {
3508
3528
  return `Type '${typeText}' requires property '${propertyNameText}'.`;
3509
3529
  }
3510
3530
  static typesOfPropertyAreNotCompatible(propertyNameText) {
@@ -3523,7 +3543,6 @@ class MatchWorker {
3523
3543
  assertion;
3524
3544
  #compiler;
3525
3545
  #signatureCache = new Map();
3526
- #typeCache = new Map();
3527
3546
  #typeChecker;
3528
3547
  constructor(compiler, typeChecker, assertion) {
3529
3548
  this.#compiler = compiler;
@@ -3575,7 +3594,7 @@ class MatchWorker {
3575
3594
  if (!parameter) {
3576
3595
  return;
3577
3596
  }
3578
- return this.#getTypeOfNode(parameter);
3597
+ return this.getType(parameter);
3579
3598
  }
3580
3599
  getSignatures(node) {
3581
3600
  let signatures = this.#signatureCache.get(node);
@@ -3593,21 +3612,7 @@ class MatchWorker {
3593
3612
  return this.#typeChecker.typeToString(type);
3594
3613
  }
3595
3614
  getType(node) {
3596
- return this.#compiler.isTypeNode(node) ? this.#getTypeOfTypeNode(node) : this.#getTypeOfNode(node);
3597
- }
3598
- #getTypeOfNode(node) {
3599
- let type = this.#typeCache.get(node);
3600
- if (!type) {
3601
- type = this.#typeChecker.getTypeAtLocation(node);
3602
- }
3603
- return type;
3604
- }
3605
- #getTypeOfTypeNode(node) {
3606
- let type = this.#typeCache.get(node);
3607
- if (!type) {
3608
- type = this.#typeChecker.getTypeFromTypeNode(node);
3609
- }
3610
- return type;
3615
+ return this.#typeChecker.getTypeAtLocation(node);
3611
3616
  }
3612
3617
  isStringOrNumberLiteralType(type) {
3613
3618
  return !!(type.flags & this.#compiler.TypeFlags.StringOrNumberLiteral);
@@ -3655,8 +3660,8 @@ class ToAcceptProps {
3655
3660
  return signatures.reduce((accumulator, signature, index) => {
3656
3661
  let diagnostic;
3657
3662
  const introText = matchWorker.assertion.isNot
3658
- ? ExpectDiagnosticText.componentAcceptsProps(this.#compiler.isTypeNode(sourceNode))
3659
- : ExpectDiagnosticText.componentDoesNotAcceptProps(this.#compiler.isTypeNode(sourceNode));
3663
+ ? ExpectDiagnosticText.acceptsProps(this.#compiler.isTypeNode(sourceNode))
3664
+ : ExpectDiagnosticText.doesNotAcceptProps(this.#compiler.isTypeNode(sourceNode));
3660
3665
  const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
3661
3666
  if (signatures.length > 1) {
3662
3667
  const signatureText = this.#typeChecker.signatureToString(signature, sourceNode);
@@ -3722,8 +3727,8 @@ class ToAcceptProps {
3722
3727
  const sourceProperty = sourceType?.getProperty(targetPropertyName);
3723
3728
  if (!sourceProperty) {
3724
3729
  const text = [
3725
- ExpectDiagnosticText.typeIsNotCompatibleWith(sourceTypeText, targetTypeText),
3726
- ExpectDiagnosticText.typeDoesNotHaveProperty(sourceTypeText, targetPropertyName),
3730
+ ExpectDiagnosticText.isNotCompatibleWith(sourceTypeText, targetTypeText),
3731
+ ExpectDiagnosticText.doesNotHaveProperty(sourceTypeText, targetPropertyName),
3727
3732
  ];
3728
3733
  const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
3729
3734
  diagnostics.push(diagnostic.extendWith(text, origin));
@@ -3731,8 +3736,8 @@ class ToAcceptProps {
3731
3736
  }
3732
3737
  if (this.#isOptionalProperty(targetProperty) && !this.#isOptionalProperty(sourceProperty)) {
3733
3738
  const text = [
3734
- ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText),
3735
- ExpectDiagnosticText.typeRequiresProperty(sourceTypeText, targetPropertyName),
3739
+ ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
3740
+ ExpectDiagnosticText.requiresProperty(sourceTypeText, targetPropertyName),
3736
3741
  ];
3737
3742
  const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
3738
3743
  diagnostics.push(diagnostic.extendWith(text, origin));
@@ -3744,9 +3749,9 @@ class ToAcceptProps {
3744
3749
  const targetPropertyTypeText = this.#typeChecker.typeToString(targetPropertyType);
3745
3750
  const sourcePropertyTypeText = this.#typeChecker.typeToString(sourcePropertyType);
3746
3751
  const text = [
3747
- ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText),
3752
+ ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
3748
3753
  ExpectDiagnosticText.typesOfPropertyAreNotCompatible(targetPropertyName),
3749
- ExpectDiagnosticText.typeIsNotAssignableWith(sourcePropertyTypeText, targetPropertyTypeText),
3754
+ ExpectDiagnosticText.isNotAssignableWith(sourcePropertyTypeText, targetPropertyTypeText),
3750
3755
  ];
3751
3756
  const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
3752
3757
  diagnostics.push(diagnostic.extendWith(text, origin));
@@ -3758,15 +3763,15 @@ class ToAcceptProps {
3758
3763
  const targetProperty = targetType.getProperty(sourcePropertyName);
3759
3764
  if (!targetProperty && !this.#isOptionalProperty(sourceProperty)) {
3760
3765
  const text = [
3761
- ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText),
3762
- ExpectDiagnosticText.typeRequiresProperty(sourceTypeText, sourcePropertyName),
3766
+ ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
3767
+ ExpectDiagnosticText.requiresProperty(sourceTypeText, sourcePropertyName),
3763
3768
  ];
3764
3769
  diagnostics.push(diagnostic.extendWith(text));
3765
3770
  }
3766
3771
  }
3767
3772
  }
3768
3773
  if (diagnostics.length === 0) {
3769
- const text = ExpectDiagnosticText.typeIsAssignableWith(sourceTypeText, targetTypeText);
3774
+ const text = ExpectDiagnosticText.isAssignableWith(sourceTypeText, targetTypeText);
3770
3775
  diagnostics.push(diagnostic.extendWith(text));
3771
3776
  return { diagnostics, isMatch: true };
3772
3777
  }
@@ -3776,8 +3781,8 @@ class ToAcceptProps {
3776
3781
  let accumulator = [];
3777
3782
  const isMatch = sourceType.types.some((sourceType) => {
3778
3783
  const text = matchWorker.assertion.isNot
3779
- ? ExpectDiagnosticText.typeIsAssignableWith(sourceTypeText, targetTypeText)
3780
- : ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText);
3784
+ ? ExpectDiagnosticText.isAssignableWith(sourceTypeText, targetTypeText)
3785
+ : ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText);
3781
3786
  const { diagnostics, isMatch } = explain(sourceType, targetType, diagnostic.extendWith(text));
3782
3787
  if (isMatch) {
3783
3788
  accumulator = diagnostics;
@@ -3839,8 +3844,8 @@ class RelationMatcherBase {
3839
3844
  }
3840
3845
 
3841
3846
  class ToBe extends RelationMatcherBase {
3842
- explainText = ExpectDiagnosticText.typeIsIdenticalTo;
3843
- explainNotText = ExpectDiagnosticText.typeIsNotIdenticalTo;
3847
+ explainText = ExpectDiagnosticText.isIdenticalTo;
3848
+ explainNotText = ExpectDiagnosticText.isNotIdenticalTo;
3844
3849
  match(matchWorker, sourceNode, targetNode) {
3845
3850
  return {
3846
3851
  explain: () => this.explain(matchWorker, sourceNode, targetNode),
@@ -3886,7 +3891,7 @@ class ToBeApplicable {
3886
3891
  if (matchWorker.assertion.abilityDiagnostics) {
3887
3892
  for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
3888
3893
  const text = [
3889
- ExpectDiagnosticText.decoratorCanNotBeApplied(targetText),
3894
+ ExpectDiagnosticText.cannotBeApplied(targetText),
3890
3895
  typeof diagnostic.messageText === "string"
3891
3896
  ? diagnostic.messageText
3892
3897
  : Diagnostic.toMessageText(diagnostic.messageText),
@@ -3897,7 +3902,7 @@ class ToBeApplicable {
3897
3902
  }
3898
3903
  else {
3899
3904
  const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
3900
- diagnostics.push(Diagnostic.error(ExpectDiagnosticText.decoratorCanBeApplied(targetText), origin));
3905
+ diagnostics.push(Diagnostic.error(ExpectDiagnosticText.canBeApplied(targetText), origin));
3901
3906
  }
3902
3907
  return diagnostics;
3903
3908
  }
@@ -3920,8 +3925,8 @@ class ToBeApplicable {
3920
3925
  }
3921
3926
 
3922
3927
  class ToBeAssignableTo extends RelationMatcherBase {
3923
- explainText = ExpectDiagnosticText.typeIsAssignableTo;
3924
- explainNotText = ExpectDiagnosticText.typeIsNotAssignableTo;
3928
+ explainText = ExpectDiagnosticText.isAssignableTo;
3929
+ explainNotText = ExpectDiagnosticText.isNotAssignableTo;
3925
3930
  match(matchWorker, sourceNode, targetNode) {
3926
3931
  return {
3927
3932
  explain: () => this.explain(matchWorker, sourceNode, targetNode),
@@ -3931,8 +3936,8 @@ class ToBeAssignableTo extends RelationMatcherBase {
3931
3936
  }
3932
3937
 
3933
3938
  class ToBeAssignableWith extends RelationMatcherBase {
3934
- explainText = ExpectDiagnosticText.typeIsAssignableWith;
3935
- explainNotText = ExpectDiagnosticText.typeIsNotAssignableWith;
3939
+ explainText = ExpectDiagnosticText.isAssignableWith;
3940
+ explainNotText = ExpectDiagnosticText.isNotAssignableWith;
3936
3941
  match(matchWorker, sourceNode, targetNode) {
3937
3942
  return {
3938
3943
  explain: () => this.explain(matchWorker, sourceNode, targetNode),
@@ -3941,6 +3946,68 @@ class ToBeAssignableWith extends RelationMatcherBase {
3941
3946
  }
3942
3947
  }
3943
3948
 
3949
+ class ToBeCallableWith {
3950
+ #compiler;
3951
+ constructor(compiler) {
3952
+ this.#compiler = compiler;
3953
+ }
3954
+ isDiagnosticWithLocation(diagnostic) {
3955
+ return diagnostic.start != null;
3956
+ }
3957
+ #resolveTargetText(nodes) {
3958
+ if (nodes.length === 0) {
3959
+ return "without arguments";
3960
+ }
3961
+ if (nodes.length === 1 && nodes[0]?.kind === this.#compiler.SyntaxKind.SpreadElement) {
3962
+ return "with the given arguments";
3963
+ }
3964
+ return `with the given argument${nodes.length === 1 ? "" : "s"}`;
3965
+ }
3966
+ #explain(matchWorker, sourceNode, targetNodes) {
3967
+ const isTypeNode = this.#compiler.isTypeNode(sourceNode);
3968
+ const targetText = this.#resolveTargetText(targetNodes);
3969
+ const diagnostics = [];
3970
+ if (matchWorker.assertion.abilityDiagnostics) {
3971
+ for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
3972
+ const text = [
3973
+ ExpectDiagnosticText.cannotBeCalled(isTypeNode, targetText),
3974
+ typeof diagnostic.messageText === "string"
3975
+ ? diagnostic.messageText
3976
+ : Diagnostic.toMessageText(diagnostic.messageText),
3977
+ ];
3978
+ let origin;
3979
+ if (this.isDiagnosticWithLocation(diagnostic) &&
3980
+ diagnostic.start >= targetNodes.pos &&
3981
+ diagnostic.start <= targetNodes.end) {
3982
+ origin = new DiagnosticOrigin(diagnostic.start, diagnostic.start + diagnostic.length, sourceNode.getSourceFile());
3983
+ }
3984
+ else {
3985
+ origin =
3986
+ targetNodes.length > 0
3987
+ ? DiagnosticOrigin.fromNodes(targetNodes)
3988
+ : DiagnosticOrigin.fromAssertion(matchWorker.assertion);
3989
+ }
3990
+ let related;
3991
+ if (diagnostic.relatedInformation != null) {
3992
+ related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
3993
+ }
3994
+ diagnostics.push(Diagnostic.error(text.flat(), origin).add({ related }));
3995
+ }
3996
+ }
3997
+ else {
3998
+ const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
3999
+ diagnostics.push(Diagnostic.error(ExpectDiagnosticText.canBeCalled(isTypeNode, targetText), origin));
4000
+ }
4001
+ return diagnostics;
4002
+ }
4003
+ match(matchWorker, sourceNode, targetNodes, _onDiagnostics) {
4004
+ return {
4005
+ explain: () => this.#explain(matchWorker, sourceNode, targetNodes),
4006
+ isMatch: !matchWorker.assertion.abilityDiagnostics,
4007
+ };
4008
+ }
4009
+ }
4010
+
3944
4011
  class ToHaveProperty {
3945
4012
  #compiler;
3946
4013
  constructor(compiler) {
@@ -3958,8 +4025,8 @@ class ToHaveProperty {
3958
4025
  }
3959
4026
  const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
3960
4027
  return matchWorker.assertion.isNot
3961
- ? [Diagnostic.error(ExpectDiagnosticText.typeHasProperty(sourceTypeText, propertyNameText), origin)]
3962
- : [Diagnostic.error(ExpectDiagnosticText.typeDoesNotHaveProperty(sourceTypeText, propertyNameText), origin)];
4028
+ ? [Diagnostic.error(ExpectDiagnosticText.hasProperty(sourceTypeText, propertyNameText), origin)]
4029
+ : [Diagnostic.error(ExpectDiagnosticText.doesNotHaveProperty(sourceTypeText, propertyNameText), origin)];
3963
4030
  }
3964
4031
  match(matchWorker, sourceNode, targetNode, onDiagnostics) {
3965
4032
  const diagnostics = [];
@@ -4009,12 +4076,12 @@ class ToRaiseError {
4009
4076
  const isTypeNode = this.#compiler.isTypeNode(sourceNode);
4010
4077
  const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
4011
4078
  if (matchWorker.assertion.diagnostics.size === 0) {
4012
- const text = ExpectDiagnosticText.typeDidNotRaiseError(isTypeNode);
4079
+ const text = ExpectDiagnosticText.didNotRaiseError(isTypeNode);
4013
4080
  return [Diagnostic.error(text, origin)];
4014
4081
  }
4015
4082
  if (matchWorker.assertion.diagnostics.size !== targetNodes.length) {
4016
4083
  const count = matchWorker.assertion.diagnostics.size;
4017
- const text = ExpectDiagnosticText.typeRaisedError(isTypeNode, count, targetNodes.length);
4084
+ const text = ExpectDiagnosticText.raisedError(isTypeNode, count, targetNodes.length);
4018
4085
  const related = [
4019
4086
  Diagnostic.error(ExpectDiagnosticText.raisedTypeError(count)),
4020
4087
  ...Diagnostic.fromDiagnostics([...matchWorker.assertion.diagnostics]),
@@ -4026,8 +4093,8 @@ class ToRaiseError {
4026
4093
  const isMatch = this.#matchExpectedError(diagnostic, targetNode);
4027
4094
  if (matchWorker.assertion.isNot ? isMatch : !isMatch) {
4028
4095
  const text = matchWorker.assertion.isNot
4029
- ? ExpectDiagnosticText.typeRaisedMatchingError(isTypeNode)
4030
- : ExpectDiagnosticText.typeDidNotRaiseMatchingError(isTypeNode);
4096
+ ? ExpectDiagnosticText.raisedMatchingError(isTypeNode)
4097
+ : ExpectDiagnosticText.didNotRaiseMatchingError(isTypeNode);
4031
4098
  const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
4032
4099
  const related = [
4033
4100
  Diagnostic.error(ExpectDiagnosticText.raisedTypeError()),
@@ -4092,6 +4159,7 @@ class ExpectService {
4092
4159
  toBeApplicable;
4093
4160
  toBeAssignableTo;
4094
4161
  toBeAssignableWith;
4162
+ toBeCallableWith;
4095
4163
  toHaveProperty;
4096
4164
  toRaiseError;
4097
4165
  constructor(compiler, typeChecker, resolvedConfig) {
@@ -4108,6 +4176,7 @@ class ExpectService {
4108
4176
  this.toBeApplicable = new ToBeApplicable(compiler);
4109
4177
  this.toBeAssignableTo = new ToBeAssignableTo();
4110
4178
  this.toBeAssignableWith = new ToBeAssignableWith();
4179
+ this.toBeCallableWith = new ToBeCallableWith(compiler);
4111
4180
  this.toHaveProperty = new ToHaveProperty(compiler);
4112
4181
  this.toRaiseError = new ToRaiseError(compiler);
4113
4182
  }
@@ -4134,14 +4203,15 @@ class ExpectService {
4134
4203
  return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
4135
4204
  case "toBeApplicable":
4136
4205
  return this.toBeApplicable.match(matchWorker, assertion.source[0], onDiagnostics);
4206
+ case "toBeCallableWith":
4207
+ case "toRaiseError":
4208
+ return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target, onDiagnostics);
4137
4209
  case "toHaveProperty":
4138
4210
  if (!assertion.target?.[0]) {
4139
4211
  this.#onTargetArgumentMustBeProvided("key", assertion, onDiagnostics);
4140
4212
  return;
4141
4213
  }
4142
4214
  return this.toHaveProperty.match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
4143
- case "toRaiseError":
4144
- return this.toRaiseError.match(matchWorker, assertion.source[0], [...assertion.target], onDiagnostics);
4145
4215
  default:
4146
4216
  this.#onMatcherIsNotSupported(matcherNameText, assertion, onDiagnostics);
4147
4217
  }
@@ -4203,7 +4273,7 @@ class TestTreeWalker {
4203
4273
  #position;
4204
4274
  #resolvedConfig;
4205
4275
  #taskResult;
4206
- constructor(resolvedConfig, compiler, typeChecker, options) {
4276
+ constructor(compiler, typeChecker, resolvedConfig, options) {
4207
4277
  this.#resolvedConfig = resolvedConfig;
4208
4278
  this.#cancellationToken = options.cancellationToken;
4209
4279
  this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
@@ -4349,14 +4419,14 @@ class TestTreeWalker {
4349
4419
  }
4350
4420
 
4351
4421
  class TaskRunner {
4352
- #compiler;
4353
4422
  #collectService;
4423
+ #compiler;
4354
4424
  #resolvedConfig;
4355
4425
  #projectService;
4356
- constructor(resolvedConfig, compiler) {
4357
- this.#resolvedConfig = resolvedConfig;
4426
+ constructor(compiler, resolvedConfig) {
4358
4427
  this.#compiler = compiler;
4359
- this.#projectService = new ProjectService(this.#resolvedConfig, compiler);
4428
+ this.#resolvedConfig = resolvedConfig;
4429
+ this.#projectService = new ProjectService(compiler, this.#resolvedConfig);
4360
4430
  this.#collectService = new CollectService(compiler, this.#projectService, this.#resolvedConfig);
4361
4431
  }
4362
4432
  run(task, cancellationToken) {
@@ -4408,7 +4478,7 @@ class TaskRunner {
4408
4478
  return;
4409
4479
  }
4410
4480
  const typeChecker = program.getTypeChecker();
4411
- const testTreeWalker = new TestTreeWalker(this.#resolvedConfig, this.#compiler, typeChecker, {
4481
+ const testTreeWalker = new TestTreeWalker(this.#compiler, typeChecker, this.#resolvedConfig, {
4412
4482
  cancellationToken,
4413
4483
  taskResult,
4414
4484
  hasOnly: testTree.hasOnly,
@@ -4421,7 +4491,7 @@ class TaskRunner {
4421
4491
  class Runner {
4422
4492
  #eventEmitter = new EventEmitter();
4423
4493
  #resolvedConfig;
4424
- static version = "4.0.0-beta.1";
4494
+ static version = "4.0.0-beta.3";
4425
4495
  constructor(resolvedConfig) {
4426
4496
  this.#resolvedConfig = resolvedConfig;
4427
4497
  }
@@ -4479,7 +4549,7 @@ class Runner {
4479
4549
  EventEmitter.dispatch(["target:start", { result: targetResult }]);
4480
4550
  const compiler = await Store.load(target);
4481
4551
  if (compiler) {
4482
- const taskRunner = new TaskRunner(this.#resolvedConfig, compiler);
4552
+ const taskRunner = new TaskRunner(compiler, this.#resolvedConfig);
4483
4553
  for (const task of tasks) {
4484
4554
  taskRunner.run(task, cancellationToken);
4485
4555
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tstyche",
3
- "version": "4.0.0-beta.1",
3
+ "version": "4.0.0-beta.3",
4
4
  "description": "The Essential Type Testing Tool.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -28,6 +28,8 @@
28
28
  "./package.json": "./package.json",
29
29
  "./tstyche": "./build/tstyche.js"
30
30
  },
31
+ "main": "./build/index.js",
32
+ "types": "./build/index.d.ts",
31
33
  "bin": "./build/bin.js",
32
34
  "files": [
33
35
  "build/*"
@@ -80,7 +82,7 @@
80
82
  "optional": true
81
83
  }
82
84
  },
83
- "packageManager": "yarn@4.8.1",
85
+ "packageManager": "yarn@4.9.0",
84
86
  "engines": {
85
87
  "node": ">=20.9"
86
88
  }