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/README.md +8 -5
- package/build/index.cjs +1 -0
- package/build/index.d.cts +36 -4
- package/build/index.d.ts +36 -4
- package/build/index.js +1 -1
- package/build/tstyche.d.ts +95 -75
- package/build/tstyche.js +1190 -851
- package/package.json +12 -10
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 =
|
|
142
|
-
? diagnostic.messageText
|
|
143
|
-
: Diagnostic.toMessageText(diagnostic.messageText);
|
|
168
|
+
const text = getDiagnosticMessageText(diagnostic);
|
|
144
169
|
return new Diagnostic(text, DiagnosticCategory.Error, origin).add({ code, related });
|
|
145
170
|
});
|
|
146
171
|
}
|
|
147
|
-
static toMessageText(chain) {
|
|
148
|
-
const result = [chain.messageText];
|
|
149
|
-
if (chain.next != null) {
|
|
150
|
-
for (const nextChain of chain.next) {
|
|
151
|
-
result.push(...Diagnostic.toMessageText(nextChain));
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return result;
|
|
155
|
-
}
|
|
156
172
|
static warning(text, origin) {
|
|
157
173
|
return new Diagnostic(text, DiagnosticCategory.Warning, origin);
|
|
158
174
|
}
|
|
@@ -556,7 +572,6 @@ class ManifestService {
|
|
|
556
572
|
#manifestFilePath;
|
|
557
573
|
#npmRegistry;
|
|
558
574
|
#storePath;
|
|
559
|
-
#supportedVersionRegex = /^(5)\.\d\.\d$/;
|
|
560
575
|
constructor(storePath, npmRegistry, fetcher) {
|
|
561
576
|
this.#storePath = storePath;
|
|
562
577
|
this.#npmRegistry = npmRegistry;
|
|
@@ -586,7 +601,7 @@ class ManifestService {
|
|
|
586
601
|
const versions = [];
|
|
587
602
|
const packageMetadata = (await response.json());
|
|
588
603
|
for (const [tag, meta] of Object.entries(packageMetadata.versions)) {
|
|
589
|
-
if (
|
|
604
|
+
if (!tag.includes("-") && Version.isSatisfiedWith(tag, "4.7.2")) {
|
|
590
605
|
versions.push(tag);
|
|
591
606
|
packages[tag] = { integrity: meta.dist.integrity, tarball: meta.dist.tarball };
|
|
592
607
|
}
|
|
@@ -685,6 +700,9 @@ class PackageService {
|
|
|
685
700
|
if (response?.body != null) {
|
|
686
701
|
const targetPath = `${packagePath}-${Math.random().toString(32).slice(2)}`;
|
|
687
702
|
for await (const file of TarReader.extract(response.body)) {
|
|
703
|
+
if (!file.name.startsWith("package/")) {
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
688
706
|
const filePath = Path.join(targetPath, file.name.replace("package/", ""));
|
|
689
707
|
const directoryPath = Path.dirname(filePath);
|
|
690
708
|
if (!existsSync(directoryPath)) {
|
|
@@ -792,11 +810,7 @@ class Store {
|
|
|
792
810
|
modulePath = Path.resolve(modulePath, "../tsserverlibrary.js");
|
|
793
811
|
}
|
|
794
812
|
const sourceText = await fs.readFile(modulePath, { encoding: "utf8" });
|
|
795
|
-
const toExpose = [
|
|
796
|
-
"isApplicableIndexType",
|
|
797
|
-
"isTypeRelatedTo",
|
|
798
|
-
"relation: { assignable: assignableRelation, identity: identityRelation }",
|
|
799
|
-
];
|
|
813
|
+
const toExpose = ["isApplicableIndexType", "isTypeIdenticalTo"];
|
|
800
814
|
const modifiedSourceText = sourceText.replace("return checker;", `return { ...checker, ${toExpose.join(", ")} };`);
|
|
801
815
|
const compiledWrapper = vm.compileFunction(modifiedSourceText, ["exports", "require", "module", "__filename", "__dirname"], { filename: modulePath });
|
|
802
816
|
compiledWrapper(exports, createRequire(modulePath), module, modulePath, Path.dirname(modulePath));
|
|
@@ -1656,14 +1670,12 @@ class ResultCount {
|
|
|
1656
1670
|
class Result {
|
|
1657
1671
|
expectCount = new ResultCount();
|
|
1658
1672
|
fileCount = new ResultCount();
|
|
1659
|
-
resolvedConfig;
|
|
1660
1673
|
results = [];
|
|
1661
1674
|
targetCount = new ResultCount();
|
|
1662
1675
|
tasks;
|
|
1663
1676
|
testCount = new ResultCount();
|
|
1664
1677
|
timing = new ResultTiming();
|
|
1665
|
-
constructor(
|
|
1666
|
-
this.resolvedConfig = resolvedConfig;
|
|
1678
|
+
constructor(tasks) {
|
|
1667
1679
|
this.tasks = tasks;
|
|
1668
1680
|
}
|
|
1669
1681
|
}
|
|
@@ -1765,6 +1777,7 @@ class ResultHandler {
|
|
|
1765
1777
|
this.#taskResult.timing.start = Date.now();
|
|
1766
1778
|
break;
|
|
1767
1779
|
case "task:error":
|
|
1780
|
+
case "collect:error":
|
|
1768
1781
|
this.#targetResult.status = ResultStatus.Failed;
|
|
1769
1782
|
this.#taskResult.status = ResultStatus.Failed;
|
|
1770
1783
|
this.#taskResult.diagnostics.push(...payload.diagnostics);
|
|
@@ -1899,492 +1912,100 @@ class ResultHandler {
|
|
|
1899
1912
|
}
|
|
1900
1913
|
}
|
|
1901
1914
|
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
TestTreeNodeBrand["Test"] = "test";
|
|
1906
|
-
TestTreeNodeBrand["Expect"] = "expect";
|
|
1907
|
-
})(TestTreeNodeBrand || (TestTreeNodeBrand = {}));
|
|
1915
|
+
function jsx(type, props) {
|
|
1916
|
+
return { props, type };
|
|
1917
|
+
}
|
|
1908
1918
|
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
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
|
-
|
|
1971
|
-
|
|
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
|
|
1999
|
-
#
|
|
2000
|
-
#
|
|
2001
|
-
#
|
|
2002
|
-
#
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
this.#
|
|
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
|
-
#
|
|
2009
|
-
|
|
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
|
-
#
|
|
2026
|
-
|
|
2027
|
-
|
|
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
|
-
|
|
2033
|
-
if (
|
|
2034
|
-
this
|
|
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
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
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
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
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
|
-
|
|
2079
|
-
(
|
|
2080
|
-
|
|
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
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
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
|
|
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 }) })
|
|
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
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
}
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
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
|
|
3389
|
-
for (const sourceFile of program.getSourceFiles()) {
|
|
3482
|
+
const sourceFilesToCheck = program.getSourceFiles().filter((sourceFile) => {
|
|
3390
3483
|
if (program.isSourceFileFromExternalLibrary(sourceFile) || program.isSourceFileDefaultLibrary(sourceFile)) {
|
|
3391
|
-
|
|
3484
|
+
return false;
|
|
3392
3485
|
}
|
|
3393
|
-
if (
|
|
3394
|
-
|
|
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
|
|
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
|
|
3419
|
-
static
|
|
3420
|
-
return
|
|
3511
|
+
class EnsureDiagnosticText {
|
|
3512
|
+
static argumentMustBeProvided(argumentNameText) {
|
|
3513
|
+
return `An argument for '${argumentNameText}' must be provided.`;
|
|
3514
|
+
}
|
|
3515
|
+
static argumentOrTypeArgumentMustBeProvided(argumentNameText, typeArgumentNameText) {
|
|
3516
|
+
return `An argument for '${argumentNameText}' or type argument for '${typeArgumentNameText}' must be provided.`;
|
|
3421
3517
|
}
|
|
3422
3518
|
}
|
|
3423
3519
|
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
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
|
-
|
|
3429
|
-
|
|
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
|
|
3435
|
-
return `
|
|
3544
|
+
static typeArgumentMustBe(argumentNameText, expectedText) {
|
|
3545
|
+
return `A type argument for '${argumentNameText}' must be ${expectedText}.`;
|
|
3436
3546
|
}
|
|
3437
|
-
static
|
|
3438
|
-
return `${
|
|
3547
|
+
static isCallable(isExpression, targetText) {
|
|
3548
|
+
return `${isExpression ? "Expression" : "Type"} is callable ${targetText}.`;
|
|
3439
3549
|
}
|
|
3440
|
-
static
|
|
3441
|
-
return `${
|
|
3550
|
+
static isNotCallable(isExpression, targetText) {
|
|
3551
|
+
return `${isExpression ? "Expression" : "Type"} is not callable ${targetText}.`;
|
|
3442
3552
|
}
|
|
3443
|
-
static
|
|
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
|
|
3447
|
-
return `The decorator function
|
|
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
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
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
|
|
3468
|
-
return `${
|
|
3596
|
+
static didNotRaiseError(isExpression) {
|
|
3597
|
+
return `${isExpression ? "Expression" : "Type"} did not raise a type error.`;
|
|
3469
3598
|
}
|
|
3470
|
-
static
|
|
3471
|
-
return
|
|
3599
|
+
static raisedMatchingError(isExpression) {
|
|
3600
|
+
return `${isExpression ? "Expression" : "Type"} raised a matching type error.`;
|
|
3472
3601
|
}
|
|
3473
|
-
static
|
|
3474
|
-
return
|
|
3602
|
+
static didNotRaiseMatchingError(isExpression) {
|
|
3603
|
+
return `${isExpression ? "Expression" : "Type"} did not raise a matching type error.`;
|
|
3475
3604
|
}
|
|
3476
|
-
static
|
|
3605
|
+
static isAssignableTo(sourceTypeText, targetTypeText) {
|
|
3477
3606
|
return `Type '${sourceTypeText}' is assignable to type '${targetTypeText}'.`;
|
|
3478
3607
|
}
|
|
3479
|
-
static
|
|
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
|
|
3489
|
-
return `Type '${sourceTypeText}' is
|
|
3611
|
+
static isAssignableWith(sourceTypeText, targetTypeText) {
|
|
3612
|
+
return `Type '${sourceTypeText}' is assignable with type '${targetTypeText}'.`;
|
|
3490
3613
|
}
|
|
3491
|
-
static
|
|
3492
|
-
return `Type '${sourceTypeText}' is not
|
|
3614
|
+
static isNotAssignableWith(sourceTypeText, targetTypeText) {
|
|
3615
|
+
return `Type '${sourceTypeText}' is not assignable with type '${targetTypeText}'.`;
|
|
3493
3616
|
}
|
|
3494
|
-
static
|
|
3495
|
-
return `Type '${sourceTypeText}' is
|
|
3617
|
+
static isTheSame(sourceTypeText, targetTypeText) {
|
|
3618
|
+
return `Type '${sourceTypeText}' is the same as type '${targetTypeText}'.`;
|
|
3496
3619
|
}
|
|
3497
|
-
static
|
|
3498
|
-
|
|
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
|
|
3505
|
-
return
|
|
3623
|
+
static isNotCompatibleWith(sourceTypeText, targetTypeText) {
|
|
3624
|
+
return `Type '${sourceTypeText}' is not compatible with type '${targetTypeText}'.`;
|
|
3506
3625
|
}
|
|
3507
|
-
static
|
|
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
|
-
|
|
3527
|
-
#typeChecker;
|
|
3644
|
+
typeChecker;
|
|
3528
3645
|
constructor(compiler, typeChecker, assertion) {
|
|
3529
3646
|
this.#compiler = compiler;
|
|
3530
|
-
this
|
|
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
|
|
3653
|
+
return this.typeChecker
|
|
3537
3654
|
.getIndexInfosOfType(sourceType)
|
|
3538
|
-
.some(({ keyType }) => this
|
|
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
|
-
|
|
3548
|
-
return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
|
|
3664
|
+
return this.#checkIsRelatedTo(sourceNode, targetNode, Relation.Assignable);
|
|
3549
3665
|
}
|
|
3550
3666
|
checkIsAssignableWith(sourceNode, targetNode) {
|
|
3551
|
-
|
|
3552
|
-
return this.#checkIsRelatedTo(targetNode, sourceNode, relation);
|
|
3667
|
+
return this.#checkIsRelatedTo(targetNode, sourceNode, Relation.Assignable);
|
|
3553
3668
|
}
|
|
3554
3669
|
checkIsIdenticalTo(sourceNode, targetNode) {
|
|
3555
|
-
|
|
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.#
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
3593
|
-
return this.#typeChecker.typeToString(type);
|
|
3707
|
+
return this.typeChecker.typeToString(this.getType(node));
|
|
3594
3708
|
}
|
|
3595
3709
|
getType(node) {
|
|
3596
|
-
return this
|
|
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
|
|
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.
|
|
3659
|
-
: ExpectDiagnosticText.
|
|
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(
|
|
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 &&
|
|
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.
|
|
3726
|
-
ExpectDiagnosticText.
|
|
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.
|
|
3735
|
-
ExpectDiagnosticText.
|
|
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.
|
|
3846
|
+
ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
|
|
3748
3847
|
ExpectDiagnosticText.typesOfPropertyAreNotCompatible(targetPropertyName),
|
|
3749
|
-
ExpectDiagnosticText.
|
|
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.
|
|
3762
|
-
ExpectDiagnosticText.
|
|
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.
|
|
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 &&
|
|
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.
|
|
3780
|
-
: ExpectDiagnosticText.
|
|
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
|
|
3800
|
-
? ExpectDiagnosticText.
|
|
3801
|
-
: ExpectDiagnosticText.
|
|
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 (!
|
|
3905
|
+
if (!(targetType.flags & this.#compiler.TypeFlags.Object)) {
|
|
3807
3906
|
const expectedText = "of an object type";
|
|
3808
|
-
const text = this.#compiler
|
|
3809
|
-
? ExpectDiagnosticText.
|
|
3810
|
-
: ExpectDiagnosticText.
|
|
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(
|
|
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.
|
|
3843
|
-
explainNotText = ExpectDiagnosticText.
|
|
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.
|
|
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
|
|
3909
|
-
? ExpectDiagnosticText.
|
|
3910
|
-
: ExpectDiagnosticText.
|
|
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.
|
|
3924
|
-
explainNotText = ExpectDiagnosticText.
|
|
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.
|
|
3935
|
-
explainNotText = ExpectDiagnosticText.
|
|
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 (
|
|
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.
|
|
3962
|
-
: [Diagnostic.error(ExpectDiagnosticText.
|
|
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
|
|
3971
|
-
? ExpectDiagnosticText.
|
|
3972
|
-
: ExpectDiagnosticText.
|
|
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 (
|
|
4233
|
+
if (isStringOrNumberLiteralType(this.#compiler, targetType)) {
|
|
3979
4234
|
propertyNameText = targetType.value.toString();
|
|
3980
4235
|
}
|
|
3981
|
-
else if (
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
4030
|
-
: ExpectDiagnosticText.
|
|
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
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
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
|
-
#
|
|
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,
|
|
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.#
|
|
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
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
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
|
-
|
|
4161
|
-
|
|
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
|
-
|
|
4166
|
-
const
|
|
4167
|
-
|
|
4168
|
-
|
|
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
|
-
|
|
4171
|
-
for (const rejectedType of this.#
|
|
4172
|
-
const allowedKeyword = this.#compiler.SyntaxKind[`${
|
|
4173
|
-
if (
|
|
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
|
|
4178
|
-
|
|
4179
|
-
if (!argumentNode) {
|
|
4454
|
+
for (const [name, node] of target) {
|
|
4455
|
+
if (!node) {
|
|
4180
4456
|
continue;
|
|
4181
4457
|
}
|
|
4182
|
-
if (
|
|
4458
|
+
if (this.#typeChecker.getTypeAtLocation(node).flags & this.#compiler.TypeFlags[capitalize(rejectedType)]) {
|
|
4183
4459
|
const text = [
|
|
4184
|
-
this.#compiler
|
|
4185
|
-
?
|
|
4186
|
-
:
|
|
4187
|
-
...
|
|
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(
|
|
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
|
-
#
|
|
4206
|
-
constructor(
|
|
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
|
-
|
|
4212
|
-
this.#expectService = new ExpectService(compiler, typeChecker,
|
|
4544
|
+
const reject = new Reject(compiler, typeChecker, resolvedConfig);
|
|
4545
|
+
this.#expectService = new ExpectService(compiler, typeChecker, reject);
|
|
4546
|
+
this.#whenService = new WhenService(reject, onTaskDiagnostics);
|
|
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(
|
|
4238
|
-
for (const
|
|
4571
|
+
visit(nodes, runMode, parentResult) {
|
|
4572
|
+
for (const node of nodes) {
|
|
4239
4573
|
if (this.#cancellationToken?.isCancellationRequested) {
|
|
4240
4574
|
break;
|
|
4241
4575
|
}
|
|
4242
|
-
|
|
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(
|
|
4578
|
+
this.#visitDescribe(node, runMode, parentResult);
|
|
4250
4579
|
break;
|
|
4251
4580
|
case TestTreeNodeBrand.Test:
|
|
4252
|
-
this.#visitTest(
|
|
4581
|
+
this.#visitTest(node, runMode, parentResult);
|
|
4253
4582
|
break;
|
|
4254
4583
|
case TestTreeNodeBrand.Expect:
|
|
4255
|
-
this.#visitAssertion(
|
|
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
|
-
|
|
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(
|
|
4357
|
-
this.#resolvedConfig = resolvedConfig;
|
|
4685
|
+
constructor(compiler, resolvedConfig) {
|
|
4358
4686
|
this.#compiler = compiler;
|
|
4359
|
-
this.#
|
|
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
|
-
|
|
4363
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
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
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
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
|
-
|
|
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
|
|
4411
|
-
const
|
|
4749
|
+
const typeChecker = program?.getTypeChecker();
|
|
4750
|
+
const onTaskDiagnostics = (diagnostics) => {
|
|
4751
|
+
this.#onDiagnostics(diagnostics, taskResult);
|
|
4752
|
+
};
|
|
4753
|
+
const testTreeWalker = new TestTreeWalker(this.#compiler, typeChecker, this.#resolvedConfig, onTaskDiagnostics, {
|
|
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.
|
|
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(
|
|
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
|
|
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,
|
|
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 };
|