tstyche 4.0.0-beta.9 → 4.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -77,7 +77,7 @@ This simple! (And it has watch mode too.)
77
77
 
78
78
  ## Documentation
79
79
 
80
- Visit [https://tstyche.org](https://tstyche.org) to view the full documentation.
80
+ Visit [tstyche.org](https://tstyche.org) to view the full documentation.
81
81
 
82
82
  ## Feedback
83
83
 
package/build/index.d.cts CHANGED
@@ -85,15 +85,15 @@ interface Matchers {
85
85
  (target: unknown): void;
86
86
  };
87
87
  /**
88
- * Checks if the source type is identical to the target type.
88
+ * Checks if the source type is the same as the target type.
89
89
  */
90
90
  toBe: {
91
91
  /**
92
- * Checks if the source type is identical to the target type.
92
+ * Checks if the source type is the same as the target type.
93
93
  */
94
94
  <Target>(): void;
95
95
  /**
96
- * Checks if the source type is identical to type of the target expression.
96
+ * Checks if the source type is the same as type of the target expression.
97
97
  */
98
98
  (target: unknown): void;
99
99
  };
package/build/index.d.ts CHANGED
@@ -85,15 +85,15 @@ interface Matchers {
85
85
  (target: unknown): void;
86
86
  };
87
87
  /**
88
- * Checks if the source type is identical to the target type.
88
+ * Checks if the source type is the same as the target type.
89
89
  */
90
90
  toBe: {
91
91
  /**
92
- * Checks if the source type is identical to the target type.
92
+ * Checks if the source type is the same as the target type.
93
93
  */
94
94
  <Target>(): void;
95
95
  /**
96
- * Checks if the source type is identical to type of the target expression.
96
+ * Checks if the source type is the same as type of the target expression.
97
97
  */
98
98
  (target: unknown): void;
99
99
  };
@@ -20,60 +20,6 @@ declare class Cli {
20
20
  run(commandLine: Array<string>, cancellationToken?: CancellationToken): Promise<void>;
21
21
  }
22
22
 
23
- declare enum TestTreeNodeBrand {
24
- Describe = "describe",
25
- Test = "test",
26
- Expect = "expect",
27
- When = "when"
28
- }
29
-
30
- declare enum TestTreeNodeFlags {
31
- None = 0,
32
- Fail = 1,
33
- Only = 2,
34
- Skip = 4,
35
- Todo = 8
36
- }
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
-
46
- declare class TestTreeNode {
47
- brand: TestTreeNodeBrand;
48
- children: Array<TestTreeNode | AssertionNode | WhenNode>;
49
- diagnostics: Set<ts.Diagnostic>;
50
- flags: TestTreeNodeFlags;
51
- name: string;
52
- node: ts.CallExpression;
53
- parent: TestTree | TestTreeNode;
54
- constructor(compiler: typeof ts, brand: TestTreeNodeBrand, node: ts.CallExpression, parent: TestTree | TestTreeNode, flags: TestTreeNodeFlags);
55
- }
56
-
57
- declare class TestTree {
58
- children: Array<TestTreeNode | AssertionNode | WhenNode>;
59
- diagnostics: Set<ts.Diagnostic>;
60
- hasOnly: boolean;
61
- sourceFile: ts.SourceFile;
62
- constructor(diagnostics: Set<ts.Diagnostic>, sourceFile: ts.SourceFile);
63
- }
64
-
65
- declare class AssertionNode extends TestTreeNode {
66
- abilityDiagnostics: Set<ts.Diagnostic> | undefined;
67
- isNot: boolean;
68
- matcherNode: ts.CallExpression | ts.Decorator;
69
- matcherNameNode: ts.PropertyAccessExpression;
70
- modifierNode: ts.PropertyAccessExpression;
71
- notNode: ts.PropertyAccessExpression | undefined;
72
- source: ts.NodeArray<ts.Expression> | ts.NodeArray<ts.TypeNode>;
73
- target: ts.NodeArray<ts.Expression> | ts.NodeArray<ts.TypeNode> | undefined;
74
- constructor(compiler: typeof ts, brand: TestTreeNodeBrand, node: ts.CallExpression, parent: TestTree | TestTreeNode, flags: TestTreeNodeFlags, matcherNode: ts.CallExpression | ts.Decorator, matcherNameNode: ts.PropertyAccessExpression, modifierNode: ts.PropertyAccessExpression, notNode?: ts.PropertyAccessExpression);
75
- }
76
-
77
23
  declare enum OptionBrand {
78
24
  String = "string",
79
25
  Number = "number",
@@ -100,52 +46,6 @@ declare class ConfigDiagnosticText {
100
46
  static watchCannotBeEnabled(): string;
101
47
  }
102
48
 
103
- /**
104
- * Options loaded from the configuration file.
105
- */
106
- interface ConfigFileOptions {
107
- /**
108
- * Enable type error reporting for source files.
109
- */
110
- checkSourceFiles?: boolean;
111
- /**
112
- * Stop running tests after the first failed assertion.
113
- */
114
- failFast?: boolean;
115
- /**
116
- * The list of plugins to use.
117
- */
118
- plugins?: Array<string>;
119
- /**
120
- * Reject the 'any' type passed as an argument to the 'expect()' function or a matcher.
121
- */
122
- rejectAnyType?: boolean;
123
- /**
124
- * Reject the 'never' type passed as an argument to the 'expect()' function or a matcher.
125
- */
126
- rejectNeverType?: boolean;
127
- /**
128
- * The list of reporters to use.
129
- */
130
- reporters?: Array<string>;
131
- /**
132
- * The path to a directory containing files of a test project.
133
- */
134
- rootPath?: string;
135
- /**
136
- * The list of TypeScript versions to be tested on.
137
- */
138
- target?: Array<string>;
139
- /**
140
- * The list of glob patterns matching the test files.
141
- */
142
- testFileMatch?: Array<string>;
143
- /**
144
- * The look up strategy to be used to find the TSConfig file.
145
- */
146
- tsconfig?: string;
147
- }
148
-
149
49
  /**
150
50
  * Options passed through the command line.
151
51
  */
@@ -220,6 +120,58 @@ interface CommandLineOptions {
220
120
  watch?: boolean;
221
121
  }
222
122
 
123
+ /**
124
+ * Options loaded from the configuration file.
125
+ */
126
+ interface ConfigFileOptions {
127
+ /**
128
+ * Enable type error reporting for source files.
129
+ */
130
+ checkSourceFiles?: boolean;
131
+ /**
132
+ * Stop running tests after the first failed assertion.
133
+ */
134
+ failFast?: boolean;
135
+ /**
136
+ * The list of plugins to use.
137
+ */
138
+ plugins?: Array<string>;
139
+ /**
140
+ * Reject the 'any' type passed as an argument to the 'expect()' function or a matcher.
141
+ */
142
+ rejectAnyType?: boolean;
143
+ /**
144
+ * Reject the 'never' type passed as an argument to the 'expect()' function or a matcher.
145
+ */
146
+ rejectNeverType?: boolean;
147
+ /**
148
+ * The list of reporters to use.
149
+ */
150
+ reporters?: Array<string>;
151
+ /**
152
+ * The path to a directory containing files of a test project.
153
+ */
154
+ rootPath?: string;
155
+ /**
156
+ * The list of TypeScript versions to be tested on.
157
+ */
158
+ target?: Array<string>;
159
+ /**
160
+ * The list of glob patterns matching the test files.
161
+ */
162
+ testFileMatch?: Array<string>;
163
+ /**
164
+ * The look up strategy to be used to find the TSConfig file.
165
+ */
166
+ tsconfig?: string;
167
+ }
168
+
169
+ interface InlineConfig {
170
+ if?: {
171
+ target?: Array<string>;
172
+ };
173
+ template?: boolean;
174
+ }
223
175
  interface ResolvedConfig extends Omit<CommandLineOptions, "config" | keyof ConfigFileOptions>, Required<ConfigFileOptions> {
224
176
  /**
225
177
  * The path to a TSTyche configuration file.
@@ -230,6 +182,7 @@ interface ResolvedConfig extends Omit<CommandLineOptions, "config" | keyof Confi
230
182
  */
231
183
  pathMatch: Array<string>;
232
184
  }
185
+
233
186
  declare class Config {
234
187
  #private;
235
188
  static parseCommandLine(commandLine: Array<string>): Promise<{
@@ -249,6 +202,25 @@ declare class Config {
249
202
  static resolveConfigFilePath(filePath?: string): string;
250
203
  }
251
204
 
205
+ interface TextRange {
206
+ start: number;
207
+ end: number;
208
+ text: string;
209
+ }
210
+ interface DirectiveRange {
211
+ namespace: TextRange;
212
+ directive?: TextRange;
213
+ argument?: TextRange;
214
+ }
215
+ type DirectiveRanges = Array<DirectiveRange> & {
216
+ sourceFile: ts.SourceFile;
217
+ };
218
+ declare class Directive {
219
+ #private;
220
+ static getDirectiveRanges(compiler: typeof ts, sourceFile: ts.SourceFile, position?: number): DirectiveRanges | undefined;
221
+ static getInlineConfig(ranges: DirectiveRanges | undefined): Promise<InlineConfig | undefined>;
222
+ }
223
+
252
224
  declare enum DiagnosticCategory {
253
225
  Error = "error",
254
226
  Warning = "warning"
@@ -304,6 +276,7 @@ type DiagnosticsHandler<T extends Diagnostic | Array<Diagnostic> = Diagnostic> =
304
276
  declare enum OptionGroup {
305
277
  CommandLine = 2,
306
278
  ConfigFile = 4,
279
+ InlineConditions = 8,
307
280
  ResolvedConfig = 6
308
281
  }
309
282
 
@@ -334,6 +307,62 @@ declare class Options {
334
307
 
335
308
  declare const defaultOptions: Required<ConfigFileOptions>;
336
309
 
310
+ declare enum TestTreeNodeBrand {
311
+ Describe = "describe",
312
+ Test = "test",
313
+ Expect = "expect",
314
+ When = "when"
315
+ }
316
+
317
+ declare enum TestTreeNodeFlags {
318
+ None = 0,
319
+ Fail = 1,
320
+ Only = 2,
321
+ Skip = 4,
322
+ Todo = 8
323
+ }
324
+
325
+ declare class WhenNode extends TestTreeNode {
326
+ actionNode: ts.CallExpression;
327
+ actionNameNode: ts.PropertyAccessExpression;
328
+ abilityDiagnostics: Set<ts.Diagnostic> | undefined;
329
+ target: ts.NodeArray<ts.Expression> | ts.NodeArray<ts.TypeNode>;
330
+ constructor(compiler: typeof ts, brand: TestTreeNodeBrand, node: ts.CallExpression, parent: TestTree | TestTreeNode, flags: TestTreeNodeFlags, actionNode: ts.CallExpression, actionNameNode: ts.PropertyAccessExpression);
331
+ }
332
+
333
+ declare class TestTreeNode {
334
+ brand: TestTreeNodeBrand;
335
+ children: Array<TestTreeNode | AssertionNode | WhenNode>;
336
+ diagnostics: Set<ts.Diagnostic>;
337
+ flags: TestTreeNodeFlags;
338
+ name: string;
339
+ node: ts.CallExpression;
340
+ parent: TestTree | TestTreeNode;
341
+ constructor(compiler: typeof ts, brand: TestTreeNodeBrand, node: ts.CallExpression, parent: TestTree | TestTreeNode, flags: TestTreeNodeFlags);
342
+ getDirectiveRanges(compiler: typeof ts): DirectiveRanges | undefined;
343
+ }
344
+
345
+ declare class TestTree {
346
+ children: Array<TestTreeNode | AssertionNode | WhenNode>;
347
+ diagnostics: Set<ts.Diagnostic>;
348
+ hasOnly: boolean;
349
+ sourceFile: ts.SourceFile;
350
+ constructor(diagnostics: Set<ts.Diagnostic>, sourceFile: ts.SourceFile);
351
+ getDirectiveRanges(compiler: typeof ts): DirectiveRanges | undefined;
352
+ }
353
+
354
+ declare class AssertionNode extends TestTreeNode {
355
+ abilityDiagnostics: Set<ts.Diagnostic> | undefined;
356
+ isNot: boolean;
357
+ matcherNode: ts.CallExpression | ts.Decorator;
358
+ matcherNameNode: ts.PropertyAccessExpression;
359
+ modifierNode: ts.PropertyAccessExpression;
360
+ notNode: ts.PropertyAccessExpression | undefined;
361
+ source: ts.NodeArray<ts.Expression> | ts.NodeArray<ts.TypeNode>;
362
+ target: ts.NodeArray<ts.Expression> | ts.NodeArray<ts.TypeNode> | undefined;
363
+ constructor(compiler: typeof ts, brand: TestTreeNodeBrand, node: ts.CallExpression, parent: TestTree | TestTreeNode, flags: TestTreeNodeFlags, matcherNode: ts.CallExpression | ts.Decorator, matcherNameNode: ts.PropertyAccessExpression, modifierNode: ts.PropertyAccessExpression, notNode: ts.PropertyAccessExpression | undefined);
364
+ }
365
+
337
366
  declare class ProjectService {
338
367
  #private;
339
368
  constructor(compiler: typeof ts, resolvedConfig: ResolvedConfig);
@@ -546,6 +575,8 @@ type Event = ["config:error", {
546
575
  result: TaskResult;
547
576
  }] | ["task:end", {
548
577
  result: TaskResult;
578
+ }] | ["directive:error", {
579
+ diagnostics: Array<Diagnostic>;
549
580
  }] | ["collect:start", {
550
581
  tree: TestTree;
551
582
  }] | ["collect:error", {
@@ -610,14 +641,9 @@ interface MatchResult {
610
641
  explain: () => Array<Diagnostic>;
611
642
  isMatch: boolean;
612
643
  }
613
- type Relation = Map<string, unknown>;
614
644
  interface TypeChecker extends ts.TypeChecker {
615
645
  isApplicableIndexType: (source: ts.Type, target: ts.Type) => boolean;
616
- isTypeRelatedTo: (source: ts.Type, target: ts.Type, relation: Relation) => boolean;
617
- relation: {
618
- assignable: Relation;
619
- identity: Relation;
620
- };
646
+ isTypeIdenticalTo: (source: ts.Type, target: ts.Type) => boolean;
621
647
  }
622
648
 
623
649
  declare class ExpectService {
@@ -860,6 +886,7 @@ declare class Store {
860
886
  declare class Version {
861
887
  #private;
862
888
  static isGreaterThan(source: string, target: string): boolean;
889
+ static isIncluded(source: string, range: Array<string>): boolean;
863
890
  static isSatisfiedWith(source: string, target: string): boolean;
864
891
  }
865
892
 
@@ -891,5 +918,5 @@ declare class WhenService {
891
918
  action(when: WhenNode): void;
892
919
  }
893
920
 
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 };
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 };
921
+ export { AssertionNode, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Directive, 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 };
922
+ export type { CodeFrameOptions, CommandLineOptions, ConfigFileOptions, DiagnosticsHandler, DirectiveRange, DirectiveRanges, EnvironmentOptions, Event, EventHandler, FileWatchHandler, InlineConfig, InputHandler, ItemDefinition, MatchResult, OptionDefinition, Plugin, Reporter, ReporterEvent, ResolvedConfig, ScribblerOptions, SelectHookContext, TargetResultStatus, TaskResultStatus, TypeChecker, WatchHandler, WatcherOptions };
package/build/tstyche.js CHANGED
@@ -292,6 +292,7 @@ var OptionGroup;
292
292
  (function (OptionGroup) {
293
293
  OptionGroup[OptionGroup["CommandLine"] = 2] = "CommandLine";
294
294
  OptionGroup[OptionGroup["ConfigFile"] = 4] = "ConfigFile";
295
+ OptionGroup[OptionGroup["InlineConditions"] = 8] = "InlineConditions";
295
296
  OptionGroup[OptionGroup["ResolvedConfig"] = 6] = "ResolvedConfig";
296
297
  })(OptionGroup || (OptionGroup = {}));
297
298
 
@@ -376,6 +377,9 @@ class Version {
376
377
  static isGreaterThan(source, target) {
377
378
  return !(source === target) && Version.#satisfies(source, target);
378
379
  }
380
+ static isIncluded(source, range) {
381
+ return range.some((target) => source.startsWith(target));
382
+ }
379
383
  static isSatisfiedWith(source, target) {
380
384
  return source === target || Version.#satisfies(source, target);
381
385
  }
@@ -810,11 +814,7 @@ class Store {
810
814
  modulePath = Path.resolve(modulePath, "../tsserverlibrary.js");
811
815
  }
812
816
  const sourceText = await fs.readFile(modulePath, { encoding: "utf8" });
813
- const toExpose = [
814
- "isApplicableIndexType",
815
- "isTypeRelatedTo",
816
- "relation: { assignable: assignableRelation, identity: identityRelation }",
817
- ];
817
+ const toExpose = ["isApplicableIndexType", "isTypeIdenticalTo"];
818
818
  const modifiedSourceText = sourceText.replace("return checker;", `return { ...checker, ${toExpose.join(", ")} };`);
819
819
  const compiledWrapper = vm.compileFunction(modifiedSourceText, ["exports", "require", "module", "__filename", "__dirname"], { filename: modulePath });
820
820
  compiledWrapper(exports, createRequire(modulePath), module, modulePath, Path.dirname(modulePath));
@@ -1008,7 +1008,7 @@ class Options {
1008
1008
  {
1009
1009
  brand: OptionBrand.List,
1010
1010
  description: "The list of TypeScript versions to be tested on.",
1011
- group: OptionGroup.CommandLine | OptionGroup.ConfigFile,
1011
+ group: OptionGroup.CommandLine | OptionGroup.ConfigFile | OptionGroup.InlineConditions,
1012
1012
  items: {
1013
1013
  brand: OptionBrand.String,
1014
1014
  name: "target",
@@ -1243,146 +1243,18 @@ class CommandLineParser {
1243
1243
  }
1244
1244
  }
1245
1245
 
1246
- class JsonNode {
1247
- origin;
1248
- text;
1249
- constructor(text, origin) {
1250
- this.origin = origin;
1251
- this.text = text;
1252
- }
1253
- getValue(options) {
1254
- if (this.text == null) {
1255
- return undefined;
1256
- }
1257
- if (/^['"]/.test(this.text)) {
1258
- return this.text.slice(1, -1);
1259
- }
1260
- if (options?.expectsIdentifier) {
1261
- return this.text;
1262
- }
1263
- if (this.text === "true") {
1264
- return true;
1265
- }
1266
- if (this.text === "false") {
1267
- return false;
1268
- }
1269
- if (/^\d/.test(this.text)) {
1270
- return Number.parseFloat(this.text);
1271
- }
1272
- return undefined;
1273
- }
1274
- }
1275
-
1276
- class JsonScanner {
1277
- #currentPosition = 0;
1278
- #previousPosition = 0;
1279
- #sourceFile;
1280
- constructor(sourceFile) {
1281
- this.#sourceFile = sourceFile;
1282
- }
1283
- #getOrigin() {
1284
- return new DiagnosticOrigin(this.#previousPosition, this.#currentPosition, this.#sourceFile);
1285
- }
1286
- isRead() {
1287
- return !(this.#currentPosition < this.#sourceFile.text.length);
1288
- }
1289
- #peekCharacter() {
1290
- return this.#sourceFile.text.charAt(this.#currentPosition);
1291
- }
1292
- #peekNextCharacter() {
1293
- return this.#sourceFile.text.charAt(this.#currentPosition + 1);
1294
- }
1295
- peekToken(token) {
1296
- this.#skipTrivia();
1297
- return this.#peekCharacter() === token;
1298
- }
1299
- read() {
1300
- this.#skipTrivia();
1301
- this.#previousPosition = this.#currentPosition;
1302
- if (/[\s,:\]}]/.test(this.#peekCharacter())) {
1303
- return new JsonNode(undefined, this.#getOrigin());
1304
- }
1305
- let text = "";
1306
- let closingTokenText = "";
1307
- if (/[[{'"]/.test(this.#peekCharacter())) {
1308
- text += this.#readCharacter();
1309
- switch (text) {
1310
- case "[":
1311
- closingTokenText = "]";
1312
- break;
1313
- case "{":
1314
- closingTokenText = "}";
1315
- break;
1316
- default:
1317
- closingTokenText = text;
1318
- }
1319
- }
1320
- while (!this.isRead()) {
1321
- text += this.#readCharacter();
1322
- if (text.slice(-1) === closingTokenText || (!closingTokenText && /[\s,:\]}]/.test(this.#peekCharacter()))) {
1323
- break;
1324
- }
1325
- }
1326
- return new JsonNode(text, this.#getOrigin());
1327
- }
1328
- #readCharacter() {
1329
- return this.#sourceFile.text.charAt(this.#currentPosition++);
1330
- }
1331
- readToken(token) {
1332
- this.#skipTrivia();
1333
- this.#previousPosition = this.#currentPosition;
1334
- if (this.#peekCharacter() === token) {
1335
- this.#currentPosition++;
1336
- return new JsonNode(token, this.#getOrigin());
1337
- }
1338
- return new JsonNode(undefined, this.#getOrigin());
1339
- }
1340
- #skipTrivia() {
1341
- while (!this.isRead()) {
1342
- if (/\s/.test(this.#peekCharacter())) {
1343
- this.#currentPosition++;
1344
- continue;
1345
- }
1346
- if (this.#peekCharacter() === "/") {
1347
- if (this.#peekNextCharacter() === "/") {
1348
- this.#currentPosition += 2;
1349
- while (!this.isRead()) {
1350
- if (this.#readCharacter() === "\n") {
1351
- break;
1352
- }
1353
- }
1354
- continue;
1355
- }
1356
- if (this.#peekNextCharacter() === "*") {
1357
- this.#currentPosition += 2;
1358
- while (!this.isRead()) {
1359
- if (this.#peekCharacter() === "*" && this.#peekNextCharacter() === "/") {
1360
- this.#currentPosition += 2;
1361
- break;
1362
- }
1363
- this.#currentPosition++;
1364
- }
1365
- continue;
1366
- }
1367
- }
1368
- break;
1369
- }
1370
- this.#previousPosition = this.#currentPosition;
1371
- }
1372
- }
1373
-
1374
- class ConfigFileParser {
1246
+ class ConfigParser {
1375
1247
  #configFileOptions;
1376
1248
  #jsonScanner;
1377
1249
  #onDiagnostics;
1378
1250
  #options;
1379
1251
  #sourceFile;
1380
- constructor(configFileOptions, sourceFile, onDiagnostics) {
1381
- this.#configFileOptions = configFileOptions;
1382
- this.#sourceFile = sourceFile;
1252
+ constructor(configOptions, optionGroup, sourceFile, jsonScanner, onDiagnostics) {
1253
+ this.#configFileOptions = configOptions;
1254
+ this.#jsonScanner = jsonScanner;
1383
1255
  this.#onDiagnostics = onDiagnostics;
1384
- this.#options = Options.for(OptionGroup.ConfigFile);
1385
- this.#jsonScanner = new JsonScanner(this.#sourceFile);
1256
+ this.#sourceFile = sourceFile;
1257
+ this.#options = Options.for(optionGroup);
1386
1258
  }
1387
1259
  #onRequiresValue(optionDefinition, jsonNode, isListItem) {
1388
1260
  const text = isListItem
@@ -1475,9 +1347,8 @@ class ConfigFileParser {
1475
1347
  if (!optionDefinition) {
1476
1348
  const text = ConfigDiagnosticText.unknownOption(optionName);
1477
1349
  this.#onDiagnostics(Diagnostic.error(text, optionNameNode.origin));
1478
- if (this.#jsonScanner.readToken(":")) {
1479
- this.#jsonScanner.read();
1480
- }
1350
+ this.#jsonScanner.readToken(":");
1351
+ this.#jsonScanner.read();
1481
1352
  const commaToken = this.#jsonScanner.readToken(",");
1482
1353
  if (!commaToken.text) {
1483
1354
  break;
@@ -1511,6 +1382,138 @@ class ConfigFileParser {
1511
1382
  }
1512
1383
  }
1513
1384
 
1385
+ class JsonNode {
1386
+ origin;
1387
+ text;
1388
+ constructor(text, origin) {
1389
+ this.origin = origin;
1390
+ this.text = text;
1391
+ }
1392
+ getValue(options) {
1393
+ if (this.text == null) {
1394
+ return undefined;
1395
+ }
1396
+ if (/^['"]/.test(this.text)) {
1397
+ return this.text.slice(1, -1);
1398
+ }
1399
+ if (options?.expectsIdentifier) {
1400
+ return this.text;
1401
+ }
1402
+ if (this.text === "true") {
1403
+ return true;
1404
+ }
1405
+ if (this.text === "false") {
1406
+ return false;
1407
+ }
1408
+ if (/^\d/.test(this.text)) {
1409
+ return Number.parseFloat(this.text);
1410
+ }
1411
+ return undefined;
1412
+ }
1413
+ }
1414
+
1415
+ class JsonScanner {
1416
+ #end;
1417
+ #position;
1418
+ #previousPosition;
1419
+ #sourceFile;
1420
+ constructor(sourceFile, options) {
1421
+ this.#end = options?.end ?? sourceFile.text.length;
1422
+ this.#position = options?.start ?? 0;
1423
+ this.#previousPosition = options?.start ?? 0;
1424
+ this.#sourceFile = sourceFile;
1425
+ }
1426
+ #getOrigin() {
1427
+ return new DiagnosticOrigin(this.#previousPosition, this.#position, this.#sourceFile);
1428
+ }
1429
+ isRead() {
1430
+ return !(this.#position < this.#end);
1431
+ }
1432
+ #peekCharacter() {
1433
+ return this.#sourceFile.text.charAt(this.#position);
1434
+ }
1435
+ #peekNextCharacter() {
1436
+ return this.#sourceFile.text.charAt(this.#position + 1);
1437
+ }
1438
+ peekToken(token) {
1439
+ this.#skipTrivia();
1440
+ return this.#peekCharacter() === token;
1441
+ }
1442
+ read() {
1443
+ this.#skipTrivia();
1444
+ this.#previousPosition = this.#position;
1445
+ if (/[\s,:\]}]/.test(this.#peekCharacter())) {
1446
+ return new JsonNode(undefined, this.#getOrigin());
1447
+ }
1448
+ let text = "";
1449
+ let closingTokenText = "";
1450
+ if (/[[{'"]/.test(this.#peekCharacter())) {
1451
+ text += this.#readCharacter();
1452
+ switch (text) {
1453
+ case "[":
1454
+ closingTokenText = "]";
1455
+ break;
1456
+ case "{":
1457
+ closingTokenText = "}";
1458
+ break;
1459
+ default:
1460
+ closingTokenText = text;
1461
+ }
1462
+ }
1463
+ while (!this.isRead()) {
1464
+ text += this.#readCharacter();
1465
+ if (text.slice(-1) === closingTokenText || (!closingTokenText && /[\s,:\]}]/.test(this.#peekCharacter()))) {
1466
+ break;
1467
+ }
1468
+ }
1469
+ return new JsonNode(text, this.#getOrigin());
1470
+ }
1471
+ #readCharacter() {
1472
+ return this.#sourceFile.text.charAt(this.#position++);
1473
+ }
1474
+ readToken(token) {
1475
+ this.#skipTrivia();
1476
+ this.#previousPosition = this.#position;
1477
+ if (this.#peekCharacter() === token) {
1478
+ this.#position++;
1479
+ return new JsonNode(token, this.#getOrigin());
1480
+ }
1481
+ return new JsonNode(undefined, this.#getOrigin());
1482
+ }
1483
+ #skipTrivia() {
1484
+ while (!this.isRead()) {
1485
+ if (/\s/.test(this.#peekCharacter())) {
1486
+ this.#position++;
1487
+ continue;
1488
+ }
1489
+ if (this.#peekCharacter() === "/") {
1490
+ if (this.#peekNextCharacter() === "/") {
1491
+ this.#position += 2;
1492
+ while (!this.isRead()) {
1493
+ if (this.#readCharacter() === "\n") {
1494
+ break;
1495
+ }
1496
+ }
1497
+ continue;
1498
+ }
1499
+ if (this.#peekNextCharacter() === "*") {
1500
+ this.#position += 2;
1501
+ while (!this.isRead()) {
1502
+ if (this.#peekCharacter() === "*" && this.#peekNextCharacter() === "/") {
1503
+ this.#position += 2;
1504
+ break;
1505
+ }
1506
+ this.#position++;
1507
+ }
1508
+ continue;
1509
+ }
1510
+ }
1511
+ break;
1512
+ }
1513
+ this.#previousPosition = this.#position;
1514
+ }
1515
+ }
1516
+
1514
1517
  const defaultOptions = {
1515
1518
  checkSourceFiles: true,
1516
1519
  failFast: false,
@@ -1548,7 +1551,7 @@ class Config {
1548
1551
  encoding: "utf8",
1549
1552
  });
1550
1553
  const sourceFile = new SourceFile(configFilePath, configFileText);
1551
- const configFileParser = new ConfigFileParser(configFileOptions, sourceFile, Config.#onDiagnostics);
1554
+ const configFileParser = new ConfigParser(configFileOptions, OptionGroup.ConfigFile, sourceFile, new JsonScanner(sourceFile), Config.#onDiagnostics);
1552
1555
  await configFileParser.parse();
1553
1556
  if (configFileOptions.target != null) {
1554
1557
  configFileOptions.target = await Target.expand(configFileOptions.target);
@@ -1574,6 +1577,114 @@ class Config {
1574
1577
  }
1575
1578
  }
1576
1579
 
1580
+ class DirectiveDiagnosticText {
1581
+ static doesNotTakeArgument(directiveName) {
1582
+ return `Directive '${directiveName}' does not take an argument.`;
1583
+ }
1584
+ static isNotSupported(directive) {
1585
+ return `The '${directive}' directive is not supported.`;
1586
+ }
1587
+ static requiresArgument(directiveName) {
1588
+ return `Directive '${directiveName}' requires an argument.`;
1589
+ }
1590
+ }
1591
+
1592
+ class Directive {
1593
+ static #commentSeparatorRegex = /--+/;
1594
+ static #directiveRegex = /^(\/\/\s*@tstyche)(\s*|-)?(\S*)?(\s*)?(.*)?/i;
1595
+ static getDirectiveRanges(compiler, sourceFile, position = 0) {
1596
+ const comments = compiler.getLeadingCommentRanges(sourceFile.text, position);
1597
+ if (!comments || comments.length === 0) {
1598
+ return;
1599
+ }
1600
+ const ranges = Object.assign([], { sourceFile });
1601
+ for (const comment of comments) {
1602
+ if (comment.kind !== compiler.SyntaxKind.SingleLineCommentTrivia) {
1603
+ continue;
1604
+ }
1605
+ const range = Directive.#getRange(sourceFile, comment);
1606
+ if (range != null) {
1607
+ ranges.push(range);
1608
+ }
1609
+ }
1610
+ return ranges;
1611
+ }
1612
+ static async getInlineConfig(ranges) {
1613
+ if (!ranges) {
1614
+ return;
1615
+ }
1616
+ const inlineConfig = {};
1617
+ for (const range of ranges) {
1618
+ await Directive.#parse(inlineConfig, ranges.sourceFile, range);
1619
+ }
1620
+ return inlineConfig;
1621
+ }
1622
+ static #getRange(sourceFile, comment) {
1623
+ const [text] = sourceFile.text.substring(comment.pos, comment.end).split(Directive.#commentSeparatorRegex);
1624
+ const found = text?.match(Directive.#directiveRegex);
1625
+ const namespaceText = found?.[1];
1626
+ if (!namespaceText) {
1627
+ return;
1628
+ }
1629
+ const ranges = {
1630
+ namespace: { start: comment.pos, end: comment.pos + namespaceText.length, text: namespaceText },
1631
+ };
1632
+ const directiveSeparatorText = found?.[2];
1633
+ const directiveText = found?.[3];
1634
+ if (directiveText != null && directiveText.length > 0) {
1635
+ const start = ranges.namespace.end + (directiveSeparatorText?.length ?? 0);
1636
+ ranges.directive = { start, end: start + directiveText.length, text: directiveText };
1637
+ }
1638
+ const argumentSeparatorText = found?.[4];
1639
+ const argumentText = found?.[5]?.trimEnd();
1640
+ if (ranges.directive != null && argumentText != null && argumentText.length > 0) {
1641
+ const start = ranges.directive.end + (argumentSeparatorText?.length ?? 0);
1642
+ ranges.argument = { start, end: start + argumentText.length, text: argumentText };
1643
+ }
1644
+ return ranges;
1645
+ }
1646
+ static #onDiagnostics(diagnostic) {
1647
+ EventEmitter.dispatch(["directive:error", { diagnostics: [diagnostic] }]);
1648
+ }
1649
+ static async #parse(inlineConfig, sourceFile, ranges) {
1650
+ switch (ranges.directive?.text) {
1651
+ case "if":
1652
+ {
1653
+ if (!ranges.argument?.text) {
1654
+ const text = DirectiveDiagnosticText.requiresArgument(ranges.directive.text);
1655
+ const origin = new DiagnosticOrigin(ranges.directive.start, ranges.directive.end, sourceFile);
1656
+ Directive.#onDiagnostics(Diagnostic.error(text, origin));
1657
+ return;
1658
+ }
1659
+ const value = await Directive.#parseJson(sourceFile, ranges.argument.start, ranges.argument.end);
1660
+ inlineConfig.if = value;
1661
+ }
1662
+ return;
1663
+ case "template":
1664
+ if (ranges.argument?.text != null) {
1665
+ const text = DirectiveDiagnosticText.doesNotTakeArgument(ranges.directive.text);
1666
+ const origin = new DiagnosticOrigin(ranges.directive.start, ranges.directive.end, sourceFile);
1667
+ Directive.#onDiagnostics(Diagnostic.error(text, origin));
1668
+ }
1669
+ inlineConfig.template = true;
1670
+ return;
1671
+ }
1672
+ const target = ranges?.directive ?? ranges.namespace;
1673
+ const text = DirectiveDiagnosticText.isNotSupported(target.text);
1674
+ const origin = new DiagnosticOrigin(target.start, target.end, sourceFile);
1675
+ Directive.#onDiagnostics(Diagnostic.error(text, origin));
1676
+ }
1677
+ static async #parseJson(sourceFile, start, end) {
1678
+ const inlineOptions = {};
1679
+ const configParser = new ConfigParser(inlineOptions, OptionGroup.InlineConditions, sourceFile, new JsonScanner(sourceFile, { start, end }), Directive.#onDiagnostics);
1680
+ await configParser.parse();
1681
+ if ("target" in inlineOptions) {
1682
+ inlineOptions["target"] = await Target.expand(inlineOptions["target"]);
1683
+ }
1684
+ return inlineOptions;
1685
+ }
1686
+ }
1687
+
1577
1688
  class CancellationHandler {
1578
1689
  #cancellationToken;
1579
1690
  #cancellationReason;
@@ -1781,6 +1892,7 @@ class ResultHandler {
1781
1892
  this.#taskResult.timing.start = Date.now();
1782
1893
  break;
1783
1894
  case "task:error":
1895
+ case "directive:error":
1784
1896
  case "collect:error":
1785
1897
  this.#targetResult.status = ResultStatus.Failed;
1786
1898
  this.#taskResult.status = ResultStatus.Failed;
@@ -2384,6 +2496,7 @@ class ListReporter extends BaseReporter {
2384
2496
  this.#hasReportedError = false;
2385
2497
  break;
2386
2498
  case "task:error":
2499
+ case "directive:error":
2387
2500
  case "collect:error":
2388
2501
  for (const diagnostic of payload.diagnostics) {
2389
2502
  this.#fileView.addMessage(diagnosticText(diagnostic));
@@ -2899,6 +3012,9 @@ class TestTreeNode {
2899
3012
  }
2900
3013
  }
2901
3014
  }
3015
+ getDirectiveRanges(compiler) {
3016
+ return Directive.getDirectiveRanges(compiler, this.node.getSourceFile(), this.node.getFullStart());
3017
+ }
2902
3018
  }
2903
3019
 
2904
3020
  class AssertionNode extends TestTreeNode {
@@ -2930,10 +3046,10 @@ class AssertionNode extends TestTreeNode {
2930
3046
  }
2931
3047
 
2932
3048
  function nodeBelongsToArgumentList(compiler, node) {
2933
- if (compiler.isCallExpression(node.parent)) {
2934
- return node.parent.arguments.some((argument) => argument === node);
2935
- }
2936
- return false;
3049
+ return compiler.isCallExpression(node.parent) && node.parent.arguments.some((argument) => argument === node);
3050
+ }
3051
+ function nodeIsChildOfExpressionStatement(compiler, node) {
3052
+ return compiler.isExpressionStatement(node.parent);
2937
3053
  }
2938
3054
 
2939
3055
  class AbilityLayer {
@@ -3013,7 +3129,7 @@ class AbilityLayer {
3013
3129
  {
3014
3130
  start: whenStart,
3015
3131
  end: whenExpressionEnd,
3016
- replacement: nodeBelongsToArgumentList(this.#compiler, whenNode.actionNode) ? "" : ";",
3132
+ replacement: nodeIsChildOfExpressionStatement(this.#compiler, whenNode.actionNode) ? ";" : "",
3017
3133
  },
3018
3134
  { start: whenEnd, end: actionNameEnd },
3019
3135
  ]);
@@ -3038,7 +3154,7 @@ class AbilityLayer {
3038
3154
  {
3039
3155
  start: expectStart,
3040
3156
  end: expectExpressionEnd,
3041
- replacement: nodeBelongsToArgumentList(this.#compiler, assertionNode.matcherNode) ? "" : ";",
3157
+ replacement: nodeIsChildOfExpressionStatement(this.#compiler, assertionNode.matcherNode) ? ";" : "",
3042
3158
  },
3043
3159
  { start: expectEnd, end: matcherNameEnd },
3044
3160
  ]);
@@ -3049,7 +3165,7 @@ class AbilityLayer {
3049
3165
  {
3050
3166
  start: expectStart,
3051
3167
  end: expectExpressionEnd,
3052
- replacement: nodeBelongsToArgumentList(this.#compiler, assertionNode.matcherNode) ? "new" : "; new",
3168
+ replacement: nodeIsChildOfExpressionStatement(this.#compiler, assertionNode.matcherNode) ? "; new" : "new",
3053
3169
  },
3054
3170
  { start: expectEnd, end: matcherNameEnd },
3055
3171
  ]);
@@ -3189,6 +3305,9 @@ class TestTree {
3189
3305
  this.diagnostics = diagnostics;
3190
3306
  this.sourceFile = sourceFile;
3191
3307
  }
3308
+ getDirectiveRanges(compiler) {
3309
+ return Directive.getDirectiveRanges(compiler, this.sourceFile);
3310
+ }
3192
3311
  }
3193
3312
 
3194
3313
  class WhenNode extends TestTreeNode {
@@ -3618,11 +3737,11 @@ class ExpectDiagnosticText {
3618
3737
  static isNotAssignableWith(sourceTypeText, targetTypeText) {
3619
3738
  return `Type '${sourceTypeText}' is not assignable with type '${targetTypeText}'.`;
3620
3739
  }
3621
- static isIdenticalTo(sourceTypeText, targetTypeText) {
3622
- return `Type '${sourceTypeText}' is identical to type '${targetTypeText}'.`;
3740
+ static isTheSame(sourceTypeText, targetTypeText) {
3741
+ return `Type '${sourceTypeText}' is the same as type '${targetTypeText}'.`;
3623
3742
  }
3624
- static isNotIdenticalTo(sourceTypeText, targetTypeText) {
3625
- return `Type '${sourceTypeText}' is not identical to type '${targetTypeText}'.`;
3743
+ static isNotTheSame(sourceTypeText, targetTypeText) {
3744
+ return `Type '${sourceTypeText}' is not the same as type '${targetTypeText}'.`;
3626
3745
  }
3627
3746
  static isNotCompatibleWith(sourceTypeText, targetTypeText) {
3628
3747
  return `Type '${sourceTypeText}' is not compatible with type '${targetTypeText}'.`;
@@ -3635,6 +3754,12 @@ class ExpectDiagnosticText {
3635
3754
  }
3636
3755
  }
3637
3756
 
3757
+ var Relation;
3758
+ (function (Relation) {
3759
+ Relation["Assignable"] = "assignable";
3760
+ Relation["Identical"] = "identical";
3761
+ })(Relation || (Relation = {}));
3762
+
3638
3763
  class MatchWorker {
3639
3764
  assertion;
3640
3765
  #compiler;
@@ -3659,27 +3784,25 @@ class MatchWorker {
3659
3784
  .some((property) => this.#compiler.unescapeLeadingUnderscores(property.escapedName) === propertyNameText);
3660
3785
  }
3661
3786
  checkIsAssignableTo(sourceNode, targetNode) {
3662
- const relation = this.typeChecker.relation.assignable;
3663
- return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
3787
+ return this.#checkIsRelatedTo(sourceNode, targetNode, Relation.Assignable);
3664
3788
  }
3665
3789
  checkIsAssignableWith(sourceNode, targetNode) {
3666
- const relation = this.typeChecker.relation.assignable;
3667
- return this.#checkIsRelatedTo(targetNode, sourceNode, relation);
3790
+ return this.#checkIsRelatedTo(targetNode, sourceNode, Relation.Assignable);
3668
3791
  }
3669
3792
  checkIsIdenticalTo(sourceNode, targetNode) {
3670
- const relation = this.typeChecker.relation.identity;
3671
- return (this.#checkIsRelatedTo(sourceNode, targetNode, relation) &&
3793
+ return (this.#checkIsRelatedTo(sourceNode, targetNode, Relation.Identical) &&
3672
3794
  this.checkIsAssignableTo(sourceNode, targetNode) &&
3673
3795
  this.checkIsAssignableWith(sourceNode, targetNode));
3674
3796
  }
3675
3797
  #checkIsRelatedTo(sourceNode, targetNode, relation) {
3676
- const sourceType = relation === this.typeChecker.relation.identity
3677
- ? this.#simplifyType(this.getType(sourceNode))
3678
- : this.getType(sourceNode);
3679
- const targetType = relation === this.typeChecker.relation.identity
3680
- ? this.#simplifyType(this.getType(targetNode))
3681
- : this.getType(targetNode);
3682
- return this.typeChecker.isTypeRelatedTo(sourceType, targetType, relation);
3798
+ const sourceType = relation === "identical" ? this.#simplifyType(this.getType(sourceNode)) : this.getType(sourceNode);
3799
+ const targetType = relation === "identical" ? this.#simplifyType(this.getType(targetNode)) : this.getType(targetNode);
3800
+ switch (relation) {
3801
+ case Relation.Assignable:
3802
+ return this.typeChecker.isTypeAssignableTo(sourceType, targetType);
3803
+ case Relation.Identical:
3804
+ return this.typeChecker.isTypeIdenticalTo(sourceType, targetType);
3805
+ }
3683
3806
  }
3684
3807
  extendsObjectType(type) {
3685
3808
  const nonPrimitiveType = { flags: this.#compiler.TypeFlags.NonPrimitive };
@@ -3723,7 +3846,7 @@ class MatchWorker {
3723
3846
  #simplifyType(type) {
3724
3847
  if (type.isUnionOrIntersection()) {
3725
3848
  const candidateType = this.#simplifyType(type.types[0]);
3726
- if (type.types.every((type) => this.typeChecker.isTypeRelatedTo(this.#simplifyType(type), candidateType, this.typeChecker.relation.identity))) {
3849
+ if (type.types.every((type) => this.typeChecker.isTypeIdenticalTo(this.#simplifyType(type), candidateType))) {
3727
3850
  return candidateType;
3728
3851
  }
3729
3852
  }
@@ -3938,8 +4061,8 @@ class RelationMatcherBase {
3938
4061
  }
3939
4062
 
3940
4063
  class ToBe extends RelationMatcherBase {
3941
- explainText = ExpectDiagnosticText.isIdenticalTo;
3942
- explainNotText = ExpectDiagnosticText.isNotIdenticalTo;
4064
+ explainText = ExpectDiagnosticText.isTheSame;
4065
+ explainNotText = ExpectDiagnosticText.isNotTheSame;
3943
4066
  match(matchWorker, sourceNode, targetNode) {
3944
4067
  return {
3945
4068
  explain: () => this.explain(matchWorker, sourceNode, targetNode),
@@ -4529,6 +4652,7 @@ class WhenService {
4529
4652
 
4530
4653
  class TestTreeWalker {
4531
4654
  #cancellationToken;
4655
+ #compiler;
4532
4656
  #expectService;
4533
4657
  #hasOnly;
4534
4658
  #onTaskDiagnostics;
@@ -4536,6 +4660,7 @@ class TestTreeWalker {
4536
4660
  #resolvedConfig;
4537
4661
  #whenService;
4538
4662
  constructor(compiler, typeChecker, resolvedConfig, onTaskDiagnostics, options) {
4663
+ this.#compiler = compiler;
4539
4664
  this.#resolvedConfig = resolvedConfig;
4540
4665
  this.#onTaskDiagnostics = onTaskDiagnostics;
4541
4666
  this.#cancellationToken = options.cancellationToken;
@@ -4545,43 +4670,46 @@ class TestTreeWalker {
4545
4670
  this.#expectService = new ExpectService(compiler, typeChecker, reject);
4546
4671
  this.#whenService = new WhenService(reject, onTaskDiagnostics);
4547
4672
  }
4548
- #resolveRunMode(mode, testNode) {
4549
- if (testNode.flags & TestTreeNodeFlags.Fail) {
4673
+ async #resolveRunMode(mode, node) {
4674
+ const directiveRanges = node.getDirectiveRanges(this.#compiler);
4675
+ const inlineConfig = await Directive.getInlineConfig(directiveRanges);
4676
+ if (inlineConfig?.if?.target != null && !Version.isIncluded(this.#compiler.version, inlineConfig.if.target)) {
4677
+ mode |= RunMode.Skip;
4678
+ }
4679
+ if (node.flags & TestTreeNodeFlags.Fail) {
4550
4680
  mode |= RunMode.Fail;
4551
4681
  }
4552
- if (testNode.flags & TestTreeNodeFlags.Only ||
4553
- (this.#resolvedConfig.only != null &&
4554
- testNode.name.toLowerCase().includes(this.#resolvedConfig.only.toLowerCase()))) {
4682
+ if (node.flags & TestTreeNodeFlags.Only ||
4683
+ (this.#resolvedConfig.only != null && node.name.toLowerCase().includes(this.#resolvedConfig.only.toLowerCase()))) {
4555
4684
  mode |= RunMode.Only;
4556
4685
  }
4557
- if (testNode.flags & TestTreeNodeFlags.Skip ||
4558
- (this.#resolvedConfig.skip != null &&
4559
- testNode.name.toLowerCase().includes(this.#resolvedConfig.skip.toLowerCase()))) {
4686
+ if (node.flags & TestTreeNodeFlags.Skip ||
4687
+ (this.#resolvedConfig.skip != null && node.name.toLowerCase().includes(this.#resolvedConfig.skip.toLowerCase()))) {
4560
4688
  mode |= RunMode.Skip;
4561
4689
  }
4562
- if (testNode.flags & TestTreeNodeFlags.Todo) {
4690
+ if (node.flags & TestTreeNodeFlags.Todo) {
4563
4691
  mode |= RunMode.Todo;
4564
4692
  }
4565
- if (this.#position != null && testNode.node.getStart() === this.#position) {
4693
+ if (this.#position != null && node.node.getStart() === this.#position) {
4566
4694
  mode |= RunMode.Only;
4567
4695
  mode &= ~RunMode.Skip;
4568
4696
  }
4569
4697
  return mode;
4570
4698
  }
4571
- visit(nodes, runMode, parentResult) {
4699
+ async visit(nodes, runMode, parentResult) {
4572
4700
  for (const node of nodes) {
4573
4701
  if (this.#cancellationToken?.isCancellationRequested) {
4574
4702
  break;
4575
4703
  }
4576
4704
  switch (node.brand) {
4577
4705
  case TestTreeNodeBrand.Describe:
4578
- this.#visitDescribe(node, runMode, parentResult);
4706
+ await this.#visitDescribe(node, runMode, parentResult);
4579
4707
  break;
4580
4708
  case TestTreeNodeBrand.Test:
4581
- this.#visitTest(node, runMode, parentResult);
4709
+ await this.#visitTest(node, runMode, parentResult);
4582
4710
  break;
4583
4711
  case TestTreeNodeBrand.Expect:
4584
- this.#visitAssertion(node, runMode, parentResult);
4712
+ await this.#visitAssertion(node, runMode, parentResult);
4585
4713
  break;
4586
4714
  case TestTreeNodeBrand.When:
4587
4715
  this.#visitWhen(node);
@@ -4589,11 +4717,11 @@ class TestTreeWalker {
4589
4717
  }
4590
4718
  }
4591
4719
  }
4592
- #visitAssertion(assertion, runMode, parentResult) {
4593
- this.visit(assertion.children, runMode, parentResult);
4720
+ async #visitAssertion(assertion, runMode, parentResult) {
4721
+ await this.visit(assertion.children, runMode, parentResult);
4594
4722
  const expectResult = new ExpectResult(assertion, parentResult);
4595
4723
  EventEmitter.dispatch(["expect:start", { result: expectResult }]);
4596
- runMode = this.#resolveRunMode(runMode, assertion);
4724
+ runMode = await this.#resolveRunMode(runMode, assertion);
4597
4725
  if (runMode & RunMode.Skip || (this.#hasOnly && !(runMode & RunMode.Only))) {
4598
4726
  EventEmitter.dispatch(["expect:skip", { result: expectResult }]);
4599
4727
  return;
@@ -4629,23 +4757,23 @@ class TestTreeWalker {
4629
4757
  EventEmitter.dispatch(["expect:fail", { diagnostics: matchResult.explain(), result: expectResult }]);
4630
4758
  }
4631
4759
  }
4632
- #visitDescribe(describe, runMode, parentResult) {
4760
+ async #visitDescribe(describe, runMode, parentResult) {
4633
4761
  const describeResult = new DescribeResult(describe, parentResult);
4634
4762
  EventEmitter.dispatch(["describe:start", { result: describeResult }]);
4635
- runMode = this.#resolveRunMode(runMode, describe);
4763
+ runMode = await this.#resolveRunMode(runMode, describe);
4636
4764
  if (!(runMode & RunMode.Skip || (this.#hasOnly && !(runMode & RunMode.Only)) || runMode & RunMode.Todo) &&
4637
4765
  describe.diagnostics.size > 0) {
4638
4766
  this.#onTaskDiagnostics(Diagnostic.fromDiagnostics([...describe.diagnostics]));
4639
4767
  }
4640
4768
  else {
4641
- this.visit(describe.children, runMode, describeResult);
4769
+ await this.visit(describe.children, runMode, describeResult);
4642
4770
  }
4643
4771
  EventEmitter.dispatch(["describe:end", { result: describeResult }]);
4644
4772
  }
4645
- #visitTest(test, runMode, parentResult) {
4773
+ async #visitTest(test, runMode, parentResult) {
4646
4774
  const testResult = new TestResult(test, parentResult);
4647
4775
  EventEmitter.dispatch(["test:start", { result: testResult }]);
4648
- runMode = this.#resolveRunMode(runMode, test);
4776
+ runMode = await this.#resolveRunMode(runMode, test);
4649
4777
  if (runMode & RunMode.Todo) {
4650
4778
  EventEmitter.dispatch(["test:todo", { result: testResult }]);
4651
4779
  return;
@@ -4660,7 +4788,7 @@ class TestTreeWalker {
4660
4788
  ]);
4661
4789
  return;
4662
4790
  }
4663
- this.visit(test.children, runMode, testResult);
4791
+ await this.visit(test.children, runMode, testResult);
4664
4792
  if (runMode & RunMode.Skip || (this.#hasOnly && !(runMode & RunMode.Only))) {
4665
4793
  EventEmitter.dispatch(["test:skip", { result: testResult }]);
4666
4794
  return;
@@ -4702,21 +4830,27 @@ class TaskRunner {
4702
4830
  EventEmitter.dispatch(["task:end", { result: taskResult }]);
4703
4831
  this.#projectService.closeFile(task.filePath);
4704
4832
  }
4705
- async #run(task, taskResult, cancellationToken) {
4706
- if (!existsSync(task.filePath)) {
4707
- this.#onDiagnostics([Diagnostic.error(`Test file '${task.filePath}' does not exist.`)], taskResult);
4708
- return;
4709
- }
4710
- let languageService = this.#projectService.getLanguageService(task.filePath);
4833
+ async #resolveTaskFacts(task, taskResult, runMode = RunMode.Normal) {
4834
+ const languageService = this.#projectService.getLanguageService(task.filePath);
4711
4835
  const syntacticDiagnostics = languageService?.getSyntacticDiagnostics(task.filePath);
4712
4836
  if (syntacticDiagnostics != null && syntacticDiagnostics.length > 0) {
4713
4837
  this.#onDiagnostics(Diagnostic.fromDiagnostics(syntacticDiagnostics), taskResult);
4714
4838
  return;
4715
4839
  }
4716
- let semanticDiagnostics = languageService?.getSemanticDiagnostics(task.filePath);
4717
- let program = languageService?.getProgram();
4718
- let sourceFile = program?.getSourceFile(task.filePath);
4719
- if (sourceFile?.text.startsWith("// @tstyche-template")) {
4840
+ const semanticDiagnostics = languageService?.getSemanticDiagnostics(task.filePath);
4841
+ const program = languageService?.getProgram();
4842
+ const typeChecker = program?.getTypeChecker();
4843
+ const sourceFile = program?.getSourceFile(task.filePath);
4844
+ if (!sourceFile) {
4845
+ return;
4846
+ }
4847
+ const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics);
4848
+ const directiveRanges = testTree.getDirectiveRanges(this.#compiler);
4849
+ const inlineConfig = await Directive.getInlineConfig(directiveRanges);
4850
+ if (inlineConfig?.if?.target != null && !Version.isIncluded(this.#compiler.version, inlineConfig.if.target)) {
4851
+ runMode |= RunMode.Skip;
4852
+ }
4853
+ if (inlineConfig?.template) {
4720
4854
  if (semanticDiagnostics != null && semanticDiagnostics.length > 0) {
4721
4855
  this.#onDiagnostics(Diagnostic.fromDiagnostics(semanticDiagnostics), taskResult);
4722
4856
  return;
@@ -4728,41 +4862,39 @@ class TaskRunner {
4728
4862
  return;
4729
4863
  }
4730
4864
  this.#projectService.openFile(task.filePath, testText, this.#resolvedConfig.rootPath);
4731
- languageService = this.#projectService.getLanguageService(task.filePath);
4732
- const syntacticDiagnostics = languageService?.getSyntacticDiagnostics(task.filePath);
4733
- if (syntacticDiagnostics != null && syntacticDiagnostics.length > 0) {
4734
- this.#onDiagnostics(Diagnostic.fromDiagnostics(syntacticDiagnostics), taskResult);
4735
- return;
4736
- }
4737
- semanticDiagnostics = languageService?.getSemanticDiagnostics(task.filePath);
4738
- program = languageService?.getProgram();
4739
- sourceFile = program?.getSourceFile(task.filePath);
4865
+ return this.#resolveTaskFacts(task, taskResult, runMode);
4740
4866
  }
4741
- if (!sourceFile) {
4867
+ return { runMode, testTree, typeChecker };
4868
+ }
4869
+ async #run(task, taskResult, cancellationToken) {
4870
+ if (!existsSync(task.filePath)) {
4871
+ this.#onDiagnostics([Diagnostic.error(`Test file '${task.filePath}' does not exist.`)], taskResult);
4742
4872
  return;
4743
4873
  }
4744
- const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics);
4745
- if (testTree.diagnostics.size > 0) {
4746
- this.#onDiagnostics(Diagnostic.fromDiagnostics([...testTree.diagnostics]), taskResult);
4874
+ const facts = await this.#resolveTaskFacts(task, taskResult);
4875
+ if (!facts) {
4876
+ return;
4877
+ }
4878
+ if (facts.testTree.diagnostics.size > 0) {
4879
+ this.#onDiagnostics(Diagnostic.fromDiagnostics([...facts.testTree.diagnostics]), taskResult);
4747
4880
  return;
4748
4881
  }
4749
- const typeChecker = program?.getTypeChecker();
4750
4882
  const onTaskDiagnostics = (diagnostics) => {
4751
4883
  this.#onDiagnostics(diagnostics, taskResult);
4752
4884
  };
4753
- const testTreeWalker = new TestTreeWalker(this.#compiler, typeChecker, this.#resolvedConfig, onTaskDiagnostics, {
4885
+ const testTreeWalker = new TestTreeWalker(this.#compiler, facts.typeChecker, this.#resolvedConfig, onTaskDiagnostics, {
4754
4886
  cancellationToken,
4755
- hasOnly: testTree.hasOnly,
4887
+ hasOnly: facts.testTree.hasOnly,
4756
4888
  position: task.position,
4757
4889
  });
4758
- testTreeWalker.visit(testTree.children, RunMode.Normal, undefined);
4890
+ await testTreeWalker.visit(facts.testTree.children, facts.runMode, undefined);
4759
4891
  }
4760
4892
  }
4761
4893
 
4762
4894
  class Runner {
4763
4895
  #eventEmitter = new EventEmitter();
4764
4896
  #resolvedConfig;
4765
- static version = "4.0.0-beta.9";
4897
+ static version = "4.0.0-rc.1";
4766
4898
  constructor(resolvedConfig) {
4767
4899
  this.#resolvedConfig = resolvedConfig;
4768
4900
  }
@@ -4962,4 +5094,4 @@ class Cli {
4962
5094
  }
4963
5095
  }
4964
5096
 
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 };
5097
+ export { AssertionNode, BaseReporter, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, Config, ConfigDiagnosticText, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Directive, 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.9",
3
+ "version": "4.0.0-rc.1",
4
4
  "description": "The Essential Type Testing Tool.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -31,50 +31,6 @@
31
31
  "main": "./build/index.js",
32
32
  "types": "./build/index.d.ts",
33
33
  "bin": "./build/bin.js",
34
- "files": [
35
- "build/*"
36
- ],
37
- "scripts": {
38
- "bench": "./benchmarks/tstyche.bench.sh",
39
- "build": "rollup --config rollup.config.js",
40
- "build:watch": "yarn build --sourcemap --watch",
41
- "check": "yarn check:spelling && yarn check:types",
42
- "check:spelling": "cspell --config cspell.config.json --quiet",
43
- "check:types": "tsc --noEmit --project tsconfig.json",
44
- "format": "biome format --write",
45
- "generate": "yarn generate:schema && yarn generate:types",
46
- "generate:schema": "node ./scripts/generate-schema.js",
47
- "generate:types": "node ./scripts/generate-types.js",
48
- "lint": "biome lint --write",
49
- "prepublish": "yarn build",
50
- "test": "yarn test:unit && yarn test:e2e",
51
- "test:coverage": "yarn test:coverage:collect && yarn test:coverage:report",
52
- "test:coverage:collect": "yarn build --sourcemap && NODE_V8_COVERAGE='./coverage/v8-coverage' yarn test:e2e",
53
- "test:coverage:report": "node ./scripts/report-coverage.js",
54
- "test:e2e": "yarn test:e2e:parallel && yarn test:e2e:serial",
55
- "test:e2e:parallel": "yarn test:run tests/*.test.js --exclude feature --parallel",
56
- "test:e2e:serial": "yarn test:run tests/*.test.js --include feature",
57
- "test:examples": "NODE_OPTIONS='--import ts-blank-space/register' tstyche examples",
58
- "test:run": "rm -rf ./tests/__fixtures__/.generated && yarn node ./tests/__utilities__/runner.js",
59
- "test:types": "tstyche typetests",
60
- "test:unit": "yarn test:run **/__tests__/*.test.js --parallel"
61
- },
62
- "devDependencies": {
63
- "@biomejs/biome": "1.9.4",
64
- "@rollup/plugin-typescript": "12.1.2",
65
- "@types/node": "22.15.15",
66
- "@types/react": "19.1.3",
67
- "ajv": "8.17.1",
68
- "cspell": "9.0.0",
69
- "magic-string": "0.30.17",
70
- "monocart-coverage-reports": "2.12.4",
71
- "pretty-ansi": "3.0.0",
72
- "rollup": "4.40.2",
73
- "rollup-plugin-dts": "6.2.1",
74
- "ts-blank-space": "0.6.1",
75
- "tslib": "2.8.1",
76
- "typescript": "5.8.3"
77
- },
78
34
  "peerDependencies": {
79
35
  "typescript": ">=4.7"
80
36
  },
@@ -83,7 +39,6 @@
83
39
  "optional": true
84
40
  }
85
41
  },
86
- "packageManager": "yarn@4.9.1",
87
42
  "engines": {
88
43
  "node": ">=20.9"
89
44
  }