tstyche 4.0.0-beta.6 → 4.0.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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(resolvedConfig, tasks) {
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
- var TestTreeNodeBrand;
1919
- (function (TestTreeNodeBrand) {
1920
- TestTreeNodeBrand["Describe"] = "describe";
1921
- TestTreeNodeBrand["Test"] = "test";
1922
- TestTreeNodeBrand["Expect"] = "expect";
1923
- })(TestTreeNodeBrand || (TestTreeNodeBrand = {}));
1917
+ function jsx(type, props) {
1918
+ return { props, type };
1919
+ }
1924
1920
 
1925
- class TestTreeNode {
1926
- brand;
1927
- children = [];
1928
- #compiler;
1929
- diagnostics = new Set();
1930
- flags;
1931
- name = "";
1932
- node;
1933
- parent;
1934
- constructor(compiler, brand, node, parent, flags) {
1935
- this.brand = brand;
1936
- this.#compiler = compiler;
1937
- this.node = node;
1938
- this.parent = parent;
1939
- this.flags = flags;
1940
- if (node.arguments[0] != null && compiler.isStringLiteralLike(node.arguments[0])) {
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
- class AssertionNode extends TestTreeNode {
1983
- abilityDiagnostics;
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 AbilityLayer {
2011
- #filePath = "";
2012
- #nodes = [];
2013
- #projectService;
2014
- #resolvedConfig;
2015
- #text = "";
2016
- constructor(projectService, resolvedConfig) {
2017
- this.#projectService = projectService;
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
- #getErasedRangeText(range) {
2021
- if (this.#text.indexOf("\n", range.start) >= range.end) {
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
- #addRanges(node, ranges) {
2039
- this.#nodes.push(node);
2040
- for (const range of ranges) {
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
- close() {
2048
- if (this.#nodes.length > 0) {
2049
- this.#projectService.openFile(this.#filePath, this.#text, this.#resolvedConfig.rootPath);
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
- this.#filePath = "";
2065
- this.#nodes = [];
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
- open(sourceFile) {
2095
- this.#filePath = sourceFile.fileName;
2096
- this.#text = sourceFile.text;
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
- var TestTreeNodeFlags;
2101
- (function (TestTreeNodeFlags) {
2102
- TestTreeNodeFlags[TestTreeNodeFlags["None"] = 0] = "None";
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 MatchText({ text }) {
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 }) }), jsx(Line, {}), jsx(RanFilesText, { onlyMatch: onlyMatch, pathMatch: pathMatch, skipMatch: skipMatch })] }));
2236
+ return (jsx(Text, { children: [targetCountText, fileCountText, testCount.total > 0 ? testCountText : undefined, expectCount.total > 0 ? assertionCountText : undefined, jsx(RowText, { label: "Duration", text: jsx(DurationText, { seconds: duration / 1000 }) })] }));
2663
2237
  }
2664
2238
 
2665
2239
  function StatusText({ status }) {
@@ -2808,6 +2382,7 @@ class ListReporter extends BaseReporter {
2808
2382
  this.#hasReportedError = false;
2809
2383
  break;
2810
2384
  case "task:error":
2385
+ case "collect:error":
2811
2386
  for (const diagnostic of payload.diagnostics) {
2812
2387
  this.#fileView.addMessage(diagnosticText(diagnostic));
2813
2388
  }
@@ -2903,9 +2478,6 @@ class SummaryReporter extends BaseReporter {
2903
2478
  duration: payload.result.timing.duration,
2904
2479
  expectCount: payload.result.expectCount,
2905
2480
  fileCount: payload.result.fileCount,
2906
- onlyMatch: payload.result.resolvedConfig.only,
2907
- pathMatch: payload.result.resolvedConfig.pathMatch,
2908
- skipMatch: payload.result.resolvedConfig.skip,
2909
2481
  targetCount: payload.result.targetCount,
2910
2482
  testCount: payload.result.testCount,
2911
2483
  }));
@@ -2975,6 +2547,7 @@ var CancellationReason;
2975
2547
  (function (CancellationReason) {
2976
2548
  CancellationReason["ConfigChange"] = "configChange";
2977
2549
  CancellationReason["ConfigError"] = "configError";
2550
+ CancellationReason["CollectError"] = "collectError";
2978
2551
  CancellationReason["FailFast"] = "failFast";
2979
2552
  CancellationReason["WatchClose"] = "watchClose";
2980
2553
  })(CancellationReason || (CancellationReason = {}));
@@ -3202,101 +2775,587 @@ class Debounce {
3202
2775
  this.#resolve?.(this.#onResolve());
3203
2776
  }, this.#delay);
3204
2777
  }
3205
- resolve(value) {
3206
- this.#resolve?.(value);
2778
+ resolve(value) {
2779
+ this.#resolve?.(value);
2780
+ }
2781
+ schedule() {
2782
+ return new Promise((resolve) => {
2783
+ this.#resolve = resolve;
2784
+ });
2785
+ }
2786
+ }
2787
+
2788
+ class WatchService {
2789
+ #changedTestFiles = new Map();
2790
+ #inputService;
2791
+ #resolvedConfig;
2792
+ #watchedTestFiles;
2793
+ #watchers = [];
2794
+ constructor(resolvedConfig, tasks) {
2795
+ this.#resolvedConfig = resolvedConfig;
2796
+ this.#watchedTestFiles = new Map(tasks.map((task) => [task.filePath, task]));
2797
+ }
2798
+ #onDiagnostics(diagnostic) {
2799
+ EventEmitter.dispatch(["watch:error", { diagnostics: [diagnostic] }]);
2800
+ }
2801
+ async *watch(cancellationToken) {
2802
+ const onResolve = () => {
2803
+ const testFiles = [...this.#changedTestFiles.values()];
2804
+ this.#changedTestFiles.clear();
2805
+ return testFiles;
2806
+ };
2807
+ const debounce = new Debounce(100, onResolve);
2808
+ const onClose = (reason) => {
2809
+ debounce.cancel();
2810
+ this.#inputService?.close();
2811
+ for (const watcher of this.#watchers) {
2812
+ watcher.close();
2813
+ }
2814
+ cancellationToken.cancel(reason);
2815
+ debounce.resolve([]);
2816
+ };
2817
+ if (!environmentOptions.noInteractive) {
2818
+ const onInput = (chunk) => {
2819
+ switch (chunk.toLowerCase()) {
2820
+ case "\u0003":
2821
+ case "\u0004":
2822
+ case "\u001B":
2823
+ case "q":
2824
+ case "x":
2825
+ onClose(CancellationReason.WatchClose);
2826
+ break;
2827
+ case "\u000D":
2828
+ case "\u0020":
2829
+ case "a":
2830
+ debounce.cancel();
2831
+ if (this.#watchedTestFiles.size > 0) {
2832
+ debounce.resolve([...this.#watchedTestFiles.values()]);
2833
+ }
2834
+ break;
2835
+ }
2836
+ };
2837
+ this.#inputService = new InputService(onInput);
2838
+ }
2839
+ const onChangedFile = (filePath) => {
2840
+ debounce.refresh();
2841
+ let task = this.#watchedTestFiles.get(filePath);
2842
+ if (task != null) {
2843
+ this.#changedTestFiles.set(filePath, task);
2844
+ }
2845
+ else if (Select.isTestFile(filePath, this.#resolvedConfig)) {
2846
+ task = new Task(filePath);
2847
+ this.#changedTestFiles.set(filePath, task);
2848
+ this.#watchedTestFiles.set(filePath, task);
2849
+ }
2850
+ };
2851
+ const onRemovedFile = (filePath) => {
2852
+ this.#changedTestFiles.delete(filePath);
2853
+ this.#watchedTestFiles.delete(filePath);
2854
+ if (this.#watchedTestFiles.size === 0) {
2855
+ debounce.cancel();
2856
+ this.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereLeft(this.#resolvedConfig)));
2857
+ }
2858
+ };
2859
+ this.#watchers.push(new Watcher(this.#resolvedConfig.rootPath, onChangedFile, onRemovedFile, { recursive: true }));
2860
+ const onChangedConfigFile = () => {
2861
+ onClose(CancellationReason.ConfigChange);
2862
+ };
2863
+ this.#watchers.push(new FileWatcher(this.#resolvedConfig.configFilePath, onChangedConfigFile));
2864
+ for (const watcher of this.#watchers) {
2865
+ watcher.watch();
2866
+ }
2867
+ while (!cancellationToken.isCancellationRequested) {
2868
+ const testFiles = await debounce.schedule();
2869
+ if (testFiles.length > 0) {
2870
+ yield testFiles;
2871
+ }
2872
+ }
2873
+ }
2874
+ }
2875
+
2876
+ var TestTreeNodeBrand;
2877
+ (function (TestTreeNodeBrand) {
2878
+ TestTreeNodeBrand["Describe"] = "describe";
2879
+ TestTreeNodeBrand["Test"] = "test";
2880
+ TestTreeNodeBrand["Expect"] = "expect";
2881
+ TestTreeNodeBrand["When"] = "when";
2882
+ })(TestTreeNodeBrand || (TestTreeNodeBrand = {}));
2883
+
2884
+ class TestTreeNode {
2885
+ brand;
2886
+ children = [];
2887
+ #compiler;
2888
+ diagnostics = new Set();
2889
+ flags;
2890
+ name = "";
2891
+ node;
2892
+ parent;
2893
+ constructor(compiler, brand, node, parent, flags) {
2894
+ this.brand = brand;
2895
+ this.#compiler = compiler;
2896
+ this.node = node;
2897
+ this.parent = parent;
2898
+ this.flags = flags;
2899
+ if (node.arguments[0] != null && compiler.isStringLiteralLike(node.arguments[0])) {
2900
+ this.name = node.arguments[0].text;
2901
+ }
2902
+ if (node.arguments[1] != null && compiler.isFunctionLike(node.arguments[1])) {
2903
+ for (const diagnostic of parent.diagnostics) {
2904
+ if (diagnosticBelongsToNode(diagnostic, node.arguments[1].body)) {
2905
+ this.diagnostics.add(diagnostic);
2906
+ parent.diagnostics.delete(diagnostic);
2907
+ }
2908
+ }
2909
+ }
2910
+ }
2911
+ validate() {
2912
+ const diagnostics = [];
2913
+ const getText = (node) => `'${node.expression.getText()}()' cannot be nested within '${this.node.expression.getText()}()'.`;
2914
+ const getParentCallExpression = (node) => {
2915
+ while (!this.#compiler.isCallExpression(node.parent)) {
2916
+ node = node.parent;
2917
+ }
2918
+ return node.parent;
2919
+ };
2920
+ switch (this.brand) {
2921
+ case TestTreeNodeBrand.Describe:
2922
+ for (const child of this.children) {
2923
+ if (child.brand === TestTreeNodeBrand.Expect || child.brand === TestTreeNodeBrand.When) {
2924
+ diagnostics.push(Diagnostic.error(getText(child.node), DiagnosticOrigin.fromNode(getParentCallExpression(child.node))));
2925
+ }
2926
+ }
2927
+ break;
2928
+ case TestTreeNodeBrand.Test:
2929
+ case TestTreeNodeBrand.Expect:
2930
+ for (const child of this.children) {
2931
+ if (child.brand === TestTreeNodeBrand.Describe || child.brand === TestTreeNodeBrand.Test) {
2932
+ diagnostics.push(Diagnostic.error(getText(child.node), DiagnosticOrigin.fromNode(child.node)));
2933
+ }
2934
+ }
2935
+ break;
2936
+ }
2937
+ return diagnostics;
2938
+ }
2939
+ }
2940
+
2941
+ class AssertionNode extends TestTreeNode {
2942
+ abilityDiagnostics;
2943
+ isNot;
2944
+ matcherNode;
2945
+ matcherNameNode;
2946
+ modifierNode;
2947
+ notNode;
2948
+ source;
2949
+ target;
2950
+ constructor(compiler, brand, node, parent, flags, matcherNode, matcherNameNode, modifierNode, notNode) {
2951
+ super(compiler, brand, node, parent, flags);
2952
+ this.isNot = notNode != null;
2953
+ this.matcherNode = matcherNode;
2954
+ this.matcherNameNode = matcherNameNode;
2955
+ this.modifierNode = modifierNode;
2956
+ this.source = this.node.typeArguments ?? this.node.arguments;
2957
+ if (compiler.isCallExpression(this.matcherNode)) {
2958
+ this.target = this.matcherNode.typeArguments ?? this.matcherNode.arguments;
2959
+ }
2960
+ for (const diagnostic of parent.diagnostics) {
2961
+ if (diagnosticBelongsToNode(diagnostic, this.source)) {
2962
+ this.diagnostics.add(diagnostic);
2963
+ parent.diagnostics.delete(diagnostic);
2964
+ }
2965
+ }
2966
+ }
2967
+ }
2968
+
2969
+ function nodeBelongsToArgumentList(compiler, node) {
2970
+ if (compiler.isCallExpression(node.parent)) {
2971
+ return node.parent.arguments.some((argument) => argument === node);
2972
+ }
2973
+ return false;
2974
+ }
2975
+
2976
+ class AbilityLayer {
2977
+ #compiler;
2978
+ #filePath = "";
2979
+ #nodes = [];
2980
+ #projectService;
2981
+ #resolvedConfig;
2982
+ #text = "";
2983
+ constructor(compiler, projectService, resolvedConfig) {
2984
+ this.#compiler = compiler;
2985
+ this.#projectService = projectService;
2986
+ this.#resolvedConfig = resolvedConfig;
2987
+ }
2988
+ #getErasedRangeText(range) {
2989
+ if (this.#text.indexOf("\n", range.start) >= range.end) {
2990
+ return " ".repeat(range.end - range.start);
2991
+ }
2992
+ const text = [];
2993
+ for (let index = range.start; index < range.end; index++) {
2994
+ const character = this.#text.charAt(index);
2995
+ switch (character) {
2996
+ case "\n":
2997
+ case "\r":
2998
+ text.push(character);
2999
+ break;
3000
+ default:
3001
+ text.push(" ");
3002
+ }
3003
+ }
3004
+ return text.join("");
3005
+ }
3006
+ #addRanges(node, ranges) {
3007
+ this.#nodes.push(node);
3008
+ for (const range of ranges) {
3009
+ const rangeText = range.replacement != null
3010
+ ? `${range.replacement}${this.#getErasedRangeText(range).slice(range.replacement.length)}`
3011
+ : this.#getErasedRangeText(range);
3012
+ this.#text = `${this.#text.slice(0, range.start)}${rangeText}${this.#text.slice(range.end)}`;
3013
+ }
3014
+ }
3015
+ close() {
3016
+ if (this.#nodes.length > 0) {
3017
+ this.#projectService.openFile(this.#filePath, this.#text, this.#resolvedConfig.rootPath);
3018
+ const languageService = this.#projectService.getLanguageService(this.#filePath);
3019
+ const diagnostics = new Set(languageService?.getSemanticDiagnostics(this.#filePath));
3020
+ for (const node of this.#nodes.reverse()) {
3021
+ for (const diagnostic of diagnostics) {
3022
+ if (diagnosticBelongsToNode(diagnostic, "matcherNode" in node ? node.matcherNode : node.actionNode)) {
3023
+ if (!node.abilityDiagnostics) {
3024
+ node.abilityDiagnostics = new Set();
3025
+ }
3026
+ node.abilityDiagnostics.add(diagnostic);
3027
+ diagnostics.delete(diagnostic);
3028
+ }
3029
+ }
3030
+ }
3031
+ }
3032
+ this.#filePath = "";
3033
+ this.#nodes = [];
3034
+ this.#text = "";
3035
+ }
3036
+ handleWhen(whenNode) {
3037
+ const whenStart = whenNode.node.getStart();
3038
+ const whenExpressionEnd = whenNode.node.expression.getEnd();
3039
+ const whenEnd = whenNode.node.getEnd();
3040
+ const actionNameEnd = whenNode.actionNameNode.getEnd();
3041
+ switch (whenNode.actionNameNode.name.text) {
3042
+ case "isCalledWith":
3043
+ this.#addRanges(whenNode, [
3044
+ {
3045
+ end: whenExpressionEnd,
3046
+ start: whenStart,
3047
+ replacement: nodeBelongsToArgumentList(this.#compiler, whenNode.actionNode) ? "" : ";",
3048
+ },
3049
+ { end: actionNameEnd, start: whenEnd },
3050
+ ]);
3051
+ break;
3052
+ }
3053
+ }
3054
+ handleAssertion(assertionNode) {
3055
+ const expectStart = assertionNode.node.getStart();
3056
+ const expectExpressionEnd = assertionNode.node.expression.getEnd();
3057
+ const expectEnd = assertionNode.node.getEnd();
3058
+ const matcherNameEnd = assertionNode.matcherNameNode.getEnd();
3059
+ switch (assertionNode.matcherNameNode.name.text) {
3060
+ case "toBeApplicable":
3061
+ this.#addRanges(assertionNode, [
3062
+ { end: expectExpressionEnd, start: expectStart },
3063
+ { end: matcherNameEnd, start: expectEnd },
3064
+ ]);
3065
+ break;
3066
+ case "toBeCallableWith":
3067
+ this.#addRanges(assertionNode, [
3068
+ {
3069
+ end: expectExpressionEnd,
3070
+ start: expectStart,
3071
+ replacement: nodeBelongsToArgumentList(this.#compiler, assertionNode.matcherNode) ? "" : ";",
3072
+ },
3073
+ { end: matcherNameEnd, start: expectEnd },
3074
+ ]);
3075
+ break;
3076
+ case "toBeConstructableWith":
3077
+ this.#addRanges(assertionNode, [
3078
+ {
3079
+ end: expectExpressionEnd,
3080
+ start: expectStart,
3081
+ replacement: nodeBelongsToArgumentList(this.#compiler, assertionNode.matcherNode) ? "new" : "; new",
3082
+ },
3083
+ { end: matcherNameEnd, start: expectEnd },
3084
+ ]);
3085
+ break;
3086
+ }
3087
+ }
3088
+ open(sourceFile) {
3089
+ this.#filePath = sourceFile.fileName;
3090
+ this.#text = sourceFile.text;
3091
+ }
3092
+ }
3093
+
3094
+ class CollectDiagnosticText {
3095
+ static cannotBeNestedWithin(source, target) {
3096
+ return `'${source}()' cannot be nested within '${target}()'.`;
3097
+ }
3098
+ }
3099
+
3100
+ var TestTreeNodeFlags;
3101
+ (function (TestTreeNodeFlags) {
3102
+ TestTreeNodeFlags[TestTreeNodeFlags["None"] = 0] = "None";
3103
+ TestTreeNodeFlags[TestTreeNodeFlags["Fail"] = 1] = "Fail";
3104
+ TestTreeNodeFlags[TestTreeNodeFlags["Only"] = 2] = "Only";
3105
+ TestTreeNodeFlags[TestTreeNodeFlags["Skip"] = 4] = "Skip";
3106
+ TestTreeNodeFlags[TestTreeNodeFlags["Todo"] = 8] = "Todo";
3107
+ })(TestTreeNodeFlags || (TestTreeNodeFlags = {}));
3108
+
3109
+ class IdentifierLookup {
3110
+ #compiler;
3111
+ #identifiers;
3112
+ #moduleSpecifiers = ['"tstyche"', "'tstyche'"];
3113
+ constructor(compiler, identifiers) {
3114
+ this.#compiler = compiler;
3115
+ this.#identifiers = identifiers ?? {
3116
+ namedImports: {
3117
+ describe: undefined,
3118
+ expect: undefined,
3119
+ it: undefined,
3120
+ namespace: undefined,
3121
+ test: undefined,
3122
+ when: undefined,
3123
+ },
3124
+ namespace: undefined,
3125
+ };
3126
+ }
3127
+ handleImportDeclaration(node) {
3128
+ if (this.#moduleSpecifiers.includes(node.moduleSpecifier.getText()) &&
3129
+ node.importClause?.isTypeOnly !== true &&
3130
+ node.importClause?.namedBindings != null) {
3131
+ if (this.#compiler.isNamedImports(node.importClause.namedBindings)) {
3132
+ for (const element of node.importClause.namedBindings.elements) {
3133
+ if (element.isTypeOnly) {
3134
+ continue;
3135
+ }
3136
+ let identifierKey;
3137
+ if (element.propertyName) {
3138
+ identifierKey = element.propertyName.getText();
3139
+ }
3140
+ else {
3141
+ identifierKey = element.name.getText();
3142
+ }
3143
+ if (identifierKey in this.#identifiers.namedImports) {
3144
+ this.#identifiers.namedImports[identifierKey] = element.name.getText();
3145
+ }
3146
+ }
3147
+ }
3148
+ if (this.#compiler.isNamespaceImport(node.importClause.namedBindings)) {
3149
+ this.#identifiers.namespace = node.importClause.namedBindings.name.getText();
3150
+ }
3151
+ }
3152
+ }
3153
+ resolveTestMemberMeta(node) {
3154
+ let flags = TestTreeNodeFlags.None;
3155
+ let expression = node.expression;
3156
+ while (this.#compiler.isPropertyAccessExpression(expression)) {
3157
+ if (expression.expression.getText() === this.#identifiers.namespace) {
3158
+ break;
3159
+ }
3160
+ switch (expression.name.getText()) {
3161
+ case "fail":
3162
+ flags |= TestTreeNodeFlags.Fail;
3163
+ break;
3164
+ case "only":
3165
+ flags |= TestTreeNodeFlags.Only;
3166
+ break;
3167
+ case "skip":
3168
+ flags |= TestTreeNodeFlags.Skip;
3169
+ break;
3170
+ case "todo":
3171
+ flags |= TestTreeNodeFlags.Todo;
3172
+ break;
3173
+ }
3174
+ expression = expression.expression;
3175
+ }
3176
+ let identifier;
3177
+ if (this.#compiler.isPropertyAccessExpression(expression) &&
3178
+ expression.expression.getText() === this.#identifiers.namespace) {
3179
+ identifier = expression.name.getText();
3180
+ }
3181
+ else {
3182
+ identifier = Object.keys(this.#identifiers.namedImports).find((key) => this.#identifiers.namedImports[key] === expression.getText());
3183
+ }
3184
+ if (!identifier) {
3185
+ return;
3186
+ }
3187
+ switch (identifier) {
3188
+ case "describe":
3189
+ return { brand: TestTreeNodeBrand.Describe, flags, identifier };
3190
+ case "it":
3191
+ case "test":
3192
+ return { brand: TestTreeNodeBrand.Test, flags, identifier };
3193
+ case "expect":
3194
+ return { brand: TestTreeNodeBrand.Expect, flags, identifier };
3195
+ case "when":
3196
+ return { brand: TestTreeNodeBrand.When, flags, identifier };
3197
+ }
3198
+ return;
3199
+ }
3200
+ }
3201
+
3202
+ class TestTree {
3203
+ children = [];
3204
+ diagnostics;
3205
+ hasOnly = false;
3206
+ sourceFile;
3207
+ constructor(diagnostics, sourceFile) {
3208
+ this.diagnostics = diagnostics;
3209
+ this.sourceFile = sourceFile;
3207
3210
  }
3208
- schedule() {
3209
- return new Promise((resolve) => {
3210
- this.#resolve = resolve;
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 WatchService {
3216
- #changedTestFiles = new Map();
3217
- #inputService;
3232
+ class CollectService {
3233
+ #abilityLayer;
3234
+ #compiler;
3235
+ #identifierLookup;
3236
+ #projectService;
3218
3237
  #resolvedConfig;
3219
- #watchedTestFiles;
3220
- #watchers = [];
3221
- constructor(resolvedConfig, tasks) {
3238
+ #testTree;
3239
+ constructor(compiler, projectService, resolvedConfig) {
3240
+ this.#compiler = compiler;
3241
+ this.#projectService = projectService;
3222
3242
  this.#resolvedConfig = resolvedConfig;
3223
- this.#watchedTestFiles = new Map(tasks.map((task) => [task.filePath, task]));
3243
+ this.#abilityLayer = new AbilityLayer(compiler, this.#projectService, this.#resolvedConfig);
3244
+ this.#identifierLookup = new IdentifierLookup(compiler);
3224
3245
  }
3225
- #onDiagnostics(diagnostic) {
3226
- EventEmitter.dispatch(["watch:error", { diagnostics: [diagnostic] }]);
3227
- }
3228
- async *watch(cancellationToken) {
3229
- const onResolve = () => {
3230
- const testFiles = [...this.#changedTestFiles.values()];
3231
- this.#changedTestFiles.clear();
3232
- return testFiles;
3233
- };
3234
- const debounce = new Debounce(100, onResolve);
3235
- const onClose = (reason) => {
3236
- debounce.cancel();
3237
- this.#inputService?.close();
3238
- for (const watcher of this.#watchers) {
3239
- watcher.close();
3246
+ #collectTestTreeNodes(node, parent) {
3247
+ if (this.#compiler.isCallExpression(node)) {
3248
+ const meta = this.#identifierLookup.resolveTestMemberMeta(node);
3249
+ if (meta != null && (meta.brand === TestTreeNodeBrand.Describe || meta.brand === TestTreeNodeBrand.Test)) {
3250
+ const testTreeNode = new TestTreeNode(this.#compiler, meta.brand, node, parent, meta.flags);
3251
+ this.#compiler.forEachChild(node, (node) => {
3252
+ this.#collectTestTreeNodes(node, testTreeNode);
3253
+ });
3254
+ this.#onNode(testTreeNode, parent);
3255
+ return;
3240
3256
  }
3241
- cancellationToken.cancel(reason);
3242
- debounce.resolve([]);
3243
- };
3244
- if (!environmentOptions.noInteractive) {
3245
- const onInput = (chunk) => {
3246
- switch (chunk.toLowerCase()) {
3247
- case "\u0003":
3248
- case "\u0004":
3249
- case "\u001B":
3250
- case "q":
3251
- case "x":
3252
- onClose(CancellationReason.WatchClose);
3253
- break;
3254
- case "\u000D":
3255
- case "\u0020":
3256
- case "a":
3257
- debounce.cancel();
3258
- if (this.#watchedTestFiles.size > 0) {
3259
- debounce.resolve([...this.#watchedTestFiles.values()]);
3260
- }
3261
- break;
3257
+ if (meta != null && meta.brand === TestTreeNodeBrand.Expect) {
3258
+ const modifierNode = this.#getChainedNode(node, "type");
3259
+ if (!modifierNode) {
3260
+ return;
3262
3261
  }
3263
- };
3264
- this.#inputService = new InputService(onInput);
3265
- }
3266
- const onChangedFile = (filePath) => {
3267
- debounce.refresh();
3268
- let task = this.#watchedTestFiles.get(filePath);
3269
- if (task != null) {
3270
- this.#changedTestFiles.set(filePath, task);
3271
- }
3272
- else if (Select.isTestFile(filePath, this.#resolvedConfig)) {
3273
- task = new Task(filePath);
3274
- this.#changedTestFiles.set(filePath, task);
3275
- this.#watchedTestFiles.set(filePath, task);
3262
+ const notNode = this.#getChainedNode(modifierNode, "not");
3263
+ const matcherNameNode = this.#getChainedNode(notNode ?? modifierNode);
3264
+ if (!matcherNameNode) {
3265
+ return;
3266
+ }
3267
+ const matcherNode = this.#getMatcherNode(matcherNameNode);
3268
+ if (!matcherNode) {
3269
+ return;
3270
+ }
3271
+ const assertionNode = new AssertionNode(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, matcherNameNode, modifierNode, notNode);
3272
+ this.#abilityLayer.handleAssertion(assertionNode);
3273
+ this.#compiler.forEachChild(node, (node) => {
3274
+ this.#collectTestTreeNodes(node, assertionNode);
3275
+ });
3276
+ this.#onNode(assertionNode, parent);
3277
+ return;
3276
3278
  }
3277
- };
3278
- const onRemovedFile = (filePath) => {
3279
- this.#changedTestFiles.delete(filePath);
3280
- this.#watchedTestFiles.delete(filePath);
3281
- if (this.#watchedTestFiles.size === 0) {
3282
- debounce.cancel();
3283
- this.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereLeft(this.#resolvedConfig)));
3279
+ if (meta != null && meta.brand === TestTreeNodeBrand.When) {
3280
+ const actionNameNode = this.#getChainedNode(node);
3281
+ if (!actionNameNode) {
3282
+ return;
3283
+ }
3284
+ const actionNode = this.#getActionNode(actionNameNode);
3285
+ if (!actionNode) {
3286
+ return;
3287
+ }
3288
+ this.#compiler.forEachChild(actionNode, (node) => {
3289
+ if (this.#compiler.isCallExpression(node)) {
3290
+ const meta = this.#identifierLookup.resolveTestMemberMeta(node);
3291
+ if (meta != null && (meta.brand === TestTreeNodeBrand.Describe || meta.brand === TestTreeNodeBrand.Test)) {
3292
+ const text = CollectDiagnosticText.cannotBeNestedWithin(meta.identifier, "when");
3293
+ const origin = DiagnosticOrigin.fromNode(node);
3294
+ this.#onDiagnostics(Diagnostic.error(text, origin));
3295
+ }
3296
+ }
3297
+ });
3298
+ const whenNode = new WhenNode(this.#compiler, meta.brand, node, parent, meta.flags, actionNode, actionNameNode);
3299
+ this.#abilityLayer.handleWhen(whenNode);
3300
+ this.#onNode(whenNode, parent);
3301
+ return;
3284
3302
  }
3285
- };
3286
- this.#watchers.push(new Watcher(this.#resolvedConfig.rootPath, onChangedFile, onRemovedFile, { recursive: true }));
3287
- const onChangedConfigFile = () => {
3288
- onClose(CancellationReason.ConfigChange);
3289
- };
3290
- this.#watchers.push(new FileWatcher(this.#resolvedConfig.configFilePath, onChangedConfigFile));
3291
- for (const watcher of this.#watchers) {
3292
- watcher.watch();
3293
3303
  }
3294
- while (!cancellationToken.isCancellationRequested) {
3295
- const testFiles = await debounce.schedule();
3296
- if (testFiles.length > 0) {
3297
- yield testFiles;
3298
- }
3304
+ if (this.#compiler.isImportDeclaration(node)) {
3305
+ this.#identifierLookup.handleImportDeclaration(node);
3306
+ return;
3307
+ }
3308
+ this.#compiler.forEachChild(node, (node) => {
3309
+ this.#collectTestTreeNodes(node, parent);
3310
+ });
3311
+ }
3312
+ createTestTree(sourceFile, semanticDiagnostics = []) {
3313
+ const testTree = new TestTree(new Set(semanticDiagnostics), sourceFile);
3314
+ EventEmitter.dispatch(["collect:start", { tree: testTree }]);
3315
+ this.#testTree = testTree;
3316
+ this.#abilityLayer.open(sourceFile);
3317
+ this.#collectTestTreeNodes(sourceFile, testTree);
3318
+ this.#abilityLayer.close();
3319
+ this.#testTree = undefined;
3320
+ EventEmitter.dispatch(["collect:end", { tree: testTree }]);
3321
+ return testTree;
3322
+ }
3323
+ #getChainedNode({ parent }, name) {
3324
+ if (!this.#compiler.isPropertyAccessExpression(parent)) {
3325
+ return;
3326
+ }
3327
+ if (name != null && name !== parent.name.getText()) {
3328
+ return;
3329
+ }
3330
+ return parent;
3331
+ }
3332
+ #getMatcherNode(node) {
3333
+ if (this.#compiler.isCallExpression(node.parent)) {
3334
+ return node.parent;
3335
+ }
3336
+ if (this.#compiler.isDecorator(node.parent)) {
3337
+ return node.parent;
3338
+ }
3339
+ if (this.#compiler.isParenthesizedExpression(node.parent)) {
3340
+ return this.#getMatcherNode(node.parent);
3341
+ }
3342
+ return;
3343
+ }
3344
+ #getActionNode(node) {
3345
+ if (this.#compiler.isCallExpression(node.parent)) {
3346
+ return node.parent;
3347
+ }
3348
+ return;
3349
+ }
3350
+ #onDiagnostics(diagnostic) {
3351
+ EventEmitter.dispatch(["collect:error", { diagnostics: [diagnostic] }]);
3352
+ }
3353
+ #onNode(node, parent) {
3354
+ parent.children.push(node);
3355
+ if (node.flags & TestTreeNodeFlags.Only) {
3356
+ this.#testTree.hasOnly = true;
3299
3357
  }
3358
+ EventEmitter.dispatch(["collect:node", { node }]);
3300
3359
  }
3301
3360
  }
3302
3361
 
@@ -3305,6 +3364,7 @@ class ProjectService {
3305
3364
  #lastSeenProject = "";
3306
3365
  #resolvedConfig;
3307
3366
  #seenPrograms = new WeakSet();
3367
+ #seenTestFiles = new Set();
3308
3368
  #service;
3309
3369
  constructor(compiler, resolvedConfig) {
3310
3370
  this.#compiler = compiler;
@@ -3342,15 +3402,6 @@ class ProjectService {
3342
3402
  useInferredProjectPerProjectRoot: true,
3343
3403
  useSingleInferredProject: false,
3344
3404
  });
3345
- switch (this.#resolvedConfig.tsconfig) {
3346
- case "findup":
3347
- break;
3348
- case "ignore":
3349
- this.#service.getConfigFileNameForFile = () => undefined;
3350
- break;
3351
- default:
3352
- this.#service.getConfigFileNameForFile = () => this.#resolvedConfig.tsconfig;
3353
- }
3354
3405
  this.#service.setCompilerOptionsForInferredProjects(this.#getDefaultCompilerOptions());
3355
3406
  }
3356
3407
  closeFile(filePath) {
@@ -3385,7 +3436,23 @@ class ProjectService {
3385
3436
  const project = this.getDefaultProject(filePath);
3386
3437
  return project?.getLanguageService(true);
3387
3438
  }
3439
+ #isFileIncluded(filePath) {
3440
+ const configSourceFile = this.#compiler.readJsonConfigFile(this.#resolvedConfig.tsconfig, this.#compiler.sys.readFile);
3441
+ const { fileNames } = this.#compiler.parseJsonSourceFileConfigFileContent(configSourceFile, this.#compiler.sys, Path.dirname(this.#resolvedConfig.tsconfig), undefined, this.#resolvedConfig.tsconfig);
3442
+ return fileNames.includes(filePath);
3443
+ }
3388
3444
  openFile(filePath, sourceText, projectRootPath) {
3445
+ switch (this.#resolvedConfig.tsconfig) {
3446
+ case "findup":
3447
+ break;
3448
+ case "ignore":
3449
+ this.#service.getConfigFileNameForFile = () => undefined;
3450
+ break;
3451
+ default:
3452
+ this.#service.getConfigFileNameForFile = this.#isFileIncluded(filePath)
3453
+ ? () => this.#resolvedConfig.tsconfig
3454
+ : () => undefined;
3455
+ }
3389
3456
  const { configFileErrors, configFileName } = this.#service.openClientFile(filePath, sourceText, undefined, projectRootPath);
3390
3457
  if (configFileName !== this.#lastSeenProject) {
3391
3458
  this.#lastSeenProject = configFileName;
@@ -3400,29 +3467,29 @@ class ProjectService {
3400
3467
  { diagnostics: Diagnostic.fromDiagnostics(configFileErrors) },
3401
3468
  ]);
3402
3469
  }
3403
- if (this.#resolvedConfig.checkSourceFiles) {
3470
+ if (this.#resolvedConfig.checkSourceFiles && !this.#seenTestFiles.has(filePath)) {
3471
+ this.#seenTestFiles.add(filePath);
3404
3472
  const languageService = this.getLanguageService(filePath);
3405
3473
  const program = languageService?.getProgram();
3406
3474
  if (!program || this.#seenPrograms.has(program)) {
3407
3475
  return;
3408
3476
  }
3409
3477
  this.#seenPrograms.add(program);
3410
- const filesToCheck = [];
3411
- for (const sourceFile of program.getSourceFiles()) {
3478
+ const sourceFilesToCheck = program.getSourceFiles().filter((sourceFile) => {
3412
3479
  if (program.isSourceFileFromExternalLibrary(sourceFile) || program.isSourceFileDefaultLibrary(sourceFile)) {
3413
- continue;
3480
+ return false;
3414
3481
  }
3415
- if (!Select.isTestFile(sourceFile.fileName, { ...this.#resolvedConfig, pathMatch: [] })) {
3416
- filesToCheck.push(sourceFile);
3482
+ if (Select.isTestFile(sourceFile.fileName, { ...this.#resolvedConfig, pathMatch: [] })) {
3483
+ return false;
3417
3484
  }
3418
- }
3485
+ return true;
3486
+ });
3419
3487
  const diagnostics = [];
3420
- for (const sourceFile of filesToCheck) {
3488
+ for (const sourceFile of sourceFilesToCheck) {
3421
3489
  diagnostics.push(...program.getSyntacticDiagnostics(sourceFile), ...program.getSemanticDiagnostics(sourceFile));
3422
3490
  }
3423
3491
  if (diagnostics.length > 0) {
3424
3492
  EventEmitter.dispatch(["project:error", { diagnostics: Diagnostic.fromDiagnostics(diagnostics) }]);
3425
- return;
3426
3493
  }
3427
3494
  }
3428
3495
  }
@@ -3437,10 +3504,8 @@ var RunMode;
3437
3504
  RunMode[RunMode["Todo"] = 8] = "Todo";
3438
3505
  })(RunMode || (RunMode = {}));
3439
3506
 
3440
- class Format {
3441
- static capitalize(text) {
3442
- return text.replace(/^./, text.charAt(0).toUpperCase());
3443
- }
3507
+ function capitalize(text) {
3508
+ return text.replace(/^./, text.charAt(0).toUpperCase());
3444
3509
  }
3445
3510
 
3446
3511
  class ExpectDiagnosticText {
@@ -3456,23 +3521,23 @@ class ExpectDiagnosticText {
3456
3521
  static argumentMustBeProvided(argumentNameText) {
3457
3522
  return `An argument for '${argumentNameText}' must be provided.`;
3458
3523
  }
3459
- static isCallable(isTypeNode, targetText) {
3460
- return `${isTypeNode ? "Type" : "Expression"} is callable ${targetText}.`;
3524
+ static isCallable(isExpression, targetText) {
3525
+ return `${isExpression ? "Expression" : "Type"} is callable ${targetText}.`;
3461
3526
  }
3462
- static isNotCallable(isTypeNode, targetText) {
3463
- return `${isTypeNode ? "Type" : "Expression"} is not callable ${targetText}.`;
3527
+ static isNotCallable(isExpression, targetText) {
3528
+ return `${isExpression ? "Expression" : "Type"} is not callable ${targetText}.`;
3464
3529
  }
3465
- static isConstructable(isTypeNode, targetText) {
3466
- return `${isTypeNode ? "Type" : "Expression"} is constructable ${targetText}.`;
3530
+ static isConstructable(isExpression, targetText) {
3531
+ return `${isExpression ? "Expression" : "Type"} is constructable ${targetText}.`;
3467
3532
  }
3468
- static isNotConstructable(isTypeNode, targetText) {
3469
- return `${isTypeNode ? "Type" : "Expression"} is not constructable ${targetText}.`;
3533
+ static isNotConstructable(isExpression, targetText) {
3534
+ return `${isExpression ? "Expression" : "Type"} is not constructable ${targetText}.`;
3470
3535
  }
3471
- static acceptsProps(isTypeNode) {
3472
- return `${isTypeNode ? "Component type" : "Component"} accepts props of the given type.`;
3536
+ static acceptsProps(isExpression) {
3537
+ return `${isExpression ? "Component" : "Component type"} accepts props of the given type.`;
3473
3538
  }
3474
- static doesNotAcceptProps(isTypeNode) {
3475
- return `${isTypeNode ? "Component type" : "Component"} does not accept props of the given type.`;
3539
+ static doesNotAcceptProps(isExpression) {
3540
+ return `${isExpression ? "Component" : "Component type"} does not accept props of the given type.`;
3476
3541
  }
3477
3542
  static canBeApplied(targetText) {
3478
3543
  return `The decorator function can be applied${targetText}.`;
@@ -3504,21 +3569,21 @@ class ExpectDiagnosticText {
3504
3569
  static typeArgumentMustBe(argumentNameText, expectedText) {
3505
3570
  return `A type argument for '${argumentNameText}' must be ${expectedText}.`;
3506
3571
  }
3507
- static raisedError(isTypeNode, count, targetCount) {
3572
+ static raisedError(isExpression, count, targetCount) {
3508
3573
  let countText = "a";
3509
3574
  if (count > 1 || targetCount > 1) {
3510
3575
  countText = count > targetCount ? `${count}` : `only ${count}`;
3511
3576
  }
3512
- return `${isTypeNode ? "Type" : "Expression type"} raised ${countText} type error${count === 1 ? "" : "s"}.`;
3577
+ return `${isExpression ? "Expression" : "Type"} raised ${countText} type error${count === 1 ? "" : "s"}.`;
3513
3578
  }
3514
- static didNotRaiseError(isTypeNode) {
3515
- return `${isTypeNode ? "Type" : "Expression type"} did not raise a type error.`;
3579
+ static didNotRaiseError(isExpression) {
3580
+ return `${isExpression ? "Expression" : "Type"} did not raise a type error.`;
3516
3581
  }
3517
- static raisedMatchingError(isTypeNode) {
3518
- return `${isTypeNode ? "Type" : "Expression type"} raised a matching type error.`;
3582
+ static raisedMatchingError(isExpression) {
3583
+ return `${isExpression ? "Expression" : "Type"} raised a matching type error.`;
3519
3584
  }
3520
- static didNotRaiseMatchingError(isTypeNode) {
3521
- return `${isTypeNode ? "Type" : "Expression type"} did not raise a matching type error.`;
3585
+ static didNotRaiseMatchingError(isExpression) {
3586
+ return `${isExpression ? "Expression" : "Type"} did not raise a matching type error.`;
3522
3587
  }
3523
3588
  static isAssignableTo(sourceTypeText, targetTypeText) {
3524
3589
  return `Type '${sourceTypeText}' is assignable to type '${targetTypeText}'.`;
@@ -3548,7 +3613,7 @@ class ExpectDiagnosticText {
3548
3613
  return `Types of property '${propertyNameText}' are not compatible.`;
3549
3614
  }
3550
3615
  static typeWasRejected(typeText) {
3551
- const optionNameText = `reject${Format.capitalize(typeText)}Type`;
3616
+ const optionNameText = `reject${capitalize(typeText)}Type`;
3552
3617
  return [
3553
3618
  `The '${typeText}' type was rejected because the '${optionNameText}' option is enabled.`,
3554
3619
  `If this check is necessary, pass '${typeText}' as the type argument explicitly.`,
@@ -3670,12 +3735,13 @@ class ToAcceptProps {
3670
3735
  this.#typeChecker = typeChecker;
3671
3736
  }
3672
3737
  #explain(matchWorker, sourceNode, targetNode) {
3738
+ const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
3673
3739
  const signatures = matchWorker.getSignatures(sourceNode);
3674
3740
  return signatures.reduce((accumulator, signature, index) => {
3675
3741
  let diagnostic;
3676
3742
  const introText = matchWorker.assertion.isNot
3677
- ? ExpectDiagnosticText.acceptsProps(this.#compiler.isTypeNode(sourceNode))
3678
- : ExpectDiagnosticText.doesNotAcceptProps(this.#compiler.isTypeNode(sourceNode));
3743
+ ? ExpectDiagnosticText.acceptsProps(isExpression)
3744
+ : ExpectDiagnosticText.doesNotAcceptProps(isExpression);
3679
3745
  const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
3680
3746
  if (signatures.length > 1) {
3681
3747
  const signatureText = this.#typeChecker.signatureToString(signature, sourceNode);
@@ -3815,18 +3881,18 @@ class ToAcceptProps {
3815
3881
  const signatures = matchWorker.getSignatures(sourceNode);
3816
3882
  if (signatures.length === 0) {
3817
3883
  const expectedText = "of a function or class type";
3818
- const text = this.#compiler.isTypeNode(sourceNode)
3819
- ? ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText)
3820
- : ExpectDiagnosticText.argumentMustBe("source", expectedText);
3884
+ const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
3885
+ ? ExpectDiagnosticText.argumentMustBe("source", expectedText)
3886
+ : ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText);
3821
3887
  const origin = DiagnosticOrigin.fromNode(sourceNode);
3822
3888
  diagnostics.push(Diagnostic.error(text, origin));
3823
3889
  }
3824
3890
  const targetType = matchWorker.getType(targetNode);
3825
3891
  if (!(targetType.flags & this.#compiler.TypeFlags.Object)) {
3826
3892
  const expectedText = "of an object type";
3827
- const text = this.#compiler.isTypeNode(targetNode)
3828
- ? ExpectDiagnosticText.typeArgumentMustBe("Target", expectedText)
3829
- : ExpectDiagnosticText.argumentMustBe("target", expectedText);
3893
+ const text = nodeBelongsToArgumentList(this.#compiler, targetNode)
3894
+ ? ExpectDiagnosticText.argumentMustBe("target", expectedText)
3895
+ : ExpectDiagnosticText.typeArgumentMustBe("Target", expectedText);
3830
3896
  const origin = DiagnosticOrigin.fromNode(targetNode);
3831
3897
  diagnostics.push(Diagnostic.error(text, origin));
3832
3898
  }
@@ -3919,9 +3985,9 @@ class ToBeApplicable {
3919
3985
  const type = matchWorker.getType(sourceNode);
3920
3986
  if (type.getCallSignatures().length === 0) {
3921
3987
  const expectedText = "of a function type";
3922
- const text = this.#compiler.isTypeNode(sourceNode)
3923
- ? ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText)
3924
- : ExpectDiagnosticText.argumentMustBe("source", expectedText);
3988
+ const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
3989
+ ? ExpectDiagnosticText.argumentMustBe("source", expectedText)
3990
+ : ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText);
3925
3991
  const origin = DiagnosticOrigin.fromNode(sourceNode);
3926
3992
  onDiagnostics([Diagnostic.error(text, origin)]);
3927
3993
  return;
@@ -3970,12 +4036,15 @@ class ToBeCallableWith {
3970
4036
  return `with the given argument${nodes.length === 1 ? "" : "s"}`;
3971
4037
  }
3972
4038
  #explain(matchWorker, sourceNode, targetNodes) {
3973
- const isTypeNode = this.#compiler.isTypeNode(sourceNode);
4039
+ const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
3974
4040
  const targetText = this.#resolveTargetText(targetNodes);
3975
4041
  const diagnostics = [];
3976
4042
  if (matchWorker.assertion.abilityDiagnostics) {
3977
4043
  for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
3978
- const text = [ExpectDiagnosticText.isNotCallable(isTypeNode, targetText), getDiagnosticMessageText(diagnostic)];
4044
+ const text = [
4045
+ ExpectDiagnosticText.isNotCallable(isExpression, targetText),
4046
+ getDiagnosticMessageText(diagnostic),
4047
+ ];
3979
4048
  let origin;
3980
4049
  if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, targetNodes)) {
3981
4050
  origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), sourceNode.getSourceFile());
@@ -3995,7 +4064,7 @@ class ToBeCallableWith {
3995
4064
  }
3996
4065
  else {
3997
4066
  const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
3998
- diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isCallable(isTypeNode, targetText), origin));
4067
+ diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isCallable(isExpression, targetText), origin));
3999
4068
  }
4000
4069
  return diagnostics;
4001
4070
  }
@@ -4014,11 +4083,11 @@ class ToBeCallableWith {
4014
4083
  }
4015
4084
  if (!type || type.getCallSignatures().length === 0) {
4016
4085
  const text = [];
4017
- if (this.#compiler.isTypeNode(sourceNode)) {
4018
- text.push(ExpectDiagnosticText.typeArgumentMustBe("Source", "a callable type"));
4086
+ if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
4087
+ text.push(ExpectDiagnosticText.argumentMustBe("source", "a callable expression"));
4019
4088
  }
4020
4089
  else {
4021
- text.push(ExpectDiagnosticText.argumentMustBe("source", "a callable expression"));
4090
+ text.push(ExpectDiagnosticText.typeArgumentMustBe("Source", "a callable type"));
4022
4091
  }
4023
4092
  if (type != null && type.getConstructSignatures().length > 0) {
4024
4093
  text.push(ExpectDiagnosticText.didYouMeanToUse("the '.toBeConstructableWith()' matcher"));
@@ -4049,13 +4118,13 @@ class ToBeConstructableWith {
4049
4118
  return `with the given argument${nodes.length === 1 ? "" : "s"}`;
4050
4119
  }
4051
4120
  #explain(matchWorker, sourceNode, targetNodes) {
4052
- const isTypeNode = this.#compiler.isTypeNode(sourceNode);
4121
+ const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
4053
4122
  const targetText = this.#resolveTargetText(targetNodes);
4054
4123
  const diagnostics = [];
4055
4124
  if (matchWorker.assertion.abilityDiagnostics) {
4056
4125
  for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
4057
4126
  const text = [
4058
- ExpectDiagnosticText.isNotConstructable(isTypeNode, targetText),
4127
+ ExpectDiagnosticText.isNotConstructable(isExpression, targetText),
4059
4128
  getDiagnosticMessageText(diagnostic),
4060
4129
  ];
4061
4130
  let origin;
@@ -4077,7 +4146,7 @@ class ToBeConstructableWith {
4077
4146
  }
4078
4147
  else {
4079
4148
  const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
4080
- diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isConstructable(isTypeNode, targetText), origin));
4149
+ diagnostics.push(Diagnostic.error(ExpectDiagnosticText.isConstructable(isExpression, targetText), origin));
4081
4150
  }
4082
4151
  return diagnostics;
4083
4152
  }
@@ -4093,11 +4162,11 @@ class ToBeConstructableWith {
4093
4162
  }
4094
4163
  if (!type || type.getConstructSignatures().length === 0) {
4095
4164
  const text = [];
4096
- if (this.#compiler.isTypeNode(sourceNode)) {
4097
- text.push(ExpectDiagnosticText.typeArgumentMustBe("Source", "a constructable type"));
4165
+ if (nodeBelongsToArgumentList(this.#compiler, sourceNode)) {
4166
+ text.push(ExpectDiagnosticText.argumentMustBe("source", "a constructable expression"));
4098
4167
  }
4099
4168
  else {
4100
- text.push(ExpectDiagnosticText.argumentMustBe("source", "a constructable expression"));
4169
+ text.push(ExpectDiagnosticText.typeArgumentMustBe("Source", "a constructable type"));
4101
4170
  }
4102
4171
  if (type != null && type.getCallSignatures().length > 0) {
4103
4172
  text.push(ExpectDiagnosticText.didYouMeanToUse("the '.toBeCallableWith()' matcher"));
@@ -4139,9 +4208,9 @@ class ToHaveProperty {
4139
4208
  if (sourceType.flags & (this.#compiler.TypeFlags.Any | this.#compiler.TypeFlags.Never) ||
4140
4209
  !matchWorker.extendsObjectType(sourceType)) {
4141
4210
  const expectedText = "of an object type";
4142
- const text = this.#compiler.isTypeNode(sourceNode)
4143
- ? ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText)
4144
- : ExpectDiagnosticText.argumentMustBe("source", expectedText);
4211
+ const text = nodeBelongsToArgumentList(this.#compiler, sourceNode)
4212
+ ? ExpectDiagnosticText.argumentMustBe("source", expectedText)
4213
+ : ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText);
4145
4214
  const origin = DiagnosticOrigin.fromNode(sourceNode);
4146
4215
  diagnostics.push(Diagnostic.error(text, origin));
4147
4216
  }
@@ -4178,15 +4247,15 @@ class ToRaiseError {
4178
4247
  this.#compiler = compiler;
4179
4248
  }
4180
4249
  #explain(matchWorker, sourceNode, targetNodes) {
4181
- const isTypeNode = this.#compiler.isTypeNode(sourceNode);
4250
+ const isExpression = nodeBelongsToArgumentList(this.#compiler, sourceNode);
4182
4251
  const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
4183
4252
  if (matchWorker.assertion.diagnostics.size === 0) {
4184
- const text = ExpectDiagnosticText.didNotRaiseError(isTypeNode);
4253
+ const text = ExpectDiagnosticText.didNotRaiseError(isExpression);
4185
4254
  return [Diagnostic.error(text, origin)];
4186
4255
  }
4187
4256
  if (matchWorker.assertion.diagnostics.size !== targetNodes.length) {
4188
4257
  const count = matchWorker.assertion.diagnostics.size;
4189
- const text = ExpectDiagnosticText.raisedError(isTypeNode, count, targetNodes.length);
4258
+ const text = ExpectDiagnosticText.raisedError(isExpression, count, targetNodes.length);
4190
4259
  const related = [
4191
4260
  Diagnostic.error(ExpectDiagnosticText.raisedTypeError(count)),
4192
4261
  ...Diagnostic.fromDiagnostics([...matchWorker.assertion.diagnostics]),
@@ -4198,8 +4267,8 @@ class ToRaiseError {
4198
4267
  const isMatch = this.#matchExpectedError(diagnostic, targetNode);
4199
4268
  if (matchWorker.assertion.isNot ? isMatch : !isMatch) {
4200
4269
  const text = matchWorker.assertion.isNot
4201
- ? ExpectDiagnosticText.raisedMatchingError(isTypeNode)
4202
- : ExpectDiagnosticText.didNotRaiseMatchingError(isTypeNode);
4270
+ ? ExpectDiagnosticText.raisedMatchingError(isExpression)
4271
+ : ExpectDiagnosticText.didNotRaiseMatchingError(isExpression);
4203
4272
  const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
4204
4273
  const related = [
4205
4274
  Diagnostic.error(ExpectDiagnosticText.raisedTypeError()),
@@ -4348,7 +4417,7 @@ class ExpectService {
4348
4417
  }
4349
4418
  #rejectsTypeArguments(matchWorker, onDiagnostics) {
4350
4419
  for (const rejectedType of this.#rejectTypes) {
4351
- const allowedKeyword = this.#compiler.SyntaxKind[`${Format.capitalize(rejectedType)}Keyword`];
4420
+ const allowedKeyword = this.#compiler.SyntaxKind[`${capitalize(rejectedType)}Keyword`];
4352
4421
  if (matchWorker.assertion.source[0]?.kind === allowedKeyword ||
4353
4422
  matchWorker.assertion.target?.[0]?.kind === allowedKeyword) {
4354
4423
  continue;
@@ -4358,11 +4427,11 @@ class ExpectService {
4358
4427
  if (!argumentNode) {
4359
4428
  continue;
4360
4429
  }
4361
- if (matchWorker.getType(argumentNode).flags & this.#compiler.TypeFlags[Format.capitalize(rejectedType)]) {
4430
+ if (matchWorker.getType(argumentNode).flags & this.#compiler.TypeFlags[capitalize(rejectedType)]) {
4362
4431
  const text = [
4363
- this.#compiler.isTypeNode(argumentNode)
4364
- ? ExpectDiagnosticText.typeArgumentCannotBeOfType(Format.capitalize(argumentName), rejectedType)
4365
- : ExpectDiagnosticText.argumentCannotBeOfType(argumentName, rejectedType),
4432
+ nodeBelongsToArgumentList(this.#compiler, argumentNode)
4433
+ ? ExpectDiagnosticText.argumentCannotBeOfType(argumentName, rejectedType)
4434
+ : ExpectDiagnosticText.typeArgumentCannotBeOfType(capitalize(argumentName), rejectedType),
4366
4435
  ...ExpectDiagnosticText.typeWasRejected(rejectedType),
4367
4436
  ];
4368
4437
  const origin = DiagnosticOrigin.fromNode(argumentNode);
@@ -4433,6 +4502,9 @@ class TestTreeWalker {
4433
4502
  case TestTreeNodeBrand.Expect:
4434
4503
  this.#visitAssertion(testNode, runMode, parentResult);
4435
4504
  break;
4505
+ case TestTreeNodeBrand.When:
4506
+ this.#visitWhen(testNode, runMode, parentResult);
4507
+ break;
4436
4508
  }
4437
4509
  }
4438
4510
  }
@@ -4525,11 +4597,37 @@ class TestTreeWalker {
4525
4597
  EventEmitter.dispatch(["test:pass", { result: testResult }]);
4526
4598
  }
4527
4599
  }
4600
+ #visitWhen(when, runMode, parentResult) {
4601
+ if (when.abilityDiagnostics != null && when.abilityDiagnostics.size > 0) {
4602
+ const diagnostics = [];
4603
+ for (const diagnostic of when.abilityDiagnostics) {
4604
+ if (isDiagnosticWithLocation(diagnostic)) {
4605
+ const text = getDiagnosticMessageText(diagnostic);
4606
+ let origin;
4607
+ if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, when.node)) {
4608
+ origin = DiagnosticOrigin.fromNodes(when.target);
4609
+ }
4610
+ else {
4611
+ origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), when.node.getSourceFile());
4612
+ }
4613
+ let related;
4614
+ if (diagnostic.relatedInformation != null) {
4615
+ related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
4616
+ }
4617
+ diagnostics.push(Diagnostic.error(text, origin).add({ related }));
4618
+ }
4619
+ }
4620
+ EventEmitter.dispatch(["task:error", { diagnostics, result: this.#taskResult }]);
4621
+ return;
4622
+ }
4623
+ this.visit(when.children, runMode, parentResult);
4624
+ }
4528
4625
  }
4529
4626
 
4530
4627
  class TaskRunner {
4531
4628
  #collectService;
4532
4629
  #compiler;
4630
+ #eventEmitter = new EventEmitter();
4533
4631
  #resolvedConfig;
4534
4632
  #projectService;
4535
4633
  constructor(compiler, resolvedConfig) {
@@ -4538,55 +4636,71 @@ class TaskRunner {
4538
4636
  this.#projectService = new ProjectService(compiler, this.#resolvedConfig);
4539
4637
  this.#collectService = new CollectService(compiler, this.#projectService, this.#resolvedConfig);
4540
4638
  }
4541
- run(task, cancellationToken) {
4542
- if (cancellationToken?.isCancellationRequested) {
4639
+ #onDiagnostics(diagnostics, result) {
4640
+ EventEmitter.dispatch(["task:error", { diagnostics, result }]);
4641
+ }
4642
+ async run(task, cancellationToken) {
4643
+ if (cancellationToken.isCancellationRequested) {
4543
4644
  return;
4544
4645
  }
4545
4646
  this.#projectService.openFile(task.filePath, undefined, this.#resolvedConfig.rootPath);
4546
4647
  const taskResult = new TaskResult(task);
4547
4648
  EventEmitter.dispatch(["task:start", { result: taskResult }]);
4548
- this.#run(task, taskResult, cancellationToken);
4649
+ await this.#run(task, taskResult, cancellationToken);
4549
4650
  EventEmitter.dispatch(["task:end", { result: taskResult }]);
4550
4651
  this.#projectService.closeFile(task.filePath);
4551
4652
  }
4552
- #run(task, taskResult, cancellationToken) {
4653
+ async #run(task, taskResult, cancellationToken) {
4553
4654
  if (!existsSync(task.filePath)) {
4554
- EventEmitter.dispatch([
4555
- "task:error",
4556
- { diagnostics: [Diagnostic.error(`Test file '${task.filePath}' does not exist.`)], result: taskResult },
4557
- ]);
4558
- return;
4559
- }
4560
- const languageService = this.#projectService.getLanguageService(task.filePath);
4561
- if (!languageService) {
4655
+ this.#onDiagnostics([Diagnostic.error(`Test file '${task.filePath}' does not exist.`)], taskResult);
4562
4656
  return;
4563
4657
  }
4564
- const syntacticDiagnostics = languageService.getSyntacticDiagnostics(task.filePath);
4565
- if (syntacticDiagnostics.length > 0) {
4566
- EventEmitter.dispatch([
4567
- "task:error",
4568
- { diagnostics: Diagnostic.fromDiagnostics(syntacticDiagnostics), result: taskResult },
4569
- ]);
4658
+ let languageService = this.#projectService.getLanguageService(task.filePath);
4659
+ const syntacticDiagnostics = languageService?.getSyntacticDiagnostics(task.filePath);
4660
+ if (syntacticDiagnostics != null && syntacticDiagnostics.length > 0) {
4661
+ this.#onDiagnostics(Diagnostic.fromDiagnostics(syntacticDiagnostics), taskResult);
4570
4662
  return;
4571
4663
  }
4572
- const semanticDiagnostics = languageService.getSemanticDiagnostics(task.filePath);
4573
- const program = languageService.getProgram();
4574
- if (!program) {
4575
- return;
4664
+ let semanticDiagnostics = languageService?.getSemanticDiagnostics(task.filePath);
4665
+ let program = languageService?.getProgram();
4666
+ let sourceFile = program?.getSourceFile(task.filePath);
4667
+ if (sourceFile?.text.startsWith("// @tstyche-template")) {
4668
+ if (semanticDiagnostics != null && semanticDiagnostics.length > 0) {
4669
+ this.#onDiagnostics(Diagnostic.fromDiagnostics(semanticDiagnostics), taskResult);
4670
+ return;
4671
+ }
4672
+ const moduleSpecifier = pathToFileURL(task.filePath).toString();
4673
+ const testText = (await import(moduleSpecifier))?.default;
4674
+ if (typeof testText !== "string") {
4675
+ this.#onDiagnostics([Diagnostic.error("A template test file must export a string.")], taskResult);
4676
+ return;
4677
+ }
4678
+ this.#projectService.openFile(task.filePath, testText, this.#resolvedConfig.rootPath);
4679
+ languageService = this.#projectService.getLanguageService(task.filePath);
4680
+ const syntacticDiagnostics = languageService?.getSyntacticDiagnostics(task.filePath);
4681
+ if (syntacticDiagnostics != null && syntacticDiagnostics.length > 0) {
4682
+ this.#onDiagnostics(Diagnostic.fromDiagnostics(syntacticDiagnostics), taskResult);
4683
+ return;
4684
+ }
4685
+ semanticDiagnostics = languageService?.getSemanticDiagnostics(task.filePath);
4686
+ program = languageService?.getProgram();
4687
+ sourceFile = program?.getSourceFile(task.filePath);
4576
4688
  }
4577
- const sourceFile = program.getSourceFile(task.filePath);
4578
4689
  if (!sourceFile) {
4579
4690
  return;
4580
4691
  }
4692
+ const cancellationHandler = new CancellationHandler(cancellationToken, CancellationReason.CollectError);
4693
+ this.#eventEmitter.addHandler(cancellationHandler);
4581
4694
  const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics);
4695
+ this.#eventEmitter.removeHandler(cancellationHandler);
4696
+ if (cancellationToken.isCancellationRequested) {
4697
+ return;
4698
+ }
4582
4699
  if (testTree.diagnostics.size > 0) {
4583
- EventEmitter.dispatch([
4584
- "task:error",
4585
- { diagnostics: Diagnostic.fromDiagnostics([...testTree.diagnostics]), result: taskResult },
4586
- ]);
4700
+ this.#onDiagnostics(Diagnostic.fromDiagnostics([...testTree.diagnostics]), taskResult);
4587
4701
  return;
4588
4702
  }
4589
- const typeChecker = program.getTypeChecker();
4703
+ const typeChecker = program?.getTypeChecker();
4590
4704
  const testTreeWalker = new TestTreeWalker(this.#compiler, typeChecker, this.#resolvedConfig, {
4591
4705
  cancellationToken,
4592
4706
  taskResult,
@@ -4600,15 +4714,13 @@ class TaskRunner {
4600
4714
  class Runner {
4601
4715
  #eventEmitter = new EventEmitter();
4602
4716
  #resolvedConfig;
4603
- static version = "4.0.0-beta.6";
4717
+ static version = "4.0.0-beta.8";
4604
4718
  constructor(resolvedConfig) {
4605
4719
  this.#resolvedConfig = resolvedConfig;
4606
4720
  }
4607
4721
  #addHandlers(cancellationToken) {
4608
4722
  const resultHandler = new ResultHandler();
4609
4723
  this.#eventEmitter.addHandler(resultHandler);
4610
- const testTreeHandler = new TestTreeHandler();
4611
- this.#eventEmitter.addHandler(testTreeHandler);
4612
4724
  if (this.#resolvedConfig.failFast) {
4613
4725
  const cancellationHandler = new CancellationHandler(cancellationToken, CancellationReason.FailFast);
4614
4726
  this.#eventEmitter.addHandler(cancellationHandler);
@@ -4651,7 +4763,7 @@ class Runner {
4651
4763
  this.#eventEmitter.removeHandlers();
4652
4764
  }
4653
4765
  async #run(tasks, cancellationToken) {
4654
- const result = new Result(this.#resolvedConfig, tasks);
4766
+ const result = new Result(tasks);
4655
4767
  EventEmitter.dispatch(["run:start", { result }]);
4656
4768
  for (const target of this.#resolvedConfig.target) {
4657
4769
  const targetResult = new TargetResult(target, tasks);
@@ -4660,7 +4772,7 @@ class Runner {
4660
4772
  if (compiler) {
4661
4773
  const taskRunner = new TaskRunner(compiler, this.#resolvedConfig);
4662
4774
  for (const task of tasks) {
4663
- taskRunner.run(task, cancellationToken);
4775
+ await taskRunner.run(task, cancellationToken);
4664
4776
  }
4665
4777
  }
4666
4778
  EventEmitter.dispatch(["target:end", { result: targetResult }]);
@@ -4802,4 +4914,4 @@ class Cli {
4802
4914
  }
4803
4915
  }
4804
4916
 
4805
- export { AssertionNode, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileWatcher, InputService, Line, ListReporter, OptionBrand, OptionGroup, Options, OutputService, Path, PluginService, ProjectResult, ProjectService, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, Runner, Scribbler, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, TargetResult, Task, TaskResult, TestResult, TestTree, TestTreeHandler, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, addsPackageText, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, environmentOptions, fileViewText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
4917
+ export { AssertionNode, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileWatcher, InputService, Line, ListReporter, OptionBrand, OptionGroup, Options, OutputService, Path, PluginService, ProjectResult, ProjectService, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, Runner, Scribbler, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, TargetResult, Task, TaskResult, TestResult, TestTree, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, WhenNode, addsPackageText, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, environmentOptions, fileViewText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, nodeBelongsToArgumentList, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };