tstyche 3.5.0 → 4.0.0-beta.1
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 +3 -3
- package/build/index.d.cts +4 -69
- package/build/index.d.ts +4 -69
- package/build/tstyche.d.ts +70 -80
- package/build/tstyche.js +1074 -991
- package/package.json +12 -15
package/build/tstyche.js
CHANGED
|
@@ -93,7 +93,7 @@ class DiagnosticOrigin {
|
|
|
93
93
|
this.assertion = assertion;
|
|
94
94
|
}
|
|
95
95
|
static fromAssertion(assertion) {
|
|
96
|
-
const node = assertion.
|
|
96
|
+
const node = assertion.matcherNameNode.name;
|
|
97
97
|
return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertion);
|
|
98
98
|
}
|
|
99
99
|
static fromNode(node, assertion) {
|
|
@@ -140,15 +140,15 @@ class Diagnostic {
|
|
|
140
140
|
}
|
|
141
141
|
const text = typeof diagnostic.messageText === "string"
|
|
142
142
|
? diagnostic.messageText
|
|
143
|
-
: Diagnostic
|
|
143
|
+
: Diagnostic.toMessageText(diagnostic.messageText);
|
|
144
144
|
return new Diagnostic(text, DiagnosticCategory.Error, origin).add({ code, related });
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
|
-
static
|
|
147
|
+
static toMessageText(chain) {
|
|
148
148
|
const result = [chain.messageText];
|
|
149
149
|
if (chain.next != null) {
|
|
150
150
|
for (const nextChain of chain.next) {
|
|
151
|
-
result.push(...Diagnostic
|
|
151
|
+
result.push(...Diagnostic.toMessageText(nextChain));
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
return result;
|
|
@@ -363,9 +363,6 @@ class Version {
|
|
|
363
363
|
static isSatisfiedWith(source, target) {
|
|
364
364
|
return source === target || Version.#satisfies(source, target);
|
|
365
365
|
}
|
|
366
|
-
static isVersionTag(target) {
|
|
367
|
-
return /^\d+/.test(target);
|
|
368
|
-
}
|
|
369
366
|
static #satisfies(source, target) {
|
|
370
367
|
const sourceElements = source.split(/\.|-/);
|
|
371
368
|
const targetElements = target.split(/\.|-/);
|
|
@@ -394,8 +391,8 @@ class StoreDiagnosticText {
|
|
|
394
391
|
static failedToFetchMetadata(registry) {
|
|
395
392
|
return `Failed to fetch metadata of the 'typescript' package from '${registry}'.`;
|
|
396
393
|
}
|
|
397
|
-
static
|
|
398
|
-
return `Failed to
|
|
394
|
+
static failedToFetchPackage(version) {
|
|
395
|
+
return `Failed to fetch the 'typescript@${version}' package.`;
|
|
399
396
|
}
|
|
400
397
|
static failedToUpdateMetadata(registry) {
|
|
401
398
|
return `Failed to update metadata of the 'typescript' package from '${registry}'.`;
|
|
@@ -499,9 +496,10 @@ class LockService {
|
|
|
499
496
|
}
|
|
500
497
|
|
|
501
498
|
class Manifest {
|
|
502
|
-
static #version = "
|
|
499
|
+
static #version = "3";
|
|
503
500
|
$version;
|
|
504
501
|
lastUpdated;
|
|
502
|
+
minorVersions;
|
|
505
503
|
npmRegistry;
|
|
506
504
|
packages;
|
|
507
505
|
resolutions;
|
|
@@ -509,6 +507,7 @@ class Manifest {
|
|
|
509
507
|
constructor(data) {
|
|
510
508
|
this.$version = data.$version ?? Manifest.#version;
|
|
511
509
|
this.lastUpdated = data.lastUpdated ?? Date.now();
|
|
510
|
+
this.minorVersions = data.minorVersions;
|
|
512
511
|
this.npmRegistry = data.npmRegistry;
|
|
513
512
|
this.packages = data.packages;
|
|
514
513
|
this.resolutions = data.resolutions;
|
|
@@ -542,6 +541,7 @@ class Manifest {
|
|
|
542
541
|
const manifestData = {
|
|
543
542
|
$version: this.$version,
|
|
544
543
|
lastUpdated: this.lastUpdated,
|
|
544
|
+
minorVersions: this.minorVersions,
|
|
545
545
|
npmRegistry: this.npmRegistry,
|
|
546
546
|
packages: this.packages,
|
|
547
547
|
resolutions: this.resolutions,
|
|
@@ -556,7 +556,7 @@ class ManifestService {
|
|
|
556
556
|
#manifestFilePath;
|
|
557
557
|
#npmRegistry;
|
|
558
558
|
#storePath;
|
|
559
|
-
#supportedVersionRegex = /^(
|
|
559
|
+
#supportedVersionRegex = /^(5)\.\d\.\d$/;
|
|
560
560
|
constructor(storePath, npmRegistry, fetcher) {
|
|
561
561
|
this.#storePath = storePath;
|
|
562
562
|
this.#npmRegistry = npmRegistry;
|
|
@@ -591,7 +591,7 @@ class ManifestService {
|
|
|
591
591
|
packages[tag] = { integrity: meta.dist.integrity, tarball: meta.dist.tarball };
|
|
592
592
|
}
|
|
593
593
|
}
|
|
594
|
-
const minorVersions = new Set(versions.map((version) => version.slice(0, -2)));
|
|
594
|
+
const minorVersions = [...new Set(versions.map((version) => version.slice(0, -2)))];
|
|
595
595
|
for (const tag of minorVersions) {
|
|
596
596
|
const resolvedVersion = versions.findLast((version) => version.startsWith(tag));
|
|
597
597
|
if (resolvedVersion != null) {
|
|
@@ -608,7 +608,7 @@ class ManifestService {
|
|
|
608
608
|
}
|
|
609
609
|
}
|
|
610
610
|
}
|
|
611
|
-
return new Manifest({ npmRegistry: this.#npmRegistry, packages, resolutions, versions });
|
|
611
|
+
return new Manifest({ minorVersions, npmRegistry: this.#npmRegistry, packages, resolutions, versions });
|
|
612
612
|
}
|
|
613
613
|
async open(options) {
|
|
614
614
|
if (!existsSync(this.#manifestFilePath)) {
|
|
@@ -685,9 +685,6 @@ class PackageService {
|
|
|
685
685
|
if (response?.body != null) {
|
|
686
686
|
const targetPath = `${packagePath}-${Math.random().toString(32).slice(2)}`;
|
|
687
687
|
for await (const file of TarReader.extract(response.body)) {
|
|
688
|
-
if (!file.name.startsWith("package/")) {
|
|
689
|
-
continue;
|
|
690
|
-
}
|
|
691
688
|
const filePath = Path.join(targetPath, file.name.replace("package/", ""));
|
|
692
689
|
const directoryPath = Path.dirname(filePath);
|
|
693
690
|
if (!existsSync(directoryPath)) {
|
|
@@ -702,7 +699,7 @@ class PackageService {
|
|
|
702
699
|
}
|
|
703
700
|
async ensure(packageVersion, manifest) {
|
|
704
701
|
let packagePath = Path.join(this.#storePath, `typescript@${packageVersion}`);
|
|
705
|
-
const diagnostic = Diagnostic.error(StoreDiagnosticText.
|
|
702
|
+
const diagnostic = Diagnostic.error(StoreDiagnosticText.failedToFetchPackage(packageVersion));
|
|
706
703
|
if (await this.#lockService.isLocked(packagePath, diagnostic)) {
|
|
707
704
|
return;
|
|
708
705
|
}
|
|
@@ -742,11 +739,7 @@ class Store {
|
|
|
742
739
|
Store.#packageService = new PackageService(Store.#storePath, Store.#fetcher, Store.#lockService);
|
|
743
740
|
Store.#manifestService = new ManifestService(Store.#storePath, Store.#npmRegistry, Store.#fetcher);
|
|
744
741
|
}
|
|
745
|
-
static async
|
|
746
|
-
await Store.open();
|
|
747
|
-
return Store.#supportedTags;
|
|
748
|
-
}
|
|
749
|
-
static async install(tag) {
|
|
742
|
+
static async fetch(tag) {
|
|
750
743
|
if (tag === "current") {
|
|
751
744
|
return;
|
|
752
745
|
}
|
|
@@ -799,14 +792,11 @@ class Store {
|
|
|
799
792
|
modulePath = Path.resolve(modulePath, "../tsserverlibrary.js");
|
|
800
793
|
}
|
|
801
794
|
const sourceText = await fs.readFile(modulePath, { encoding: "utf8" });
|
|
802
|
-
const toExpose = [
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
toExpose.push("getTypeOfSymbol");
|
|
808
|
-
}
|
|
809
|
-
toExpose.push("isTypeRelatedTo", "relation: { assignable: assignableRelation, identity: identityRelation, subtype: strictSubtypeRelation }");
|
|
795
|
+
const toExpose = [
|
|
796
|
+
"isApplicableIndexType",
|
|
797
|
+
"isTypeRelatedTo",
|
|
798
|
+
"relation: { assignable: assignableRelation, identity: identityRelation }",
|
|
799
|
+
];
|
|
810
800
|
const modifiedSourceText = sourceText.replace("return checker;", `return { ...checker, ${toExpose.join(", ")} };`);
|
|
811
801
|
const compiledWrapper = vm.compileFunction(modifiedSourceText, ["exports", "require", "module", "__filename", "__dirname"], { filename: modulePath });
|
|
812
802
|
compiledWrapper(exports, createRequire(modulePath), module, modulePath, Path.dirname(modulePath));
|
|
@@ -857,7 +847,7 @@ class Target {
|
|
|
857
847
|
}
|
|
858
848
|
await Store.open();
|
|
859
849
|
if (Store.manifest != null) {
|
|
860
|
-
let versions =
|
|
850
|
+
let versions = [...Store.manifest.minorVersions];
|
|
861
851
|
for (const comparator of query.split(" ")) {
|
|
862
852
|
versions = Target.#filter(comparator, versions);
|
|
863
853
|
}
|
|
@@ -913,15 +903,15 @@ class Options {
|
|
|
913
903
|
},
|
|
914
904
|
{
|
|
915
905
|
brand: OptionBrand.BareTrue,
|
|
916
|
-
description: "
|
|
906
|
+
description: "Fetch the specified versions of the 'typescript' package and exit.",
|
|
917
907
|
group: OptionGroup.CommandLine,
|
|
918
|
-
name: "
|
|
908
|
+
name: "fetch",
|
|
919
909
|
},
|
|
920
910
|
{
|
|
921
911
|
brand: OptionBrand.BareTrue,
|
|
922
|
-
description: "
|
|
912
|
+
description: "Print the list of command line options with brief descriptions and exit.",
|
|
923
913
|
group: OptionGroup.CommandLine,
|
|
924
|
-
name: "
|
|
914
|
+
name: "help",
|
|
925
915
|
},
|
|
926
916
|
{
|
|
927
917
|
brand: OptionBrand.BareTrue,
|
|
@@ -1504,11 +1494,11 @@ class ConfigFileParser {
|
|
|
1504
1494
|
}
|
|
1505
1495
|
|
|
1506
1496
|
const defaultOptions = {
|
|
1507
|
-
checkSourceFiles:
|
|
1497
|
+
checkSourceFiles: true,
|
|
1508
1498
|
failFast: false,
|
|
1509
1499
|
plugins: [],
|
|
1510
|
-
rejectAnyType:
|
|
1511
|
-
rejectNeverType:
|
|
1500
|
+
rejectAnyType: true,
|
|
1501
|
+
rejectNeverType: true,
|
|
1512
1502
|
reporters: ["list", "summary"],
|
|
1513
1503
|
rootPath: Path.resolve("./"),
|
|
1514
1504
|
target: environmentOptions.typescriptModule != null ? ["current"] : ["latest"],
|
|
@@ -1909,152 +1899,544 @@ class ResultHandler {
|
|
|
1909
1899
|
}
|
|
1910
1900
|
}
|
|
1911
1901
|
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
(
|
|
1918
|
-
Color["Reset"] = "0";
|
|
1919
|
-
Color["Red"] = "31";
|
|
1920
|
-
Color["Green"] = "32";
|
|
1921
|
-
Color["Yellow"] = "33";
|
|
1922
|
-
Color["Blue"] = "34";
|
|
1923
|
-
Color["Magenta"] = "35";
|
|
1924
|
-
Color["Cyan"] = "36";
|
|
1925
|
-
Color["Gray"] = "90";
|
|
1926
|
-
})(Color || (Color = {}));
|
|
1902
|
+
var TestTreeNodeBrand;
|
|
1903
|
+
(function (TestTreeNodeBrand) {
|
|
1904
|
+
TestTreeNodeBrand["Describe"] = "describe";
|
|
1905
|
+
TestTreeNodeBrand["Test"] = "test";
|
|
1906
|
+
TestTreeNodeBrand["Expect"] = "expect";
|
|
1907
|
+
})(TestTreeNodeBrand || (TestTreeNodeBrand = {}));
|
|
1927
1908
|
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1909
|
+
class TestTreeNode {
|
|
1910
|
+
brand;
|
|
1911
|
+
children = [];
|
|
1912
|
+
#compiler;
|
|
1913
|
+
diagnostics = new Set();
|
|
1914
|
+
flags;
|
|
1915
|
+
name = "";
|
|
1916
|
+
node;
|
|
1917
|
+
parent;
|
|
1918
|
+
constructor(compiler, brand, node, parent, flags) {
|
|
1919
|
+
this.brand = brand;
|
|
1920
|
+
this.#compiler = compiler;
|
|
1921
|
+
this.node = node;
|
|
1922
|
+
this.parent = parent;
|
|
1923
|
+
this.flags = flags;
|
|
1924
|
+
if (node.arguments[0] != null && compiler.isStringLiteralLike(node.arguments[0])) {
|
|
1925
|
+
this.name = node.arguments[0].text;
|
|
1926
|
+
}
|
|
1927
|
+
if (node.arguments[1] != null &&
|
|
1928
|
+
compiler.isFunctionLike(node.arguments[1]) &&
|
|
1929
|
+
compiler.isBlock(node.arguments[1].body)) {
|
|
1930
|
+
const blockStart = node.arguments[1].body.getStart();
|
|
1931
|
+
const blockEnd = node.arguments[1].body.getEnd();
|
|
1932
|
+
for (const diagnostic of parent.diagnostics) {
|
|
1933
|
+
if (diagnostic.start != null && diagnostic.start >= blockStart && diagnostic.start <= blockEnd) {
|
|
1934
|
+
this.diagnostics.add(diagnostic);
|
|
1935
|
+
parent.diagnostics.delete(diagnostic);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
validate() {
|
|
1941
|
+
const diagnostics = [];
|
|
1942
|
+
const getText = (node) => `'${node.expression.getText()}()' cannot be nested within '${this.node.expression.getText()}()'.`;
|
|
1943
|
+
const getParentCallExpression = (node) => {
|
|
1944
|
+
while (!this.#compiler.isCallExpression(node.parent)) {
|
|
1945
|
+
node = node.parent;
|
|
1946
|
+
}
|
|
1947
|
+
return node.parent;
|
|
1948
|
+
};
|
|
1949
|
+
switch (this.brand) {
|
|
1950
|
+
case TestTreeNodeBrand.Describe:
|
|
1951
|
+
for (const child of this.children) {
|
|
1952
|
+
if (child.brand === TestTreeNodeBrand.Expect) {
|
|
1953
|
+
diagnostics.push(Diagnostic.error(getText(child.node), DiagnosticOrigin.fromNode(getParentCallExpression(child.node))));
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
break;
|
|
1957
|
+
case TestTreeNodeBrand.Test:
|
|
1958
|
+
case TestTreeNodeBrand.Expect:
|
|
1959
|
+
for (const child of this.children) {
|
|
1960
|
+
if (child.brand !== TestTreeNodeBrand.Expect) {
|
|
1961
|
+
diagnostics.push(Diagnostic.error(getText(child.node), DiagnosticOrigin.fromNode(child.node)));
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
break;
|
|
1965
|
+
}
|
|
1966
|
+
return diagnostics;
|
|
1932
1967
|
}
|
|
1933
|
-
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] }));
|
|
1934
1968
|
}
|
|
1935
1969
|
|
|
1936
|
-
|
|
1937
|
-
|
|
1970
|
+
class AssertionNode extends TestTreeNode {
|
|
1971
|
+
abilityDiagnostics;
|
|
1972
|
+
isNot;
|
|
1973
|
+
matcherNode;
|
|
1974
|
+
matcherNameNode;
|
|
1975
|
+
modifierNode;
|
|
1976
|
+
notNode;
|
|
1977
|
+
source;
|
|
1978
|
+
target;
|
|
1979
|
+
constructor(compiler, brand, node, parent, flags, matcherNode, matcherNameNode, modifierNode, notNode) {
|
|
1980
|
+
super(compiler, brand, node, parent, flags);
|
|
1981
|
+
this.isNot = notNode != null;
|
|
1982
|
+
this.matcherNode = matcherNode;
|
|
1983
|
+
this.matcherNameNode = matcherNameNode;
|
|
1984
|
+
this.modifierNode = modifierNode;
|
|
1985
|
+
this.source = this.node.typeArguments ?? this.node.arguments;
|
|
1986
|
+
if (compiler.isCallExpression(this.matcherNode)) {
|
|
1987
|
+
this.target = this.matcherNode.typeArguments ?? this.matcherNode.arguments;
|
|
1988
|
+
}
|
|
1989
|
+
for (const diagnostic of parent.diagnostics) {
|
|
1990
|
+
if (diagnostic.start != null && diagnostic.start >= this.source.pos && diagnostic.start <= this.source.end) {
|
|
1991
|
+
this.diagnostics.add(diagnostic);
|
|
1992
|
+
parent.diagnostics.delete(diagnostic);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1938
1996
|
}
|
|
1939
1997
|
|
|
1940
|
-
class
|
|
1941
|
-
#
|
|
1942
|
-
#
|
|
1943
|
-
#
|
|
1944
|
-
#
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
this.#
|
|
1948
|
-
|
|
1949
|
-
#escapeSequence(attributes) {
|
|
1950
|
-
return ["\u001B[", Array.isArray(attributes) ? attributes.join(";") : attributes, "m"].join("");
|
|
1951
|
-
}
|
|
1952
|
-
#indentEachLine(lines, level) {
|
|
1953
|
-
if (level === 0) {
|
|
1954
|
-
return lines;
|
|
1955
|
-
}
|
|
1956
|
-
return lines.replace(this.#notEmptyLineRegex, this.#indentStep.repeat(level));
|
|
1998
|
+
class AbilityLayer {
|
|
1999
|
+
#filePath = "";
|
|
2000
|
+
#nodes = [];
|
|
2001
|
+
#projectService;
|
|
2002
|
+
#resolvedConfig;
|
|
2003
|
+
#text = "";
|
|
2004
|
+
constructor(projectService, resolvedConfig) {
|
|
2005
|
+
this.#projectService = projectService;
|
|
2006
|
+
this.#resolvedConfig = resolvedConfig;
|
|
1957
2007
|
}
|
|
1958
|
-
|
|
1959
|
-
if (
|
|
1960
|
-
return
|
|
1961
|
-
}
|
|
1962
|
-
if (element.type === "ansi" && !this.#noColor) {
|
|
1963
|
-
return this.#escapeSequence(element.props.escapes);
|
|
2008
|
+
#getErasedRangeText(range) {
|
|
2009
|
+
if (this.#text.indexOf("\n", range.start) >= range.end) {
|
|
2010
|
+
return " ".repeat(range.end - range.start);
|
|
1964
2011
|
}
|
|
1965
|
-
|
|
1966
|
-
|
|
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
|
+
}
|
|
1967
2022
|
}
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
2023
|
+
return text.join("");
|
|
2024
|
+
}
|
|
2025
|
+
#addRanges(node, ranges) {
|
|
2026
|
+
this.#nodes.push(node);
|
|
2027
|
+
for (const range of ranges) {
|
|
2028
|
+
const rangeText = this.#getErasedRangeText(range);
|
|
2029
|
+
this.#text = `${this.#text.slice(0, range.start)}${rangeText}${this.#text.slice(range.end)}`;
|
|
1971
2030
|
}
|
|
1972
|
-
return "";
|
|
1973
2031
|
}
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
2032
|
+
close() {
|
|
2033
|
+
if (this.#nodes.length > 0) {
|
|
2034
|
+
this.#projectService.openFile(this.#filePath, this.#text, this.#resolvedConfig.rootPath);
|
|
2035
|
+
const languageService = this.#projectService.getLanguageService(this.#filePath);
|
|
2036
|
+
const diagnostics = new Set(languageService?.getSemanticDiagnostics(this.#filePath)?.toReversed());
|
|
2037
|
+
if (diagnostics.size > 0) {
|
|
2038
|
+
for (const node of this.#nodes.toReversed()) {
|
|
2039
|
+
for (const diagnostic of diagnostics) {
|
|
2040
|
+
if (diagnostic.start != null &&
|
|
2041
|
+
diagnostic.start >= node.matcherNode.pos &&
|
|
2042
|
+
diagnostic.start <= node.matcherNode.end) {
|
|
2043
|
+
if (!node.abilityDiagnostics) {
|
|
2044
|
+
node.abilityDiagnostics = new Set();
|
|
2045
|
+
}
|
|
2046
|
+
node.abilityDiagnostics.add(diagnostic);
|
|
2047
|
+
diagnostics.delete(diagnostic);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
1984
2051
|
}
|
|
1985
|
-
|
|
1986
|
-
|
|
2052
|
+
}
|
|
2053
|
+
this.#filePath = "";
|
|
2054
|
+
this.#nodes = [];
|
|
2055
|
+
this.#text = "";
|
|
2056
|
+
}
|
|
2057
|
+
handleNode(assertionNode) {
|
|
2058
|
+
switch (assertionNode.matcherNameNode.name.text) {
|
|
2059
|
+
case "toBeApplicable": {
|
|
2060
|
+
const expectStart = assertionNode.node.pos;
|
|
2061
|
+
const expectExpressionEnd = assertionNode.node.expression.end;
|
|
2062
|
+
const expectEnd = assertionNode.node.end;
|
|
2063
|
+
const matcherNameEnd = assertionNode.matcherNameNode.end;
|
|
2064
|
+
this.#addRanges(assertionNode, [
|
|
2065
|
+
{ end: expectExpressionEnd + 1, start: expectStart },
|
|
2066
|
+
{ end: matcherNameEnd, start: expectEnd - 1 },
|
|
2067
|
+
]);
|
|
2068
|
+
break;
|
|
1987
2069
|
}
|
|
1988
2070
|
}
|
|
1989
|
-
|
|
2071
|
+
}
|
|
2072
|
+
open(sourceFile) {
|
|
2073
|
+
this.#filePath = sourceFile.fileName;
|
|
2074
|
+
this.#text = sourceFile.text;
|
|
1990
2075
|
}
|
|
1991
2076
|
}
|
|
1992
2077
|
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2078
|
+
var TestTreeNodeFlags;
|
|
2079
|
+
(function (TestTreeNodeFlags) {
|
|
2080
|
+
TestTreeNodeFlags[TestTreeNodeFlags["None"] = 0] = "None";
|
|
2081
|
+
TestTreeNodeFlags[TestTreeNodeFlags["Fail"] = 1] = "Fail";
|
|
2082
|
+
TestTreeNodeFlags[TestTreeNodeFlags["Only"] = 2] = "Only";
|
|
2083
|
+
TestTreeNodeFlags[TestTreeNodeFlags["Skip"] = 4] = "Skip";
|
|
2084
|
+
TestTreeNodeFlags[TestTreeNodeFlags["Todo"] = 8] = "Todo";
|
|
2085
|
+
})(TestTreeNodeFlags || (TestTreeNodeFlags = {}));
|
|
2000
2086
|
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
const linesAbove = options?.linesAbove ?? 2;
|
|
2018
|
-
const linesBelow = options?.linesBelow ?? 3;
|
|
2019
|
-
const showBreadcrumbs = options?.showBreadcrumbs ?? true;
|
|
2020
|
-
const lineMap = diagnosticOrigin.sourceFile.getLineStarts();
|
|
2021
|
-
const { character: firstMarkedLineCharacter, line: firstMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.start);
|
|
2022
|
-
const { character: lastMarkedLineCharacter, line: lastMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.end);
|
|
2023
|
-
const firstLine = Math.max(firstMarkedLine - linesAbove, 0);
|
|
2024
|
-
const lastLine = Math.min(lastMarkedLine + linesBelow, lineMap.length - 1);
|
|
2025
|
-
const gutterWidth = (lastLine + 1).toString().length + 2;
|
|
2026
|
-
let highlightColor;
|
|
2027
|
-
switch (diagnosticCategory) {
|
|
2028
|
-
case DiagnosticCategory.Error:
|
|
2029
|
-
highlightColor = Color.Red;
|
|
2030
|
-
break;
|
|
2031
|
-
case DiagnosticCategory.Warning:
|
|
2032
|
-
highlightColor = Color.Yellow;
|
|
2033
|
-
break;
|
|
2087
|
+
class IdentifierLookup {
|
|
2088
|
+
#compiler;
|
|
2089
|
+
#identifiers;
|
|
2090
|
+
#moduleSpecifiers = ['"tstyche"', "'tstyche'"];
|
|
2091
|
+
constructor(compiler, identifiers) {
|
|
2092
|
+
this.#compiler = compiler;
|
|
2093
|
+
this.#identifiers = identifiers ?? {
|
|
2094
|
+
namedImports: {
|
|
2095
|
+
describe: undefined,
|
|
2096
|
+
expect: undefined,
|
|
2097
|
+
it: undefined,
|
|
2098
|
+
namespace: undefined,
|
|
2099
|
+
test: undefined,
|
|
2100
|
+
},
|
|
2101
|
+
namespace: undefined,
|
|
2102
|
+
};
|
|
2034
2103
|
}
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
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
|
+
}
|
|
2050
2124
|
}
|
|
2051
|
-
|
|
2052
|
-
|
|
2125
|
+
if (this.#compiler.isNamespaceImport(node.importClause.namedBindings)) {
|
|
2126
|
+
this.#identifiers.namespace = node.importClause.namedBindings.name.getText();
|
|
2053
2127
|
}
|
|
2054
2128
|
}
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
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;
|
|
2388
|
+
}
|
|
2389
|
+
text.push("");
|
|
2390
|
+
return jsx(Text, { color: Color.Gray, children: text.reverse().join(" ❭ ") });
|
|
2391
|
+
}
|
|
2392
|
+
function CodeLineText({ gutterWidth, lineNumber, lineNumberColor = Color.Gray, lineText }) {
|
|
2393
|
+
return (jsx(Line, { children: [jsx(Text, { color: lineNumberColor, children: lineNumber.toString().padStart(gutterWidth) }), jsx(Text, { color: Color.Gray, children: " | " }), lineText] }));
|
|
2394
|
+
}
|
|
2395
|
+
function SquiggleLineText({ gutterWidth, indentWidth = 0, squiggleColor, squiggleWidth }) {
|
|
2396
|
+
return (jsx(Line, { children: [" ".repeat(gutterWidth), jsx(Text, { color: Color.Gray, children: " | " }), " ".repeat(indentWidth), jsx(Text, { color: squiggleColor, children: "~".repeat(squiggleWidth === 0 ? 1 : squiggleWidth) })] }));
|
|
2397
|
+
}
|
|
2398
|
+
function CodeFrameText({ diagnosticCategory, diagnosticOrigin, options }) {
|
|
2399
|
+
const linesAbove = options?.linesAbove ?? 2;
|
|
2400
|
+
const linesBelow = options?.linesBelow ?? 3;
|
|
2401
|
+
const showBreadcrumbs = options?.showBreadcrumbs ?? true;
|
|
2402
|
+
const lineMap = diagnosticOrigin.sourceFile.getLineStarts();
|
|
2403
|
+
const { character: firstMarkedLineCharacter, line: firstMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.start);
|
|
2404
|
+
const { character: lastMarkedLineCharacter, line: lastMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.end);
|
|
2405
|
+
const firstLine = Math.max(firstMarkedLine - linesAbove, 0);
|
|
2406
|
+
const lastLine = Math.min(lastMarkedLine + linesBelow, lineMap.length - 1);
|
|
2407
|
+
const gutterWidth = (lastLine + 1).toString().length + 2;
|
|
2408
|
+
let highlightColor;
|
|
2409
|
+
switch (diagnosticCategory) {
|
|
2410
|
+
case DiagnosticCategory.Error:
|
|
2411
|
+
highlightColor = Color.Red;
|
|
2412
|
+
break;
|
|
2413
|
+
case DiagnosticCategory.Warning:
|
|
2414
|
+
highlightColor = Color.Yellow;
|
|
2415
|
+
break;
|
|
2416
|
+
}
|
|
2417
|
+
const codeFrame = [];
|
|
2418
|
+
for (let index = firstLine; index <= lastLine; index++) {
|
|
2419
|
+
const lineStart = lineMap[index];
|
|
2420
|
+
const lineEnd = index === lineMap.length - 1 ? diagnosticOrigin.sourceFile.text.length : lineMap[index + 1];
|
|
2421
|
+
const lineText = diagnosticOrigin.sourceFile.text.slice(lineStart, lineEnd).trimEnd().replace(/\t/g, " ");
|
|
2422
|
+
if (index >= firstMarkedLine && index <= lastMarkedLine) {
|
|
2423
|
+
codeFrame.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumber: index + 1, lineNumberColor: highlightColor, lineText: lineText }));
|
|
2424
|
+
if (index === firstMarkedLine) {
|
|
2425
|
+
const squiggleLength = index === lastMarkedLine
|
|
2426
|
+
? lastMarkedLineCharacter - firstMarkedLineCharacter
|
|
2427
|
+
: lineText.length - firstMarkedLineCharacter;
|
|
2428
|
+
codeFrame.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, indentWidth: firstMarkedLineCharacter, squiggleColor: highlightColor, squiggleWidth: squiggleLength }));
|
|
2429
|
+
}
|
|
2430
|
+
else if (index === lastMarkedLine) {
|
|
2431
|
+
codeFrame.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleColor: highlightColor, squiggleWidth: lastMarkedLineCharacter }));
|
|
2432
|
+
}
|
|
2433
|
+
else {
|
|
2434
|
+
codeFrame.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleColor: highlightColor, squiggleWidth: lineText.length }));
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
else {
|
|
2438
|
+
codeFrame.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumber: index + 1, lineText: lineText }));
|
|
2439
|
+
}
|
|
2058
2440
|
}
|
|
2059
2441
|
let breadcrumbs;
|
|
2060
2442
|
if (showBreadcrumbs && diagnosticOrigin.assertion != null) {
|
|
@@ -2150,7 +2532,7 @@ function CommandLineUsageText() {
|
|
|
2150
2532
|
const usage = [
|
|
2151
2533
|
["tstyche", "Run all tests."],
|
|
2152
2534
|
["tstyche path/to/first.test.ts", "Only run the test files with matching path."],
|
|
2153
|
-
["tstyche --target
|
|
2535
|
+
["tstyche --target 5.3,5.6.2,current", "Test on all specified versions of TypeScript."],
|
|
2154
2536
|
];
|
|
2155
2537
|
const usageText = usage.map(([commandText, descriptionText]) => (jsx(Line, { children: [jsx(CommandText, { text: commandText }), jsx(OptionDescriptionText, { text: descriptionText })] })));
|
|
2156
2538
|
return jsx(Text, { children: usageText });
|
|
@@ -2363,21 +2745,11 @@ class ListReporter extends BaseReporter {
|
|
|
2363
2745
|
#hasReportedError = false;
|
|
2364
2746
|
#hasReportedUses = false;
|
|
2365
2747
|
#isFileViewExpanded = false;
|
|
2366
|
-
#seenDeprecations = new Set();
|
|
2367
2748
|
get #isLastFile() {
|
|
2368
2749
|
return this.#fileCount === 0;
|
|
2369
2750
|
}
|
|
2370
|
-
on([event, payload]) {
|
|
2371
|
-
switch (event) {
|
|
2372
|
-
case "deprecation:info": {
|
|
2373
|
-
for (const diagnostic of payload.diagnostics) {
|
|
2374
|
-
if (!this.#seenDeprecations.has(diagnostic.text.toString())) {
|
|
2375
|
-
this.#fileView.addMessage(diagnosticText(diagnostic));
|
|
2376
|
-
this.#seenDeprecations.add(diagnostic.text.toString());
|
|
2377
|
-
}
|
|
2378
|
-
}
|
|
2379
|
-
break;
|
|
2380
|
-
}
|
|
2751
|
+
on([event, payload]) {
|
|
2752
|
+
switch (event) {
|
|
2381
2753
|
case "run:start":
|
|
2382
2754
|
this.#isFileViewExpanded = payload.result.tasks.length === 1 && this.resolvedConfig.watch !== true;
|
|
2383
2755
|
break;
|
|
@@ -2429,7 +2801,6 @@ class ListReporter extends BaseReporter {
|
|
|
2429
2801
|
this.#hasReportedError = true;
|
|
2430
2802
|
}
|
|
2431
2803
|
this.#fileView.clear();
|
|
2432
|
-
this.#seenDeprecations.clear();
|
|
2433
2804
|
break;
|
|
2434
2805
|
case "describe:start":
|
|
2435
2806
|
if (this.#isFileViewExpanded) {
|
|
@@ -2472,707 +2843,438 @@ class ListReporter extends BaseReporter {
|
|
|
2472
2843
|
case "expect:error":
|
|
2473
2844
|
case "expect:fail":
|
|
2474
2845
|
for (const diagnostic of payload.diagnostics) {
|
|
2475
|
-
this.#fileView.addMessage(diagnosticText(diagnostic));
|
|
2476
|
-
}
|
|
2477
|
-
break;
|
|
2478
|
-
}
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
|
-
|
|
2482
|
-
class SetupReporter {
|
|
2483
|
-
on([event, payload]) {
|
|
2484
|
-
if (event === "store:adds") {
|
|
2485
|
-
OutputService.writeMessage(addsPackageText(payload.packageVersion, payload.packagePath));
|
|
2486
|
-
return;
|
|
2487
|
-
}
|
|
2488
|
-
if ("diagnostics" in payload) {
|
|
2489
|
-
for (const diagnostic of payload.diagnostics) {
|
|
2490
|
-
switch (diagnostic.category) {
|
|
2491
|
-
case DiagnosticCategory.Error:
|
|
2492
|
-
OutputService.writeError(diagnosticText(diagnostic));
|
|
2493
|
-
break;
|
|
2494
|
-
case DiagnosticCategory.Warning:
|
|
2495
|
-
OutputService.writeWarning(diagnosticText(diagnostic));
|
|
2496
|
-
break;
|
|
2497
|
-
}
|
|
2498
|
-
}
|
|
2499
|
-
}
|
|
2500
|
-
}
|
|
2501
|
-
}
|
|
2502
|
-
|
|
2503
|
-
class SummaryReporter extends BaseReporter {
|
|
2504
|
-
on([event, payload]) {
|
|
2505
|
-
if (this.resolvedConfig.watch) {
|
|
2506
|
-
return;
|
|
2507
|
-
}
|
|
2508
|
-
if (event === "run:end") {
|
|
2509
|
-
OutputService.writeMessage(summaryText({
|
|
2510
|
-
duration: payload.result.timing.duration,
|
|
2511
|
-
expectCount: payload.result.expectCount,
|
|
2512
|
-
fileCount: payload.result.fileCount,
|
|
2513
|
-
onlyMatch: payload.result.resolvedConfig.only,
|
|
2514
|
-
pathMatch: payload.result.resolvedConfig.pathMatch,
|
|
2515
|
-
skipMatch: payload.result.resolvedConfig.skip,
|
|
2516
|
-
targetCount: payload.result.targetCount,
|
|
2517
|
-
testCount: payload.result.testCount,
|
|
2518
|
-
}));
|
|
2519
|
-
}
|
|
2520
|
-
}
|
|
2521
|
-
}
|
|
2522
|
-
|
|
2523
|
-
class WatchReporter extends BaseReporter {
|
|
2524
|
-
on([event, payload]) {
|
|
2525
|
-
switch (event) {
|
|
2526
|
-
case "run:start":
|
|
2527
|
-
OutputService.clearTerminal();
|
|
2528
|
-
break;
|
|
2529
|
-
case "run:end":
|
|
2530
|
-
OutputService.writeMessage(watchUsageText());
|
|
2531
|
-
break;
|
|
2532
|
-
case "watch:error":
|
|
2533
|
-
OutputService.clearTerminal();
|
|
2534
|
-
for (const diagnostic of payload.diagnostics) {
|
|
2535
|
-
OutputService.writeError(diagnosticText(diagnostic));
|
|
2536
|
-
}
|
|
2537
|
-
OutputService.writeMessage(waitingForFileChangesText());
|
|
2538
|
-
break;
|
|
2539
|
-
}
|
|
2540
|
-
}
|
|
2541
|
-
}
|
|
2542
|
-
|
|
2543
|
-
class Task {
|
|
2544
|
-
filePath;
|
|
2545
|
-
position;
|
|
2546
|
-
constructor(filePath, position) {
|
|
2547
|
-
this.filePath = Path.resolve(this.#toPath(filePath));
|
|
2548
|
-
this.position = position;
|
|
2549
|
-
}
|
|
2550
|
-
#toPath(filePath) {
|
|
2551
|
-
if (typeof filePath === "string" && !filePath.startsWith("file:")) {
|
|
2552
|
-
return filePath;
|
|
2553
|
-
}
|
|
2554
|
-
return fileURLToPath(filePath);
|
|
2555
|
-
}
|
|
2556
|
-
}
|
|
2557
|
-
|
|
2558
|
-
class CancellationToken {
|
|
2559
|
-
#isCancelled = false;
|
|
2560
|
-
#reason;
|
|
2561
|
-
get isCancellationRequested() {
|
|
2562
|
-
return this.#isCancelled;
|
|
2563
|
-
}
|
|
2564
|
-
get reason() {
|
|
2565
|
-
return this.#reason;
|
|
2566
|
-
}
|
|
2567
|
-
cancel(reason) {
|
|
2568
|
-
if (!this.#isCancelled) {
|
|
2569
|
-
this.#isCancelled = true;
|
|
2570
|
-
this.#reason = reason;
|
|
2571
|
-
}
|
|
2572
|
-
}
|
|
2573
|
-
reset() {
|
|
2574
|
-
if (this.#isCancelled) {
|
|
2575
|
-
this.#isCancelled = false;
|
|
2576
|
-
this.#reason = undefined;
|
|
2577
|
-
}
|
|
2578
|
-
}
|
|
2579
|
-
}
|
|
2580
|
-
|
|
2581
|
-
var CancellationReason;
|
|
2582
|
-
(function (CancellationReason) {
|
|
2583
|
-
CancellationReason["ConfigChange"] = "configChange";
|
|
2584
|
-
CancellationReason["ConfigError"] = "configError";
|
|
2585
|
-
CancellationReason["FailFast"] = "failFast";
|
|
2586
|
-
CancellationReason["WatchClose"] = "watchClose";
|
|
2587
|
-
})(CancellationReason || (CancellationReason = {}));
|
|
2588
|
-
|
|
2589
|
-
class Watcher {
|
|
2590
|
-
#onChanged;
|
|
2591
|
-
#onRemoved;
|
|
2592
|
-
#recursive;
|
|
2593
|
-
#targetPath;
|
|
2594
|
-
#watcher;
|
|
2595
|
-
constructor(targetPath, onChanged, onRemoved, options) {
|
|
2596
|
-
this.#targetPath = targetPath;
|
|
2597
|
-
this.#onChanged = onChanged;
|
|
2598
|
-
this.#onRemoved = onRemoved ?? onChanged;
|
|
2599
|
-
this.#recursive = options?.recursive;
|
|
2600
|
-
}
|
|
2601
|
-
close() {
|
|
2602
|
-
this.#watcher?.close();
|
|
2603
|
-
}
|
|
2604
|
-
watch() {
|
|
2605
|
-
this.#watcher = watch(this.#targetPath, { recursive: this.#recursive }, (_eventType, fileName) => {
|
|
2606
|
-
if (fileName != null) {
|
|
2607
|
-
const filePath = Path.resolve(this.#targetPath, fileName);
|
|
2608
|
-
if (existsSync(filePath)) {
|
|
2609
|
-
this.#onChanged(filePath);
|
|
2610
|
-
}
|
|
2611
|
-
else {
|
|
2612
|
-
this.#onRemoved(filePath);
|
|
2613
|
-
}
|
|
2614
|
-
}
|
|
2615
|
-
});
|
|
2616
|
-
}
|
|
2617
|
-
}
|
|
2618
|
-
|
|
2619
|
-
class FileWatcher extends Watcher {
|
|
2620
|
-
constructor(targetPath, onChanged) {
|
|
2621
|
-
const onChangedFile = (filePath) => {
|
|
2622
|
-
if (filePath === targetPath) {
|
|
2623
|
-
onChanged();
|
|
2624
|
-
}
|
|
2625
|
-
};
|
|
2626
|
-
super(Path.dirname(targetPath), onChangedFile);
|
|
2627
|
-
}
|
|
2628
|
-
}
|
|
2629
|
-
|
|
2630
|
-
class InputService {
|
|
2631
|
-
#onInput;
|
|
2632
|
-
#stdin = process.stdin;
|
|
2633
|
-
constructor(onInput) {
|
|
2634
|
-
this.#onInput = onInput;
|
|
2635
|
-
this.#stdin.setRawMode?.(true);
|
|
2636
|
-
this.#stdin.setEncoding("utf8");
|
|
2637
|
-
this.#stdin.unref();
|
|
2638
|
-
this.#stdin.addListener("data", this.#onInput);
|
|
2639
|
-
}
|
|
2640
|
-
close() {
|
|
2641
|
-
this.#stdin.removeListener("data", this.#onInput);
|
|
2642
|
-
this.#stdin.setRawMode?.(false);
|
|
2643
|
-
}
|
|
2644
|
-
}
|
|
2645
|
-
|
|
2646
|
-
class GlobPattern {
|
|
2647
|
-
static #reservedCharacterRegex = /[^\w\s/]/g;
|
|
2648
|
-
static #parse(pattern, usageTarget) {
|
|
2649
|
-
const segments = pattern.split("/");
|
|
2650
|
-
let resultPattern = "\\.";
|
|
2651
|
-
let optionalSegmentCount = 0;
|
|
2652
|
-
for (const segment of segments) {
|
|
2653
|
-
if (segment === ".") {
|
|
2654
|
-
continue;
|
|
2655
|
-
}
|
|
2656
|
-
if (segment === "**") {
|
|
2657
|
-
resultPattern += "(\\/(?!(node_modules)(\\/|$))[^./][^/]*)*?";
|
|
2658
|
-
continue;
|
|
2659
|
-
}
|
|
2660
|
-
if (usageTarget === "directories") {
|
|
2661
|
-
resultPattern += "(";
|
|
2662
|
-
optionalSegmentCount++;
|
|
2663
|
-
}
|
|
2664
|
-
resultPattern += "\\/";
|
|
2665
|
-
const segmentPattern = segment.replace(GlobPattern.#reservedCharacterRegex, GlobPattern.#replaceReservedCharacter);
|
|
2666
|
-
if (segmentPattern !== segment) {
|
|
2667
|
-
resultPattern += "(?!(node_modules)(\\/|$))";
|
|
2668
|
-
}
|
|
2669
|
-
resultPattern += segmentPattern;
|
|
2670
|
-
}
|
|
2671
|
-
resultPattern += ")?".repeat(optionalSegmentCount);
|
|
2672
|
-
return resultPattern;
|
|
2673
|
-
}
|
|
2674
|
-
static #replaceReservedCharacter(match, offset) {
|
|
2675
|
-
switch (match) {
|
|
2676
|
-
case "*":
|
|
2677
|
-
return offset === 0 ? "([^./][^/]*)?" : "([^/]*)?";
|
|
2678
|
-
case "?":
|
|
2679
|
-
return offset === 0 ? "[^./]" : "[^/]";
|
|
2680
|
-
default:
|
|
2681
|
-
return `\\${match}`;
|
|
2682
|
-
}
|
|
2683
|
-
}
|
|
2684
|
-
static toRegex(patterns, target) {
|
|
2685
|
-
const patternText = patterns.map((pattern) => `(${GlobPattern.#parse(pattern, target)})`).join("|");
|
|
2686
|
-
return new RegExp(`^(${patternText})$`);
|
|
2687
|
-
}
|
|
2688
|
-
}
|
|
2689
|
-
|
|
2690
|
-
class SelectDiagnosticText {
|
|
2691
|
-
static #pathSelectOptions(resolvedConfig) {
|
|
2692
|
-
const text = [
|
|
2693
|
-
`Root path: ${resolvedConfig.rootPath}`,
|
|
2694
|
-
`Test file match: ${resolvedConfig.testFileMatch.join(", ")}`,
|
|
2695
|
-
];
|
|
2696
|
-
if (resolvedConfig.pathMatch.length > 0) {
|
|
2697
|
-
text.push(`Path match: ${resolvedConfig.pathMatch.join(", ")}`);
|
|
2698
|
-
}
|
|
2699
|
-
return text;
|
|
2700
|
-
}
|
|
2701
|
-
static noTestFilesWereLeft(resolvedConfig) {
|
|
2702
|
-
return [
|
|
2703
|
-
"No test files were left to run using current configuration.",
|
|
2704
|
-
...SelectDiagnosticText.#pathSelectOptions(resolvedConfig),
|
|
2705
|
-
];
|
|
2706
|
-
}
|
|
2707
|
-
static noTestFilesWereSelected(resolvedConfig) {
|
|
2708
|
-
return [
|
|
2709
|
-
"No test files were selected using current configuration.",
|
|
2710
|
-
...SelectDiagnosticText.#pathSelectOptions(resolvedConfig),
|
|
2711
|
-
];
|
|
2712
|
-
}
|
|
2713
|
-
}
|
|
2714
|
-
|
|
2715
|
-
class Select {
|
|
2716
|
-
static #patternsCache = new WeakMap();
|
|
2717
|
-
static async #getAccessibleFileSystemEntries(targetPath) {
|
|
2718
|
-
const directories = [];
|
|
2719
|
-
const files = [];
|
|
2720
|
-
try {
|
|
2721
|
-
const entries = await fs.readdir(targetPath, { withFileTypes: true });
|
|
2722
|
-
for (const entry of entries) {
|
|
2723
|
-
let entryMeta = entry;
|
|
2724
|
-
if (entry.isSymbolicLink()) {
|
|
2725
|
-
entryMeta = await fs.stat([targetPath, entry.name].join("/"));
|
|
2726
|
-
}
|
|
2727
|
-
if (entryMeta.isDirectory()) {
|
|
2728
|
-
directories.push(entry.name);
|
|
2846
|
+
this.#fileView.addMessage(diagnosticText(diagnostic));
|
|
2729
2847
|
}
|
|
2730
|
-
|
|
2731
|
-
|
|
2848
|
+
break;
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
class SetupReporter {
|
|
2854
|
+
on([event, payload]) {
|
|
2855
|
+
if (event === "store:adds") {
|
|
2856
|
+
OutputService.writeMessage(addsPackageText(payload.packageVersion, payload.packagePath));
|
|
2857
|
+
return;
|
|
2858
|
+
}
|
|
2859
|
+
if ("diagnostics" in payload) {
|
|
2860
|
+
for (const diagnostic of payload.diagnostics) {
|
|
2861
|
+
switch (diagnostic.category) {
|
|
2862
|
+
case DiagnosticCategory.Error:
|
|
2863
|
+
OutputService.writeError(diagnosticText(diagnostic));
|
|
2864
|
+
break;
|
|
2865
|
+
case DiagnosticCategory.Warning:
|
|
2866
|
+
OutputService.writeWarning(diagnosticText(diagnostic));
|
|
2867
|
+
break;
|
|
2732
2868
|
}
|
|
2733
2869
|
}
|
|
2734
2870
|
}
|
|
2735
|
-
catch {
|
|
2736
|
-
}
|
|
2737
|
-
return { directories, files };
|
|
2738
2871
|
}
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
};
|
|
2746
|
-
Select.#patternsCache.set(globPatterns, matchPatterns);
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
class SummaryReporter extends BaseReporter {
|
|
2875
|
+
on([event, payload]) {
|
|
2876
|
+
if (this.resolvedConfig.watch) {
|
|
2877
|
+
return;
|
|
2747
2878
|
}
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2879
|
+
if (event === "run:end") {
|
|
2880
|
+
OutputService.writeMessage(summaryText({
|
|
2881
|
+
duration: payload.result.timing.duration,
|
|
2882
|
+
expectCount: payload.result.expectCount,
|
|
2883
|
+
fileCount: payload.result.fileCount,
|
|
2884
|
+
onlyMatch: payload.result.resolvedConfig.only,
|
|
2885
|
+
pathMatch: payload.result.resolvedConfig.pathMatch,
|
|
2886
|
+
skipMatch: payload.result.resolvedConfig.skip,
|
|
2887
|
+
targetCount: payload.result.targetCount,
|
|
2888
|
+
testCount: payload.result.testCount,
|
|
2889
|
+
}));
|
|
2757
2890
|
}
|
|
2758
|
-
return matchPatterns.includedFile.test(filePath);
|
|
2759
|
-
}
|
|
2760
|
-
static isTestFile(filePath, resolvedConfig) {
|
|
2761
|
-
const matchPatterns = Select.#getMatchPatterns(resolvedConfig.testFileMatch);
|
|
2762
|
-
return Select.#isFileIncluded(Path.relative(resolvedConfig.rootPath, filePath), matchPatterns, resolvedConfig);
|
|
2763
|
-
}
|
|
2764
|
-
static #onDiagnostics(diagnostic) {
|
|
2765
|
-
EventEmitter.dispatch(["select:error", { diagnostics: [diagnostic] }]);
|
|
2766
2891
|
}
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
class WatchReporter extends BaseReporter {
|
|
2895
|
+
on([event, payload]) {
|
|
2896
|
+
switch (event) {
|
|
2897
|
+
case "run:start":
|
|
2898
|
+
OutputService.clearTerminal();
|
|
2899
|
+
break;
|
|
2900
|
+
case "run:end":
|
|
2901
|
+
OutputService.writeMessage(watchUsageText());
|
|
2902
|
+
break;
|
|
2903
|
+
case "watch:error":
|
|
2904
|
+
OutputService.clearTerminal();
|
|
2905
|
+
for (const diagnostic of payload.diagnostics) {
|
|
2906
|
+
OutputService.writeError(diagnosticText(diagnostic));
|
|
2907
|
+
}
|
|
2908
|
+
OutputService.writeMessage(waitingForFileChangesText());
|
|
2909
|
+
break;
|
|
2773
2910
|
}
|
|
2774
|
-
return testFilePaths.sort();
|
|
2775
2911
|
}
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
testFilePaths.push([targetPath, fileName].join("/"));
|
|
2789
|
-
}
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
class Task {
|
|
2915
|
+
filePath;
|
|
2916
|
+
position;
|
|
2917
|
+
constructor(filePath, position) {
|
|
2918
|
+
this.filePath = Path.resolve(this.#toPath(filePath));
|
|
2919
|
+
this.position = position;
|
|
2920
|
+
}
|
|
2921
|
+
#toPath(filePath) {
|
|
2922
|
+
if (typeof filePath === "string" && !filePath.startsWith("file:")) {
|
|
2923
|
+
return filePath;
|
|
2790
2924
|
}
|
|
2925
|
+
return fileURLToPath(filePath);
|
|
2791
2926
|
}
|
|
2792
2927
|
}
|
|
2793
2928
|
|
|
2794
|
-
class
|
|
2795
|
-
#
|
|
2796
|
-
#
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
constructor(delay, onResolve) {
|
|
2800
|
-
this.#delay = delay;
|
|
2801
|
-
this.#onResolve = onResolve;
|
|
2802
|
-
}
|
|
2803
|
-
clearTimeout() {
|
|
2804
|
-
clearTimeout(this.#timeout);
|
|
2929
|
+
class CancellationToken {
|
|
2930
|
+
#isCancelled = false;
|
|
2931
|
+
#reason;
|
|
2932
|
+
get isCancellationRequested() {
|
|
2933
|
+
return this.#isCancelled;
|
|
2805
2934
|
}
|
|
2806
|
-
|
|
2807
|
-
this
|
|
2808
|
-
this.#timeout = setTimeout(() => {
|
|
2809
|
-
this.#resolve?.(this.#onResolve());
|
|
2810
|
-
}, this.#delay);
|
|
2935
|
+
get reason() {
|
|
2936
|
+
return this.#reason;
|
|
2811
2937
|
}
|
|
2812
|
-
|
|
2813
|
-
this.#
|
|
2938
|
+
cancel(reason) {
|
|
2939
|
+
if (!this.#isCancelled) {
|
|
2940
|
+
this.#isCancelled = true;
|
|
2941
|
+
this.#reason = reason;
|
|
2942
|
+
}
|
|
2814
2943
|
}
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
this.#
|
|
2818
|
-
|
|
2944
|
+
reset() {
|
|
2945
|
+
if (this.#isCancelled) {
|
|
2946
|
+
this.#isCancelled = false;
|
|
2947
|
+
this.#reason = undefined;
|
|
2948
|
+
}
|
|
2819
2949
|
}
|
|
2820
2950
|
}
|
|
2821
2951
|
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2952
|
+
var CancellationReason;
|
|
2953
|
+
(function (CancellationReason) {
|
|
2954
|
+
CancellationReason["ConfigChange"] = "configChange";
|
|
2955
|
+
CancellationReason["ConfigError"] = "configError";
|
|
2956
|
+
CancellationReason["FailFast"] = "failFast";
|
|
2957
|
+
CancellationReason["WatchClose"] = "watchClose";
|
|
2958
|
+
})(CancellationReason || (CancellationReason = {}));
|
|
2959
|
+
|
|
2960
|
+
class Watcher {
|
|
2961
|
+
#onChanged;
|
|
2962
|
+
#onRemoved;
|
|
2963
|
+
#recursive;
|
|
2964
|
+
#targetPath;
|
|
2965
|
+
#watcher;
|
|
2966
|
+
constructor(targetPath, onChanged, onRemoved, options) {
|
|
2967
|
+
this.#targetPath = targetPath;
|
|
2968
|
+
this.#onChanged = onChanged;
|
|
2969
|
+
this.#onRemoved = onRemoved ?? onChanged;
|
|
2970
|
+
this.#recursive = options?.recursive;
|
|
2831
2971
|
}
|
|
2832
|
-
|
|
2833
|
-
|
|
2972
|
+
close() {
|
|
2973
|
+
this.#watcher?.close();
|
|
2834
2974
|
}
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
this.#inputService?.close();
|
|
2845
|
-
for (const watcher of this.#watchers) {
|
|
2846
|
-
watcher.close();
|
|
2847
|
-
}
|
|
2848
|
-
cancellationToken.cancel(reason);
|
|
2849
|
-
debounce.resolveWith([]);
|
|
2850
|
-
};
|
|
2851
|
-
if (!environmentOptions.noInteractive) {
|
|
2852
|
-
const onInput = (chunk) => {
|
|
2853
|
-
switch (chunk.toLowerCase()) {
|
|
2854
|
-
case "\u0003":
|
|
2855
|
-
case "\u0004":
|
|
2856
|
-
case "\u001B":
|
|
2857
|
-
case "q":
|
|
2858
|
-
case "x":
|
|
2859
|
-
onClose(CancellationReason.WatchClose);
|
|
2860
|
-
break;
|
|
2861
|
-
case "\u000D":
|
|
2862
|
-
case "\u0020":
|
|
2863
|
-
case "a":
|
|
2864
|
-
debounce.clearTimeout();
|
|
2865
|
-
if (this.#watchedTestFiles.size > 0) {
|
|
2866
|
-
debounce.resolveWith([...this.#watchedTestFiles.values()]);
|
|
2867
|
-
}
|
|
2868
|
-
break;
|
|
2975
|
+
watch() {
|
|
2976
|
+
this.#watcher = watch(this.#targetPath, { recursive: this.#recursive }, (_eventType, fileName) => {
|
|
2977
|
+
if (fileName != null) {
|
|
2978
|
+
const filePath = Path.resolve(this.#targetPath, fileName);
|
|
2979
|
+
if (existsSync(filePath)) {
|
|
2980
|
+
this.#onChanged(filePath);
|
|
2981
|
+
}
|
|
2982
|
+
else {
|
|
2983
|
+
this.#onRemoved(filePath);
|
|
2869
2984
|
}
|
|
2870
|
-
};
|
|
2871
|
-
this.#inputService = new InputService(onInput);
|
|
2872
|
-
}
|
|
2873
|
-
const onChangedFile = (filePath) => {
|
|
2874
|
-
debounce.refreshTimeout();
|
|
2875
|
-
let task = this.#watchedTestFiles.get(filePath);
|
|
2876
|
-
if (task != null) {
|
|
2877
|
-
this.#changedTestFiles.set(filePath, task);
|
|
2878
|
-
}
|
|
2879
|
-
else if (Select.isTestFile(filePath, this.#resolvedConfig)) {
|
|
2880
|
-
task = new Task(filePath);
|
|
2881
|
-
this.#changedTestFiles.set(filePath, task);
|
|
2882
|
-
this.#watchedTestFiles.set(filePath, task);
|
|
2883
|
-
}
|
|
2884
|
-
};
|
|
2885
|
-
const onRemovedFile = (filePath) => {
|
|
2886
|
-
this.#changedTestFiles.delete(filePath);
|
|
2887
|
-
this.#watchedTestFiles.delete(filePath);
|
|
2888
|
-
if (this.#watchedTestFiles.size === 0) {
|
|
2889
|
-
debounce.clearTimeout();
|
|
2890
|
-
this.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereLeft(this.#resolvedConfig)));
|
|
2891
2985
|
}
|
|
2892
|
-
};
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
while (!cancellationToken.isCancellationRequested) {
|
|
2902
|
-
const testFiles = await debounce.setup();
|
|
2903
|
-
if (testFiles.length > 0) {
|
|
2904
|
-
yield testFiles;
|
|
2986
|
+
});
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
class FileWatcher extends Watcher {
|
|
2991
|
+
constructor(targetPath, onChanged) {
|
|
2992
|
+
const onChangedFile = (filePath) => {
|
|
2993
|
+
if (filePath === targetPath) {
|
|
2994
|
+
onChanged();
|
|
2905
2995
|
}
|
|
2906
|
-
}
|
|
2996
|
+
};
|
|
2997
|
+
super(Path.dirname(targetPath), onChangedFile);
|
|
2907
2998
|
}
|
|
2908
2999
|
}
|
|
2909
3000
|
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
3001
|
+
class InputService {
|
|
3002
|
+
#onInput;
|
|
3003
|
+
#stdin = process.stdin;
|
|
3004
|
+
constructor(onInput) {
|
|
3005
|
+
this.#onInput = onInput;
|
|
3006
|
+
this.#stdin.setRawMode?.(true);
|
|
3007
|
+
this.#stdin.setEncoding("utf8");
|
|
3008
|
+
this.#stdin.unref();
|
|
3009
|
+
this.#stdin.addListener("data", this.#onInput);
|
|
3010
|
+
}
|
|
3011
|
+
close() {
|
|
3012
|
+
this.#stdin.removeListener("data", this.#onInput);
|
|
3013
|
+
this.#stdin.setRawMode?.(false);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
2916
3016
|
|
|
2917
|
-
class
|
|
2918
|
-
|
|
2919
|
-
#
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
const blockEnd = node.arguments[1].body.getEnd();
|
|
2940
|
-
for (const diagnostic of parent.diagnostics) {
|
|
2941
|
-
if (diagnostic.start != null && diagnostic.start >= blockStart && diagnostic.start <= blockEnd) {
|
|
2942
|
-
this.diagnostics.add(diagnostic);
|
|
2943
|
-
parent.diagnostics.delete(diagnostic);
|
|
2944
|
-
}
|
|
3017
|
+
class GlobPattern {
|
|
3018
|
+
static #reservedCharacterRegex = /[^\w\s/]/g;
|
|
3019
|
+
static #parse(pattern, usageTarget) {
|
|
3020
|
+
const segments = pattern.split("/");
|
|
3021
|
+
let resultPattern = "\\.";
|
|
3022
|
+
let optionalSegmentCount = 0;
|
|
3023
|
+
for (const segment of segments) {
|
|
3024
|
+
if (segment === ".") {
|
|
3025
|
+
continue;
|
|
3026
|
+
}
|
|
3027
|
+
if (segment === "**") {
|
|
3028
|
+
resultPattern += "(\\/(?!(node_modules)(\\/|$))[^./][^/]*)*?";
|
|
3029
|
+
continue;
|
|
3030
|
+
}
|
|
3031
|
+
if (usageTarget === "directories") {
|
|
3032
|
+
resultPattern += "(";
|
|
3033
|
+
optionalSegmentCount++;
|
|
3034
|
+
}
|
|
3035
|
+
resultPattern += "\\/";
|
|
3036
|
+
const segmentPattern = segment.replace(GlobPattern.#reservedCharacterRegex, GlobPattern.#replaceReservedCharacter);
|
|
3037
|
+
if (segmentPattern !== segment) {
|
|
3038
|
+
resultPattern += "(?!(node_modules)(\\/|$))";
|
|
2945
3039
|
}
|
|
3040
|
+
resultPattern += segmentPattern;
|
|
2946
3041
|
}
|
|
3042
|
+
resultPattern += ")?".repeat(optionalSegmentCount);
|
|
3043
|
+
return resultPattern;
|
|
2947
3044
|
}
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
};
|
|
2957
|
-
switch (this.brand) {
|
|
2958
|
-
case TestMemberBrand.Describe:
|
|
2959
|
-
for (const member of this.members) {
|
|
2960
|
-
if (member.brand === TestMemberBrand.Expect) {
|
|
2961
|
-
diagnostics.push(Diagnostic.error(getText(member.node), DiagnosticOrigin.fromNode(getParentCallExpression(member.node))));
|
|
2962
|
-
}
|
|
2963
|
-
}
|
|
2964
|
-
break;
|
|
2965
|
-
case TestMemberBrand.Test:
|
|
2966
|
-
case TestMemberBrand.Expect:
|
|
2967
|
-
for (const member of this.members) {
|
|
2968
|
-
if (member.brand !== TestMemberBrand.Expect) {
|
|
2969
|
-
diagnostics.push(Diagnostic.error(getText(member.node), DiagnosticOrigin.fromNode(member.node)));
|
|
2970
|
-
}
|
|
2971
|
-
}
|
|
2972
|
-
break;
|
|
3045
|
+
static #replaceReservedCharacter(match, offset) {
|
|
3046
|
+
switch (match) {
|
|
3047
|
+
case "*":
|
|
3048
|
+
return offset === 0 ? "([^./][^/]*)?" : "([^/]*)?";
|
|
3049
|
+
case "?":
|
|
3050
|
+
return offset === 0 ? "[^./]" : "[^/]";
|
|
3051
|
+
default:
|
|
3052
|
+
return `\\${match}`;
|
|
2973
3053
|
}
|
|
2974
|
-
|
|
3054
|
+
}
|
|
3055
|
+
static toRegex(patterns, target) {
|
|
3056
|
+
const patternText = patterns.map((pattern) => `(${GlobPattern.#parse(pattern, target)})`).join("|");
|
|
3057
|
+
return new RegExp(`^(${patternText})$`);
|
|
2975
3058
|
}
|
|
2976
3059
|
}
|
|
2977
3060
|
|
|
2978
|
-
class
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
constructor(compiler, brand, node, parent, flags, matcherNode, modifierNode, notNode) {
|
|
2987
|
-
super(compiler, brand, node, parent, flags);
|
|
2988
|
-
this.isNot = notNode != null;
|
|
2989
|
-
this.matcherName = matcherNode.expression.name;
|
|
2990
|
-
this.matcherNode = matcherNode;
|
|
2991
|
-
this.modifierNode = modifierNode;
|
|
2992
|
-
this.source = this.node.typeArguments ?? this.node.arguments;
|
|
2993
|
-
this.target = this.matcherNode.typeArguments ?? this.matcherNode.arguments;
|
|
2994
|
-
for (const diagnostic of parent.diagnostics) {
|
|
2995
|
-
if (diagnostic.start != null && diagnostic.start >= this.source.pos && diagnostic.start <= this.source.end) {
|
|
2996
|
-
this.diagnostics.add(diagnostic);
|
|
2997
|
-
parent.diagnostics.delete(diagnostic);
|
|
2998
|
-
}
|
|
3061
|
+
class SelectDiagnosticText {
|
|
3062
|
+
static #pathSelectOptions(resolvedConfig) {
|
|
3063
|
+
const text = [
|
|
3064
|
+
`Root path: ${resolvedConfig.rootPath}`,
|
|
3065
|
+
`Test file match: ${resolvedConfig.testFileMatch.join(", ")}`,
|
|
3066
|
+
];
|
|
3067
|
+
if (resolvedConfig.pathMatch.length > 0) {
|
|
3068
|
+
text.push(`Path match: ${resolvedConfig.pathMatch.join(", ")}`);
|
|
2999
3069
|
}
|
|
3070
|
+
return text;
|
|
3071
|
+
}
|
|
3072
|
+
static noTestFilesWereLeft(resolvedConfig) {
|
|
3073
|
+
return [
|
|
3074
|
+
"No test files were left to run using current configuration.",
|
|
3075
|
+
...SelectDiagnosticText.#pathSelectOptions(resolvedConfig),
|
|
3076
|
+
];
|
|
3077
|
+
}
|
|
3078
|
+
static noTestFilesWereSelected(resolvedConfig) {
|
|
3079
|
+
return [
|
|
3080
|
+
"No test files were selected using current configuration.",
|
|
3081
|
+
...SelectDiagnosticText.#pathSelectOptions(resolvedConfig),
|
|
3082
|
+
];
|
|
3000
3083
|
}
|
|
3001
3084
|
}
|
|
3002
3085
|
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
describe: undefined,
|
|
3021
|
-
expect: undefined,
|
|
3022
|
-
it: undefined,
|
|
3023
|
-
namespace: undefined,
|
|
3024
|
-
test: undefined,
|
|
3025
|
-
},
|
|
3026
|
-
namespace: undefined,
|
|
3027
|
-
};
|
|
3028
|
-
}
|
|
3029
|
-
handleImportDeclaration(node) {
|
|
3030
|
-
if (this.#moduleSpecifiers.includes(node.moduleSpecifier.getText()) &&
|
|
3031
|
-
node.importClause?.isTypeOnly !== true &&
|
|
3032
|
-
node.importClause?.namedBindings != null) {
|
|
3033
|
-
if (this.#compiler.isNamedImports(node.importClause.namedBindings)) {
|
|
3034
|
-
for (const element of node.importClause.namedBindings.elements) {
|
|
3035
|
-
if (element.isTypeOnly) {
|
|
3036
|
-
continue;
|
|
3037
|
-
}
|
|
3038
|
-
let identifierKey;
|
|
3039
|
-
if (element.propertyName) {
|
|
3040
|
-
identifierKey = element.propertyName.getText();
|
|
3041
|
-
}
|
|
3042
|
-
else {
|
|
3043
|
-
identifierKey = element.name.getText();
|
|
3044
|
-
}
|
|
3045
|
-
if (identifierKey in this.#identifiers.namedImports) {
|
|
3046
|
-
this.#identifiers.namedImports[identifierKey] = element.name.getText();
|
|
3047
|
-
}
|
|
3086
|
+
class Select {
|
|
3087
|
+
static #patternsCache = new WeakMap();
|
|
3088
|
+
static async #getAccessibleFileSystemEntries(targetPath) {
|
|
3089
|
+
const directories = [];
|
|
3090
|
+
const files = [];
|
|
3091
|
+
try {
|
|
3092
|
+
const entries = await fs.readdir(targetPath, { withFileTypes: true });
|
|
3093
|
+
for (const entry of entries) {
|
|
3094
|
+
let entryMeta = entry;
|
|
3095
|
+
if (entry.isSymbolicLink()) {
|
|
3096
|
+
entryMeta = await fs.stat([targetPath, entry.name].join("/"));
|
|
3097
|
+
}
|
|
3098
|
+
if (entryMeta.isDirectory()) {
|
|
3099
|
+
directories.push(entry.name);
|
|
3100
|
+
}
|
|
3101
|
+
else if (entryMeta.isFile()) {
|
|
3102
|
+
files.push(entry.name);
|
|
3048
3103
|
}
|
|
3049
3104
|
}
|
|
3050
|
-
if (this.#compiler.isNamespaceImport(node.importClause.namedBindings)) {
|
|
3051
|
-
this.#identifiers.namespace = node.importClause.namedBindings.name.getText();
|
|
3052
|
-
}
|
|
3053
3105
|
}
|
|
3106
|
+
catch {
|
|
3107
|
+
}
|
|
3108
|
+
return { directories, files };
|
|
3054
3109
|
}
|
|
3055
|
-
|
|
3056
|
-
let
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
}
|
|
3062
|
-
|
|
3063
|
-
case "fail":
|
|
3064
|
-
flags |= TestMemberFlags.Fail;
|
|
3065
|
-
break;
|
|
3066
|
-
case "only":
|
|
3067
|
-
flags |= TestMemberFlags.Only;
|
|
3068
|
-
break;
|
|
3069
|
-
case "skip":
|
|
3070
|
-
flags |= TestMemberFlags.Skip;
|
|
3071
|
-
break;
|
|
3072
|
-
case "todo":
|
|
3073
|
-
flags |= TestMemberFlags.Todo;
|
|
3074
|
-
break;
|
|
3075
|
-
}
|
|
3076
|
-
expression = expression.expression;
|
|
3110
|
+
static #getMatchPatterns(globPatterns) {
|
|
3111
|
+
let matchPatterns = Select.#patternsCache.get(globPatterns);
|
|
3112
|
+
if (!matchPatterns) {
|
|
3113
|
+
matchPatterns = {
|
|
3114
|
+
includedDirectory: GlobPattern.toRegex(globPatterns, "directories"),
|
|
3115
|
+
includedFile: GlobPattern.toRegex(globPatterns, "files"),
|
|
3116
|
+
};
|
|
3117
|
+
Select.#patternsCache.set(globPatterns, matchPatterns);
|
|
3077
3118
|
}
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3119
|
+
return matchPatterns;
|
|
3120
|
+
}
|
|
3121
|
+
static #isDirectoryIncluded(directoryPath, matchPatterns) {
|
|
3122
|
+
return matchPatterns.includedDirectory.test(directoryPath);
|
|
3123
|
+
}
|
|
3124
|
+
static #isFileIncluded(filePath, matchPatterns, resolvedConfig) {
|
|
3125
|
+
if (resolvedConfig.pathMatch.length > 0 &&
|
|
3126
|
+
!resolvedConfig.pathMatch.some((match) => filePath.toLowerCase().includes(match.toLowerCase()))) {
|
|
3127
|
+
return false;
|
|
3082
3128
|
}
|
|
3083
|
-
|
|
3084
|
-
|
|
3129
|
+
return matchPatterns.includedFile.test(filePath);
|
|
3130
|
+
}
|
|
3131
|
+
static isTestFile(filePath, resolvedConfig) {
|
|
3132
|
+
const matchPatterns = Select.#getMatchPatterns(resolvedConfig.testFileMatch);
|
|
3133
|
+
return Select.#isFileIncluded(Path.relative(resolvedConfig.rootPath, filePath), matchPatterns, resolvedConfig);
|
|
3134
|
+
}
|
|
3135
|
+
static #onDiagnostics(diagnostic) {
|
|
3136
|
+
EventEmitter.dispatch(["select:error", { diagnostics: [diagnostic] }]);
|
|
3137
|
+
}
|
|
3138
|
+
static async selectFiles(resolvedConfig) {
|
|
3139
|
+
const matchPatterns = Select.#getMatchPatterns(resolvedConfig.testFileMatch);
|
|
3140
|
+
const testFilePaths = [];
|
|
3141
|
+
await Select.#visitDirectory(".", testFilePaths, matchPatterns, resolvedConfig);
|
|
3142
|
+
if (testFilePaths.length === 0) {
|
|
3143
|
+
Select.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereSelected(resolvedConfig)));
|
|
3085
3144
|
}
|
|
3086
|
-
|
|
3087
|
-
|
|
3145
|
+
return testFilePaths.sort();
|
|
3146
|
+
}
|
|
3147
|
+
static async #visitDirectory(currentPath, testFilePaths, matchPatterns, resolvedConfig) {
|
|
3148
|
+
const targetPath = Path.join(resolvedConfig.rootPath, currentPath);
|
|
3149
|
+
const entries = await Select.#getAccessibleFileSystemEntries(targetPath);
|
|
3150
|
+
for (const directoryName of entries.directories) {
|
|
3151
|
+
const directoryPath = [currentPath, directoryName].join("/");
|
|
3152
|
+
if (Select.#isDirectoryIncluded(directoryPath, matchPatterns)) {
|
|
3153
|
+
await Select.#visitDirectory(directoryPath, testFilePaths, matchPatterns, resolvedConfig);
|
|
3154
|
+
}
|
|
3088
3155
|
}
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
return { brand: TestMemberBrand.Test, flags };
|
|
3095
|
-
case "expect":
|
|
3096
|
-
return { brand: TestMemberBrand.Expect, flags };
|
|
3156
|
+
for (const fileName of entries.files) {
|
|
3157
|
+
const filePath = [currentPath, fileName].join("/");
|
|
3158
|
+
if (Select.#isFileIncluded(filePath, matchPatterns, resolvedConfig)) {
|
|
3159
|
+
testFilePaths.push([targetPath, fileName].join("/"));
|
|
3160
|
+
}
|
|
3097
3161
|
}
|
|
3098
|
-
return;
|
|
3099
3162
|
}
|
|
3100
3163
|
}
|
|
3101
3164
|
|
|
3102
|
-
class
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
this
|
|
3165
|
+
class Debounce {
|
|
3166
|
+
#delay;
|
|
3167
|
+
#onResolve;
|
|
3168
|
+
#resolve;
|
|
3169
|
+
#timeout;
|
|
3170
|
+
constructor(delay, onResolve) {
|
|
3171
|
+
this.#delay = delay;
|
|
3172
|
+
this.#onResolve = onResolve;
|
|
3109
3173
|
}
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3174
|
+
cancel() {
|
|
3175
|
+
clearTimeout(this.#timeout);
|
|
3176
|
+
}
|
|
3177
|
+
refresh() {
|
|
3178
|
+
this.cancel();
|
|
3179
|
+
this.#timeout = setTimeout(() => {
|
|
3180
|
+
this.#resolve?.(this.#onResolve());
|
|
3181
|
+
}, this.#delay);
|
|
3182
|
+
}
|
|
3183
|
+
resolve(value) {
|
|
3184
|
+
this.#resolve?.(value);
|
|
3185
|
+
}
|
|
3186
|
+
schedule() {
|
|
3187
|
+
return new Promise((resolve) => {
|
|
3188
|
+
this.#resolve = resolve;
|
|
3189
|
+
});
|
|
3115
3190
|
}
|
|
3116
3191
|
}
|
|
3117
3192
|
|
|
3118
|
-
class
|
|
3119
|
-
#
|
|
3120
|
-
|
|
3121
|
-
|
|
3193
|
+
class WatchService {
|
|
3194
|
+
#changedTestFiles = new Map();
|
|
3195
|
+
#inputService;
|
|
3196
|
+
#resolvedConfig;
|
|
3197
|
+
#watchedTestFiles;
|
|
3198
|
+
#watchers = [];
|
|
3199
|
+
constructor(resolvedConfig, tasks) {
|
|
3200
|
+
this.#resolvedConfig = resolvedConfig;
|
|
3201
|
+
this.#watchedTestFiles = new Map(tasks.map((task) => [task.filePath, task]));
|
|
3122
3202
|
}
|
|
3123
|
-
#
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3203
|
+
#onDiagnostics(diagnostic) {
|
|
3204
|
+
EventEmitter.dispatch(["watch:error", { diagnostics: [diagnostic] }]);
|
|
3205
|
+
}
|
|
3206
|
+
async *watch(cancellationToken) {
|
|
3207
|
+
const onResolve = () => {
|
|
3208
|
+
const testFiles = [...this.#changedTestFiles.values()];
|
|
3209
|
+
this.#changedTestFiles.clear();
|
|
3210
|
+
return testFiles;
|
|
3211
|
+
};
|
|
3212
|
+
const debounce = new Debounce(100, onResolve);
|
|
3213
|
+
const onClose = (reason) => {
|
|
3214
|
+
debounce.cancel();
|
|
3215
|
+
this.#inputService?.close();
|
|
3216
|
+
for (const watcher of this.#watchers) {
|
|
3217
|
+
watcher.close();
|
|
3133
3218
|
}
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3219
|
+
cancellationToken.cancel(reason);
|
|
3220
|
+
debounce.resolve([]);
|
|
3221
|
+
};
|
|
3222
|
+
if (!environmentOptions.noInteractive) {
|
|
3223
|
+
const onInput = (chunk) => {
|
|
3224
|
+
switch (chunk.toLowerCase()) {
|
|
3225
|
+
case "\u0003":
|
|
3226
|
+
case "\u0004":
|
|
3227
|
+
case "\u001B":
|
|
3228
|
+
case "q":
|
|
3229
|
+
case "x":
|
|
3230
|
+
onClose(CancellationReason.WatchClose);
|
|
3231
|
+
break;
|
|
3232
|
+
case "\u000D":
|
|
3233
|
+
case "\u0020":
|
|
3234
|
+
case "a":
|
|
3235
|
+
debounce.cancel();
|
|
3236
|
+
if (this.#watchedTestFiles.size > 0) {
|
|
3237
|
+
debounce.resolve([...this.#watchedTestFiles.values()]);
|
|
3238
|
+
}
|
|
3239
|
+
break;
|
|
3143
3240
|
}
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
this.#compiler.forEachChild(node, (node) => {
|
|
3147
|
-
this.#collectTestMembers(node, identifiers, assertion);
|
|
3148
|
-
});
|
|
3149
|
-
return;
|
|
3150
|
-
}
|
|
3151
|
-
}
|
|
3152
|
-
if (this.#compiler.isImportDeclaration(node)) {
|
|
3153
|
-
identifiers.handleImportDeclaration(node);
|
|
3154
|
-
return;
|
|
3241
|
+
};
|
|
3242
|
+
this.#inputService = new InputService(onInput);
|
|
3155
3243
|
}
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3244
|
+
const onChangedFile = (filePath) => {
|
|
3245
|
+
debounce.refresh();
|
|
3246
|
+
let task = this.#watchedTestFiles.get(filePath);
|
|
3247
|
+
if (task != null) {
|
|
3248
|
+
this.#changedTestFiles.set(filePath, task);
|
|
3249
|
+
}
|
|
3250
|
+
else if (Select.isTestFile(filePath, this.#resolvedConfig)) {
|
|
3251
|
+
task = new Task(filePath);
|
|
3252
|
+
this.#changedTestFiles.set(filePath, task);
|
|
3253
|
+
this.#watchedTestFiles.set(filePath, task);
|
|
3254
|
+
}
|
|
3255
|
+
};
|
|
3256
|
+
const onRemovedFile = (filePath) => {
|
|
3257
|
+
this.#changedTestFiles.delete(filePath);
|
|
3258
|
+
this.#watchedTestFiles.delete(filePath);
|
|
3259
|
+
if (this.#watchedTestFiles.size === 0) {
|
|
3260
|
+
debounce.cancel();
|
|
3261
|
+
this.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereLeft(this.#resolvedConfig)));
|
|
3262
|
+
}
|
|
3263
|
+
};
|
|
3264
|
+
this.#watchers.push(new Watcher(this.#resolvedConfig.rootPath, onChangedFile, onRemovedFile, { recursive: true }));
|
|
3265
|
+
const onChangedConfigFile = () => {
|
|
3266
|
+
onClose(CancellationReason.ConfigChange);
|
|
3267
|
+
};
|
|
3268
|
+
this.#watchers.push(new FileWatcher(this.#resolvedConfig.configFilePath, onChangedConfigFile));
|
|
3269
|
+
for (const watcher of this.#watchers) {
|
|
3270
|
+
watcher.watch();
|
|
3168
3271
|
}
|
|
3169
|
-
|
|
3170
|
-
|
|
3272
|
+
while (!cancellationToken.isCancellationRequested) {
|
|
3273
|
+
const testFiles = await debounce.schedule();
|
|
3274
|
+
if (testFiles.length > 0) {
|
|
3275
|
+
yield testFiles;
|
|
3276
|
+
}
|
|
3171
3277
|
}
|
|
3172
|
-
return parent;
|
|
3173
|
-
}
|
|
3174
|
-
#isMatcherNode(node) {
|
|
3175
|
-
return this.#compiler.isCallExpression(node) && this.#compiler.isPropertyAccessExpression(node.expression);
|
|
3176
3278
|
}
|
|
3177
3279
|
}
|
|
3178
3280
|
|
|
@@ -3234,24 +3336,19 @@ class ProjectService {
|
|
|
3234
3336
|
}
|
|
3235
3337
|
#getDefaultCompilerOptions() {
|
|
3236
3338
|
const defaultCompilerOptions = {
|
|
3339
|
+
allowImportingTsExtensions: true,
|
|
3237
3340
|
allowJs: true,
|
|
3238
3341
|
checkJs: true,
|
|
3239
|
-
|
|
3240
|
-
jsx:
|
|
3241
|
-
module:
|
|
3242
|
-
moduleResolution:
|
|
3342
|
+
exactOptionalPropertyTypes: true,
|
|
3343
|
+
jsx: this.#compiler.JsxEmit.Preserve,
|
|
3344
|
+
module: this.#compiler.ModuleKind.NodeNext,
|
|
3345
|
+
moduleResolution: this.#compiler.ModuleResolutionKind.NodeNext,
|
|
3346
|
+
noUncheckedIndexedAccess: true,
|
|
3243
3347
|
resolveJsonModule: true,
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3348
|
+
strict: true,
|
|
3349
|
+
target: this.#compiler.ScriptTarget.ESNext,
|
|
3350
|
+
verbatimModuleSyntax: true,
|
|
3247
3351
|
};
|
|
3248
|
-
if (Version.isSatisfiedWith(this.#compiler.version, "5.4")) {
|
|
3249
|
-
defaultCompilerOptions.module = "preserve";
|
|
3250
|
-
}
|
|
3251
|
-
if (Version.isSatisfiedWith(this.#compiler.version, "5.0")) {
|
|
3252
|
-
defaultCompilerOptions.allowImportingTsExtensions = true;
|
|
3253
|
-
defaultCompilerOptions.moduleResolution = "bundler";
|
|
3254
|
-
}
|
|
3255
3352
|
return defaultCompilerOptions;
|
|
3256
3353
|
}
|
|
3257
3354
|
getDefaultProject(filePath) {
|
|
@@ -3343,11 +3440,11 @@ class ExpectDiagnosticText {
|
|
|
3343
3440
|
static componentDoesNotAcceptProps(isTypeNode) {
|
|
3344
3441
|
return `${isTypeNode ? "Component type" : "Component"} does not accept props of the given type.`;
|
|
3345
3442
|
}
|
|
3346
|
-
static
|
|
3347
|
-
return
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3443
|
+
static decoratorCanBeApplied(targetText) {
|
|
3444
|
+
return `The decorator function can be applied${targetText}.`;
|
|
3445
|
+
}
|
|
3446
|
+
static decoratorCanNotBeApplied(targetText) {
|
|
3447
|
+
return `The decorator function can not be applied${targetText}.`;
|
|
3351
3448
|
}
|
|
3352
3449
|
static matcherIsNotSupported(matcherNameText) {
|
|
3353
3450
|
return `The '.${matcherNameText}()' matcher is not supported.`;
|
|
@@ -3373,18 +3470,9 @@ class ExpectDiagnosticText {
|
|
|
3373
3470
|
static typeDoesNotHaveProperty(typeText, propertyNameText) {
|
|
3374
3471
|
return `Type '${typeText}' does not have property '${propertyNameText}'.`;
|
|
3375
3472
|
}
|
|
3376
|
-
static typeDoesMatch(sourceTypeText, targetTypeText) {
|
|
3377
|
-
return `Type '${sourceTypeText}' does match type '${targetTypeText}'.`;
|
|
3378
|
-
}
|
|
3379
|
-
static typeDoesNotMatch(sourceTypeText, targetTypeText) {
|
|
3380
|
-
return `Type '${sourceTypeText}' does not match type '${targetTypeText}'.`;
|
|
3381
|
-
}
|
|
3382
3473
|
static typeHasProperty(typeText, propertyNameText) {
|
|
3383
3474
|
return `Type '${typeText}' has property '${propertyNameText}'.`;
|
|
3384
3475
|
}
|
|
3385
|
-
static typeIs(typeText) {
|
|
3386
|
-
return `Type is '${typeText}'.`;
|
|
3387
|
-
}
|
|
3388
3476
|
static typeIsAssignableTo(sourceTypeText, targetTypeText) {
|
|
3389
3477
|
return `Type '${sourceTypeText}' is assignable to type '${targetTypeText}'.`;
|
|
3390
3478
|
}
|
|
@@ -3445,18 +3533,9 @@ class MatchWorker {
|
|
|
3445
3533
|
checkHasApplicableIndexType(sourceNode, targetNode) {
|
|
3446
3534
|
const sourceType = this.getType(sourceNode);
|
|
3447
3535
|
const targetType = this.getType(targetNode);
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
.some(({ keyType }) => this.#typeChecker.isApplicableIndexType(targetType, keyType));
|
|
3452
|
-
}
|
|
3453
|
-
if (targetType.flags & this.#compiler.TypeFlags.StringLiteral) {
|
|
3454
|
-
return sourceType.getStringIndexType() != null;
|
|
3455
|
-
}
|
|
3456
|
-
if (targetType.flags & this.#compiler.TypeFlags.NumberLiteral) {
|
|
3457
|
-
return (sourceType.getStringIndexType() ?? sourceType.getNumberIndexType()) != null;
|
|
3458
|
-
}
|
|
3459
|
-
return false;
|
|
3536
|
+
return this.#typeChecker
|
|
3537
|
+
.getIndexInfosOfType(sourceType)
|
|
3538
|
+
.some(({ keyType }) => this.#typeChecker.isApplicableIndexType(targetType, keyType));
|
|
3460
3539
|
}
|
|
3461
3540
|
checkHasProperty(sourceNode, propertyNameText) {
|
|
3462
3541
|
const sourceType = this.getType(sourceNode);
|
|
@@ -3478,10 +3557,6 @@ class MatchWorker {
|
|
|
3478
3557
|
this.checkIsAssignableTo(sourceNode, targetNode) &&
|
|
3479
3558
|
this.checkIsAssignableWith(sourceNode, targetNode));
|
|
3480
3559
|
}
|
|
3481
|
-
checkIsSubtype(sourceNode, targetNode) {
|
|
3482
|
-
const relation = this.#typeChecker.relation.subtype;
|
|
3483
|
-
return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
|
|
3484
|
-
}
|
|
3485
3560
|
#checkIsRelatedTo(sourceNode, targetNode, relation) {
|
|
3486
3561
|
const sourceType = relation === this.#typeChecker.relation.identity
|
|
3487
3562
|
? this.#simplifyType(this.getType(sourceNode))
|
|
@@ -3568,26 +3643,6 @@ class MatchWorker {
|
|
|
3568
3643
|
}
|
|
3569
3644
|
}
|
|
3570
3645
|
|
|
3571
|
-
class PrimitiveTypeMatcher {
|
|
3572
|
-
#targetTypeFlag;
|
|
3573
|
-
constructor(targetTypeFlag) {
|
|
3574
|
-
this.#targetTypeFlag = targetTypeFlag;
|
|
3575
|
-
}
|
|
3576
|
-
#explain(matchWorker, sourceNode) {
|
|
3577
|
-
const sourceTypeText = matchWorker.getTypeText(sourceNode);
|
|
3578
|
-
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
3579
|
-
return [Diagnostic.error(ExpectDiagnosticText.typeIs(sourceTypeText), origin)];
|
|
3580
|
-
}
|
|
3581
|
-
match(matchWorker, sourceNode) {
|
|
3582
|
-
const sourceType = matchWorker.getType(sourceNode);
|
|
3583
|
-
const isMatch = !!(sourceType.flags & this.#targetTypeFlag);
|
|
3584
|
-
return {
|
|
3585
|
-
explain: () => this.#explain(matchWorker, sourceNode),
|
|
3586
|
-
isMatch,
|
|
3587
|
-
};
|
|
3588
|
-
}
|
|
3589
|
-
}
|
|
3590
|
-
|
|
3591
3646
|
class ToAcceptProps {
|
|
3592
3647
|
#compiler;
|
|
3593
3648
|
#typeChecker;
|
|
@@ -3794,6 +3849,76 @@ class ToBe extends RelationMatcherBase {
|
|
|
3794
3849
|
}
|
|
3795
3850
|
}
|
|
3796
3851
|
|
|
3852
|
+
class ToBeApplicable {
|
|
3853
|
+
#compiler;
|
|
3854
|
+
constructor(compiler) {
|
|
3855
|
+
this.#compiler = compiler;
|
|
3856
|
+
}
|
|
3857
|
+
#resolveTargetText(node) {
|
|
3858
|
+
let text = "";
|
|
3859
|
+
switch (node.kind) {
|
|
3860
|
+
case this.#compiler.SyntaxKind.ClassDeclaration:
|
|
3861
|
+
text = "class";
|
|
3862
|
+
break;
|
|
3863
|
+
case this.#compiler.SyntaxKind.MethodDeclaration:
|
|
3864
|
+
text = "method";
|
|
3865
|
+
break;
|
|
3866
|
+
case this.#compiler.SyntaxKind.PropertyDeclaration:
|
|
3867
|
+
text = node.modifiers?.some((modifier) => modifier.kind === this.#compiler.SyntaxKind.AccessorKeyword)
|
|
3868
|
+
? "accessor"
|
|
3869
|
+
: "field";
|
|
3870
|
+
break;
|
|
3871
|
+
case this.#compiler.SyntaxKind.GetAccessor:
|
|
3872
|
+
text = "getter";
|
|
3873
|
+
break;
|
|
3874
|
+
case this.#compiler.SyntaxKind.SetAccessor:
|
|
3875
|
+
text = "setter";
|
|
3876
|
+
break;
|
|
3877
|
+
}
|
|
3878
|
+
if (text.length > 0) {
|
|
3879
|
+
text = ` to this ${text}`;
|
|
3880
|
+
}
|
|
3881
|
+
return text;
|
|
3882
|
+
}
|
|
3883
|
+
#explain(matchWorker, sourceNode) {
|
|
3884
|
+
const targetText = this.#resolveTargetText(matchWorker.assertion.matcherNode.parent);
|
|
3885
|
+
const diagnostics = [];
|
|
3886
|
+
if (matchWorker.assertion.abilityDiagnostics) {
|
|
3887
|
+
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
|
+
];
|
|
3894
|
+
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
3895
|
+
diagnostics.push(Diagnostic.error(text.flat(), origin));
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
else {
|
|
3899
|
+
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
3900
|
+
diagnostics.push(Diagnostic.error(ExpectDiagnosticText.decoratorCanBeApplied(targetText), origin));
|
|
3901
|
+
}
|
|
3902
|
+
return diagnostics;
|
|
3903
|
+
}
|
|
3904
|
+
match(matchWorker, sourceNode, onDiagnostics) {
|
|
3905
|
+
const type = matchWorker.getType(sourceNode);
|
|
3906
|
+
if (type.getCallSignatures().length === 0) {
|
|
3907
|
+
const expectedText = "of a function type";
|
|
3908
|
+
const text = this.#compiler.isTypeNode(sourceNode)
|
|
3909
|
+
? ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText)
|
|
3910
|
+
: ExpectDiagnosticText.argumentMustBe("source", expectedText);
|
|
3911
|
+
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
3912
|
+
onDiagnostics([Diagnostic.error(text, origin)]);
|
|
3913
|
+
return;
|
|
3914
|
+
}
|
|
3915
|
+
return {
|
|
3916
|
+
explain: () => this.#explain(matchWorker, sourceNode),
|
|
3917
|
+
isMatch: !matchWorker.assertion.abilityDiagnostics,
|
|
3918
|
+
};
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
|
|
3797
3922
|
class ToBeAssignableTo extends RelationMatcherBase {
|
|
3798
3923
|
explainText = ExpectDiagnosticText.typeIsAssignableTo;
|
|
3799
3924
|
explainNotText = ExpectDiagnosticText.typeIsNotAssignableTo;
|
|
@@ -3875,17 +4000,6 @@ class ToHaveProperty {
|
|
|
3875
4000
|
}
|
|
3876
4001
|
}
|
|
3877
4002
|
|
|
3878
|
-
class ToMatch extends RelationMatcherBase {
|
|
3879
|
-
explainText = ExpectDiagnosticText.typeDoesMatch;
|
|
3880
|
-
explainNotText = ExpectDiagnosticText.typeDoesNotMatch;
|
|
3881
|
-
match(matchWorker, sourceNode, targetNode) {
|
|
3882
|
-
return {
|
|
3883
|
-
explain: () => this.explain(matchWorker, sourceNode, targetNode),
|
|
3884
|
-
isMatch: matchWorker.checkIsSubtype(sourceNode, targetNode),
|
|
3885
|
-
};
|
|
3886
|
-
}
|
|
3887
|
-
}
|
|
3888
|
-
|
|
3889
4003
|
class ToRaiseError {
|
|
3890
4004
|
#compiler;
|
|
3891
4005
|
constructor(compiler) {
|
|
@@ -3955,14 +4069,17 @@ class ToRaiseError {
|
|
|
3955
4069
|
};
|
|
3956
4070
|
}
|
|
3957
4071
|
#matchExpectedError(diagnostic, targetNode) {
|
|
4072
|
+
if (this.#compiler.isNumericLiteral(targetNode)) {
|
|
4073
|
+
return Number.parseInt(targetNode.text, 10) === diagnostic.code;
|
|
4074
|
+
}
|
|
4075
|
+
const messageText = typeof diagnostic.messageText === "string"
|
|
4076
|
+
? diagnostic.messageText
|
|
4077
|
+
: Diagnostic.toMessageText(diagnostic.messageText).join("\n");
|
|
3958
4078
|
if (this.#compiler.isRegularExpressionLiteral(targetNode)) {
|
|
3959
4079
|
const targetRegex = new RegExp(...targetNode.text.slice(1).split("/"));
|
|
3960
|
-
return targetRegex.test(
|
|
3961
|
-
}
|
|
3962
|
-
if (this.#compiler.isStringLiteralLike(targetNode)) {
|
|
3963
|
-
return this.#compiler.flattenDiagnosticMessageText(diagnostic.messageText, " ", 0).includes(targetNode.text);
|
|
4080
|
+
return targetRegex.test(messageText);
|
|
3964
4081
|
}
|
|
3965
|
-
return
|
|
4082
|
+
return messageText.includes(targetNode.text);
|
|
3966
4083
|
}
|
|
3967
4084
|
}
|
|
3968
4085
|
|
|
@@ -3972,22 +4089,10 @@ class ExpectService {
|
|
|
3972
4089
|
#typeChecker;
|
|
3973
4090
|
toAcceptProps;
|
|
3974
4091
|
toBe;
|
|
3975
|
-
|
|
4092
|
+
toBeApplicable;
|
|
3976
4093
|
toBeAssignableTo;
|
|
3977
4094
|
toBeAssignableWith;
|
|
3978
|
-
toBeBigInt;
|
|
3979
|
-
toBeBoolean;
|
|
3980
|
-
toBeNever;
|
|
3981
|
-
toBeNull;
|
|
3982
|
-
toBeNumber;
|
|
3983
|
-
toBeString;
|
|
3984
|
-
toBeSymbol;
|
|
3985
|
-
toBeUndefined;
|
|
3986
|
-
toBeUniqueSymbol;
|
|
3987
|
-
toBeUnknown;
|
|
3988
|
-
toBeVoid;
|
|
3989
4095
|
toHaveProperty;
|
|
3990
|
-
toMatch;
|
|
3991
4096
|
toRaiseError;
|
|
3992
4097
|
constructor(compiler, typeChecker, resolvedConfig) {
|
|
3993
4098
|
this.#compiler = compiler;
|
|
@@ -4000,73 +4105,42 @@ class ExpectService {
|
|
|
4000
4105
|
}
|
|
4001
4106
|
this.toAcceptProps = new ToAcceptProps(compiler, typeChecker);
|
|
4002
4107
|
this.toBe = new ToBe();
|
|
4003
|
-
this.
|
|
4108
|
+
this.toBeApplicable = new ToBeApplicable(compiler);
|
|
4004
4109
|
this.toBeAssignableTo = new ToBeAssignableTo();
|
|
4005
4110
|
this.toBeAssignableWith = new ToBeAssignableWith();
|
|
4006
|
-
this.toBeBigInt = new PrimitiveTypeMatcher(compiler.TypeFlags.BigInt);
|
|
4007
|
-
this.toBeBoolean = new PrimitiveTypeMatcher(compiler.TypeFlags.Boolean);
|
|
4008
|
-
this.toBeNever = new PrimitiveTypeMatcher(compiler.TypeFlags.Never);
|
|
4009
|
-
this.toBeNull = new PrimitiveTypeMatcher(compiler.TypeFlags.Null);
|
|
4010
|
-
this.toBeNumber = new PrimitiveTypeMatcher(compiler.TypeFlags.Number);
|
|
4011
|
-
this.toBeString = new PrimitiveTypeMatcher(compiler.TypeFlags.String);
|
|
4012
|
-
this.toBeSymbol = new PrimitiveTypeMatcher(compiler.TypeFlags.ESSymbol);
|
|
4013
|
-
this.toBeUndefined = new PrimitiveTypeMatcher(compiler.TypeFlags.Undefined);
|
|
4014
|
-
this.toBeUniqueSymbol = new PrimitiveTypeMatcher(compiler.TypeFlags.UniqueESSymbol);
|
|
4015
|
-
this.toBeUnknown = new PrimitiveTypeMatcher(compiler.TypeFlags.Unknown);
|
|
4016
|
-
this.toBeVoid = new PrimitiveTypeMatcher(compiler.TypeFlags.Void);
|
|
4017
4111
|
this.toHaveProperty = new ToHaveProperty(compiler);
|
|
4018
|
-
this.toMatch = new ToMatch();
|
|
4019
4112
|
this.toRaiseError = new ToRaiseError(compiler);
|
|
4020
4113
|
}
|
|
4021
4114
|
match(assertion, onDiagnostics) {
|
|
4022
|
-
const matcherNameText = assertion.
|
|
4023
|
-
if (matcherNameText === "toMatch") {
|
|
4024
|
-
const text = ExpectDiagnosticText.matcherIsDeprecated(matcherNameText);
|
|
4025
|
-
const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
|
|
4026
|
-
EventEmitter.dispatch(["deprecation:info", { diagnostics: [Diagnostic.warning(text, origin)] }]);
|
|
4027
|
-
}
|
|
4115
|
+
const matcherNameText = assertion.matcherNameNode.name.text;
|
|
4028
4116
|
if (!assertion.source[0]) {
|
|
4029
4117
|
this.#onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
|
|
4030
4118
|
return;
|
|
4031
4119
|
}
|
|
4032
4120
|
const matchWorker = new MatchWorker(this.#compiler, this.#typeChecker, assertion);
|
|
4121
|
+
if (!(matcherNameText === "toRaiseError" && assertion.isNot === false) &&
|
|
4122
|
+
this.#rejectsTypeArguments(matchWorker, onDiagnostics)) {
|
|
4123
|
+
return;
|
|
4124
|
+
}
|
|
4033
4125
|
switch (matcherNameText) {
|
|
4034
4126
|
case "toAcceptProps":
|
|
4035
4127
|
case "toBe":
|
|
4036
4128
|
case "toBeAssignableTo":
|
|
4037
4129
|
case "toBeAssignableWith":
|
|
4038
|
-
|
|
4039
|
-
if (!assertion.target[0]) {
|
|
4130
|
+
if (!assertion.target?.[0]) {
|
|
4040
4131
|
this.#onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
|
|
4041
4132
|
return;
|
|
4042
4133
|
}
|
|
4043
|
-
if (this.#rejectsTypeArguments(matchWorker, onDiagnostics)) {
|
|
4044
|
-
return;
|
|
4045
|
-
}
|
|
4046
4134
|
return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
|
|
4047
|
-
case "
|
|
4048
|
-
|
|
4049
|
-
case "toBeBoolean":
|
|
4050
|
-
case "toBeNever":
|
|
4051
|
-
case "toBeNull":
|
|
4052
|
-
case "toBeNumber":
|
|
4053
|
-
case "toBeString":
|
|
4054
|
-
case "toBeSymbol":
|
|
4055
|
-
case "toBeUndefined":
|
|
4056
|
-
case "toBeUniqueSymbol":
|
|
4057
|
-
case "toBeUnknown":
|
|
4058
|
-
case "toBeVoid":
|
|
4059
|
-
return this[matcherNameText].match(matchWorker, assertion.source[0]);
|
|
4135
|
+
case "toBeApplicable":
|
|
4136
|
+
return this.toBeApplicable.match(matchWorker, assertion.source[0], onDiagnostics);
|
|
4060
4137
|
case "toHaveProperty":
|
|
4061
|
-
if (!assertion.target[0]) {
|
|
4138
|
+
if (!assertion.target?.[0]) {
|
|
4062
4139
|
this.#onTargetArgumentMustBeProvided("key", assertion, onDiagnostics);
|
|
4063
4140
|
return;
|
|
4064
4141
|
}
|
|
4065
4142
|
return this.toHaveProperty.match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
|
|
4066
4143
|
case "toRaiseError":
|
|
4067
|
-
if (assertion.isNot && this.#rejectsTypeArguments(matchWorker, onDiagnostics)) {
|
|
4068
|
-
return;
|
|
4069
|
-
}
|
|
4070
4144
|
return this.toRaiseError.match(matchWorker, assertion.source[0], [...assertion.target], onDiagnostics);
|
|
4071
4145
|
default:
|
|
4072
4146
|
this.#onMatcherIsNotSupported(matcherNameText, assertion, onDiagnostics);
|
|
@@ -4075,7 +4149,7 @@ class ExpectService {
|
|
|
4075
4149
|
}
|
|
4076
4150
|
#onMatcherIsNotSupported(matcherNameText, assertion, onDiagnostics) {
|
|
4077
4151
|
const text = ExpectDiagnosticText.matcherIsNotSupported(matcherNameText);
|
|
4078
|
-
const origin = DiagnosticOrigin.fromNode(assertion.
|
|
4152
|
+
const origin = DiagnosticOrigin.fromNode(assertion.matcherNameNode.name);
|
|
4079
4153
|
onDiagnostics(Diagnostic.error(text, origin));
|
|
4080
4154
|
}
|
|
4081
4155
|
#onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics) {
|
|
@@ -4085,20 +4159,24 @@ class ExpectService {
|
|
|
4085
4159
|
}
|
|
4086
4160
|
#onTargetArgumentMustBeProvided(argumentNameText, assertion, onDiagnostics) {
|
|
4087
4161
|
const text = ExpectDiagnosticText.argumentMustBeProvided(argumentNameText);
|
|
4088
|
-
const origin = DiagnosticOrigin.fromNode(assertion.
|
|
4162
|
+
const origin = DiagnosticOrigin.fromNode(assertion.matcherNameNode.name);
|
|
4089
4163
|
onDiagnostics(Diagnostic.error(text, origin));
|
|
4090
4164
|
}
|
|
4091
4165
|
#onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics) {
|
|
4092
4166
|
const text = ExpectDiagnosticText.argumentOrTypeArgumentMustBeProvided("target", "Target");
|
|
4093
|
-
const origin = DiagnosticOrigin.fromNode(assertion.
|
|
4167
|
+
const origin = DiagnosticOrigin.fromNode(assertion.matcherNameNode.name);
|
|
4094
4168
|
onDiagnostics(Diagnostic.error(text, origin));
|
|
4095
4169
|
}
|
|
4096
4170
|
#rejectsTypeArguments(matchWorker, onDiagnostics) {
|
|
4097
4171
|
for (const rejectedType of this.#rejectTypes) {
|
|
4172
|
+
const allowedKeyword = this.#compiler.SyntaxKind[`${Format.capitalize(rejectedType)}Keyword`];
|
|
4173
|
+
if (matchWorker.assertion.source[0]?.kind === allowedKeyword ||
|
|
4174
|
+
matchWorker.assertion.target?.[0]?.kind === allowedKeyword) {
|
|
4175
|
+
continue;
|
|
4176
|
+
}
|
|
4098
4177
|
for (const argumentName of ["source", "target"]) {
|
|
4099
|
-
const argumentNode = matchWorker.assertion[argumentName][0];
|
|
4100
|
-
if (!argumentNode
|
|
4101
|
-
argumentNode.kind === this.#compiler.SyntaxKind[`${Format.capitalize(rejectedType)}Keyword`]) {
|
|
4178
|
+
const argumentNode = matchWorker.assertion[argumentName]?.[0];
|
|
4179
|
+
if (!argumentNode) {
|
|
4102
4180
|
continue;
|
|
4103
4181
|
}
|
|
4104
4182
|
if (matchWorker.getType(argumentNode).flags & this.#compiler.TypeFlags[Format.capitalize(rejectedType)]) {
|
|
@@ -4119,7 +4197,6 @@ class ExpectService {
|
|
|
4119
4197
|
}
|
|
4120
4198
|
|
|
4121
4199
|
class TestTreeWalker {
|
|
4122
|
-
#compiler;
|
|
4123
4200
|
#cancellationToken;
|
|
4124
4201
|
#expectService;
|
|
4125
4202
|
#hasOnly;
|
|
@@ -4128,59 +4205,60 @@ class TestTreeWalker {
|
|
|
4128
4205
|
#taskResult;
|
|
4129
4206
|
constructor(resolvedConfig, compiler, typeChecker, options) {
|
|
4130
4207
|
this.#resolvedConfig = resolvedConfig;
|
|
4131
|
-
this.#compiler = compiler;
|
|
4132
4208
|
this.#cancellationToken = options.cancellationToken;
|
|
4133
4209
|
this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
|
|
4134
4210
|
this.#position = options.position;
|
|
4135
4211
|
this.#taskResult = options.taskResult;
|
|
4136
4212
|
this.#expectService = new ExpectService(compiler, typeChecker, this.#resolvedConfig);
|
|
4137
4213
|
}
|
|
4138
|
-
#resolveRunMode(mode,
|
|
4139
|
-
if (
|
|
4214
|
+
#resolveRunMode(mode, testNode) {
|
|
4215
|
+
if (testNode.flags & TestTreeNodeFlags.Fail) {
|
|
4140
4216
|
mode |= RunMode.Fail;
|
|
4141
4217
|
}
|
|
4142
|
-
if (
|
|
4143
|
-
(this.#resolvedConfig.only != null &&
|
|
4218
|
+
if (testNode.flags & TestTreeNodeFlags.Only ||
|
|
4219
|
+
(this.#resolvedConfig.only != null &&
|
|
4220
|
+
testNode.name.toLowerCase().includes(this.#resolvedConfig.only.toLowerCase()))) {
|
|
4144
4221
|
mode |= RunMode.Only;
|
|
4145
4222
|
}
|
|
4146
|
-
if (
|
|
4147
|
-
(this.#resolvedConfig.skip != null &&
|
|
4223
|
+
if (testNode.flags & TestTreeNodeFlags.Skip ||
|
|
4224
|
+
(this.#resolvedConfig.skip != null &&
|
|
4225
|
+
testNode.name.toLowerCase().includes(this.#resolvedConfig.skip.toLowerCase()))) {
|
|
4148
4226
|
mode |= RunMode.Skip;
|
|
4149
4227
|
}
|
|
4150
|
-
if (
|
|
4228
|
+
if (testNode.flags & TestTreeNodeFlags.Todo) {
|
|
4151
4229
|
mode |= RunMode.Todo;
|
|
4152
4230
|
}
|
|
4153
|
-
if (this.#position != null &&
|
|
4231
|
+
if (this.#position != null && testNode.node.getStart() === this.#position) {
|
|
4154
4232
|
mode |= RunMode.Only;
|
|
4155
4233
|
mode &= ~RunMode.Skip;
|
|
4156
4234
|
}
|
|
4157
4235
|
return mode;
|
|
4158
4236
|
}
|
|
4159
|
-
visit(
|
|
4160
|
-
for (const
|
|
4237
|
+
visit(testNodes, runMode, parentResult) {
|
|
4238
|
+
for (const testNode of testNodes) {
|
|
4161
4239
|
if (this.#cancellationToken?.isCancellationRequested) {
|
|
4162
4240
|
break;
|
|
4163
4241
|
}
|
|
4164
|
-
const validationError =
|
|
4242
|
+
const validationError = testNode.validate();
|
|
4165
4243
|
if (validationError.length > 0) {
|
|
4166
4244
|
EventEmitter.dispatch(["task:error", { diagnostics: validationError, result: this.#taskResult }]);
|
|
4167
4245
|
break;
|
|
4168
4246
|
}
|
|
4169
|
-
switch (
|
|
4170
|
-
case
|
|
4171
|
-
this.#visitDescribe(
|
|
4247
|
+
switch (testNode.brand) {
|
|
4248
|
+
case TestTreeNodeBrand.Describe:
|
|
4249
|
+
this.#visitDescribe(testNode, runMode, parentResult);
|
|
4172
4250
|
break;
|
|
4173
|
-
case
|
|
4174
|
-
this.#visitTest(
|
|
4251
|
+
case TestTreeNodeBrand.Test:
|
|
4252
|
+
this.#visitTest(testNode, runMode, parentResult);
|
|
4175
4253
|
break;
|
|
4176
|
-
case
|
|
4177
|
-
this.#visitAssertion(
|
|
4254
|
+
case TestTreeNodeBrand.Expect:
|
|
4255
|
+
this.#visitAssertion(testNode, runMode, parentResult);
|
|
4178
4256
|
break;
|
|
4179
4257
|
}
|
|
4180
4258
|
}
|
|
4181
4259
|
}
|
|
4182
4260
|
#visitAssertion(assertion, runMode, parentResult) {
|
|
4183
|
-
this.visit(assertion.
|
|
4261
|
+
this.visit(assertion.children, runMode, parentResult);
|
|
4184
4262
|
const expectResult = new ExpectResult(assertion, parentResult);
|
|
4185
4263
|
EventEmitter.dispatch(["expect:start", { result: expectResult }]);
|
|
4186
4264
|
runMode = this.#resolveRunMode(runMode, assertion);
|
|
@@ -4194,7 +4272,7 @@ class TestTreeWalker {
|
|
|
4194
4272
|
{ diagnostics: Array.isArray(diagnostics) ? diagnostics : [diagnostics], result: expectResult },
|
|
4195
4273
|
]);
|
|
4196
4274
|
};
|
|
4197
|
-
if (assertion.diagnostics.size > 0 && assertion.
|
|
4275
|
+
if (assertion.diagnostics.size > 0 && assertion.matcherNameNode.name.text !== "toRaiseError") {
|
|
4198
4276
|
onExpectDiagnostics(Diagnostic.fromDiagnostics([...assertion.diagnostics]));
|
|
4199
4277
|
return;
|
|
4200
4278
|
}
|
|
@@ -4234,7 +4312,7 @@ class TestTreeWalker {
|
|
|
4234
4312
|
]);
|
|
4235
4313
|
}
|
|
4236
4314
|
else {
|
|
4237
|
-
this.visit(describe.
|
|
4315
|
+
this.visit(describe.children, runMode, describeResult);
|
|
4238
4316
|
}
|
|
4239
4317
|
EventEmitter.dispatch(["describe:end", { result: describeResult }]);
|
|
4240
4318
|
}
|
|
@@ -4256,7 +4334,7 @@ class TestTreeWalker {
|
|
|
4256
4334
|
]);
|
|
4257
4335
|
return;
|
|
4258
4336
|
}
|
|
4259
|
-
this.visit(test.
|
|
4337
|
+
this.visit(test.children, runMode, testResult);
|
|
4260
4338
|
if (runMode & RunMode.Skip || (this.#hasOnly && !(runMode & RunMode.Only))) {
|
|
4261
4339
|
EventEmitter.dispatch(["test:skip", { result: testResult }]);
|
|
4262
4340
|
return;
|
|
@@ -4278,8 +4356,8 @@ class TaskRunner {
|
|
|
4278
4356
|
constructor(resolvedConfig, compiler) {
|
|
4279
4357
|
this.#resolvedConfig = resolvedConfig;
|
|
4280
4358
|
this.#compiler = compiler;
|
|
4281
|
-
this.#collectService = new CollectService(compiler);
|
|
4282
4359
|
this.#projectService = new ProjectService(this.#resolvedConfig, compiler);
|
|
4360
|
+
this.#collectService = new CollectService(compiler, this.#projectService, this.#resolvedConfig);
|
|
4283
4361
|
}
|
|
4284
4362
|
run(task, cancellationToken) {
|
|
4285
4363
|
if (cancellationToken?.isCancellationRequested) {
|
|
@@ -4336,17 +4414,27 @@ class TaskRunner {
|
|
|
4336
4414
|
hasOnly: testTree.hasOnly,
|
|
4337
4415
|
position: task.position,
|
|
4338
4416
|
});
|
|
4339
|
-
testTreeWalker.visit(testTree.
|
|
4417
|
+
testTreeWalker.visit(testTree.children, RunMode.Normal, undefined);
|
|
4340
4418
|
}
|
|
4341
4419
|
}
|
|
4342
4420
|
|
|
4343
4421
|
class Runner {
|
|
4344
4422
|
#eventEmitter = new EventEmitter();
|
|
4345
4423
|
#resolvedConfig;
|
|
4346
|
-
static version = "
|
|
4424
|
+
static version = "4.0.0-beta.1";
|
|
4347
4425
|
constructor(resolvedConfig) {
|
|
4348
4426
|
this.#resolvedConfig = resolvedConfig;
|
|
4349
4427
|
}
|
|
4428
|
+
#addHandlers(cancellationToken) {
|
|
4429
|
+
const resultHandler = new ResultHandler();
|
|
4430
|
+
this.#eventEmitter.addHandler(resultHandler);
|
|
4431
|
+
const testTreeHandler = new TestTreeHandler();
|
|
4432
|
+
this.#eventEmitter.addHandler(testTreeHandler);
|
|
4433
|
+
if (this.#resolvedConfig.failFast) {
|
|
4434
|
+
const cancellationHandler = new CancellationHandler(cancellationToken, CancellationReason.FailFast);
|
|
4435
|
+
this.#eventEmitter.addHandler(cancellationHandler);
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4350
4438
|
async #addReporters() {
|
|
4351
4439
|
if (this.#resolvedConfig.watch && !environmentOptions.noInteractive) {
|
|
4352
4440
|
const watchReporter = new WatchReporter(this.#resolvedConfig);
|
|
@@ -4374,13 +4462,8 @@ class Runner {
|
|
|
4374
4462
|
}
|
|
4375
4463
|
async run(testFiles, cancellationToken = new CancellationToken()) {
|
|
4376
4464
|
const tasks = testFiles.map((testFile) => (testFile instanceof Task ? testFile : new Task(testFile)));
|
|
4377
|
-
|
|
4378
|
-
this.#eventEmitter.addHandler(resultHandler);
|
|
4465
|
+
this.#addHandlers(cancellationToken);
|
|
4379
4466
|
await this.#addReporters();
|
|
4380
|
-
if (this.#resolvedConfig.failFast) {
|
|
4381
|
-
const cancellationHandler = new CancellationHandler(cancellationToken, CancellationReason.FailFast);
|
|
4382
|
-
this.#eventEmitter.addHandler(cancellationHandler);
|
|
4383
|
-
}
|
|
4384
4467
|
await this.#run(tasks, cancellationToken);
|
|
4385
4468
|
if (this.#resolvedConfig.watch) {
|
|
4386
4469
|
await this.#watch(tasks, cancellationToken);
|
|
@@ -4483,9 +4566,9 @@ class Cli {
|
|
|
4483
4566
|
OutputService.writeMessage(formattedText({ ...resolvedConfig, ...environmentOptions }));
|
|
4484
4567
|
continue;
|
|
4485
4568
|
}
|
|
4486
|
-
if (commandLine.includes("--
|
|
4569
|
+
if (commandLine.includes("--fetch")) {
|
|
4487
4570
|
for (const tag of resolvedConfig.target) {
|
|
4488
|
-
await Store.
|
|
4571
|
+
await Store.fetch(tag);
|
|
4489
4572
|
}
|
|
4490
4573
|
continue;
|
|
4491
4574
|
}
|
|
@@ -4540,4 +4623,4 @@ class Cli {
|
|
|
4540
4623
|
}
|
|
4541
4624
|
}
|
|
4542
4625
|
|
|
4543
|
-
export {
|
|
4626
|
+
export { AssertionNode, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileWatcher, InputService, Line, ListReporter, OptionBrand, OptionGroup, Options, OutputService, Path, PluginService, ProjectResult, ProjectService, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, Runner, Scribbler, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, TargetResult, Task, TaskResult, TestResult, TestTree, TestTreeHandler, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, addsPackageText, defaultOptions, describeNameText, diagnosticText, environmentOptions, fileViewText, formattedText, helpText, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
|