tstyche 4.0.0-beta.0 → 4.0.0-beta.2

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
 
@@ -57,9 +61,11 @@ test("handles numbers", () => {
57
61
  Here is the list of all matchers:
58
62
 
59
63
  - `.toBe()`, `.toBeAssignableTo()`, `.toBeAssignableWith()` compare types or types of expression,
60
- - `.toAcceptProps()` checks JSX component props type,
64
+ - `.toAcceptProps()` checks the type of JSX component props,
65
+ - `.toBeApplicable` ensures that the decorator function can be applied,
66
+ - `.toBeCallableWith()` checks whether a function can be called with the given arguments,
61
67
  - `.toHaveProperty()` looks up keys on an object type,
62
- - `.toRaiseError()` captures the type error message or code.
68
+ - `.toRaiseError()` captures the message or code of a type error.
63
69
 
64
70
  ## Runner
65
71
 
package/build/index.d.cts CHANGED
@@ -97,6 +97,10 @@ interface Matchers {
97
97
  */
98
98
  (target: unknown): void;
99
99
  };
100
+ /**
101
+ * Checks if the decorator function can be applied.
102
+ */
103
+ toBeApplicable: (target: unknown, context: DecoratorContext) => void;
100
104
  /**
101
105
  * Checks if the source type is assignable to the target type.
102
106
  */
@@ -123,6 +127,10 @@ interface Matchers {
123
127
  */
124
128
  (target: unknown): void;
125
129
  };
130
+ /**
131
+ * Checks if the source type can be called with the target arguments.
132
+ */
133
+ toBeCallableWith: (...target: Array<unknown>) => void;
126
134
  /**
127
135
  * Checks if a property key exists on the source type.
128
136
  */
package/build/index.d.ts CHANGED
@@ -97,6 +97,10 @@ interface Matchers {
97
97
  */
98
98
  (target: unknown): void;
99
99
  };
100
+ /**
101
+ * Checks if the decorator function can be applied.
102
+ */
103
+ toBeApplicable: (target: unknown, context: DecoratorContext) => void;
100
104
  /**
101
105
  * Checks if the source type is assignable to the target type.
102
106
  */
@@ -123,6 +127,10 @@ interface Matchers {
123
127
  */
124
128
  (target: unknown): void;
125
129
  };
130
+ /**
131
+ * Checks if the source type can be called with the target arguments.
132
+ */
133
+ toBeCallableWith: (...target: Array<unknown>) => void;
126
134
  /**
127
135
  * Checks if a property key exists on the source type.
128
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 {
@@ -102,24 +103,16 @@ declare class TestTree {
102
103
  constructor(diagnostics: Set<ts.Diagnostic>, sourceFile: ts.SourceFile);
103
104
  }
104
105
 
105
- interface MatcherNode extends ts.CallExpression {
106
- expression: ts.PropertyAccessExpression;
107
- }
108
106
  declare class AssertionNode extends TestTreeNode {
107
+ abilityDiagnostics: Set<ts.Diagnostic> | undefined;
109
108
  isNot: boolean;
110
- matcherName: ts.MemberName;
111
- matcherNode: MatcherNode;
109
+ matcherNode: ts.CallExpression | ts.Decorator;
110
+ matcherNameNode: ts.PropertyAccessExpression;
112
111
  modifierNode: ts.PropertyAccessExpression;
113
112
  notNode: ts.PropertyAccessExpression | undefined;
114
113
  source: ts.NodeArray<ts.Expression> | ts.NodeArray<ts.TypeNode>;
115
- target: ts.NodeArray<ts.Expression> | ts.NodeArray<ts.TypeNode>;
116
- constructor(compiler: typeof ts, brand: TestTreeNodeBrand, node: ts.CallExpression, parent: TestTree | TestTreeNode, flags: TestTreeNodeFlags, matcherNode: MatcherNode, modifierNode: ts.PropertyAccessExpression, notNode?: ts.PropertyAccessExpression);
117
- }
118
-
119
- declare class CollectService {
120
- #private;
121
- constructor(compiler: typeof ts);
122
- createTestTree(sourceFile: ts.SourceFile, semanticDiagnostics?: Array<ts.Diagnostic>): TestTree;
114
+ target: ts.NodeArray<ts.Expression> | ts.NodeArray<ts.TypeNode> | undefined;
115
+ constructor(compiler: typeof ts, brand: TestTreeNodeBrand, node: ts.CallExpression, parent: TestTree | TestTreeNode, flags: TestTreeNodeFlags, matcherNode: ts.CallExpression | ts.Decorator, matcherNameNode: ts.PropertyAccessExpression, modifierNode: ts.PropertyAccessExpression, notNode?: ts.PropertyAccessExpression);
123
116
  }
124
117
 
125
118
  declare enum OptionBrand {
@@ -207,7 +200,7 @@ interface CommandLineOptions {
207
200
  */
208
201
  failFast?: boolean;
209
202
  /**
210
- * Fetch specified versions of the 'typescript' package and exit.
203
+ * Fetch the specified versions of the 'typescript' package and exit.
211
204
  */
212
205
  fetch?: boolean;
213
206
  /**
@@ -330,6 +323,21 @@ declare class Options {
330
323
 
331
324
  declare const defaultOptions: Required<ConfigFileOptions>;
332
325
 
326
+ declare class ProjectService {
327
+ #private;
328
+ constructor(compiler: typeof ts, resolvedConfig: ResolvedConfig);
329
+ closeFile(filePath: string): void;
330
+ getDefaultProject(filePath: string): ts.server.Project | undefined;
331
+ getLanguageService(filePath: string): ts.LanguageService | undefined;
332
+ openFile(filePath: string, sourceText?: string | undefined, projectRootPath?: string | undefined): void;
333
+ }
334
+
335
+ declare class CollectService {
336
+ #private;
337
+ constructor(compiler: typeof ts, projectService: ProjectService, resolvedConfig: ResolvedConfig);
338
+ createTestTree(sourceFile: ts.SourceFile, semanticDiagnostics?: Array<ts.Diagnostic>): TestTree;
339
+ }
340
+
333
341
  interface EnvironmentOptions {
334
342
  /**
335
343
  * Is `true` if the process is running in continuous integration environment.
@@ -592,8 +600,10 @@ declare class ExpectService {
592
600
  #private;
593
601
  private toAcceptProps;
594
602
  private toBe;
603
+ private toBeApplicable;
595
604
  private toBeAssignableTo;
596
605
  private toBeAssignableWith;
606
+ private toBeCallableWith;
597
607
  private toHaveProperty;
598
608
  private toRaiseError;
599
609
  constructor(compiler: typeof ts, typeChecker: TypeChecker, resolvedConfig: ResolvedConfig);
@@ -767,15 +777,6 @@ declare class PluginService {
767
777
  static removeHandlers(): void;
768
778
  }
769
779
 
770
- declare class ProjectService {
771
- #private;
772
- constructor(resolvedConfig: ResolvedConfig, compiler: typeof ts);
773
- closeFile(filePath: string): void;
774
- getDefaultProject(filePath: string): ts.server.Project | undefined;
775
- getLanguageService(filePath: string): ts.LanguageService | undefined;
776
- openFile(filePath: string, sourceText?: string | undefined, projectRootPath?: string | undefined): void;
777
- }
778
-
779
780
  declare class Runner {
780
781
  #private;
781
782
  static version: string;
package/build/tstyche.js CHANGED
@@ -93,12 +93,15 @@ class DiagnosticOrigin {
93
93
  this.assertion = assertion;
94
94
  }
95
95
  static fromAssertion(assertion) {
96
- const node = assertion.matcherName;
96
+ const node = assertion.matcherNameNode.name;
97
97
  return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertion);
98
98
  }
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 {
@@ -903,7 +906,7 @@ class Options {
903
906
  },
904
907
  {
905
908
  brand: OptionBrand.BareTrue,
906
- description: "Fetch specified versions of the 'typescript' package and exit.",
909
+ description: "Fetch the specified versions of the 'typescript' package and exit.",
907
910
  group: OptionGroup.CommandLine,
908
911
  name: "fetch",
909
912
  },
@@ -1948,17 +1951,17 @@ class TestTreeNode {
1948
1951
  };
1949
1952
  switch (this.brand) {
1950
1953
  case TestTreeNodeBrand.Describe:
1951
- for (const member of this.children) {
1952
- if (member.brand === TestTreeNodeBrand.Expect) {
1953
- diagnostics.push(Diagnostic.error(getText(member.node), DiagnosticOrigin.fromNode(getParentCallExpression(member.node))));
1954
+ for (const child of this.children) {
1955
+ if (child.brand === TestTreeNodeBrand.Expect) {
1956
+ diagnostics.push(Diagnostic.error(getText(child.node), DiagnosticOrigin.fromNode(getParentCallExpression(child.node))));
1954
1957
  }
1955
1958
  }
1956
1959
  break;
1957
1960
  case TestTreeNodeBrand.Test:
1958
1961
  case TestTreeNodeBrand.Expect:
1959
- for (const member of this.children) {
1960
- if (member.brand !== TestTreeNodeBrand.Expect) {
1961
- diagnostics.push(Diagnostic.error(getText(member.node), DiagnosticOrigin.fromNode(member.node)));
1962
+ for (const child of this.children) {
1963
+ if (child.brand !== TestTreeNodeBrand.Expect) {
1964
+ diagnostics.push(Diagnostic.error(getText(child.node), DiagnosticOrigin.fromNode(child.node)));
1962
1965
  }
1963
1966
  }
1964
1967
  break;
@@ -1968,21 +1971,24 @@ class TestTreeNode {
1968
1971
  }
1969
1972
 
1970
1973
  class AssertionNode extends TestTreeNode {
1974
+ abilityDiagnostics;
1971
1975
  isNot;
1972
- matcherName;
1973
1976
  matcherNode;
1977
+ matcherNameNode;
1974
1978
  modifierNode;
1975
1979
  notNode;
1976
1980
  source;
1977
1981
  target;
1978
- constructor(compiler, brand, node, parent, flags, matcherNode, modifierNode, notNode) {
1982
+ constructor(compiler, brand, node, parent, flags, matcherNode, matcherNameNode, modifierNode, notNode) {
1979
1983
  super(compiler, brand, node, parent, flags);
1980
1984
  this.isNot = notNode != null;
1981
- this.matcherName = matcherNode.expression.name;
1982
1985
  this.matcherNode = matcherNode;
1986
+ this.matcherNameNode = matcherNameNode;
1983
1987
  this.modifierNode = modifierNode;
1984
1988
  this.source = this.node.typeArguments ?? this.node.arguments;
1985
- this.target = this.matcherNode.typeArguments ?? this.matcherNode.arguments;
1989
+ if (compiler.isCallExpression(this.matcherNode)) {
1990
+ this.target = this.matcherNode.typeArguments ?? this.matcherNode.arguments;
1991
+ }
1986
1992
  for (const diagnostic of parent.diagnostics) {
1987
1993
  if (diagnostic.start != null && diagnostic.start >= this.source.pos && diagnostic.start <= this.source.end) {
1988
1994
  this.diagnostics.add(diagnostic);
@@ -1992,6 +1998,97 @@ class AssertionNode extends TestTreeNode {
1992
1998
  }
1993
1999
  }
1994
2000
 
2001
+ class AbilityLayer {
2002
+ #filePath = "";
2003
+ #nodes = [];
2004
+ #projectService;
2005
+ #resolvedConfig;
2006
+ #text = "";
2007
+ constructor(projectService, resolvedConfig) {
2008
+ this.#projectService = projectService;
2009
+ this.#resolvedConfig = resolvedConfig;
2010
+ }
2011
+ #getErasedRangeText(range) {
2012
+ if (this.#text.indexOf("\n", range.start) >= range.end) {
2013
+ return " ".repeat(range.end - range.start);
2014
+ }
2015
+ const text = [];
2016
+ for (let index = range.start; index < range.end; index++) {
2017
+ const character = this.#text.charAt(index);
2018
+ switch (character) {
2019
+ case "\n":
2020
+ case "\r":
2021
+ text.push(character);
2022
+ break;
2023
+ default:
2024
+ text.push(" ");
2025
+ }
2026
+ }
2027
+ return text.join("");
2028
+ }
2029
+ #addRanges(node, ranges) {
2030
+ this.#nodes.push(node);
2031
+ for (const range of ranges) {
2032
+ const rangeText = this.#getErasedRangeText(range);
2033
+ this.#text = `${this.#text.slice(0, range.start)}${rangeText}${this.#text.slice(range.end)}`;
2034
+ }
2035
+ }
2036
+ close() {
2037
+ if (this.#nodes.length > 0) {
2038
+ this.#projectService.openFile(this.#filePath, this.#text, this.#resolvedConfig.rootPath);
2039
+ const languageService = this.#projectService.getLanguageService(this.#filePath);
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();
2049
+ }
2050
+ node.abilityDiagnostics.add(diagnostic);
2051
+ diagnostics.delete(diagnostic);
2052
+ }
2053
+ }
2054
+ }
2055
+ }
2056
+ this.#filePath = "";
2057
+ this.#nodes = [];
2058
+ this.#text = "";
2059
+ }
2060
+ handleNode(assertionNode) {
2061
+ switch (assertionNode.matcherNameNode.name.text) {
2062
+ case "toBeApplicable": {
2063
+ const expectStart = assertionNode.node.pos;
2064
+ const expectExpressionEnd = assertionNode.node.expression.end;
2065
+ const expectEnd = assertionNode.node.end;
2066
+ const matcherNameEnd = assertionNode.matcherNameNode.end;
2067
+ this.#addRanges(assertionNode, [
2068
+ { end: expectExpressionEnd + 1, start: expectStart },
2069
+ { end: matcherNameEnd, start: expectEnd - 1 },
2070
+ ]);
2071
+ break;
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
+ }
2084
+ }
2085
+ }
2086
+ open(sourceFile) {
2087
+ this.#filePath = sourceFile.fileName;
2088
+ this.#text = sourceFile.text;
2089
+ }
2090
+ }
2091
+
1995
2092
  var TestTreeNodeFlags;
1996
2093
  (function (TestTreeNodeFlags) {
1997
2094
  TestTreeNodeFlags[TestTreeNodeFlags["None"] = 0] = "None";
@@ -2103,9 +2200,15 @@ class TestTree {
2103
2200
  }
2104
2201
 
2105
2202
  class CollectService {
2203
+ #abilityLayer;
2106
2204
  #compiler;
2107
- constructor(compiler) {
2205
+ #projectService;
2206
+ #resolvedConfig;
2207
+ constructor(compiler, projectService, resolvedConfig) {
2108
2208
  this.#compiler = compiler;
2209
+ this.#projectService = projectService;
2210
+ this.#resolvedConfig = resolvedConfig;
2211
+ this.#abilityLayer = new AbilityLayer(this.#projectService, this.#resolvedConfig);
2109
2212
  }
2110
2213
  #collectTestTreeNodes(node, identifiers, parent) {
2111
2214
  if (this.#compiler.isCallExpression(node)) {
@@ -2125,12 +2228,17 @@ class CollectService {
2125
2228
  return;
2126
2229
  }
2127
2230
  const notNode = this.#getChainedNode(modifierNode, "not");
2128
- const matcherNode = this.#getChainedNode(notNode ?? modifierNode)?.parent;
2129
- if (!matcherNode || !this.#isMatcherNode(matcherNode)) {
2231
+ const matcherNameNode = this.#getChainedNode(notNode ?? modifierNode);
2232
+ if (!matcherNameNode) {
2233
+ return;
2234
+ }
2235
+ const matcherNode = this.#getMatcherNode(matcherNameNode);
2236
+ if (!matcherNode) {
2130
2237
  return;
2131
2238
  }
2132
- const assertionNode = new AssertionNode(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, modifierNode, notNode);
2239
+ const assertionNode = new AssertionNode(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, matcherNameNode, modifierNode, notNode);
2133
2240
  parent.children.push(assertionNode);
2241
+ this.#abilityLayer.handleNode(assertionNode);
2134
2242
  EventEmitter.dispatch(["collect:node", { testNode: assertionNode }]);
2135
2243
  this.#compiler.forEachChild(node, (node) => {
2136
2244
  this.#collectTestTreeNodes(node, identifiers, assertionNode);
@@ -2149,7 +2257,9 @@ class CollectService {
2149
2257
  createTestTree(sourceFile, semanticDiagnostics = []) {
2150
2258
  const testTree = new TestTree(new Set(semanticDiagnostics), sourceFile);
2151
2259
  EventEmitter.dispatch(["collect:start", { testTree }]);
2260
+ this.#abilityLayer.open(sourceFile);
2152
2261
  this.#collectTestTreeNodes(sourceFile, new IdentifierLookup(this.#compiler), testTree);
2262
+ this.#abilityLayer.close();
2153
2263
  EventEmitter.dispatch(["collect:end", { testTree }]);
2154
2264
  return testTree;
2155
2265
  }
@@ -2162,8 +2272,17 @@ class CollectService {
2162
2272
  }
2163
2273
  return parent;
2164
2274
  }
2165
- #isMatcherNode(node) {
2166
- return this.#compiler.isCallExpression(node) && this.#compiler.isPropertyAccessExpression(node.expression);
2275
+ #getMatcherNode(node) {
2276
+ if (this.#compiler.isCallExpression(node.parent)) {
2277
+ return node.parent;
2278
+ }
2279
+ if (this.#compiler.isDecorator(node.parent)) {
2280
+ return node.parent;
2281
+ }
2282
+ if (this.#compiler.isParenthesizedExpression(node.parent)) {
2283
+ return this.#getMatcherNode(node.parent);
2284
+ }
2285
+ return;
2167
2286
  }
2168
2287
  }
2169
2288
 
@@ -3179,9 +3298,9 @@ class ProjectService {
3179
3298
  #resolvedConfig;
3180
3299
  #seenPrograms = new WeakSet();
3181
3300
  #service;
3182
- constructor(resolvedConfig, compiler) {
3183
- this.#resolvedConfig = resolvedConfig;
3301
+ constructor(compiler, resolvedConfig) {
3184
3302
  this.#compiler = compiler;
3303
+ this.#resolvedConfig = resolvedConfig;
3185
3304
  const noop = () => undefined;
3186
3305
  const noopLogger = {
3187
3306
  close: noop,
@@ -3329,12 +3448,30 @@ class ExpectDiagnosticText {
3329
3448
  static argumentMustBeProvided(argumentNameText) {
3330
3449
  return `An argument for '${argumentNameText}' must be provided.`;
3331
3450
  }
3332
- 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) {
3333
3458
  return `${isTypeNode ? "Component type" : "Component"} accepts props of the given type.`;
3334
3459
  }
3335
- static componentDoesNotAcceptProps(isTypeNode) {
3460
+ static doesNotAcceptProps(isTypeNode) {
3336
3461
  return `${isTypeNode ? "Component type" : "Component"} does not accept props of the given type.`;
3337
3462
  }
3463
+ static canBeApplied(targetText) {
3464
+ return `The decorator function can be applied${targetText}.`;
3465
+ }
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}'.`;
3474
+ }
3338
3475
  static matcherIsNotSupported(matcherNameText) {
3339
3476
  return `The '.${matcherNameText}()' matcher is not supported.`;
3340
3477
  }
@@ -3350,50 +3487,44 @@ class ExpectDiagnosticText {
3350
3487
  static typeArgumentMustBe(argumentNameText, expectedText) {
3351
3488
  return `A type argument for '${argumentNameText}' must be ${expectedText}.`;
3352
3489
  }
3353
- static typeDidNotRaiseError(isTypeNode) {
3354
- 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"}.`;
3355
3496
  }
3356
- static typeDidNotRaiseMatchingError(isTypeNode) {
3357
- 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.`;
3358
3499
  }
3359
- static typeDoesNotHaveProperty(typeText, propertyNameText) {
3360
- return `Type '${typeText}' does not have property '${propertyNameText}'.`;
3500
+ static raisedMatchingError(isTypeNode) {
3501
+ return `${isTypeNode ? "Type" : "Expression type"} raised a matching type error.`;
3361
3502
  }
3362
- static typeHasProperty(typeText, propertyNameText) {
3363
- return `Type '${typeText}' has property '${propertyNameText}'.`;
3503
+ static didNotRaiseMatchingError(isTypeNode) {
3504
+ return `${isTypeNode ? "Type" : "Expression type"} did not raise a matching type error.`;
3364
3505
  }
3365
- static typeIsAssignableTo(sourceTypeText, targetTypeText) {
3506
+ static isAssignableTo(sourceTypeText, targetTypeText) {
3366
3507
  return `Type '${sourceTypeText}' is assignable to type '${targetTypeText}'.`;
3367
3508
  }
3368
- static typeIsAssignableWith(sourceTypeText, targetTypeText) {
3369
- return `Type '${sourceTypeText}' is assignable with type '${targetTypeText}'.`;
3370
- }
3371
- static typeIsIdenticalTo(sourceTypeText, targetTypeText) {
3372
- return `Type '${sourceTypeText}' is identical to type '${targetTypeText}'.`;
3373
- }
3374
- static typeIsNotAssignableTo(sourceTypeText, targetTypeText) {
3509
+ static isNotAssignableTo(sourceTypeText, targetTypeText) {
3375
3510
  return `Type '${sourceTypeText}' is not assignable to type '${targetTypeText}'.`;
3376
3511
  }
3377
- static typeIsNotAssignableWith(sourceTypeText, targetTypeText) {
3512
+ static isAssignableWith(sourceTypeText, targetTypeText) {
3513
+ return `Type '${sourceTypeText}' is assignable with type '${targetTypeText}'.`;
3514
+ }
3515
+ static isNotAssignableWith(sourceTypeText, targetTypeText) {
3378
3516
  return `Type '${sourceTypeText}' is not assignable with type '${targetTypeText}'.`;
3379
3517
  }
3380
- static typeIsNotCompatibleWith(sourceTypeText, targetTypeText) {
3381
- return `Type '${sourceTypeText}' is not compatible with type '${targetTypeText}'.`;
3518
+ static isIdenticalTo(sourceTypeText, targetTypeText) {
3519
+ return `Type '${sourceTypeText}' is identical to type '${targetTypeText}'.`;
3382
3520
  }
3383
- static typeIsNotIdenticalTo(sourceTypeText, targetTypeText) {
3521
+ static isNotIdenticalTo(sourceTypeText, targetTypeText) {
3384
3522
  return `Type '${sourceTypeText}' is not identical to type '${targetTypeText}'.`;
3385
3523
  }
3386
- static typeRaisedError(isTypeNode, count, targetCount) {
3387
- let countText = "a";
3388
- if (count > 1 || targetCount > 1) {
3389
- countText = count > targetCount ? `${count}` : `only ${count}`;
3390
- }
3391
- return `${isTypeNode ? "Type" : "Expression type"} raised ${countText} type error${count === 1 ? "" : "s"}.`;
3392
- }
3393
- static typeRaisedMatchingError(isTypeNode) {
3394
- 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}'.`;
3395
3526
  }
3396
- static typeRequiresProperty(typeText, propertyNameText) {
3527
+ static requiresProperty(typeText, propertyNameText) {
3397
3528
  return `Type '${typeText}' requires property '${propertyNameText}'.`;
3398
3529
  }
3399
3530
  static typesOfPropertyAreNotCompatible(propertyNameText) {
@@ -3412,7 +3543,6 @@ class MatchWorker {
3412
3543
  assertion;
3413
3544
  #compiler;
3414
3545
  #signatureCache = new Map();
3415
- #typeCache = new Map();
3416
3546
  #typeChecker;
3417
3547
  constructor(compiler, typeChecker, assertion) {
3418
3548
  this.#compiler = compiler;
@@ -3464,7 +3594,7 @@ class MatchWorker {
3464
3594
  if (!parameter) {
3465
3595
  return;
3466
3596
  }
3467
- return this.#getTypeOfNode(parameter);
3597
+ return this.getType(parameter);
3468
3598
  }
3469
3599
  getSignatures(node) {
3470
3600
  let signatures = this.#signatureCache.get(node);
@@ -3482,21 +3612,7 @@ class MatchWorker {
3482
3612
  return this.#typeChecker.typeToString(type);
3483
3613
  }
3484
3614
  getType(node) {
3485
- return this.#compiler.isTypeNode(node) ? this.#getTypeOfTypeNode(node) : this.#getTypeOfNode(node);
3486
- }
3487
- #getTypeOfNode(node) {
3488
- let type = this.#typeCache.get(node);
3489
- if (!type) {
3490
- type = this.#typeChecker.getTypeAtLocation(node);
3491
- }
3492
- return type;
3493
- }
3494
- #getTypeOfTypeNode(node) {
3495
- let type = this.#typeCache.get(node);
3496
- if (!type) {
3497
- type = this.#typeChecker.getTypeFromTypeNode(node);
3498
- }
3499
- return type;
3615
+ return this.#typeChecker.getTypeAtLocation(node);
3500
3616
  }
3501
3617
  isStringOrNumberLiteralType(type) {
3502
3618
  return !!(type.flags & this.#compiler.TypeFlags.StringOrNumberLiteral);
@@ -3544,8 +3660,8 @@ class ToAcceptProps {
3544
3660
  return signatures.reduce((accumulator, signature, index) => {
3545
3661
  let diagnostic;
3546
3662
  const introText = matchWorker.assertion.isNot
3547
- ? ExpectDiagnosticText.componentAcceptsProps(this.#compiler.isTypeNode(sourceNode))
3548
- : ExpectDiagnosticText.componentDoesNotAcceptProps(this.#compiler.isTypeNode(sourceNode));
3663
+ ? ExpectDiagnosticText.acceptsProps(this.#compiler.isTypeNode(sourceNode))
3664
+ : ExpectDiagnosticText.doesNotAcceptProps(this.#compiler.isTypeNode(sourceNode));
3549
3665
  const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
3550
3666
  if (signatures.length > 1) {
3551
3667
  const signatureText = this.#typeChecker.signatureToString(signature, sourceNode);
@@ -3611,8 +3727,8 @@ class ToAcceptProps {
3611
3727
  const sourceProperty = sourceType?.getProperty(targetPropertyName);
3612
3728
  if (!sourceProperty) {
3613
3729
  const text = [
3614
- ExpectDiagnosticText.typeIsNotCompatibleWith(sourceTypeText, targetTypeText),
3615
- ExpectDiagnosticText.typeDoesNotHaveProperty(sourceTypeText, targetPropertyName),
3730
+ ExpectDiagnosticText.isNotCompatibleWith(sourceTypeText, targetTypeText),
3731
+ ExpectDiagnosticText.doesNotHaveProperty(sourceTypeText, targetPropertyName),
3616
3732
  ];
3617
3733
  const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
3618
3734
  diagnostics.push(diagnostic.extendWith(text, origin));
@@ -3620,8 +3736,8 @@ class ToAcceptProps {
3620
3736
  }
3621
3737
  if (this.#isOptionalProperty(targetProperty) && !this.#isOptionalProperty(sourceProperty)) {
3622
3738
  const text = [
3623
- ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText),
3624
- ExpectDiagnosticText.typeRequiresProperty(sourceTypeText, targetPropertyName),
3739
+ ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
3740
+ ExpectDiagnosticText.requiresProperty(sourceTypeText, targetPropertyName),
3625
3741
  ];
3626
3742
  const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
3627
3743
  diagnostics.push(diagnostic.extendWith(text, origin));
@@ -3633,9 +3749,9 @@ class ToAcceptProps {
3633
3749
  const targetPropertyTypeText = this.#typeChecker.typeToString(targetPropertyType);
3634
3750
  const sourcePropertyTypeText = this.#typeChecker.typeToString(sourcePropertyType);
3635
3751
  const text = [
3636
- ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText),
3752
+ ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
3637
3753
  ExpectDiagnosticText.typesOfPropertyAreNotCompatible(targetPropertyName),
3638
- ExpectDiagnosticText.typeIsNotAssignableWith(sourcePropertyTypeText, targetPropertyTypeText),
3754
+ ExpectDiagnosticText.isNotAssignableWith(sourcePropertyTypeText, targetPropertyTypeText),
3639
3755
  ];
3640
3756
  const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
3641
3757
  diagnostics.push(diagnostic.extendWith(text, origin));
@@ -3647,15 +3763,15 @@ class ToAcceptProps {
3647
3763
  const targetProperty = targetType.getProperty(sourcePropertyName);
3648
3764
  if (!targetProperty && !this.#isOptionalProperty(sourceProperty)) {
3649
3765
  const text = [
3650
- ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText),
3651
- ExpectDiagnosticText.typeRequiresProperty(sourceTypeText, sourcePropertyName),
3766
+ ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
3767
+ ExpectDiagnosticText.requiresProperty(sourceTypeText, sourcePropertyName),
3652
3768
  ];
3653
3769
  diagnostics.push(diagnostic.extendWith(text));
3654
3770
  }
3655
3771
  }
3656
3772
  }
3657
3773
  if (diagnostics.length === 0) {
3658
- const text = ExpectDiagnosticText.typeIsAssignableWith(sourceTypeText, targetTypeText);
3774
+ const text = ExpectDiagnosticText.isAssignableWith(sourceTypeText, targetTypeText);
3659
3775
  diagnostics.push(diagnostic.extendWith(text));
3660
3776
  return { diagnostics, isMatch: true };
3661
3777
  }
@@ -3665,8 +3781,8 @@ class ToAcceptProps {
3665
3781
  let accumulator = [];
3666
3782
  const isMatch = sourceType.types.some((sourceType) => {
3667
3783
  const text = matchWorker.assertion.isNot
3668
- ? ExpectDiagnosticText.typeIsAssignableWith(sourceTypeText, targetTypeText)
3669
- : ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText);
3784
+ ? ExpectDiagnosticText.isAssignableWith(sourceTypeText, targetTypeText)
3785
+ : ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText);
3670
3786
  const { diagnostics, isMatch } = explain(sourceType, targetType, diagnostic.extendWith(text));
3671
3787
  if (isMatch) {
3672
3788
  accumulator = diagnostics;
@@ -3728,8 +3844,8 @@ class RelationMatcherBase {
3728
3844
  }
3729
3845
 
3730
3846
  class ToBe extends RelationMatcherBase {
3731
- explainText = ExpectDiagnosticText.typeIsIdenticalTo;
3732
- explainNotText = ExpectDiagnosticText.typeIsNotIdenticalTo;
3847
+ explainText = ExpectDiagnosticText.isIdenticalTo;
3848
+ explainNotText = ExpectDiagnosticText.isNotIdenticalTo;
3733
3849
  match(matchWorker, sourceNode, targetNode) {
3734
3850
  return {
3735
3851
  explain: () => this.explain(matchWorker, sourceNode, targetNode),
@@ -3738,9 +3854,79 @@ class ToBe extends RelationMatcherBase {
3738
3854
  }
3739
3855
  }
3740
3856
 
3857
+ class ToBeApplicable {
3858
+ #compiler;
3859
+ constructor(compiler) {
3860
+ this.#compiler = compiler;
3861
+ }
3862
+ #resolveTargetText(node) {
3863
+ let text = "";
3864
+ switch (node.kind) {
3865
+ case this.#compiler.SyntaxKind.ClassDeclaration:
3866
+ text = "class";
3867
+ break;
3868
+ case this.#compiler.SyntaxKind.MethodDeclaration:
3869
+ text = "method";
3870
+ break;
3871
+ case this.#compiler.SyntaxKind.PropertyDeclaration:
3872
+ text = node.modifiers?.some((modifier) => modifier.kind === this.#compiler.SyntaxKind.AccessorKeyword)
3873
+ ? "accessor"
3874
+ : "field";
3875
+ break;
3876
+ case this.#compiler.SyntaxKind.GetAccessor:
3877
+ text = "getter";
3878
+ break;
3879
+ case this.#compiler.SyntaxKind.SetAccessor:
3880
+ text = "setter";
3881
+ break;
3882
+ }
3883
+ if (text.length > 0) {
3884
+ text = ` to this ${text}`;
3885
+ }
3886
+ return text;
3887
+ }
3888
+ #explain(matchWorker, sourceNode) {
3889
+ const targetText = this.#resolveTargetText(matchWorker.assertion.matcherNode.parent);
3890
+ const diagnostics = [];
3891
+ if (matchWorker.assertion.abilityDiagnostics) {
3892
+ for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
3893
+ const text = [
3894
+ ExpectDiagnosticText.cannotBeApplied(targetText),
3895
+ typeof diagnostic.messageText === "string"
3896
+ ? diagnostic.messageText
3897
+ : Diagnostic.toMessageText(diagnostic.messageText),
3898
+ ];
3899
+ const origin = DiagnosticOrigin.fromNode(sourceNode);
3900
+ diagnostics.push(Diagnostic.error(text.flat(), origin));
3901
+ }
3902
+ }
3903
+ else {
3904
+ const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
3905
+ diagnostics.push(Diagnostic.error(ExpectDiagnosticText.canBeApplied(targetText), origin));
3906
+ }
3907
+ return diagnostics;
3908
+ }
3909
+ match(matchWorker, sourceNode, onDiagnostics) {
3910
+ const type = matchWorker.getType(sourceNode);
3911
+ if (type.getCallSignatures().length === 0) {
3912
+ const expectedText = "of a function type";
3913
+ const text = this.#compiler.isTypeNode(sourceNode)
3914
+ ? ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText)
3915
+ : ExpectDiagnosticText.argumentMustBe("source", expectedText);
3916
+ const origin = DiagnosticOrigin.fromNode(sourceNode);
3917
+ onDiagnostics([Diagnostic.error(text, origin)]);
3918
+ return;
3919
+ }
3920
+ return {
3921
+ explain: () => this.#explain(matchWorker, sourceNode),
3922
+ isMatch: !matchWorker.assertion.abilityDiagnostics,
3923
+ };
3924
+ }
3925
+ }
3926
+
3741
3927
  class ToBeAssignableTo extends RelationMatcherBase {
3742
- explainText = ExpectDiagnosticText.typeIsAssignableTo;
3743
- explainNotText = ExpectDiagnosticText.typeIsNotAssignableTo;
3928
+ explainText = ExpectDiagnosticText.isAssignableTo;
3929
+ explainNotText = ExpectDiagnosticText.isNotAssignableTo;
3744
3930
  match(matchWorker, sourceNode, targetNode) {
3745
3931
  return {
3746
3932
  explain: () => this.explain(matchWorker, sourceNode, targetNode),
@@ -3750,8 +3936,8 @@ class ToBeAssignableTo extends RelationMatcherBase {
3750
3936
  }
3751
3937
 
3752
3938
  class ToBeAssignableWith extends RelationMatcherBase {
3753
- explainText = ExpectDiagnosticText.typeIsAssignableWith;
3754
- explainNotText = ExpectDiagnosticText.typeIsNotAssignableWith;
3939
+ explainText = ExpectDiagnosticText.isAssignableWith;
3940
+ explainNotText = ExpectDiagnosticText.isNotAssignableWith;
3755
3941
  match(matchWorker, sourceNode, targetNode) {
3756
3942
  return {
3757
3943
  explain: () => this.explain(matchWorker, sourceNode, targetNode),
@@ -3760,6 +3946,84 @@ class ToBeAssignableWith extends RelationMatcherBase {
3760
3946
  }
3761
3947
  }
3762
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
+ let mustBeText;
4005
+ if (!(sourceNode.kind === this.#compiler.SyntaxKind.Identifier ||
4006
+ sourceNode.kind === this.#compiler.SyntaxKind.ExpressionWithTypeArguments)) {
4007
+ mustBeText = "an identifier or instantiation expression";
4008
+ }
4009
+ if (matchWorker.getType(sourceNode).getCallSignatures().length === 0) {
4010
+ mustBeText = "of a function type";
4011
+ }
4012
+ if (mustBeText != null) {
4013
+ const text = this.#compiler.isTypeNode(sourceNode)
4014
+ ? ExpectDiagnosticText.typeArgumentMustBe("Source", mustBeText)
4015
+ : ExpectDiagnosticText.argumentMustBe("source", mustBeText);
4016
+ const origin = DiagnosticOrigin.fromNode(sourceNode);
4017
+ onDiagnostics([Diagnostic.error(text, origin)]);
4018
+ return;
4019
+ }
4020
+ return {
4021
+ explain: () => this.#explain(matchWorker, sourceNode, targetNodes),
4022
+ isMatch: !matchWorker.assertion.abilityDiagnostics,
4023
+ };
4024
+ }
4025
+ }
4026
+
3763
4027
  class ToHaveProperty {
3764
4028
  #compiler;
3765
4029
  constructor(compiler) {
@@ -3777,8 +4041,8 @@ class ToHaveProperty {
3777
4041
  }
3778
4042
  const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
3779
4043
  return matchWorker.assertion.isNot
3780
- ? [Diagnostic.error(ExpectDiagnosticText.typeHasProperty(sourceTypeText, propertyNameText), origin)]
3781
- : [Diagnostic.error(ExpectDiagnosticText.typeDoesNotHaveProperty(sourceTypeText, propertyNameText), origin)];
4044
+ ? [Diagnostic.error(ExpectDiagnosticText.hasProperty(sourceTypeText, propertyNameText), origin)]
4045
+ : [Diagnostic.error(ExpectDiagnosticText.doesNotHaveProperty(sourceTypeText, propertyNameText), origin)];
3782
4046
  }
3783
4047
  match(matchWorker, sourceNode, targetNode, onDiagnostics) {
3784
4048
  const diagnostics = [];
@@ -3828,12 +4092,12 @@ class ToRaiseError {
3828
4092
  const isTypeNode = this.#compiler.isTypeNode(sourceNode);
3829
4093
  const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
3830
4094
  if (matchWorker.assertion.diagnostics.size === 0) {
3831
- const text = ExpectDiagnosticText.typeDidNotRaiseError(isTypeNode);
4095
+ const text = ExpectDiagnosticText.didNotRaiseError(isTypeNode);
3832
4096
  return [Diagnostic.error(text, origin)];
3833
4097
  }
3834
4098
  if (matchWorker.assertion.diagnostics.size !== targetNodes.length) {
3835
4099
  const count = matchWorker.assertion.diagnostics.size;
3836
- const text = ExpectDiagnosticText.typeRaisedError(isTypeNode, count, targetNodes.length);
4100
+ const text = ExpectDiagnosticText.raisedError(isTypeNode, count, targetNodes.length);
3837
4101
  const related = [
3838
4102
  Diagnostic.error(ExpectDiagnosticText.raisedTypeError(count)),
3839
4103
  ...Diagnostic.fromDiagnostics([...matchWorker.assertion.diagnostics]),
@@ -3845,8 +4109,8 @@ class ToRaiseError {
3845
4109
  const isMatch = this.#matchExpectedError(diagnostic, targetNode);
3846
4110
  if (matchWorker.assertion.isNot ? isMatch : !isMatch) {
3847
4111
  const text = matchWorker.assertion.isNot
3848
- ? ExpectDiagnosticText.typeRaisedMatchingError(isTypeNode)
3849
- : ExpectDiagnosticText.typeDidNotRaiseMatchingError(isTypeNode);
4112
+ ? ExpectDiagnosticText.raisedMatchingError(isTypeNode)
4113
+ : ExpectDiagnosticText.didNotRaiseMatchingError(isTypeNode);
3850
4114
  const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
3851
4115
  const related = [
3852
4116
  Diagnostic.error(ExpectDiagnosticText.raisedTypeError()),
@@ -3908,8 +4172,10 @@ class ExpectService {
3908
4172
  #typeChecker;
3909
4173
  toAcceptProps;
3910
4174
  toBe;
4175
+ toBeApplicable;
3911
4176
  toBeAssignableTo;
3912
4177
  toBeAssignableWith;
4178
+ toBeCallableWith;
3913
4179
  toHaveProperty;
3914
4180
  toRaiseError;
3915
4181
  constructor(compiler, typeChecker, resolvedConfig) {
@@ -3923,42 +4189,45 @@ class ExpectService {
3923
4189
  }
3924
4190
  this.toAcceptProps = new ToAcceptProps(compiler, typeChecker);
3925
4191
  this.toBe = new ToBe();
4192
+ this.toBeApplicable = new ToBeApplicable(compiler);
3926
4193
  this.toBeAssignableTo = new ToBeAssignableTo();
3927
4194
  this.toBeAssignableWith = new ToBeAssignableWith();
4195
+ this.toBeCallableWith = new ToBeCallableWith(compiler);
3928
4196
  this.toHaveProperty = new ToHaveProperty(compiler);
3929
4197
  this.toRaiseError = new ToRaiseError(compiler);
3930
4198
  }
3931
4199
  match(assertion, onDiagnostics) {
3932
- const matcherNameText = assertion.matcherName.getText();
4200
+ const matcherNameText = assertion.matcherNameNode.name.text;
3933
4201
  if (!assertion.source[0]) {
3934
4202
  this.#onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
3935
4203
  return;
3936
4204
  }
3937
4205
  const matchWorker = new MatchWorker(this.#compiler, this.#typeChecker, assertion);
4206
+ if (!(matcherNameText === "toRaiseError" && assertion.isNot === false) &&
4207
+ this.#rejectsTypeArguments(matchWorker, onDiagnostics)) {
4208
+ return;
4209
+ }
3938
4210
  switch (matcherNameText) {
3939
4211
  case "toAcceptProps":
3940
4212
  case "toBe":
3941
4213
  case "toBeAssignableTo":
3942
4214
  case "toBeAssignableWith":
3943
- if (!assertion.target[0]) {
4215
+ if (!assertion.target?.[0]) {
3944
4216
  this.#onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
3945
4217
  return;
3946
4218
  }
3947
- if (this.#rejectsTypeArguments(matchWorker, onDiagnostics)) {
3948
- return;
3949
- }
3950
4219
  return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
4220
+ case "toBeApplicable":
4221
+ return this.toBeApplicable.match(matchWorker, assertion.source[0], onDiagnostics);
4222
+ case "toBeCallableWith":
4223
+ case "toRaiseError":
4224
+ return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target, onDiagnostics);
3951
4225
  case "toHaveProperty":
3952
- if (!assertion.target[0]) {
4226
+ if (!assertion.target?.[0]) {
3953
4227
  this.#onTargetArgumentMustBeProvided("key", assertion, onDiagnostics);
3954
4228
  return;
3955
4229
  }
3956
4230
  return this.toHaveProperty.match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
3957
- case "toRaiseError":
3958
- if (assertion.isNot && this.#rejectsTypeArguments(matchWorker, onDiagnostics)) {
3959
- return;
3960
- }
3961
- return this.toRaiseError.match(matchWorker, assertion.source[0], [...assertion.target], onDiagnostics);
3962
4231
  default:
3963
4232
  this.#onMatcherIsNotSupported(matcherNameText, assertion, onDiagnostics);
3964
4233
  }
@@ -3966,7 +4235,7 @@ class ExpectService {
3966
4235
  }
3967
4236
  #onMatcherIsNotSupported(matcherNameText, assertion, onDiagnostics) {
3968
4237
  const text = ExpectDiagnosticText.matcherIsNotSupported(matcherNameText);
3969
- const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
4238
+ const origin = DiagnosticOrigin.fromNode(assertion.matcherNameNode.name);
3970
4239
  onDiagnostics(Diagnostic.error(text, origin));
3971
4240
  }
3972
4241
  #onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics) {
@@ -3976,23 +4245,23 @@ class ExpectService {
3976
4245
  }
3977
4246
  #onTargetArgumentMustBeProvided(argumentNameText, assertion, onDiagnostics) {
3978
4247
  const text = ExpectDiagnosticText.argumentMustBeProvided(argumentNameText);
3979
- const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
4248
+ const origin = DiagnosticOrigin.fromNode(assertion.matcherNameNode.name);
3980
4249
  onDiagnostics(Diagnostic.error(text, origin));
3981
4250
  }
3982
4251
  #onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics) {
3983
4252
  const text = ExpectDiagnosticText.argumentOrTypeArgumentMustBeProvided("target", "Target");
3984
- const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
4253
+ const origin = DiagnosticOrigin.fromNode(assertion.matcherNameNode.name);
3985
4254
  onDiagnostics(Diagnostic.error(text, origin));
3986
4255
  }
3987
4256
  #rejectsTypeArguments(matchWorker, onDiagnostics) {
3988
4257
  for (const rejectedType of this.#rejectTypes) {
3989
4258
  const allowedKeyword = this.#compiler.SyntaxKind[`${Format.capitalize(rejectedType)}Keyword`];
3990
4259
  if (matchWorker.assertion.source[0]?.kind === allowedKeyword ||
3991
- matchWorker.assertion.target[0]?.kind === allowedKeyword) {
4260
+ matchWorker.assertion.target?.[0]?.kind === allowedKeyword) {
3992
4261
  continue;
3993
4262
  }
3994
4263
  for (const argumentName of ["source", "target"]) {
3995
- const argumentNode = matchWorker.assertion[argumentName][0];
4264
+ const argumentNode = matchWorker.assertion[argumentName]?.[0];
3996
4265
  if (!argumentNode) {
3997
4266
  continue;
3998
4267
  }
@@ -4020,7 +4289,7 @@ class TestTreeWalker {
4020
4289
  #position;
4021
4290
  #resolvedConfig;
4022
4291
  #taskResult;
4023
- constructor(resolvedConfig, compiler, typeChecker, options) {
4292
+ constructor(compiler, typeChecker, resolvedConfig, options) {
4024
4293
  this.#resolvedConfig = resolvedConfig;
4025
4294
  this.#cancellationToken = options.cancellationToken;
4026
4295
  this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
@@ -4089,7 +4358,7 @@ class TestTreeWalker {
4089
4358
  { diagnostics: Array.isArray(diagnostics) ? diagnostics : [diagnostics], result: expectResult },
4090
4359
  ]);
4091
4360
  };
4092
- if (assertion.diagnostics.size > 0 && assertion.matcherName.getText() !== "toRaiseError") {
4361
+ if (assertion.diagnostics.size > 0 && assertion.matcherNameNode.name.text !== "toRaiseError") {
4093
4362
  onExpectDiagnostics(Diagnostic.fromDiagnostics([...assertion.diagnostics]));
4094
4363
  return;
4095
4364
  }
@@ -4166,15 +4435,15 @@ class TestTreeWalker {
4166
4435
  }
4167
4436
 
4168
4437
  class TaskRunner {
4169
- #compiler;
4170
4438
  #collectService;
4439
+ #compiler;
4171
4440
  #resolvedConfig;
4172
4441
  #projectService;
4173
- constructor(resolvedConfig, compiler) {
4174
- this.#resolvedConfig = resolvedConfig;
4442
+ constructor(compiler, resolvedConfig) {
4175
4443
  this.#compiler = compiler;
4176
- this.#collectService = new CollectService(compiler);
4177
- this.#projectService = new ProjectService(this.#resolvedConfig, compiler);
4444
+ this.#resolvedConfig = resolvedConfig;
4445
+ this.#projectService = new ProjectService(compiler, this.#resolvedConfig);
4446
+ this.#collectService = new CollectService(compiler, this.#projectService, this.#resolvedConfig);
4178
4447
  }
4179
4448
  run(task, cancellationToken) {
4180
4449
  if (cancellationToken?.isCancellationRequested) {
@@ -4225,7 +4494,7 @@ class TaskRunner {
4225
4494
  return;
4226
4495
  }
4227
4496
  const typeChecker = program.getTypeChecker();
4228
- const testTreeWalker = new TestTreeWalker(this.#resolvedConfig, this.#compiler, typeChecker, {
4497
+ const testTreeWalker = new TestTreeWalker(this.#compiler, typeChecker, this.#resolvedConfig, {
4229
4498
  cancellationToken,
4230
4499
  taskResult,
4231
4500
  hasOnly: testTree.hasOnly,
@@ -4238,7 +4507,7 @@ class TaskRunner {
4238
4507
  class Runner {
4239
4508
  #eventEmitter = new EventEmitter();
4240
4509
  #resolvedConfig;
4241
- static version = "4.0.0-beta.0";
4510
+ static version = "4.0.0-beta.2";
4242
4511
  constructor(resolvedConfig) {
4243
4512
  this.#resolvedConfig = resolvedConfig;
4244
4513
  }
@@ -4296,7 +4565,7 @@ class Runner {
4296
4565
  EventEmitter.dispatch(["target:start", { result: targetResult }]);
4297
4566
  const compiler = await Store.load(target);
4298
4567
  if (compiler) {
4299
- const taskRunner = new TaskRunner(this.#resolvedConfig, compiler);
4568
+ const taskRunner = new TaskRunner(compiler, this.#resolvedConfig);
4300
4569
  for (const task of tasks) {
4301
4570
  taskRunner.run(task, cancellationToken);
4302
4571
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tstyche",
3
- "version": "4.0.0-beta.0",
3
+ "version": "4.0.0-beta.2",
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/*"
@@ -60,17 +62,17 @@
60
62
  "devDependencies": {
61
63
  "@biomejs/biome": "1.9.4",
62
64
  "@rollup/plugin-typescript": "12.1.2",
63
- "@types/node": "22.13.14",
64
- "@types/react": "19.0.12",
65
+ "@types/node": "22.14.0",
66
+ "@types/react": "19.1.0",
65
67
  "ajv": "8.17.1",
66
68
  "cspell": "8.18.1",
67
69
  "magic-string": "0.30.17",
68
70
  "monocart-coverage-reports": "2.12.3",
69
71
  "pretty-ansi": "3.0.0",
70
- "rollup": "4.38.0",
72
+ "rollup": "4.39.0",
71
73
  "rollup-plugin-dts": "6.2.1",
72
74
  "tslib": "2.8.1",
73
- "typescript": "5.8.2"
75
+ "typescript": "5.8.3"
74
76
  },
75
77
  "peerDependencies": {
76
78
  "typescript": "5.x"
@@ -80,7 +82,7 @@
80
82
  "optional": true
81
83
  }
82
84
  },
83
- "packageManager": "yarn@4.8.0",
85
+ "packageManager": "yarn@4.9.0",
84
86
  "engines": {
85
87
  "node": ">=20.9"
86
88
  }