tstyche 4.0.0-beta.8 → 4.0.0-beta.9

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.
@@ -3,7 +3,6 @@ import type ts from 'typescript';
3
3
  declare enum CancellationReason {
4
4
  ConfigChange = "configChange",
5
5
  ConfigError = "configError",
6
- CollectError = "collectError",
7
6
  FailFast = "failFast",
8
7
  WatchClose = "watchClose"
9
8
  }
@@ -21,58 +20,6 @@ declare class Cli {
21
20
  run(commandLine: Array<string>, cancellationToken?: CancellationToken): Promise<void>;
22
21
  }
23
22
 
24
- declare enum DiagnosticCategory {
25
- Error = "error",
26
- Warning = "warning"
27
- }
28
-
29
- declare class SourceFile {
30
- #private;
31
- fileName: string;
32
- text: string;
33
- constructor(fileName: string, text: string);
34
- getLineStarts(): Array<number>;
35
- getLineAndCharacterOfPosition(position: number): {
36
- line: number;
37
- character: number;
38
- };
39
- }
40
-
41
- declare class DiagnosticOrigin {
42
- assertion: AssertionNode | undefined;
43
- end: number;
44
- sourceFile: SourceFile | ts.SourceFile;
45
- start: number;
46
- constructor(start: number, end: number, sourceFile: SourceFile | ts.SourceFile, assertion?: AssertionNode);
47
- static fromAssertion(assertion: AssertionNode): DiagnosticOrigin;
48
- static fromNode(node: ts.Node, assertion?: AssertionNode): DiagnosticOrigin;
49
- static fromNodes(nodes: ts.NodeArray<ts.Node>, assertion?: AssertionNode): DiagnosticOrigin;
50
- }
51
-
52
- declare class Diagnostic {
53
- category: DiagnosticCategory;
54
- code: string | undefined;
55
- origin: DiagnosticOrigin | undefined;
56
- related: Array<Diagnostic> | undefined;
57
- text: string | Array<string>;
58
- constructor(text: string | Array<string>, category: DiagnosticCategory, origin?: DiagnosticOrigin);
59
- add(options: {
60
- code?: string | undefined;
61
- related?: Array<Diagnostic> | undefined;
62
- }): this;
63
- static error(text: string | Array<string>, origin?: DiagnosticOrigin): Diagnostic;
64
- extendWith(text: string | Array<string>, origin?: DiagnosticOrigin): Diagnostic;
65
- static fromDiagnostics(diagnostics: Array<ts.Diagnostic>): Array<Diagnostic>;
66
- static warning(text: string | Array<string>, origin?: DiagnosticOrigin): Diagnostic;
67
- }
68
-
69
- declare function diagnosticBelongsToNode(diagnostic: ts.Diagnostic, node: ts.NodeArray<ts.Node> | ts.Node): boolean;
70
- declare function getDiagnosticMessageText(diagnostic: ts.Diagnostic): string | Array<string>;
71
- declare function getTextSpanEnd(span: ts.TextSpan): number;
72
- declare function isDiagnosticWithLocation(diagnostic: ts.Diagnostic): diagnostic is ts.DiagnosticWithLocation;
73
-
74
- type DiagnosticsHandler<T extends Diagnostic | Array<Diagnostic> = Diagnostic> = (this: void, diagnostics: T) => void;
75
-
76
23
  declare enum TestTreeNodeBrand {
77
24
  Describe = "describe",
78
25
  Test = "test",
@@ -88,21 +35,27 @@ declare enum TestTreeNodeFlags {
88
35
  Todo = 8
89
36
  }
90
37
 
38
+ declare class WhenNode extends TestTreeNode {
39
+ actionNode: ts.CallExpression;
40
+ actionNameNode: ts.PropertyAccessExpression;
41
+ abilityDiagnostics: Set<ts.Diagnostic> | undefined;
42
+ target: ts.NodeArray<ts.Expression> | ts.NodeArray<ts.TypeNode>;
43
+ constructor(compiler: typeof ts, brand: TestTreeNodeBrand, node: ts.CallExpression, parent: TestTree | TestTreeNode, flags: TestTreeNodeFlags, actionNode: ts.CallExpression, actionNameNode: ts.PropertyAccessExpression);
44
+ }
45
+
91
46
  declare class TestTreeNode {
92
- #private;
93
47
  brand: TestTreeNodeBrand;
94
- children: Array<TestTreeNode | AssertionNode>;
48
+ children: Array<TestTreeNode | AssertionNode | WhenNode>;
95
49
  diagnostics: Set<ts.Diagnostic>;
96
50
  flags: TestTreeNodeFlags;
97
51
  name: string;
98
52
  node: ts.CallExpression;
99
53
  parent: TestTree | TestTreeNode;
100
54
  constructor(compiler: typeof ts, brand: TestTreeNodeBrand, node: ts.CallExpression, parent: TestTree | TestTreeNode, flags: TestTreeNodeFlags);
101
- validate(): Array<Diagnostic>;
102
55
  }
103
56
 
104
57
  declare class TestTree {
105
- children: Array<TestTreeNode | AssertionNode>;
58
+ children: Array<TestTreeNode | AssertionNode | WhenNode>;
106
59
  diagnostics: Set<ts.Diagnostic>;
107
60
  hasOnly: boolean;
108
61
  sourceFile: ts.SourceFile;
@@ -296,6 +249,58 @@ declare class Config {
296
249
  static resolveConfigFilePath(filePath?: string): string;
297
250
  }
298
251
 
252
+ declare enum DiagnosticCategory {
253
+ Error = "error",
254
+ Warning = "warning"
255
+ }
256
+
257
+ declare class SourceFile {
258
+ #private;
259
+ fileName: string;
260
+ text: string;
261
+ constructor(fileName: string, text: string);
262
+ getLineStarts(): Array<number>;
263
+ getLineAndCharacterOfPosition(position: number): {
264
+ line: number;
265
+ character: number;
266
+ };
267
+ }
268
+
269
+ declare class DiagnosticOrigin {
270
+ assertion: AssertionNode | undefined;
271
+ end: number;
272
+ sourceFile: SourceFile | ts.SourceFile;
273
+ start: number;
274
+ constructor(start: number, end: number, sourceFile: SourceFile | ts.SourceFile, assertion?: AssertionNode);
275
+ static fromAssertion(assertion: AssertionNode): DiagnosticOrigin;
276
+ static fromNode(node: ts.Node, assertion?: AssertionNode): DiagnosticOrigin;
277
+ static fromNodes(nodes: ts.NodeArray<ts.Node>, assertion?: AssertionNode): DiagnosticOrigin;
278
+ }
279
+
280
+ declare class Diagnostic {
281
+ category: DiagnosticCategory;
282
+ code: string | undefined;
283
+ origin: DiagnosticOrigin | undefined;
284
+ related: Array<Diagnostic> | undefined;
285
+ text: string | Array<string>;
286
+ constructor(text: string | Array<string>, category: DiagnosticCategory, origin?: DiagnosticOrigin);
287
+ add(options: {
288
+ code?: string | undefined;
289
+ related?: Array<Diagnostic> | undefined;
290
+ }): this;
291
+ static error(text: string | Array<string>, origin?: DiagnosticOrigin): Diagnostic;
292
+ extendWith(text: string | Array<string>, origin?: DiagnosticOrigin): Diagnostic;
293
+ static fromDiagnostics(diagnostics: Array<ts.Diagnostic>): Array<Diagnostic>;
294
+ static warning(text: string | Array<string>, origin?: DiagnosticOrigin): Diagnostic;
295
+ }
296
+
297
+ declare function diagnosticBelongsToNode(diagnostic: ts.Diagnostic, node: ts.NodeArray<ts.Node> | ts.Node): boolean;
298
+ declare function getDiagnosticMessageText(diagnostic: ts.Diagnostic): string | Array<string>;
299
+ declare function getTextSpanEnd(span: ts.TextSpan): number;
300
+ declare function isDiagnosticWithLocation(diagnostic: ts.Diagnostic): diagnostic is ts.DiagnosticWithLocation;
301
+
302
+ type DiagnosticsHandler<T extends Diagnostic | Array<Diagnostic> = Diagnostic> = (this: void, diagnostics: T) => void;
303
+
299
304
  declare enum OptionGroup {
300
305
  CommandLine = 2,
301
306
  ConfigFile = 4,
@@ -346,13 +351,9 @@ declare class CollectService {
346
351
 
347
352
  declare function nodeBelongsToArgumentList(compiler: typeof ts, node: ts.Node): boolean;
348
353
 
349
- declare class WhenNode extends TestTreeNode {
350
- actionNode: ts.CallExpression;
351
- actionNameNode: ts.PropertyAccessExpression;
352
- abilityDiagnostics: Set<ts.Diagnostic> | undefined;
353
- target: ts.NodeArray<ts.Expression> | ts.NodeArray<ts.TypeNode>;
354
- constructor(compiler: typeof ts, brand: TestTreeNodeBrand, node: ts.CallExpression, parent: TestTree | TestTreeNode, flags: TestTreeNodeFlags, actionNode: ts.CallExpression, actionNameNode: ts.PropertyAccessExpression);
355
- }
354
+ declare function argumentIsProvided<T>(argumentNameText: string, node: T, enclosingNode: ts.Node, onDiagnostics: DiagnosticsHandler<Array<Diagnostic>>): node is NonNullable<T>;
355
+
356
+ declare function argumentOrTypeArgumentIsProvided<T>(argumentNameText: string, typeArgumentNameText: string, node: T, enclosingNode: ts.Node, onDiagnostics: DiagnosticsHandler<Array<Diagnostic>>): node is NonNullable<T>;
356
357
 
357
358
  interface EnvironmentOptions {
358
359
  /**
@@ -599,6 +600,12 @@ declare class EventEmitter {
599
600
  removeReporters(): void;
600
601
  }
601
602
 
603
+ declare class Reject {
604
+ #private;
605
+ constructor(compiler: typeof ts, typeChecker: ts.TypeChecker, resolvedConfig: ResolvedConfig);
606
+ argumentType(target: Array<[name: string, node: ts.Expression | ts.TypeNode | undefined]>, onDiagnostics: DiagnosticsHandler<Array<Diagnostic>>): boolean;
607
+ }
608
+
602
609
  interface MatchResult {
603
610
  explain: () => Array<Diagnostic>;
604
611
  isMatch: boolean;
@@ -624,7 +631,7 @@ declare class ExpectService {
624
631
  private toBeConstructableWith;
625
632
  private toHaveProperty;
626
633
  private toRaiseError;
627
- constructor(compiler: typeof ts, typeChecker: TypeChecker, resolvedConfig: ResolvedConfig);
634
+ constructor(compiler: typeof ts, typeChecker: TypeChecker, reject: Reject);
628
635
  match(assertion: AssertionNode, onDiagnostics: DiagnosticsHandler<Diagnostic | Array<Diagnostic>>): MatchResult | undefined;
629
636
  }
630
637
 
@@ -878,5 +885,11 @@ declare class WatchService {
878
885
  watch(cancellationToken: CancellationToken): AsyncIterable<Array<Task>>;
879
886
  }
880
887
 
881
- 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, ScribblerJsx, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, TargetResult, Task, TaskResult, TestResult, TestTree, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, WhenNode, addsPackageText, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, environmentOptions, fileViewText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, nodeBelongsToArgumentList, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
888
+ declare class WhenService {
889
+ #private;
890
+ constructor(reject: Reject, onDiagnostics: DiagnosticsHandler<Array<Diagnostic>>);
891
+ action(when: WhenNode): void;
892
+ }
893
+
894
+ export { AssertionNode, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileWatcher, InputService, Line, ListReporter, OptionBrand, OptionGroup, Options, OutputService, Path, PluginService, ProjectResult, ProjectService, Reject, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, Runner, Scribbler, ScribblerJsx, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, TargetResult, Task, TaskResult, TestResult, TestTree, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, WhenNode, WhenService, addsPackageText, argumentIsProvided, argumentOrTypeArgumentIsProvided, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, environmentOptions, fileViewText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, nodeBelongsToArgumentList, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
882
895
  export type { CodeFrameOptions, CommandLineOptions, ConfigFileOptions, DiagnosticsHandler, EnvironmentOptions, Event, EventHandler, FileWatchHandler, InputHandler, ItemDefinition, MatchResult, OptionDefinition, Plugin, Reporter, ReporterEvent, ResolvedConfig, ScribblerOptions, SelectHookContext, TargetResultStatus, TaskResultStatus, TypeChecker, WatchHandler, WatcherOptions };
package/build/tstyche.js CHANGED
@@ -572,7 +572,6 @@ class ManifestService {
572
572
  #manifestFilePath;
573
573
  #npmRegistry;
574
574
  #storePath;
575
- #supportedVersionRegex = /^(5)\.\d\.\d$/;
576
575
  constructor(storePath, npmRegistry, fetcher) {
577
576
  this.#storePath = storePath;
578
577
  this.#npmRegistry = npmRegistry;
@@ -602,7 +601,7 @@ class ManifestService {
602
601
  const versions = [];
603
602
  const packageMetadata = (await response.json());
604
603
  for (const [tag, meta] of Object.entries(packageMetadata.versions)) {
605
- if (this.#supportedVersionRegex.test(tag)) {
604
+ if (!tag.includes("-") && Version.isSatisfiedWith(tag, "4.7.2")) {
606
605
  versions.push(tag);
607
606
  packages[tag] = { integrity: meta.dist.integrity, tarball: meta.dist.tarball };
608
607
  }
@@ -701,6 +700,9 @@ class PackageService {
701
700
  if (response?.body != null) {
702
701
  const targetPath = `${packagePath}-${Math.random().toString(32).slice(2)}`;
703
702
  for await (const file of TarReader.extract(response.body)) {
703
+ if (!file.name.startsWith("package/")) {
704
+ continue;
705
+ }
704
706
  const filePath = Path.join(targetPath, file.name.replace("package/", ""));
705
707
  const directoryPath = Path.dirname(filePath);
706
708
  if (!existsSync(directoryPath)) {
@@ -2547,7 +2549,6 @@ var CancellationReason;
2547
2549
  (function (CancellationReason) {
2548
2550
  CancellationReason["ConfigChange"] = "configChange";
2549
2551
  CancellationReason["ConfigError"] = "configError";
2550
- CancellationReason["CollectError"] = "collectError";
2551
2552
  CancellationReason["FailFast"] = "failFast";
2552
2553
  CancellationReason["WatchClose"] = "watchClose";
2553
2554
  })(CancellationReason || (CancellationReason = {}));
@@ -2873,18 +2874,9 @@ class WatchService {
2873
2874
  }
2874
2875
  }
2875
2876
 
2876
- var TestTreeNodeBrand;
2877
- (function (TestTreeNodeBrand) {
2878
- TestTreeNodeBrand["Describe"] = "describe";
2879
- TestTreeNodeBrand["Test"] = "test";
2880
- TestTreeNodeBrand["Expect"] = "expect";
2881
- TestTreeNodeBrand["When"] = "when";
2882
- })(TestTreeNodeBrand || (TestTreeNodeBrand = {}));
2883
-
2884
2877
  class TestTreeNode {
2885
2878
  brand;
2886
2879
  children = [];
2887
- #compiler;
2888
2880
  diagnostics = new Set();
2889
2881
  flags;
2890
2882
  name = "";
@@ -2892,7 +2884,6 @@ class TestTreeNode {
2892
2884
  parent;
2893
2885
  constructor(compiler, brand, node, parent, flags) {
2894
2886
  this.brand = brand;
2895
- this.#compiler = compiler;
2896
2887
  this.node = node;
2897
2888
  this.parent = parent;
2898
2889
  this.flags = flags;
@@ -2908,34 +2899,6 @@ class TestTreeNode {
2908
2899
  }
2909
2900
  }
2910
2901
  }
2911
- validate() {
2912
- const diagnostics = [];
2913
- const getText = (node) => `'${node.expression.getText()}()' cannot be nested within '${this.node.expression.getText()}()'.`;
2914
- const getParentCallExpression = (node) => {
2915
- while (!this.#compiler.isCallExpression(node.parent)) {
2916
- node = node.parent;
2917
- }
2918
- return node.parent;
2919
- };
2920
- switch (this.brand) {
2921
- case TestTreeNodeBrand.Describe:
2922
- for (const child of this.children) {
2923
- if (child.brand === TestTreeNodeBrand.Expect || child.brand === TestTreeNodeBrand.When) {
2924
- diagnostics.push(Diagnostic.error(getText(child.node), DiagnosticOrigin.fromNode(getParentCallExpression(child.node))));
2925
- }
2926
- }
2927
- break;
2928
- case TestTreeNodeBrand.Test:
2929
- case TestTreeNodeBrand.Expect:
2930
- for (const child of this.children) {
2931
- if (child.brand === TestTreeNodeBrand.Describe || child.brand === TestTreeNodeBrand.Test) {
2932
- diagnostics.push(Diagnostic.error(getText(child.node), DiagnosticOrigin.fromNode(child.node)));
2933
- }
2934
- }
2935
- break;
2936
- }
2937
- return diagnostics;
2938
- }
2939
2902
  }
2940
2903
 
2941
2904
  class AssertionNode extends TestTreeNode {
@@ -3033,6 +2996,11 @@ class AbilityLayer {
3033
2996
  this.#nodes = [];
3034
2997
  this.#text = "";
3035
2998
  }
2999
+ #eraseTrailingComma(node, parent) {
3000
+ if (node.hasTrailingComma) {
3001
+ this.#addRanges(parent, [{ start: node.end - 1, end: node.end }]);
3002
+ }
3003
+ }
3036
3004
  handleWhen(whenNode) {
3037
3005
  const whenStart = whenNode.node.getStart();
3038
3006
  const whenExpressionEnd = whenNode.node.expression.getEnd();
@@ -3040,13 +3008,14 @@ class AbilityLayer {
3040
3008
  const actionNameEnd = whenNode.actionNameNode.getEnd();
3041
3009
  switch (whenNode.actionNameNode.name.text) {
3042
3010
  case "isCalledWith":
3011
+ this.#eraseTrailingComma(whenNode.target, whenNode);
3043
3012
  this.#addRanges(whenNode, [
3044
3013
  {
3045
- end: whenExpressionEnd,
3046
3014
  start: whenStart,
3015
+ end: whenExpressionEnd,
3047
3016
  replacement: nodeBelongsToArgumentList(this.#compiler, whenNode.actionNode) ? "" : ";",
3048
3017
  },
3049
- { end: actionNameEnd, start: whenEnd },
3018
+ { start: whenEnd, end: actionNameEnd },
3050
3019
  ]);
3051
3020
  break;
3052
3021
  }
@@ -3059,28 +3028,30 @@ class AbilityLayer {
3059
3028
  switch (assertionNode.matcherNameNode.name.text) {
3060
3029
  case "toBeApplicable":
3061
3030
  this.#addRanges(assertionNode, [
3062
- { end: expectExpressionEnd, start: expectStart },
3063
- { end: matcherNameEnd, start: expectEnd },
3031
+ { start: expectStart, end: expectExpressionEnd },
3032
+ { start: expectEnd, end: matcherNameEnd },
3064
3033
  ]);
3065
3034
  break;
3066
3035
  case "toBeCallableWith":
3036
+ this.#eraseTrailingComma(assertionNode.source, assertionNode);
3067
3037
  this.#addRanges(assertionNode, [
3068
3038
  {
3069
- end: expectExpressionEnd,
3070
3039
  start: expectStart,
3040
+ end: expectExpressionEnd,
3071
3041
  replacement: nodeBelongsToArgumentList(this.#compiler, assertionNode.matcherNode) ? "" : ";",
3072
3042
  },
3073
- { end: matcherNameEnd, start: expectEnd },
3043
+ { start: expectEnd, end: matcherNameEnd },
3074
3044
  ]);
3075
3045
  break;
3076
3046
  case "toBeConstructableWith":
3047
+ this.#eraseTrailingComma(assertionNode.source, assertionNode);
3077
3048
  this.#addRanges(assertionNode, [
3078
3049
  {
3079
- end: expectExpressionEnd,
3080
3050
  start: expectStart,
3051
+ end: expectExpressionEnd,
3081
3052
  replacement: nodeBelongsToArgumentList(this.#compiler, assertionNode.matcherNode) ? "new" : "; new",
3082
3053
  },
3083
- { end: matcherNameEnd, start: expectEnd },
3054
+ { start: expectEnd, end: matcherNameEnd },
3084
3055
  ]);
3085
3056
  break;
3086
3057
  }
@@ -3097,6 +3068,14 @@ class CollectDiagnosticText {
3097
3068
  }
3098
3069
  }
3099
3070
 
3071
+ var TestTreeNodeBrand;
3072
+ (function (TestTreeNodeBrand) {
3073
+ TestTreeNodeBrand["Describe"] = "describe";
3074
+ TestTreeNodeBrand["Test"] = "test";
3075
+ TestTreeNodeBrand["Expect"] = "expect";
3076
+ TestTreeNodeBrand["When"] = "when";
3077
+ })(TestTreeNodeBrand || (TestTreeNodeBrand = {}));
3078
+
3100
3079
  var TestTreeNodeFlags;
3101
3080
  (function (TestTreeNodeFlags) {
3102
3081
  TestTreeNodeFlags[TestTreeNodeFlags["None"] = 0] = "None";
@@ -3110,19 +3089,8 @@ class IdentifierLookup {
3110
3089
  #compiler;
3111
3090
  #identifiers;
3112
3091
  #moduleSpecifiers = ['"tstyche"', "'tstyche'"];
3113
- constructor(compiler, identifiers) {
3092
+ constructor(compiler) {
3114
3093
  this.#compiler = compiler;
3115
- this.#identifiers = identifiers ?? {
3116
- namedImports: {
3117
- describe: undefined,
3118
- expect: undefined,
3119
- it: undefined,
3120
- namespace: undefined,
3121
- test: undefined,
3122
- when: undefined,
3123
- },
3124
- namespace: undefined,
3125
- };
3126
3094
  }
3127
3095
  handleImportDeclaration(node) {
3128
3096
  if (this.#moduleSpecifiers.includes(node.moduleSpecifier.getText()) &&
@@ -3150,7 +3118,20 @@ class IdentifierLookup {
3150
3118
  }
3151
3119
  }
3152
3120
  }
3153
- resolveTestMemberMeta(node) {
3121
+ open() {
3122
+ this.#identifiers = {
3123
+ namedImports: {
3124
+ describe: undefined,
3125
+ expect: undefined,
3126
+ it: undefined,
3127
+ namespace: undefined,
3128
+ test: undefined,
3129
+ when: undefined,
3130
+ },
3131
+ namespace: undefined,
3132
+ };
3133
+ }
3134
+ resolveTestTreeNodeMeta(node) {
3154
3135
  let flags = TestTreeNodeFlags.None;
3155
3136
  let expression = node.expression;
3156
3137
  while (this.#compiler.isPropertyAccessExpression(expression)) {
@@ -3233,72 +3214,72 @@ class CollectService {
3233
3214
  #abilityLayer;
3234
3215
  #compiler;
3235
3216
  #identifierLookup;
3236
- #projectService;
3237
- #resolvedConfig;
3238
- #testTree;
3239
3217
  constructor(compiler, projectService, resolvedConfig) {
3240
3218
  this.#compiler = compiler;
3241
- this.#projectService = projectService;
3242
- this.#resolvedConfig = resolvedConfig;
3243
- this.#abilityLayer = new AbilityLayer(compiler, this.#projectService, this.#resolvedConfig);
3219
+ this.#abilityLayer = new AbilityLayer(compiler, projectService, resolvedConfig);
3244
3220
  this.#identifierLookup = new IdentifierLookup(compiler);
3245
3221
  }
3246
- #collectTestTreeNodes(node, parent) {
3222
+ #collectTestTreeNodes(node, parent, testTree) {
3247
3223
  if (this.#compiler.isCallExpression(node)) {
3248
- const meta = this.#identifierLookup.resolveTestMemberMeta(node);
3249
- if (meta != null && (meta.brand === TestTreeNodeBrand.Describe || meta.brand === TestTreeNodeBrand.Test)) {
3250
- const testTreeNode = new TestTreeNode(this.#compiler, meta.brand, node, parent, meta.flags);
3251
- this.#compiler.forEachChild(node, (node) => {
3252
- this.#collectTestTreeNodes(node, testTreeNode);
3253
- });
3254
- this.#onNode(testTreeNode, parent);
3255
- return;
3256
- }
3257
- if (meta != null && meta.brand === TestTreeNodeBrand.Expect) {
3258
- const modifierNode = this.#getChainedNode(node, "type");
3259
- if (!modifierNode) {
3224
+ const meta = this.#identifierLookup.resolveTestTreeNodeMeta(node);
3225
+ if (meta != null) {
3226
+ if (!this.#checkNode(node, meta, parent)) {
3260
3227
  return;
3261
3228
  }
3262
- const notNode = this.#getChainedNode(modifierNode, "not");
3263
- const matcherNameNode = this.#getChainedNode(notNode ?? modifierNode);
3264
- if (!matcherNameNode) {
3265
- return;
3266
- }
3267
- const matcherNode = this.#getMatcherNode(matcherNameNode);
3268
- if (!matcherNode) {
3229
+ if (meta.brand === TestTreeNodeBrand.Describe || meta.brand === TestTreeNodeBrand.Test) {
3230
+ const testTreeNode = new TestTreeNode(this.#compiler, meta.brand, node, parent, meta.flags);
3231
+ this.#compiler.forEachChild(node, (node) => {
3232
+ this.#collectTestTreeNodes(node, testTreeNode, testTree);
3233
+ });
3234
+ this.#onNode(testTreeNode, parent, testTree);
3269
3235
  return;
3270
3236
  }
3271
- const assertionNode = new AssertionNode(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, matcherNameNode, modifierNode, notNode);
3272
- this.#abilityLayer.handleAssertion(assertionNode);
3273
- this.#compiler.forEachChild(node, (node) => {
3274
- this.#collectTestTreeNodes(node, assertionNode);
3275
- });
3276
- this.#onNode(assertionNode, parent);
3277
- return;
3278
- }
3279
- if (meta != null && meta.brand === TestTreeNodeBrand.When) {
3280
- const actionNameNode = this.#getChainedNode(node);
3281
- if (!actionNameNode) {
3237
+ if (meta.brand === TestTreeNodeBrand.Expect) {
3238
+ const modifierNode = this.#getChainedNode(node, "type");
3239
+ if (!modifierNode) {
3240
+ return;
3241
+ }
3242
+ const notNode = this.#getChainedNode(modifierNode, "not");
3243
+ const matcherNameNode = this.#getChainedNode(notNode ?? modifierNode);
3244
+ if (!matcherNameNode) {
3245
+ return;
3246
+ }
3247
+ const matcherNode = this.#getMatcherNode(matcherNameNode);
3248
+ if (!matcherNode) {
3249
+ return;
3250
+ }
3251
+ const assertionNode = new AssertionNode(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, matcherNameNode, modifierNode, notNode);
3252
+ this.#abilityLayer.handleAssertion(assertionNode);
3253
+ this.#compiler.forEachChild(node, (node) => {
3254
+ this.#collectTestTreeNodes(node, assertionNode, testTree);
3255
+ });
3256
+ this.#onNode(assertionNode, parent, testTree);
3282
3257
  return;
3283
3258
  }
3284
- const actionNode = this.#getActionNode(actionNameNode);
3285
- if (!actionNode) {
3259
+ if (meta.brand === TestTreeNodeBrand.When) {
3260
+ const actionNameNode = this.#getChainedNode(node);
3261
+ if (!actionNameNode) {
3262
+ return;
3263
+ }
3264
+ const actionNode = this.#getActionNode(actionNameNode);
3265
+ if (!actionNode) {
3266
+ return;
3267
+ }
3268
+ this.#compiler.forEachChild(actionNode, (node) => {
3269
+ if (this.#compiler.isCallExpression(node)) {
3270
+ const meta = this.#identifierLookup.resolveTestTreeNodeMeta(node);
3271
+ if (meta?.brand === TestTreeNodeBrand.Describe || meta?.brand === TestTreeNodeBrand.Test) {
3272
+ const text = CollectDiagnosticText.cannotBeNestedWithin(meta.identifier, "when");
3273
+ const origin = DiagnosticOrigin.fromNode(node);
3274
+ this.#onDiagnostics(Diagnostic.error(text, origin));
3275
+ }
3276
+ }
3277
+ });
3278
+ const whenNode = new WhenNode(this.#compiler, meta.brand, node, parent, meta.flags, actionNode, actionNameNode);
3279
+ this.#abilityLayer.handleWhen(whenNode);
3280
+ this.#onNode(whenNode, parent, testTree);
3286
3281
  return;
3287
3282
  }
3288
- this.#compiler.forEachChild(actionNode, (node) => {
3289
- if (this.#compiler.isCallExpression(node)) {
3290
- const meta = this.#identifierLookup.resolveTestMemberMeta(node);
3291
- if (meta != null && (meta.brand === TestTreeNodeBrand.Describe || meta.brand === TestTreeNodeBrand.Test)) {
3292
- const text = CollectDiagnosticText.cannotBeNestedWithin(meta.identifier, "when");
3293
- const origin = DiagnosticOrigin.fromNode(node);
3294
- this.#onDiagnostics(Diagnostic.error(text, origin));
3295
- }
3296
- }
3297
- });
3298
- const whenNode = new WhenNode(this.#compiler, meta.brand, node, parent, meta.flags, actionNode, actionNameNode);
3299
- this.#abilityLayer.handleWhen(whenNode);
3300
- this.#onNode(whenNode, parent);
3301
- return;
3302
3283
  }
3303
3284
  }
3304
3285
  if (this.#compiler.isImportDeclaration(node)) {
@@ -3306,20 +3287,45 @@ class CollectService {
3306
3287
  return;
3307
3288
  }
3308
3289
  this.#compiler.forEachChild(node, (node) => {
3309
- this.#collectTestTreeNodes(node, parent);
3290
+ this.#collectTestTreeNodes(node, parent, testTree);
3310
3291
  });
3311
3292
  }
3312
3293
  createTestTree(sourceFile, semanticDiagnostics = []) {
3313
3294
  const testTree = new TestTree(new Set(semanticDiagnostics), sourceFile);
3314
3295
  EventEmitter.dispatch(["collect:start", { tree: testTree }]);
3315
- this.#testTree = testTree;
3316
3296
  this.#abilityLayer.open(sourceFile);
3317
- this.#collectTestTreeNodes(sourceFile, testTree);
3297
+ this.#identifierLookup.open();
3298
+ this.#collectTestTreeNodes(sourceFile, testTree, testTree);
3318
3299
  this.#abilityLayer.close();
3319
- this.#testTree = undefined;
3320
3300
  EventEmitter.dispatch(["collect:end", { tree: testTree }]);
3321
3301
  return testTree;
3322
3302
  }
3303
+ #checkNode(node, meta, parent) {
3304
+ if ("brand" in parent && !this.#isNodeAllowed(meta, parent)) {
3305
+ const text = CollectDiagnosticText.cannotBeNestedWithin(meta.identifier, parent.node.expression.getText());
3306
+ const origin = DiagnosticOrigin.fromNode(node);
3307
+ this.#onDiagnostics(Diagnostic.error(text, origin));
3308
+ return false;
3309
+ }
3310
+ return true;
3311
+ }
3312
+ #isNodeAllowed(meta, parent) {
3313
+ switch (meta.brand) {
3314
+ case TestTreeNodeBrand.Describe:
3315
+ case TestTreeNodeBrand.Test:
3316
+ if (parent.brand === TestTreeNodeBrand.Test || parent.brand === TestTreeNodeBrand.Expect) {
3317
+ return false;
3318
+ }
3319
+ break;
3320
+ case TestTreeNodeBrand.Expect:
3321
+ case TestTreeNodeBrand.When:
3322
+ if (parent.brand === TestTreeNodeBrand.Describe) {
3323
+ return false;
3324
+ }
3325
+ break;
3326
+ }
3327
+ return true;
3328
+ }
3323
3329
  #getChainedNode({ parent }, name) {
3324
3330
  if (!this.#compiler.isPropertyAccessExpression(parent)) {
3325
3331
  return;
@@ -3350,10 +3356,10 @@ class CollectService {
3350
3356
  #onDiagnostics(diagnostic) {
3351
3357
  EventEmitter.dispatch(["collect:error", { diagnostics: [diagnostic] }]);
3352
3358
  }
3353
- #onNode(node, parent) {
3359
+ #onNode(node, parent, testTree) {
3354
3360
  parent.children.push(node);
3355
3361
  if (node.flags & TestTreeNodeFlags.Only) {
3356
- this.#testTree.hasOnly = true;
3362
+ testTree.hasOnly = true;
3357
3363
  }
3358
3364
  EventEmitter.dispatch(["collect:node", { node }]);
3359
3365
  }
@@ -3409,7 +3415,6 @@ class ProjectService {
3409
3415
  }
3410
3416
  #getDefaultCompilerOptions() {
3411
3417
  const defaultCompilerOptions = {
3412
- allowImportingTsExtensions: true,
3413
3418
  allowJs: true,
3414
3419
  checkJs: true,
3415
3420
  exactOptionalPropertyTypes: true,
@@ -3420,8 +3425,11 @@ class ProjectService {
3420
3425
  resolveJsonModule: true,
3421
3426
  strict: true,
3422
3427
  target: this.#compiler.ScriptTarget.ESNext,
3423
- verbatimModuleSyntax: true,
3424
3428
  };
3429
+ if (Version.isSatisfiedWith(this.#compiler.version, "5.0")) {
3430
+ defaultCompilerOptions.allowImportingTsExtensions = true;
3431
+ defaultCompilerOptions.verbatimModuleSyntax = true;
3432
+ }
3425
3433
  return defaultCompilerOptions;
3426
3434
  }
3427
3435
  getDefaultProject(filePath) {
@@ -3504,22 +3512,41 @@ var RunMode;
3504
3512
  RunMode[RunMode["Todo"] = 8] = "Todo";
3505
3513
  })(RunMode || (RunMode = {}));
3506
3514
 
3507
- function capitalize(text) {
3508
- return text.replace(/^./, text.charAt(0).toUpperCase());
3509
- }
3510
-
3511
- class ExpectDiagnosticText {
3512
- static argumentCannotBeOfType(argumentNameText, typeText) {
3513
- return `An argument for '${argumentNameText}' cannot be of the '${typeText}' type.`;
3515
+ class EnsureDiagnosticText {
3516
+ static argumentMustBeProvided(argumentNameText) {
3517
+ return `An argument for '${argumentNameText}' must be provided.`;
3514
3518
  }
3515
3519
  static argumentOrTypeArgumentMustBeProvided(argumentNameText, typeArgumentNameText) {
3516
3520
  return `An argument for '${argumentNameText}' or type argument for '${typeArgumentNameText}' must be provided.`;
3517
3521
  }
3522
+ }
3523
+
3524
+ function argumentIsProvided(argumentNameText, node, enclosingNode, onDiagnostics) {
3525
+ if (!node) {
3526
+ const text = EnsureDiagnosticText.argumentMustBeProvided(argumentNameText);
3527
+ const origin = DiagnosticOrigin.fromNode(enclosingNode);
3528
+ onDiagnostics([Diagnostic.error(text, origin)]);
3529
+ return false;
3530
+ }
3531
+ return true;
3532
+ }
3533
+
3534
+ function argumentOrTypeArgumentIsProvided(argumentNameText, typeArgumentNameText, node, enclosingNode, onDiagnostics) {
3535
+ if (!node) {
3536
+ const text = EnsureDiagnosticText.argumentOrTypeArgumentMustBeProvided(argumentNameText, typeArgumentNameText);
3537
+ const origin = DiagnosticOrigin.fromNode(enclosingNode);
3538
+ onDiagnostics([Diagnostic.error(text, origin)]);
3539
+ return false;
3540
+ }
3541
+ return true;
3542
+ }
3543
+
3544
+ class ExpectDiagnosticText {
3518
3545
  static argumentMustBe(argumentNameText, expectedText) {
3519
3546
  return `An argument for '${argumentNameText}' must be ${expectedText}.`;
3520
3547
  }
3521
- static argumentMustBeProvided(argumentNameText) {
3522
- return `An argument for '${argumentNameText}' must be provided.`;
3548
+ static typeArgumentMustBe(argumentNameText, expectedText) {
3549
+ return `A type argument for '${argumentNameText}' must be ${expectedText}.`;
3523
3550
  }
3524
3551
  static isCallable(isExpression, targetText) {
3525
3552
  return `${isExpression ? "Expression" : "Type"} is callable ${targetText}.`;
@@ -3545,12 +3572,12 @@ class ExpectDiagnosticText {
3545
3572
  static cannotBeApplied(targetText) {
3546
3573
  return `The decorator function cannot be applied${targetText}.`;
3547
3574
  }
3548
- static doesNotHaveProperty(typeText, propertyNameText) {
3549
- return `Type '${typeText}' does not have property '${propertyNameText}'.`;
3550
- }
3551
3575
  static hasProperty(typeText, propertyNameText) {
3552
3576
  return `Type '${typeText}' has property '${propertyNameText}'.`;
3553
3577
  }
3578
+ static doesNotHaveProperty(typeText, propertyNameText) {
3579
+ return `Type '${typeText}' does not have property '${propertyNameText}'.`;
3580
+ }
3554
3581
  static didYouMeanToUse(suggestionText) {
3555
3582
  return `Did you mean to use ${suggestionText}?`;
3556
3583
  }
@@ -3563,12 +3590,6 @@ class ExpectDiagnosticText {
3563
3590
  static raisedTypeError(count = 1) {
3564
3591
  return `The raised type error${count === 1 ? "" : "s"}:`;
3565
3592
  }
3566
- static typeArgumentCannotBeOfType(argumentNameText, typeText) {
3567
- return `A type argument for '${argumentNameText}' cannot be of the '${typeText}' type.`;
3568
- }
3569
- static typeArgumentMustBe(argumentNameText, expectedText) {
3570
- return `A type argument for '${argumentNameText}' must be ${expectedText}.`;
3571
- }
3572
3593
  static raisedError(isExpression, count, targetCount) {
3573
3594
  let countText = "a";
3574
3595
  if (count > 1 || targetCount > 1) {
@@ -3612,13 +3633,6 @@ class ExpectDiagnosticText {
3612
3633
  static typesOfPropertyAreNotCompatible(propertyNameText) {
3613
3634
  return `Types of property '${propertyNameText}' are not compatible.`;
3614
3635
  }
3615
- static typeWasRejected(typeText) {
3616
- const optionNameText = `reject${capitalize(typeText)}Type`;
3617
- return [
3618
- `The '${typeText}' type was rejected because the '${optionNameText}' option is enabled.`,
3619
- `If this check is necessary, pass '${typeText}' as the type argument explicitly.`,
3620
- ];
3621
- }
3622
3636
  }
3623
3637
 
3624
3638
  class MatchWorker {
@@ -4327,7 +4341,7 @@ class ToRaiseError {
4327
4341
 
4328
4342
  class ExpectService {
4329
4343
  #compiler;
4330
- #rejectTypes = new Set();
4344
+ #reject;
4331
4345
  #typeChecker;
4332
4346
  toAcceptProps;
4333
4347
  toBe;
@@ -4338,15 +4352,10 @@ class ExpectService {
4338
4352
  toBeConstructableWith;
4339
4353
  toHaveProperty;
4340
4354
  toRaiseError;
4341
- constructor(compiler, typeChecker, resolvedConfig) {
4355
+ constructor(compiler, typeChecker, reject) {
4342
4356
  this.#compiler = compiler;
4357
+ this.#reject = reject;
4343
4358
  this.#typeChecker = typeChecker;
4344
- if (resolvedConfig?.rejectAnyType) {
4345
- this.#rejectTypes.add("any");
4346
- }
4347
- if (resolvedConfig?.rejectNeverType) {
4348
- this.#rejectTypes.add("never");
4349
- }
4350
4359
  this.toAcceptProps = new ToAcceptProps(compiler, typeChecker);
4351
4360
  this.toBe = new ToBe();
4352
4361
  this.toBeApplicable = new ToBeApplicable(compiler);
@@ -4359,13 +4368,15 @@ class ExpectService {
4359
4368
  }
4360
4369
  match(assertion, onDiagnostics) {
4361
4370
  const matcherNameText = assertion.matcherNameNode.name.text;
4362
- if (!assertion.source[0]) {
4363
- this.#onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
4371
+ if (!argumentOrTypeArgumentIsProvided("source", "Source", assertion.source[0], assertion.node.expression, onDiagnostics)) {
4364
4372
  return;
4365
4373
  }
4366
4374
  const matchWorker = new MatchWorker(this.#compiler, this.#typeChecker, assertion);
4367
4375
  if (!(matcherNameText === "toRaiseError" && assertion.isNot === false) &&
4368
- this.#rejectsTypeArguments(matchWorker, onDiagnostics)) {
4376
+ this.#reject.argumentType([
4377
+ ["source", assertion.source[0]],
4378
+ ["target", assertion.target?.[0]],
4379
+ ], onDiagnostics)) {
4369
4380
  return;
4370
4381
  }
4371
4382
  switch (matcherNameText) {
@@ -4373,8 +4384,7 @@ class ExpectService {
4373
4384
  case "toBe":
4374
4385
  case "toBeAssignableTo":
4375
4386
  case "toBeAssignableWith":
4376
- if (!assertion.target?.[0]) {
4377
- this.#onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
4387
+ if (!argumentOrTypeArgumentIsProvided("target", "Target", assertion.target?.[0], assertion.matcherNameNode.name, onDiagnostics)) {
4378
4388
  return;
4379
4389
  }
4380
4390
  return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
@@ -4385,8 +4395,7 @@ class ExpectService {
4385
4395
  case "toRaiseError":
4386
4396
  return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target, onDiagnostics);
4387
4397
  case "toHaveProperty":
4388
- if (!assertion.target?.[0]) {
4389
- this.#onTargetArgumentMustBeProvided("key", assertion, onDiagnostics);
4398
+ if (!argumentIsProvided("key", assertion.target?.[0], assertion.matcherNameNode.name, onDiagnostics)) {
4390
4399
  return;
4391
4400
  }
4392
4401
  return this.toHaveProperty.match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
@@ -4400,42 +4409,61 @@ class ExpectService {
4400
4409
  const origin = DiagnosticOrigin.fromNode(assertion.matcherNameNode.name);
4401
4410
  onDiagnostics(Diagnostic.error(text, origin));
4402
4411
  }
4403
- #onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics) {
4404
- const text = ExpectDiagnosticText.argumentOrTypeArgumentMustBeProvided("source", "Source");
4405
- const origin = DiagnosticOrigin.fromNode(assertion.node.expression);
4406
- onDiagnostics(Diagnostic.error(text, origin));
4412
+ }
4413
+
4414
+ function capitalize(text) {
4415
+ return text.replace(/^./, text.charAt(0).toUpperCase());
4416
+ }
4417
+
4418
+ class RejectDiagnosticText {
4419
+ static argumentCannotBeOfType(argumentNameText, typeText) {
4420
+ return `An argument for '${argumentNameText}' cannot be of the '${typeText}' type.`;
4407
4421
  }
4408
- #onTargetArgumentMustBeProvided(argumentNameText, assertion, onDiagnostics) {
4409
- const text = ExpectDiagnosticText.argumentMustBeProvided(argumentNameText);
4410
- const origin = DiagnosticOrigin.fromNode(assertion.matcherNameNode.name);
4411
- onDiagnostics(Diagnostic.error(text, origin));
4422
+ static typeArgumentCannotBeOfType(argumentNameText, typeText) {
4423
+ return `A type argument for '${argumentNameText}' cannot be of the '${typeText}' type.`;
4412
4424
  }
4413
- #onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics) {
4414
- const text = ExpectDiagnosticText.argumentOrTypeArgumentMustBeProvided("target", "Target");
4415
- const origin = DiagnosticOrigin.fromNode(assertion.matcherNameNode.name);
4416
- onDiagnostics(Diagnostic.error(text, origin));
4425
+ static typeWasRejected(typeText) {
4426
+ const optionNameText = `reject${capitalize(typeText)}Type`;
4427
+ return [
4428
+ `The '${typeText}' type was rejected because the '${optionNameText}' option is enabled.`,
4429
+ `If this check is necessary, pass '${typeText}' as the type argument explicitly.`,
4430
+ ];
4431
+ }
4432
+ }
4433
+
4434
+ class Reject {
4435
+ #compiler;
4436
+ #rejectedArgumentTypes = new Set();
4437
+ #typeChecker;
4438
+ constructor(compiler, typeChecker, resolvedConfig) {
4439
+ this.#compiler = compiler;
4440
+ this.#typeChecker = typeChecker;
4441
+ if (resolvedConfig?.rejectAnyType) {
4442
+ this.#rejectedArgumentTypes.add("any");
4443
+ }
4444
+ if (resolvedConfig?.rejectNeverType) {
4445
+ this.#rejectedArgumentTypes.add("never");
4446
+ }
4417
4447
  }
4418
- #rejectsTypeArguments(matchWorker, onDiagnostics) {
4419
- for (const rejectedType of this.#rejectTypes) {
4448
+ argumentType(target, onDiagnostics) {
4449
+ for (const rejectedType of this.#rejectedArgumentTypes) {
4420
4450
  const allowedKeyword = this.#compiler.SyntaxKind[`${capitalize(rejectedType)}Keyword`];
4421
- if (matchWorker.assertion.source[0]?.kind === allowedKeyword ||
4422
- matchWorker.assertion.target?.[0]?.kind === allowedKeyword) {
4451
+ if (target.some(([, node]) => node?.kind === allowedKeyword)) {
4423
4452
  continue;
4424
4453
  }
4425
- for (const argumentName of ["source", "target"]) {
4426
- const argumentNode = matchWorker.assertion[argumentName]?.[0];
4427
- if (!argumentNode) {
4454
+ for (const [name, node] of target) {
4455
+ if (!node) {
4428
4456
  continue;
4429
4457
  }
4430
- if (matchWorker.getType(argumentNode).flags & this.#compiler.TypeFlags[capitalize(rejectedType)]) {
4458
+ if (this.#typeChecker.getTypeAtLocation(node).flags & this.#compiler.TypeFlags[capitalize(rejectedType)]) {
4431
4459
  const text = [
4432
- nodeBelongsToArgumentList(this.#compiler, argumentNode)
4433
- ? ExpectDiagnosticText.argumentCannotBeOfType(argumentName, rejectedType)
4434
- : ExpectDiagnosticText.typeArgumentCannotBeOfType(capitalize(argumentName), rejectedType),
4435
- ...ExpectDiagnosticText.typeWasRejected(rejectedType),
4460
+ nodeBelongsToArgumentList(this.#compiler, node)
4461
+ ? RejectDiagnosticText.argumentCannotBeOfType(name, rejectedType)
4462
+ : RejectDiagnosticText.typeArgumentCannotBeOfType(capitalize(name), rejectedType),
4463
+ ...RejectDiagnosticText.typeWasRejected(rejectedType),
4436
4464
  ];
4437
- const origin = DiagnosticOrigin.fromNode(argumentNode);
4438
- onDiagnostics(Diagnostic.error(text, origin));
4465
+ const origin = DiagnosticOrigin.fromNode(node);
4466
+ onDiagnostics([Diagnostic.error(text, origin)]);
4439
4467
  return true;
4440
4468
  }
4441
4469
  }
@@ -4444,20 +4472,78 @@ class ExpectService {
4444
4472
  }
4445
4473
  }
4446
4474
 
4475
+ class WhenDiagnosticText {
4476
+ static actionIsNotSupported(actionNameText) {
4477
+ return `The '.${actionNameText}()' action is not supported.`;
4478
+ }
4479
+ }
4480
+
4481
+ class WhenService {
4482
+ #onDiagnostics;
4483
+ #reject;
4484
+ constructor(reject, onDiagnostics) {
4485
+ this.#reject = reject;
4486
+ this.#onDiagnostics = onDiagnostics;
4487
+ }
4488
+ action(when) {
4489
+ if (!argumentIsProvided("target", when.target[0], when.node.expression, this.#onDiagnostics) ||
4490
+ this.#reject.argumentType([["target", when.target[0]]], this.#onDiagnostics)) {
4491
+ return;
4492
+ }
4493
+ const actionNameText = when.actionNameNode.name.getText();
4494
+ switch (actionNameText) {
4495
+ case "isCalledWith":
4496
+ break;
4497
+ default:
4498
+ this.#onActionIsNotSupported(actionNameText, when, this.#onDiagnostics);
4499
+ return;
4500
+ }
4501
+ if (when.abilityDiagnostics != null && when.abilityDiagnostics.size > 0) {
4502
+ const diagnostics = [];
4503
+ for (const diagnostic of when.abilityDiagnostics) {
4504
+ if (isDiagnosticWithLocation(diagnostic)) {
4505
+ const text = getDiagnosticMessageText(diagnostic);
4506
+ let origin;
4507
+ if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, when.node)) {
4508
+ origin = DiagnosticOrigin.fromNodes(when.target);
4509
+ }
4510
+ else {
4511
+ origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), when.node.getSourceFile());
4512
+ }
4513
+ let related;
4514
+ if (diagnostic.relatedInformation != null) {
4515
+ related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
4516
+ }
4517
+ diagnostics.push(Diagnostic.error(text, origin).add({ related }));
4518
+ }
4519
+ }
4520
+ this.#onDiagnostics(diagnostics);
4521
+ }
4522
+ }
4523
+ #onActionIsNotSupported(actionNameText, when, onDiagnostics) {
4524
+ const text = WhenDiagnosticText.actionIsNotSupported(actionNameText);
4525
+ const origin = DiagnosticOrigin.fromNode(when.actionNameNode.name);
4526
+ onDiagnostics([Diagnostic.error(text, origin)]);
4527
+ }
4528
+ }
4529
+
4447
4530
  class TestTreeWalker {
4448
4531
  #cancellationToken;
4449
4532
  #expectService;
4450
4533
  #hasOnly;
4534
+ #onTaskDiagnostics;
4451
4535
  #position;
4452
4536
  #resolvedConfig;
4453
- #taskResult;
4454
- constructor(compiler, typeChecker, resolvedConfig, options) {
4537
+ #whenService;
4538
+ constructor(compiler, typeChecker, resolvedConfig, onTaskDiagnostics, options) {
4455
4539
  this.#resolvedConfig = resolvedConfig;
4540
+ this.#onTaskDiagnostics = onTaskDiagnostics;
4456
4541
  this.#cancellationToken = options.cancellationToken;
4457
4542
  this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
4458
4543
  this.#position = options.position;
4459
- this.#taskResult = options.taskResult;
4460
- this.#expectService = new ExpectService(compiler, typeChecker, this.#resolvedConfig);
4544
+ const reject = new Reject(compiler, typeChecker, resolvedConfig);
4545
+ this.#expectService = new ExpectService(compiler, typeChecker, reject);
4546
+ this.#whenService = new WhenService(reject, onTaskDiagnostics);
4461
4547
  }
4462
4548
  #resolveRunMode(mode, testNode) {
4463
4549
  if (testNode.flags & TestTreeNodeFlags.Fail) {
@@ -4482,28 +4568,23 @@ class TestTreeWalker {
4482
4568
  }
4483
4569
  return mode;
4484
4570
  }
4485
- visit(testNodes, runMode, parentResult) {
4486
- for (const testNode of testNodes) {
4571
+ visit(nodes, runMode, parentResult) {
4572
+ for (const node of nodes) {
4487
4573
  if (this.#cancellationToken?.isCancellationRequested) {
4488
4574
  break;
4489
4575
  }
4490
- const validationError = testNode.validate();
4491
- if (validationError.length > 0) {
4492
- EventEmitter.dispatch(["task:error", { diagnostics: validationError, result: this.#taskResult }]);
4493
- break;
4494
- }
4495
- switch (testNode.brand) {
4576
+ switch (node.brand) {
4496
4577
  case TestTreeNodeBrand.Describe:
4497
- this.#visitDescribe(testNode, runMode, parentResult);
4578
+ this.#visitDescribe(node, runMode, parentResult);
4498
4579
  break;
4499
4580
  case TestTreeNodeBrand.Test:
4500
- this.#visitTest(testNode, runMode, parentResult);
4581
+ this.#visitTest(node, runMode, parentResult);
4501
4582
  break;
4502
4583
  case TestTreeNodeBrand.Expect:
4503
- this.#visitAssertion(testNode, runMode, parentResult);
4584
+ this.#visitAssertion(node, runMode, parentResult);
4504
4585
  break;
4505
4586
  case TestTreeNodeBrand.When:
4506
- this.#visitWhen(testNode, runMode, parentResult);
4587
+ this.#visitWhen(node);
4507
4588
  break;
4508
4589
  }
4509
4590
  }
@@ -4554,13 +4635,7 @@ class TestTreeWalker {
4554
4635
  runMode = this.#resolveRunMode(runMode, describe);
4555
4636
  if (!(runMode & RunMode.Skip || (this.#hasOnly && !(runMode & RunMode.Only)) || runMode & RunMode.Todo) &&
4556
4637
  describe.diagnostics.size > 0) {
4557
- EventEmitter.dispatch([
4558
- "task:error",
4559
- {
4560
- diagnostics: Diagnostic.fromDiagnostics([...describe.diagnostics]),
4561
- result: this.#taskResult,
4562
- },
4563
- ]);
4638
+ this.#onTaskDiagnostics(Diagnostic.fromDiagnostics([...describe.diagnostics]));
4564
4639
  }
4565
4640
  else {
4566
4641
  this.visit(describe.children, runMode, describeResult);
@@ -4597,37 +4672,14 @@ class TestTreeWalker {
4597
4672
  EventEmitter.dispatch(["test:pass", { result: testResult }]);
4598
4673
  }
4599
4674
  }
4600
- #visitWhen(when, runMode, parentResult) {
4601
- if (when.abilityDiagnostics != null && when.abilityDiagnostics.size > 0) {
4602
- const diagnostics = [];
4603
- for (const diagnostic of when.abilityDiagnostics) {
4604
- if (isDiagnosticWithLocation(diagnostic)) {
4605
- const text = getDiagnosticMessageText(diagnostic);
4606
- let origin;
4607
- if (isDiagnosticWithLocation(diagnostic) && diagnosticBelongsToNode(diagnostic, when.node)) {
4608
- origin = DiagnosticOrigin.fromNodes(when.target);
4609
- }
4610
- else {
4611
- origin = new DiagnosticOrigin(diagnostic.start, getTextSpanEnd(diagnostic), when.node.getSourceFile());
4612
- }
4613
- let related;
4614
- if (diagnostic.relatedInformation != null) {
4615
- related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
4616
- }
4617
- diagnostics.push(Diagnostic.error(text, origin).add({ related }));
4618
- }
4619
- }
4620
- EventEmitter.dispatch(["task:error", { diagnostics, result: this.#taskResult }]);
4621
- return;
4622
- }
4623
- this.visit(when.children, runMode, parentResult);
4675
+ #visitWhen(when) {
4676
+ this.#whenService.action(when);
4624
4677
  }
4625
4678
  }
4626
4679
 
4627
4680
  class TaskRunner {
4628
4681
  #collectService;
4629
4682
  #compiler;
4630
- #eventEmitter = new EventEmitter();
4631
4683
  #resolvedConfig;
4632
4684
  #projectService;
4633
4685
  constructor(compiler, resolvedConfig) {
@@ -4689,21 +4741,17 @@ class TaskRunner {
4689
4741
  if (!sourceFile) {
4690
4742
  return;
4691
4743
  }
4692
- const cancellationHandler = new CancellationHandler(cancellationToken, CancellationReason.CollectError);
4693
- this.#eventEmitter.addHandler(cancellationHandler);
4694
4744
  const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics);
4695
- this.#eventEmitter.removeHandler(cancellationHandler);
4696
- if (cancellationToken.isCancellationRequested) {
4697
- return;
4698
- }
4699
4745
  if (testTree.diagnostics.size > 0) {
4700
4746
  this.#onDiagnostics(Diagnostic.fromDiagnostics([...testTree.diagnostics]), taskResult);
4701
4747
  return;
4702
4748
  }
4703
4749
  const typeChecker = program?.getTypeChecker();
4704
- const testTreeWalker = new TestTreeWalker(this.#compiler, typeChecker, this.#resolvedConfig, {
4750
+ const onTaskDiagnostics = (diagnostics) => {
4751
+ this.#onDiagnostics(diagnostics, taskResult);
4752
+ };
4753
+ const testTreeWalker = new TestTreeWalker(this.#compiler, typeChecker, this.#resolvedConfig, onTaskDiagnostics, {
4705
4754
  cancellationToken,
4706
- taskResult,
4707
4755
  hasOnly: testTree.hasOnly,
4708
4756
  position: task.position,
4709
4757
  });
@@ -4714,7 +4762,7 @@ class TaskRunner {
4714
4762
  class Runner {
4715
4763
  #eventEmitter = new EventEmitter();
4716
4764
  #resolvedConfig;
4717
- static version = "4.0.0-beta.8";
4765
+ static version = "4.0.0-beta.9";
4718
4766
  constructor(resolvedConfig) {
4719
4767
  this.#resolvedConfig = resolvedConfig;
4720
4768
  }
@@ -4914,4 +4962,4 @@ class Cli {
4914
4962
  }
4915
4963
  }
4916
4964
 
4917
- export { AssertionNode, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileWatcher, InputService, Line, ListReporter, OptionBrand, OptionGroup, Options, OutputService, Path, PluginService, ProjectResult, ProjectService, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, Runner, Scribbler, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, TargetResult, Task, TaskResult, TestResult, TestTree, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, WhenNode, addsPackageText, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, environmentOptions, fileViewText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, nodeBelongsToArgumentList, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
4965
+ export { AssertionNode, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileWatcher, InputService, Line, ListReporter, OptionBrand, OptionGroup, Options, OutputService, Path, PluginService, ProjectResult, ProjectService, Reject, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, Runner, Scribbler, Select, SelectDiagnosticText, SetupReporter, SourceFile, Store, SummaryReporter, TargetResult, Task, TaskResult, TestResult, TestTree, TestTreeNode, TestTreeNodeBrand, TestTreeNodeFlags, Text, Version, WatchReporter, WatchService, Watcher, WhenNode, WhenService, addsPackageText, argumentIsProvided, argumentOrTypeArgumentIsProvided, defaultOptions, describeNameText, diagnosticBelongsToNode, diagnosticText, environmentOptions, fileViewText, formattedText, getDiagnosticMessageText, getTextSpanEnd, helpText, isDiagnosticWithLocation, nodeBelongsToArgumentList, summaryText, taskStatusText, testNameText, usesCompilerText, waitingForFileChangesText, watchUsageText };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tstyche",
3
- "version": "4.0.0-beta.8",
3
+ "version": "4.0.0-beta.9",
4
4
  "description": "The Essential Type Testing Tool.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -62,21 +62,21 @@
62
62
  "devDependencies": {
63
63
  "@biomejs/biome": "1.9.4",
64
64
  "@rollup/plugin-typescript": "12.1.2",
65
- "@types/node": "22.15.3",
66
- "@types/react": "19.1.2",
65
+ "@types/node": "22.15.15",
66
+ "@types/react": "19.1.3",
67
67
  "ajv": "8.17.1",
68
68
  "cspell": "9.0.0",
69
69
  "magic-string": "0.30.17",
70
70
  "monocart-coverage-reports": "2.12.4",
71
71
  "pretty-ansi": "3.0.0",
72
- "rollup": "4.40.1",
72
+ "rollup": "4.40.2",
73
73
  "rollup-plugin-dts": "6.2.1",
74
74
  "ts-blank-space": "0.6.1",
75
75
  "tslib": "2.8.1",
76
76
  "typescript": "5.8.3"
77
77
  },
78
78
  "peerDependencies": {
79
- "typescript": "5.x"
79
+ "typescript": ">=4.7"
80
80
  },
81
81
  "peerDependenciesMeta": {
82
82
  "typescript": {