tstyche 4.0.0-beta.0 → 4.0.0-beta.10
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 +11 -7
- package/build/index.cjs +1 -0
- package/build/index.d.cts +40 -4
- package/build/index.d.ts +40 -4
- package/build/index.js +1 -1
- package/build/tstyche.d.ts +116 -97
- package/build/tstyche.js +1173 -651
- package/package.json +13 -11
package/build/tstyche.js
CHANGED
|
@@ -93,12 +93,39 @@ 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
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function diagnosticBelongsToNode(diagnostic, node) {
|
|
108
|
+
return diagnostic.start != null && diagnostic.start >= node.pos && diagnostic.start <= node.end;
|
|
109
|
+
}
|
|
110
|
+
function diagnosticMessageChainToText(chain) {
|
|
111
|
+
const result = [chain.messageText];
|
|
112
|
+
if (chain.next != null) {
|
|
113
|
+
for (const nextChain of chain.next) {
|
|
114
|
+
result.push(...diagnosticMessageChainToText(nextChain));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
function getDiagnosticMessageText(diagnostic) {
|
|
120
|
+
return typeof diagnostic.messageText === "string"
|
|
121
|
+
? diagnostic.messageText
|
|
122
|
+
: diagnosticMessageChainToText(diagnostic.messageText);
|
|
123
|
+
}
|
|
124
|
+
function getTextSpanEnd(span) {
|
|
125
|
+
return span.start + span.length;
|
|
126
|
+
}
|
|
127
|
+
function isDiagnosticWithLocation(diagnostic) {
|
|
128
|
+
return diagnostic.file != null && diagnostic.start != null && diagnostic.length != null;
|
|
102
129
|
}
|
|
103
130
|
|
|
104
131
|
class Diagnostic {
|
|
@@ -138,21 +165,10 @@ class Diagnostic {
|
|
|
138
165
|
if (diagnostic.relatedInformation != null) {
|
|
139
166
|
related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
|
|
140
167
|
}
|
|
141
|
-
const text =
|
|
142
|
-
? diagnostic.messageText
|
|
143
|
-
: Diagnostic.toMessageText(diagnostic.messageText);
|
|
168
|
+
const text = getDiagnosticMessageText(diagnostic);
|
|
144
169
|
return new Diagnostic(text, DiagnosticCategory.Error, origin).add({ code, related });
|
|
145
170
|
});
|
|
146
171
|
}
|
|
147
|
-
static toMessageText(chain) {
|
|
148
|
-
const result = [chain.messageText];
|
|
149
|
-
if (chain.next != null) {
|
|
150
|
-
for (const nextChain of chain.next) {
|
|
151
|
-
result.push(...Diagnostic.toMessageText(nextChain));
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return result;
|
|
155
|
-
}
|
|
156
172
|
static warning(text, origin) {
|
|
157
173
|
return new Diagnostic(text, DiagnosticCategory.Warning, origin);
|
|
158
174
|
}
|
|
@@ -556,7 +572,6 @@ class ManifestService {
|
|
|
556
572
|
#manifestFilePath;
|
|
557
573
|
#npmRegistry;
|
|
558
574
|
#storePath;
|
|
559
|
-
#supportedVersionRegex = /^(5)\.\d\.\d$/;
|
|
560
575
|
constructor(storePath, npmRegistry, fetcher) {
|
|
561
576
|
this.#storePath = storePath;
|
|
562
577
|
this.#npmRegistry = npmRegistry;
|
|
@@ -586,7 +601,7 @@ class ManifestService {
|
|
|
586
601
|
const versions = [];
|
|
587
602
|
const packageMetadata = (await response.json());
|
|
588
603
|
for (const [tag, meta] of Object.entries(packageMetadata.versions)) {
|
|
589
|
-
if (
|
|
604
|
+
if (!tag.includes("-") && Version.isSatisfiedWith(tag, "4.7.2")) {
|
|
590
605
|
versions.push(tag);
|
|
591
606
|
packages[tag] = { integrity: meta.dist.integrity, tarball: meta.dist.tarball };
|
|
592
607
|
}
|
|
@@ -685,6 +700,9 @@ class PackageService {
|
|
|
685
700
|
if (response?.body != null) {
|
|
686
701
|
const targetPath = `${packagePath}-${Math.random().toString(32).slice(2)}`;
|
|
687
702
|
for await (const file of TarReader.extract(response.body)) {
|
|
703
|
+
if (!file.name.startsWith("package/")) {
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
688
706
|
const filePath = Path.join(targetPath, file.name.replace("package/", ""));
|
|
689
707
|
const directoryPath = Path.dirname(filePath);
|
|
690
708
|
if (!existsSync(directoryPath)) {
|
|
@@ -792,11 +810,7 @@ class Store {
|
|
|
792
810
|
modulePath = Path.resolve(modulePath, "../tsserverlibrary.js");
|
|
793
811
|
}
|
|
794
812
|
const sourceText = await fs.readFile(modulePath, { encoding: "utf8" });
|
|
795
|
-
const toExpose = [
|
|
796
|
-
"isApplicableIndexType",
|
|
797
|
-
"isTypeRelatedTo",
|
|
798
|
-
"relation: { assignable: assignableRelation, identity: identityRelation }",
|
|
799
|
-
];
|
|
813
|
+
const toExpose = ["isApplicableIndexType", "isTypeIdenticalTo"];
|
|
800
814
|
const modifiedSourceText = sourceText.replace("return checker;", `return { ...checker, ${toExpose.join(", ")} };`);
|
|
801
815
|
const compiledWrapper = vm.compileFunction(modifiedSourceText, ["exports", "require", "module", "__filename", "__dirname"], { filename: modulePath });
|
|
802
816
|
compiledWrapper(exports, createRequire(modulePath), module, modulePath, Path.dirname(modulePath));
|
|
@@ -903,7 +917,7 @@ class Options {
|
|
|
903
917
|
},
|
|
904
918
|
{
|
|
905
919
|
brand: OptionBrand.BareTrue,
|
|
906
|
-
description: "Fetch specified versions of the 'typescript' package and exit.",
|
|
920
|
+
description: "Fetch the specified versions of the 'typescript' package and exit.",
|
|
907
921
|
group: OptionGroup.CommandLine,
|
|
908
922
|
name: "fetch",
|
|
909
923
|
},
|
|
@@ -1656,14 +1670,12 @@ class ResultCount {
|
|
|
1656
1670
|
class Result {
|
|
1657
1671
|
expectCount = new ResultCount();
|
|
1658
1672
|
fileCount = new ResultCount();
|
|
1659
|
-
resolvedConfig;
|
|
1660
1673
|
results = [];
|
|
1661
1674
|
targetCount = new ResultCount();
|
|
1662
1675
|
tasks;
|
|
1663
1676
|
testCount = new ResultCount();
|
|
1664
1677
|
timing = new ResultTiming();
|
|
1665
|
-
constructor(
|
|
1666
|
-
this.resolvedConfig = resolvedConfig;
|
|
1678
|
+
constructor(tasks) {
|
|
1667
1679
|
this.tasks = tasks;
|
|
1668
1680
|
}
|
|
1669
1681
|
}
|
|
@@ -1765,6 +1777,7 @@ class ResultHandler {
|
|
|
1765
1777
|
this.#taskResult.timing.start = Date.now();
|
|
1766
1778
|
break;
|
|
1767
1779
|
case "task:error":
|
|
1780
|
+
case "collect:error":
|
|
1768
1781
|
this.#targetResult.status = ResultStatus.Failed;
|
|
1769
1782
|
this.#taskResult.status = ResultStatus.Failed;
|
|
1770
1783
|
this.#taskResult.diagnostics.push(...payload.diagnostics);
|
|
@@ -1899,293 +1912,6 @@ class ResultHandler {
|
|
|
1899
1912
|
}
|
|
1900
1913
|
}
|
|
1901
1914
|
|
|
1902
|
-
var TestTreeNodeBrand;
|
|
1903
|
-
(function (TestTreeNodeBrand) {
|
|
1904
|
-
TestTreeNodeBrand["Describe"] = "describe";
|
|
1905
|
-
TestTreeNodeBrand["Test"] = "test";
|
|
1906
|
-
TestTreeNodeBrand["Expect"] = "expect";
|
|
1907
|
-
})(TestTreeNodeBrand || (TestTreeNodeBrand = {}));
|
|
1908
|
-
|
|
1909
|
-
class TestTreeNode {
|
|
1910
|
-
brand;
|
|
1911
|
-
children = [];
|
|
1912
|
-
#compiler;
|
|
1913
|
-
diagnostics = new Set();
|
|
1914
|
-
flags;
|
|
1915
|
-
name = "";
|
|
1916
|
-
node;
|
|
1917
|
-
parent;
|
|
1918
|
-
constructor(compiler, brand, node, parent, flags) {
|
|
1919
|
-
this.brand = brand;
|
|
1920
|
-
this.#compiler = compiler;
|
|
1921
|
-
this.node = node;
|
|
1922
|
-
this.parent = parent;
|
|
1923
|
-
this.flags = flags;
|
|
1924
|
-
if (node.arguments[0] != null && compiler.isStringLiteralLike(node.arguments[0])) {
|
|
1925
|
-
this.name = node.arguments[0].text;
|
|
1926
|
-
}
|
|
1927
|
-
if (node.arguments[1] != null &&
|
|
1928
|
-
compiler.isFunctionLike(node.arguments[1]) &&
|
|
1929
|
-
compiler.isBlock(node.arguments[1].body)) {
|
|
1930
|
-
const blockStart = node.arguments[1].body.getStart();
|
|
1931
|
-
const blockEnd = node.arguments[1].body.getEnd();
|
|
1932
|
-
for (const diagnostic of parent.diagnostics) {
|
|
1933
|
-
if (diagnostic.start != null && diagnostic.start >= blockStart && diagnostic.start <= blockEnd) {
|
|
1934
|
-
this.diagnostics.add(diagnostic);
|
|
1935
|
-
parent.diagnostics.delete(diagnostic);
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
}
|
|
1940
|
-
validate() {
|
|
1941
|
-
const diagnostics = [];
|
|
1942
|
-
const getText = (node) => `'${node.expression.getText()}()' cannot be nested within '${this.node.expression.getText()}()'.`;
|
|
1943
|
-
const getParentCallExpression = (node) => {
|
|
1944
|
-
while (!this.#compiler.isCallExpression(node.parent)) {
|
|
1945
|
-
node = node.parent;
|
|
1946
|
-
}
|
|
1947
|
-
return node.parent;
|
|
1948
|
-
};
|
|
1949
|
-
switch (this.brand) {
|
|
1950
|
-
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
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
break;
|
|
1957
|
-
case TestTreeNodeBrand.Test:
|
|
1958
|
-
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
|
-
}
|
|
1963
|
-
}
|
|
1964
|
-
break;
|
|
1965
|
-
}
|
|
1966
|
-
return diagnostics;
|
|
1967
|
-
}
|
|
1968
|
-
}
|
|
1969
|
-
|
|
1970
|
-
class AssertionNode extends TestTreeNode {
|
|
1971
|
-
isNot;
|
|
1972
|
-
matcherName;
|
|
1973
|
-
matcherNode;
|
|
1974
|
-
modifierNode;
|
|
1975
|
-
notNode;
|
|
1976
|
-
source;
|
|
1977
|
-
target;
|
|
1978
|
-
constructor(compiler, brand, node, parent, flags, matcherNode, modifierNode, notNode) {
|
|
1979
|
-
super(compiler, brand, node, parent, flags);
|
|
1980
|
-
this.isNot = notNode != null;
|
|
1981
|
-
this.matcherName = matcherNode.expression.name;
|
|
1982
|
-
this.matcherNode = matcherNode;
|
|
1983
|
-
this.modifierNode = modifierNode;
|
|
1984
|
-
this.source = this.node.typeArguments ?? this.node.arguments;
|
|
1985
|
-
this.target = this.matcherNode.typeArguments ?? this.matcherNode.arguments;
|
|
1986
|
-
for (const diagnostic of parent.diagnostics) {
|
|
1987
|
-
if (diagnostic.start != null && diagnostic.start >= this.source.pos && diagnostic.start <= this.source.end) {
|
|
1988
|
-
this.diagnostics.add(diagnostic);
|
|
1989
|
-
parent.diagnostics.delete(diagnostic);
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
}
|
|
1994
|
-
|
|
1995
|
-
var TestTreeNodeFlags;
|
|
1996
|
-
(function (TestTreeNodeFlags) {
|
|
1997
|
-
TestTreeNodeFlags[TestTreeNodeFlags["None"] = 0] = "None";
|
|
1998
|
-
TestTreeNodeFlags[TestTreeNodeFlags["Fail"] = 1] = "Fail";
|
|
1999
|
-
TestTreeNodeFlags[TestTreeNodeFlags["Only"] = 2] = "Only";
|
|
2000
|
-
TestTreeNodeFlags[TestTreeNodeFlags["Skip"] = 4] = "Skip";
|
|
2001
|
-
TestTreeNodeFlags[TestTreeNodeFlags["Todo"] = 8] = "Todo";
|
|
2002
|
-
})(TestTreeNodeFlags || (TestTreeNodeFlags = {}));
|
|
2003
|
-
|
|
2004
|
-
class IdentifierLookup {
|
|
2005
|
-
#compiler;
|
|
2006
|
-
#identifiers;
|
|
2007
|
-
#moduleSpecifiers = ['"tstyche"', "'tstyche'"];
|
|
2008
|
-
constructor(compiler, identifiers) {
|
|
2009
|
-
this.#compiler = compiler;
|
|
2010
|
-
this.#identifiers = identifiers ?? {
|
|
2011
|
-
namedImports: {
|
|
2012
|
-
describe: undefined,
|
|
2013
|
-
expect: undefined,
|
|
2014
|
-
it: undefined,
|
|
2015
|
-
namespace: undefined,
|
|
2016
|
-
test: undefined,
|
|
2017
|
-
},
|
|
2018
|
-
namespace: undefined,
|
|
2019
|
-
};
|
|
2020
|
-
}
|
|
2021
|
-
handleImportDeclaration(node) {
|
|
2022
|
-
if (this.#moduleSpecifiers.includes(node.moduleSpecifier.getText()) &&
|
|
2023
|
-
node.importClause?.isTypeOnly !== true &&
|
|
2024
|
-
node.importClause?.namedBindings != null) {
|
|
2025
|
-
if (this.#compiler.isNamedImports(node.importClause.namedBindings)) {
|
|
2026
|
-
for (const element of node.importClause.namedBindings.elements) {
|
|
2027
|
-
if (element.isTypeOnly) {
|
|
2028
|
-
continue;
|
|
2029
|
-
}
|
|
2030
|
-
let identifierKey;
|
|
2031
|
-
if (element.propertyName) {
|
|
2032
|
-
identifierKey = element.propertyName.getText();
|
|
2033
|
-
}
|
|
2034
|
-
else {
|
|
2035
|
-
identifierKey = element.name.getText();
|
|
2036
|
-
}
|
|
2037
|
-
if (identifierKey in this.#identifiers.namedImports) {
|
|
2038
|
-
this.#identifiers.namedImports[identifierKey] = element.name.getText();
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
|
-
}
|
|
2042
|
-
if (this.#compiler.isNamespaceImport(node.importClause.namedBindings)) {
|
|
2043
|
-
this.#identifiers.namespace = node.importClause.namedBindings.name.getText();
|
|
2044
|
-
}
|
|
2045
|
-
}
|
|
2046
|
-
}
|
|
2047
|
-
resolveTestMemberMeta(node) {
|
|
2048
|
-
let flags = TestTreeNodeFlags.None;
|
|
2049
|
-
let expression = node.expression;
|
|
2050
|
-
while (this.#compiler.isPropertyAccessExpression(expression)) {
|
|
2051
|
-
if (expression.expression.getText() === this.#identifiers.namespace) {
|
|
2052
|
-
break;
|
|
2053
|
-
}
|
|
2054
|
-
switch (expression.name.getText()) {
|
|
2055
|
-
case "fail":
|
|
2056
|
-
flags |= TestTreeNodeFlags.Fail;
|
|
2057
|
-
break;
|
|
2058
|
-
case "only":
|
|
2059
|
-
flags |= TestTreeNodeFlags.Only;
|
|
2060
|
-
break;
|
|
2061
|
-
case "skip":
|
|
2062
|
-
flags |= TestTreeNodeFlags.Skip;
|
|
2063
|
-
break;
|
|
2064
|
-
case "todo":
|
|
2065
|
-
flags |= TestTreeNodeFlags.Todo;
|
|
2066
|
-
break;
|
|
2067
|
-
}
|
|
2068
|
-
expression = expression.expression;
|
|
2069
|
-
}
|
|
2070
|
-
let identifierName;
|
|
2071
|
-
if (this.#compiler.isPropertyAccessExpression(expression) &&
|
|
2072
|
-
expression.expression.getText() === this.#identifiers.namespace) {
|
|
2073
|
-
identifierName = expression.name.getText();
|
|
2074
|
-
}
|
|
2075
|
-
else {
|
|
2076
|
-
identifierName = Object.keys(this.#identifiers.namedImports).find((key) => this.#identifiers.namedImports[key] === expression.getText());
|
|
2077
|
-
}
|
|
2078
|
-
if (!identifierName) {
|
|
2079
|
-
return;
|
|
2080
|
-
}
|
|
2081
|
-
switch (identifierName) {
|
|
2082
|
-
case "describe":
|
|
2083
|
-
return { brand: TestTreeNodeBrand.Describe, flags };
|
|
2084
|
-
case "it":
|
|
2085
|
-
case "test":
|
|
2086
|
-
return { brand: TestTreeNodeBrand.Test, flags };
|
|
2087
|
-
case "expect":
|
|
2088
|
-
return { brand: TestTreeNodeBrand.Expect, flags };
|
|
2089
|
-
}
|
|
2090
|
-
return;
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
class TestTree {
|
|
2095
|
-
children = [];
|
|
2096
|
-
diagnostics;
|
|
2097
|
-
hasOnly = false;
|
|
2098
|
-
sourceFile;
|
|
2099
|
-
constructor(diagnostics, sourceFile) {
|
|
2100
|
-
this.diagnostics = diagnostics;
|
|
2101
|
-
this.sourceFile = sourceFile;
|
|
2102
|
-
}
|
|
2103
|
-
}
|
|
2104
|
-
|
|
2105
|
-
class CollectService {
|
|
2106
|
-
#compiler;
|
|
2107
|
-
constructor(compiler) {
|
|
2108
|
-
this.#compiler = compiler;
|
|
2109
|
-
}
|
|
2110
|
-
#collectTestTreeNodes(node, identifiers, parent) {
|
|
2111
|
-
if (this.#compiler.isCallExpression(node)) {
|
|
2112
|
-
const meta = identifiers.resolveTestMemberMeta(node);
|
|
2113
|
-
if (meta != null && (meta.brand === TestTreeNodeBrand.Describe || meta.brand === TestTreeNodeBrand.Test)) {
|
|
2114
|
-
const testTreeNode = new TestTreeNode(this.#compiler, meta.brand, node, parent, meta.flags);
|
|
2115
|
-
parent.children.push(testTreeNode);
|
|
2116
|
-
EventEmitter.dispatch(["collect:node", { testNode: testTreeNode }]);
|
|
2117
|
-
this.#compiler.forEachChild(node, (node) => {
|
|
2118
|
-
this.#collectTestTreeNodes(node, identifiers, testTreeNode);
|
|
2119
|
-
});
|
|
2120
|
-
return;
|
|
2121
|
-
}
|
|
2122
|
-
if (meta != null && meta.brand === TestTreeNodeBrand.Expect) {
|
|
2123
|
-
const modifierNode = this.#getChainedNode(node, "type");
|
|
2124
|
-
if (!modifierNode) {
|
|
2125
|
-
return;
|
|
2126
|
-
}
|
|
2127
|
-
const notNode = this.#getChainedNode(modifierNode, "not");
|
|
2128
|
-
const matcherNode = this.#getChainedNode(notNode ?? modifierNode)?.parent;
|
|
2129
|
-
if (!matcherNode || !this.#isMatcherNode(matcherNode)) {
|
|
2130
|
-
return;
|
|
2131
|
-
}
|
|
2132
|
-
const assertionNode = new AssertionNode(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, modifierNode, notNode);
|
|
2133
|
-
parent.children.push(assertionNode);
|
|
2134
|
-
EventEmitter.dispatch(["collect:node", { testNode: assertionNode }]);
|
|
2135
|
-
this.#compiler.forEachChild(node, (node) => {
|
|
2136
|
-
this.#collectTestTreeNodes(node, identifiers, assertionNode);
|
|
2137
|
-
});
|
|
2138
|
-
return;
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
if (this.#compiler.isImportDeclaration(node)) {
|
|
2142
|
-
identifiers.handleImportDeclaration(node);
|
|
2143
|
-
return;
|
|
2144
|
-
}
|
|
2145
|
-
this.#compiler.forEachChild(node, (node) => {
|
|
2146
|
-
this.#collectTestTreeNodes(node, identifiers, parent);
|
|
2147
|
-
});
|
|
2148
|
-
}
|
|
2149
|
-
createTestTree(sourceFile, semanticDiagnostics = []) {
|
|
2150
|
-
const testTree = new TestTree(new Set(semanticDiagnostics), sourceFile);
|
|
2151
|
-
EventEmitter.dispatch(["collect:start", { testTree }]);
|
|
2152
|
-
this.#collectTestTreeNodes(sourceFile, new IdentifierLookup(this.#compiler), testTree);
|
|
2153
|
-
EventEmitter.dispatch(["collect:end", { testTree }]);
|
|
2154
|
-
return testTree;
|
|
2155
|
-
}
|
|
2156
|
-
#getChainedNode({ parent }, name) {
|
|
2157
|
-
if (!this.#compiler.isPropertyAccessExpression(parent)) {
|
|
2158
|
-
return;
|
|
2159
|
-
}
|
|
2160
|
-
if (name != null && name !== parent.name.getText()) {
|
|
2161
|
-
return;
|
|
2162
|
-
}
|
|
2163
|
-
return parent;
|
|
2164
|
-
}
|
|
2165
|
-
#isMatcherNode(node) {
|
|
2166
|
-
return this.#compiler.isCallExpression(node) && this.#compiler.isPropertyAccessExpression(node.expression);
|
|
2167
|
-
}
|
|
2168
|
-
}
|
|
2169
|
-
|
|
2170
|
-
class TestTreeHandler {
|
|
2171
|
-
testTree;
|
|
2172
|
-
on([event, payload]) {
|
|
2173
|
-
switch (event) {
|
|
2174
|
-
case "collect:start":
|
|
2175
|
-
this.testTree = payload.testTree;
|
|
2176
|
-
break;
|
|
2177
|
-
case "collect:end":
|
|
2178
|
-
this.testTree = undefined;
|
|
2179
|
-
break;
|
|
2180
|
-
case "collect:node":
|
|
2181
|
-
if (payload.testNode.flags & TestTreeNodeFlags.Only) {
|
|
2182
|
-
this.testTree.hasOnly = true;
|
|
2183
|
-
}
|
|
2184
|
-
break;
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2188
|
-
|
|
2189
1915
|
function jsx(type, props) {
|
|
2190
1916
|
return { props, type };
|
|
2191
1917
|
}
|
|
@@ -2500,39 +2226,12 @@ function CountText({ failed, passed, skipped, todo, total }) {
|
|
|
2500
2226
|
function DurationText({ seconds }) {
|
|
2501
2227
|
return jsx(Text, { children: `${Math.round(seconds * 10) / 10}s` });
|
|
2502
2228
|
}
|
|
2503
|
-
function
|
|
2504
|
-
if (typeof text === "string") {
|
|
2505
|
-
return jsx(Text, { children: ["'", text, "'"] });
|
|
2506
|
-
}
|
|
2507
|
-
if (text.length === 1) {
|
|
2508
|
-
return jsx(Text, { children: ["'", ...text, "'"] });
|
|
2509
|
-
}
|
|
2510
|
-
const lastItem = text.pop();
|
|
2511
|
-
return (jsx(Text, { children: [text.map((match, index, list) => (jsx(Text, { children: ["'", match, "'", index === list.length - 1 ? jsx(Text, { children: " " }) : jsx(Text, { color: Color.Gray, children: ", " })] }))), jsx(Text, { color: Color.Gray, children: "or" }), " '", lastItem, "'"] }));
|
|
2512
|
-
}
|
|
2513
|
-
function RanFilesText({ onlyMatch, pathMatch, skipMatch }) {
|
|
2514
|
-
const testNameMatchText = [];
|
|
2515
|
-
if (onlyMatch != null) {
|
|
2516
|
-
testNameMatchText.push(jsx(Text, { children: [jsx(Text, { color: Color.Gray, children: "matching " }), jsx(MatchText, { text: onlyMatch })] }));
|
|
2517
|
-
}
|
|
2518
|
-
if (skipMatch != null) {
|
|
2519
|
-
testNameMatchText.push(jsx(Text, { children: [onlyMatch && jsx(Text, { color: Color.Gray, children: " and " }), jsx(Text, { color: Color.Gray, children: "not matching " }), jsx(MatchText, { text: skipMatch })] }));
|
|
2520
|
-
}
|
|
2521
|
-
let pathMatchText;
|
|
2522
|
-
if (pathMatch.length > 0) {
|
|
2523
|
-
pathMatchText = (jsx(Text, { children: [jsx(Text, { color: Color.Gray, children: "test files matching " }), jsx(MatchText, { text: pathMatch }), jsx(Text, { color: Color.Gray, children: "." })] }));
|
|
2524
|
-
}
|
|
2525
|
-
else {
|
|
2526
|
-
pathMatchText = jsx(Text, { color: Color.Gray, children: "all test files." });
|
|
2527
|
-
}
|
|
2528
|
-
return (jsx(Line, { children: [jsx(Text, { color: Color.Gray, children: "Ran " }), testNameMatchText.length > 0 ? jsx(Text, { color: Color.Gray, children: "tests " }) : undefined, testNameMatchText, testNameMatchText.length > 0 ? jsx(Text, { color: Color.Gray, children: " in " }) : undefined, pathMatchText] }));
|
|
2529
|
-
}
|
|
2530
|
-
function summaryText({ duration, expectCount, fileCount, onlyMatch, pathMatch, skipMatch, targetCount, testCount, }) {
|
|
2229
|
+
function summaryText({ duration, expectCount, fileCount, targetCount, testCount, }) {
|
|
2531
2230
|
const targetCountText = (jsx(RowText, { label: "Targets", text: jsx(CountText, { failed: targetCount.failed, passed: targetCount.passed, skipped: targetCount.skipped, todo: targetCount.todo, total: targetCount.total }) }));
|
|
2532
2231
|
const fileCountText = (jsx(RowText, { label: "Test files", text: jsx(CountText, { failed: fileCount.failed, passed: fileCount.passed, skipped: fileCount.skipped, todo: fileCount.todo, total: fileCount.total }) }));
|
|
2533
2232
|
const testCountText = (jsx(RowText, { label: "Tests", text: jsx(CountText, { failed: testCount.failed, passed: testCount.passed, skipped: testCount.skipped, todo: testCount.todo, total: testCount.total }) }));
|
|
2534
2233
|
const assertionCountText = (jsx(RowText, { label: "Assertions", text: jsx(CountText, { failed: expectCount.failed, passed: expectCount.passed, skipped: expectCount.skipped, todo: expectCount.todo, total: expectCount.total }) }));
|
|
2535
|
-
return (jsx(Text, { children: [targetCountText, fileCountText, testCount.total > 0 ? testCountText : undefined, expectCount.total > 0 ? assertionCountText : undefined, jsx(RowText, { label: "Duration", text: jsx(DurationText, { seconds: duration / 1000 }) })
|
|
2234
|
+
return (jsx(Text, { children: [targetCountText, fileCountText, testCount.total > 0 ? testCountText : undefined, expectCount.total > 0 ? assertionCountText : undefined, jsx(RowText, { label: "Duration", text: jsx(DurationText, { seconds: duration / 1000 }) })] }));
|
|
2536
2235
|
}
|
|
2537
2236
|
|
|
2538
2237
|
function StatusText({ status }) {
|
|
@@ -2681,6 +2380,7 @@ class ListReporter extends BaseReporter {
|
|
|
2681
2380
|
this.#hasReportedError = false;
|
|
2682
2381
|
break;
|
|
2683
2382
|
case "task:error":
|
|
2383
|
+
case "collect:error":
|
|
2684
2384
|
for (const diagnostic of payload.diagnostics) {
|
|
2685
2385
|
this.#fileView.addMessage(diagnosticText(diagnostic));
|
|
2686
2386
|
}
|
|
@@ -2776,9 +2476,6 @@ class SummaryReporter extends BaseReporter {
|
|
|
2776
2476
|
duration: payload.result.timing.duration,
|
|
2777
2477
|
expectCount: payload.result.expectCount,
|
|
2778
2478
|
fileCount: payload.result.fileCount,
|
|
2779
|
-
onlyMatch: payload.result.resolvedConfig.only,
|
|
2780
|
-
pathMatch: payload.result.resolvedConfig.pathMatch,
|
|
2781
|
-
skipMatch: payload.result.resolvedConfig.skip,
|
|
2782
2479
|
targetCount: payload.result.targetCount,
|
|
2783
2480
|
testCount: payload.result.testCount,
|
|
2784
2481
|
}));
|
|
@@ -3173,20 +2870,512 @@ class WatchService {
|
|
|
3173
2870
|
}
|
|
3174
2871
|
}
|
|
3175
2872
|
|
|
3176
|
-
class
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
2873
|
+
class TestTreeNode {
|
|
2874
|
+
brand;
|
|
2875
|
+
children = [];
|
|
2876
|
+
diagnostics = new Set();
|
|
2877
|
+
flags;
|
|
2878
|
+
name = "";
|
|
2879
|
+
node;
|
|
2880
|
+
parent;
|
|
2881
|
+
constructor(compiler, brand, node, parent, flags) {
|
|
2882
|
+
this.brand = brand;
|
|
2883
|
+
this.node = node;
|
|
2884
|
+
this.parent = parent;
|
|
2885
|
+
this.flags = flags;
|
|
2886
|
+
if (node.arguments[0] != null && compiler.isStringLiteralLike(node.arguments[0])) {
|
|
2887
|
+
this.name = node.arguments[0].text;
|
|
2888
|
+
}
|
|
2889
|
+
if (node.arguments[1] != null && compiler.isFunctionLike(node.arguments[1])) {
|
|
2890
|
+
for (const diagnostic of parent.diagnostics) {
|
|
2891
|
+
if (diagnosticBelongsToNode(diagnostic, node.arguments[1].body)) {
|
|
2892
|
+
this.diagnostics.add(diagnostic);
|
|
2893
|
+
parent.diagnostics.delete(diagnostic);
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
class AssertionNode extends TestTreeNode {
|
|
2901
|
+
abilityDiagnostics;
|
|
2902
|
+
isNot;
|
|
2903
|
+
matcherNode;
|
|
2904
|
+
matcherNameNode;
|
|
2905
|
+
modifierNode;
|
|
2906
|
+
notNode;
|
|
2907
|
+
source;
|
|
2908
|
+
target;
|
|
2909
|
+
constructor(compiler, brand, node, parent, flags, matcherNode, matcherNameNode, modifierNode, notNode) {
|
|
2910
|
+
super(compiler, brand, node, parent, flags);
|
|
2911
|
+
this.isNot = notNode != null;
|
|
2912
|
+
this.matcherNode = matcherNode;
|
|
2913
|
+
this.matcherNameNode = matcherNameNode;
|
|
2914
|
+
this.modifierNode = modifierNode;
|
|
2915
|
+
this.source = this.node.typeArguments ?? this.node.arguments;
|
|
2916
|
+
if (compiler.isCallExpression(this.matcherNode)) {
|
|
2917
|
+
this.target = this.matcherNode.typeArguments ?? this.matcherNode.arguments;
|
|
2918
|
+
}
|
|
2919
|
+
for (const diagnostic of parent.diagnostics) {
|
|
2920
|
+
if (diagnosticBelongsToNode(diagnostic, this.source)) {
|
|
2921
|
+
this.diagnostics.add(diagnostic);
|
|
2922
|
+
parent.diagnostics.delete(diagnostic);
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
function nodeBelongsToArgumentList(compiler, node) {
|
|
2929
|
+
return compiler.isCallExpression(node.parent) && node.parent.arguments.some((argument) => argument === node);
|
|
2930
|
+
}
|
|
2931
|
+
function nodeIsChildOfExpressionStatement(compiler, node) {
|
|
2932
|
+
return compiler.isExpressionStatement(node.parent);
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
class AbilityLayer {
|
|
2936
|
+
#compiler;
|
|
2937
|
+
#filePath = "";
|
|
2938
|
+
#nodes = [];
|
|
2939
|
+
#projectService;
|
|
2940
|
+
#resolvedConfig;
|
|
2941
|
+
#text = "";
|
|
2942
|
+
constructor(compiler, projectService, resolvedConfig) {
|
|
2943
|
+
this.#compiler = compiler;
|
|
2944
|
+
this.#projectService = projectService;
|
|
2945
|
+
this.#resolvedConfig = resolvedConfig;
|
|
2946
|
+
}
|
|
2947
|
+
#getErasedRangeText(range) {
|
|
2948
|
+
if (this.#text.indexOf("\n", range.start) >= range.end) {
|
|
2949
|
+
return " ".repeat(range.end - range.start);
|
|
2950
|
+
}
|
|
2951
|
+
const text = [];
|
|
2952
|
+
for (let index = range.start; index < range.end; index++) {
|
|
2953
|
+
const character = this.#text.charAt(index);
|
|
2954
|
+
switch (character) {
|
|
2955
|
+
case "\n":
|
|
2956
|
+
case "\r":
|
|
2957
|
+
text.push(character);
|
|
2958
|
+
break;
|
|
2959
|
+
default:
|
|
2960
|
+
text.push(" ");
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
return text.join("");
|
|
2964
|
+
}
|
|
2965
|
+
#addRanges(node, ranges) {
|
|
2966
|
+
this.#nodes.push(node);
|
|
2967
|
+
for (const range of ranges) {
|
|
2968
|
+
const rangeText = range.replacement != null
|
|
2969
|
+
? `${range.replacement}${this.#getErasedRangeText(range).slice(range.replacement.length)}`
|
|
2970
|
+
: this.#getErasedRangeText(range);
|
|
2971
|
+
this.#text = `${this.#text.slice(0, range.start)}${rangeText}${this.#text.slice(range.end)}`;
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
close() {
|
|
2975
|
+
if (this.#nodes.length > 0) {
|
|
2976
|
+
this.#projectService.openFile(this.#filePath, this.#text, this.#resolvedConfig.rootPath);
|
|
2977
|
+
const languageService = this.#projectService.getLanguageService(this.#filePath);
|
|
2978
|
+
const diagnostics = new Set(languageService?.getSemanticDiagnostics(this.#filePath));
|
|
2979
|
+
for (const node of this.#nodes.reverse()) {
|
|
2980
|
+
for (const diagnostic of diagnostics) {
|
|
2981
|
+
if (diagnosticBelongsToNode(diagnostic, "matcherNode" in node ? node.matcherNode : node.actionNode)) {
|
|
2982
|
+
if (!node.abilityDiagnostics) {
|
|
2983
|
+
node.abilityDiagnostics = new Set();
|
|
2984
|
+
}
|
|
2985
|
+
node.abilityDiagnostics.add(diagnostic);
|
|
2986
|
+
diagnostics.delete(diagnostic);
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
this.#filePath = "";
|
|
2992
|
+
this.#nodes = [];
|
|
2993
|
+
this.#text = "";
|
|
2994
|
+
}
|
|
2995
|
+
#eraseTrailingComma(node, parent) {
|
|
2996
|
+
if (node.hasTrailingComma) {
|
|
2997
|
+
this.#addRanges(parent, [{ start: node.end - 1, end: node.end }]);
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
handleWhen(whenNode) {
|
|
3001
|
+
const whenStart = whenNode.node.getStart();
|
|
3002
|
+
const whenExpressionEnd = whenNode.node.expression.getEnd();
|
|
3003
|
+
const whenEnd = whenNode.node.getEnd();
|
|
3004
|
+
const actionNameEnd = whenNode.actionNameNode.getEnd();
|
|
3005
|
+
switch (whenNode.actionNameNode.name.text) {
|
|
3006
|
+
case "isCalledWith":
|
|
3007
|
+
this.#eraseTrailingComma(whenNode.target, whenNode);
|
|
3008
|
+
this.#addRanges(whenNode, [
|
|
3009
|
+
{
|
|
3010
|
+
start: whenStart,
|
|
3011
|
+
end: whenExpressionEnd,
|
|
3012
|
+
replacement: nodeIsChildOfExpressionStatement(this.#compiler, whenNode.actionNode) ? ";" : "",
|
|
3013
|
+
},
|
|
3014
|
+
{ start: whenEnd, end: actionNameEnd },
|
|
3015
|
+
]);
|
|
3016
|
+
break;
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
handleAssertion(assertionNode) {
|
|
3020
|
+
const expectStart = assertionNode.node.getStart();
|
|
3021
|
+
const expectExpressionEnd = assertionNode.node.expression.getEnd();
|
|
3022
|
+
const expectEnd = assertionNode.node.getEnd();
|
|
3023
|
+
const matcherNameEnd = assertionNode.matcherNameNode.getEnd();
|
|
3024
|
+
switch (assertionNode.matcherNameNode.name.text) {
|
|
3025
|
+
case "toBeApplicable":
|
|
3026
|
+
this.#addRanges(assertionNode, [
|
|
3027
|
+
{ start: expectStart, end: expectExpressionEnd },
|
|
3028
|
+
{ start: expectEnd, end: matcherNameEnd },
|
|
3029
|
+
]);
|
|
3030
|
+
break;
|
|
3031
|
+
case "toBeCallableWith":
|
|
3032
|
+
this.#eraseTrailingComma(assertionNode.source, assertionNode);
|
|
3033
|
+
this.#addRanges(assertionNode, [
|
|
3034
|
+
{
|
|
3035
|
+
start: expectStart,
|
|
3036
|
+
end: expectExpressionEnd,
|
|
3037
|
+
replacement: nodeIsChildOfExpressionStatement(this.#compiler, assertionNode.matcherNode) ? ";" : "",
|
|
3038
|
+
},
|
|
3039
|
+
{ start: expectEnd, end: matcherNameEnd },
|
|
3040
|
+
]);
|
|
3041
|
+
break;
|
|
3042
|
+
case "toBeConstructableWith":
|
|
3043
|
+
this.#eraseTrailingComma(assertionNode.source, assertionNode);
|
|
3044
|
+
this.#addRanges(assertionNode, [
|
|
3045
|
+
{
|
|
3046
|
+
start: expectStart,
|
|
3047
|
+
end: expectExpressionEnd,
|
|
3048
|
+
replacement: nodeIsChildOfExpressionStatement(this.#compiler, assertionNode.matcherNode) ? "; new" : "new",
|
|
3049
|
+
},
|
|
3050
|
+
{ start: expectEnd, end: matcherNameEnd },
|
|
3051
|
+
]);
|
|
3052
|
+
break;
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
open(sourceFile) {
|
|
3056
|
+
this.#filePath = sourceFile.fileName;
|
|
3057
|
+
this.#text = sourceFile.text;
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
class CollectDiagnosticText {
|
|
3062
|
+
static cannotBeNestedWithin(source, target) {
|
|
3063
|
+
return `'${source}()' cannot be nested within '${target}()'.`;
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
var TestTreeNodeBrand;
|
|
3068
|
+
(function (TestTreeNodeBrand) {
|
|
3069
|
+
TestTreeNodeBrand["Describe"] = "describe";
|
|
3070
|
+
TestTreeNodeBrand["Test"] = "test";
|
|
3071
|
+
TestTreeNodeBrand["Expect"] = "expect";
|
|
3072
|
+
TestTreeNodeBrand["When"] = "when";
|
|
3073
|
+
})(TestTreeNodeBrand || (TestTreeNodeBrand = {}));
|
|
3074
|
+
|
|
3075
|
+
var TestTreeNodeFlags;
|
|
3076
|
+
(function (TestTreeNodeFlags) {
|
|
3077
|
+
TestTreeNodeFlags[TestTreeNodeFlags["None"] = 0] = "None";
|
|
3078
|
+
TestTreeNodeFlags[TestTreeNodeFlags["Fail"] = 1] = "Fail";
|
|
3079
|
+
TestTreeNodeFlags[TestTreeNodeFlags["Only"] = 2] = "Only";
|
|
3080
|
+
TestTreeNodeFlags[TestTreeNodeFlags["Skip"] = 4] = "Skip";
|
|
3081
|
+
TestTreeNodeFlags[TestTreeNodeFlags["Todo"] = 8] = "Todo";
|
|
3082
|
+
})(TestTreeNodeFlags || (TestTreeNodeFlags = {}));
|
|
3083
|
+
|
|
3084
|
+
class IdentifierLookup {
|
|
3085
|
+
#compiler;
|
|
3086
|
+
#identifiers;
|
|
3087
|
+
#moduleSpecifiers = ['"tstyche"', "'tstyche'"];
|
|
3088
|
+
constructor(compiler) {
|
|
3089
|
+
this.#compiler = compiler;
|
|
3090
|
+
}
|
|
3091
|
+
handleImportDeclaration(node) {
|
|
3092
|
+
if (this.#moduleSpecifiers.includes(node.moduleSpecifier.getText()) &&
|
|
3093
|
+
node.importClause?.isTypeOnly !== true &&
|
|
3094
|
+
node.importClause?.namedBindings != null) {
|
|
3095
|
+
if (this.#compiler.isNamedImports(node.importClause.namedBindings)) {
|
|
3096
|
+
for (const element of node.importClause.namedBindings.elements) {
|
|
3097
|
+
if (element.isTypeOnly) {
|
|
3098
|
+
continue;
|
|
3099
|
+
}
|
|
3100
|
+
let identifierKey;
|
|
3101
|
+
if (element.propertyName) {
|
|
3102
|
+
identifierKey = element.propertyName.getText();
|
|
3103
|
+
}
|
|
3104
|
+
else {
|
|
3105
|
+
identifierKey = element.name.getText();
|
|
3106
|
+
}
|
|
3107
|
+
if (identifierKey in this.#identifiers.namedImports) {
|
|
3108
|
+
this.#identifiers.namedImports[identifierKey] = element.name.getText();
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
if (this.#compiler.isNamespaceImport(node.importClause.namedBindings)) {
|
|
3113
|
+
this.#identifiers.namespace = node.importClause.namedBindings.name.getText();
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
open() {
|
|
3118
|
+
this.#identifiers = {
|
|
3119
|
+
namedImports: {
|
|
3120
|
+
describe: undefined,
|
|
3121
|
+
expect: undefined,
|
|
3122
|
+
it: undefined,
|
|
3123
|
+
namespace: undefined,
|
|
3124
|
+
test: undefined,
|
|
3125
|
+
when: undefined,
|
|
3126
|
+
},
|
|
3127
|
+
namespace: undefined,
|
|
3128
|
+
};
|
|
3129
|
+
}
|
|
3130
|
+
resolveTestTreeNodeMeta(node) {
|
|
3131
|
+
let flags = TestTreeNodeFlags.None;
|
|
3132
|
+
let expression = node.expression;
|
|
3133
|
+
while (this.#compiler.isPropertyAccessExpression(expression)) {
|
|
3134
|
+
if (expression.expression.getText() === this.#identifiers.namespace) {
|
|
3135
|
+
break;
|
|
3136
|
+
}
|
|
3137
|
+
switch (expression.name.getText()) {
|
|
3138
|
+
case "fail":
|
|
3139
|
+
flags |= TestTreeNodeFlags.Fail;
|
|
3140
|
+
break;
|
|
3141
|
+
case "only":
|
|
3142
|
+
flags |= TestTreeNodeFlags.Only;
|
|
3143
|
+
break;
|
|
3144
|
+
case "skip":
|
|
3145
|
+
flags |= TestTreeNodeFlags.Skip;
|
|
3146
|
+
break;
|
|
3147
|
+
case "todo":
|
|
3148
|
+
flags |= TestTreeNodeFlags.Todo;
|
|
3149
|
+
break;
|
|
3150
|
+
}
|
|
3151
|
+
expression = expression.expression;
|
|
3152
|
+
}
|
|
3153
|
+
let identifier;
|
|
3154
|
+
if (this.#compiler.isPropertyAccessExpression(expression) &&
|
|
3155
|
+
expression.expression.getText() === this.#identifiers.namespace) {
|
|
3156
|
+
identifier = expression.name.getText();
|
|
3157
|
+
}
|
|
3158
|
+
else {
|
|
3159
|
+
identifier = Object.keys(this.#identifiers.namedImports).find((key) => this.#identifiers.namedImports[key] === expression.getText());
|
|
3160
|
+
}
|
|
3161
|
+
if (!identifier) {
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
switch (identifier) {
|
|
3165
|
+
case "describe":
|
|
3166
|
+
return { brand: TestTreeNodeBrand.Describe, flags, identifier };
|
|
3167
|
+
case "it":
|
|
3168
|
+
case "test":
|
|
3169
|
+
return { brand: TestTreeNodeBrand.Test, flags, identifier };
|
|
3170
|
+
case "expect":
|
|
3171
|
+
return { brand: TestTreeNodeBrand.Expect, flags, identifier };
|
|
3172
|
+
case "when":
|
|
3173
|
+
return { brand: TestTreeNodeBrand.When, flags, identifier };
|
|
3174
|
+
}
|
|
3175
|
+
return;
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
class TestTree {
|
|
3180
|
+
children = [];
|
|
3181
|
+
diagnostics;
|
|
3182
|
+
hasOnly = false;
|
|
3183
|
+
sourceFile;
|
|
3184
|
+
constructor(diagnostics, sourceFile) {
|
|
3185
|
+
this.diagnostics = diagnostics;
|
|
3186
|
+
this.sourceFile = sourceFile;
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
class WhenNode extends TestTreeNode {
|
|
3191
|
+
actionNode;
|
|
3192
|
+
actionNameNode;
|
|
3193
|
+
abilityDiagnostics;
|
|
3194
|
+
target;
|
|
3195
|
+
constructor(compiler, brand, node, parent, flags, actionNode, actionNameNode) {
|
|
3196
|
+
super(compiler, brand, node, parent, flags);
|
|
3197
|
+
this.actionNode = actionNode;
|
|
3198
|
+
this.actionNameNode = actionNameNode;
|
|
3199
|
+
this.target = this.node.typeArguments ?? this.node.arguments;
|
|
3200
|
+
for (const diagnostic of parent.diagnostics) {
|
|
3201
|
+
if (diagnosticBelongsToNode(diagnostic, node)) {
|
|
3202
|
+
this.diagnostics.add(diagnostic);
|
|
3203
|
+
parent.diagnostics.delete(diagnostic);
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3209
|
+
class CollectService {
|
|
3210
|
+
#abilityLayer;
|
|
3211
|
+
#compiler;
|
|
3212
|
+
#identifierLookup;
|
|
3213
|
+
constructor(compiler, projectService, resolvedConfig) {
|
|
3214
|
+
this.#compiler = compiler;
|
|
3215
|
+
this.#abilityLayer = new AbilityLayer(compiler, projectService, resolvedConfig);
|
|
3216
|
+
this.#identifierLookup = new IdentifierLookup(compiler);
|
|
3217
|
+
}
|
|
3218
|
+
#collectTestTreeNodes(node, parent, testTree) {
|
|
3219
|
+
if (this.#compiler.isCallExpression(node)) {
|
|
3220
|
+
const meta = this.#identifierLookup.resolveTestTreeNodeMeta(node);
|
|
3221
|
+
if (meta != null) {
|
|
3222
|
+
if (!this.#checkNode(node, meta, parent)) {
|
|
3223
|
+
return;
|
|
3224
|
+
}
|
|
3225
|
+
if (meta.brand === TestTreeNodeBrand.Describe || meta.brand === TestTreeNodeBrand.Test) {
|
|
3226
|
+
const testTreeNode = new TestTreeNode(this.#compiler, meta.brand, node, parent, meta.flags);
|
|
3227
|
+
this.#compiler.forEachChild(node, (node) => {
|
|
3228
|
+
this.#collectTestTreeNodes(node, testTreeNode, testTree);
|
|
3229
|
+
});
|
|
3230
|
+
this.#onNode(testTreeNode, parent, testTree);
|
|
3231
|
+
return;
|
|
3232
|
+
}
|
|
3233
|
+
if (meta.brand === TestTreeNodeBrand.Expect) {
|
|
3234
|
+
const modifierNode = this.#getChainedNode(node, "type");
|
|
3235
|
+
if (!modifierNode) {
|
|
3236
|
+
return;
|
|
3237
|
+
}
|
|
3238
|
+
const notNode = this.#getChainedNode(modifierNode, "not");
|
|
3239
|
+
const matcherNameNode = this.#getChainedNode(notNode ?? modifierNode);
|
|
3240
|
+
if (!matcherNameNode) {
|
|
3241
|
+
return;
|
|
3242
|
+
}
|
|
3243
|
+
const matcherNode = this.#getMatcherNode(matcherNameNode);
|
|
3244
|
+
if (!matcherNode) {
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
const assertionNode = new AssertionNode(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, matcherNameNode, modifierNode, notNode);
|
|
3248
|
+
this.#abilityLayer.handleAssertion(assertionNode);
|
|
3249
|
+
this.#compiler.forEachChild(node, (node) => {
|
|
3250
|
+
this.#collectTestTreeNodes(node, assertionNode, testTree);
|
|
3251
|
+
});
|
|
3252
|
+
this.#onNode(assertionNode, parent, testTree);
|
|
3253
|
+
return;
|
|
3254
|
+
}
|
|
3255
|
+
if (meta.brand === TestTreeNodeBrand.When) {
|
|
3256
|
+
const actionNameNode = this.#getChainedNode(node);
|
|
3257
|
+
if (!actionNameNode) {
|
|
3258
|
+
return;
|
|
3259
|
+
}
|
|
3260
|
+
const actionNode = this.#getActionNode(actionNameNode);
|
|
3261
|
+
if (!actionNode) {
|
|
3262
|
+
return;
|
|
3263
|
+
}
|
|
3264
|
+
this.#compiler.forEachChild(actionNode, (node) => {
|
|
3265
|
+
if (this.#compiler.isCallExpression(node)) {
|
|
3266
|
+
const meta = this.#identifierLookup.resolveTestTreeNodeMeta(node);
|
|
3267
|
+
if (meta?.brand === TestTreeNodeBrand.Describe || meta?.brand === TestTreeNodeBrand.Test) {
|
|
3268
|
+
const text = CollectDiagnosticText.cannotBeNestedWithin(meta.identifier, "when");
|
|
3269
|
+
const origin = DiagnosticOrigin.fromNode(node);
|
|
3270
|
+
this.#onDiagnostics(Diagnostic.error(text, origin));
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
});
|
|
3274
|
+
const whenNode = new WhenNode(this.#compiler, meta.brand, node, parent, meta.flags, actionNode, actionNameNode);
|
|
3275
|
+
this.#abilityLayer.handleWhen(whenNode);
|
|
3276
|
+
this.#onNode(whenNode, parent, testTree);
|
|
3277
|
+
return;
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
if (this.#compiler.isImportDeclaration(node)) {
|
|
3282
|
+
this.#identifierLookup.handleImportDeclaration(node);
|
|
3283
|
+
return;
|
|
3284
|
+
}
|
|
3285
|
+
this.#compiler.forEachChild(node, (node) => {
|
|
3286
|
+
this.#collectTestTreeNodes(node, parent, testTree);
|
|
3287
|
+
});
|
|
3288
|
+
}
|
|
3289
|
+
createTestTree(sourceFile, semanticDiagnostics = []) {
|
|
3290
|
+
const testTree = new TestTree(new Set(semanticDiagnostics), sourceFile);
|
|
3291
|
+
EventEmitter.dispatch(["collect:start", { tree: testTree }]);
|
|
3292
|
+
this.#abilityLayer.open(sourceFile);
|
|
3293
|
+
this.#identifierLookup.open();
|
|
3294
|
+
this.#collectTestTreeNodes(sourceFile, testTree, testTree);
|
|
3295
|
+
this.#abilityLayer.close();
|
|
3296
|
+
EventEmitter.dispatch(["collect:end", { tree: testTree }]);
|
|
3297
|
+
return testTree;
|
|
3298
|
+
}
|
|
3299
|
+
#checkNode(node, meta, parent) {
|
|
3300
|
+
if ("brand" in parent && !this.#isNodeAllowed(meta, parent)) {
|
|
3301
|
+
const text = CollectDiagnosticText.cannotBeNestedWithin(meta.identifier, parent.node.expression.getText());
|
|
3302
|
+
const origin = DiagnosticOrigin.fromNode(node);
|
|
3303
|
+
this.#onDiagnostics(Diagnostic.error(text, origin));
|
|
3304
|
+
return false;
|
|
3305
|
+
}
|
|
3306
|
+
return true;
|
|
3307
|
+
}
|
|
3308
|
+
#isNodeAllowed(meta, parent) {
|
|
3309
|
+
switch (meta.brand) {
|
|
3310
|
+
case TestTreeNodeBrand.Describe:
|
|
3311
|
+
case TestTreeNodeBrand.Test:
|
|
3312
|
+
if (parent.brand === TestTreeNodeBrand.Test || parent.brand === TestTreeNodeBrand.Expect) {
|
|
3313
|
+
return false;
|
|
3314
|
+
}
|
|
3315
|
+
break;
|
|
3316
|
+
case TestTreeNodeBrand.Expect:
|
|
3317
|
+
case TestTreeNodeBrand.When:
|
|
3318
|
+
if (parent.brand === TestTreeNodeBrand.Describe) {
|
|
3319
|
+
return false;
|
|
3320
|
+
}
|
|
3321
|
+
break;
|
|
3322
|
+
}
|
|
3323
|
+
return true;
|
|
3324
|
+
}
|
|
3325
|
+
#getChainedNode({ parent }, name) {
|
|
3326
|
+
if (!this.#compiler.isPropertyAccessExpression(parent)) {
|
|
3327
|
+
return;
|
|
3328
|
+
}
|
|
3329
|
+
if (name != null && name !== parent.name.getText()) {
|
|
3330
|
+
return;
|
|
3331
|
+
}
|
|
3332
|
+
return parent;
|
|
3333
|
+
}
|
|
3334
|
+
#getMatcherNode(node) {
|
|
3335
|
+
if (this.#compiler.isCallExpression(node.parent)) {
|
|
3336
|
+
return node.parent;
|
|
3337
|
+
}
|
|
3338
|
+
if (this.#compiler.isDecorator(node.parent)) {
|
|
3339
|
+
return node.parent;
|
|
3340
|
+
}
|
|
3341
|
+
if (this.#compiler.isParenthesizedExpression(node.parent)) {
|
|
3342
|
+
return this.#getMatcherNode(node.parent);
|
|
3343
|
+
}
|
|
3344
|
+
return;
|
|
3345
|
+
}
|
|
3346
|
+
#getActionNode(node) {
|
|
3347
|
+
if (this.#compiler.isCallExpression(node.parent)) {
|
|
3348
|
+
return node.parent;
|
|
3349
|
+
}
|
|
3350
|
+
return;
|
|
3351
|
+
}
|
|
3352
|
+
#onDiagnostics(diagnostic) {
|
|
3353
|
+
EventEmitter.dispatch(["collect:error", { diagnostics: [diagnostic] }]);
|
|
3354
|
+
}
|
|
3355
|
+
#onNode(node, parent, testTree) {
|
|
3356
|
+
parent.children.push(node);
|
|
3357
|
+
if (node.flags & TestTreeNodeFlags.Only) {
|
|
3358
|
+
testTree.hasOnly = true;
|
|
3359
|
+
}
|
|
3360
|
+
EventEmitter.dispatch(["collect:node", { node }]);
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
class ProjectService {
|
|
3365
|
+
#compiler;
|
|
3366
|
+
#lastSeenProject = "";
|
|
3367
|
+
#resolvedConfig;
|
|
3368
|
+
#seenPrograms = new WeakSet();
|
|
3369
|
+
#seenTestFiles = new Set();
|
|
3370
|
+
#service;
|
|
3371
|
+
constructor(compiler, resolvedConfig) {
|
|
3372
|
+
this.#compiler = compiler;
|
|
3373
|
+
this.#resolvedConfig = resolvedConfig;
|
|
3374
|
+
const noop = () => undefined;
|
|
3375
|
+
const noopLogger = {
|
|
3376
|
+
close: noop,
|
|
3377
|
+
endGroup: noop,
|
|
3378
|
+
getLogFileName: noop,
|
|
3190
3379
|
hasLevel: () => false,
|
|
3191
3380
|
info: noop,
|
|
3192
3381
|
loggingEnabled: () => false,
|
|
@@ -3215,15 +3404,6 @@ class ProjectService {
|
|
|
3215
3404
|
useInferredProjectPerProjectRoot: true,
|
|
3216
3405
|
useSingleInferredProject: false,
|
|
3217
3406
|
});
|
|
3218
|
-
switch (this.#resolvedConfig.tsconfig) {
|
|
3219
|
-
case "findup":
|
|
3220
|
-
break;
|
|
3221
|
-
case "ignore":
|
|
3222
|
-
this.#service.getConfigFileNameForFile = () => undefined;
|
|
3223
|
-
break;
|
|
3224
|
-
default:
|
|
3225
|
-
this.#service.getConfigFileNameForFile = () => this.#resolvedConfig.tsconfig;
|
|
3226
|
-
}
|
|
3227
3407
|
this.#service.setCompilerOptionsForInferredProjects(this.#getDefaultCompilerOptions());
|
|
3228
3408
|
}
|
|
3229
3409
|
closeFile(filePath) {
|
|
@@ -3231,7 +3411,6 @@ class ProjectService {
|
|
|
3231
3411
|
}
|
|
3232
3412
|
#getDefaultCompilerOptions() {
|
|
3233
3413
|
const defaultCompilerOptions = {
|
|
3234
|
-
allowImportingTsExtensions: true,
|
|
3235
3414
|
allowJs: true,
|
|
3236
3415
|
checkJs: true,
|
|
3237
3416
|
exactOptionalPropertyTypes: true,
|
|
@@ -3242,8 +3421,11 @@ class ProjectService {
|
|
|
3242
3421
|
resolveJsonModule: true,
|
|
3243
3422
|
strict: true,
|
|
3244
3423
|
target: this.#compiler.ScriptTarget.ESNext,
|
|
3245
|
-
verbatimModuleSyntax: true,
|
|
3246
3424
|
};
|
|
3425
|
+
if (Version.isSatisfiedWith(this.#compiler.version, "5.0")) {
|
|
3426
|
+
defaultCompilerOptions.allowImportingTsExtensions = true;
|
|
3427
|
+
defaultCompilerOptions.verbatimModuleSyntax = true;
|
|
3428
|
+
}
|
|
3247
3429
|
return defaultCompilerOptions;
|
|
3248
3430
|
}
|
|
3249
3431
|
getDefaultProject(filePath) {
|
|
@@ -3258,7 +3440,23 @@ class ProjectService {
|
|
|
3258
3440
|
const project = this.getDefaultProject(filePath);
|
|
3259
3441
|
return project?.getLanguageService(true);
|
|
3260
3442
|
}
|
|
3443
|
+
#isFileIncluded(filePath) {
|
|
3444
|
+
const configSourceFile = this.#compiler.readJsonConfigFile(this.#resolvedConfig.tsconfig, this.#compiler.sys.readFile);
|
|
3445
|
+
const { fileNames } = this.#compiler.parseJsonSourceFileConfigFileContent(configSourceFile, this.#compiler.sys, Path.dirname(this.#resolvedConfig.tsconfig), undefined, this.#resolvedConfig.tsconfig);
|
|
3446
|
+
return fileNames.includes(filePath);
|
|
3447
|
+
}
|
|
3261
3448
|
openFile(filePath, sourceText, projectRootPath) {
|
|
3449
|
+
switch (this.#resolvedConfig.tsconfig) {
|
|
3450
|
+
case "findup":
|
|
3451
|
+
break;
|
|
3452
|
+
case "ignore":
|
|
3453
|
+
this.#service.getConfigFileNameForFile = () => undefined;
|
|
3454
|
+
break;
|
|
3455
|
+
default:
|
|
3456
|
+
this.#service.getConfigFileNameForFile = this.#isFileIncluded(filePath)
|
|
3457
|
+
? () => this.#resolvedConfig.tsconfig
|
|
3458
|
+
: () => undefined;
|
|
3459
|
+
}
|
|
3262
3460
|
const { configFileErrors, configFileName } = this.#service.openClientFile(filePath, sourceText, undefined, projectRootPath);
|
|
3263
3461
|
if (configFileName !== this.#lastSeenProject) {
|
|
3264
3462
|
this.#lastSeenProject = configFileName;
|
|
@@ -3273,29 +3471,29 @@ class ProjectService {
|
|
|
3273
3471
|
{ diagnostics: Diagnostic.fromDiagnostics(configFileErrors) },
|
|
3274
3472
|
]);
|
|
3275
3473
|
}
|
|
3276
|
-
if (this.#resolvedConfig.checkSourceFiles) {
|
|
3474
|
+
if (this.#resolvedConfig.checkSourceFiles && !this.#seenTestFiles.has(filePath)) {
|
|
3475
|
+
this.#seenTestFiles.add(filePath);
|
|
3277
3476
|
const languageService = this.getLanguageService(filePath);
|
|
3278
3477
|
const program = languageService?.getProgram();
|
|
3279
3478
|
if (!program || this.#seenPrograms.has(program)) {
|
|
3280
3479
|
return;
|
|
3281
3480
|
}
|
|
3282
3481
|
this.#seenPrograms.add(program);
|
|
3283
|
-
const
|
|
3284
|
-
for (const sourceFile of program.getSourceFiles()) {
|
|
3482
|
+
const sourceFilesToCheck = program.getSourceFiles().filter((sourceFile) => {
|
|
3285
3483
|
if (program.isSourceFileFromExternalLibrary(sourceFile) || program.isSourceFileDefaultLibrary(sourceFile)) {
|
|
3286
|
-
|
|
3484
|
+
return false;
|
|
3287
3485
|
}
|
|
3288
|
-
if (
|
|
3289
|
-
|
|
3486
|
+
if (Select.isTestFile(sourceFile.fileName, { ...this.#resolvedConfig, pathMatch: [] })) {
|
|
3487
|
+
return false;
|
|
3290
3488
|
}
|
|
3291
|
-
|
|
3489
|
+
return true;
|
|
3490
|
+
});
|
|
3292
3491
|
const diagnostics = [];
|
|
3293
|
-
for (const sourceFile of
|
|
3492
|
+
for (const sourceFile of sourceFilesToCheck) {
|
|
3294
3493
|
diagnostics.push(...program.getSyntacticDiagnostics(sourceFile), ...program.getSemanticDiagnostics(sourceFile));
|
|
3295
3494
|
}
|
|
3296
3495
|
if (diagnostics.length > 0) {
|
|
3297
3496
|
EventEmitter.dispatch(["project:error", { diagnostics: Diagnostic.fromDiagnostics(diagnostics) }]);
|
|
3298
|
-
return;
|
|
3299
3497
|
}
|
|
3300
3498
|
}
|
|
3301
3499
|
}
|
|
@@ -3310,30 +3508,74 @@ var RunMode;
|
|
|
3310
3508
|
RunMode[RunMode["Todo"] = 8] = "Todo";
|
|
3311
3509
|
})(RunMode || (RunMode = {}));
|
|
3312
3510
|
|
|
3313
|
-
class
|
|
3314
|
-
static
|
|
3315
|
-
return
|
|
3511
|
+
class EnsureDiagnosticText {
|
|
3512
|
+
static argumentMustBeProvided(argumentNameText) {
|
|
3513
|
+
return `An argument for '${argumentNameText}' must be provided.`;
|
|
3514
|
+
}
|
|
3515
|
+
static argumentOrTypeArgumentMustBeProvided(argumentNameText, typeArgumentNameText) {
|
|
3516
|
+
return `An argument for '${argumentNameText}' or type argument for '${typeArgumentNameText}' must be provided.`;
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
|
|
3520
|
+
function argumentIsProvided(argumentNameText, node, enclosingNode, onDiagnostics) {
|
|
3521
|
+
if (!node) {
|
|
3522
|
+
const text = EnsureDiagnosticText.argumentMustBeProvided(argumentNameText);
|
|
3523
|
+
const origin = DiagnosticOrigin.fromNode(enclosingNode);
|
|
3524
|
+
onDiagnostics([Diagnostic.error(text, origin)]);
|
|
3525
|
+
return false;
|
|
3526
|
+
}
|
|
3527
|
+
return true;
|
|
3528
|
+
}
|
|
3529
|
+
|
|
3530
|
+
function argumentOrTypeArgumentIsProvided(argumentNameText, typeArgumentNameText, node, enclosingNode, onDiagnostics) {
|
|
3531
|
+
if (!node) {
|
|
3532
|
+
const text = EnsureDiagnosticText.argumentOrTypeArgumentMustBeProvided(argumentNameText, typeArgumentNameText);
|
|
3533
|
+
const origin = DiagnosticOrigin.fromNode(enclosingNode);
|
|
3534
|
+
onDiagnostics([Diagnostic.error(text, origin)]);
|
|
3535
|
+
return false;
|
|
3536
|
+
}
|
|
3537
|
+
return true;
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
class ExpectDiagnosticText {
|
|
3541
|
+
static argumentMustBe(argumentNameText, expectedText) {
|
|
3542
|
+
return `An argument for '${argumentNameText}' must be ${expectedText}.`;
|
|
3543
|
+
}
|
|
3544
|
+
static typeArgumentMustBe(argumentNameText, expectedText) {
|
|
3545
|
+
return `A type argument for '${argumentNameText}' must be ${expectedText}.`;
|
|
3546
|
+
}
|
|
3547
|
+
static isCallable(isExpression, targetText) {
|
|
3548
|
+
return `${isExpression ? "Expression" : "Type"} is callable ${targetText}.`;
|
|
3549
|
+
}
|
|
3550
|
+
static isNotCallable(isExpression, targetText) {
|
|
3551
|
+
return `${isExpression ? "Expression" : "Type"} is not callable ${targetText}.`;
|
|
3552
|
+
}
|
|
3553
|
+
static isConstructable(isExpression, targetText) {
|
|
3554
|
+
return `${isExpression ? "Expression" : "Type"} is constructable ${targetText}.`;
|
|
3555
|
+
}
|
|
3556
|
+
static isNotConstructable(isExpression, targetText) {
|
|
3557
|
+
return `${isExpression ? "Expression" : "Type"} is not constructable ${targetText}.`;
|
|
3316
3558
|
}
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
class ExpectDiagnosticText {
|
|
3320
|
-
static argumentCannotBeOfType(argumentNameText, typeText) {
|
|
3321
|
-
return `An argument for '${argumentNameText}' cannot be of the '${typeText}' type.`;
|
|
3559
|
+
static acceptsProps(isExpression) {
|
|
3560
|
+
return `${isExpression ? "Component" : "Component type"} accepts props of the given type.`;
|
|
3322
3561
|
}
|
|
3323
|
-
static
|
|
3324
|
-
return
|
|
3562
|
+
static doesNotAcceptProps(isExpression) {
|
|
3563
|
+
return `${isExpression ? "Component" : "Component type"} does not accept props of the given type.`;
|
|
3325
3564
|
}
|
|
3326
|
-
static
|
|
3327
|
-
return `
|
|
3565
|
+
static canBeApplied(targetText) {
|
|
3566
|
+
return `The decorator function can be applied${targetText}.`;
|
|
3328
3567
|
}
|
|
3329
|
-
static
|
|
3330
|
-
return `
|
|
3568
|
+
static cannotBeApplied(targetText) {
|
|
3569
|
+
return `The decorator function cannot be applied${targetText}.`;
|
|
3570
|
+
}
|
|
3571
|
+
static hasProperty(typeText, propertyNameText) {
|
|
3572
|
+
return `Type '${typeText}' has property '${propertyNameText}'.`;
|
|
3331
3573
|
}
|
|
3332
|
-
static
|
|
3333
|
-
return
|
|
3574
|
+
static doesNotHaveProperty(typeText, propertyNameText) {
|
|
3575
|
+
return `Type '${typeText}' does not have property '${propertyNameText}'.`;
|
|
3334
3576
|
}
|
|
3335
|
-
static
|
|
3336
|
-
return
|
|
3577
|
+
static didYouMeanToUse(suggestionText) {
|
|
3578
|
+
return `Did you mean to use ${suggestionText}?`;
|
|
3337
3579
|
}
|
|
3338
3580
|
static matcherIsNotSupported(matcherNameText) {
|
|
3339
3581
|
return `The '.${matcherNameText}()' matcher is not supported.`;
|
|
@@ -3344,87 +3586,73 @@ class ExpectDiagnosticText {
|
|
|
3344
3586
|
static raisedTypeError(count = 1) {
|
|
3345
3587
|
return `The raised type error${count === 1 ? "" : "s"}:`;
|
|
3346
3588
|
}
|
|
3347
|
-
static
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
static typeDidNotRaiseError(isTypeNode) {
|
|
3354
|
-
return `${isTypeNode ? "Type" : "Expression type"} did not raise a type error.`;
|
|
3589
|
+
static raisedError(isExpression, count, targetCount) {
|
|
3590
|
+
let countText = "a";
|
|
3591
|
+
if (count > 1 || targetCount > 1) {
|
|
3592
|
+
countText = count > targetCount ? `${count}` : `only ${count}`;
|
|
3593
|
+
}
|
|
3594
|
+
return `${isExpression ? "Expression" : "Type"} raised ${countText} type error${count === 1 ? "" : "s"}.`;
|
|
3355
3595
|
}
|
|
3356
|
-
static
|
|
3357
|
-
return `${
|
|
3596
|
+
static didNotRaiseError(isExpression) {
|
|
3597
|
+
return `${isExpression ? "Expression" : "Type"} did not raise a type error.`;
|
|
3358
3598
|
}
|
|
3359
|
-
static
|
|
3360
|
-
return
|
|
3599
|
+
static raisedMatchingError(isExpression) {
|
|
3600
|
+
return `${isExpression ? "Expression" : "Type"} raised a matching type error.`;
|
|
3361
3601
|
}
|
|
3362
|
-
static
|
|
3363
|
-
return
|
|
3602
|
+
static didNotRaiseMatchingError(isExpression) {
|
|
3603
|
+
return `${isExpression ? "Expression" : "Type"} did not raise a matching type error.`;
|
|
3364
3604
|
}
|
|
3365
|
-
static
|
|
3605
|
+
static isAssignableTo(sourceTypeText, targetTypeText) {
|
|
3366
3606
|
return `Type '${sourceTypeText}' is assignable to type '${targetTypeText}'.`;
|
|
3367
3607
|
}
|
|
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) {
|
|
3608
|
+
static isNotAssignableTo(sourceTypeText, targetTypeText) {
|
|
3375
3609
|
return `Type '${sourceTypeText}' is not assignable to type '${targetTypeText}'.`;
|
|
3376
3610
|
}
|
|
3377
|
-
static
|
|
3378
|
-
return `Type '${sourceTypeText}' is
|
|
3611
|
+
static isAssignableWith(sourceTypeText, targetTypeText) {
|
|
3612
|
+
return `Type '${sourceTypeText}' is assignable with type '${targetTypeText}'.`;
|
|
3379
3613
|
}
|
|
3380
|
-
static
|
|
3381
|
-
return `Type '${sourceTypeText}' is not
|
|
3614
|
+
static isNotAssignableWith(sourceTypeText, targetTypeText) {
|
|
3615
|
+
return `Type '${sourceTypeText}' is not assignable with type '${targetTypeText}'.`;
|
|
3382
3616
|
}
|
|
3383
|
-
static
|
|
3384
|
-
return `Type '${sourceTypeText}' is
|
|
3617
|
+
static isTheSame(sourceTypeText, targetTypeText) {
|
|
3618
|
+
return `Type '${sourceTypeText}' is the same as type '${targetTypeText}'.`;
|
|
3385
3619
|
}
|
|
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"}.`;
|
|
3620
|
+
static isNotTheSame(sourceTypeText, targetTypeText) {
|
|
3621
|
+
return `Type '${sourceTypeText}' is not the same as type '${targetTypeText}'.`;
|
|
3392
3622
|
}
|
|
3393
|
-
static
|
|
3394
|
-
return
|
|
3623
|
+
static isNotCompatibleWith(sourceTypeText, targetTypeText) {
|
|
3624
|
+
return `Type '${sourceTypeText}' is not compatible with type '${targetTypeText}'.`;
|
|
3395
3625
|
}
|
|
3396
|
-
static
|
|
3626
|
+
static requiresProperty(typeText, propertyNameText) {
|
|
3397
3627
|
return `Type '${typeText}' requires property '${propertyNameText}'.`;
|
|
3398
3628
|
}
|
|
3399
3629
|
static typesOfPropertyAreNotCompatible(propertyNameText) {
|
|
3400
3630
|
return `Types of property '${propertyNameText}' are not compatible.`;
|
|
3401
3631
|
}
|
|
3402
|
-
static typeWasRejected(typeText) {
|
|
3403
|
-
const optionNameText = `reject${Format.capitalize(typeText)}Type`;
|
|
3404
|
-
return [
|
|
3405
|
-
`The '${typeText}' type was rejected because the '${optionNameText}' option is enabled.`,
|
|
3406
|
-
`If this check is necessary, pass '${typeText}' as the type argument explicitly.`,
|
|
3407
|
-
];
|
|
3408
|
-
}
|
|
3409
3632
|
}
|
|
3410
3633
|
|
|
3634
|
+
var Relation;
|
|
3635
|
+
(function (Relation) {
|
|
3636
|
+
Relation["Assignable"] = "assignable";
|
|
3637
|
+
Relation["Identical"] = "identical";
|
|
3638
|
+
})(Relation || (Relation = {}));
|
|
3639
|
+
|
|
3411
3640
|
class MatchWorker {
|
|
3412
3641
|
assertion;
|
|
3413
3642
|
#compiler;
|
|
3414
3643
|
#signatureCache = new Map();
|
|
3415
|
-
|
|
3416
|
-
#typeChecker;
|
|
3644
|
+
typeChecker;
|
|
3417
3645
|
constructor(compiler, typeChecker, assertion) {
|
|
3418
3646
|
this.#compiler = compiler;
|
|
3419
|
-
this
|
|
3647
|
+
this.typeChecker = typeChecker;
|
|
3420
3648
|
this.assertion = assertion;
|
|
3421
3649
|
}
|
|
3422
3650
|
checkHasApplicableIndexType(sourceNode, targetNode) {
|
|
3423
3651
|
const sourceType = this.getType(sourceNode);
|
|
3424
3652
|
const targetType = this.getType(targetNode);
|
|
3425
|
-
return this
|
|
3653
|
+
return this.typeChecker
|
|
3426
3654
|
.getIndexInfosOfType(sourceType)
|
|
3427
|
-
.some(({ keyType }) => this
|
|
3655
|
+
.some(({ keyType }) => this.typeChecker.isApplicableIndexType(targetType, keyType));
|
|
3428
3656
|
}
|
|
3429
3657
|
checkHasProperty(sourceNode, propertyNameText) {
|
|
3430
3658
|
const sourceType = this.getType(sourceNode);
|
|
@@ -3433,38 +3661,36 @@ class MatchWorker {
|
|
|
3433
3661
|
.some((property) => this.#compiler.unescapeLeadingUnderscores(property.escapedName) === propertyNameText);
|
|
3434
3662
|
}
|
|
3435
3663
|
checkIsAssignableTo(sourceNode, targetNode) {
|
|
3436
|
-
|
|
3437
|
-
return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
|
|
3664
|
+
return this.#checkIsRelatedTo(sourceNode, targetNode, Relation.Assignable);
|
|
3438
3665
|
}
|
|
3439
3666
|
checkIsAssignableWith(sourceNode, targetNode) {
|
|
3440
|
-
|
|
3441
|
-
return this.#checkIsRelatedTo(targetNode, sourceNode, relation);
|
|
3667
|
+
return this.#checkIsRelatedTo(targetNode, sourceNode, Relation.Assignable);
|
|
3442
3668
|
}
|
|
3443
3669
|
checkIsIdenticalTo(sourceNode, targetNode) {
|
|
3444
|
-
|
|
3445
|
-
return (this.#checkIsRelatedTo(sourceNode, targetNode, relation) &&
|
|
3670
|
+
return (this.#checkIsRelatedTo(sourceNode, targetNode, Relation.Identical) &&
|
|
3446
3671
|
this.checkIsAssignableTo(sourceNode, targetNode) &&
|
|
3447
3672
|
this.checkIsAssignableWith(sourceNode, targetNode));
|
|
3448
3673
|
}
|
|
3449
3674
|
#checkIsRelatedTo(sourceNode, targetNode, relation) {
|
|
3450
|
-
const sourceType = relation === this.#
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3675
|
+
const sourceType = relation === "identical" ? this.#simplifyType(this.getType(sourceNode)) : this.getType(sourceNode);
|
|
3676
|
+
const targetType = relation === "identical" ? this.#simplifyType(this.getType(targetNode)) : this.getType(targetNode);
|
|
3677
|
+
switch (relation) {
|
|
3678
|
+
case Relation.Assignable:
|
|
3679
|
+
return this.typeChecker.isTypeAssignableTo(sourceType, targetType);
|
|
3680
|
+
case Relation.Identical:
|
|
3681
|
+
return this.typeChecker.isTypeIdenticalTo(sourceType, targetType);
|
|
3682
|
+
}
|
|
3457
3683
|
}
|
|
3458
3684
|
extendsObjectType(type) {
|
|
3459
3685
|
const nonPrimitiveType = { flags: this.#compiler.TypeFlags.NonPrimitive };
|
|
3460
|
-
return this
|
|
3686
|
+
return this.typeChecker.isTypeAssignableTo(type, nonPrimitiveType);
|
|
3461
3687
|
}
|
|
3462
3688
|
getParameterType(signature, index) {
|
|
3463
3689
|
const parameter = signature.getDeclaration().parameters[index];
|
|
3464
3690
|
if (!parameter) {
|
|
3465
3691
|
return;
|
|
3466
3692
|
}
|
|
3467
|
-
return this
|
|
3693
|
+
return this.getType(parameter);
|
|
3468
3694
|
}
|
|
3469
3695
|
getSignatures(node) {
|
|
3470
3696
|
let signatures = this.#signatureCache.get(node);
|
|
@@ -3478,37 +3704,10 @@ class MatchWorker {
|
|
|
3478
3704
|
return signatures;
|
|
3479
3705
|
}
|
|
3480
3706
|
getTypeText(node) {
|
|
3481
|
-
|
|
3482
|
-
return this.#typeChecker.typeToString(type);
|
|
3707
|
+
return this.typeChecker.typeToString(this.getType(node));
|
|
3483
3708
|
}
|
|
3484
3709
|
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;
|
|
3500
|
-
}
|
|
3501
|
-
isStringOrNumberLiteralType(type) {
|
|
3502
|
-
return !!(type.flags & this.#compiler.TypeFlags.StringOrNumberLiteral);
|
|
3503
|
-
}
|
|
3504
|
-
isObjectType(type) {
|
|
3505
|
-
return !!(type.flags & this.#compiler.TypeFlags.Object);
|
|
3506
|
-
}
|
|
3507
|
-
isUnionType(type) {
|
|
3508
|
-
return !!(type.flags & this.#compiler.TypeFlags.Union);
|
|
3509
|
-
}
|
|
3510
|
-
isUniqueSymbolType(type) {
|
|
3511
|
-
return !!(type.flags & this.#compiler.TypeFlags.UniqueESSymbol);
|
|
3710
|
+
return this.typeChecker.getTypeAtLocation(node);
|
|
3512
3711
|
}
|
|
3513
3712
|
resolveDiagnosticOrigin(symbol, enclosingNode) {
|
|
3514
3713
|
if (symbol.valueDeclaration != null &&
|
|
@@ -3524,7 +3723,7 @@ class MatchWorker {
|
|
|
3524
3723
|
#simplifyType(type) {
|
|
3525
3724
|
if (type.isUnionOrIntersection()) {
|
|
3526
3725
|
const candidateType = this.#simplifyType(type.types[0]);
|
|
3527
|
-
if (type.types.every((type) => this
|
|
3726
|
+
if (type.types.every((type) => this.typeChecker.isTypeIdenticalTo(this.#simplifyType(type), candidateType))) {
|
|
3528
3727
|
return candidateType;
|
|
3529
3728
|
}
|
|
3530
3729
|
}
|
|
@@ -3532,6 +3731,16 @@ class MatchWorker {
|
|
|
3532
3731
|
}
|
|
3533
3732
|
}
|
|
3534
3733
|
|
|
3734
|
+
function isStringOrNumberLiteralType(compiler, type) {
|
|
3735
|
+
return !!(type.flags & compiler.TypeFlags.StringOrNumberLiteral);
|
|
3736
|
+
}
|
|
3737
|
+
function isUnionType(compiler, type) {
|
|
3738
|
+
return !!(type.flags & compiler.TypeFlags.Union);
|
|
3739
|
+
}
|
|
3740
|
+
function isUniqueSymbolType(compiler, type) {
|
|
3741
|
+
return !!(type.flags & compiler.TypeFlags.UniqueESSymbol);
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3535
3744
|
class ToAcceptProps {
|
|
3536
3745
|
#compiler;
|
|
3537
3746
|
#typeChecker;
|
|
@@ -3540,12 +3749,13 @@ class ToAcceptProps {
|
|
|
3540
3749
|
this.#typeChecker = typeChecker;
|
|
3541
3750
|
}
|
|
3542
3751
|
#explain(matchWorker, sourceNode, targetNode) {
|
|
3752
|
+
const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
|
|
3543
3753
|
const signatures = matchWorker.getSignatures(sourceNode);
|
|
3544
3754
|
return signatures.reduce((accumulator, signature, index) => {
|
|
3545
3755
|
let diagnostic;
|
|
3546
3756
|
const introText = matchWorker.assertion.isNot
|
|
3547
|
-
? ExpectDiagnosticText.
|
|
3548
|
-
: ExpectDiagnosticText.
|
|
3757
|
+
? ExpectDiagnosticText.acceptsProps(isExpression)
|
|
3758
|
+
: ExpectDiagnosticText.doesNotAcceptProps(isExpression);
|
|
3549
3759
|
const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
|
|
3550
3760
|
if (signatures.length > 1) {
|
|
3551
3761
|
const signatureText = this.#typeChecker.signatureToString(signature, sourceNode);
|
|
@@ -3565,7 +3775,7 @@ class ToAcceptProps {
|
|
|
3565
3775
|
#isOptionalProperty(symbol) {
|
|
3566
3776
|
return symbol.declarations?.every((declaration) => this.#compiler.isPropertySignature(declaration) && declaration.questionToken != null);
|
|
3567
3777
|
}
|
|
3568
|
-
#checkProperties(
|
|
3778
|
+
#checkProperties(sourceType, targetType) {
|
|
3569
3779
|
const check = (sourceType, targetType) => {
|
|
3570
3780
|
for (const targetProperty of targetType.getProperties()) {
|
|
3571
3781
|
const targetPropertyName = targetProperty.getName();
|
|
@@ -3593,7 +3803,7 @@ class ToAcceptProps {
|
|
|
3593
3803
|
}
|
|
3594
3804
|
return true;
|
|
3595
3805
|
};
|
|
3596
|
-
if (sourceType != null &&
|
|
3806
|
+
if (sourceType != null && isUnionType(this.#compiler, sourceType)) {
|
|
3597
3807
|
return sourceType.types.some((sourceType) => check(sourceType, targetType));
|
|
3598
3808
|
}
|
|
3599
3809
|
return check(sourceType, targetType);
|
|
@@ -3611,8 +3821,8 @@ class ToAcceptProps {
|
|
|
3611
3821
|
const sourceProperty = sourceType?.getProperty(targetPropertyName);
|
|
3612
3822
|
if (!sourceProperty) {
|
|
3613
3823
|
const text = [
|
|
3614
|
-
ExpectDiagnosticText.
|
|
3615
|
-
ExpectDiagnosticText.
|
|
3824
|
+
ExpectDiagnosticText.isNotCompatibleWith(sourceTypeText, targetTypeText),
|
|
3825
|
+
ExpectDiagnosticText.doesNotHaveProperty(sourceTypeText, targetPropertyName),
|
|
3616
3826
|
];
|
|
3617
3827
|
const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
|
|
3618
3828
|
diagnostics.push(diagnostic.extendWith(text, origin));
|
|
@@ -3620,8 +3830,8 @@ class ToAcceptProps {
|
|
|
3620
3830
|
}
|
|
3621
3831
|
if (this.#isOptionalProperty(targetProperty) && !this.#isOptionalProperty(sourceProperty)) {
|
|
3622
3832
|
const text = [
|
|
3623
|
-
ExpectDiagnosticText.
|
|
3624
|
-
ExpectDiagnosticText.
|
|
3833
|
+
ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
|
|
3834
|
+
ExpectDiagnosticText.requiresProperty(sourceTypeText, targetPropertyName),
|
|
3625
3835
|
];
|
|
3626
3836
|
const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
|
|
3627
3837
|
diagnostics.push(diagnostic.extendWith(text, origin));
|
|
@@ -3633,9 +3843,9 @@ class ToAcceptProps {
|
|
|
3633
3843
|
const targetPropertyTypeText = this.#typeChecker.typeToString(targetPropertyType);
|
|
3634
3844
|
const sourcePropertyTypeText = this.#typeChecker.typeToString(sourcePropertyType);
|
|
3635
3845
|
const text = [
|
|
3636
|
-
ExpectDiagnosticText.
|
|
3846
|
+
ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
|
|
3637
3847
|
ExpectDiagnosticText.typesOfPropertyAreNotCompatible(targetPropertyName),
|
|
3638
|
-
ExpectDiagnosticText.
|
|
3848
|
+
ExpectDiagnosticText.isNotAssignableWith(sourcePropertyTypeText, targetPropertyTypeText),
|
|
3639
3849
|
];
|
|
3640
3850
|
const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
|
|
3641
3851
|
diagnostics.push(diagnostic.extendWith(text, origin));
|
|
@@ -3647,26 +3857,26 @@ class ToAcceptProps {
|
|
|
3647
3857
|
const targetProperty = targetType.getProperty(sourcePropertyName);
|
|
3648
3858
|
if (!targetProperty && !this.#isOptionalProperty(sourceProperty)) {
|
|
3649
3859
|
const text = [
|
|
3650
|
-
ExpectDiagnosticText.
|
|
3651
|
-
ExpectDiagnosticText.
|
|
3860
|
+
ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
|
|
3861
|
+
ExpectDiagnosticText.requiresProperty(sourceTypeText, sourcePropertyName),
|
|
3652
3862
|
];
|
|
3653
3863
|
diagnostics.push(diagnostic.extendWith(text));
|
|
3654
3864
|
}
|
|
3655
3865
|
}
|
|
3656
3866
|
}
|
|
3657
3867
|
if (diagnostics.length === 0) {
|
|
3658
|
-
const text = ExpectDiagnosticText.
|
|
3868
|
+
const text = ExpectDiagnosticText.isAssignableWith(sourceTypeText, targetTypeText);
|
|
3659
3869
|
diagnostics.push(diagnostic.extendWith(text));
|
|
3660
3870
|
return { diagnostics, isMatch: true };
|
|
3661
3871
|
}
|
|
3662
3872
|
return { diagnostics, isMatch: false };
|
|
3663
3873
|
};
|
|
3664
|
-
if (sourceType != null &&
|
|
3874
|
+
if (sourceType != null && isUnionType(this.#compiler, sourceType)) {
|
|
3665
3875
|
let accumulator = [];
|
|
3666
3876
|
const isMatch = sourceType.types.some((sourceType) => {
|
|
3667
3877
|
const text = matchWorker.assertion.isNot
|
|
3668
|
-
? ExpectDiagnosticText.
|
|
3669
|
-
: ExpectDiagnosticText.
|
|
3878
|
+
? ExpectDiagnosticText.isAssignableWith(sourceTypeText, targetTypeText)
|
|
3879
|
+
: ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText);
|
|
3670
3880
|
const { diagnostics, isMatch } = explain(sourceType, targetType, diagnostic.extendWith(text));
|
|
3671
3881
|
if (isMatch) {
|
|
3672
3882
|
accumulator = diagnostics;
|
|
@@ -3685,18 +3895,18 @@ class ToAcceptProps {
|
|
|
3685
3895
|
const signatures = matchWorker.getSignatures(sourceNode);
|
|
3686
3896
|
if (signatures.length === 0) {
|
|
3687
3897
|
const expectedText = "of a function or class type";
|
|
3688
|
-
const text = this.#compiler
|
|
3689
|
-
? ExpectDiagnosticText.
|
|
3690
|
-
: ExpectDiagnosticText.
|
|
3898
|
+
const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
|
|
3899
|
+
? ExpectDiagnosticText.argumentMustBe("source", expectedText)
|
|
3900
|
+
: ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText);
|
|
3691
3901
|
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
3692
3902
|
diagnostics.push(Diagnostic.error(text, origin));
|
|
3693
3903
|
}
|
|
3694
3904
|
const targetType = matchWorker.getType(targetNode);
|
|
3695
|
-
if (!
|
|
3905
|
+
if (!(targetType.flags & this.#compiler.TypeFlags.Object)) {
|
|
3696
3906
|
const expectedText = "of an object type";
|
|
3697
|
-
const text = this.#compiler
|
|
3698
|
-
? ExpectDiagnosticText.
|
|
3699
|
-
: ExpectDiagnosticText.
|
|
3907
|
+
const text = nodeBelongsToArgumentList(this.#compiler, targetNode)
|
|
3908
|
+
? ExpectDiagnosticText.argumentMustBe("target", expectedText)
|
|
3909
|
+
: ExpectDiagnosticText.typeArgumentMustBe("Target", expectedText);
|
|
3700
3910
|
const origin = DiagnosticOrigin.fromNode(targetNode);
|
|
3701
3911
|
diagnostics.push(Diagnostic.error(text, origin));
|
|
3702
3912
|
}
|
|
@@ -3706,7 +3916,7 @@ class ToAcceptProps {
|
|
|
3706
3916
|
}
|
|
3707
3917
|
const isMatch = signatures.some((signature) => {
|
|
3708
3918
|
const sourceType = matchWorker.getParameterType(signature, 0);
|
|
3709
|
-
return this.#checkProperties(
|
|
3919
|
+
return this.#checkProperties(sourceType, targetType);
|
|
3710
3920
|
});
|
|
3711
3921
|
return {
|
|
3712
3922
|
explain: () => this.#explain(matchWorker, sourceNode, targetNode),
|
|
@@ -3728,8 +3938,8 @@ class RelationMatcherBase {
|
|
|
3728
3938
|
}
|
|
3729
3939
|
|
|
3730
3940
|
class ToBe extends RelationMatcherBase {
|
|
3731
|
-
explainText = ExpectDiagnosticText.
|
|
3732
|
-
explainNotText = ExpectDiagnosticText.
|
|
3941
|
+
explainText = ExpectDiagnosticText.isTheSame;
|
|
3942
|
+
explainNotText = ExpectDiagnosticText.isNotTheSame;
|
|
3733
3943
|
match(matchWorker, sourceNode, targetNode) {
|
|
3734
3944
|
return {
|
|
3735
3945
|
explain: () => this.explain(matchWorker, sourceNode, targetNode),
|
|
@@ -3738,9 +3948,74 @@ class ToBe extends RelationMatcherBase {
|
|
|
3738
3948
|
}
|
|
3739
3949
|
}
|
|
3740
3950
|
|
|
3951
|
+
class ToBeApplicable {
|
|
3952
|
+
#compiler;
|
|
3953
|
+
constructor(compiler) {
|
|
3954
|
+
this.#compiler = compiler;
|
|
3955
|
+
}
|
|
3956
|
+
#resolveTargetText(node) {
|
|
3957
|
+
let text = "";
|
|
3958
|
+
switch (node.kind) {
|
|
3959
|
+
case this.#compiler.SyntaxKind.ClassDeclaration:
|
|
3960
|
+
text = "class";
|
|
3961
|
+
break;
|
|
3962
|
+
case this.#compiler.SyntaxKind.MethodDeclaration:
|
|
3963
|
+
text = "method";
|
|
3964
|
+
break;
|
|
3965
|
+
case this.#compiler.SyntaxKind.PropertyDeclaration:
|
|
3966
|
+
text = node.modifiers?.some((modifier) => modifier.kind === this.#compiler.SyntaxKind.AccessorKeyword)
|
|
3967
|
+
? "accessor"
|
|
3968
|
+
: "field";
|
|
3969
|
+
break;
|
|
3970
|
+
case this.#compiler.SyntaxKind.GetAccessor:
|
|
3971
|
+
text = "getter";
|
|
3972
|
+
break;
|
|
3973
|
+
case this.#compiler.SyntaxKind.SetAccessor:
|
|
3974
|
+
text = "setter";
|
|
3975
|
+
break;
|
|
3976
|
+
}
|
|
3977
|
+
if (text.length > 0) {
|
|
3978
|
+
text = ` to this ${text}`;
|
|
3979
|
+
}
|
|
3980
|
+
return text;
|
|
3981
|
+
}
|
|
3982
|
+
#explain(matchWorker, sourceNode) {
|
|
3983
|
+
const targetText = this.#resolveTargetText(matchWorker.assertion.matcherNode.parent);
|
|
3984
|
+
const diagnostics = [];
|
|
3985
|
+
if (matchWorker.assertion.abilityDiagnostics) {
|
|
3986
|
+
for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
|
|
3987
|
+
const text = [ExpectDiagnosticText.cannotBeApplied(targetText), getDiagnosticMessageText(diagnostic)];
|
|
3988
|
+
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
3989
|
+
diagnostics.push(Diagnostic.error(text.flat(), origin));
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
else {
|
|
3993
|
+
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
3994
|
+
diagnostics.push(Diagnostic.error(ExpectDiagnosticText.canBeApplied(targetText), origin));
|
|
3995
|
+
}
|
|
3996
|
+
return diagnostics;
|
|
3997
|
+
}
|
|
3998
|
+
match(matchWorker, sourceNode, onDiagnostics) {
|
|
3999
|
+
const type = matchWorker.getType(sourceNode);
|
|
4000
|
+
if (type.getCallSignatures().length === 0) {
|
|
4001
|
+
const expectedText = "of a function type";
|
|
4002
|
+
const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
|
|
4003
|
+
? ExpectDiagnosticText.argumentMustBe("source", expectedText)
|
|
4004
|
+
: ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText);
|
|
4005
|
+
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
4006
|
+
onDiagnostics([Diagnostic.error(text, origin)]);
|
|
4007
|
+
return;
|
|
4008
|
+
}
|
|
4009
|
+
return {
|
|
4010
|
+
explain: () => this.#explain(matchWorker, sourceNode),
|
|
4011
|
+
isMatch: !matchWorker.assertion.abilityDiagnostics,
|
|
4012
|
+
};
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
|
|
3741
4016
|
class ToBeAssignableTo extends RelationMatcherBase {
|
|
3742
|
-
explainText = ExpectDiagnosticText.
|
|
3743
|
-
explainNotText = ExpectDiagnosticText.
|
|
4017
|
+
explainText = ExpectDiagnosticText.isAssignableTo;
|
|
4018
|
+
explainNotText = ExpectDiagnosticText.isNotAssignableTo;
|
|
3744
4019
|
match(matchWorker, sourceNode, targetNode) {
|
|
3745
4020
|
return {
|
|
3746
4021
|
explain: () => this.explain(matchWorker, sourceNode, targetNode),
|
|
@@ -3750,8 +4025,8 @@ class ToBeAssignableTo extends RelationMatcherBase {
|
|
|
3750
4025
|
}
|
|
3751
4026
|
|
|
3752
4027
|
class ToBeAssignableWith extends RelationMatcherBase {
|
|
3753
|
-
explainText = ExpectDiagnosticText.
|
|
3754
|
-
explainNotText = ExpectDiagnosticText.
|
|
4028
|
+
explainText = ExpectDiagnosticText.isAssignableWith;
|
|
4029
|
+
explainNotText = ExpectDiagnosticText.isNotAssignableWith;
|
|
3755
4030
|
match(matchWorker, sourceNode, targetNode) {
|
|
3756
4031
|
return {
|
|
3757
4032
|
explain: () => this.explain(matchWorker, sourceNode, targetNode),
|
|
@@ -3760,6 +4035,167 @@ class ToBeAssignableWith extends RelationMatcherBase {
|
|
|
3760
4035
|
}
|
|
3761
4036
|
}
|
|
3762
4037
|
|
|
4038
|
+
class ToBeCallableWith {
|
|
4039
|
+
#compiler;
|
|
4040
|
+
constructor(compiler) {
|
|
4041
|
+
this.#compiler = compiler;
|
|
4042
|
+
}
|
|
4043
|
+
#resolveTargetText(nodes) {
|
|
4044
|
+
if (nodes.length === 0) {
|
|
4045
|
+
return "without arguments";
|
|
4046
|
+
}
|
|
4047
|
+
if (nodes.length === 1 && nodes[0]?.kind === this.#compiler.SyntaxKind.SpreadElement) {
|
|
4048
|
+
return "with the given arguments";
|
|
4049
|
+
}
|
|
4050
|
+
return `with the given argument${nodes.length === 1 ? "" : "s"}`;
|
|
4051
|
+
}
|
|
4052
|
+
#explain(matchWorker, sourceNode, targetNodes) {
|
|
4053
|
+
const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
|
|
4054
|
+
const targetText = this.#resolveTargetText(targetNodes);
|
|
4055
|
+
const diagnostics = [];
|
|
4056
|
+
if (matchWorker.assertion.abilityDiagnostics) {
|
|
4057
|
+
for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
|
|
4058
|
+
const text = [
|
|
4059
|
+
ExpectDiagnosticText.isNotCallable(isExpression, targetText),
|
|
4060
|
+
getDiagnosticMessageText(diagnostic),
|
|
4061
|
+
];
|
|
4062
|
+
let origin;
|
|
4063
|
+
if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, targetNodes)) {
|
|
4064
|
+
origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceNode.getSourceFile());
|
|
4065
|
+
}
|
|
4066
|
+
else {
|
|
4067
|
+
origin =
|
|
4068
|
+
targetNodes.length > 0
|
|
4069
|
+
? DiagnosticOrigin.fromNodes(targetNodes)
|
|
4070
|
+
: DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
4071
|
+
}
|
|
4072
|
+
let related;
|
|
4073
|
+
if (diagnostic.relatedInformation != null) {
|
|
4074
|
+
related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
|
|
4075
|
+
}
|
|
4076
|
+
diagnostics.push(Diagnostic.error(text.flat(), origin).add({ related }));
|
|
4077
|
+
}
|
|
4078
|
+
}
|
|
4079
|
+
else {
|
|
4080
|
+
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
4081
|
+
diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isCallable(isExpression, targetText), origin));
|
|
4082
|
+
}
|
|
4083
|
+
return diagnostics;
|
|
4084
|
+
}
|
|
4085
|
+
match(matchWorker, sourceNode, targetNodes, onDiagnostics) {
|
|
4086
|
+
let type;
|
|
4087
|
+
if (this.#compiler.isCallExpression(sourceNode)) {
|
|
4088
|
+
type = matchWorker.typeChecker.getResolvedSignature(sourceNode)?.getReturnType();
|
|
4089
|
+
}
|
|
4090
|
+
if (this.#compiler.isArrowFunction(sourceNode) ||
|
|
4091
|
+
this.#compiler.isFunctionDeclaration(sourceNode) ||
|
|
4092
|
+
this.#compiler.isFunctionExpression(sourceNode) ||
|
|
4093
|
+
this.#compiler.isExpressionWithTypeArguments(sourceNode) ||
|
|
4094
|
+
this.#compiler.isIdentifier(sourceNode) ||
|
|
4095
|
+
this.#compiler.isPropertyAccessExpression(sourceNode)) {
|
|
4096
|
+
type = matchWorker.getType(sourceNode);
|
|
4097
|
+
}
|
|
4098
|
+
if (!type || type.getCallSignatures().length === 0) {
|
|
4099
|
+
const text = [];
|
|
4100
|
+
if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
|
|
4101
|
+
text.push(ExpectDiagnosticText.argumentMustBe("source", "a callable expression"));
|
|
4102
|
+
}
|
|
4103
|
+
else {
|
|
4104
|
+
text.push(ExpectDiagnosticText.typeArgumentMustBe("Source", "a callable type"));
|
|
4105
|
+
}
|
|
4106
|
+
if (type != null && type.getConstructSignatures().length > 0) {
|
|
4107
|
+
text.push(ExpectDiagnosticText.didYouMeanToUse("the '.toBeConstructableWith()' matcher"));
|
|
4108
|
+
}
|
|
4109
|
+
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
4110
|
+
onDiagnostics([Diagnostic.error(text, origin)]);
|
|
4111
|
+
return;
|
|
4112
|
+
}
|
|
4113
|
+
return {
|
|
4114
|
+
explain: () => this.#explain(matchWorker, sourceNode, targetNodes),
|
|
4115
|
+
isMatch: !matchWorker.assertion.abilityDiagnostics,
|
|
4116
|
+
};
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
|
|
4120
|
+
class ToBeConstructableWith {
|
|
4121
|
+
#compiler;
|
|
4122
|
+
constructor(compiler) {
|
|
4123
|
+
this.#compiler = compiler;
|
|
4124
|
+
}
|
|
4125
|
+
#resolveTargetText(nodes) {
|
|
4126
|
+
if (nodes.length === 0) {
|
|
4127
|
+
return "without arguments";
|
|
4128
|
+
}
|
|
4129
|
+
if (nodes.length === 1 && nodes[0]?.kind === this.#compiler.SyntaxKind.SpreadElement) {
|
|
4130
|
+
return "with the given arguments";
|
|
4131
|
+
}
|
|
4132
|
+
return `with the given argument${nodes.length === 1 ? "" : "s"}`;
|
|
4133
|
+
}
|
|
4134
|
+
#explain(matchWorker, sourceNode, targetNodes) {
|
|
4135
|
+
const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
|
|
4136
|
+
const targetText = this.#resolveTargetText(targetNodes);
|
|
4137
|
+
const diagnostics = [];
|
|
4138
|
+
if (matchWorker.assertion.abilityDiagnostics) {
|
|
4139
|
+
for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
|
|
4140
|
+
const text = [
|
|
4141
|
+
ExpectDiagnosticText.isNotConstructable(isExpression, targetText),
|
|
4142
|
+
getDiagnosticMessageText(diagnostic),
|
|
4143
|
+
];
|
|
4144
|
+
let origin;
|
|
4145
|
+
if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, targetNodes)) {
|
|
4146
|
+
origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceNode.getSourceFile());
|
|
4147
|
+
}
|
|
4148
|
+
else {
|
|
4149
|
+
origin =
|
|
4150
|
+
targetNodes.length > 0
|
|
4151
|
+
? DiagnosticOrigin.fromNodes(targetNodes)
|
|
4152
|
+
: DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
4153
|
+
}
|
|
4154
|
+
let related;
|
|
4155
|
+
if (diagnostic.relatedInformation != null) {
|
|
4156
|
+
related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
|
|
4157
|
+
}
|
|
4158
|
+
diagnostics.push(Diagnostic.error(text.flat(), origin).add({ related }));
|
|
4159
|
+
}
|
|
4160
|
+
}
|
|
4161
|
+
else {
|
|
4162
|
+
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
4163
|
+
diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isConstructable(isExpression, targetText), origin));
|
|
4164
|
+
}
|
|
4165
|
+
return diagnostics;
|
|
4166
|
+
}
|
|
4167
|
+
match(matchWorker, sourceNode, targetNodes, onDiagnostics) {
|
|
4168
|
+
let type;
|
|
4169
|
+
if (this.#compiler.isCallExpression(sourceNode)) {
|
|
4170
|
+
type = matchWorker.typeChecker.getResolvedSignature(sourceNode)?.getReturnType();
|
|
4171
|
+
}
|
|
4172
|
+
if (this.#compiler.isExpressionWithTypeArguments(sourceNode) ||
|
|
4173
|
+
this.#compiler.isIdentifier(sourceNode) ||
|
|
4174
|
+
this.#compiler.isPropertyAccessExpression(sourceNode)) {
|
|
4175
|
+
type = matchWorker.getType(sourceNode);
|
|
4176
|
+
}
|
|
4177
|
+
if (!type || type.getConstructSignatures().length === 0) {
|
|
4178
|
+
const text = [];
|
|
4179
|
+
if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
|
|
4180
|
+
text.push(ExpectDiagnosticText.argumentMustBe("source", "a constructable expression"));
|
|
4181
|
+
}
|
|
4182
|
+
else {
|
|
4183
|
+
text.push(ExpectDiagnosticText.typeArgumentMustBe("Source", "a constructable type"));
|
|
4184
|
+
}
|
|
4185
|
+
if (type != null && type.getCallSignatures().length > 0) {
|
|
4186
|
+
text.push(ExpectDiagnosticText.didYouMeanToUse("the '.toBeCallableWith()' matcher"));
|
|
4187
|
+
}
|
|
4188
|
+
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
4189
|
+
onDiagnostics([Diagnostic.error(text, origin)]);
|
|
4190
|
+
return;
|
|
4191
|
+
}
|
|
4192
|
+
return {
|
|
4193
|
+
explain: () => this.#explain(matchWorker, sourceNode, targetNodes),
|
|
4194
|
+
isMatch: !matchWorker.assertion.abilityDiagnostics,
|
|
4195
|
+
};
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4198
|
+
|
|
3763
4199
|
class ToHaveProperty {
|
|
3764
4200
|
#compiler;
|
|
3765
4201
|
constructor(compiler) {
|
|
@@ -3769,7 +4205,7 @@ class ToHaveProperty {
|
|
|
3769
4205
|
const sourceTypeText = matchWorker.getTypeText(sourceNode);
|
|
3770
4206
|
const targetType = matchWorker.getType(targetNode);
|
|
3771
4207
|
let propertyNameText;
|
|
3772
|
-
if (
|
|
4208
|
+
if (isStringOrNumberLiteralType(this.#compiler, targetType)) {
|
|
3773
4209
|
propertyNameText = targetType.value.toString();
|
|
3774
4210
|
}
|
|
3775
4211
|
else {
|
|
@@ -3777,8 +4213,8 @@ class ToHaveProperty {
|
|
|
3777
4213
|
}
|
|
3778
4214
|
const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
|
|
3779
4215
|
return matchWorker.assertion.isNot
|
|
3780
|
-
? [Diagnostic.error(ExpectDiagnosticText.
|
|
3781
|
-
: [Diagnostic.error(ExpectDiagnosticText.
|
|
4216
|
+
? [Diagnostic.error(ExpectDiagnosticText.hasProperty(sourceTypeText, propertyNameText), origin)]
|
|
4217
|
+
: [Diagnostic.error(ExpectDiagnosticText.doesNotHaveProperty(sourceTypeText, propertyNameText), origin)];
|
|
3782
4218
|
}
|
|
3783
4219
|
match(matchWorker, sourceNode, targetNode, onDiagnostics) {
|
|
3784
4220
|
const diagnostics = [];
|
|
@@ -3786,18 +4222,18 @@ class ToHaveProperty {
|
|
|
3786
4222
|
if (sourceType.flags & (this.#compiler.TypeFlags.Any | this.#compiler.TypeFlags.Never) ||
|
|
3787
4223
|
!matchWorker.extendsObjectType(sourceType)) {
|
|
3788
4224
|
const expectedText = "of an object type";
|
|
3789
|
-
const text = this.#compiler
|
|
3790
|
-
? ExpectDiagnosticText.
|
|
3791
|
-
: ExpectDiagnosticText.
|
|
4225
|
+
const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
|
|
4226
|
+
? ExpectDiagnosticText.argumentMustBe("source", expectedText)
|
|
4227
|
+
: ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText);
|
|
3792
4228
|
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
3793
4229
|
diagnostics.push(Diagnostic.error(text, origin));
|
|
3794
4230
|
}
|
|
3795
4231
|
const targetType = matchWorker.getType(targetNode);
|
|
3796
4232
|
let propertyNameText = "";
|
|
3797
|
-
if (
|
|
4233
|
+
if (isStringOrNumberLiteralType(this.#compiler, targetType)) {
|
|
3798
4234
|
propertyNameText = targetType.value.toString();
|
|
3799
4235
|
}
|
|
3800
|
-
else if (
|
|
4236
|
+
else if (isUniqueSymbolType(this.#compiler, targetType)) {
|
|
3801
4237
|
propertyNameText = this.#compiler.unescapeLeadingUnderscores(targetType.escapedName);
|
|
3802
4238
|
}
|
|
3803
4239
|
else {
|
|
@@ -3825,15 +4261,15 @@ class ToRaiseError {
|
|
|
3825
4261
|
this.#compiler = compiler;
|
|
3826
4262
|
}
|
|
3827
4263
|
#explain(matchWorker, sourceNode, targetNodes) {
|
|
3828
|
-
const
|
|
4264
|
+
const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
|
|
3829
4265
|
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
3830
4266
|
if (matchWorker.assertion.diagnostics.size === 0) {
|
|
3831
|
-
const text = ExpectDiagnosticText.
|
|
4267
|
+
const text = ExpectDiagnosticText.didNotRaiseError(isExpression);
|
|
3832
4268
|
return [Diagnostic.error(text, origin)];
|
|
3833
4269
|
}
|
|
3834
4270
|
if (matchWorker.assertion.diagnostics.size !== targetNodes.length) {
|
|
3835
4271
|
const count = matchWorker.assertion.diagnostics.size;
|
|
3836
|
-
const text = ExpectDiagnosticText.
|
|
4272
|
+
const text = ExpectDiagnosticText.raisedError(isExpression, count, targetNodes.length);
|
|
3837
4273
|
const related = [
|
|
3838
4274
|
Diagnostic.error(ExpectDiagnosticText.raisedTypeError(count)),
|
|
3839
4275
|
...Diagnostic.fromDiagnostics([...matchWorker.assertion.diagnostics]),
|
|
@@ -3845,8 +4281,8 @@ class ToRaiseError {
|
|
|
3845
4281
|
const isMatch = this.#matchExpectedError(diagnostic, targetNode);
|
|
3846
4282
|
if (matchWorker.assertion.isNot ? isMatch : !isMatch) {
|
|
3847
4283
|
const text = matchWorker.assertion.isNot
|
|
3848
|
-
? ExpectDiagnosticText.
|
|
3849
|
-
: ExpectDiagnosticText.
|
|
4284
|
+
? ExpectDiagnosticText.raisedMatchingError(isExpression)
|
|
4285
|
+
: ExpectDiagnosticText.didNotRaiseMatchingError(isExpression);
|
|
3850
4286
|
const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
|
|
3851
4287
|
const related = [
|
|
3852
4288
|
Diagnostic.error(ExpectDiagnosticText.raisedTypeError()),
|
|
@@ -3891,9 +4327,10 @@ class ToRaiseError {
|
|
|
3891
4327
|
if (this.#compiler.isNumericLiteral(targetNode)) {
|
|
3892
4328
|
return Number.parseInt(targetNode.text, 10) === diagnostic.code;
|
|
3893
4329
|
}
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
4330
|
+
let messageText = getDiagnosticMessageText(diagnostic);
|
|
4331
|
+
if (Array.isArray(messageText)) {
|
|
4332
|
+
messageText = messageText.join("\n");
|
|
4333
|
+
}
|
|
3897
4334
|
if (this.#compiler.isRegularExpressionLiteral(targetNode)) {
|
|
3898
4335
|
const targetRegex = new RegExp(...targetNode.text.slice(1).split("/"));
|
|
3899
4336
|
return targetRegex.test(messageText);
|
|
@@ -3904,61 +4341,64 @@ class ToRaiseError {
|
|
|
3904
4341
|
|
|
3905
4342
|
class ExpectService {
|
|
3906
4343
|
#compiler;
|
|
3907
|
-
#
|
|
4344
|
+
#reject;
|
|
3908
4345
|
#typeChecker;
|
|
3909
4346
|
toAcceptProps;
|
|
3910
4347
|
toBe;
|
|
4348
|
+
toBeApplicable;
|
|
3911
4349
|
toBeAssignableTo;
|
|
3912
4350
|
toBeAssignableWith;
|
|
4351
|
+
toBeCallableWith;
|
|
4352
|
+
toBeConstructableWith;
|
|
3913
4353
|
toHaveProperty;
|
|
3914
4354
|
toRaiseError;
|
|
3915
|
-
constructor(compiler, typeChecker,
|
|
4355
|
+
constructor(compiler, typeChecker, reject) {
|
|
3916
4356
|
this.#compiler = compiler;
|
|
4357
|
+
this.#reject = reject;
|
|
3917
4358
|
this.#typeChecker = typeChecker;
|
|
3918
|
-
if (resolvedConfig?.rejectAnyType) {
|
|
3919
|
-
this.#rejectTypes.add("any");
|
|
3920
|
-
}
|
|
3921
|
-
if (resolvedConfig?.rejectNeverType) {
|
|
3922
|
-
this.#rejectTypes.add("never");
|
|
3923
|
-
}
|
|
3924
4359
|
this.toAcceptProps = new ToAcceptProps(compiler, typeChecker);
|
|
3925
4360
|
this.toBe = new ToBe();
|
|
4361
|
+
this.toBeApplicable = new ToBeApplicable(compiler);
|
|
3926
4362
|
this.toBeAssignableTo = new ToBeAssignableTo();
|
|
3927
4363
|
this.toBeAssignableWith = new ToBeAssignableWith();
|
|
4364
|
+
this.toBeCallableWith = new ToBeCallableWith(compiler);
|
|
4365
|
+
this.toBeConstructableWith = new ToBeConstructableWith(compiler);
|
|
3928
4366
|
this.toHaveProperty = new ToHaveProperty(compiler);
|
|
3929
4367
|
this.toRaiseError = new ToRaiseError(compiler);
|
|
3930
4368
|
}
|
|
3931
4369
|
match(assertion, onDiagnostics) {
|
|
3932
|
-
const matcherNameText = assertion.
|
|
3933
|
-
if (!assertion.source[0]) {
|
|
3934
|
-
this.#onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
|
|
4370
|
+
const matcherNameText = assertion.matcherNameNode.name.text;
|
|
4371
|
+
if (!argumentOrTypeArgumentIsProvided("source", "Source", assertion.source[0], assertion.node.expression, onDiagnostics)) {
|
|
3935
4372
|
return;
|
|
3936
4373
|
}
|
|
3937
4374
|
const matchWorker = new MatchWorker(this.#compiler, this.#typeChecker, assertion);
|
|
4375
|
+
if (!(matcherNameText === "toRaiseError" && assertion.isNot === false) &&
|
|
4376
|
+
this.#reject.argumentType([
|
|
4377
|
+
["source", assertion.source[0]],
|
|
4378
|
+
["target", assertion.target?.[0]],
|
|
4379
|
+
], onDiagnostics)) {
|
|
4380
|
+
return;
|
|
4381
|
+
}
|
|
3938
4382
|
switch (matcherNameText) {
|
|
3939
4383
|
case "toAcceptProps":
|
|
3940
4384
|
case "toBe":
|
|
3941
4385
|
case "toBeAssignableTo":
|
|
3942
4386
|
case "toBeAssignableWith":
|
|
3943
|
-
if (!assertion.target[0]) {
|
|
3944
|
-
this.#onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
|
|
3945
|
-
return;
|
|
3946
|
-
}
|
|
3947
|
-
if (this.#rejectsTypeArguments(matchWorker, onDiagnostics)) {
|
|
4387
|
+
if (!argumentOrTypeArgumentIsProvided("target", "Target", assertion.target?.[0], assertion.matcherNameNode.name, onDiagnostics)) {
|
|
3948
4388
|
return;
|
|
3949
4389
|
}
|
|
3950
4390
|
return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
|
|
4391
|
+
case "toBeApplicable":
|
|
4392
|
+
return this.toBeApplicable.match(matchWorker, assertion.source[0], onDiagnostics);
|
|
4393
|
+
case "toBeCallableWith":
|
|
4394
|
+
case "toBeConstructableWith":
|
|
4395
|
+
case "toRaiseError":
|
|
4396
|
+
return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target, onDiagnostics);
|
|
3951
4397
|
case "toHaveProperty":
|
|
3952
|
-
if (!assertion.target[0]) {
|
|
3953
|
-
this.#onTargetArgumentMustBeProvided("key", assertion, onDiagnostics);
|
|
4398
|
+
if (!argumentIsProvided("key", assertion.target?.[0], assertion.matcherNameNode.name, onDiagnostics)) {
|
|
3954
4399
|
return;
|
|
3955
4400
|
}
|
|
3956
4401
|
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
4402
|
default:
|
|
3963
4403
|
this.#onMatcherIsNotSupported(matcherNameText, assertion, onDiagnostics);
|
|
3964
4404
|
}
|
|
@@ -3966,45 +4406,64 @@ class ExpectService {
|
|
|
3966
4406
|
}
|
|
3967
4407
|
#onMatcherIsNotSupported(matcherNameText, assertion, onDiagnostics) {
|
|
3968
4408
|
const text = ExpectDiagnosticText.matcherIsNotSupported(matcherNameText);
|
|
3969
|
-
const origin = DiagnosticOrigin.fromNode(assertion.
|
|
4409
|
+
const origin = DiagnosticOrigin.fromNode(assertion.matcherNameNode.name);
|
|
3970
4410
|
onDiagnostics(Diagnostic.error(text, origin));
|
|
3971
4411
|
}
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
4412
|
+
}
|
|
4413
|
+
|
|
4414
|
+
function capitalize(text) {
|
|
4415
|
+
return text.replace(/^./, text.charAt(0).toUpperCase());
|
|
4416
|
+
}
|
|
4417
|
+
|
|
4418
|
+
class RejectDiagnosticText {
|
|
4419
|
+
static argumentCannotBeOfType(argumentNameText, typeText) {
|
|
4420
|
+
return `An argument for '${argumentNameText}' cannot be of the '${typeText}' type.`;
|
|
3976
4421
|
}
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
|
|
3980
|
-
onDiagnostics(Diagnostic.error(text, origin));
|
|
4422
|
+
static typeArgumentCannotBeOfType(argumentNameText, typeText) {
|
|
4423
|
+
return `A type argument for '${argumentNameText}' cannot be of the '${typeText}' type.`;
|
|
3981
4424
|
}
|
|
3982
|
-
|
|
3983
|
-
const
|
|
3984
|
-
|
|
3985
|
-
|
|
4425
|
+
static typeWasRejected(typeText) {
|
|
4426
|
+
const optionNameText = `reject${capitalize(typeText)}Type`;
|
|
4427
|
+
return [
|
|
4428
|
+
`The '${typeText}' type was rejected because the '${optionNameText}' option is enabled.`,
|
|
4429
|
+
`If this check is necessary, pass '${typeText}' as the type argument explicitly.`,
|
|
4430
|
+
];
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
|
|
4434
|
+
class Reject {
|
|
4435
|
+
#compiler;
|
|
4436
|
+
#rejectedArgumentTypes = new Set();
|
|
4437
|
+
#typeChecker;
|
|
4438
|
+
constructor(compiler, typeChecker, resolvedConfig) {
|
|
4439
|
+
this.#compiler = compiler;
|
|
4440
|
+
this.#typeChecker = typeChecker;
|
|
4441
|
+
if (resolvedConfig?.rejectAnyType) {
|
|
4442
|
+
this.#rejectedArgumentTypes.add("any");
|
|
4443
|
+
}
|
|
4444
|
+
if (resolvedConfig?.rejectNeverType) {
|
|
4445
|
+
this.#rejectedArgumentTypes.add("never");
|
|
4446
|
+
}
|
|
3986
4447
|
}
|
|
3987
|
-
|
|
3988
|
-
for (const rejectedType of this.#
|
|
3989
|
-
const allowedKeyword = this.#compiler.SyntaxKind[`${
|
|
3990
|
-
if (
|
|
3991
|
-
matchWorker.assertion.target[0]?.kind === allowedKeyword) {
|
|
4448
|
+
argumentType(target, onDiagnostics) {
|
|
4449
|
+
for (const rejectedType of this.#rejectedArgumentTypes) {
|
|
4450
|
+
const allowedKeyword = this.#compiler.SyntaxKind[`${capitalize(rejectedType)}Keyword`];
|
|
4451
|
+
if (target.some(([, node]) => node?.kind === allowedKeyword)) {
|
|
3992
4452
|
continue;
|
|
3993
4453
|
}
|
|
3994
|
-
for (const
|
|
3995
|
-
|
|
3996
|
-
if (!argumentNode) {
|
|
4454
|
+
for (const [name, node] of target) {
|
|
4455
|
+
if (!node) {
|
|
3997
4456
|
continue;
|
|
3998
4457
|
}
|
|
3999
|
-
if (
|
|
4458
|
+
if (this.#typeChecker.getTypeAtLocation(node).flags & this.#compiler.TypeFlags[capitalize(rejectedType)]) {
|
|
4000
4459
|
const text = [
|
|
4001
|
-
this.#compiler
|
|
4002
|
-
?
|
|
4003
|
-
:
|
|
4004
|
-
...
|
|
4460
|
+
nodeBelongsToArgumentList(this.#compiler, node)
|
|
4461
|
+
? RejectDiagnosticText.argumentCannotBeOfType(name, rejectedType)
|
|
4462
|
+
: RejectDiagnosticText.typeArgumentCannotBeOfType(capitalize(name), rejectedType),
|
|
4463
|
+
...RejectDiagnosticText.typeWasRejected(rejectedType),
|
|
4005
4464
|
];
|
|
4006
|
-
const origin = DiagnosticOrigin.fromNode(
|
|
4007
|
-
onDiagnostics(Diagnostic.error(text, origin));
|
|
4465
|
+
const origin = DiagnosticOrigin.fromNode(node);
|
|
4466
|
+
onDiagnostics([Diagnostic.error(text, origin)]);
|
|
4008
4467
|
return true;
|
|
4009
4468
|
}
|
|
4010
4469
|
}
|
|
@@ -4013,20 +4472,78 @@ class ExpectService {
|
|
|
4013
4472
|
}
|
|
4014
4473
|
}
|
|
4015
4474
|
|
|
4475
|
+
class WhenDiagnosticText {
|
|
4476
|
+
static actionIsNotSupported(actionNameText) {
|
|
4477
|
+
return `The '.${actionNameText}()' action is not supported.`;
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
|
|
4481
|
+
class WhenService {
|
|
4482
|
+
#onDiagnostics;
|
|
4483
|
+
#reject;
|
|
4484
|
+
constructor(reject, onDiagnostics) {
|
|
4485
|
+
this.#reject = reject;
|
|
4486
|
+
this.#onDiagnostics = onDiagnostics;
|
|
4487
|
+
}
|
|
4488
|
+
action(when) {
|
|
4489
|
+
if (!argumentIsProvided("target", when.target[0], when.node.expression, this.#onDiagnostics) ||
|
|
4490
|
+
this.#reject.argumentType([["target", when.target[0]]], this.#onDiagnostics)) {
|
|
4491
|
+
return;
|
|
4492
|
+
}
|
|
4493
|
+
const actionNameText = when.actionNameNode.name.getText();
|
|
4494
|
+
switch (actionNameText) {
|
|
4495
|
+
case "isCalledWith":
|
|
4496
|
+
break;
|
|
4497
|
+
default:
|
|
4498
|
+
this.#onActionIsNotSupported(actionNameText, when, this.#onDiagnostics);
|
|
4499
|
+
return;
|
|
4500
|
+
}
|
|
4501
|
+
if (when.abilityDiagnostics != null && when.abilityDiagnostics.size > 0) {
|
|
4502
|
+
const diagnostics = [];
|
|
4503
|
+
for (const diagnostic of when.abilityDiagnostics) {
|
|
4504
|
+
if (isDiagnosticWithLocation(diagnostic)) {
|
|
4505
|
+
const text = getDiagnosticMessageText(diagnostic);
|
|
4506
|
+
let origin;
|
|
4507
|
+
if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, when.node)) {
|
|
4508
|
+
origin = DiagnosticOrigin.fromNodes(when.target);
|
|
4509
|
+
}
|
|
4510
|
+
else {
|
|
4511
|
+
origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), when.node.getSourceFile());
|
|
4512
|
+
}
|
|
4513
|
+
let related;
|
|
4514
|
+
if (diagnostic.relatedInformation != null) {
|
|
4515
|
+
related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
|
|
4516
|
+
}
|
|
4517
|
+
diagnostics.push(Diagnostic.error(text, origin).add({ related }));
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4520
|
+
this.#onDiagnostics(diagnostics);
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4523
|
+
#onActionIsNotSupported(actionNameText, when, onDiagnostics) {
|
|
4524
|
+
const text = WhenDiagnosticText.actionIsNotSupported(actionNameText);
|
|
4525
|
+
const origin = DiagnosticOrigin.fromNode(when.actionNameNode.name);
|
|
4526
|
+
onDiagnostics([Diagnostic.error(text, origin)]);
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
|
|
4016
4530
|
class TestTreeWalker {
|
|
4017
4531
|
#cancellationToken;
|
|
4018
4532
|
#expectService;
|
|
4019
4533
|
#hasOnly;
|
|
4534
|
+
#onTaskDiagnostics;
|
|
4020
4535
|
#position;
|
|
4021
4536
|
#resolvedConfig;
|
|
4022
|
-
#
|
|
4023
|
-
constructor(
|
|
4537
|
+
#whenService;
|
|
4538
|
+
constructor(compiler, typeChecker, resolvedConfig, onTaskDiagnostics, options) {
|
|
4024
4539
|
this.#resolvedConfig = resolvedConfig;
|
|
4540
|
+
this.#onTaskDiagnostics = onTaskDiagnostics;
|
|
4025
4541
|
this.#cancellationToken = options.cancellationToken;
|
|
4026
4542
|
this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
|
|
4027
4543
|
this.#position = options.position;
|
|
4028
|
-
|
|
4029
|
-
this.#expectService = new ExpectService(compiler, typeChecker,
|
|
4544
|
+
const reject = new Reject(compiler, typeChecker, resolvedConfig);
|
|
4545
|
+
this.#expectService = new ExpectService(compiler, typeChecker, reject);
|
|
4546
|
+
this.#whenService = new WhenService(reject, onTaskDiagnostics);
|
|
4030
4547
|
}
|
|
4031
4548
|
#resolveRunMode(mode, testNode) {
|
|
4032
4549
|
if (testNode.flags & TestTreeNodeFlags.Fail) {
|
|
@@ -4051,25 +4568,23 @@ class TestTreeWalker {
|
|
|
4051
4568
|
}
|
|
4052
4569
|
return mode;
|
|
4053
4570
|
}
|
|
4054
|
-
visit(
|
|
4055
|
-
for (const
|
|
4571
|
+
visit(nodes, runMode, parentResult) {
|
|
4572
|
+
for (const node of nodes) {
|
|
4056
4573
|
if (this.#cancellationToken?.isCancellationRequested) {
|
|
4057
4574
|
break;
|
|
4058
4575
|
}
|
|
4059
|
-
|
|
4060
|
-
if (validationError.length > 0) {
|
|
4061
|
-
EventEmitter.dispatch(["task:error", { diagnostics: validationError, result: this.#taskResult }]);
|
|
4062
|
-
break;
|
|
4063
|
-
}
|
|
4064
|
-
switch (testNode.brand) {
|
|
4576
|
+
switch (node.brand) {
|
|
4065
4577
|
case TestTreeNodeBrand.Describe:
|
|
4066
|
-
this.#visitDescribe(
|
|
4578
|
+
this.#visitDescribe(node, runMode, parentResult);
|
|
4067
4579
|
break;
|
|
4068
4580
|
case TestTreeNodeBrand.Test:
|
|
4069
|
-
this.#visitTest(
|
|
4581
|
+
this.#visitTest(node, runMode, parentResult);
|
|
4070
4582
|
break;
|
|
4071
4583
|
case TestTreeNodeBrand.Expect:
|
|
4072
|
-
this.#visitAssertion(
|
|
4584
|
+
this.#visitAssertion(node, runMode, parentResult);
|
|
4585
|
+
break;
|
|
4586
|
+
case TestTreeNodeBrand.When:
|
|
4587
|
+
this.#visitWhen(node);
|
|
4073
4588
|
break;
|
|
4074
4589
|
}
|
|
4075
4590
|
}
|
|
@@ -4089,7 +4604,7 @@ class TestTreeWalker {
|
|
|
4089
4604
|
{ diagnostics: Array.isArray(diagnostics) ? diagnostics : [diagnostics], result: expectResult },
|
|
4090
4605
|
]);
|
|
4091
4606
|
};
|
|
4092
|
-
if (assertion.diagnostics.size > 0 && assertion.
|
|
4607
|
+
if (assertion.diagnostics.size > 0 && assertion.matcherNameNode.name.text !== "toRaiseError") {
|
|
4093
4608
|
onExpectDiagnostics(Diagnostic.fromDiagnostics([...assertion.diagnostics]));
|
|
4094
4609
|
return;
|
|
4095
4610
|
}
|
|
@@ -4120,13 +4635,7 @@ class TestTreeWalker {
|
|
|
4120
4635
|
runMode = this.#resolveRunMode(runMode, describe);
|
|
4121
4636
|
if (!(runMode & RunMode.Skip || (this.#hasOnly && !(runMode & RunMode.Only)) || runMode & RunMode.Todo) &&
|
|
4122
4637
|
describe.diagnostics.size > 0) {
|
|
4123
|
-
|
|
4124
|
-
"task:error",
|
|
4125
|
-
{
|
|
4126
|
-
diagnostics: Diagnostic.fromDiagnostics([...describe.diagnostics]),
|
|
4127
|
-
result: this.#taskResult,
|
|
4128
|
-
},
|
|
4129
|
-
]);
|
|
4638
|
+
this.#onTaskDiagnostics(Diagnostic.fromDiagnostics([...describe.diagnostics]));
|
|
4130
4639
|
}
|
|
4131
4640
|
else {
|
|
4132
4641
|
this.visit(describe.children, runMode, describeResult);
|
|
@@ -4163,71 +4672,86 @@ class TestTreeWalker {
|
|
|
4163
4672
|
EventEmitter.dispatch(["test:pass", { result: testResult }]);
|
|
4164
4673
|
}
|
|
4165
4674
|
}
|
|
4675
|
+
#visitWhen(when) {
|
|
4676
|
+
this.#whenService.action(when);
|
|
4677
|
+
}
|
|
4166
4678
|
}
|
|
4167
4679
|
|
|
4168
4680
|
class TaskRunner {
|
|
4169
|
-
#compiler;
|
|
4170
4681
|
#collectService;
|
|
4682
|
+
#compiler;
|
|
4171
4683
|
#resolvedConfig;
|
|
4172
4684
|
#projectService;
|
|
4173
|
-
constructor(
|
|
4174
|
-
this.#resolvedConfig = resolvedConfig;
|
|
4685
|
+
constructor(compiler, resolvedConfig) {
|
|
4175
4686
|
this.#compiler = compiler;
|
|
4176
|
-
this.#
|
|
4177
|
-
this.#projectService = new ProjectService(this.#resolvedConfig
|
|
4687
|
+
this.#resolvedConfig = resolvedConfig;
|
|
4688
|
+
this.#projectService = new ProjectService(compiler, this.#resolvedConfig);
|
|
4689
|
+
this.#collectService = new CollectService(compiler, this.#projectService, this.#resolvedConfig);
|
|
4178
4690
|
}
|
|
4179
|
-
|
|
4180
|
-
|
|
4691
|
+
#onDiagnostics(diagnostics, result) {
|
|
4692
|
+
EventEmitter.dispatch(["task:error", { diagnostics, result }]);
|
|
4693
|
+
}
|
|
4694
|
+
async run(task, cancellationToken) {
|
|
4695
|
+
if (cancellationToken.isCancellationRequested) {
|
|
4181
4696
|
return;
|
|
4182
4697
|
}
|
|
4183
4698
|
this.#projectService.openFile(task.filePath, undefined, this.#resolvedConfig.rootPath);
|
|
4184
4699
|
const taskResult = new TaskResult(task);
|
|
4185
4700
|
EventEmitter.dispatch(["task:start", { result: taskResult }]);
|
|
4186
|
-
this.#run(task, taskResult, cancellationToken);
|
|
4701
|
+
await this.#run(task, taskResult, cancellationToken);
|
|
4187
4702
|
EventEmitter.dispatch(["task:end", { result: taskResult }]);
|
|
4188
4703
|
this.#projectService.closeFile(task.filePath);
|
|
4189
4704
|
}
|
|
4190
|
-
#run(task, taskResult, cancellationToken) {
|
|
4705
|
+
async #run(task, taskResult, cancellationToken) {
|
|
4191
4706
|
if (!existsSync(task.filePath)) {
|
|
4192
|
-
|
|
4193
|
-
"task:error",
|
|
4194
|
-
{ diagnostics: [Diagnostic.error(`Test file '${task.filePath}' does not exist.`)], result: taskResult },
|
|
4195
|
-
]);
|
|
4196
|
-
return;
|
|
4197
|
-
}
|
|
4198
|
-
const languageService = this.#projectService.getLanguageService(task.filePath);
|
|
4199
|
-
if (!languageService) {
|
|
4707
|
+
this.#onDiagnostics([Diagnostic.error(`Test file '${task.filePath}' does not exist.`)], taskResult);
|
|
4200
4708
|
return;
|
|
4201
4709
|
}
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
{ diagnostics: Diagnostic.fromDiagnostics(syntacticDiagnostics), result: taskResult },
|
|
4207
|
-
]);
|
|
4710
|
+
let languageService = this.#projectService.getLanguageService(task.filePath);
|
|
4711
|
+
const syntacticDiagnostics = languageService?.getSyntacticDiagnostics(task.filePath);
|
|
4712
|
+
if (syntacticDiagnostics != null && syntacticDiagnostics.length > 0) {
|
|
4713
|
+
this.#onDiagnostics(Diagnostic.fromDiagnostics(syntacticDiagnostics), taskResult);
|
|
4208
4714
|
return;
|
|
4209
4715
|
}
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4716
|
+
let semanticDiagnostics = languageService?.getSemanticDiagnostics(task.filePath);
|
|
4717
|
+
let program = languageService?.getProgram();
|
|
4718
|
+
let sourceFile = program?.getSourceFile(task.filePath);
|
|
4719
|
+
if (sourceFile?.text.startsWith("// @tstyche-template")) {
|
|
4720
|
+
if (semanticDiagnostics != null && semanticDiagnostics.length > 0) {
|
|
4721
|
+
this.#onDiagnostics(Diagnostic.fromDiagnostics(semanticDiagnostics), taskResult);
|
|
4722
|
+
return;
|
|
4723
|
+
}
|
|
4724
|
+
const moduleSpecifier = pathToFileURL(task.filePath).toString();
|
|
4725
|
+
const testText = (await import(moduleSpecifier))?.default;
|
|
4726
|
+
if (typeof testText !== "string") {
|
|
4727
|
+
this.#onDiagnostics([Diagnostic.error("A template test file must export a string.")], taskResult);
|
|
4728
|
+
return;
|
|
4729
|
+
}
|
|
4730
|
+
this.#projectService.openFile(task.filePath, testText, this.#resolvedConfig.rootPath);
|
|
4731
|
+
languageService = this.#projectService.getLanguageService(task.filePath);
|
|
4732
|
+
const syntacticDiagnostics = languageService?.getSyntacticDiagnostics(task.filePath);
|
|
4733
|
+
if (syntacticDiagnostics != null && syntacticDiagnostics.length > 0) {
|
|
4734
|
+
this.#onDiagnostics(Diagnostic.fromDiagnostics(syntacticDiagnostics), taskResult);
|
|
4735
|
+
return;
|
|
4736
|
+
}
|
|
4737
|
+
semanticDiagnostics = languageService?.getSemanticDiagnostics(task.filePath);
|
|
4738
|
+
program = languageService?.getProgram();
|
|
4739
|
+
sourceFile = program?.getSourceFile(task.filePath);
|
|
4214
4740
|
}
|
|
4215
|
-
const sourceFile = program.getSourceFile(task.filePath);
|
|
4216
4741
|
if (!sourceFile) {
|
|
4217
4742
|
return;
|
|
4218
4743
|
}
|
|
4219
4744
|
const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics);
|
|
4220
4745
|
if (testTree.diagnostics.size > 0) {
|
|
4221
|
-
|
|
4222
|
-
"task:error",
|
|
4223
|
-
{ diagnostics: Diagnostic.fromDiagnostics([...testTree.diagnostics]), result: taskResult },
|
|
4224
|
-
]);
|
|
4746
|
+
this.#onDiagnostics(Diagnostic.fromDiagnostics([...testTree.diagnostics]), taskResult);
|
|
4225
4747
|
return;
|
|
4226
4748
|
}
|
|
4227
|
-
const typeChecker = program
|
|
4228
|
-
const
|
|
4749
|
+
const typeChecker = program?.getTypeChecker();
|
|
4750
|
+
const onTaskDiagnostics = (diagnostics) => {
|
|
4751
|
+
this.#onDiagnostics(diagnostics, taskResult);
|
|
4752
|
+
};
|
|
4753
|
+
const testTreeWalker = new TestTreeWalker(this.#compiler, typeChecker, this.#resolvedConfig, onTaskDiagnostics, {
|
|
4229
4754
|
cancellationToken,
|
|
4230
|
-
taskResult,
|
|
4231
4755
|
hasOnly: testTree.hasOnly,
|
|
4232
4756
|
position: task.position,
|
|
4233
4757
|
});
|
|
@@ -4238,15 +4762,13 @@ class TaskRunner {
|
|
|
4238
4762
|
class Runner {
|
|
4239
4763
|
#eventEmitter = new EventEmitter();
|
|
4240
4764
|
#resolvedConfig;
|
|
4241
|
-
static version = "4.0.0-beta.
|
|
4765
|
+
static version = "4.0.0-beta.10";
|
|
4242
4766
|
constructor(resolvedConfig) {
|
|
4243
4767
|
this.#resolvedConfig = resolvedConfig;
|
|
4244
4768
|
}
|
|
4245
4769
|
#addHandlers(cancellationToken) {
|
|
4246
4770
|
const resultHandler = new ResultHandler();
|
|
4247
4771
|
this.#eventEmitter.addHandler(resultHandler);
|
|
4248
|
-
const testTreeHandler = new TestTreeHandler();
|
|
4249
|
-
this.#eventEmitter.addHandler(testTreeHandler);
|
|
4250
4772
|
if (this.#resolvedConfig.failFast) {
|
|
4251
4773
|
const cancellationHandler = new CancellationHandler(cancellationToken, CancellationReason.FailFast);
|
|
4252
4774
|
this.#eventEmitter.addHandler(cancellationHandler);
|
|
@@ -4289,16 +4811,16 @@ class Runner {
|
|
|
4289
4811
|
this.#eventEmitter.removeHandlers();
|
|
4290
4812
|
}
|
|
4291
4813
|
async #run(tasks, cancellationToken) {
|
|
4292
|
-
const result = new Result(
|
|
4814
|
+
const result = new Result(tasks);
|
|
4293
4815
|
EventEmitter.dispatch(["run:start", { result }]);
|
|
4294
4816
|
for (const target of this.#resolvedConfig.target) {
|
|
4295
4817
|
const targetResult = new TargetResult(target, tasks);
|
|
4296
4818
|
EventEmitter.dispatch(["target:start", { result: targetResult }]);
|
|
4297
4819
|
const compiler = await Store.load(target);
|
|
4298
4820
|
if (compiler) {
|
|
4299
|
-
const taskRunner = new TaskRunner(this.#resolvedConfig
|
|
4821
|
+
const taskRunner = new TaskRunner(compiler, this.#resolvedConfig);
|
|
4300
4822
|
for (const task of tasks) {
|
|
4301
|
-
taskRunner.run(task, cancellationToken);
|
|
4823
|
+
await taskRunner.run(task, cancellationToken);
|
|
4302
4824
|
}
|
|
4303
4825
|
}
|
|
4304
4826
|
EventEmitter.dispatch(["target:end", { result: targetResult }]);
|
|
@@ -4440,4 +4962,4 @@ class Cli {
|
|
|
4440
4962
|
}
|
|
4441
4963
|
}
|
|
4442
4964
|
|
|
4443
|
-
export { AssertionNode, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileWatcher, InputService, Line, ListReporter, OptionBrand, OptionGroup, Options, OutputService, Path, PluginService, ProjectResult, ProjectService, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, Runner, Scribbler, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, TargetResult, Task, TaskResult, TestResult, TestTree,
|
|
4965
|
+
export { AssertionNode, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileWatcher, InputService, Line, ListReporter, OptionBrand, OptionGroup, Options, OutputService, Path, PluginService, ProjectResult, ProjectService, Reject, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, Runner, Scribbler, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, TargetResult, Task, TaskResult, TestResult, TestTree, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, WhenNode, WhenService, addsPackageText, argumentIsProvided, argumentOrTypeArgumentIsProvided, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, environmentOptions, fileViewText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, nodeBelongsToArgumentList, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
|