tstyche 4.0.0-beta.6 → 4.0.0-beta.8
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 +6 -9
- package/build/index.cjs +1 -0
- package/build/index.d.cts +25 -1
- package/build/index.d.ts +25 -1
- package/build/index.js +1 -1
- package/build/tstyche.d.ts +21 -16
- package/build/tstyche.js +808 -696
- package/package.json +6 -5
package/build/tstyche.js
CHANGED
|
@@ -1672,14 +1672,12 @@ class ResultCount {
|
|
|
1672
1672
|
class Result {
|
|
1673
1673
|
expectCount = new ResultCount();
|
|
1674
1674
|
fileCount = new ResultCount();
|
|
1675
|
-
resolvedConfig;
|
|
1676
1675
|
results = [];
|
|
1677
1676
|
targetCount = new ResultCount();
|
|
1678
1677
|
tasks;
|
|
1679
1678
|
testCount = new ResultCount();
|
|
1680
1679
|
timing = new ResultTiming();
|
|
1681
|
-
constructor(
|
|
1682
|
-
this.resolvedConfig = resolvedConfig;
|
|
1680
|
+
constructor(tasks) {
|
|
1683
1681
|
this.tasks = tasks;
|
|
1684
1682
|
}
|
|
1685
1683
|
}
|
|
@@ -1781,6 +1779,7 @@ class ResultHandler {
|
|
|
1781
1779
|
this.#taskResult.timing.start = Date.now();
|
|
1782
1780
|
break;
|
|
1783
1781
|
case "task:error":
|
|
1782
|
+
case "collect:error":
|
|
1784
1783
|
this.#targetResult.status = ResultStatus.Failed;
|
|
1785
1784
|
this.#taskResult.status = ResultStatus.Failed;
|
|
1786
1785
|
this.#taskResult.diagnostics.push(...payload.diagnostics);
|
|
@@ -1915,488 +1914,90 @@ class ResultHandler {
|
|
|
1915
1914
|
}
|
|
1916
1915
|
}
|
|
1917
1916
|
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
TestTreeNodeBrand["Test"] = "test";
|
|
1922
|
-
TestTreeNodeBrand["Expect"] = "expect";
|
|
1923
|
-
})(TestTreeNodeBrand || (TestTreeNodeBrand = {}));
|
|
1917
|
+
function jsx(type, props) {
|
|
1918
|
+
return { props, type };
|
|
1919
|
+
}
|
|
1924
1920
|
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
this.name = node.arguments[0].text;
|
|
1942
|
-
}
|
|
1943
|
-
if (node.arguments[1] != null && compiler.isFunctionLike(node.arguments[1])) {
|
|
1944
|
-
for (const diagnostic of parent.diagnostics) {
|
|
1945
|
-
if (diagnosticBelongsToNode(diagnostic, node.arguments[1].body)) {
|
|
1946
|
-
this.diagnostics.add(diagnostic);
|
|
1947
|
-
parent.diagnostics.delete(diagnostic);
|
|
1948
|
-
}
|
|
1949
|
-
}
|
|
1950
|
-
}
|
|
1951
|
-
}
|
|
1952
|
-
validate() {
|
|
1953
|
-
const diagnostics = [];
|
|
1954
|
-
const getText = (node) => `'${node.expression.getText()}()' cannot be nested within '${this.node.expression.getText()}()'.`;
|
|
1955
|
-
const getParentCallExpression = (node) => {
|
|
1956
|
-
while (!this.#compiler.isCallExpression(node.parent)) {
|
|
1957
|
-
node = node.parent;
|
|
1958
|
-
}
|
|
1959
|
-
return node.parent;
|
|
1960
|
-
};
|
|
1961
|
-
switch (this.brand) {
|
|
1962
|
-
case TestTreeNodeBrand.Describe:
|
|
1963
|
-
for (const child of this.children) {
|
|
1964
|
-
if (child.brand === TestTreeNodeBrand.Expect) {
|
|
1965
|
-
diagnostics.push(Diagnostic.error(getText(child.node), DiagnosticOrigin.fromNode(getParentCallExpression(child.node))));
|
|
1966
|
-
}
|
|
1967
|
-
}
|
|
1968
|
-
break;
|
|
1969
|
-
case TestTreeNodeBrand.Test:
|
|
1970
|
-
case TestTreeNodeBrand.Expect:
|
|
1971
|
-
for (const child of this.children) {
|
|
1972
|
-
if (child.brand !== TestTreeNodeBrand.Expect) {
|
|
1973
|
-
diagnostics.push(Diagnostic.error(getText(child.node), DiagnosticOrigin.fromNode(child.node)));
|
|
1974
|
-
}
|
|
1975
|
-
}
|
|
1976
|
-
break;
|
|
1977
|
-
}
|
|
1978
|
-
return diagnostics;
|
|
1921
|
+
var Color;
|
|
1922
|
+
(function (Color) {
|
|
1923
|
+
Color["Reset"] = "0";
|
|
1924
|
+
Color["Red"] = "31";
|
|
1925
|
+
Color["Green"] = "32";
|
|
1926
|
+
Color["Yellow"] = "33";
|
|
1927
|
+
Color["Blue"] = "34";
|
|
1928
|
+
Color["Magenta"] = "35";
|
|
1929
|
+
Color["Cyan"] = "36";
|
|
1930
|
+
Color["Gray"] = "90";
|
|
1931
|
+
})(Color || (Color = {}));
|
|
1932
|
+
|
|
1933
|
+
function Text({ children, color, indent }) {
|
|
1934
|
+
const ansiEscapes = [];
|
|
1935
|
+
if (color != null) {
|
|
1936
|
+
ansiEscapes.push(color);
|
|
1979
1937
|
}
|
|
1938
|
+
return (jsx("text", { indent: indent ?? 0, children: [ansiEscapes.length > 0 ? jsx("ansi", { escapes: ansiEscapes }) : undefined, children, ansiEscapes.length > 0 ? jsx("ansi", { escapes: Color.Reset }) : undefined] }));
|
|
1980
1939
|
}
|
|
1981
1940
|
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
isNot;
|
|
1985
|
-
matcherNode;
|
|
1986
|
-
matcherNameNode;
|
|
1987
|
-
modifierNode;
|
|
1988
|
-
notNode;
|
|
1989
|
-
source;
|
|
1990
|
-
target;
|
|
1991
|
-
constructor(compiler, brand, node, parent, flags, matcherNode, matcherNameNode, modifierNode, notNode) {
|
|
1992
|
-
super(compiler, brand, node, parent, flags);
|
|
1993
|
-
this.isNot = notNode != null;
|
|
1994
|
-
this.matcherNode = matcherNode;
|
|
1995
|
-
this.matcherNameNode = matcherNameNode;
|
|
1996
|
-
this.modifierNode = modifierNode;
|
|
1997
|
-
this.source = this.node.typeArguments ?? this.node.arguments;
|
|
1998
|
-
if (compiler.isCallExpression(this.matcherNode)) {
|
|
1999
|
-
this.target = this.matcherNode.typeArguments ?? this.matcherNode.arguments;
|
|
2000
|
-
}
|
|
2001
|
-
for (const diagnostic of parent.diagnostics) {
|
|
2002
|
-
if (diagnosticBelongsToNode(diagnostic, this.source)) {
|
|
2003
|
-
this.diagnostics.add(diagnostic);
|
|
2004
|
-
parent.diagnostics.delete(diagnostic);
|
|
2005
|
-
}
|
|
2006
|
-
}
|
|
2007
|
-
}
|
|
1941
|
+
function Line({ children, color, indent }) {
|
|
1942
|
+
return (jsx(Text, { color: color, indent: indent, children: [children, jsx("newLine", {})] }));
|
|
2008
1943
|
}
|
|
2009
1944
|
|
|
2010
|
-
class
|
|
2011
|
-
#
|
|
2012
|
-
#
|
|
2013
|
-
#
|
|
2014
|
-
#
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
this.#
|
|
2018
|
-
this.#resolvedConfig = resolvedConfig;
|
|
1945
|
+
class Scribbler {
|
|
1946
|
+
#indentStep = " ";
|
|
1947
|
+
#newLine;
|
|
1948
|
+
#noColor;
|
|
1949
|
+
#notEmptyLineRegex = /^(?!$)/gm;
|
|
1950
|
+
constructor(options) {
|
|
1951
|
+
this.#newLine = options?.newLine ?? "\n";
|
|
1952
|
+
this.#noColor = options?.noColor ?? environmentOptions.noColor;
|
|
2019
1953
|
}
|
|
2020
|
-
#
|
|
2021
|
-
|
|
2022
|
-
return " ".repeat(range.end - range.start);
|
|
2023
|
-
}
|
|
2024
|
-
const text = [];
|
|
2025
|
-
for (let index = range.start; index < range.end; index++) {
|
|
2026
|
-
const character = this.#text.charAt(index);
|
|
2027
|
-
switch (character) {
|
|
2028
|
-
case "\n":
|
|
2029
|
-
case "\r":
|
|
2030
|
-
text.push(character);
|
|
2031
|
-
break;
|
|
2032
|
-
default:
|
|
2033
|
-
text.push(" ");
|
|
2034
|
-
}
|
|
2035
|
-
}
|
|
2036
|
-
return text.join("");
|
|
1954
|
+
#escapeSequence(attributes) {
|
|
1955
|
+
return ["\u001B[", Array.isArray(attributes) ? attributes.join(";") : attributes, "m"].join("");
|
|
2037
1956
|
}
|
|
2038
|
-
#
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
const rangeText = range.replacement != null
|
|
2042
|
-
? `${range.replacement}${this.#getErasedRangeText(range).slice(range.replacement.length)}`
|
|
2043
|
-
: this.#getErasedRangeText(range);
|
|
2044
|
-
this.#text = `${this.#text.slice(0, range.start)}${rangeText}${this.#text.slice(range.end)}`;
|
|
1957
|
+
#indentEachLine(lines, level) {
|
|
1958
|
+
if (level === 0) {
|
|
1959
|
+
return lines;
|
|
2045
1960
|
}
|
|
1961
|
+
return lines.replace(this.#notEmptyLineRegex, this.#indentStep.repeat(level));
|
|
2046
1962
|
}
|
|
2047
|
-
|
|
2048
|
-
if (
|
|
2049
|
-
this
|
|
2050
|
-
const languageService = this.#projectService.getLanguageService(this.#filePath);
|
|
2051
|
-
const diagnostics = new Set(languageService?.getSemanticDiagnostics(this.#filePath));
|
|
2052
|
-
for (const node of this.#nodes.reverse()) {
|
|
2053
|
-
for (const diagnostic of diagnostics) {
|
|
2054
|
-
if (diagnosticBelongsToNode(diagnostic, node.matcherNode)) {
|
|
2055
|
-
if (!node.abilityDiagnostics) {
|
|
2056
|
-
node.abilityDiagnostics = new Set();
|
|
2057
|
-
}
|
|
2058
|
-
node.abilityDiagnostics.add(diagnostic);
|
|
2059
|
-
diagnostics.delete(diagnostic);
|
|
2060
|
-
}
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
1963
|
+
render(element) {
|
|
1964
|
+
if (typeof element.type === "function") {
|
|
1965
|
+
return this.render(element.type({ ...element.props }));
|
|
2063
1966
|
}
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
this.#text = "";
|
|
2067
|
-
}
|
|
2068
|
-
handleNode(assertionNode) {
|
|
2069
|
-
const expectStart = assertionNode.node.getStart();
|
|
2070
|
-
const expectExpressionEnd = assertionNode.node.expression.getEnd();
|
|
2071
|
-
const expectEnd = assertionNode.node.getEnd();
|
|
2072
|
-
const matcherNameEnd = assertionNode.matcherNameNode.getEnd();
|
|
2073
|
-
switch (assertionNode.matcherNameNode.name.text) {
|
|
2074
|
-
case "toBeApplicable":
|
|
2075
|
-
this.#addRanges(assertionNode, [
|
|
2076
|
-
{ end: expectExpressionEnd, start: expectStart },
|
|
2077
|
-
{ end: matcherNameEnd, start: expectEnd },
|
|
2078
|
-
]);
|
|
2079
|
-
break;
|
|
2080
|
-
case "toBeCallableWith":
|
|
2081
|
-
this.#addRanges(assertionNode, [
|
|
2082
|
-
{ end: expectExpressionEnd, start: expectStart, replacement: ";" },
|
|
2083
|
-
{ end: matcherNameEnd, start: expectEnd },
|
|
2084
|
-
]);
|
|
2085
|
-
break;
|
|
2086
|
-
case "toBeConstructableWith":
|
|
2087
|
-
this.#addRanges(assertionNode, [
|
|
2088
|
-
{ end: expectExpressionEnd, start: expectStart, replacement: "; new" },
|
|
2089
|
-
{ end: matcherNameEnd, start: expectEnd },
|
|
2090
|
-
]);
|
|
2091
|
-
break;
|
|
1967
|
+
if (element.type === "ansi" && !this.#noColor) {
|
|
1968
|
+
return this.#escapeSequence(element.props.escapes);
|
|
2092
1969
|
}
|
|
1970
|
+
if (element.type === "newLine") {
|
|
1971
|
+
return this.#newLine;
|
|
1972
|
+
}
|
|
1973
|
+
if (element.type === "text") {
|
|
1974
|
+
const text = this.#visitChildren(element.props.children);
|
|
1975
|
+
return this.#indentEachLine(text, element.props.indent);
|
|
1976
|
+
}
|
|
1977
|
+
return "";
|
|
2093
1978
|
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
1979
|
+
#visitChildren(children) {
|
|
1980
|
+
const text = [];
|
|
1981
|
+
for (const child of children) {
|
|
1982
|
+
if (typeof child === "string" || typeof child === "number") {
|
|
1983
|
+
text.push(child);
|
|
1984
|
+
continue;
|
|
1985
|
+
}
|
|
1986
|
+
if (Array.isArray(child)) {
|
|
1987
|
+
text.push(this.#visitChildren(child));
|
|
1988
|
+
continue;
|
|
1989
|
+
}
|
|
1990
|
+
if (child != null && typeof child === "object") {
|
|
1991
|
+
text.push(this.render(child));
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
return text.join("");
|
|
2097
1995
|
}
|
|
2098
1996
|
}
|
|
2099
1997
|
|
|
2100
|
-
|
|
2101
|
-
(
|
|
2102
|
-
|
|
2103
|
-
TestTreeNodeFlags[TestTreeNodeFlags["Fail"] = 1] = "Fail";
|
|
2104
|
-
TestTreeNodeFlags[TestTreeNodeFlags["Only"] = 2] = "Only";
|
|
2105
|
-
TestTreeNodeFlags[TestTreeNodeFlags["Skip"] = 4] = "Skip";
|
|
2106
|
-
TestTreeNodeFlags[TestTreeNodeFlags["Todo"] = 8] = "Todo";
|
|
2107
|
-
})(TestTreeNodeFlags || (TestTreeNodeFlags = {}));
|
|
2108
|
-
|
|
2109
|
-
class IdentifierLookup {
|
|
2110
|
-
#compiler;
|
|
2111
|
-
#identifiers;
|
|
2112
|
-
#moduleSpecifiers = ['"tstyche"', "'tstyche'"];
|
|
2113
|
-
constructor(compiler, identifiers) {
|
|
2114
|
-
this.#compiler = compiler;
|
|
2115
|
-
this.#identifiers = identifiers ?? {
|
|
2116
|
-
namedImports: {
|
|
2117
|
-
describe: undefined,
|
|
2118
|
-
expect: undefined,
|
|
2119
|
-
it: undefined,
|
|
2120
|
-
namespace: undefined,
|
|
2121
|
-
test: undefined,
|
|
2122
|
-
},
|
|
2123
|
-
namespace: undefined,
|
|
2124
|
-
};
|
|
2125
|
-
}
|
|
2126
|
-
handleImportDeclaration(node) {
|
|
2127
|
-
if (this.#moduleSpecifiers.includes(node.moduleSpecifier.getText()) &&
|
|
2128
|
-
node.importClause?.isTypeOnly !== true &&
|
|
2129
|
-
node.importClause?.namedBindings != null) {
|
|
2130
|
-
if (this.#compiler.isNamedImports(node.importClause.namedBindings)) {
|
|
2131
|
-
for (const element of node.importClause.namedBindings.elements) {
|
|
2132
|
-
if (element.isTypeOnly) {
|
|
2133
|
-
continue;
|
|
2134
|
-
}
|
|
2135
|
-
let identifierKey;
|
|
2136
|
-
if (element.propertyName) {
|
|
2137
|
-
identifierKey = element.propertyName.getText();
|
|
2138
|
-
}
|
|
2139
|
-
else {
|
|
2140
|
-
identifierKey = element.name.getText();
|
|
2141
|
-
}
|
|
2142
|
-
if (identifierKey in this.#identifiers.namedImports) {
|
|
2143
|
-
this.#identifiers.namedImports[identifierKey] = element.name.getText();
|
|
2144
|
-
}
|
|
2145
|
-
}
|
|
2146
|
-
}
|
|
2147
|
-
if (this.#compiler.isNamespaceImport(node.importClause.namedBindings)) {
|
|
2148
|
-
this.#identifiers.namespace = node.importClause.namedBindings.name.getText();
|
|
2149
|
-
}
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
resolveTestMemberMeta(node) {
|
|
2153
|
-
let flags = TestTreeNodeFlags.None;
|
|
2154
|
-
let expression = node.expression;
|
|
2155
|
-
while (this.#compiler.isPropertyAccessExpression(expression)) {
|
|
2156
|
-
if (expression.expression.getText() === this.#identifiers.namespace) {
|
|
2157
|
-
break;
|
|
2158
|
-
}
|
|
2159
|
-
switch (expression.name.getText()) {
|
|
2160
|
-
case "fail":
|
|
2161
|
-
flags |= TestTreeNodeFlags.Fail;
|
|
2162
|
-
break;
|
|
2163
|
-
case "only":
|
|
2164
|
-
flags |= TestTreeNodeFlags.Only;
|
|
2165
|
-
break;
|
|
2166
|
-
case "skip":
|
|
2167
|
-
flags |= TestTreeNodeFlags.Skip;
|
|
2168
|
-
break;
|
|
2169
|
-
case "todo":
|
|
2170
|
-
flags |= TestTreeNodeFlags.Todo;
|
|
2171
|
-
break;
|
|
2172
|
-
}
|
|
2173
|
-
expression = expression.expression;
|
|
2174
|
-
}
|
|
2175
|
-
let identifierName;
|
|
2176
|
-
if (this.#compiler.isPropertyAccessExpression(expression) &&
|
|
2177
|
-
expression.expression.getText() === this.#identifiers.namespace) {
|
|
2178
|
-
identifierName = expression.name.getText();
|
|
2179
|
-
}
|
|
2180
|
-
else {
|
|
2181
|
-
identifierName = Object.keys(this.#identifiers.namedImports).find((key) => this.#identifiers.namedImports[key] === expression.getText());
|
|
2182
|
-
}
|
|
2183
|
-
if (!identifierName) {
|
|
2184
|
-
return;
|
|
2185
|
-
}
|
|
2186
|
-
switch (identifierName) {
|
|
2187
|
-
case "describe":
|
|
2188
|
-
return { brand: TestTreeNodeBrand.Describe, flags };
|
|
2189
|
-
case "it":
|
|
2190
|
-
case "test":
|
|
2191
|
-
return { brand: TestTreeNodeBrand.Test, flags };
|
|
2192
|
-
case "expect":
|
|
2193
|
-
return { brand: TestTreeNodeBrand.Expect, flags };
|
|
2194
|
-
}
|
|
2195
|
-
return;
|
|
2196
|
-
}
|
|
2197
|
-
}
|
|
2198
|
-
|
|
2199
|
-
class TestTree {
|
|
2200
|
-
children = [];
|
|
2201
|
-
diagnostics;
|
|
2202
|
-
hasOnly = false;
|
|
2203
|
-
sourceFile;
|
|
2204
|
-
constructor(diagnostics, sourceFile) {
|
|
2205
|
-
this.diagnostics = diagnostics;
|
|
2206
|
-
this.sourceFile = sourceFile;
|
|
2207
|
-
}
|
|
2208
|
-
}
|
|
2209
|
-
|
|
2210
|
-
class CollectService {
|
|
2211
|
-
#abilityLayer;
|
|
2212
|
-
#compiler;
|
|
2213
|
-
#projectService;
|
|
2214
|
-
#resolvedConfig;
|
|
2215
|
-
constructor(compiler, projectService, resolvedConfig) {
|
|
2216
|
-
this.#compiler = compiler;
|
|
2217
|
-
this.#projectService = projectService;
|
|
2218
|
-
this.#resolvedConfig = resolvedConfig;
|
|
2219
|
-
this.#abilityLayer = new AbilityLayer(this.#projectService, this.#resolvedConfig);
|
|
2220
|
-
}
|
|
2221
|
-
#collectTestTreeNodes(node, identifiers, parent) {
|
|
2222
|
-
if (this.#compiler.isCallExpression(node)) {
|
|
2223
|
-
const meta = identifiers.resolveTestMemberMeta(node);
|
|
2224
|
-
if (meta != null && (meta.brand === TestTreeNodeBrand.Describe || meta.brand === TestTreeNodeBrand.Test)) {
|
|
2225
|
-
const testTreeNode = new TestTreeNode(this.#compiler, meta.brand, node, parent, meta.flags);
|
|
2226
|
-
parent.children.push(testTreeNode);
|
|
2227
|
-
EventEmitter.dispatch(["collect:node", { testNode: testTreeNode }]);
|
|
2228
|
-
this.#compiler.forEachChild(node, (node) => {
|
|
2229
|
-
this.#collectTestTreeNodes(node, identifiers, testTreeNode);
|
|
2230
|
-
});
|
|
2231
|
-
return;
|
|
2232
|
-
}
|
|
2233
|
-
if (meta != null && meta.brand === TestTreeNodeBrand.Expect) {
|
|
2234
|
-
const modifierNode = this.#getChainedNode(node, "type");
|
|
2235
|
-
if (!modifierNode) {
|
|
2236
|
-
return;
|
|
2237
|
-
}
|
|
2238
|
-
const notNode = this.#getChainedNode(modifierNode, "not");
|
|
2239
|
-
const matcherNameNode = this.#getChainedNode(notNode ?? modifierNode);
|
|
2240
|
-
if (!matcherNameNode) {
|
|
2241
|
-
return;
|
|
2242
|
-
}
|
|
2243
|
-
const matcherNode = this.#getMatcherNode(matcherNameNode);
|
|
2244
|
-
if (!matcherNode) {
|
|
2245
|
-
return;
|
|
2246
|
-
}
|
|
2247
|
-
const assertionNode = new AssertionNode(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, matcherNameNode, modifierNode, notNode);
|
|
2248
|
-
parent.children.push(assertionNode);
|
|
2249
|
-
this.#abilityLayer.handleNode(assertionNode);
|
|
2250
|
-
EventEmitter.dispatch(["collect:node", { testNode: assertionNode }]);
|
|
2251
|
-
this.#compiler.forEachChild(node, (node) => {
|
|
2252
|
-
this.#collectTestTreeNodes(node, identifiers, assertionNode);
|
|
2253
|
-
});
|
|
2254
|
-
return;
|
|
2255
|
-
}
|
|
2256
|
-
}
|
|
2257
|
-
if (this.#compiler.isImportDeclaration(node)) {
|
|
2258
|
-
identifiers.handleImportDeclaration(node);
|
|
2259
|
-
return;
|
|
2260
|
-
}
|
|
2261
|
-
this.#compiler.forEachChild(node, (node) => {
|
|
2262
|
-
this.#collectTestTreeNodes(node, identifiers, parent);
|
|
2263
|
-
});
|
|
2264
|
-
}
|
|
2265
|
-
createTestTree(sourceFile, semanticDiagnostics = []) {
|
|
2266
|
-
const testTree = new TestTree(new Set(semanticDiagnostics), sourceFile);
|
|
2267
|
-
EventEmitter.dispatch(["collect:start", { testTree }]);
|
|
2268
|
-
this.#abilityLayer.open(sourceFile);
|
|
2269
|
-
this.#collectTestTreeNodes(sourceFile, new IdentifierLookup(this.#compiler), testTree);
|
|
2270
|
-
this.#abilityLayer.close();
|
|
2271
|
-
EventEmitter.dispatch(["collect:end", { testTree }]);
|
|
2272
|
-
return testTree;
|
|
2273
|
-
}
|
|
2274
|
-
#getChainedNode({ parent }, name) {
|
|
2275
|
-
if (!this.#compiler.isPropertyAccessExpression(parent)) {
|
|
2276
|
-
return;
|
|
2277
|
-
}
|
|
2278
|
-
if (name != null && name !== parent.name.getText()) {
|
|
2279
|
-
return;
|
|
2280
|
-
}
|
|
2281
|
-
return parent;
|
|
2282
|
-
}
|
|
2283
|
-
#getMatcherNode(node) {
|
|
2284
|
-
if (this.#compiler.isCallExpression(node.parent)) {
|
|
2285
|
-
return node.parent;
|
|
2286
|
-
}
|
|
2287
|
-
if (this.#compiler.isDecorator(node.parent)) {
|
|
2288
|
-
return node.parent;
|
|
2289
|
-
}
|
|
2290
|
-
if (this.#compiler.isParenthesizedExpression(node.parent)) {
|
|
2291
|
-
return this.#getMatcherNode(node.parent);
|
|
2292
|
-
}
|
|
2293
|
-
return;
|
|
2294
|
-
}
|
|
2295
|
-
}
|
|
2296
|
-
|
|
2297
|
-
class TestTreeHandler {
|
|
2298
|
-
testTree;
|
|
2299
|
-
on([event, payload]) {
|
|
2300
|
-
switch (event) {
|
|
2301
|
-
case "collect:start":
|
|
2302
|
-
this.testTree = payload.testTree;
|
|
2303
|
-
break;
|
|
2304
|
-
case "collect:end":
|
|
2305
|
-
this.testTree = undefined;
|
|
2306
|
-
break;
|
|
2307
|
-
case "collect:node":
|
|
2308
|
-
if (payload.testNode.flags & TestTreeNodeFlags.Only) {
|
|
2309
|
-
this.testTree.hasOnly = true;
|
|
2310
|
-
}
|
|
2311
|
-
break;
|
|
2312
|
-
}
|
|
2313
|
-
}
|
|
2314
|
-
}
|
|
2315
|
-
|
|
2316
|
-
function jsx(type, props) {
|
|
2317
|
-
return { props, type };
|
|
2318
|
-
}
|
|
2319
|
-
|
|
2320
|
-
var Color;
|
|
2321
|
-
(function (Color) {
|
|
2322
|
-
Color["Reset"] = "0";
|
|
2323
|
-
Color["Red"] = "31";
|
|
2324
|
-
Color["Green"] = "32";
|
|
2325
|
-
Color["Yellow"] = "33";
|
|
2326
|
-
Color["Blue"] = "34";
|
|
2327
|
-
Color["Magenta"] = "35";
|
|
2328
|
-
Color["Cyan"] = "36";
|
|
2329
|
-
Color["Gray"] = "90";
|
|
2330
|
-
})(Color || (Color = {}));
|
|
2331
|
-
|
|
2332
|
-
function Text({ children, color, indent }) {
|
|
2333
|
-
const ansiEscapes = [];
|
|
2334
|
-
if (color != null) {
|
|
2335
|
-
ansiEscapes.push(color);
|
|
2336
|
-
}
|
|
2337
|
-
return (jsx("text", { indent: indent ?? 0, children: [ansiEscapes.length > 0 ? jsx("ansi", { escapes: ansiEscapes }) : undefined, children, ansiEscapes.length > 0 ? jsx("ansi", { escapes: Color.Reset }) : undefined] }));
|
|
2338
|
-
}
|
|
2339
|
-
|
|
2340
|
-
function Line({ children, color, indent }) {
|
|
2341
|
-
return (jsx(Text, { color: color, indent: indent, children: [children, jsx("newLine", {})] }));
|
|
2342
|
-
}
|
|
2343
|
-
|
|
2344
|
-
class Scribbler {
|
|
2345
|
-
#indentStep = " ";
|
|
2346
|
-
#newLine;
|
|
2347
|
-
#noColor;
|
|
2348
|
-
#notEmptyLineRegex = /^(?!$)/gm;
|
|
2349
|
-
constructor(options) {
|
|
2350
|
-
this.#newLine = options?.newLine ?? "\n";
|
|
2351
|
-
this.#noColor = options?.noColor ?? environmentOptions.noColor;
|
|
2352
|
-
}
|
|
2353
|
-
#escapeSequence(attributes) {
|
|
2354
|
-
return ["\u001B[", Array.isArray(attributes) ? attributes.join(";") : attributes, "m"].join("");
|
|
2355
|
-
}
|
|
2356
|
-
#indentEachLine(lines, level) {
|
|
2357
|
-
if (level === 0) {
|
|
2358
|
-
return lines;
|
|
2359
|
-
}
|
|
2360
|
-
return lines.replace(this.#notEmptyLineRegex, this.#indentStep.repeat(level));
|
|
2361
|
-
}
|
|
2362
|
-
render(element) {
|
|
2363
|
-
if (typeof element.type === "function") {
|
|
2364
|
-
return this.render(element.type({ ...element.props }));
|
|
2365
|
-
}
|
|
2366
|
-
if (element.type === "ansi" && !this.#noColor) {
|
|
2367
|
-
return this.#escapeSequence(element.props.escapes);
|
|
2368
|
-
}
|
|
2369
|
-
if (element.type === "newLine") {
|
|
2370
|
-
return this.#newLine;
|
|
2371
|
-
}
|
|
2372
|
-
if (element.type === "text") {
|
|
2373
|
-
const text = this.#visitChildren(element.props.children);
|
|
2374
|
-
return this.#indentEachLine(text, element.props.indent);
|
|
2375
|
-
}
|
|
2376
|
-
return "";
|
|
2377
|
-
}
|
|
2378
|
-
#visitChildren(children) {
|
|
2379
|
-
const text = [];
|
|
2380
|
-
for (const child of children) {
|
|
2381
|
-
if (typeof child === "string" || typeof child === "number") {
|
|
2382
|
-
text.push(child);
|
|
2383
|
-
continue;
|
|
2384
|
-
}
|
|
2385
|
-
if (Array.isArray(child)) {
|
|
2386
|
-
text.push(this.#visitChildren(child));
|
|
2387
|
-
continue;
|
|
2388
|
-
}
|
|
2389
|
-
if (child != null && typeof child === "object") {
|
|
2390
|
-
text.push(this.render(child));
|
|
2391
|
-
}
|
|
2392
|
-
}
|
|
2393
|
-
return text.join("");
|
|
2394
|
-
}
|
|
2395
|
-
}
|
|
2396
|
-
|
|
2397
|
-
function addsPackageText(packageVersion, packagePath) {
|
|
2398
|
-
return (jsx(Line, { children: [jsx(Text, { color: Color.Gray, children: "adds" }), " TypeScript ", packageVersion, jsx(Text, { color: Color.Gray, children: [" to ", packagePath] })] }));
|
|
2399
|
-
}
|
|
1998
|
+
function addsPackageText(packageVersion, packagePath) {
|
|
1999
|
+
return (jsx(Line, { children: [jsx(Text, { color: Color.Gray, children: "adds" }), " TypeScript ", packageVersion, jsx(Text, { color: Color.Gray, children: [" to ", packagePath] })] }));
|
|
2000
|
+
}
|
|
2400
2001
|
|
|
2401
2002
|
function describeNameText(name, indent = 0) {
|
|
2402
2003
|
return jsx(Line, { indent: indent + 1, children: name });
|
|
@@ -2627,39 +2228,12 @@ function CountText({ failed, passed, skipped, todo, total }) {
|
|
|
2627
2228
|
function DurationText({ seconds }) {
|
|
2628
2229
|
return jsx(Text, { children: `${Math.round(seconds * 10) / 10}s` });
|
|
2629
2230
|
}
|
|
2630
|
-
function
|
|
2631
|
-
if (typeof text === "string") {
|
|
2632
|
-
return jsx(Text, { children: ["'", text, "'"] });
|
|
2633
|
-
}
|
|
2634
|
-
if (text.length === 1) {
|
|
2635
|
-
return jsx(Text, { children: ["'", ...text, "'"] });
|
|
2636
|
-
}
|
|
2637
|
-
const lastItem = text.pop();
|
|
2638
|
-
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, "'"] }));
|
|
2639
|
-
}
|
|
2640
|
-
function RanFilesText({ onlyMatch, pathMatch, skipMatch }) {
|
|
2641
|
-
const testNameMatchText = [];
|
|
2642
|
-
if (onlyMatch != null) {
|
|
2643
|
-
testNameMatchText.push(jsx(Text, { children: [jsx(Text, { color: Color.Gray, children: "matching " }), jsx(MatchText, { text: onlyMatch })] }));
|
|
2644
|
-
}
|
|
2645
|
-
if (skipMatch != null) {
|
|
2646
|
-
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 })] }));
|
|
2647
|
-
}
|
|
2648
|
-
let pathMatchText;
|
|
2649
|
-
if (pathMatch.length > 0) {
|
|
2650
|
-
pathMatchText = (jsx(Text, { children: [jsx(Text, { color: Color.Gray, children: "test files matching " }), jsx(MatchText, { text: pathMatch }), jsx(Text, { color: Color.Gray, children: "." })] }));
|
|
2651
|
-
}
|
|
2652
|
-
else {
|
|
2653
|
-
pathMatchText = jsx(Text, { color: Color.Gray, children: "all test files." });
|
|
2654
|
-
}
|
|
2655
|
-
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] }));
|
|
2656
|
-
}
|
|
2657
|
-
function summaryText({ duration, expectCount, fileCount, onlyMatch, pathMatch, skipMatch, targetCount, testCount, }) {
|
|
2231
|
+
function summaryText({ duration, expectCount, fileCount, targetCount, testCount, }) {
|
|
2658
2232
|
const targetCountText = (jsx(RowText, { label: "Targets", text: jsx(CountText, { failed: targetCount.failed, passed: targetCount.passed, skipped: targetCount.skipped, todo: targetCount.todo, total: targetCount.total }) }));
|
|
2659
2233
|
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 }) }));
|
|
2660
2234
|
const testCountText = (jsx(RowText, { label: "Tests", text: jsx(CountText, { failed: testCount.failed, passed: testCount.passed, skipped: testCount.skipped, todo: testCount.todo, total: testCount.total }) }));
|
|
2661
2235
|
const assertionCountText = (jsx(RowText, { label: "Assertions", text: jsx(CountText, { failed: expectCount.failed, passed: expectCount.passed, skipped: expectCount.skipped, todo: expectCount.todo, total: expectCount.total }) }));
|
|
2662
|
-
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 }) })
|
|
2236
|
+
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 }) })] }));
|
|
2663
2237
|
}
|
|
2664
2238
|
|
|
2665
2239
|
function StatusText({ status }) {
|
|
@@ -2808,6 +2382,7 @@ class ListReporter extends BaseReporter {
|
|
|
2808
2382
|
this.#hasReportedError = false;
|
|
2809
2383
|
break;
|
|
2810
2384
|
case "task:error":
|
|
2385
|
+
case "collect:error":
|
|
2811
2386
|
for (const diagnostic of payload.diagnostics) {
|
|
2812
2387
|
this.#fileView.addMessage(diagnosticText(diagnostic));
|
|
2813
2388
|
}
|
|
@@ -2903,9 +2478,6 @@ class SummaryReporter extends BaseReporter {
|
|
|
2903
2478
|
duration: payload.result.timing.duration,
|
|
2904
2479
|
expectCount: payload.result.expectCount,
|
|
2905
2480
|
fileCount: payload.result.fileCount,
|
|
2906
|
-
onlyMatch: payload.result.resolvedConfig.only,
|
|
2907
|
-
pathMatch: payload.result.resolvedConfig.pathMatch,
|
|
2908
|
-
skipMatch: payload.result.resolvedConfig.skip,
|
|
2909
2481
|
targetCount: payload.result.targetCount,
|
|
2910
2482
|
testCount: payload.result.testCount,
|
|
2911
2483
|
}));
|
|
@@ -2975,6 +2547,7 @@ var CancellationReason;
|
|
|
2975
2547
|
(function (CancellationReason) {
|
|
2976
2548
|
CancellationReason["ConfigChange"] = "configChange";
|
|
2977
2549
|
CancellationReason["ConfigError"] = "configError";
|
|
2550
|
+
CancellationReason["CollectError"] = "collectError";
|
|
2978
2551
|
CancellationReason["FailFast"] = "failFast";
|
|
2979
2552
|
CancellationReason["WatchClose"] = "watchClose";
|
|
2980
2553
|
})(CancellationReason || (CancellationReason = {}));
|
|
@@ -3202,101 +2775,587 @@ class Debounce {
|
|
|
3202
2775
|
this.#resolve?.(this.#onResolve());
|
|
3203
2776
|
}, this.#delay);
|
|
3204
2777
|
}
|
|
3205
|
-
resolve(value) {
|
|
3206
|
-
this.#resolve?.(value);
|
|
2778
|
+
resolve(value) {
|
|
2779
|
+
this.#resolve?.(value);
|
|
2780
|
+
}
|
|
2781
|
+
schedule() {
|
|
2782
|
+
return new Promise((resolve) => {
|
|
2783
|
+
this.#resolve = resolve;
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
class WatchService {
|
|
2789
|
+
#changedTestFiles = new Map();
|
|
2790
|
+
#inputService;
|
|
2791
|
+
#resolvedConfig;
|
|
2792
|
+
#watchedTestFiles;
|
|
2793
|
+
#watchers = [];
|
|
2794
|
+
constructor(resolvedConfig, tasks) {
|
|
2795
|
+
this.#resolvedConfig = resolvedConfig;
|
|
2796
|
+
this.#watchedTestFiles = new Map(tasks.map((task) => [task.filePath, task]));
|
|
2797
|
+
}
|
|
2798
|
+
#onDiagnostics(diagnostic) {
|
|
2799
|
+
EventEmitter.dispatch(["watch:error", { diagnostics: [diagnostic] }]);
|
|
2800
|
+
}
|
|
2801
|
+
async *watch(cancellationToken) {
|
|
2802
|
+
const onResolve = () => {
|
|
2803
|
+
const testFiles = [...this.#changedTestFiles.values()];
|
|
2804
|
+
this.#changedTestFiles.clear();
|
|
2805
|
+
return testFiles;
|
|
2806
|
+
};
|
|
2807
|
+
const debounce = new Debounce(100, onResolve);
|
|
2808
|
+
const onClose = (reason) => {
|
|
2809
|
+
debounce.cancel();
|
|
2810
|
+
this.#inputService?.close();
|
|
2811
|
+
for (const watcher of this.#watchers) {
|
|
2812
|
+
watcher.close();
|
|
2813
|
+
}
|
|
2814
|
+
cancellationToken.cancel(reason);
|
|
2815
|
+
debounce.resolve([]);
|
|
2816
|
+
};
|
|
2817
|
+
if (!environmentOptions.noInteractive) {
|
|
2818
|
+
const onInput = (chunk) => {
|
|
2819
|
+
switch (chunk.toLowerCase()) {
|
|
2820
|
+
case "\u0003":
|
|
2821
|
+
case "\u0004":
|
|
2822
|
+
case "\u001B":
|
|
2823
|
+
case "q":
|
|
2824
|
+
case "x":
|
|
2825
|
+
onClose(CancellationReason.WatchClose);
|
|
2826
|
+
break;
|
|
2827
|
+
case "\u000D":
|
|
2828
|
+
case "\u0020":
|
|
2829
|
+
case "a":
|
|
2830
|
+
debounce.cancel();
|
|
2831
|
+
if (this.#watchedTestFiles.size > 0) {
|
|
2832
|
+
debounce.resolve([...this.#watchedTestFiles.values()]);
|
|
2833
|
+
}
|
|
2834
|
+
break;
|
|
2835
|
+
}
|
|
2836
|
+
};
|
|
2837
|
+
this.#inputService = new InputService(onInput);
|
|
2838
|
+
}
|
|
2839
|
+
const onChangedFile = (filePath) => {
|
|
2840
|
+
debounce.refresh();
|
|
2841
|
+
let task = this.#watchedTestFiles.get(filePath);
|
|
2842
|
+
if (task != null) {
|
|
2843
|
+
this.#changedTestFiles.set(filePath, task);
|
|
2844
|
+
}
|
|
2845
|
+
else if (Select.isTestFile(filePath, this.#resolvedConfig)) {
|
|
2846
|
+
task = new Task(filePath);
|
|
2847
|
+
this.#changedTestFiles.set(filePath, task);
|
|
2848
|
+
this.#watchedTestFiles.set(filePath, task);
|
|
2849
|
+
}
|
|
2850
|
+
};
|
|
2851
|
+
const onRemovedFile = (filePath) => {
|
|
2852
|
+
this.#changedTestFiles.delete(filePath);
|
|
2853
|
+
this.#watchedTestFiles.delete(filePath);
|
|
2854
|
+
if (this.#watchedTestFiles.size === 0) {
|
|
2855
|
+
debounce.cancel();
|
|
2856
|
+
this.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereLeft(this.#resolvedConfig)));
|
|
2857
|
+
}
|
|
2858
|
+
};
|
|
2859
|
+
this.#watchers.push(new Watcher(this.#resolvedConfig.rootPath, onChangedFile, onRemovedFile, { recursive: true }));
|
|
2860
|
+
const onChangedConfigFile = () => {
|
|
2861
|
+
onClose(CancellationReason.ConfigChange);
|
|
2862
|
+
};
|
|
2863
|
+
this.#watchers.push(new FileWatcher(this.#resolvedConfig.configFilePath, onChangedConfigFile));
|
|
2864
|
+
for (const watcher of this.#watchers) {
|
|
2865
|
+
watcher.watch();
|
|
2866
|
+
}
|
|
2867
|
+
while (!cancellationToken.isCancellationRequested) {
|
|
2868
|
+
const testFiles = await debounce.schedule();
|
|
2869
|
+
if (testFiles.length > 0) {
|
|
2870
|
+
yield testFiles;
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
var TestTreeNodeBrand;
|
|
2877
|
+
(function (TestTreeNodeBrand) {
|
|
2878
|
+
TestTreeNodeBrand["Describe"] = "describe";
|
|
2879
|
+
TestTreeNodeBrand["Test"] = "test";
|
|
2880
|
+
TestTreeNodeBrand["Expect"] = "expect";
|
|
2881
|
+
TestTreeNodeBrand["When"] = "when";
|
|
2882
|
+
})(TestTreeNodeBrand || (TestTreeNodeBrand = {}));
|
|
2883
|
+
|
|
2884
|
+
class TestTreeNode {
|
|
2885
|
+
brand;
|
|
2886
|
+
children = [];
|
|
2887
|
+
#compiler;
|
|
2888
|
+
diagnostics = new Set();
|
|
2889
|
+
flags;
|
|
2890
|
+
name = "";
|
|
2891
|
+
node;
|
|
2892
|
+
parent;
|
|
2893
|
+
constructor(compiler, brand, node, parent, flags) {
|
|
2894
|
+
this.brand = brand;
|
|
2895
|
+
this.#compiler = compiler;
|
|
2896
|
+
this.node = node;
|
|
2897
|
+
this.parent = parent;
|
|
2898
|
+
this.flags = flags;
|
|
2899
|
+
if (node.arguments[0] != null && compiler.isStringLiteralLike(node.arguments[0])) {
|
|
2900
|
+
this.name = node.arguments[0].text;
|
|
2901
|
+
}
|
|
2902
|
+
if (node.arguments[1] != null && compiler.isFunctionLike(node.arguments[1])) {
|
|
2903
|
+
for (const diagnostic of parent.diagnostics) {
|
|
2904
|
+
if (diagnosticBelongsToNode(diagnostic, node.arguments[1].body)) {
|
|
2905
|
+
this.diagnostics.add(diagnostic);
|
|
2906
|
+
parent.diagnostics.delete(diagnostic);
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
validate() {
|
|
2912
|
+
const diagnostics = [];
|
|
2913
|
+
const getText = (node) => `'${node.expression.getText()}()' cannot be nested within '${this.node.expression.getText()}()'.`;
|
|
2914
|
+
const getParentCallExpression = (node) => {
|
|
2915
|
+
while (!this.#compiler.isCallExpression(node.parent)) {
|
|
2916
|
+
node = node.parent;
|
|
2917
|
+
}
|
|
2918
|
+
return node.parent;
|
|
2919
|
+
};
|
|
2920
|
+
switch (this.brand) {
|
|
2921
|
+
case TestTreeNodeBrand.Describe:
|
|
2922
|
+
for (const child of this.children) {
|
|
2923
|
+
if (child.brand === TestTreeNodeBrand.Expect || child.brand === TestTreeNodeBrand.When) {
|
|
2924
|
+
diagnostics.push(Diagnostic.error(getText(child.node), DiagnosticOrigin.fromNode(getParentCallExpression(child.node))));
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
break;
|
|
2928
|
+
case TestTreeNodeBrand.Test:
|
|
2929
|
+
case TestTreeNodeBrand.Expect:
|
|
2930
|
+
for (const child of this.children) {
|
|
2931
|
+
if (child.brand === TestTreeNodeBrand.Describe || child.brand === TestTreeNodeBrand.Test) {
|
|
2932
|
+
diagnostics.push(Diagnostic.error(getText(child.node), DiagnosticOrigin.fromNode(child.node)));
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
break;
|
|
2936
|
+
}
|
|
2937
|
+
return diagnostics;
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
class AssertionNode extends TestTreeNode {
|
|
2942
|
+
abilityDiagnostics;
|
|
2943
|
+
isNot;
|
|
2944
|
+
matcherNode;
|
|
2945
|
+
matcherNameNode;
|
|
2946
|
+
modifierNode;
|
|
2947
|
+
notNode;
|
|
2948
|
+
source;
|
|
2949
|
+
target;
|
|
2950
|
+
constructor(compiler, brand, node, parent, flags, matcherNode, matcherNameNode, modifierNode, notNode) {
|
|
2951
|
+
super(compiler, brand, node, parent, flags);
|
|
2952
|
+
this.isNot = notNode != null;
|
|
2953
|
+
this.matcherNode = matcherNode;
|
|
2954
|
+
this.matcherNameNode = matcherNameNode;
|
|
2955
|
+
this.modifierNode = modifierNode;
|
|
2956
|
+
this.source = this.node.typeArguments ?? this.node.arguments;
|
|
2957
|
+
if (compiler.isCallExpression(this.matcherNode)) {
|
|
2958
|
+
this.target = this.matcherNode.typeArguments ?? this.matcherNode.arguments;
|
|
2959
|
+
}
|
|
2960
|
+
for (const diagnostic of parent.diagnostics) {
|
|
2961
|
+
if (diagnosticBelongsToNode(diagnostic, this.source)) {
|
|
2962
|
+
this.diagnostics.add(diagnostic);
|
|
2963
|
+
parent.diagnostics.delete(diagnostic);
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
function nodeBelongsToArgumentList(compiler, node) {
|
|
2970
|
+
if (compiler.isCallExpression(node.parent)) {
|
|
2971
|
+
return node.parent.arguments.some((argument) => argument === node);
|
|
2972
|
+
}
|
|
2973
|
+
return false;
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2976
|
+
class AbilityLayer {
|
|
2977
|
+
#compiler;
|
|
2978
|
+
#filePath = "";
|
|
2979
|
+
#nodes = [];
|
|
2980
|
+
#projectService;
|
|
2981
|
+
#resolvedConfig;
|
|
2982
|
+
#text = "";
|
|
2983
|
+
constructor(compiler, projectService, resolvedConfig) {
|
|
2984
|
+
this.#compiler = compiler;
|
|
2985
|
+
this.#projectService = projectService;
|
|
2986
|
+
this.#resolvedConfig = resolvedConfig;
|
|
2987
|
+
}
|
|
2988
|
+
#getErasedRangeText(range) {
|
|
2989
|
+
if (this.#text.indexOf("\n", range.start) >= range.end) {
|
|
2990
|
+
return " ".repeat(range.end - range.start);
|
|
2991
|
+
}
|
|
2992
|
+
const text = [];
|
|
2993
|
+
for (let index = range.start; index < range.end; index++) {
|
|
2994
|
+
const character = this.#text.charAt(index);
|
|
2995
|
+
switch (character) {
|
|
2996
|
+
case "\n":
|
|
2997
|
+
case "\r":
|
|
2998
|
+
text.push(character);
|
|
2999
|
+
break;
|
|
3000
|
+
default:
|
|
3001
|
+
text.push(" ");
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
return text.join("");
|
|
3005
|
+
}
|
|
3006
|
+
#addRanges(node, ranges) {
|
|
3007
|
+
this.#nodes.push(node);
|
|
3008
|
+
for (const range of ranges) {
|
|
3009
|
+
const rangeText = range.replacement != null
|
|
3010
|
+
? `${range.replacement}${this.#getErasedRangeText(range).slice(range.replacement.length)}`
|
|
3011
|
+
: this.#getErasedRangeText(range);
|
|
3012
|
+
this.#text = `${this.#text.slice(0, range.start)}${rangeText}${this.#text.slice(range.end)}`;
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
close() {
|
|
3016
|
+
if (this.#nodes.length > 0) {
|
|
3017
|
+
this.#projectService.openFile(this.#filePath, this.#text, this.#resolvedConfig.rootPath);
|
|
3018
|
+
const languageService = this.#projectService.getLanguageService(this.#filePath);
|
|
3019
|
+
const diagnostics = new Set(languageService?.getSemanticDiagnostics(this.#filePath));
|
|
3020
|
+
for (const node of this.#nodes.reverse()) {
|
|
3021
|
+
for (const diagnostic of diagnostics) {
|
|
3022
|
+
if (diagnosticBelongsToNode(diagnostic, "matcherNode" in node ? node.matcherNode : node.actionNode)) {
|
|
3023
|
+
if (!node.abilityDiagnostics) {
|
|
3024
|
+
node.abilityDiagnostics = new Set();
|
|
3025
|
+
}
|
|
3026
|
+
node.abilityDiagnostics.add(diagnostic);
|
|
3027
|
+
diagnostics.delete(diagnostic);
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
this.#filePath = "";
|
|
3033
|
+
this.#nodes = [];
|
|
3034
|
+
this.#text = "";
|
|
3035
|
+
}
|
|
3036
|
+
handleWhen(whenNode) {
|
|
3037
|
+
const whenStart = whenNode.node.getStart();
|
|
3038
|
+
const whenExpressionEnd = whenNode.node.expression.getEnd();
|
|
3039
|
+
const whenEnd = whenNode.node.getEnd();
|
|
3040
|
+
const actionNameEnd = whenNode.actionNameNode.getEnd();
|
|
3041
|
+
switch (whenNode.actionNameNode.name.text) {
|
|
3042
|
+
case "isCalledWith":
|
|
3043
|
+
this.#addRanges(whenNode, [
|
|
3044
|
+
{
|
|
3045
|
+
end: whenExpressionEnd,
|
|
3046
|
+
start: whenStart,
|
|
3047
|
+
replacement: nodeBelongsToArgumentList(this.#compiler, whenNode.actionNode) ? "" : ";",
|
|
3048
|
+
},
|
|
3049
|
+
{ end: actionNameEnd, start: whenEnd },
|
|
3050
|
+
]);
|
|
3051
|
+
break;
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
handleAssertion(assertionNode) {
|
|
3055
|
+
const expectStart = assertionNode.node.getStart();
|
|
3056
|
+
const expectExpressionEnd = assertionNode.node.expression.getEnd();
|
|
3057
|
+
const expectEnd = assertionNode.node.getEnd();
|
|
3058
|
+
const matcherNameEnd = assertionNode.matcherNameNode.getEnd();
|
|
3059
|
+
switch (assertionNode.matcherNameNode.name.text) {
|
|
3060
|
+
case "toBeApplicable":
|
|
3061
|
+
this.#addRanges(assertionNode, [
|
|
3062
|
+
{ end: expectExpressionEnd, start: expectStart },
|
|
3063
|
+
{ end: matcherNameEnd, start: expectEnd },
|
|
3064
|
+
]);
|
|
3065
|
+
break;
|
|
3066
|
+
case "toBeCallableWith":
|
|
3067
|
+
this.#addRanges(assertionNode, [
|
|
3068
|
+
{
|
|
3069
|
+
end: expectExpressionEnd,
|
|
3070
|
+
start: expectStart,
|
|
3071
|
+
replacement: nodeBelongsToArgumentList(this.#compiler, assertionNode.matcherNode) ? "" : ";",
|
|
3072
|
+
},
|
|
3073
|
+
{ end: matcherNameEnd, start: expectEnd },
|
|
3074
|
+
]);
|
|
3075
|
+
break;
|
|
3076
|
+
case "toBeConstructableWith":
|
|
3077
|
+
this.#addRanges(assertionNode, [
|
|
3078
|
+
{
|
|
3079
|
+
end: expectExpressionEnd,
|
|
3080
|
+
start: expectStart,
|
|
3081
|
+
replacement: nodeBelongsToArgumentList(this.#compiler, assertionNode.matcherNode) ? "new" : "; new",
|
|
3082
|
+
},
|
|
3083
|
+
{ end: matcherNameEnd, start: expectEnd },
|
|
3084
|
+
]);
|
|
3085
|
+
break;
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
open(sourceFile) {
|
|
3089
|
+
this.#filePath = sourceFile.fileName;
|
|
3090
|
+
this.#text = sourceFile.text;
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
|
|
3094
|
+
class CollectDiagnosticText {
|
|
3095
|
+
static cannotBeNestedWithin(source, target) {
|
|
3096
|
+
return `'${source}()' cannot be nested within '${target}()'.`;
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
var TestTreeNodeFlags;
|
|
3101
|
+
(function (TestTreeNodeFlags) {
|
|
3102
|
+
TestTreeNodeFlags[TestTreeNodeFlags["None"] = 0] = "None";
|
|
3103
|
+
TestTreeNodeFlags[TestTreeNodeFlags["Fail"] = 1] = "Fail";
|
|
3104
|
+
TestTreeNodeFlags[TestTreeNodeFlags["Only"] = 2] = "Only";
|
|
3105
|
+
TestTreeNodeFlags[TestTreeNodeFlags["Skip"] = 4] = "Skip";
|
|
3106
|
+
TestTreeNodeFlags[TestTreeNodeFlags["Todo"] = 8] = "Todo";
|
|
3107
|
+
})(TestTreeNodeFlags || (TestTreeNodeFlags = {}));
|
|
3108
|
+
|
|
3109
|
+
class IdentifierLookup {
|
|
3110
|
+
#compiler;
|
|
3111
|
+
#identifiers;
|
|
3112
|
+
#moduleSpecifiers = ['"tstyche"', "'tstyche'"];
|
|
3113
|
+
constructor(compiler, identifiers) {
|
|
3114
|
+
this.#compiler = compiler;
|
|
3115
|
+
this.#identifiers = identifiers ?? {
|
|
3116
|
+
namedImports: {
|
|
3117
|
+
describe: undefined,
|
|
3118
|
+
expect: undefined,
|
|
3119
|
+
it: undefined,
|
|
3120
|
+
namespace: undefined,
|
|
3121
|
+
test: undefined,
|
|
3122
|
+
when: undefined,
|
|
3123
|
+
},
|
|
3124
|
+
namespace: undefined,
|
|
3125
|
+
};
|
|
3126
|
+
}
|
|
3127
|
+
handleImportDeclaration(node) {
|
|
3128
|
+
if (this.#moduleSpecifiers.includes(node.moduleSpecifier.getText()) &&
|
|
3129
|
+
node.importClause?.isTypeOnly !== true &&
|
|
3130
|
+
node.importClause?.namedBindings != null) {
|
|
3131
|
+
if (this.#compiler.isNamedImports(node.importClause.namedBindings)) {
|
|
3132
|
+
for (const element of node.importClause.namedBindings.elements) {
|
|
3133
|
+
if (element.isTypeOnly) {
|
|
3134
|
+
continue;
|
|
3135
|
+
}
|
|
3136
|
+
let identifierKey;
|
|
3137
|
+
if (element.propertyName) {
|
|
3138
|
+
identifierKey = element.propertyName.getText();
|
|
3139
|
+
}
|
|
3140
|
+
else {
|
|
3141
|
+
identifierKey = element.name.getText();
|
|
3142
|
+
}
|
|
3143
|
+
if (identifierKey in this.#identifiers.namedImports) {
|
|
3144
|
+
this.#identifiers.namedImports[identifierKey] = element.name.getText();
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
if (this.#compiler.isNamespaceImport(node.importClause.namedBindings)) {
|
|
3149
|
+
this.#identifiers.namespace = node.importClause.namedBindings.name.getText();
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
resolveTestMemberMeta(node) {
|
|
3154
|
+
let flags = TestTreeNodeFlags.None;
|
|
3155
|
+
let expression = node.expression;
|
|
3156
|
+
while (this.#compiler.isPropertyAccessExpression(expression)) {
|
|
3157
|
+
if (expression.expression.getText() === this.#identifiers.namespace) {
|
|
3158
|
+
break;
|
|
3159
|
+
}
|
|
3160
|
+
switch (expression.name.getText()) {
|
|
3161
|
+
case "fail":
|
|
3162
|
+
flags |= TestTreeNodeFlags.Fail;
|
|
3163
|
+
break;
|
|
3164
|
+
case "only":
|
|
3165
|
+
flags |= TestTreeNodeFlags.Only;
|
|
3166
|
+
break;
|
|
3167
|
+
case "skip":
|
|
3168
|
+
flags |= TestTreeNodeFlags.Skip;
|
|
3169
|
+
break;
|
|
3170
|
+
case "todo":
|
|
3171
|
+
flags |= TestTreeNodeFlags.Todo;
|
|
3172
|
+
break;
|
|
3173
|
+
}
|
|
3174
|
+
expression = expression.expression;
|
|
3175
|
+
}
|
|
3176
|
+
let identifier;
|
|
3177
|
+
if (this.#compiler.isPropertyAccessExpression(expression) &&
|
|
3178
|
+
expression.expression.getText() === this.#identifiers.namespace) {
|
|
3179
|
+
identifier = expression.name.getText();
|
|
3180
|
+
}
|
|
3181
|
+
else {
|
|
3182
|
+
identifier = Object.keys(this.#identifiers.namedImports).find((key) => this.#identifiers.namedImports[key] === expression.getText());
|
|
3183
|
+
}
|
|
3184
|
+
if (!identifier) {
|
|
3185
|
+
return;
|
|
3186
|
+
}
|
|
3187
|
+
switch (identifier) {
|
|
3188
|
+
case "describe":
|
|
3189
|
+
return { brand: TestTreeNodeBrand.Describe, flags, identifier };
|
|
3190
|
+
case "it":
|
|
3191
|
+
case "test":
|
|
3192
|
+
return { brand: TestTreeNodeBrand.Test, flags, identifier };
|
|
3193
|
+
case "expect":
|
|
3194
|
+
return { brand: TestTreeNodeBrand.Expect, flags, identifier };
|
|
3195
|
+
case "when":
|
|
3196
|
+
return { brand: TestTreeNodeBrand.When, flags, identifier };
|
|
3197
|
+
}
|
|
3198
|
+
return;
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
class TestTree {
|
|
3203
|
+
children = [];
|
|
3204
|
+
diagnostics;
|
|
3205
|
+
hasOnly = false;
|
|
3206
|
+
sourceFile;
|
|
3207
|
+
constructor(diagnostics, sourceFile) {
|
|
3208
|
+
this.diagnostics = diagnostics;
|
|
3209
|
+
this.sourceFile = sourceFile;
|
|
3207
3210
|
}
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
class WhenNode extends TestTreeNode {
|
|
3214
|
+
actionNode;
|
|
3215
|
+
actionNameNode;
|
|
3216
|
+
abilityDiagnostics;
|
|
3217
|
+
target;
|
|
3218
|
+
constructor(compiler, brand, node, parent, flags, actionNode, actionNameNode) {
|
|
3219
|
+
super(compiler, brand, node, parent, flags);
|
|
3220
|
+
this.actionNode = actionNode;
|
|
3221
|
+
this.actionNameNode = actionNameNode;
|
|
3222
|
+
this.target = this.node.typeArguments ?? this.node.arguments;
|
|
3223
|
+
for (const diagnostic of parent.diagnostics) {
|
|
3224
|
+
if (diagnosticBelongsToNode(diagnostic, node)) {
|
|
3225
|
+
this.diagnostics.add(diagnostic);
|
|
3226
|
+
parent.diagnostics.delete(diagnostic);
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3212
3229
|
}
|
|
3213
3230
|
}
|
|
3214
3231
|
|
|
3215
|
-
class
|
|
3216
|
-
#
|
|
3217
|
-
#
|
|
3232
|
+
class CollectService {
|
|
3233
|
+
#abilityLayer;
|
|
3234
|
+
#compiler;
|
|
3235
|
+
#identifierLookup;
|
|
3236
|
+
#projectService;
|
|
3218
3237
|
#resolvedConfig;
|
|
3219
|
-
#
|
|
3220
|
-
|
|
3221
|
-
|
|
3238
|
+
#testTree;
|
|
3239
|
+
constructor(compiler, projectService, resolvedConfig) {
|
|
3240
|
+
this.#compiler = compiler;
|
|
3241
|
+
this.#projectService = projectService;
|
|
3222
3242
|
this.#resolvedConfig = resolvedConfig;
|
|
3223
|
-
this.#
|
|
3243
|
+
this.#abilityLayer = new AbilityLayer(compiler, this.#projectService, this.#resolvedConfig);
|
|
3244
|
+
this.#identifierLookup = new IdentifierLookup(compiler);
|
|
3224
3245
|
}
|
|
3225
|
-
#
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
const onClose = (reason) => {
|
|
3236
|
-
debounce.cancel();
|
|
3237
|
-
this.#inputService?.close();
|
|
3238
|
-
for (const watcher of this.#watchers) {
|
|
3239
|
-
watcher.close();
|
|
3246
|
+
#collectTestTreeNodes(node, parent) {
|
|
3247
|
+
if (this.#compiler.isCallExpression(node)) {
|
|
3248
|
+
const meta = this.#identifierLookup.resolveTestMemberMeta(node);
|
|
3249
|
+
if (meta != null && (meta.brand === TestTreeNodeBrand.Describe || meta.brand === TestTreeNodeBrand.Test)) {
|
|
3250
|
+
const testTreeNode = new TestTreeNode(this.#compiler, meta.brand, node, parent, meta.flags);
|
|
3251
|
+
this.#compiler.forEachChild(node, (node) => {
|
|
3252
|
+
this.#collectTestTreeNodes(node, testTreeNode);
|
|
3253
|
+
});
|
|
3254
|
+
this.#onNode(testTreeNode, parent);
|
|
3255
|
+
return;
|
|
3240
3256
|
}
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
const onInput = (chunk) => {
|
|
3246
|
-
switch (chunk.toLowerCase()) {
|
|
3247
|
-
case "\u0003":
|
|
3248
|
-
case "\u0004":
|
|
3249
|
-
case "\u001B":
|
|
3250
|
-
case "q":
|
|
3251
|
-
case "x":
|
|
3252
|
-
onClose(CancellationReason.WatchClose);
|
|
3253
|
-
break;
|
|
3254
|
-
case "\u000D":
|
|
3255
|
-
case "\u0020":
|
|
3256
|
-
case "a":
|
|
3257
|
-
debounce.cancel();
|
|
3258
|
-
if (this.#watchedTestFiles.size > 0) {
|
|
3259
|
-
debounce.resolve([...this.#watchedTestFiles.values()]);
|
|
3260
|
-
}
|
|
3261
|
-
break;
|
|
3257
|
+
if (meta != null && meta.brand === TestTreeNodeBrand.Expect) {
|
|
3258
|
+
const modifierNode = this.#getChainedNode(node, "type");
|
|
3259
|
+
if (!modifierNode) {
|
|
3260
|
+
return;
|
|
3262
3261
|
}
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
this.#
|
|
3275
|
-
|
|
3262
|
+
const notNode = this.#getChainedNode(modifierNode, "not");
|
|
3263
|
+
const matcherNameNode = this.#getChainedNode(notNode ?? modifierNode);
|
|
3264
|
+
if (!matcherNameNode) {
|
|
3265
|
+
return;
|
|
3266
|
+
}
|
|
3267
|
+
const matcherNode = this.#getMatcherNode(matcherNameNode);
|
|
3268
|
+
if (!matcherNode) {
|
|
3269
|
+
return;
|
|
3270
|
+
}
|
|
3271
|
+
const assertionNode = new AssertionNode(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, matcherNameNode, modifierNode, notNode);
|
|
3272
|
+
this.#abilityLayer.handleAssertion(assertionNode);
|
|
3273
|
+
this.#compiler.forEachChild(node, (node) => {
|
|
3274
|
+
this.#collectTestTreeNodes(node, assertionNode);
|
|
3275
|
+
});
|
|
3276
|
+
this.#onNode(assertionNode, parent);
|
|
3277
|
+
return;
|
|
3276
3278
|
}
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3279
|
+
if (meta != null && meta.brand === TestTreeNodeBrand.When) {
|
|
3280
|
+
const actionNameNode = this.#getChainedNode(node);
|
|
3281
|
+
if (!actionNameNode) {
|
|
3282
|
+
return;
|
|
3283
|
+
}
|
|
3284
|
+
const actionNode = this.#getActionNode(actionNameNode);
|
|
3285
|
+
if (!actionNode) {
|
|
3286
|
+
return;
|
|
3287
|
+
}
|
|
3288
|
+
this.#compiler.forEachChild(actionNode, (node) => {
|
|
3289
|
+
if (this.#compiler.isCallExpression(node)) {
|
|
3290
|
+
const meta = this.#identifierLookup.resolveTestMemberMeta(node);
|
|
3291
|
+
if (meta != null && (meta.brand === TestTreeNodeBrand.Describe || meta.brand === TestTreeNodeBrand.Test)) {
|
|
3292
|
+
const text = CollectDiagnosticText.cannotBeNestedWithin(meta.identifier, "when");
|
|
3293
|
+
const origin = DiagnosticOrigin.fromNode(node);
|
|
3294
|
+
this.#onDiagnostics(Diagnostic.error(text, origin));
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
});
|
|
3298
|
+
const whenNode = new WhenNode(this.#compiler, meta.brand, node, parent, meta.flags, actionNode, actionNameNode);
|
|
3299
|
+
this.#abilityLayer.handleWhen(whenNode);
|
|
3300
|
+
this.#onNode(whenNode, parent);
|
|
3301
|
+
return;
|
|
3284
3302
|
}
|
|
3285
|
-
};
|
|
3286
|
-
this.#watchers.push(new Watcher(this.#resolvedConfig.rootPath, onChangedFile, onRemovedFile, { recursive: true }));
|
|
3287
|
-
const onChangedConfigFile = () => {
|
|
3288
|
-
onClose(CancellationReason.ConfigChange);
|
|
3289
|
-
};
|
|
3290
|
-
this.#watchers.push(new FileWatcher(this.#resolvedConfig.configFilePath, onChangedConfigFile));
|
|
3291
|
-
for (const watcher of this.#watchers) {
|
|
3292
|
-
watcher.watch();
|
|
3293
3303
|
}
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3304
|
+
if (this.#compiler.isImportDeclaration(node)) {
|
|
3305
|
+
this.#identifierLookup.handleImportDeclaration(node);
|
|
3306
|
+
return;
|
|
3307
|
+
}
|
|
3308
|
+
this.#compiler.forEachChild(node, (node) => {
|
|
3309
|
+
this.#collectTestTreeNodes(node, parent);
|
|
3310
|
+
});
|
|
3311
|
+
}
|
|
3312
|
+
createTestTree(sourceFile, semanticDiagnostics = []) {
|
|
3313
|
+
const testTree = new TestTree(new Set(semanticDiagnostics), sourceFile);
|
|
3314
|
+
EventEmitter.dispatch(["collect:start", { tree: testTree }]);
|
|
3315
|
+
this.#testTree = testTree;
|
|
3316
|
+
this.#abilityLayer.open(sourceFile);
|
|
3317
|
+
this.#collectTestTreeNodes(sourceFile, testTree);
|
|
3318
|
+
this.#abilityLayer.close();
|
|
3319
|
+
this.#testTree = undefined;
|
|
3320
|
+
EventEmitter.dispatch(["collect:end", { tree: testTree }]);
|
|
3321
|
+
return testTree;
|
|
3322
|
+
}
|
|
3323
|
+
#getChainedNode({ parent }, name) {
|
|
3324
|
+
if (!this.#compiler.isPropertyAccessExpression(parent)) {
|
|
3325
|
+
return;
|
|
3326
|
+
}
|
|
3327
|
+
if (name != null && name !== parent.name.getText()) {
|
|
3328
|
+
return;
|
|
3329
|
+
}
|
|
3330
|
+
return parent;
|
|
3331
|
+
}
|
|
3332
|
+
#getMatcherNode(node) {
|
|
3333
|
+
if (this.#compiler.isCallExpression(node.parent)) {
|
|
3334
|
+
return node.parent;
|
|
3335
|
+
}
|
|
3336
|
+
if (this.#compiler.isDecorator(node.parent)) {
|
|
3337
|
+
return node.parent;
|
|
3338
|
+
}
|
|
3339
|
+
if (this.#compiler.isParenthesizedExpression(node.parent)) {
|
|
3340
|
+
return this.#getMatcherNode(node.parent);
|
|
3341
|
+
}
|
|
3342
|
+
return;
|
|
3343
|
+
}
|
|
3344
|
+
#getActionNode(node) {
|
|
3345
|
+
if (this.#compiler.isCallExpression(node.parent)) {
|
|
3346
|
+
return node.parent;
|
|
3347
|
+
}
|
|
3348
|
+
return;
|
|
3349
|
+
}
|
|
3350
|
+
#onDiagnostics(diagnostic) {
|
|
3351
|
+
EventEmitter.dispatch(["collect:error", { diagnostics: [diagnostic] }]);
|
|
3352
|
+
}
|
|
3353
|
+
#onNode(node, parent) {
|
|
3354
|
+
parent.children.push(node);
|
|
3355
|
+
if (node.flags & TestTreeNodeFlags.Only) {
|
|
3356
|
+
this.#testTree.hasOnly = true;
|
|
3299
3357
|
}
|
|
3358
|
+
EventEmitter.dispatch(["collect:node", { node }]);
|
|
3300
3359
|
}
|
|
3301
3360
|
}
|
|
3302
3361
|
|
|
@@ -3305,6 +3364,7 @@ class ProjectService {
|
|
|
3305
3364
|
#lastSeenProject = "";
|
|
3306
3365
|
#resolvedConfig;
|
|
3307
3366
|
#seenPrograms = new WeakSet();
|
|
3367
|
+
#seenTestFiles = new Set();
|
|
3308
3368
|
#service;
|
|
3309
3369
|
constructor(compiler, resolvedConfig) {
|
|
3310
3370
|
this.#compiler = compiler;
|
|
@@ -3342,15 +3402,6 @@ class ProjectService {
|
|
|
3342
3402
|
useInferredProjectPerProjectRoot: true,
|
|
3343
3403
|
useSingleInferredProject: false,
|
|
3344
3404
|
});
|
|
3345
|
-
switch (this.#resolvedConfig.tsconfig) {
|
|
3346
|
-
case "findup":
|
|
3347
|
-
break;
|
|
3348
|
-
case "ignore":
|
|
3349
|
-
this.#service.getConfigFileNameForFile = () => undefined;
|
|
3350
|
-
break;
|
|
3351
|
-
default:
|
|
3352
|
-
this.#service.getConfigFileNameForFile = () => this.#resolvedConfig.tsconfig;
|
|
3353
|
-
}
|
|
3354
3405
|
this.#service.setCompilerOptionsForInferredProjects(this.#getDefaultCompilerOptions());
|
|
3355
3406
|
}
|
|
3356
3407
|
closeFile(filePath) {
|
|
@@ -3385,7 +3436,23 @@ class ProjectService {
|
|
|
3385
3436
|
const project = this.getDefaultProject(filePath);
|
|
3386
3437
|
return project?.getLanguageService(true);
|
|
3387
3438
|
}
|
|
3439
|
+
#isFileIncluded(filePath) {
|
|
3440
|
+
const configSourceFile = this.#compiler.readJsonConfigFile(this.#resolvedConfig.tsconfig, this.#compiler.sys.readFile);
|
|
3441
|
+
const { fileNames } = this.#compiler.parseJsonSourceFileConfigFileContent(configSourceFile, this.#compiler.sys, Path.dirname(this.#resolvedConfig.tsconfig), undefined, this.#resolvedConfig.tsconfig);
|
|
3442
|
+
return fileNames.includes(filePath);
|
|
3443
|
+
}
|
|
3388
3444
|
openFile(filePath, sourceText, projectRootPath) {
|
|
3445
|
+
switch (this.#resolvedConfig.tsconfig) {
|
|
3446
|
+
case "findup":
|
|
3447
|
+
break;
|
|
3448
|
+
case "ignore":
|
|
3449
|
+
this.#service.getConfigFileNameForFile = () => undefined;
|
|
3450
|
+
break;
|
|
3451
|
+
default:
|
|
3452
|
+
this.#service.getConfigFileNameForFile = this.#isFileIncluded(filePath)
|
|
3453
|
+
? () => this.#resolvedConfig.tsconfig
|
|
3454
|
+
: () => undefined;
|
|
3455
|
+
}
|
|
3389
3456
|
const { configFileErrors, configFileName } = this.#service.openClientFile(filePath, sourceText, undefined, projectRootPath);
|
|
3390
3457
|
if (configFileName !== this.#lastSeenProject) {
|
|
3391
3458
|
this.#lastSeenProject = configFileName;
|
|
@@ -3400,29 +3467,29 @@ class ProjectService {
|
|
|
3400
3467
|
{ diagnostics: Diagnostic.fromDiagnostics(configFileErrors) },
|
|
3401
3468
|
]);
|
|
3402
3469
|
}
|
|
3403
|
-
if (this.#resolvedConfig.checkSourceFiles) {
|
|
3470
|
+
if (this.#resolvedConfig.checkSourceFiles && !this.#seenTestFiles.has(filePath)) {
|
|
3471
|
+
this.#seenTestFiles.add(filePath);
|
|
3404
3472
|
const languageService = this.getLanguageService(filePath);
|
|
3405
3473
|
const program = languageService?.getProgram();
|
|
3406
3474
|
if (!program || this.#seenPrograms.has(program)) {
|
|
3407
3475
|
return;
|
|
3408
3476
|
}
|
|
3409
3477
|
this.#seenPrograms.add(program);
|
|
3410
|
-
const
|
|
3411
|
-
for (const sourceFile of program.getSourceFiles()) {
|
|
3478
|
+
const sourceFilesToCheck = program.getSourceFiles().filter((sourceFile) => {
|
|
3412
3479
|
if (program.isSourceFileFromExternalLibrary(sourceFile) || program.isSourceFileDefaultLibrary(sourceFile)) {
|
|
3413
|
-
|
|
3480
|
+
return false;
|
|
3414
3481
|
}
|
|
3415
|
-
if (
|
|
3416
|
-
|
|
3482
|
+
if (Select.isTestFile(sourceFile.fileName, { ...this.#resolvedConfig, pathMatch: [] })) {
|
|
3483
|
+
return false;
|
|
3417
3484
|
}
|
|
3418
|
-
|
|
3485
|
+
return true;
|
|
3486
|
+
});
|
|
3419
3487
|
const diagnostics = [];
|
|
3420
|
-
for (const sourceFile of
|
|
3488
|
+
for (const sourceFile of sourceFilesToCheck) {
|
|
3421
3489
|
diagnostics.push(...program.getSyntacticDiagnostics(sourceFile), ...program.getSemanticDiagnostics(sourceFile));
|
|
3422
3490
|
}
|
|
3423
3491
|
if (diagnostics.length > 0) {
|
|
3424
3492
|
EventEmitter.dispatch(["project:error", { diagnostics: Diagnostic.fromDiagnostics(diagnostics) }]);
|
|
3425
|
-
return;
|
|
3426
3493
|
}
|
|
3427
3494
|
}
|
|
3428
3495
|
}
|
|
@@ -3437,10 +3504,8 @@ var RunMode;
|
|
|
3437
3504
|
RunMode[RunMode["Todo"] = 8] = "Todo";
|
|
3438
3505
|
})(RunMode || (RunMode = {}));
|
|
3439
3506
|
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
return text.replace(/^./, text.charAt(0).toUpperCase());
|
|
3443
|
-
}
|
|
3507
|
+
function capitalize(text) {
|
|
3508
|
+
return text.replace(/^./, text.charAt(0).toUpperCase());
|
|
3444
3509
|
}
|
|
3445
3510
|
|
|
3446
3511
|
class ExpectDiagnosticText {
|
|
@@ -3456,23 +3521,23 @@ class ExpectDiagnosticText {
|
|
|
3456
3521
|
static argumentMustBeProvided(argumentNameText) {
|
|
3457
3522
|
return `An argument for '${argumentNameText}' must be provided.`;
|
|
3458
3523
|
}
|
|
3459
|
-
static isCallable(
|
|
3460
|
-
return `${
|
|
3524
|
+
static isCallable(isExpression, targetText) {
|
|
3525
|
+
return `${isExpression ? "Expression" : "Type"} is callable ${targetText}.`;
|
|
3461
3526
|
}
|
|
3462
|
-
static isNotCallable(
|
|
3463
|
-
return `${
|
|
3527
|
+
static isNotCallable(isExpression, targetText) {
|
|
3528
|
+
return `${isExpression ? "Expression" : "Type"} is not callable ${targetText}.`;
|
|
3464
3529
|
}
|
|
3465
|
-
static isConstructable(
|
|
3466
|
-
return `${
|
|
3530
|
+
static isConstructable(isExpression, targetText) {
|
|
3531
|
+
return `${isExpression ? "Expression" : "Type"} is constructable ${targetText}.`;
|
|
3467
3532
|
}
|
|
3468
|
-
static isNotConstructable(
|
|
3469
|
-
return `${
|
|
3533
|
+
static isNotConstructable(isExpression, targetText) {
|
|
3534
|
+
return `${isExpression ? "Expression" : "Type"} is not constructable ${targetText}.`;
|
|
3470
3535
|
}
|
|
3471
|
-
static acceptsProps(
|
|
3472
|
-
return `${
|
|
3536
|
+
static acceptsProps(isExpression) {
|
|
3537
|
+
return `${isExpression ? "Component" : "Component type"} accepts props of the given type.`;
|
|
3473
3538
|
}
|
|
3474
|
-
static doesNotAcceptProps(
|
|
3475
|
-
return `${
|
|
3539
|
+
static doesNotAcceptProps(isExpression) {
|
|
3540
|
+
return `${isExpression ? "Component" : "Component type"} does not accept props of the given type.`;
|
|
3476
3541
|
}
|
|
3477
3542
|
static canBeApplied(targetText) {
|
|
3478
3543
|
return `The decorator function can be applied${targetText}.`;
|
|
@@ -3504,21 +3569,21 @@ class ExpectDiagnosticText {
|
|
|
3504
3569
|
static typeArgumentMustBe(argumentNameText, expectedText) {
|
|
3505
3570
|
return `A type argument for '${argumentNameText}' must be ${expectedText}.`;
|
|
3506
3571
|
}
|
|
3507
|
-
static raisedError(
|
|
3572
|
+
static raisedError(isExpression, count, targetCount) {
|
|
3508
3573
|
let countText = "a";
|
|
3509
3574
|
if (count > 1 || targetCount > 1) {
|
|
3510
3575
|
countText = count > targetCount ? `${count}` : `only ${count}`;
|
|
3511
3576
|
}
|
|
3512
|
-
return `${
|
|
3577
|
+
return `${isExpression ? "Expression" : "Type"} raised ${countText} type error${count === 1 ? "" : "s"}.`;
|
|
3513
3578
|
}
|
|
3514
|
-
static didNotRaiseError(
|
|
3515
|
-
return `${
|
|
3579
|
+
static didNotRaiseError(isExpression) {
|
|
3580
|
+
return `${isExpression ? "Expression" : "Type"} did not raise a type error.`;
|
|
3516
3581
|
}
|
|
3517
|
-
static raisedMatchingError(
|
|
3518
|
-
return `${
|
|
3582
|
+
static raisedMatchingError(isExpression) {
|
|
3583
|
+
return `${isExpression ? "Expression" : "Type"} raised a matching type error.`;
|
|
3519
3584
|
}
|
|
3520
|
-
static didNotRaiseMatchingError(
|
|
3521
|
-
return `${
|
|
3585
|
+
static didNotRaiseMatchingError(isExpression) {
|
|
3586
|
+
return `${isExpression ? "Expression" : "Type"} did not raise a matching type error.`;
|
|
3522
3587
|
}
|
|
3523
3588
|
static isAssignableTo(sourceTypeText, targetTypeText) {
|
|
3524
3589
|
return `Type '${sourceTypeText}' is assignable to type '${targetTypeText}'.`;
|
|
@@ -3548,7 +3613,7 @@ class ExpectDiagnosticText {
|
|
|
3548
3613
|
return `Types of property '${propertyNameText}' are not compatible.`;
|
|
3549
3614
|
}
|
|
3550
3615
|
static typeWasRejected(typeText) {
|
|
3551
|
-
const optionNameText = `reject${
|
|
3616
|
+
const optionNameText = `reject${capitalize(typeText)}Type`;
|
|
3552
3617
|
return [
|
|
3553
3618
|
`The '${typeText}' type was rejected because the '${optionNameText}' option is enabled.`,
|
|
3554
3619
|
`If this check is necessary, pass '${typeText}' as the type argument explicitly.`,
|
|
@@ -3670,12 +3735,13 @@ class ToAcceptProps {
|
|
|
3670
3735
|
this.#typeChecker = typeChecker;
|
|
3671
3736
|
}
|
|
3672
3737
|
#explain(matchWorker, sourceNode, targetNode) {
|
|
3738
|
+
const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
|
|
3673
3739
|
const signatures = matchWorker.getSignatures(sourceNode);
|
|
3674
3740
|
return signatures.reduce((accumulator, signature, index) => {
|
|
3675
3741
|
let diagnostic;
|
|
3676
3742
|
const introText = matchWorker.assertion.isNot
|
|
3677
|
-
? ExpectDiagnosticText.acceptsProps(
|
|
3678
|
-
: ExpectDiagnosticText.doesNotAcceptProps(
|
|
3743
|
+
? ExpectDiagnosticText.acceptsProps(isExpression)
|
|
3744
|
+
: ExpectDiagnosticText.doesNotAcceptProps(isExpression);
|
|
3679
3745
|
const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
|
|
3680
3746
|
if (signatures.length > 1) {
|
|
3681
3747
|
const signatureText = this.#typeChecker.signatureToString(signature, sourceNode);
|
|
@@ -3815,18 +3881,18 @@ class ToAcceptProps {
|
|
|
3815
3881
|
const signatures = matchWorker.getSignatures(sourceNode);
|
|
3816
3882
|
if (signatures.length === 0) {
|
|
3817
3883
|
const expectedText = "of a function or class type";
|
|
3818
|
-
const text = this.#compiler
|
|
3819
|
-
? ExpectDiagnosticText.
|
|
3820
|
-
: ExpectDiagnosticText.
|
|
3884
|
+
const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
|
|
3885
|
+
? ExpectDiagnosticText.argumentMustBe("source", expectedText)
|
|
3886
|
+
: ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText);
|
|
3821
3887
|
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
3822
3888
|
diagnostics.push(Diagnostic.error(text, origin));
|
|
3823
3889
|
}
|
|
3824
3890
|
const targetType = matchWorker.getType(targetNode);
|
|
3825
3891
|
if (!(targetType.flags & this.#compiler.TypeFlags.Object)) {
|
|
3826
3892
|
const expectedText = "of an object type";
|
|
3827
|
-
const text = this.#compiler
|
|
3828
|
-
? ExpectDiagnosticText.
|
|
3829
|
-
: ExpectDiagnosticText.
|
|
3893
|
+
const text = nodeBelongsToArgumentList(this.#compiler, targetNode)
|
|
3894
|
+
? ExpectDiagnosticText.argumentMustBe("target", expectedText)
|
|
3895
|
+
: ExpectDiagnosticText.typeArgumentMustBe("Target", expectedText);
|
|
3830
3896
|
const origin = DiagnosticOrigin.fromNode(targetNode);
|
|
3831
3897
|
diagnostics.push(Diagnostic.error(text, origin));
|
|
3832
3898
|
}
|
|
@@ -3919,9 +3985,9 @@ class ToBeApplicable {
|
|
|
3919
3985
|
const type = matchWorker.getType(sourceNode);
|
|
3920
3986
|
if (type.getCallSignatures().length === 0) {
|
|
3921
3987
|
const expectedText = "of a function type";
|
|
3922
|
-
const text = this.#compiler
|
|
3923
|
-
? ExpectDiagnosticText.
|
|
3924
|
-
: ExpectDiagnosticText.
|
|
3988
|
+
const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
|
|
3989
|
+
? ExpectDiagnosticText.argumentMustBe("source", expectedText)
|
|
3990
|
+
: ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText);
|
|
3925
3991
|
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
3926
3992
|
onDiagnostics([Diagnostic.error(text, origin)]);
|
|
3927
3993
|
return;
|
|
@@ -3970,12 +4036,15 @@ class ToBeCallableWith {
|
|
|
3970
4036
|
return `with the given argument${nodes.length === 1 ? "" : "s"}`;
|
|
3971
4037
|
}
|
|
3972
4038
|
#explain(matchWorker, sourceNode, targetNodes) {
|
|
3973
|
-
const
|
|
4039
|
+
const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
|
|
3974
4040
|
const targetText = this.#resolveTargetText(targetNodes);
|
|
3975
4041
|
const diagnostics = [];
|
|
3976
4042
|
if (matchWorker.assertion.abilityDiagnostics) {
|
|
3977
4043
|
for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
|
|
3978
|
-
const text = [
|
|
4044
|
+
const text = [
|
|
4045
|
+
ExpectDiagnosticText.isNotCallable(isExpression, targetText),
|
|
4046
|
+
getDiagnosticMessageText(diagnostic),
|
|
4047
|
+
];
|
|
3979
4048
|
let origin;
|
|
3980
4049
|
if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, targetNodes)) {
|
|
3981
4050
|
origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceNode.getSourceFile());
|
|
@@ -3995,7 +4064,7 @@ class ToBeCallableWith {
|
|
|
3995
4064
|
}
|
|
3996
4065
|
else {
|
|
3997
4066
|
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
3998
|
-
diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isCallable(
|
|
4067
|
+
diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isCallable(isExpression, targetText), origin));
|
|
3999
4068
|
}
|
|
4000
4069
|
return diagnostics;
|
|
4001
4070
|
}
|
|
@@ -4014,11 +4083,11 @@ class ToBeCallableWith {
|
|
|
4014
4083
|
}
|
|
4015
4084
|
if (!type || type.getCallSignatures().length === 0) {
|
|
4016
4085
|
const text = [];
|
|
4017
|
-
if (this.#compiler
|
|
4018
|
-
text.push(ExpectDiagnosticText.
|
|
4086
|
+
if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
|
|
4087
|
+
text.push(ExpectDiagnosticText.argumentMustBe("source", "a callable expression"));
|
|
4019
4088
|
}
|
|
4020
4089
|
else {
|
|
4021
|
-
text.push(ExpectDiagnosticText.
|
|
4090
|
+
text.push(ExpectDiagnosticText.typeArgumentMustBe("Source", "a callable type"));
|
|
4022
4091
|
}
|
|
4023
4092
|
if (type != null && type.getConstructSignatures().length > 0) {
|
|
4024
4093
|
text.push(ExpectDiagnosticText.didYouMeanToUse("the '.toBeConstructableWith()' matcher"));
|
|
@@ -4049,13 +4118,13 @@ class ToBeConstructableWith {
|
|
|
4049
4118
|
return `with the given argument${nodes.length === 1 ? "" : "s"}`;
|
|
4050
4119
|
}
|
|
4051
4120
|
#explain(matchWorker, sourceNode, targetNodes) {
|
|
4052
|
-
const
|
|
4121
|
+
const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
|
|
4053
4122
|
const targetText = this.#resolveTargetText(targetNodes);
|
|
4054
4123
|
const diagnostics = [];
|
|
4055
4124
|
if (matchWorker.assertion.abilityDiagnostics) {
|
|
4056
4125
|
for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
|
|
4057
4126
|
const text = [
|
|
4058
|
-
ExpectDiagnosticText.isNotConstructable(
|
|
4127
|
+
ExpectDiagnosticText.isNotConstructable(isExpression, targetText),
|
|
4059
4128
|
getDiagnosticMessageText(diagnostic),
|
|
4060
4129
|
];
|
|
4061
4130
|
let origin;
|
|
@@ -4077,7 +4146,7 @@ class ToBeConstructableWith {
|
|
|
4077
4146
|
}
|
|
4078
4147
|
else {
|
|
4079
4148
|
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
4080
|
-
diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isConstructable(
|
|
4149
|
+
diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isConstructable(isExpression, targetText), origin));
|
|
4081
4150
|
}
|
|
4082
4151
|
return diagnostics;
|
|
4083
4152
|
}
|
|
@@ -4093,11 +4162,11 @@ class ToBeConstructableWith {
|
|
|
4093
4162
|
}
|
|
4094
4163
|
if (!type || type.getConstructSignatures().length === 0) {
|
|
4095
4164
|
const text = [];
|
|
4096
|
-
if (this.#compiler
|
|
4097
|
-
text.push(ExpectDiagnosticText.
|
|
4165
|
+
if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
|
|
4166
|
+
text.push(ExpectDiagnosticText.argumentMustBe("source", "a constructable expression"));
|
|
4098
4167
|
}
|
|
4099
4168
|
else {
|
|
4100
|
-
text.push(ExpectDiagnosticText.
|
|
4169
|
+
text.push(ExpectDiagnosticText.typeArgumentMustBe("Source", "a constructable type"));
|
|
4101
4170
|
}
|
|
4102
4171
|
if (type != null && type.getCallSignatures().length > 0) {
|
|
4103
4172
|
text.push(ExpectDiagnosticText.didYouMeanToUse("the '.toBeCallableWith()' matcher"));
|
|
@@ -4139,9 +4208,9 @@ class ToHaveProperty {
|
|
|
4139
4208
|
if (sourceType.flags & (this.#compiler.TypeFlags.Any | this.#compiler.TypeFlags.Never) ||
|
|
4140
4209
|
!matchWorker.extendsObjectType(sourceType)) {
|
|
4141
4210
|
const expectedText = "of an object type";
|
|
4142
|
-
const text = this.#compiler
|
|
4143
|
-
? ExpectDiagnosticText.
|
|
4144
|
-
: ExpectDiagnosticText.
|
|
4211
|
+
const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
|
|
4212
|
+
? ExpectDiagnosticText.argumentMustBe("source", expectedText)
|
|
4213
|
+
: ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText);
|
|
4145
4214
|
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
4146
4215
|
diagnostics.push(Diagnostic.error(text, origin));
|
|
4147
4216
|
}
|
|
@@ -4178,15 +4247,15 @@ class ToRaiseError {
|
|
|
4178
4247
|
this.#compiler = compiler;
|
|
4179
4248
|
}
|
|
4180
4249
|
#explain(matchWorker, sourceNode, targetNodes) {
|
|
4181
|
-
const
|
|
4250
|
+
const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
|
|
4182
4251
|
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
4183
4252
|
if (matchWorker.assertion.diagnostics.size === 0) {
|
|
4184
|
-
const text = ExpectDiagnosticText.didNotRaiseError(
|
|
4253
|
+
const text = ExpectDiagnosticText.didNotRaiseError(isExpression);
|
|
4185
4254
|
return [Diagnostic.error(text, origin)];
|
|
4186
4255
|
}
|
|
4187
4256
|
if (matchWorker.assertion.diagnostics.size !== targetNodes.length) {
|
|
4188
4257
|
const count = matchWorker.assertion.diagnostics.size;
|
|
4189
|
-
const text = ExpectDiagnosticText.raisedError(
|
|
4258
|
+
const text = ExpectDiagnosticText.raisedError(isExpression, count, targetNodes.length);
|
|
4190
4259
|
const related = [
|
|
4191
4260
|
Diagnostic.error(ExpectDiagnosticText.raisedTypeError(count)),
|
|
4192
4261
|
...Diagnostic.fromDiagnostics([...matchWorker.assertion.diagnostics]),
|
|
@@ -4198,8 +4267,8 @@ class ToRaiseError {
|
|
|
4198
4267
|
const isMatch = this.#matchExpectedError(diagnostic, targetNode);
|
|
4199
4268
|
if (matchWorker.assertion.isNot ? isMatch : !isMatch) {
|
|
4200
4269
|
const text = matchWorker.assertion.isNot
|
|
4201
|
-
? ExpectDiagnosticText.raisedMatchingError(
|
|
4202
|
-
: ExpectDiagnosticText.didNotRaiseMatchingError(
|
|
4270
|
+
? ExpectDiagnosticText.raisedMatchingError(isExpression)
|
|
4271
|
+
: ExpectDiagnosticText.didNotRaiseMatchingError(isExpression);
|
|
4203
4272
|
const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
|
|
4204
4273
|
const related = [
|
|
4205
4274
|
Diagnostic.error(ExpectDiagnosticText.raisedTypeError()),
|
|
@@ -4348,7 +4417,7 @@ class ExpectService {
|
|
|
4348
4417
|
}
|
|
4349
4418
|
#rejectsTypeArguments(matchWorker, onDiagnostics) {
|
|
4350
4419
|
for (const rejectedType of this.#rejectTypes) {
|
|
4351
|
-
const allowedKeyword = this.#compiler.SyntaxKind[`${
|
|
4420
|
+
const allowedKeyword = this.#compiler.SyntaxKind[`${capitalize(rejectedType)}Keyword`];
|
|
4352
4421
|
if (matchWorker.assertion.source[0]?.kind === allowedKeyword ||
|
|
4353
4422
|
matchWorker.assertion.target?.[0]?.kind === allowedKeyword) {
|
|
4354
4423
|
continue;
|
|
@@ -4358,11 +4427,11 @@ class ExpectService {
|
|
|
4358
4427
|
if (!argumentNode) {
|
|
4359
4428
|
continue;
|
|
4360
4429
|
}
|
|
4361
|
-
if (matchWorker.getType(argumentNode).flags & this.#compiler.TypeFlags[
|
|
4430
|
+
if (matchWorker.getType(argumentNode).flags & this.#compiler.TypeFlags[capitalize(rejectedType)]) {
|
|
4362
4431
|
const text = [
|
|
4363
|
-
this.#compiler
|
|
4364
|
-
? ExpectDiagnosticText.
|
|
4365
|
-
: ExpectDiagnosticText.
|
|
4432
|
+
nodeBelongsToArgumentList(this.#compiler, argumentNode)
|
|
4433
|
+
? ExpectDiagnosticText.argumentCannotBeOfType(argumentName, rejectedType)
|
|
4434
|
+
: ExpectDiagnosticText.typeArgumentCannotBeOfType(capitalize(argumentName), rejectedType),
|
|
4366
4435
|
...ExpectDiagnosticText.typeWasRejected(rejectedType),
|
|
4367
4436
|
];
|
|
4368
4437
|
const origin = DiagnosticOrigin.fromNode(argumentNode);
|
|
@@ -4433,6 +4502,9 @@ class TestTreeWalker {
|
|
|
4433
4502
|
case TestTreeNodeBrand.Expect:
|
|
4434
4503
|
this.#visitAssertion(testNode, runMode, parentResult);
|
|
4435
4504
|
break;
|
|
4505
|
+
case TestTreeNodeBrand.When:
|
|
4506
|
+
this.#visitWhen(testNode, runMode, parentResult);
|
|
4507
|
+
break;
|
|
4436
4508
|
}
|
|
4437
4509
|
}
|
|
4438
4510
|
}
|
|
@@ -4525,11 +4597,37 @@ class TestTreeWalker {
|
|
|
4525
4597
|
EventEmitter.dispatch(["test:pass", { result: testResult }]);
|
|
4526
4598
|
}
|
|
4527
4599
|
}
|
|
4600
|
+
#visitWhen(when, runMode, parentResult) {
|
|
4601
|
+
if (when.abilityDiagnostics != null && when.abilityDiagnostics.size > 0) {
|
|
4602
|
+
const diagnostics = [];
|
|
4603
|
+
for (const diagnostic of when.abilityDiagnostics) {
|
|
4604
|
+
if (isDiagnosticWithLocation(diagnostic)) {
|
|
4605
|
+
const text = getDiagnosticMessageText(diagnostic);
|
|
4606
|
+
let origin;
|
|
4607
|
+
if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, when.node)) {
|
|
4608
|
+
origin = DiagnosticOrigin.fromNodes(when.target);
|
|
4609
|
+
}
|
|
4610
|
+
else {
|
|
4611
|
+
origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), when.node.getSourceFile());
|
|
4612
|
+
}
|
|
4613
|
+
let related;
|
|
4614
|
+
if (diagnostic.relatedInformation != null) {
|
|
4615
|
+
related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
|
|
4616
|
+
}
|
|
4617
|
+
diagnostics.push(Diagnostic.error(text, origin).add({ related }));
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
EventEmitter.dispatch(["task:error", { diagnostics, result: this.#taskResult }]);
|
|
4621
|
+
return;
|
|
4622
|
+
}
|
|
4623
|
+
this.visit(when.children, runMode, parentResult);
|
|
4624
|
+
}
|
|
4528
4625
|
}
|
|
4529
4626
|
|
|
4530
4627
|
class TaskRunner {
|
|
4531
4628
|
#collectService;
|
|
4532
4629
|
#compiler;
|
|
4630
|
+
#eventEmitter = new EventEmitter();
|
|
4533
4631
|
#resolvedConfig;
|
|
4534
4632
|
#projectService;
|
|
4535
4633
|
constructor(compiler, resolvedConfig) {
|
|
@@ -4538,55 +4636,71 @@ class TaskRunner {
|
|
|
4538
4636
|
this.#projectService = new ProjectService(compiler, this.#resolvedConfig);
|
|
4539
4637
|
this.#collectService = new CollectService(compiler, this.#projectService, this.#resolvedConfig);
|
|
4540
4638
|
}
|
|
4541
|
-
|
|
4542
|
-
|
|
4639
|
+
#onDiagnostics(diagnostics, result) {
|
|
4640
|
+
EventEmitter.dispatch(["task:error", { diagnostics, result }]);
|
|
4641
|
+
}
|
|
4642
|
+
async run(task, cancellationToken) {
|
|
4643
|
+
if (cancellationToken.isCancellationRequested) {
|
|
4543
4644
|
return;
|
|
4544
4645
|
}
|
|
4545
4646
|
this.#projectService.openFile(task.filePath, undefined, this.#resolvedConfig.rootPath);
|
|
4546
4647
|
const taskResult = new TaskResult(task);
|
|
4547
4648
|
EventEmitter.dispatch(["task:start", { result: taskResult }]);
|
|
4548
|
-
this.#run(task, taskResult, cancellationToken);
|
|
4649
|
+
await this.#run(task, taskResult, cancellationToken);
|
|
4549
4650
|
EventEmitter.dispatch(["task:end", { result: taskResult }]);
|
|
4550
4651
|
this.#projectService.closeFile(task.filePath);
|
|
4551
4652
|
}
|
|
4552
|
-
#run(task, taskResult, cancellationToken) {
|
|
4653
|
+
async #run(task, taskResult, cancellationToken) {
|
|
4553
4654
|
if (!existsSync(task.filePath)) {
|
|
4554
|
-
|
|
4555
|
-
"task:error",
|
|
4556
|
-
{ diagnostics: [Diagnostic.error(`Test file '${task.filePath}' does not exist.`)], result: taskResult },
|
|
4557
|
-
]);
|
|
4558
|
-
return;
|
|
4559
|
-
}
|
|
4560
|
-
const languageService = this.#projectService.getLanguageService(task.filePath);
|
|
4561
|
-
if (!languageService) {
|
|
4655
|
+
this.#onDiagnostics([Diagnostic.error(`Test file '${task.filePath}' does not exist.`)], taskResult);
|
|
4562
4656
|
return;
|
|
4563
4657
|
}
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
{ diagnostics: Diagnostic.fromDiagnostics(syntacticDiagnostics), result: taskResult },
|
|
4569
|
-
]);
|
|
4658
|
+
let languageService = this.#projectService.getLanguageService(task.filePath);
|
|
4659
|
+
const syntacticDiagnostics = languageService?.getSyntacticDiagnostics(task.filePath);
|
|
4660
|
+
if (syntacticDiagnostics != null && syntacticDiagnostics.length > 0) {
|
|
4661
|
+
this.#onDiagnostics(Diagnostic.fromDiagnostics(syntacticDiagnostics), taskResult);
|
|
4570
4662
|
return;
|
|
4571
4663
|
}
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4664
|
+
let semanticDiagnostics = languageService?.getSemanticDiagnostics(task.filePath);
|
|
4665
|
+
let program = languageService?.getProgram();
|
|
4666
|
+
let sourceFile = program?.getSourceFile(task.filePath);
|
|
4667
|
+
if (sourceFile?.text.startsWith("// @tstyche-template")) {
|
|
4668
|
+
if (semanticDiagnostics != null && semanticDiagnostics.length > 0) {
|
|
4669
|
+
this.#onDiagnostics(Diagnostic.fromDiagnostics(semanticDiagnostics), taskResult);
|
|
4670
|
+
return;
|
|
4671
|
+
}
|
|
4672
|
+
const moduleSpecifier = pathToFileURL(task.filePath).toString();
|
|
4673
|
+
const testText = (await import(moduleSpecifier))?.default;
|
|
4674
|
+
if (typeof testText !== "string") {
|
|
4675
|
+
this.#onDiagnostics([Diagnostic.error("A template test file must export a string.")], taskResult);
|
|
4676
|
+
return;
|
|
4677
|
+
}
|
|
4678
|
+
this.#projectService.openFile(task.filePath, testText, this.#resolvedConfig.rootPath);
|
|
4679
|
+
languageService = this.#projectService.getLanguageService(task.filePath);
|
|
4680
|
+
const syntacticDiagnostics = languageService?.getSyntacticDiagnostics(task.filePath);
|
|
4681
|
+
if (syntacticDiagnostics != null && syntacticDiagnostics.length > 0) {
|
|
4682
|
+
this.#onDiagnostics(Diagnostic.fromDiagnostics(syntacticDiagnostics), taskResult);
|
|
4683
|
+
return;
|
|
4684
|
+
}
|
|
4685
|
+
semanticDiagnostics = languageService?.getSemanticDiagnostics(task.filePath);
|
|
4686
|
+
program = languageService?.getProgram();
|
|
4687
|
+
sourceFile = program?.getSourceFile(task.filePath);
|
|
4576
4688
|
}
|
|
4577
|
-
const sourceFile = program.getSourceFile(task.filePath);
|
|
4578
4689
|
if (!sourceFile) {
|
|
4579
4690
|
return;
|
|
4580
4691
|
}
|
|
4692
|
+
const cancellationHandler = new CancellationHandler(cancellationToken, CancellationReason.CollectError);
|
|
4693
|
+
this.#eventEmitter.addHandler(cancellationHandler);
|
|
4581
4694
|
const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics);
|
|
4695
|
+
this.#eventEmitter.removeHandler(cancellationHandler);
|
|
4696
|
+
if (cancellationToken.isCancellationRequested) {
|
|
4697
|
+
return;
|
|
4698
|
+
}
|
|
4582
4699
|
if (testTree.diagnostics.size > 0) {
|
|
4583
|
-
|
|
4584
|
-
"task:error",
|
|
4585
|
-
{ diagnostics: Diagnostic.fromDiagnostics([...testTree.diagnostics]), result: taskResult },
|
|
4586
|
-
]);
|
|
4700
|
+
this.#onDiagnostics(Diagnostic.fromDiagnostics([...testTree.diagnostics]), taskResult);
|
|
4587
4701
|
return;
|
|
4588
4702
|
}
|
|
4589
|
-
const typeChecker = program
|
|
4703
|
+
const typeChecker = program?.getTypeChecker();
|
|
4590
4704
|
const testTreeWalker = new TestTreeWalker(this.#compiler, typeChecker, this.#resolvedConfig, {
|
|
4591
4705
|
cancellationToken,
|
|
4592
4706
|
taskResult,
|
|
@@ -4600,15 +4714,13 @@ class TaskRunner {
|
|
|
4600
4714
|
class Runner {
|
|
4601
4715
|
#eventEmitter = new EventEmitter();
|
|
4602
4716
|
#resolvedConfig;
|
|
4603
|
-
static version = "4.0.0-beta.
|
|
4717
|
+
static version = "4.0.0-beta.8";
|
|
4604
4718
|
constructor(resolvedConfig) {
|
|
4605
4719
|
this.#resolvedConfig = resolvedConfig;
|
|
4606
4720
|
}
|
|
4607
4721
|
#addHandlers(cancellationToken) {
|
|
4608
4722
|
const resultHandler = new ResultHandler();
|
|
4609
4723
|
this.#eventEmitter.addHandler(resultHandler);
|
|
4610
|
-
const testTreeHandler = new TestTreeHandler();
|
|
4611
|
-
this.#eventEmitter.addHandler(testTreeHandler);
|
|
4612
4724
|
if (this.#resolvedConfig.failFast) {
|
|
4613
4725
|
const cancellationHandler = new CancellationHandler(cancellationToken, CancellationReason.FailFast);
|
|
4614
4726
|
this.#eventEmitter.addHandler(cancellationHandler);
|
|
@@ -4651,7 +4763,7 @@ class Runner {
|
|
|
4651
4763
|
this.#eventEmitter.removeHandlers();
|
|
4652
4764
|
}
|
|
4653
4765
|
async #run(tasks, cancellationToken) {
|
|
4654
|
-
const result = new Result(
|
|
4766
|
+
const result = new Result(tasks);
|
|
4655
4767
|
EventEmitter.dispatch(["run:start", { result }]);
|
|
4656
4768
|
for (const target of this.#resolvedConfig.target) {
|
|
4657
4769
|
const targetResult = new TargetResult(target, tasks);
|
|
@@ -4660,7 +4772,7 @@ class Runner {
|
|
|
4660
4772
|
if (compiler) {
|
|
4661
4773
|
const taskRunner = new TaskRunner(compiler, this.#resolvedConfig);
|
|
4662
4774
|
for (const task of tasks) {
|
|
4663
|
-
taskRunner.run(task, cancellationToken);
|
|
4775
|
+
await taskRunner.run(task, cancellationToken);
|
|
4664
4776
|
}
|
|
4665
4777
|
}
|
|
4666
4778
|
EventEmitter.dispatch(["target:end", { result: targetResult }]);
|
|
@@ -4802,4 +4914,4 @@ class Cli {
|
|
|
4802
4914
|
}
|
|
4803
4915
|
}
|
|
4804
4916
|
|
|
4805
|
-
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,
|
|
4917
|
+
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, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, WhenNode, addsPackageText, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, environmentOptions, fileViewText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, nodeBelongsToArgumentList, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
|