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/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.matcherName;
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.#toMessageText(diagnostic.messageText);
143
+ : Diagnostic.toMessageText(diagnostic.messageText);
144
144
  return new Diagnostic(text, DiagnosticCategory.Error, origin).add({ code, related });
145
145
  });
146
146
  }
147
- static #toMessageText(chain) {
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.#toMessageText(nextChain));
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 failedToInstalTypeScript(version) {
398
- return `Failed to install 'typescript@${version}'.`;
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 = "2";
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 = /^(4|5)\.\d\.\d$/;
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.failedToInstalTypeScript(packageVersion));
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 getSupportedTags() {
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
- if (Version.isSatisfiedWith(packageVersion, "4.4")) {
804
- toExpose.push("isApplicableIndexType");
805
- }
806
- if (!Version.isSatisfiedWith(packageVersion, "4.6")) {
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 = Object.keys(Store.manifest.resolutions).slice(0, -4);
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: "Print the list of command line options with brief descriptions and exit.",
906
+ description: "Fetch the specified versions of the 'typescript' package and exit.",
917
907
  group: OptionGroup.CommandLine,
918
- name: "help",
908
+ name: "fetch",
919
909
  },
920
910
  {
921
911
  brand: OptionBrand.BareTrue,
922
- description: "Install specified versions of the 'typescript' package and exit.",
912
+ description: "Print the list of command line options with brief descriptions and exit.",
923
913
  group: OptionGroup.CommandLine,
924
- name: "install",
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: false,
1497
+ checkSourceFiles: true,
1508
1498
  failFast: false,
1509
1499
  plugins: [],
1510
- rejectAnyType: false,
1511
- rejectNeverType: false,
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
- function jsx(type, props) {
1913
- return { props, type };
1914
- }
1915
-
1916
- var Color;
1917
- (function (Color) {
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
- function Text({ children, color, indent }) {
1929
- const ansiEscapes = [];
1930
- if (color != null) {
1931
- ansiEscapes.push(color);
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
- function Line({ children, color, indent }) {
1937
- return (jsx(Text, { color: color, indent: indent, children: [children, jsx("newLine", {})] }));
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 Scribbler {
1941
- #indentStep = " ";
1942
- #newLine;
1943
- #noColor;
1944
- #notEmptyLineRegex = /^(?!$)/gm;
1945
- constructor(options) {
1946
- this.#newLine = options?.newLine ?? "\n";
1947
- this.#noColor = options?.noColor ?? environmentOptions.noColor;
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
- render(element) {
1959
- if (typeof element.type === "function") {
1960
- return this.render(element.type({ ...element.props }));
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
- if (element.type === "newLine") {
1966
- return this.#newLine;
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
- if (element.type === "text") {
1969
- const text = this.#visitChildren(element.props.children);
1970
- return this.#indentEachLine(text, element.props.indent);
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
- #visitChildren(children) {
1975
- const text = [];
1976
- for (const child of children) {
1977
- if (typeof child === "string" || typeof child === "number") {
1978
- text.push(child);
1979
- continue;
1980
- }
1981
- if (Array.isArray(child)) {
1982
- text.push(this.#visitChildren(child));
1983
- continue;
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
- if (child != null && typeof child === "object") {
1986
- text.push(this.render(child));
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
- return text.join("");
2071
+ }
2072
+ open(sourceFile) {
2073
+ this.#filePath = sourceFile.fileName;
2074
+ this.#text = sourceFile.text;
1990
2075
  }
1991
2076
  }
1992
2077
 
1993
- function addsPackageText(packageVersion, packagePath) {
1994
- return (jsx(Line, { children: [jsx(Text, { color: Color.Gray, children: "adds" }), " TypeScript ", packageVersion, jsx(Text, { color: Color.Gray, children: [" to ", packagePath] })] }));
1995
- }
1996
-
1997
- function describeNameText(name, indent = 0) {
1998
- return jsx(Line, { indent: indent + 1, children: name });
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
- function BreadcrumbsText({ ancestor }) {
2002
- const text = [];
2003
- while ("name" in ancestor) {
2004
- text.push(ancestor.name);
2005
- ancestor = ancestor.parent;
2006
- }
2007
- text.push("");
2008
- return jsx(Text, { color: Color.Gray, children: text.reverse().join(" ❭ ") });
2009
- }
2010
- function CodeLineText({ gutterWidth, lineNumber, lineNumberColor = Color.Gray, lineText }) {
2011
- return (jsx(Line, { children: [jsx(Text, { color: lineNumberColor, children: lineNumber.toString().padStart(gutterWidth) }), jsx(Text, { color: Color.Gray, children: " | " }), lineText] }));
2012
- }
2013
- function SquiggleLineText({ gutterWidth, indentWidth = 0, squiggleColor, squiggleWidth }) {
2014
- return (jsx(Line, { children: [" ".repeat(gutterWidth), jsx(Text, { color: Color.Gray, children: " | " }), " ".repeat(indentWidth), jsx(Text, { color: squiggleColor, children: "~".repeat(squiggleWidth === 0 ? 1 : squiggleWidth) })] }));
2015
- }
2016
- function CodeFrameText({ diagnosticCategory, diagnosticOrigin, options }) {
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
- const codeFrame = [];
2036
- for (let index = firstLine; index <= lastLine; index++) {
2037
- const lineStart = lineMap[index];
2038
- const lineEnd = index === lineMap.length - 1 ? diagnosticOrigin.sourceFile.text.length : lineMap[index + 1];
2039
- const lineText = diagnosticOrigin.sourceFile.text.slice(lineStart, lineEnd).trimEnd().replace(/\t/g, " ");
2040
- if (index >= firstMarkedLine && index <= lastMarkedLine) {
2041
- codeFrame.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumber: index + 1, lineNumberColor: highlightColor, lineText: lineText }));
2042
- if (index === firstMarkedLine) {
2043
- const squiggleLength = index === lastMarkedLine
2044
- ? lastMarkedLineCharacter - firstMarkedLineCharacter
2045
- : lineText.length - firstMarkedLineCharacter;
2046
- codeFrame.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, indentWidth: firstMarkedLineCharacter, squiggleColor: highlightColor, squiggleWidth: squiggleLength }));
2047
- }
2048
- else if (index === lastMarkedLine) {
2049
- codeFrame.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleColor: highlightColor, squiggleWidth: lastMarkedLineCharacter }));
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
- else {
2052
- codeFrame.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleColor: highlightColor, squiggleWidth: lineText.length }));
2125
+ if (this.#compiler.isNamespaceImport(node.importClause.namedBindings)) {
2126
+ this.#identifiers.namespace = node.importClause.namedBindings.name.getText();
2053
2127
  }
2054
2128
  }
2055
- else {
2056
- codeFrame.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumber: index + 1, lineText: lineText }));
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 4.9,5.3.2,current", "Test on all specified versions of TypeScript."],
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
- else if (entryMeta.isFile()) {
2731
- files.push(entry.name);
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
- static #getMatchPatterns(globPatterns) {
2740
- let matchPatterns = Select.#patternsCache.get(globPatterns);
2741
- if (!matchPatterns) {
2742
- matchPatterns = {
2743
- includedDirectory: GlobPattern.toRegex(globPatterns, "directories"),
2744
- includedFile: GlobPattern.toRegex(globPatterns, "files"),
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
- return matchPatterns;
2749
- }
2750
- static #isDirectoryIncluded(directoryPath, matchPatterns) {
2751
- return matchPatterns.includedDirectory.test(directoryPath);
2752
- }
2753
- static #isFileIncluded(filePath, matchPatterns, resolvedConfig) {
2754
- if (resolvedConfig.pathMatch.length > 0 &&
2755
- !resolvedConfig.pathMatch.some((match) => filePath.toLowerCase().includes(match.toLowerCase()))) {
2756
- return false;
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
- static async selectFiles(resolvedConfig) {
2768
- const matchPatterns = Select.#getMatchPatterns(resolvedConfig.testFileMatch);
2769
- const testFilePaths = [];
2770
- await Select.#visitDirectory(".", testFilePaths, matchPatterns, resolvedConfig);
2771
- if (testFilePaths.length === 0) {
2772
- Select.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereSelected(resolvedConfig)));
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
- static async #visitDirectory(currentPath, testFilePaths, matchPatterns, resolvedConfig) {
2777
- const targetPath = Path.join(resolvedConfig.rootPath, currentPath);
2778
- const entries = await Select.#getAccessibleFileSystemEntries(targetPath);
2779
- for (const directoryName of entries.directories) {
2780
- const directoryPath = [currentPath, directoryName].join("/");
2781
- if (Select.#isDirectoryIncluded(directoryPath, matchPatterns)) {
2782
- await Select.#visitDirectory(directoryPath, testFilePaths, matchPatterns, resolvedConfig);
2783
- }
2784
- }
2785
- for (const fileName of entries.files) {
2786
- const filePath = [currentPath, fileName].join("/");
2787
- if (Select.#isFileIncluded(filePath, matchPatterns, resolvedConfig)) {
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 Debounce {
2795
- #delay;
2796
- #onResolve;
2797
- #resolve;
2798
- #timeout;
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
- refreshTimeout() {
2807
- this.clearTimeout();
2808
- this.#timeout = setTimeout(() => {
2809
- this.#resolve?.(this.#onResolve());
2810
- }, this.#delay);
2935
+ get reason() {
2936
+ return this.#reason;
2811
2937
  }
2812
- resolveWith(value) {
2813
- this.#resolve?.(value);
2938
+ cancel(reason) {
2939
+ if (!this.#isCancelled) {
2940
+ this.#isCancelled = true;
2941
+ this.#reason = reason;
2942
+ }
2814
2943
  }
2815
- setup() {
2816
- return new Promise((resolve) => {
2817
- this.#resolve = resolve;
2818
- });
2944
+ reset() {
2945
+ if (this.#isCancelled) {
2946
+ this.#isCancelled = false;
2947
+ this.#reason = undefined;
2948
+ }
2819
2949
  }
2820
2950
  }
2821
2951
 
2822
- class WatchService {
2823
- #changedTestFiles = new Map();
2824
- #inputService;
2825
- #resolvedConfig;
2826
- #watchedTestFiles;
2827
- #watchers = [];
2828
- constructor(resolvedConfig, tasks) {
2829
- this.#resolvedConfig = resolvedConfig;
2830
- this.#watchedTestFiles = new Map(tasks.map((task) => [task.filePath, task]));
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
- #onDiagnostics(diagnostic) {
2833
- EventEmitter.dispatch(["watch:error", { diagnostics: [diagnostic] }]);
2972
+ close() {
2973
+ this.#watcher?.close();
2834
2974
  }
2835
- async *watch(cancellationToken) {
2836
- const onResolve = () => {
2837
- const testFiles = [...this.#changedTestFiles.values()];
2838
- this.#changedTestFiles.clear();
2839
- return testFiles;
2840
- };
2841
- const debounce = new Debounce(100, onResolve);
2842
- const onClose = (reason) => {
2843
- debounce.clearTimeout();
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
- this.#watchers.push(new Watcher(this.#resolvedConfig.rootPath, onChangedFile, onRemovedFile, { recursive: true }));
2894
- const onChangedConfigFile = () => {
2895
- onClose(CancellationReason.ConfigChange);
2896
- };
2897
- this.#watchers.push(new FileWatcher(this.#resolvedConfig.configFilePath, onChangedConfigFile));
2898
- for (const watcher of this.#watchers) {
2899
- watcher.watch();
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
- var TestMemberBrand;
2911
- (function (TestMemberBrand) {
2912
- TestMemberBrand["Describe"] = "describe";
2913
- TestMemberBrand["Test"] = "test";
2914
- TestMemberBrand["Expect"] = "expect";
2915
- })(TestMemberBrand || (TestMemberBrand = {}));
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 TestMember {
2918
- brand;
2919
- #compiler;
2920
- diagnostics = new Set();
2921
- flags;
2922
- members = [];
2923
- name = "";
2924
- node;
2925
- parent;
2926
- constructor(compiler, brand, node, parent, flags) {
2927
- this.brand = brand;
2928
- this.#compiler = compiler;
2929
- this.node = node;
2930
- this.parent = parent;
2931
- this.flags = flags;
2932
- if (node.arguments[0] != null && compiler.isStringLiteralLike(node.arguments[0])) {
2933
- this.name = node.arguments[0].text;
2934
- }
2935
- if (node.arguments[1] != null &&
2936
- compiler.isFunctionLike(node.arguments[1]) &&
2937
- compiler.isBlock(node.arguments[1].body)) {
2938
- const blockStart = node.arguments[1].body.getStart();
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
- validate() {
2949
- const diagnostics = [];
2950
- const getText = (node) => `'${node.expression.getText()}()' cannot be nested within '${this.node.expression.getText()}()'.`;
2951
- const getParentCallExpression = (node) => {
2952
- while (!this.#compiler.isCallExpression(node.parent)) {
2953
- node = node.parent;
2954
- }
2955
- return node.parent;
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
- return diagnostics;
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 Assertion extends TestMember {
2979
- isNot;
2980
- matcherName;
2981
- matcherNode;
2982
- modifierNode;
2983
- notNode;
2984
- source;
2985
- target;
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
- var TestMemberFlags;
3004
- (function (TestMemberFlags) {
3005
- TestMemberFlags[TestMemberFlags["None"] = 0] = "None";
3006
- TestMemberFlags[TestMemberFlags["Fail"] = 1] = "Fail";
3007
- TestMemberFlags[TestMemberFlags["Only"] = 2] = "Only";
3008
- TestMemberFlags[TestMemberFlags["Skip"] = 4] = "Skip";
3009
- TestMemberFlags[TestMemberFlags["Todo"] = 8] = "Todo";
3010
- })(TestMemberFlags || (TestMemberFlags = {}));
3011
-
3012
- class IdentifierLookup {
3013
- #compiler;
3014
- #identifiers;
3015
- #moduleSpecifiers = ['"tstyche"', "'tstyche'"];
3016
- constructor(compiler, identifiers) {
3017
- this.#compiler = compiler;
3018
- this.#identifiers = identifiers ?? {
3019
- namedImports: {
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
- resolveTestMemberMeta(node) {
3056
- let flags = TestMemberFlags.None;
3057
- let expression = node.expression;
3058
- while (this.#compiler.isPropertyAccessExpression(expression)) {
3059
- if (expression.expression.getText() === this.#identifiers.namespace) {
3060
- break;
3061
- }
3062
- switch (expression.name.getText()) {
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
- let identifierName;
3079
- if (this.#compiler.isPropertyAccessExpression(expression) &&
3080
- expression.expression.getText() === this.#identifiers.namespace) {
3081
- identifierName = expression.name.getText();
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
- else {
3084
- identifierName = Object.keys(this.#identifiers.namedImports).find((key) => this.#identifiers.namedImports[key] === expression.getText());
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
- if (!identifierName) {
3087
- return;
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
- switch (identifierName) {
3090
- case "describe":
3091
- return { brand: TestMemberBrand.Describe, flags };
3092
- case "it":
3093
- case "test":
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 TestTree {
3103
- diagnostics;
3104
- members = [];
3105
- sourceFile;
3106
- constructor(diagnostics, sourceFile) {
3107
- this.diagnostics = diagnostics;
3108
- this.sourceFile = sourceFile;
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
- get hasOnly() {
3111
- function hasOnly(root) {
3112
- return root.members.some((branch) => branch.flags & TestMemberFlags.Only || ("members" in branch && hasOnly(branch)));
3113
- }
3114
- return hasOnly(this);
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 CollectService {
3119
- #compiler;
3120
- constructor(compiler) {
3121
- this.#compiler = compiler;
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
- #collectTestMembers(node, identifiers, parent) {
3124
- if (this.#compiler.isCallExpression(node)) {
3125
- const meta = identifiers.resolveTestMemberMeta(node);
3126
- if (meta != null && (meta.brand === TestMemberBrand.Describe || meta.brand === TestMemberBrand.Test)) {
3127
- const testMember = new TestMember(this.#compiler, meta.brand, node, parent, meta.flags);
3128
- parent.members.push(testMember);
3129
- this.#compiler.forEachChild(node, (node) => {
3130
- this.#collectTestMembers(node, identifiers, testMember);
3131
- });
3132
- return;
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
- if (meta != null && meta.brand === TestMemberBrand.Expect) {
3135
- const modifierNode = this.#getChainedNode(node, "type");
3136
- if (!modifierNode) {
3137
- return;
3138
- }
3139
- const notNode = this.#getChainedNode(modifierNode, "not");
3140
- const matcherNode = this.#getChainedNode(notNode ?? modifierNode)?.parent;
3141
- if (!matcherNode || !this.#isMatcherNode(matcherNode)) {
3142
- return;
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
- const assertion = new Assertion(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, modifierNode, notNode);
3145
- parent.members.push(assertion);
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
- this.#compiler.forEachChild(node, (node) => {
3157
- this.#collectTestMembers(node, identifiers, parent);
3158
- });
3159
- }
3160
- createTestTree(sourceFile, semanticDiagnostics = []) {
3161
- const testTree = new TestTree(new Set(semanticDiagnostics), sourceFile);
3162
- this.#collectTestMembers(sourceFile, new IdentifierLookup(this.#compiler), testTree);
3163
- return testTree;
3164
- }
3165
- #getChainedNode({ parent }, name) {
3166
- if (!this.#compiler.isPropertyAccessExpression(parent)) {
3167
- return;
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
- if (name != null && name !== parent.name.getText()) {
3170
- return;
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
- esModuleInterop: true,
3240
- jsx: "preserve",
3241
- module: "esnext",
3242
- moduleResolution: "node",
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
- strictFunctionTypes: true,
3245
- strictNullChecks: true,
3246
- target: "esnext",
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 matcherIsDeprecated(matcherNameText) {
3347
- return [
3348
- `The '.${matcherNameText}()' matcher is deprecated and will be removed in TSTyche 4.`,
3349
- "To learn more, visit https://tstyche.org/releases/tstyche-3",
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
- if (Version.isSatisfiedWith(this.#compiler.version, "4.4")) {
3449
- return this.#typeChecker
3450
- .getIndexInfosOfType(sourceType)
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(this.#compiler.flattenDiagnosticMessageText(diagnostic.messageText, " ", 0));
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 Number.parseInt(targetNode.text, 10) === diagnostic.code;
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
- toBeAny;
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.toBeAny = new PrimitiveTypeMatcher(compiler.TypeFlags.Any);
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.matcherName.getText();
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
- case "toMatch":
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 "toBeAny":
4048
- case "toBeBigInt":
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.matcherName);
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.matcherName);
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.matcherName);
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, member) {
4139
- if (member.flags & TestMemberFlags.Fail) {
4214
+ #resolveRunMode(mode, testNode) {
4215
+ if (testNode.flags & TestTreeNodeFlags.Fail) {
4140
4216
  mode |= RunMode.Fail;
4141
4217
  }
4142
- if (member.flags & TestMemberFlags.Only ||
4143
- (this.#resolvedConfig.only != null && member.name.toLowerCase().includes(this.#resolvedConfig.only.toLowerCase()))) {
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 (member.flags & TestMemberFlags.Skip ||
4147
- (this.#resolvedConfig.skip != null && member.name.toLowerCase().includes(this.#resolvedConfig.skip.toLowerCase()))) {
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 (member.flags & TestMemberFlags.Todo) {
4228
+ if (testNode.flags & TestTreeNodeFlags.Todo) {
4151
4229
  mode |= RunMode.Todo;
4152
4230
  }
4153
- if (this.#position != null && member.node.getStart() === this.#position) {
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(members, runMode, parentResult) {
4160
- for (const member of members) {
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 = member.validate();
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 (member.brand) {
4170
- case TestMemberBrand.Describe:
4171
- this.#visitDescribe(member, runMode, parentResult);
4247
+ switch (testNode.brand) {
4248
+ case TestTreeNodeBrand.Describe:
4249
+ this.#visitDescribe(testNode, runMode, parentResult);
4172
4250
  break;
4173
- case TestMemberBrand.Test:
4174
- this.#visitTest(member, runMode, parentResult);
4251
+ case TestTreeNodeBrand.Test:
4252
+ this.#visitTest(testNode, runMode, parentResult);
4175
4253
  break;
4176
- case TestMemberBrand.Expect:
4177
- this.#visitAssertion(member, runMode, parentResult);
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.members, runMode, parentResult);
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.matcherName.getText() !== "toRaiseError") {
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.members, runMode, describeResult);
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.members, runMode, testResult);
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.members, RunMode.Normal, undefined);
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 = "3.5.0";
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
- const resultHandler = new ResultHandler();
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("--install")) {
4569
+ if (commandLine.includes("--fetch")) {
4487
4570
  for (const tag of resolvedConfig.target) {
4488
- await Store.install(tag);
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 { Assertion, 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, TestMember, TestMemberBrand, TestMemberFlags, TestResult, TestTree, Text, Version, WatchReporter, WatchService, Watcher, addsPackageText, defaultOptions, describeNameText, diagnosticText, environmentOptions, fileViewText, formattedText, helpText, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
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 };