tstyche 4.0.0-beta.1 → 4.0.0-beta.10

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