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 +13 -7
- package/build/index.d.cts +8 -0
- package/build/index.d.ts +8 -0
- package/build/tstyche.d.ts +24 -23
- package/build/tstyche.js +389 -120
- package/package.json +8 -6
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
|
|
22
|
-
return
|
|
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("
|
|
26
|
-
expect(
|
|
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(
|
|
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
|
|
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
|
|
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
|
*/
|
package/build/tstyche.d.ts
CHANGED
|
@@ -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
|
-
|
|
111
|
-
|
|
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:
|
|
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.
|
|
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
|
|
1952
|
-
if (
|
|
1953
|
-
diagnostics.push(Diagnostic.error(getText(
|
|
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
|
|
1960
|
-
if (
|
|
1961
|
-
diagnostics.push(Diagnostic.error(getText(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2129
|
-
if (!
|
|
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
|
-
#
|
|
2166
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
3354
|
-
|
|
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
|
|
3357
|
-
return `${isTypeNode ? "Type" : "Expression type"} did not raise a
|
|
3497
|
+
static didNotRaiseError(isTypeNode) {
|
|
3498
|
+
return `${isTypeNode ? "Type" : "Expression type"} did not raise a type error.`;
|
|
3358
3499
|
}
|
|
3359
|
-
static
|
|
3360
|
-
return
|
|
3500
|
+
static raisedMatchingError(isTypeNode) {
|
|
3501
|
+
return `${isTypeNode ? "Type" : "Expression type"} raised a matching type error.`;
|
|
3361
3502
|
}
|
|
3362
|
-
static
|
|
3363
|
-
return
|
|
3503
|
+
static didNotRaiseMatchingError(isTypeNode) {
|
|
3504
|
+
return `${isTypeNode ? "Type" : "Expression type"} did not raise a matching type error.`;
|
|
3364
3505
|
}
|
|
3365
|
-
static
|
|
3506
|
+
static isAssignableTo(sourceTypeText, targetTypeText) {
|
|
3366
3507
|
return `Type '${sourceTypeText}' is assignable to type '${targetTypeText}'.`;
|
|
3367
3508
|
}
|
|
3368
|
-
static
|
|
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
|
|
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
|
|
3381
|
-
return `Type '${sourceTypeText}' is
|
|
3518
|
+
static isIdenticalTo(sourceTypeText, targetTypeText) {
|
|
3519
|
+
return `Type '${sourceTypeText}' is identical to type '${targetTypeText}'.`;
|
|
3382
3520
|
}
|
|
3383
|
-
static
|
|
3521
|
+
static isNotIdenticalTo(sourceTypeText, targetTypeText) {
|
|
3384
3522
|
return `Type '${sourceTypeText}' is not identical to type '${targetTypeText}'.`;
|
|
3385
3523
|
}
|
|
3386
|
-
static
|
|
3387
|
-
|
|
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
|
|
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
|
|
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.#
|
|
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.
|
|
3548
|
-
: ExpectDiagnosticText.
|
|
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.
|
|
3615
|
-
ExpectDiagnosticText.
|
|
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.
|
|
3624
|
-
ExpectDiagnosticText.
|
|
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.
|
|
3752
|
+
ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
|
|
3637
3753
|
ExpectDiagnosticText.typesOfPropertyAreNotCompatible(targetPropertyName),
|
|
3638
|
-
ExpectDiagnosticText.
|
|
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.
|
|
3651
|
-
ExpectDiagnosticText.
|
|
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.
|
|
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.
|
|
3669
|
-
: ExpectDiagnosticText.
|
|
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.
|
|
3732
|
-
explainNotText = ExpectDiagnosticText.
|
|
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.
|
|
3743
|
-
explainNotText = ExpectDiagnosticText.
|
|
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.
|
|
3754
|
-
explainNotText = ExpectDiagnosticText.
|
|
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.
|
|
3781
|
-
: [Diagnostic.error(ExpectDiagnosticText.
|
|
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.
|
|
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.
|
|
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.
|
|
3849
|
-
: ExpectDiagnosticText.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
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.
|
|
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(
|
|
4174
|
-
this.#resolvedConfig = resolvedConfig;
|
|
4442
|
+
constructor(compiler, resolvedConfig) {
|
|
4175
4443
|
this.#compiler = compiler;
|
|
4176
|
-
this.#
|
|
4177
|
-
this.#projectService = new ProjectService(this.#resolvedConfig
|
|
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.#
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
64
|
-
"@types/react": "19.0
|
|
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.
|
|
72
|
+
"rollup": "4.39.0",
|
|
71
73
|
"rollup-plugin-dts": "6.2.1",
|
|
72
74
|
"tslib": "2.8.1",
|
|
73
|
-
"typescript": "5.8.
|
|
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.
|
|
85
|
+
"packageManager": "yarn@4.9.0",
|
|
84
86
|
"engines": {
|
|
85
87
|
"node": ">=20.9"
|
|
86
88
|
}
|