tstyche 4.0.0-beta.5 → 4.0.0-beta.7
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 +974 -864
- 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
|
}));
|
|
@@ -2963,340 +2535,825 @@ class CancellationToken {
|
|
|
2963
2535
|
this.#reason = reason;
|
|
2964
2536
|
}
|
|
2965
2537
|
}
|
|
2966
|
-
reset() {
|
|
2967
|
-
if (this.#isCancelled) {
|
|
2968
|
-
this.#isCancelled = false;
|
|
2969
|
-
this.#reason = undefined;
|
|
2970
|
-
}
|
|
2538
|
+
reset() {
|
|
2539
|
+
if (this.#isCancelled) {
|
|
2540
|
+
this.#isCancelled = false;
|
|
2541
|
+
this.#reason = undefined;
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
var CancellationReason;
|
|
2547
|
+
(function (CancellationReason) {
|
|
2548
|
+
CancellationReason["ConfigChange"] = "configChange";
|
|
2549
|
+
CancellationReason["ConfigError"] = "configError";
|
|
2550
|
+
CancellationReason["CollectError"] = "collectError";
|
|
2551
|
+
CancellationReason["FailFast"] = "failFast";
|
|
2552
|
+
CancellationReason["WatchClose"] = "watchClose";
|
|
2553
|
+
})(CancellationReason || (CancellationReason = {}));
|
|
2554
|
+
|
|
2555
|
+
class Watcher {
|
|
2556
|
+
#onChanged;
|
|
2557
|
+
#onRemoved;
|
|
2558
|
+
#recursive;
|
|
2559
|
+
#targetPath;
|
|
2560
|
+
#watcher;
|
|
2561
|
+
constructor(targetPath, onChanged, onRemoved, options) {
|
|
2562
|
+
this.#targetPath = targetPath;
|
|
2563
|
+
this.#onChanged = onChanged;
|
|
2564
|
+
this.#onRemoved = onRemoved ?? onChanged;
|
|
2565
|
+
this.#recursive = options?.recursive;
|
|
2566
|
+
}
|
|
2567
|
+
close() {
|
|
2568
|
+
this.#watcher?.close();
|
|
2569
|
+
}
|
|
2570
|
+
watch() {
|
|
2571
|
+
this.#watcher = watch(this.#targetPath, { recursive: this.#recursive }, (_eventType, fileName) => {
|
|
2572
|
+
if (fileName != null) {
|
|
2573
|
+
const filePath = Path.resolve(this.#targetPath, fileName);
|
|
2574
|
+
if (existsSync(filePath)) {
|
|
2575
|
+
this.#onChanged(filePath);
|
|
2576
|
+
}
|
|
2577
|
+
else {
|
|
2578
|
+
this.#onRemoved(filePath);
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
class FileWatcher extends Watcher {
|
|
2586
|
+
constructor(targetPath, onChanged) {
|
|
2587
|
+
const onChangedFile = (filePath) => {
|
|
2588
|
+
if (filePath === targetPath) {
|
|
2589
|
+
onChanged();
|
|
2590
|
+
}
|
|
2591
|
+
};
|
|
2592
|
+
super(Path.dirname(targetPath), onChangedFile);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
class InputService {
|
|
2597
|
+
#onInput;
|
|
2598
|
+
#stdin = process.stdin;
|
|
2599
|
+
constructor(onInput) {
|
|
2600
|
+
this.#onInput = onInput;
|
|
2601
|
+
this.#stdin.setRawMode?.(true);
|
|
2602
|
+
this.#stdin.setEncoding("utf8");
|
|
2603
|
+
this.#stdin.unref();
|
|
2604
|
+
this.#stdin.addListener("data", this.#onInput);
|
|
2605
|
+
}
|
|
2606
|
+
close() {
|
|
2607
|
+
this.#stdin.removeListener("data", this.#onInput);
|
|
2608
|
+
this.#stdin.setRawMode?.(false);
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
class GlobPattern {
|
|
2613
|
+
static #reservedCharacterRegex = /[^\w\s/]/g;
|
|
2614
|
+
static #parse(pattern, usageTarget) {
|
|
2615
|
+
const segments = pattern.split("/");
|
|
2616
|
+
let resultPattern = "\\.";
|
|
2617
|
+
let optionalSegmentCount = 0;
|
|
2618
|
+
for (const segment of segments) {
|
|
2619
|
+
if (segment === ".") {
|
|
2620
|
+
continue;
|
|
2621
|
+
}
|
|
2622
|
+
if (segment === "**") {
|
|
2623
|
+
resultPattern += "(\\/(?!(node_modules)(\\/|$))[^./][^/]*)*?";
|
|
2624
|
+
continue;
|
|
2625
|
+
}
|
|
2626
|
+
if (usageTarget === "directories") {
|
|
2627
|
+
resultPattern += "(";
|
|
2628
|
+
optionalSegmentCount++;
|
|
2629
|
+
}
|
|
2630
|
+
resultPattern += "\\/";
|
|
2631
|
+
const segmentPattern = segment.replace(GlobPattern.#reservedCharacterRegex, GlobPattern.#replaceReservedCharacter);
|
|
2632
|
+
if (segmentPattern !== segment) {
|
|
2633
|
+
resultPattern += "(?!(node_modules)(\\/|$))";
|
|
2634
|
+
}
|
|
2635
|
+
resultPattern += segmentPattern;
|
|
2636
|
+
}
|
|
2637
|
+
resultPattern += ")?".repeat(optionalSegmentCount);
|
|
2638
|
+
return resultPattern;
|
|
2639
|
+
}
|
|
2640
|
+
static #replaceReservedCharacter(match, offset) {
|
|
2641
|
+
switch (match) {
|
|
2642
|
+
case "*":
|
|
2643
|
+
return offset === 0 ? "([^./][^/]*)?" : "([^/]*)?";
|
|
2644
|
+
case "?":
|
|
2645
|
+
return offset === 0 ? "[^./]" : "[^/]";
|
|
2646
|
+
default:
|
|
2647
|
+
return `\\${match}`;
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
static toRegex(patterns, target) {
|
|
2651
|
+
const patternText = patterns.map((pattern) => `(${GlobPattern.#parse(pattern, target)})`).join("|");
|
|
2652
|
+
return new RegExp(`^(${patternText})$`);
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
class SelectDiagnosticText {
|
|
2657
|
+
static #pathSelectOptions(resolvedConfig) {
|
|
2658
|
+
const text = [
|
|
2659
|
+
`Root path: ${resolvedConfig.rootPath}`,
|
|
2660
|
+
`Test file match: ${resolvedConfig.testFileMatch.join(", ")}`,
|
|
2661
|
+
];
|
|
2662
|
+
if (resolvedConfig.pathMatch.length > 0) {
|
|
2663
|
+
text.push(`Path match: ${resolvedConfig.pathMatch.join(", ")}`);
|
|
2664
|
+
}
|
|
2665
|
+
return text;
|
|
2666
|
+
}
|
|
2667
|
+
static noTestFilesWereLeft(resolvedConfig) {
|
|
2668
|
+
return [
|
|
2669
|
+
"No test files were left to run using current configuration.",
|
|
2670
|
+
...SelectDiagnosticText.#pathSelectOptions(resolvedConfig),
|
|
2671
|
+
];
|
|
2672
|
+
}
|
|
2673
|
+
static noTestFilesWereSelected(resolvedConfig) {
|
|
2674
|
+
return [
|
|
2675
|
+
"No test files were selected using current configuration.",
|
|
2676
|
+
...SelectDiagnosticText.#pathSelectOptions(resolvedConfig),
|
|
2677
|
+
];
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
class Select {
|
|
2682
|
+
static #patternsCache = new WeakMap();
|
|
2683
|
+
static async #getAccessibleFileSystemEntries(targetPath) {
|
|
2684
|
+
const directories = [];
|
|
2685
|
+
const files = [];
|
|
2686
|
+
try {
|
|
2687
|
+
const entries = await fs.readdir(targetPath, { withFileTypes: true });
|
|
2688
|
+
for (const entry of entries) {
|
|
2689
|
+
let entryMeta = entry;
|
|
2690
|
+
if (entry.isSymbolicLink()) {
|
|
2691
|
+
entryMeta = await fs.stat([targetPath, entry.name].join("/"));
|
|
2692
|
+
}
|
|
2693
|
+
if (entryMeta.isDirectory()) {
|
|
2694
|
+
directories.push(entry.name);
|
|
2695
|
+
}
|
|
2696
|
+
else if (entryMeta.isFile()) {
|
|
2697
|
+
files.push(entry.name);
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
catch {
|
|
2702
|
+
}
|
|
2703
|
+
return { directories, files };
|
|
2704
|
+
}
|
|
2705
|
+
static #getMatchPatterns(globPatterns) {
|
|
2706
|
+
let matchPatterns = Select.#patternsCache.get(globPatterns);
|
|
2707
|
+
if (!matchPatterns) {
|
|
2708
|
+
matchPatterns = {
|
|
2709
|
+
includedDirectory: GlobPattern.toRegex(globPatterns, "directories"),
|
|
2710
|
+
includedFile: GlobPattern.toRegex(globPatterns, "files"),
|
|
2711
|
+
};
|
|
2712
|
+
Select.#patternsCache.set(globPatterns, matchPatterns);
|
|
2713
|
+
}
|
|
2714
|
+
return matchPatterns;
|
|
2715
|
+
}
|
|
2716
|
+
static #isDirectoryIncluded(directoryPath, matchPatterns) {
|
|
2717
|
+
return matchPatterns.includedDirectory.test(directoryPath);
|
|
2718
|
+
}
|
|
2719
|
+
static #isFileIncluded(filePath, matchPatterns, resolvedConfig) {
|
|
2720
|
+
if (resolvedConfig.pathMatch.length > 0 &&
|
|
2721
|
+
!resolvedConfig.pathMatch.some((match) => filePath.toLowerCase().includes(match.toLowerCase()))) {
|
|
2722
|
+
return false;
|
|
2723
|
+
}
|
|
2724
|
+
return matchPatterns.includedFile.test(filePath);
|
|
2725
|
+
}
|
|
2726
|
+
static isTestFile(filePath, resolvedConfig) {
|
|
2727
|
+
const matchPatterns = Select.#getMatchPatterns(resolvedConfig.testFileMatch);
|
|
2728
|
+
return Select.#isFileIncluded(Path.relative(resolvedConfig.rootPath, filePath), matchPatterns, resolvedConfig);
|
|
2729
|
+
}
|
|
2730
|
+
static #onDiagnostics(diagnostic) {
|
|
2731
|
+
EventEmitter.dispatch(["select:error", { diagnostics: [diagnostic] }]);
|
|
2732
|
+
}
|
|
2733
|
+
static async selectFiles(resolvedConfig) {
|
|
2734
|
+
const matchPatterns = Select.#getMatchPatterns(resolvedConfig.testFileMatch);
|
|
2735
|
+
const testFilePaths = [];
|
|
2736
|
+
await Select.#visitDirectory(".", testFilePaths, matchPatterns, resolvedConfig);
|
|
2737
|
+
if (testFilePaths.length === 0) {
|
|
2738
|
+
Select.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereSelected(resolvedConfig)));
|
|
2739
|
+
}
|
|
2740
|
+
return testFilePaths.sort();
|
|
2741
|
+
}
|
|
2742
|
+
static async #visitDirectory(currentPath, testFilePaths, matchPatterns, resolvedConfig) {
|
|
2743
|
+
const targetPath = Path.join(resolvedConfig.rootPath, currentPath);
|
|
2744
|
+
const entries = await Select.#getAccessibleFileSystemEntries(targetPath);
|
|
2745
|
+
for (const directoryName of entries.directories) {
|
|
2746
|
+
const directoryPath = [currentPath, directoryName].join("/");
|
|
2747
|
+
if (Select.#isDirectoryIncluded(directoryPath, matchPatterns)) {
|
|
2748
|
+
await Select.#visitDirectory(directoryPath, testFilePaths, matchPatterns, resolvedConfig);
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
for (const fileName of entries.files) {
|
|
2752
|
+
const filePath = [currentPath, fileName].join("/");
|
|
2753
|
+
if (Select.#isFileIncluded(filePath, matchPatterns, resolvedConfig)) {
|
|
2754
|
+
testFilePaths.push([targetPath, fileName].join("/"));
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
class Debounce {
|
|
2761
|
+
#delay;
|
|
2762
|
+
#onResolve;
|
|
2763
|
+
#resolve;
|
|
2764
|
+
#timeout;
|
|
2765
|
+
constructor(delay, onResolve) {
|
|
2766
|
+
this.#delay = delay;
|
|
2767
|
+
this.#onResolve = onResolve;
|
|
2768
|
+
}
|
|
2769
|
+
cancel() {
|
|
2770
|
+
clearTimeout(this.#timeout);
|
|
2971
2771
|
}
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
(
|
|
2976
|
-
|
|
2977
|
-
CancellationReason["ConfigError"] = "configError";
|
|
2978
|
-
CancellationReason["FailFast"] = "failFast";
|
|
2979
|
-
CancellationReason["WatchClose"] = "watchClose";
|
|
2980
|
-
})(CancellationReason || (CancellationReason = {}));
|
|
2981
|
-
|
|
2982
|
-
class Watcher {
|
|
2983
|
-
#onChanged;
|
|
2984
|
-
#onRemoved;
|
|
2985
|
-
#recursive;
|
|
2986
|
-
#targetPath;
|
|
2987
|
-
#watcher;
|
|
2988
|
-
constructor(targetPath, onChanged, onRemoved, options) {
|
|
2989
|
-
this.#targetPath = targetPath;
|
|
2990
|
-
this.#onChanged = onChanged;
|
|
2991
|
-
this.#onRemoved = onRemoved ?? onChanged;
|
|
2992
|
-
this.#recursive = options?.recursive;
|
|
2772
|
+
refresh() {
|
|
2773
|
+
this.cancel();
|
|
2774
|
+
this.#timeout = setTimeout(() => {
|
|
2775
|
+
this.#resolve?.(this.#onResolve());
|
|
2776
|
+
}, this.#delay);
|
|
2993
2777
|
}
|
|
2994
|
-
|
|
2995
|
-
this.#
|
|
2778
|
+
resolve(value) {
|
|
2779
|
+
this.#resolve?.(value);
|
|
2996
2780
|
}
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
const filePath = Path.resolve(this.#targetPath, fileName);
|
|
3001
|
-
if (existsSync(filePath)) {
|
|
3002
|
-
this.#onChanged(filePath);
|
|
3003
|
-
}
|
|
3004
|
-
else {
|
|
3005
|
-
this.#onRemoved(filePath);
|
|
3006
|
-
}
|
|
3007
|
-
}
|
|
2781
|
+
schedule() {
|
|
2782
|
+
return new Promise((resolve) => {
|
|
2783
|
+
this.#resolve = resolve;
|
|
3008
2784
|
});
|
|
3009
2785
|
}
|
|
3010
2786
|
}
|
|
3011
2787
|
|
|
3012
|
-
class
|
|
3013
|
-
|
|
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
|
+
}
|
|
3014
2839
|
const onChangedFile = (filePath) => {
|
|
3015
|
-
|
|
3016
|
-
|
|
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);
|
|
3017
2849
|
}
|
|
3018
2850
|
};
|
|
3019
|
-
|
|
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
|
+
}
|
|
3020
2873
|
}
|
|
3021
2874
|
}
|
|
3022
2875
|
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
this.#stdin.unref();
|
|
3031
|
-
this.#stdin.addListener("data", this.#onInput);
|
|
3032
|
-
}
|
|
3033
|
-
close() {
|
|
3034
|
-
this.#stdin.removeListener("data", this.#onInput);
|
|
3035
|
-
this.#stdin.setRawMode?.(false);
|
|
3036
|
-
}
|
|
3037
|
-
}
|
|
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 = {}));
|
|
3038
2883
|
|
|
3039
|
-
class
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
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
|
+
}
|
|
3056
2908
|
}
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
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;
|
|
3061
2917
|
}
|
|
3062
|
-
|
|
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;
|
|
3063
2936
|
}
|
|
3064
|
-
|
|
3065
|
-
return resultPattern;
|
|
2937
|
+
return diagnostics;
|
|
3066
2938
|
}
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
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
|
+
}
|
|
3075
2965
|
}
|
|
3076
|
-
}
|
|
3077
|
-
static toRegex(patterns, target) {
|
|
3078
|
-
const patternText = patterns.map((pattern) => `(${GlobPattern.#parse(pattern, target)})`).join("|");
|
|
3079
|
-
return new RegExp(`^(${patternText})$`);
|
|
3080
2966
|
}
|
|
3081
2967
|
}
|
|
3082
2968
|
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
`Root path: ${resolvedConfig.rootPath}`,
|
|
3087
|
-
`Test file match: ${resolvedConfig.testFileMatch.join(", ")}`,
|
|
3088
|
-
];
|
|
3089
|
-
if (resolvedConfig.pathMatch.length > 0) {
|
|
3090
|
-
text.push(`Path match: ${resolvedConfig.pathMatch.join(", ")}`);
|
|
3091
|
-
}
|
|
3092
|
-
return text;
|
|
3093
|
-
}
|
|
3094
|
-
static noTestFilesWereLeft(resolvedConfig) {
|
|
3095
|
-
return [
|
|
3096
|
-
"No test files were left to run using current configuration.",
|
|
3097
|
-
...SelectDiagnosticText.#pathSelectOptions(resolvedConfig),
|
|
3098
|
-
];
|
|
3099
|
-
}
|
|
3100
|
-
static noTestFilesWereSelected(resolvedConfig) {
|
|
3101
|
-
return [
|
|
3102
|
-
"No test files were selected using current configuration.",
|
|
3103
|
-
...SelectDiagnosticText.#pathSelectOptions(resolvedConfig),
|
|
3104
|
-
];
|
|
2969
|
+
function nodeBelongsToArgumentList(compiler, node) {
|
|
2970
|
+
if (compiler.isCallExpression(node.parent)) {
|
|
2971
|
+
return node.parent.arguments.some((argument) => argument === node);
|
|
3105
2972
|
}
|
|
2973
|
+
return false;
|
|
3106
2974
|
}
|
|
3107
2975
|
|
|
3108
|
-
class
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
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(" ");
|
|
3126
3002
|
}
|
|
3127
3003
|
}
|
|
3128
|
-
|
|
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)}`;
|
|
3129
3013
|
}
|
|
3130
|
-
return { directories, files };
|
|
3131
3014
|
}
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
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
|
+
}
|
|
3140
3031
|
}
|
|
3141
|
-
|
|
3032
|
+
this.#filePath = "";
|
|
3033
|
+
this.#nodes = [];
|
|
3034
|
+
this.#text = "";
|
|
3142
3035
|
}
|
|
3143
|
-
|
|
3144
|
-
|
|
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
|
+
}
|
|
3145
3053
|
}
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
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;
|
|
3150
3086
|
}
|
|
3151
|
-
return matchPatterns.includedFile.test(filePath);
|
|
3152
3087
|
}
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3088
|
+
open(sourceFile) {
|
|
3089
|
+
this.#filePath = sourceFile.fileName;
|
|
3090
|
+
this.#text = sourceFile.text;
|
|
3156
3091
|
}
|
|
3157
|
-
|
|
3158
|
-
|
|
3092
|
+
}
|
|
3093
|
+
|
|
3094
|
+
class CollectDiagnosticText {
|
|
3095
|
+
static cannotBeNestedWithin(source, target) {
|
|
3096
|
+
return `'${source}()' cannot be nested within '${target}()'.`;
|
|
3159
3097
|
}
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
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
|
+
}
|
|
3166
3151
|
}
|
|
3167
|
-
return testFilePaths.sort();
|
|
3168
3152
|
}
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
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;
|
|
3176
3173
|
}
|
|
3174
|
+
expression = expression.expression;
|
|
3175
|
+
}
|
|
3176
|
+
let identifierName;
|
|
3177
|
+
if (this.#compiler.isPropertyAccessExpression(expression) &&
|
|
3178
|
+
expression.expression.getText() === this.#identifiers.namespace) {
|
|
3179
|
+
identifierName = expression.name.getText();
|
|
3180
|
+
}
|
|
3181
|
+
else {
|
|
3182
|
+
identifierName = Object.keys(this.#identifiers.namedImports).find((key) => this.#identifiers.namedImports[key] === expression.getText());
|
|
3183
|
+
}
|
|
3184
|
+
if (!identifierName) {
|
|
3185
|
+
return;
|
|
3177
3186
|
}
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3187
|
+
switch (identifierName) {
|
|
3188
|
+
case "describe":
|
|
3189
|
+
return { brand: TestTreeNodeBrand.Describe, flags };
|
|
3190
|
+
case "it":
|
|
3191
|
+
case "test":
|
|
3192
|
+
return { brand: TestTreeNodeBrand.Test, flags };
|
|
3193
|
+
case "expect":
|
|
3194
|
+
return { brand: TestTreeNodeBrand.Expect, flags };
|
|
3195
|
+
case "when":
|
|
3196
|
+
return { brand: TestTreeNodeBrand.When, flags };
|
|
3183
3197
|
}
|
|
3198
|
+
return;
|
|
3184
3199
|
}
|
|
3185
3200
|
}
|
|
3186
3201
|
|
|
3187
|
-
class
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
constructor(
|
|
3193
|
-
this
|
|
3194
|
-
this
|
|
3195
|
-
}
|
|
3196
|
-
cancel() {
|
|
3197
|
-
clearTimeout(this.#timeout);
|
|
3198
|
-
}
|
|
3199
|
-
refresh() {
|
|
3200
|
-
this.cancel();
|
|
3201
|
-
this.#timeout = setTimeout(() => {
|
|
3202
|
-
this.#resolve?.(this.#onResolve());
|
|
3203
|
-
}, this.#delay);
|
|
3204
|
-
}
|
|
3205
|
-
resolve(value) {
|
|
3206
|
-
this.#resolve?.(value);
|
|
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
|
+
#projectService;
|
|
3218
3236
|
#resolvedConfig;
|
|
3219
|
-
#
|
|
3220
|
-
|
|
3221
|
-
|
|
3237
|
+
#testTree;
|
|
3238
|
+
constructor(compiler, projectService, resolvedConfig) {
|
|
3239
|
+
this.#compiler = compiler;
|
|
3240
|
+
this.#projectService = projectService;
|
|
3222
3241
|
this.#resolvedConfig = resolvedConfig;
|
|
3223
|
-
this.#
|
|
3224
|
-
}
|
|
3225
|
-
#onDiagnostics(diagnostic) {
|
|
3226
|
-
EventEmitter.dispatch(["watch:error", { diagnostics: [diagnostic] }]);
|
|
3242
|
+
this.#abilityLayer = new AbilityLayer(compiler, this.#projectService, this.#resolvedConfig);
|
|
3227
3243
|
}
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
const
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
for (const watcher of this.#watchers) {
|
|
3239
|
-
watcher.close();
|
|
3244
|
+
#collectTestTreeNodes(node, identifiers, parent) {
|
|
3245
|
+
if (this.#compiler.isCallExpression(node)) {
|
|
3246
|
+
const meta = identifiers.resolveTestMemberMeta(node);
|
|
3247
|
+
if (meta != null && (meta.brand === TestTreeNodeBrand.Describe || meta.brand === TestTreeNodeBrand.Test)) {
|
|
3248
|
+
const testTreeNode = new TestTreeNode(this.#compiler, meta.brand, node, parent, meta.flags);
|
|
3249
|
+
this.#compiler.forEachChild(node, (node) => {
|
|
3250
|
+
this.#collectTestTreeNodes(node, identifiers, testTreeNode);
|
|
3251
|
+
});
|
|
3252
|
+
this.#onNode(testTreeNode, parent);
|
|
3253
|
+
return;
|
|
3240
3254
|
}
|
|
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;
|
|
3255
|
+
if (meta != null && meta.brand === TestTreeNodeBrand.Expect) {
|
|
3256
|
+
const modifierNode = this.#getChainedNode(node, "type");
|
|
3257
|
+
if (!modifierNode) {
|
|
3258
|
+
return;
|
|
3262
3259
|
}
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
this.#
|
|
3275
|
-
|
|
3260
|
+
const notNode = this.#getChainedNode(modifierNode, "not");
|
|
3261
|
+
const matcherNameNode = this.#getChainedNode(notNode ?? modifierNode);
|
|
3262
|
+
if (!matcherNameNode) {
|
|
3263
|
+
return;
|
|
3264
|
+
}
|
|
3265
|
+
const matcherNode = this.#getMatcherNode(matcherNameNode);
|
|
3266
|
+
if (!matcherNode) {
|
|
3267
|
+
return;
|
|
3268
|
+
}
|
|
3269
|
+
const assertionNode = new AssertionNode(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, matcherNameNode, modifierNode, notNode);
|
|
3270
|
+
this.#abilityLayer.handleAssertion(assertionNode);
|
|
3271
|
+
this.#compiler.forEachChild(node, (node) => {
|
|
3272
|
+
this.#collectTestTreeNodes(node, identifiers, assertionNode);
|
|
3273
|
+
});
|
|
3274
|
+
this.#onNode(assertionNode, parent);
|
|
3275
|
+
return;
|
|
3276
3276
|
}
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3277
|
+
if (meta != null && meta.brand === TestTreeNodeBrand.When) {
|
|
3278
|
+
const actionNameNode = this.#getChainedNode(node);
|
|
3279
|
+
if (!actionNameNode) {
|
|
3280
|
+
return;
|
|
3281
|
+
}
|
|
3282
|
+
const actionNode = this.#getActionNode(actionNameNode);
|
|
3283
|
+
if (!actionNode) {
|
|
3284
|
+
return;
|
|
3285
|
+
}
|
|
3286
|
+
this.#compiler.forEachChild(actionNode, (node) => {
|
|
3287
|
+
if (this.#compiler.isCallExpression(node)) {
|
|
3288
|
+
const meta = identifiers.resolveTestMemberMeta(node);
|
|
3289
|
+
if (meta != null && (meta.brand === TestTreeNodeBrand.Describe || meta.brand === TestTreeNodeBrand.Test)) {
|
|
3290
|
+
const text = CollectDiagnosticText.cannotBeNestedWithin(node.expression.getText(), "when");
|
|
3291
|
+
const origin = DiagnosticOrigin.fromNode(node);
|
|
3292
|
+
this.#onDiagnostics(Diagnostic.error(text, origin));
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
});
|
|
3296
|
+
const whenNode = new WhenNode(this.#compiler, meta.brand, node, parent, meta.flags, actionNode, actionNameNode);
|
|
3297
|
+
this.#abilityLayer.handleWhen(whenNode);
|
|
3298
|
+
this.#onNode(whenNode, parent);
|
|
3299
|
+
return;
|
|
3284
3300
|
}
|
|
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
3301
|
}
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3302
|
+
if (this.#compiler.isImportDeclaration(node)) {
|
|
3303
|
+
identifiers.handleImportDeclaration(node);
|
|
3304
|
+
return;
|
|
3305
|
+
}
|
|
3306
|
+
this.#compiler.forEachChild(node, (node) => {
|
|
3307
|
+
this.#collectTestTreeNodes(node, identifiers, parent);
|
|
3308
|
+
});
|
|
3309
|
+
}
|
|
3310
|
+
createTestTree(sourceFile, semanticDiagnostics = []) {
|
|
3311
|
+
const testTree = new TestTree(new Set(semanticDiagnostics), sourceFile);
|
|
3312
|
+
EventEmitter.dispatch(["collect:start", { tree: testTree }]);
|
|
3313
|
+
this.#testTree = testTree;
|
|
3314
|
+
this.#abilityLayer.open(sourceFile);
|
|
3315
|
+
this.#collectTestTreeNodes(sourceFile, new IdentifierLookup(this.#compiler), testTree);
|
|
3316
|
+
this.#abilityLayer.close();
|
|
3317
|
+
this.#testTree = undefined;
|
|
3318
|
+
EventEmitter.dispatch(["collect:end", { tree: testTree }]);
|
|
3319
|
+
return testTree;
|
|
3320
|
+
}
|
|
3321
|
+
#getChainedNode({ parent }, name) {
|
|
3322
|
+
if (!this.#compiler.isPropertyAccessExpression(parent)) {
|
|
3323
|
+
return;
|
|
3324
|
+
}
|
|
3325
|
+
if (name != null && name !== parent.name.getText()) {
|
|
3326
|
+
return;
|
|
3327
|
+
}
|
|
3328
|
+
return parent;
|
|
3329
|
+
}
|
|
3330
|
+
#getMatcherNode(node) {
|
|
3331
|
+
if (this.#compiler.isCallExpression(node.parent)) {
|
|
3332
|
+
return node.parent;
|
|
3333
|
+
}
|
|
3334
|
+
if (this.#compiler.isDecorator(node.parent)) {
|
|
3335
|
+
return node.parent;
|
|
3336
|
+
}
|
|
3337
|
+
if (this.#compiler.isParenthesizedExpression(node.parent)) {
|
|
3338
|
+
return this.#getMatcherNode(node.parent);
|
|
3299
3339
|
}
|
|
3340
|
+
return;
|
|
3341
|
+
}
|
|
3342
|
+
#getActionNode(node) {
|
|
3343
|
+
if (this.#compiler.isCallExpression(node.parent)) {
|
|
3344
|
+
return node.parent;
|
|
3345
|
+
}
|
|
3346
|
+
return;
|
|
3347
|
+
}
|
|
3348
|
+
#onDiagnostics(diagnostic) {
|
|
3349
|
+
EventEmitter.dispatch(["collect:error", { diagnostics: [diagnostic] }]);
|
|
3350
|
+
}
|
|
3351
|
+
#onNode(node, parent) {
|
|
3352
|
+
parent.children.push(node);
|
|
3353
|
+
if (node.flags & TestTreeNodeFlags.Only) {
|
|
3354
|
+
this.#testTree.hasOnly = true;
|
|
3355
|
+
}
|
|
3356
|
+
EventEmitter.dispatch(["collect:node", { node }]);
|
|
3300
3357
|
}
|
|
3301
3358
|
}
|
|
3302
3359
|
|
|
@@ -3400,7 +3457,7 @@ class ProjectService {
|
|
|
3400
3457
|
{ diagnostics: Diagnostic.fromDiagnostics(configFileErrors) },
|
|
3401
3458
|
]);
|
|
3402
3459
|
}
|
|
3403
|
-
if (this.#resolvedConfig.checkSourceFiles) {
|
|
3460
|
+
if (this.#resolvedConfig.checkSourceFiles && !sourceText) {
|
|
3404
3461
|
const languageService = this.getLanguageService(filePath);
|
|
3405
3462
|
const program = languageService?.getProgram();
|
|
3406
3463
|
if (!program || this.#seenPrograms.has(program)) {
|
|
@@ -3437,10 +3494,8 @@ var RunMode;
|
|
|
3437
3494
|
RunMode[RunMode["Todo"] = 8] = "Todo";
|
|
3438
3495
|
})(RunMode || (RunMode = {}));
|
|
3439
3496
|
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
return text.replace(/^./, text.charAt(0).toUpperCase());
|
|
3443
|
-
}
|
|
3497
|
+
function capitalize(text) {
|
|
3498
|
+
return text.replace(/^./, text.charAt(0).toUpperCase());
|
|
3444
3499
|
}
|
|
3445
3500
|
|
|
3446
3501
|
class ExpectDiagnosticText {
|
|
@@ -3456,23 +3511,23 @@ class ExpectDiagnosticText {
|
|
|
3456
3511
|
static argumentMustBeProvided(argumentNameText) {
|
|
3457
3512
|
return `An argument for '${argumentNameText}' must be provided.`;
|
|
3458
3513
|
}
|
|
3459
|
-
static isCallable(
|
|
3460
|
-
return `${
|
|
3514
|
+
static isCallable(isExpression, targetText) {
|
|
3515
|
+
return `${isExpression ? "Expression" : "Type"} is callable ${targetText}.`;
|
|
3461
3516
|
}
|
|
3462
|
-
static isNotCallable(
|
|
3463
|
-
return `${
|
|
3517
|
+
static isNotCallable(isExpression, targetText) {
|
|
3518
|
+
return `${isExpression ? "Expression" : "Type"} is not callable ${targetText}.`;
|
|
3464
3519
|
}
|
|
3465
|
-
static isConstructable(
|
|
3466
|
-
return `${
|
|
3520
|
+
static isConstructable(isExpression, targetText) {
|
|
3521
|
+
return `${isExpression ? "Expression" : "Type"} is constructable ${targetText}.`;
|
|
3467
3522
|
}
|
|
3468
|
-
static isNotConstructable(
|
|
3469
|
-
return `${
|
|
3523
|
+
static isNotConstructable(isExpression, targetText) {
|
|
3524
|
+
return `${isExpression ? "Expression" : "Type"} is not constructable ${targetText}.`;
|
|
3470
3525
|
}
|
|
3471
|
-
static acceptsProps(
|
|
3472
|
-
return `${
|
|
3526
|
+
static acceptsProps(isExpression) {
|
|
3527
|
+
return `${isExpression ? "Component" : "Component type"} accepts props of the given type.`;
|
|
3473
3528
|
}
|
|
3474
|
-
static doesNotAcceptProps(
|
|
3475
|
-
return `${
|
|
3529
|
+
static doesNotAcceptProps(isExpression) {
|
|
3530
|
+
return `${isExpression ? "Component" : "Component type"} does not accept props of the given type.`;
|
|
3476
3531
|
}
|
|
3477
3532
|
static canBeApplied(targetText) {
|
|
3478
3533
|
return `The decorator function can be applied${targetText}.`;
|
|
@@ -3504,21 +3559,21 @@ class ExpectDiagnosticText {
|
|
|
3504
3559
|
static typeArgumentMustBe(argumentNameText, expectedText) {
|
|
3505
3560
|
return `A type argument for '${argumentNameText}' must be ${expectedText}.`;
|
|
3506
3561
|
}
|
|
3507
|
-
static raisedError(
|
|
3562
|
+
static raisedError(isExpression, count, targetCount) {
|
|
3508
3563
|
let countText = "a";
|
|
3509
3564
|
if (count > 1 || targetCount > 1) {
|
|
3510
3565
|
countText = count > targetCount ? `${count}` : `only ${count}`;
|
|
3511
3566
|
}
|
|
3512
|
-
return `${
|
|
3567
|
+
return `${isExpression ? "Expression" : "Type"} raised ${countText} type error${count === 1 ? "" : "s"}.`;
|
|
3513
3568
|
}
|
|
3514
|
-
static didNotRaiseError(
|
|
3515
|
-
return `${
|
|
3569
|
+
static didNotRaiseError(isExpression) {
|
|
3570
|
+
return `${isExpression ? "Expression" : "Type"} did not raise a type error.`;
|
|
3516
3571
|
}
|
|
3517
|
-
static raisedMatchingError(
|
|
3518
|
-
return `${
|
|
3572
|
+
static raisedMatchingError(isExpression) {
|
|
3573
|
+
return `${isExpression ? "Expression" : "Type"} raised a matching type error.`;
|
|
3519
3574
|
}
|
|
3520
|
-
static didNotRaiseMatchingError(
|
|
3521
|
-
return `${
|
|
3575
|
+
static didNotRaiseMatchingError(isExpression) {
|
|
3576
|
+
return `${isExpression ? "Expression" : "Type"} did not raise a matching type error.`;
|
|
3522
3577
|
}
|
|
3523
3578
|
static isAssignableTo(sourceTypeText, targetTypeText) {
|
|
3524
3579
|
return `Type '${sourceTypeText}' is assignable to type '${targetTypeText}'.`;
|
|
@@ -3548,7 +3603,7 @@ class ExpectDiagnosticText {
|
|
|
3548
3603
|
return `Types of property '${propertyNameText}' are not compatible.`;
|
|
3549
3604
|
}
|
|
3550
3605
|
static typeWasRejected(typeText) {
|
|
3551
|
-
const optionNameText = `reject${
|
|
3606
|
+
const optionNameText = `reject${capitalize(typeText)}Type`;
|
|
3552
3607
|
return [
|
|
3553
3608
|
`The '${typeText}' type was rejected because the '${optionNameText}' option is enabled.`,
|
|
3554
3609
|
`If this check is necessary, pass '${typeText}' as the type argument explicitly.`,
|
|
@@ -3670,12 +3725,13 @@ class ToAcceptProps {
|
|
|
3670
3725
|
this.#typeChecker = typeChecker;
|
|
3671
3726
|
}
|
|
3672
3727
|
#explain(matchWorker, sourceNode, targetNode) {
|
|
3728
|
+
const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
|
|
3673
3729
|
const signatures = matchWorker.getSignatures(sourceNode);
|
|
3674
3730
|
return signatures.reduce((accumulator, signature, index) => {
|
|
3675
3731
|
let diagnostic;
|
|
3676
3732
|
const introText = matchWorker.assertion.isNot
|
|
3677
|
-
? ExpectDiagnosticText.acceptsProps(
|
|
3678
|
-
: ExpectDiagnosticText.doesNotAcceptProps(
|
|
3733
|
+
? ExpectDiagnosticText.acceptsProps(isExpression)
|
|
3734
|
+
: ExpectDiagnosticText.doesNotAcceptProps(isExpression);
|
|
3679
3735
|
const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
|
|
3680
3736
|
if (signatures.length > 1) {
|
|
3681
3737
|
const signatureText = this.#typeChecker.signatureToString(signature, sourceNode);
|
|
@@ -3815,18 +3871,18 @@ class ToAcceptProps {
|
|
|
3815
3871
|
const signatures = matchWorker.getSignatures(sourceNode);
|
|
3816
3872
|
if (signatures.length === 0) {
|
|
3817
3873
|
const expectedText = "of a function or class type";
|
|
3818
|
-
const text = this.#compiler
|
|
3819
|
-
? ExpectDiagnosticText.
|
|
3820
|
-
: ExpectDiagnosticText.
|
|
3874
|
+
const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
|
|
3875
|
+
? ExpectDiagnosticText.argumentMustBe("source", expectedText)
|
|
3876
|
+
: ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText);
|
|
3821
3877
|
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
3822
3878
|
diagnostics.push(Diagnostic.error(text, origin));
|
|
3823
3879
|
}
|
|
3824
3880
|
const targetType = matchWorker.getType(targetNode);
|
|
3825
3881
|
if (!(targetType.flags & this.#compiler.TypeFlags.Object)) {
|
|
3826
3882
|
const expectedText = "of an object type";
|
|
3827
|
-
const text = this.#compiler
|
|
3828
|
-
? ExpectDiagnosticText.
|
|
3829
|
-
: ExpectDiagnosticText.
|
|
3883
|
+
const text = nodeBelongsToArgumentList(this.#compiler, targetNode)
|
|
3884
|
+
? ExpectDiagnosticText.argumentMustBe("target", expectedText)
|
|
3885
|
+
: ExpectDiagnosticText.typeArgumentMustBe("Target", expectedText);
|
|
3830
3886
|
const origin = DiagnosticOrigin.fromNode(targetNode);
|
|
3831
3887
|
diagnostics.push(Diagnostic.error(text, origin));
|
|
3832
3888
|
}
|
|
@@ -3919,9 +3975,9 @@ class ToBeApplicable {
|
|
|
3919
3975
|
const type = matchWorker.getType(sourceNode);
|
|
3920
3976
|
if (type.getCallSignatures().length === 0) {
|
|
3921
3977
|
const expectedText = "of a function type";
|
|
3922
|
-
const text = this.#compiler
|
|
3923
|
-
? ExpectDiagnosticText.
|
|
3924
|
-
: ExpectDiagnosticText.
|
|
3978
|
+
const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
|
|
3979
|
+
? ExpectDiagnosticText.argumentMustBe("source", expectedText)
|
|
3980
|
+
: ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText);
|
|
3925
3981
|
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
3926
3982
|
onDiagnostics([Diagnostic.error(text, origin)]);
|
|
3927
3983
|
return;
|
|
@@ -3970,12 +4026,15 @@ class ToBeCallableWith {
|
|
|
3970
4026
|
return `with the given argument${nodes.length === 1 ? "" : "s"}`;
|
|
3971
4027
|
}
|
|
3972
4028
|
#explain(matchWorker, sourceNode, targetNodes) {
|
|
3973
|
-
const
|
|
4029
|
+
const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
|
|
3974
4030
|
const targetText = this.#resolveTargetText(targetNodes);
|
|
3975
4031
|
const diagnostics = [];
|
|
3976
4032
|
if (matchWorker.assertion.abilityDiagnostics) {
|
|
3977
4033
|
for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
|
|
3978
|
-
const text = [
|
|
4034
|
+
const text = [
|
|
4035
|
+
ExpectDiagnosticText.isNotCallable(isExpression, targetText),
|
|
4036
|
+
getDiagnosticMessageText(diagnostic),
|
|
4037
|
+
];
|
|
3979
4038
|
let origin;
|
|
3980
4039
|
if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, targetNodes)) {
|
|
3981
4040
|
origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceNode.getSourceFile());
|
|
@@ -3995,7 +4054,7 @@ class ToBeCallableWith {
|
|
|
3995
4054
|
}
|
|
3996
4055
|
else {
|
|
3997
4056
|
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
3998
|
-
diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isCallable(
|
|
4057
|
+
diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isCallable(isExpression, targetText), origin));
|
|
3999
4058
|
}
|
|
4000
4059
|
return diagnostics;
|
|
4001
4060
|
}
|
|
@@ -4008,16 +4067,17 @@ class ToBeCallableWith {
|
|
|
4008
4067
|
this.#compiler.isFunctionDeclaration(sourceNode) ||
|
|
4009
4068
|
this.#compiler.isFunctionExpression(sourceNode) ||
|
|
4010
4069
|
this.#compiler.isExpressionWithTypeArguments(sourceNode) ||
|
|
4011
|
-
this.#compiler.isIdentifier(sourceNode)
|
|
4070
|
+
this.#compiler.isIdentifier(sourceNode) ||
|
|
4071
|
+
this.#compiler.isPropertyAccessExpression(sourceNode)) {
|
|
4012
4072
|
type = matchWorker.getType(sourceNode);
|
|
4013
4073
|
}
|
|
4014
4074
|
if (!type || type.getCallSignatures().length === 0) {
|
|
4015
4075
|
const text = [];
|
|
4016
|
-
if (this.#compiler
|
|
4017
|
-
text.push(ExpectDiagnosticText.
|
|
4076
|
+
if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
|
|
4077
|
+
text.push(ExpectDiagnosticText.argumentMustBe("source", "a callable expression"));
|
|
4018
4078
|
}
|
|
4019
4079
|
else {
|
|
4020
|
-
text.push(ExpectDiagnosticText.
|
|
4080
|
+
text.push(ExpectDiagnosticText.typeArgumentMustBe("Source", "a callable type"));
|
|
4021
4081
|
}
|
|
4022
4082
|
if (type != null && type.getConstructSignatures().length > 0) {
|
|
4023
4083
|
text.push(ExpectDiagnosticText.didYouMeanToUse("the '.toBeConstructableWith()' matcher"));
|
|
@@ -4048,13 +4108,13 @@ class ToBeConstructableWith {
|
|
|
4048
4108
|
return `with the given argument${nodes.length === 1 ? "" : "s"}`;
|
|
4049
4109
|
}
|
|
4050
4110
|
#explain(matchWorker, sourceNode, targetNodes) {
|
|
4051
|
-
const
|
|
4111
|
+
const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
|
|
4052
4112
|
const targetText = this.#resolveTargetText(targetNodes);
|
|
4053
4113
|
const diagnostics = [];
|
|
4054
4114
|
if (matchWorker.assertion.abilityDiagnostics) {
|
|
4055
4115
|
for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
|
|
4056
4116
|
const text = [
|
|
4057
|
-
ExpectDiagnosticText.isNotConstructable(
|
|
4117
|
+
ExpectDiagnosticText.isNotConstructable(isExpression, targetText),
|
|
4058
4118
|
getDiagnosticMessageText(diagnostic),
|
|
4059
4119
|
];
|
|
4060
4120
|
let origin;
|
|
@@ -4076,7 +4136,7 @@ class ToBeConstructableWith {
|
|
|
4076
4136
|
}
|
|
4077
4137
|
else {
|
|
4078
4138
|
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
4079
|
-
diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isConstructable(
|
|
4139
|
+
diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isConstructable(isExpression, targetText), origin));
|
|
4080
4140
|
}
|
|
4081
4141
|
return diagnostics;
|
|
4082
4142
|
}
|
|
@@ -4086,16 +4146,17 @@ class ToBeConstructableWith {
|
|
|
4086
4146
|
type = matchWorker.typeChecker.getResolvedSignature(sourceNode)?.getReturnType();
|
|
4087
4147
|
}
|
|
4088
4148
|
if (this.#compiler.isExpressionWithTypeArguments(sourceNode) ||
|
|
4089
|
-
this.#compiler.isIdentifier(sourceNode)
|
|
4149
|
+
this.#compiler.isIdentifier(sourceNode) ||
|
|
4150
|
+
this.#compiler.isPropertyAccessExpression(sourceNode)) {
|
|
4090
4151
|
type = matchWorker.getType(sourceNode);
|
|
4091
4152
|
}
|
|
4092
4153
|
if (!type || type.getConstructSignatures().length === 0) {
|
|
4093
4154
|
const text = [];
|
|
4094
|
-
if (this.#compiler
|
|
4095
|
-
text.push(ExpectDiagnosticText.
|
|
4155
|
+
if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
|
|
4156
|
+
text.push(ExpectDiagnosticText.argumentMustBe("source", "a constructable expression"));
|
|
4096
4157
|
}
|
|
4097
4158
|
else {
|
|
4098
|
-
text.push(ExpectDiagnosticText.
|
|
4159
|
+
text.push(ExpectDiagnosticText.typeArgumentMustBe("Source", "a constructable type"));
|
|
4099
4160
|
}
|
|
4100
4161
|
if (type != null && type.getCallSignatures().length > 0) {
|
|
4101
4162
|
text.push(ExpectDiagnosticText.didYouMeanToUse("the '.toBeCallableWith()' matcher"));
|
|
@@ -4137,9 +4198,9 @@ class ToHaveProperty {
|
|
|
4137
4198
|
if (sourceType.flags & (this.#compiler.TypeFlags.Any | this.#compiler.TypeFlags.Never) ||
|
|
4138
4199
|
!matchWorker.extendsObjectType(sourceType)) {
|
|
4139
4200
|
const expectedText = "of an object type";
|
|
4140
|
-
const text = this.#compiler
|
|
4141
|
-
? ExpectDiagnosticText.
|
|
4142
|
-
: ExpectDiagnosticText.
|
|
4201
|
+
const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
|
|
4202
|
+
? ExpectDiagnosticText.argumentMustBe("source", expectedText)
|
|
4203
|
+
: ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText);
|
|
4143
4204
|
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
4144
4205
|
diagnostics.push(Diagnostic.error(text, origin));
|
|
4145
4206
|
}
|
|
@@ -4176,15 +4237,15 @@ class ToRaiseError {
|
|
|
4176
4237
|
this.#compiler = compiler;
|
|
4177
4238
|
}
|
|
4178
4239
|
#explain(matchWorker, sourceNode, targetNodes) {
|
|
4179
|
-
const
|
|
4240
|
+
const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
|
|
4180
4241
|
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
4181
4242
|
if (matchWorker.assertion.diagnostics.size === 0) {
|
|
4182
|
-
const text = ExpectDiagnosticText.didNotRaiseError(
|
|
4243
|
+
const text = ExpectDiagnosticText.didNotRaiseError(isExpression);
|
|
4183
4244
|
return [Diagnostic.error(text, origin)];
|
|
4184
4245
|
}
|
|
4185
4246
|
if (matchWorker.assertion.diagnostics.size !== targetNodes.length) {
|
|
4186
4247
|
const count = matchWorker.assertion.diagnostics.size;
|
|
4187
|
-
const text = ExpectDiagnosticText.raisedError(
|
|
4248
|
+
const text = ExpectDiagnosticText.raisedError(isExpression, count, targetNodes.length);
|
|
4188
4249
|
const related = [
|
|
4189
4250
|
Diagnostic.error(ExpectDiagnosticText.raisedTypeError(count)),
|
|
4190
4251
|
...Diagnostic.fromDiagnostics([...matchWorker.assertion.diagnostics]),
|
|
@@ -4196,8 +4257,8 @@ class ToRaiseError {
|
|
|
4196
4257
|
const isMatch = this.#matchExpectedError(diagnostic, targetNode);
|
|
4197
4258
|
if (matchWorker.assertion.isNot ? isMatch : !isMatch) {
|
|
4198
4259
|
const text = matchWorker.assertion.isNot
|
|
4199
|
-
? ExpectDiagnosticText.raisedMatchingError(
|
|
4200
|
-
: ExpectDiagnosticText.didNotRaiseMatchingError(
|
|
4260
|
+
? ExpectDiagnosticText.raisedMatchingError(isExpression)
|
|
4261
|
+
: ExpectDiagnosticText.didNotRaiseMatchingError(isExpression);
|
|
4201
4262
|
const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
|
|
4202
4263
|
const related = [
|
|
4203
4264
|
Diagnostic.error(ExpectDiagnosticText.raisedTypeError()),
|
|
@@ -4346,7 +4407,7 @@ class ExpectService {
|
|
|
4346
4407
|
}
|
|
4347
4408
|
#rejectsTypeArguments(matchWorker, onDiagnostics) {
|
|
4348
4409
|
for (const rejectedType of this.#rejectTypes) {
|
|
4349
|
-
const allowedKeyword = this.#compiler.SyntaxKind[`${
|
|
4410
|
+
const allowedKeyword = this.#compiler.SyntaxKind[`${capitalize(rejectedType)}Keyword`];
|
|
4350
4411
|
if (matchWorker.assertion.source[0]?.kind === allowedKeyword ||
|
|
4351
4412
|
matchWorker.assertion.target?.[0]?.kind === allowedKeyword) {
|
|
4352
4413
|
continue;
|
|
@@ -4356,11 +4417,11 @@ class ExpectService {
|
|
|
4356
4417
|
if (!argumentNode) {
|
|
4357
4418
|
continue;
|
|
4358
4419
|
}
|
|
4359
|
-
if (matchWorker.getType(argumentNode).flags & this.#compiler.TypeFlags[
|
|
4420
|
+
if (matchWorker.getType(argumentNode).flags & this.#compiler.TypeFlags[capitalize(rejectedType)]) {
|
|
4360
4421
|
const text = [
|
|
4361
|
-
this.#compiler
|
|
4362
|
-
? ExpectDiagnosticText.
|
|
4363
|
-
: ExpectDiagnosticText.
|
|
4422
|
+
nodeBelongsToArgumentList(this.#compiler, argumentNode)
|
|
4423
|
+
? ExpectDiagnosticText.argumentCannotBeOfType(argumentName, rejectedType)
|
|
4424
|
+
: ExpectDiagnosticText.typeArgumentCannotBeOfType(capitalize(argumentName), rejectedType),
|
|
4364
4425
|
...ExpectDiagnosticText.typeWasRejected(rejectedType),
|
|
4365
4426
|
];
|
|
4366
4427
|
const origin = DiagnosticOrigin.fromNode(argumentNode);
|
|
@@ -4431,6 +4492,9 @@ class TestTreeWalker {
|
|
|
4431
4492
|
case TestTreeNodeBrand.Expect:
|
|
4432
4493
|
this.#visitAssertion(testNode, runMode, parentResult);
|
|
4433
4494
|
break;
|
|
4495
|
+
case TestTreeNodeBrand.When:
|
|
4496
|
+
this.#visitWhen(testNode, runMode, parentResult);
|
|
4497
|
+
break;
|
|
4434
4498
|
}
|
|
4435
4499
|
}
|
|
4436
4500
|
}
|
|
@@ -4523,11 +4587,36 @@ class TestTreeWalker {
|
|
|
4523
4587
|
EventEmitter.dispatch(["test:pass", { result: testResult }]);
|
|
4524
4588
|
}
|
|
4525
4589
|
}
|
|
4590
|
+
#visitWhen(when, _runMode, _parentResult) {
|
|
4591
|
+
if (when.abilityDiagnostics != null && when.abilityDiagnostics.size > 0) {
|
|
4592
|
+
const diagnostics = [];
|
|
4593
|
+
for (const diagnostic of when.abilityDiagnostics) {
|
|
4594
|
+
if (isDiagnosticWithLocation(diagnostic)) {
|
|
4595
|
+
const text = getDiagnosticMessageText(diagnostic);
|
|
4596
|
+
let origin;
|
|
4597
|
+
if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, when.node)) {
|
|
4598
|
+
origin = DiagnosticOrigin.fromNodes(when.target);
|
|
4599
|
+
}
|
|
4600
|
+
else {
|
|
4601
|
+
origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), when.node.getSourceFile());
|
|
4602
|
+
}
|
|
4603
|
+
let related;
|
|
4604
|
+
if (diagnostic.relatedInformation != null) {
|
|
4605
|
+
related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
|
|
4606
|
+
}
|
|
4607
|
+
diagnostics.push(Diagnostic.error(text, origin).add({ related }));
|
|
4608
|
+
}
|
|
4609
|
+
}
|
|
4610
|
+
EventEmitter.dispatch(["task:error", { diagnostics, result: this.#taskResult }]);
|
|
4611
|
+
return;
|
|
4612
|
+
}
|
|
4613
|
+
}
|
|
4526
4614
|
}
|
|
4527
4615
|
|
|
4528
4616
|
class TaskRunner {
|
|
4529
4617
|
#collectService;
|
|
4530
4618
|
#compiler;
|
|
4619
|
+
#eventEmitter = new EventEmitter();
|
|
4531
4620
|
#resolvedConfig;
|
|
4532
4621
|
#projectService;
|
|
4533
4622
|
constructor(compiler, resolvedConfig) {
|
|
@@ -4536,18 +4625,18 @@ class TaskRunner {
|
|
|
4536
4625
|
this.#projectService = new ProjectService(compiler, this.#resolvedConfig);
|
|
4537
4626
|
this.#collectService = new CollectService(compiler, this.#projectService, this.#resolvedConfig);
|
|
4538
4627
|
}
|
|
4539
|
-
run(task, cancellationToken) {
|
|
4540
|
-
if (cancellationToken
|
|
4628
|
+
async run(task, cancellationToken) {
|
|
4629
|
+
if (cancellationToken.isCancellationRequested) {
|
|
4541
4630
|
return;
|
|
4542
4631
|
}
|
|
4543
4632
|
this.#projectService.openFile(task.filePath, undefined, this.#resolvedConfig.rootPath);
|
|
4544
4633
|
const taskResult = new TaskResult(task);
|
|
4545
4634
|
EventEmitter.dispatch(["task:start", { result: taskResult }]);
|
|
4546
|
-
this.#run(task, taskResult, cancellationToken);
|
|
4635
|
+
await this.#run(task, taskResult, cancellationToken);
|
|
4547
4636
|
EventEmitter.dispatch(["task:end", { result: taskResult }]);
|
|
4548
4637
|
this.#projectService.closeFile(task.filePath);
|
|
4549
4638
|
}
|
|
4550
|
-
#run(task, taskResult, cancellationToken) {
|
|
4639
|
+
async #run(task, taskResult, cancellationToken) {
|
|
4551
4640
|
if (!existsSync(task.filePath)) {
|
|
4552
4641
|
EventEmitter.dispatch([
|
|
4553
4642
|
"task:error",
|
|
@@ -4555,28 +4644,51 @@ class TaskRunner {
|
|
|
4555
4644
|
]);
|
|
4556
4645
|
return;
|
|
4557
4646
|
}
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
}
|
|
4562
|
-
const syntacticDiagnostics = languageService.getSyntacticDiagnostics(task.filePath);
|
|
4563
|
-
if (syntacticDiagnostics.length > 0) {
|
|
4647
|
+
let languageService = this.#projectService.getLanguageService(task.filePath);
|
|
4648
|
+
const syntacticDiagnostics = languageService?.getSyntacticDiagnostics(task.filePath);
|
|
4649
|
+
if (syntacticDiagnostics != null && syntacticDiagnostics.length > 0) {
|
|
4564
4650
|
EventEmitter.dispatch([
|
|
4565
4651
|
"task:error",
|
|
4566
4652
|
{ diagnostics: Diagnostic.fromDiagnostics(syntacticDiagnostics), result: taskResult },
|
|
4567
4653
|
]);
|
|
4568
4654
|
return;
|
|
4569
4655
|
}
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4656
|
+
let semanticDiagnostics = languageService?.getSemanticDiagnostics(task.filePath);
|
|
4657
|
+
let program = languageService?.getProgram();
|
|
4658
|
+
let sourceFile = program?.getSourceFile(task.filePath);
|
|
4659
|
+
if (sourceFile?.text.startsWith("// @tstyche-template")) {
|
|
4660
|
+
if (semanticDiagnostics != null && semanticDiagnostics.length > 0) {
|
|
4661
|
+
EventEmitter.dispatch([
|
|
4662
|
+
"task:error",
|
|
4663
|
+
{ diagnostics: Diagnostic.fromDiagnostics(semanticDiagnostics), result: taskResult },
|
|
4664
|
+
]);
|
|
4665
|
+
return;
|
|
4666
|
+
}
|
|
4667
|
+
const text = (await import(task.filePath)).default;
|
|
4668
|
+
this.#projectService.openFile(task.filePath, text, this.#resolvedConfig.rootPath);
|
|
4669
|
+
languageService = this.#projectService.getLanguageService(task.filePath);
|
|
4670
|
+
const syntacticDiagnostics = languageService?.getSyntacticDiagnostics(task.filePath);
|
|
4671
|
+
if (syntacticDiagnostics != null && syntacticDiagnostics.length > 0) {
|
|
4672
|
+
EventEmitter.dispatch([
|
|
4673
|
+
"task:error",
|
|
4674
|
+
{ diagnostics: Diagnostic.fromDiagnostics(syntacticDiagnostics), result: taskResult },
|
|
4675
|
+
]);
|
|
4676
|
+
return;
|
|
4677
|
+
}
|
|
4678
|
+
semanticDiagnostics = languageService?.getSemanticDiagnostics(task.filePath);
|
|
4679
|
+
program = languageService?.getProgram();
|
|
4680
|
+
sourceFile = program?.getSourceFile(task.filePath);
|
|
4574
4681
|
}
|
|
4575
|
-
const sourceFile = program.getSourceFile(task.filePath);
|
|
4576
4682
|
if (!sourceFile) {
|
|
4577
4683
|
return;
|
|
4578
4684
|
}
|
|
4685
|
+
const cancellationHandler = new CancellationHandler(cancellationToken, CancellationReason.CollectError);
|
|
4686
|
+
this.#eventEmitter.addHandler(cancellationHandler);
|
|
4579
4687
|
const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics);
|
|
4688
|
+
this.#eventEmitter.removeHandler(cancellationHandler);
|
|
4689
|
+
if (cancellationToken.isCancellationRequested) {
|
|
4690
|
+
return;
|
|
4691
|
+
}
|
|
4580
4692
|
if (testTree.diagnostics.size > 0) {
|
|
4581
4693
|
EventEmitter.dispatch([
|
|
4582
4694
|
"task:error",
|
|
@@ -4584,7 +4696,7 @@ class TaskRunner {
|
|
|
4584
4696
|
]);
|
|
4585
4697
|
return;
|
|
4586
4698
|
}
|
|
4587
|
-
const typeChecker = program
|
|
4699
|
+
const typeChecker = program?.getTypeChecker();
|
|
4588
4700
|
const testTreeWalker = new TestTreeWalker(this.#compiler, typeChecker, this.#resolvedConfig, {
|
|
4589
4701
|
cancellationToken,
|
|
4590
4702
|
taskResult,
|
|
@@ -4598,15 +4710,13 @@ class TaskRunner {
|
|
|
4598
4710
|
class Runner {
|
|
4599
4711
|
#eventEmitter = new EventEmitter();
|
|
4600
4712
|
#resolvedConfig;
|
|
4601
|
-
static version = "4.0.0-beta.
|
|
4713
|
+
static version = "4.0.0-beta.7";
|
|
4602
4714
|
constructor(resolvedConfig) {
|
|
4603
4715
|
this.#resolvedConfig = resolvedConfig;
|
|
4604
4716
|
}
|
|
4605
4717
|
#addHandlers(cancellationToken) {
|
|
4606
4718
|
const resultHandler = new ResultHandler();
|
|
4607
4719
|
this.#eventEmitter.addHandler(resultHandler);
|
|
4608
|
-
const testTreeHandler = new TestTreeHandler();
|
|
4609
|
-
this.#eventEmitter.addHandler(testTreeHandler);
|
|
4610
4720
|
if (this.#resolvedConfig.failFast) {
|
|
4611
4721
|
const cancellationHandler = new CancellationHandler(cancellationToken, CancellationReason.FailFast);
|
|
4612
4722
|
this.#eventEmitter.addHandler(cancellationHandler);
|
|
@@ -4649,7 +4759,7 @@ class Runner {
|
|
|
4649
4759
|
this.#eventEmitter.removeHandlers();
|
|
4650
4760
|
}
|
|
4651
4761
|
async #run(tasks, cancellationToken) {
|
|
4652
|
-
const result = new Result(
|
|
4762
|
+
const result = new Result(tasks);
|
|
4653
4763
|
EventEmitter.dispatch(["run:start", { result }]);
|
|
4654
4764
|
for (const target of this.#resolvedConfig.target) {
|
|
4655
4765
|
const targetResult = new TargetResult(target, tasks);
|
|
@@ -4658,7 +4768,7 @@ class Runner {
|
|
|
4658
4768
|
if (compiler) {
|
|
4659
4769
|
const taskRunner = new TaskRunner(compiler, this.#resolvedConfig);
|
|
4660
4770
|
for (const task of tasks) {
|
|
4661
|
-
taskRunner.run(task, cancellationToken);
|
|
4771
|
+
await taskRunner.run(task, cancellationToken);
|
|
4662
4772
|
}
|
|
4663
4773
|
}
|
|
4664
4774
|
EventEmitter.dispatch(["target:end", { result: targetResult }]);
|
|
@@ -4800,4 +4910,4 @@ class Cli {
|
|
|
4800
4910
|
}
|
|
4801
4911
|
}
|
|
4802
4912
|
|
|
4803
|
-
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,
|
|
4913
|
+
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 };
|