tstyche 4.0.0-rc.1 → 4.0.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
@@ -5,7 +5,7 @@
5
5
  [![install-size][install-size-badge]][install-size-url]
6
6
  [![coverage][coverage-badge]][coverage-url]
7
7
 
8
- The Essential Type Testing Tool.
8
+ Everything You Need for Type Testing.
9
9
 
10
10
  ---
11
11
 
@@ -37,21 +37,29 @@ To organize, debug and plan tests TSTyche has:
37
37
 
38
38
  ## Assertions
39
39
 
40
- The assertions can be used to write type tests (like in the above example) or mixed in your functional tests:
40
+ The assertions can be used to write type tests (like in the above example) or mixed in your unit tests:
41
41
 
42
42
  ```ts
43
43
  import assert from "node:assert";
44
44
  import test from "node:test";
45
45
  import * as tstyche from "tstyche";
46
46
 
47
- function secondItem<T>(target: Array<T>): T | undefined {
48
- return target[1];
47
+ function toMilliseconds(value: number) {
48
+ if (typeof value === "number" && !Number.isNaN(value)) {
49
+ return value * 1000;
50
+ }
51
+
52
+ throw new Error("Not a number");
49
53
  }
50
54
 
51
- test("handles numbers", () => {
52
- assert.strictEqual(secondItem([1, 2, 3]), 2);
55
+ test("toMilliseconds", () => {
56
+ const sample = toMilliseconds(10);
57
+
58
+ assert.equal(sample, 10_000);
59
+ tstyche.expect(sample).type.toBe<number>();
53
60
 
54
- tstyche.expect(secondItem([1, 2, 3])).type.toBe<number | undefined>();
61
+ // Will pass as a type test and not throw at runtime
62
+ tstyche.expect(toMilliseconds).type.not.toBeCallableWith("20");
55
63
  });
56
64
  ```
57
65
 
@@ -20,152 +20,37 @@ declare class Cli {
20
20
  run(commandLine: Array<string>, cancellationToken?: CancellationToken): Promise<void>;
21
21
  }
22
22
 
23
- declare enum OptionBrand {
24
- String = "string",
25
- Number = "number",
26
- Boolean = "boolean",
27
- BareTrue = "bareTrue",// a boolean option that does not take a value and when specified is interpreted as 'true'
28
- List = "list"
29
- }
30
-
31
- declare class ConfigDiagnosticText {
32
- static expected(element: string): string;
33
- static expectsListItemType(optionName: string, optionBrand: OptionBrand): string;
34
- static expectsValue(optionName: string): string;
35
- static fileDoesNotExist(filePath: string): string;
36
- static inspectSupportedVersions(): string;
37
- static moduleWasNotFound(specifier: string): string;
38
- static rangeIsNotValid(value: string): string;
39
- static rangeUsage(): Array<string>;
40
- static requiresValueType(optionName: string, optionBrand: OptionBrand): string;
41
- static seen(element: string): string;
42
- static testFileMatchCannotStartWith(segment: string): Array<string>;
43
- static unknownOption(optionName: string): string;
44
- static usage(optionName: string, optionBrand: OptionBrand): Array<string>;
45
- static versionIsNotSupported(value: string): string;
46
- static watchCannotBeEnabled(): string;
47
- }
48
-
49
- /**
50
- * Options passed through the command line.
51
- */
52
23
  interface CommandLineOptions {
53
- /**
54
- * The path to a TSTyche configuration file.
55
- */
56
24
  config?: string;
57
- /**
58
- * Stop running tests after the first failed assertion.
59
- */
60
25
  failFast?: boolean;
61
- /**
62
- * Fetch the specified versions of the 'typescript' package and exit.
63
- */
64
26
  fetch?: boolean;
65
- /**
66
- * Print the list of command line options with brief descriptions and exit.
67
- */
68
27
  help?: boolean;
69
- /**
70
- * Print the list of supported versions of the 'typescript' package and exit.
71
- */
72
28
  list?: boolean;
73
- /**
74
- * Print the list of the selected test files and exit.
75
- */
76
29
  listFiles?: boolean;
77
- /**
78
- * Only run tests with matching name.
79
- */
80
30
  only?: string;
81
- /**
82
- * The list of plugins to use.
83
- */
84
31
  plugins?: Array<string>;
85
- /**
86
- * Remove all installed versions of the 'typescript' package and exit.
87
- */
88
32
  prune?: boolean;
89
- /**
90
- * The list of reporters to use.
91
- */
92
33
  reporters?: Array<string>;
93
- /**
94
- * Print the resolved configuration and exit.
95
- */
96
34
  showConfig?: boolean;
97
- /**
98
- * Skip tests with matching name.
99
- */
100
35
  skip?: string;
101
- /**
102
- * The list of TypeScript versions to be tested on.
103
- */
104
36
  target?: Array<string>;
105
- /**
106
- * The look up strategy to be used to find the TSConfig file.
107
- */
108
37
  tsconfig?: string;
109
- /**
110
- * Fetch the 'typescript' package metadata from the registry and exit.
111
- */
112
38
  update?: boolean;
113
- /**
114
- * Print the version number and exit.
115
- */
116
39
  version?: boolean;
117
- /**
118
- * Watch for changes and rerun related test files.
119
- */
120
40
  watch?: boolean;
121
41
  }
122
-
123
- /**
124
- * Options loaded from the configuration file.
125
- */
126
42
  interface ConfigFileOptions {
127
- /**
128
- * Enable type error reporting for source files.
129
- */
130
43
  checkSourceFiles?: boolean;
131
- /**
132
- * Stop running tests after the first failed assertion.
133
- */
134
44
  failFast?: boolean;
135
- /**
136
- * The list of plugins to use.
137
- */
138
45
  plugins?: Array<string>;
139
- /**
140
- * Reject the 'any' type passed as an argument to the 'expect()' function or a matcher.
141
- */
142
46
  rejectAnyType?: boolean;
143
- /**
144
- * Reject the 'never' type passed as an argument to the 'expect()' function or a matcher.
145
- */
146
47
  rejectNeverType?: boolean;
147
- /**
148
- * The list of reporters to use.
149
- */
150
48
  reporters?: Array<string>;
151
- /**
152
- * The path to a directory containing files of a test project.
153
- */
154
49
  rootPath?: string;
155
- /**
156
- * The list of TypeScript versions to be tested on.
157
- */
158
50
  target?: Array<string>;
159
- /**
160
- * The list of glob patterns matching the test files.
161
- */
162
51
  testFileMatch?: Array<string>;
163
- /**
164
- * The look up strategy to be used to find the TSConfig file.
165
- */
166
52
  tsconfig?: string;
167
53
  }
168
-
169
54
  interface InlineConfig {
170
55
  if?: {
171
56
  target?: Array<string>;
@@ -173,13 +58,7 @@ interface InlineConfig {
173
58
  template?: boolean;
174
59
  }
175
60
  interface ResolvedConfig extends Omit<CommandLineOptions, "config" | keyof ConfigFileOptions>, Required<ConfigFileOptions> {
176
- /**
177
- * The path to a TSTyche configuration file.
178
- */
179
61
  configFilePath: string;
180
- /**
181
- * Only run test files with matching path.
182
- */
183
62
  pathMatch: Array<string>;
184
63
  }
185
64
 
@@ -202,6 +81,33 @@ declare class Config {
202
81
  static resolveConfigFilePath(filePath?: string): string;
203
82
  }
204
83
 
84
+ declare enum OptionBrand {
85
+ String = "string",
86
+ Number = "number",
87
+ Boolean = "boolean",
88
+ BareTrue = "bareTrue",
89
+ List = "list"
90
+ }
91
+
92
+ declare class ConfigDiagnosticText {
93
+ static expected(element: string): string;
94
+ static expectsListItemType(optionName: string, optionBrand: OptionBrand): string;
95
+ static expectsValue(optionName: string): string;
96
+ static fileDoesNotExist(filePath: string): string;
97
+ static inspectSupportedVersions(): string;
98
+ static moduleWasNotFound(specifier: string): string;
99
+ static rangeIsNotValid(value: string): string;
100
+ static rangeUsage(): Array<string>;
101
+ static requiresValueType(optionName: string, optionBrand: OptionBrand): string;
102
+ static seen(element: string): string;
103
+ static testFileMatchCannotStartWith(segment: string): Array<string>;
104
+ static unexpected(element: string): string;
105
+ static unknownOption(optionName: string): string;
106
+ static usage(optionName: string, optionBrand: OptionBrand): Array<string>;
107
+ static versionIsNotSupported(value: string): string;
108
+ static watchCannotBeEnabled(): string;
109
+ }
110
+
205
111
  interface TextRange {
206
112
  start: number;
207
113
  end: number;
@@ -221,6 +127,15 @@ declare class Directive {
221
127
  static getInlineConfig(ranges: DirectiveRanges | undefined): Promise<InlineConfig | undefined>;
222
128
  }
223
129
 
130
+ declare const defaultOptions: Required<ConfigFileOptions>;
131
+
132
+ declare enum OptionGroup {
133
+ CommandLine = 2,
134
+ ConfigFile = 4,
135
+ InlineConditions = 8,
136
+ ResolvedConfig = 6
137
+ }
138
+
224
139
  declare enum DiagnosticCategory {
225
140
  Error = "error",
226
141
  Warning = "warning"
@@ -273,13 +188,6 @@ declare function isDiagnosticWithLocation(diagnostic: ts.Diagnostic): diagnostic
273
188
 
274
189
  type DiagnosticsHandler<T extends Diagnostic | Array<Diagnostic> = Diagnostic> = (this: void, diagnostics: T) => void;
275
190
 
276
- declare enum OptionGroup {
277
- CommandLine = 2,
278
- ConfigFile = 4,
279
- InlineConditions = 8,
280
- ResolvedConfig = 6
281
- }
282
-
283
191
  interface BaseOptionDefinition {
284
192
  brand: OptionBrand;
285
193
  description: string;
@@ -305,8 +213,6 @@ declare class Options {
305
213
  static validate(optionName: string, optionValue: string, optionBrand: OptionBrand, onDiagnostics: DiagnosticsHandler, origin?: DiagnosticOrigin): Promise<void>;
306
214
  }
307
215
 
308
- declare const defaultOptions: Required<ConfigFileOptions>;
309
-
310
216
  declare enum TestTreeNodeBrand {
311
217
  Describe = "describe",
312
218
  Test = "test",
@@ -385,33 +291,12 @@ declare function argumentIsProvided<T>(argumentNameText: string, node: T, enclos
385
291
  declare function argumentOrTypeArgumentIsProvided<T>(argumentNameText: string, typeArgumentNameText: string, node: T, enclosingNode: ts.Node, onDiagnostics: DiagnosticsHandler<Array<Diagnostic>>): node is NonNullable<T>;
386
292
 
387
293
  interface EnvironmentOptions {
388
- /**
389
- * Is `true` if the process is running in continuous integration environment.
390
- */
391
294
  isCi: boolean;
392
- /**
393
- * Specifies whether color should be disabled in the output.
394
- */
395
295
  noColor: boolean;
396
- /**
397
- * Specifies whether interactive elements should be disabled in the output.
398
- */
399
296
  noInteractive: boolean;
400
- /**
401
- * The base URL of the 'npm' registry to use.
402
- */
403
297
  npmRegistry: string;
404
- /**
405
- * The directory where to store the 'typescript' packages.
406
- */
407
298
  storePath: string;
408
- /**
409
- * The number of seconds to wait before giving up stale operations.
410
- */
411
299
  timeout: number;
412
- /**
413
- * The specifier of the TypeScript module.
414
- */
415
300
  typescriptModule: string | undefined;
416
301
  }
417
302
 
@@ -751,8 +636,6 @@ interface CodeFrameOptions {
751
636
 
752
637
  declare function diagnosticText(diagnostic: Diagnostic, codeFrameOptions?: CodeFrameOptions): ScribblerJsx.Element;
753
638
 
754
- declare function taskStatusText(status: TaskResultStatus, task: Task): ScribblerJsx.Element;
755
-
756
639
  declare function fileViewText(lines: Array<ScribblerJsx.Element>, addEmptyFinalLine: boolean): ScribblerJsx.Element;
757
640
 
758
641
  declare function formattedText(input: string | Array<string> | Record<string, unknown>): ScribblerJsx.Element;
@@ -776,6 +659,8 @@ declare function summaryText({ duration, expectCount, fileCount, targetCount, te
776
659
  testCount: ResultCount;
777
660
  }): ScribblerJsx.Element;
778
661
 
662
+ declare function taskStatusText(status: TaskResultStatus, task: Task): ScribblerJsx.Element;
663
+
779
664
  declare function testNameText(status: "fail" | "pass" | "skip" | "todo", name: string, indent?: number): ScribblerJsx.Element;
780
665
 
781
666
  declare function usesCompilerText(compilerVersion: string, projectConfigFilePath: string | undefined, options?: {
@@ -798,17 +683,8 @@ interface SelectHookContext {
798
683
  resolvedConfig: ResolvedConfig;
799
684
  }
800
685
  interface Plugin {
801
- /**
802
- * The name of this plugin.
803
- */
804
686
  name: string;
805
- /**
806
- * Is called after configuration is resolved and allows to modify it.
807
- */
808
687
  config?: (resolvedConfig: ResolvedConfig) => ResolvedConfig | Promise<ResolvedConfig>;
809
- /**
810
- * Is called after test files are selected and allows to modify the list.
811
- */
812
688
  select?: (this: SelectHookContext, testFiles: Array<string>) => Array<string | URL> | Promise<Array<string | URL>>;
813
689
  }
814
690
 
package/build/tstyche.js CHANGED
@@ -8,73 +8,6 @@ import { createRequire } from 'node:module';
8
8
  import vm from 'node:vm';
9
9
  import streamConsumers from 'node:stream/consumers';
10
10
 
11
- class ConfigDiagnosticText {
12
- static expected(element) {
13
- return `Expected ${element}.`;
14
- }
15
- static expectsListItemType(optionName, optionBrand) {
16
- return `Item of the '${optionName}' list must be of type ${optionBrand}.`;
17
- }
18
- static expectsValue(optionName) {
19
- return `Option '${optionName}' expects a value.`;
20
- }
21
- static fileDoesNotExist(filePath) {
22
- return `The specified path '${filePath}' does not exist.`;
23
- }
24
- static inspectSupportedVersions() {
25
- return "Use the '--list' command line option to inspect the list of supported versions.";
26
- }
27
- static moduleWasNotFound(specifier) {
28
- return `The specified module '${specifier}' was not found.`;
29
- }
30
- static rangeIsNotValid(value) {
31
- return `The specified range '${value}' is not valid.`;
32
- }
33
- static rangeUsage() {
34
- return [
35
- "A range must be specified using an operator and a minor version.",
36
- "To set an upper bound, the intersection of two ranges can be used.",
37
- "Examples: '>=5.5', '>=5.0 <5.3'.",
38
- ];
39
- }
40
- static requiresValueType(optionName, optionBrand) {
41
- return `Option '${optionName}' requires a value of type ${optionBrand}.`;
42
- }
43
- static seen(element) {
44
- return `The ${element} was seen here.`;
45
- }
46
- static testFileMatchCannotStartWith(segment) {
47
- return [
48
- `A test file match pattern cannot start with '${segment}'.`,
49
- "The test files are only collected within the 'rootPath' directory.",
50
- ];
51
- }
52
- static unknownOption(optionName) {
53
- return `Unknown option '${optionName}'.`;
54
- }
55
- static usage(optionName, optionBrand) {
56
- switch (optionName.startsWith("--") ? optionName.slice(2) : optionName) {
57
- case "target": {
58
- const text = [];
59
- if (optionName.startsWith("--")) {
60
- text.push("Value for the '--target' option must be a string or a comma separated list.", "Examples: '--target 5.2', '--target next', '--target '>=5.0 <5.3, 5.4.2, >=5.5''.");
61
- }
62
- return text;
63
- }
64
- }
65
- return [ConfigDiagnosticText.requiresValueType(optionName, optionBrand)];
66
- }
67
- static versionIsNotSupported(value) {
68
- if (value === "current") {
69
- return "Cannot use 'current' as a target. Failed to resolve the installed TypeScript module.";
70
- }
71
- return `TypeScript version '${value}' is not supported.`;
72
- }
73
- static watchCannotBeEnabled() {
74
- return "Watch mode cannot be enabled in continuous integration environment.";
75
- }
76
- }
77
-
78
11
  var DiagnosticCategory;
79
12
  (function (DiagnosticCategory) {
80
13
  DiagnosticCategory["Error"] = "error";
@@ -279,6 +212,76 @@ class Path {
279
212
  }
280
213
  }
281
214
 
215
+ class ConfigDiagnosticText {
216
+ static expected(element) {
217
+ return `Expected ${element}.`;
218
+ }
219
+ static expectsListItemType(optionName, optionBrand) {
220
+ return `Item of the '${optionName}' list must be of type ${optionBrand}.`;
221
+ }
222
+ static expectsValue(optionName) {
223
+ return `Option '${optionName}' expects a value.`;
224
+ }
225
+ static fileDoesNotExist(filePath) {
226
+ return `The specified path '${filePath}' does not exist.`;
227
+ }
228
+ static inspectSupportedVersions() {
229
+ return "Use the '--list' command line option to inspect the list of supported versions.";
230
+ }
231
+ static moduleWasNotFound(specifier) {
232
+ return `The specified module '${specifier}' was not found.`;
233
+ }
234
+ static rangeIsNotValid(value) {
235
+ return `The specified range '${value}' is not valid.`;
236
+ }
237
+ static rangeUsage() {
238
+ return [
239
+ "A range must be specified using an operator and a minor version.",
240
+ "To set an upper bound, the intersection of two ranges can be used.",
241
+ "Examples: '>=5.5', '>=5.0 <5.3'.",
242
+ ];
243
+ }
244
+ static requiresValueType(optionName, optionBrand) {
245
+ return `Option '${optionName}' requires a value of type ${optionBrand}.`;
246
+ }
247
+ static seen(element) {
248
+ return `The ${element} was seen here.`;
249
+ }
250
+ static testFileMatchCannotStartWith(segment) {
251
+ return [
252
+ `A test file match pattern cannot start with '${segment}'.`,
253
+ "The test files are only collected within the 'rootPath' directory.",
254
+ ];
255
+ }
256
+ static unexpected(element) {
257
+ return `Unexpected ${element}.`;
258
+ }
259
+ static unknownOption(optionName) {
260
+ return `Unknown option '${optionName}'.`;
261
+ }
262
+ static usage(optionName, optionBrand) {
263
+ switch (optionName.startsWith("--") ? optionName.slice(2) : optionName) {
264
+ case "target": {
265
+ const text = [];
266
+ if (optionName.startsWith("--")) {
267
+ text.push("Value for the '--target' option must be a string or a comma separated list.", "Examples: '--target 5.2', '--target next', '--target '>=5.0 <5.3, 5.4.2, >=5.5''.");
268
+ }
269
+ return text;
270
+ }
271
+ }
272
+ return [ConfigDiagnosticText.requiresValueType(optionName, optionBrand)];
273
+ }
274
+ static versionIsNotSupported(value) {
275
+ if (value === "current") {
276
+ return "Cannot use 'current' as a target. Failed to resolve the installed TypeScript module.";
277
+ }
278
+ return `TypeScript version '${value}' is not supported.`;
279
+ }
280
+ static watchCannotBeEnabled() {
281
+ return "Watch mode cannot be enabled in continuous integration environment.";
282
+ }
283
+ }
284
+
282
285
  var OptionBrand;
283
286
  (function (OptionBrand) {
284
287
  OptionBrand["String"] = "string";
@@ -510,7 +513,7 @@ class LockService {
510
513
  }
511
514
  return isLocked;
512
515
  }
513
- async #sleep(delay) {
516
+ #sleep(delay) {
514
517
  return new Promise((resolve) => setTimeout(resolve, delay));
515
518
  }
516
519
  }
@@ -1375,6 +1378,13 @@ class ConfigParser {
1375
1378
  related: [Diagnostic.error(relatedText, leftBraceToken.origin)],
1376
1379
  });
1377
1380
  this.#onDiagnostics(diagnostic);
1381
+ return;
1382
+ }
1383
+ const unexpectedToken = this.#jsonScanner.readToken(/\S/);
1384
+ if (unexpectedToken.text != null) {
1385
+ const text = ConfigDiagnosticText.unexpected("token");
1386
+ const diagnostic = Diagnostic.error(text, unexpectedToken.origin);
1387
+ this.#onDiagnostics(diagnostic);
1378
1388
  }
1379
1389
  }
1380
1390
  async parse() {
@@ -1382,6 +1392,19 @@ class ConfigParser {
1382
1392
  }
1383
1393
  }
1384
1394
 
1395
+ const defaultOptions = {
1396
+ checkSourceFiles: true,
1397
+ failFast: false,
1398
+ plugins: [],
1399
+ rejectAnyType: true,
1400
+ rejectNeverType: true,
1401
+ reporters: ["list", "summary"],
1402
+ rootPath: Path.resolve("./"),
1403
+ target: environmentOptions.typescriptModule != null ? ["current"] : ["latest"],
1404
+ testFileMatch: ["**/*.tst.*", "**/__typetests__/*.test.*", "**/typetests/*.test.*"],
1405
+ tsconfig: "findup",
1406
+ };
1407
+
1385
1408
  class JsonNode {
1386
1409
  origin;
1387
1410
  text;
@@ -1474,9 +1497,10 @@ class JsonScanner {
1474
1497
  readToken(token) {
1475
1498
  this.#skipTrivia();
1476
1499
  this.#previousPosition = this.#position;
1477
- if (this.#peekCharacter() === token) {
1500
+ const character = this.#peekCharacter();
1501
+ if (typeof token === "string" ? token === character : token.test(character)) {
1478
1502
  this.#position++;
1479
- return new JsonNode(token, this.#getOrigin());
1503
+ return new JsonNode(character, this.#getOrigin());
1480
1504
  }
1481
1505
  return new JsonNode(undefined, this.#getOrigin());
1482
1506
  }
@@ -1514,19 +1538,6 @@ class JsonScanner {
1514
1538
  }
1515
1539
  }
1516
1540
 
1517
- const defaultOptions = {
1518
- checkSourceFiles: true,
1519
- failFast: false,
1520
- plugins: [],
1521
- rejectAnyType: true,
1522
- rejectNeverType: true,
1523
- reporters: ["list", "summary"],
1524
- rootPath: Path.resolve("./"),
1525
- target: environmentOptions.typescriptModule != null ? ["current"] : ["latest"],
1526
- testFileMatch: ["**/*.tst.*", "**/__typetests__/*.test.*", "**/typetests/*.test.*"],
1527
- tsconfig: "findup",
1528
- };
1529
-
1530
1541
  class Config {
1531
1542
  static #onDiagnostics(diagnostic) {
1532
1543
  EventEmitter.dispatch(["config:error", { diagnostics: [diagnostic] }]);
@@ -1578,20 +1589,19 @@ class Config {
1578
1589
  }
1579
1590
 
1580
1591
  class DirectiveDiagnosticText {
1581
- static doesNotTakeArgument(directiveName) {
1582
- return `Directive '${directiveName}' does not take an argument.`;
1592
+ static doesNotTakeArgument() {
1593
+ return "Directive does not take an argument.";
1583
1594
  }
1584
1595
  static isNotSupported(directive) {
1585
1596
  return `The '${directive}' directive is not supported.`;
1586
1597
  }
1587
- static requiresArgument(directiveName) {
1588
- return `Directive '${directiveName}' requires an argument.`;
1598
+ static requiresArgument() {
1599
+ return "Directive requires an argument.";
1589
1600
  }
1590
1601
  }
1591
1602
 
1592
1603
  class Directive {
1593
- static #commentSeparatorRegex = /--+/;
1594
- static #directiveRegex = /^(\/\/\s*@tstyche)(\s*|-)?(\S*)?(\s*)?(.*)?/i;
1604
+ static #directiveRegex = /^(\/\/ *@tstyche)( *|-)?(\S*)?( *)?(.*)?/i;
1595
1605
  static getDirectiveRanges(compiler, sourceFile, position = 0) {
1596
1606
  const comments = compiler.getLeadingCommentRanges(sourceFile.text, position);
1597
1607
  if (!comments || comments.length === 0) {
@@ -1620,28 +1630,28 @@ class Directive {
1620
1630
  return inlineConfig;
1621
1631
  }
1622
1632
  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];
1633
+ const [text] = sourceFile.text.substring(comment.pos, comment.end).split(/--+/);
1634
+ const match = text?.match(Directive.#directiveRegex);
1635
+ const namespaceText = match?.[1];
1626
1636
  if (!namespaceText) {
1627
1637
  return;
1628
1638
  }
1629
- const ranges = {
1639
+ const range = {
1630
1640
  namespace: { start: comment.pos, end: comment.pos + namespaceText.length, text: namespaceText },
1631
1641
  };
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 };
1642
+ const directiveSeparatorText = match?.[2];
1643
+ const directiveText = match?.[3];
1644
+ if (typeof directiveText === "string" && typeof directiveSeparatorText === "string") {
1645
+ const start = range.namespace.end + directiveSeparatorText.length;
1646
+ range.directive = { start, end: start + directiveText.length, text: directiveText };
1647
+ const argumentSeparatorText = match?.[4];
1648
+ const argumentText = match?.[5]?.trimEnd();
1649
+ if (typeof argumentSeparatorText === "string" && typeof argumentText === "string") {
1650
+ const start = range.directive.end + argumentSeparatorText.length;
1651
+ range.argument = { start, end: start + argumentText.length, text: argumentText };
1652
+ }
1643
1653
  }
1644
- return ranges;
1654
+ return range;
1645
1655
  }
1646
1656
  static #onDiagnostics(diagnostic) {
1647
1657
  EventEmitter.dispatch(["directive:error", { diagnostics: [diagnostic] }]);
@@ -1651,8 +1661,8 @@ class Directive {
1651
1661
  case "if":
1652
1662
  {
1653
1663
  if (!ranges.argument?.text) {
1654
- const text = DirectiveDiagnosticText.requiresArgument(ranges.directive.text);
1655
- const origin = new DiagnosticOrigin(ranges.directive.start, ranges.directive.end, sourceFile);
1664
+ const text = DirectiveDiagnosticText.requiresArgument();
1665
+ const origin = new DiagnosticOrigin(ranges.namespace.start, ranges.directive.end, sourceFile);
1656
1666
  Directive.#onDiagnostics(Diagnostic.error(text, origin));
1657
1667
  return;
1658
1668
  }
@@ -1662,8 +1672,8 @@ class Directive {
1662
1672
  return;
1663
1673
  case "template":
1664
1674
  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);
1675
+ const text = DirectiveDiagnosticText.doesNotTakeArgument();
1676
+ const origin = new DiagnosticOrigin(ranges.argument.start, ranges.argument.end, sourceFile);
1667
1677
  Directive.#onDiagnostics(Diagnostic.error(text, origin));
1668
1678
  }
1669
1679
  inlineConfig.template = true;
@@ -2204,33 +2214,6 @@ function diagnosticText(diagnostic, codeFrameOptions = {}) {
2204
2214
  return (jsx(Text, { children: [prefix, jsx(DiagnosticText, { codeFrameOptions: codeFrameOptions, diagnostic: diagnostic })] }));
2205
2215
  }
2206
2216
 
2207
- function FileNameText({ filePath }) {
2208
- const relativePath = Path.relative("", filePath);
2209
- const lastPathSeparator = relativePath.lastIndexOf("/");
2210
- const directoryNameText = relativePath.slice(0, lastPathSeparator + 1);
2211
- const fileNameText = relativePath.slice(lastPathSeparator + 1);
2212
- return (jsx(Text, { children: [jsx(Text, { color: Color.Gray, children: directoryNameText }), fileNameText] }));
2213
- }
2214
- function taskStatusText(status, task) {
2215
- let statusColor;
2216
- let statusText;
2217
- switch (status) {
2218
- case ResultStatus.Runs:
2219
- statusColor = Color.Yellow;
2220
- statusText = "runs";
2221
- break;
2222
- case ResultStatus.Passed:
2223
- statusColor = Color.Green;
2224
- statusText = "pass";
2225
- break;
2226
- case ResultStatus.Failed:
2227
- statusColor = Color.Red;
2228
- statusText = "fail";
2229
- break;
2230
- }
2231
- return (jsx(Line, { children: [jsx(Text, { color: statusColor, children: statusText }), " ", jsx(FileNameText, { filePath: task.filePath })] }));
2232
- }
2233
-
2234
2217
  function fileViewText(lines, addEmptyFinalLine) {
2235
2218
  return (jsx(Text, { children: [[...lines], addEmptyFinalLine ? jsx(Line, {}) : undefined] }));
2236
2219
  }
@@ -2350,6 +2333,33 @@ function summaryText({ duration, expectCount, fileCount, targetCount, testCount,
2350
2333
  return (jsx(Text, { children: [targetCountText, fileCountText, testCount.total > 0 ? testCountText : undefined, expectCount.total > 0 ? assertionCountText : undefined, jsx(RowText, { label: "Duration", text: jsx(DurationText, { seconds: duration / 1000 }) })] }));
2351
2334
  }
2352
2335
 
2336
+ function FileNameText({ filePath }) {
2337
+ const relativePath = Path.relative("", filePath);
2338
+ const lastPathSeparator = relativePath.lastIndexOf("/");
2339
+ const directoryNameText = relativePath.slice(0, lastPathSeparator + 1);
2340
+ const fileNameText = relativePath.slice(lastPathSeparator + 1);
2341
+ return (jsx(Text, { children: [jsx(Text, { color: Color.Gray, children: directoryNameText }), fileNameText] }));
2342
+ }
2343
+ function taskStatusText(status, task) {
2344
+ let statusColor;
2345
+ let statusText;
2346
+ switch (status) {
2347
+ case ResultStatus.Runs:
2348
+ statusColor = Color.Yellow;
2349
+ statusText = "runs";
2350
+ break;
2351
+ case ResultStatus.Passed:
2352
+ statusColor = Color.Green;
2353
+ statusText = "pass";
2354
+ break;
2355
+ case ResultStatus.Failed:
2356
+ statusColor = Color.Red;
2357
+ statusText = "fail";
2358
+ break;
2359
+ }
2360
+ return (jsx(Line, { children: [jsx(Text, { color: statusColor, children: statusText }), " ", jsx(FileNameText, { filePath: task.filePath })] }));
2361
+ }
2362
+
2353
2363
  function StatusText({ status }) {
2354
2364
  switch (status) {
2355
2365
  case "fail":
@@ -2635,6 +2645,14 @@ class Task {
2635
2645
  }
2636
2646
  }
2637
2647
 
2648
+ var CancellationReason;
2649
+ (function (CancellationReason) {
2650
+ CancellationReason["ConfigChange"] = "configChange";
2651
+ CancellationReason["ConfigError"] = "configError";
2652
+ CancellationReason["FailFast"] = "failFast";
2653
+ CancellationReason["WatchClose"] = "watchClose";
2654
+ })(CancellationReason || (CancellationReason = {}));
2655
+
2638
2656
  class CancellationToken {
2639
2657
  #isCancelled = false;
2640
2658
  #reason;
@@ -2658,14 +2676,6 @@ class CancellationToken {
2658
2676
  }
2659
2677
  }
2660
2678
 
2661
- var CancellationReason;
2662
- (function (CancellationReason) {
2663
- CancellationReason["ConfigChange"] = "configChange";
2664
- CancellationReason["ConfigError"] = "configError";
2665
- CancellationReason["FailFast"] = "failFast";
2666
- CancellationReason["WatchClose"] = "watchClose";
2667
- })(CancellationReason || (CancellationReason = {}));
2668
-
2669
2679
  class Watcher {
2670
2680
  #onChanged;
2671
2681
  #onRemoved;
@@ -3037,7 +3047,8 @@ class AssertionNode extends TestTreeNode {
3037
3047
  this.target = this.matcherNode.typeArguments ?? this.matcherNode.arguments;
3038
3048
  }
3039
3049
  for (const diagnostic of parent.diagnostics) {
3040
- if (diagnosticBelongsToNode(diagnostic, this.source)) {
3050
+ if (diagnosticBelongsToNode(diagnostic, this.source) ||
3051
+ (this.target != null && diagnosticBelongsToNode(diagnostic, this.target))) {
3041
3052
  this.diagnostics.add(diagnostic);
3042
3053
  parent.diagnostics.delete(diagnostic);
3043
3054
  }
@@ -3629,6 +3640,7 @@ var RunMode;
3629
3640
  RunMode[RunMode["Only"] = 2] = "Only";
3630
3641
  RunMode[RunMode["Skip"] = 4] = "Skip";
3631
3642
  RunMode[RunMode["Todo"] = 8] = "Todo";
3643
+ RunMode[RunMode["Void"] = 16] = "Void";
3632
3644
  })(RunMode || (RunMode = {}));
3633
3645
 
3634
3646
  class EnsureDiagnosticText {
@@ -4674,7 +4686,7 @@ class TestTreeWalker {
4674
4686
  const directiveRanges = node.getDirectiveRanges(this.#compiler);
4675
4687
  const inlineConfig = await Directive.getInlineConfig(directiveRanges);
4676
4688
  if (inlineConfig?.if?.target != null && !Version.isIncluded(this.#compiler.version, inlineConfig.if.target)) {
4677
- mode |= RunMode.Skip;
4689
+ mode |= RunMode.Void;
4678
4690
  }
4679
4691
  if (node.flags & TestTreeNodeFlags.Fail) {
4680
4692
  mode |= RunMode.Fail;
@@ -4719,9 +4731,12 @@ class TestTreeWalker {
4719
4731
  }
4720
4732
  async #visitAssertion(assertion, runMode, parentResult) {
4721
4733
  await this.visit(assertion.children, runMode, parentResult);
4734
+ runMode = await this.#resolveRunMode(runMode, assertion);
4735
+ if (runMode & RunMode.Void) {
4736
+ return;
4737
+ }
4722
4738
  const expectResult = new ExpectResult(assertion, parentResult);
4723
4739
  EventEmitter.dispatch(["expect:start", { result: expectResult }]);
4724
- runMode = await this.#resolveRunMode(runMode, assertion);
4725
4740
  if (runMode & RunMode.Skip || (this.#hasOnly && !(runMode & RunMode.Only))) {
4726
4741
  EventEmitter.dispatch(["expect:skip", { result: expectResult }]);
4727
4742
  return;
@@ -4758,9 +4773,12 @@ class TestTreeWalker {
4758
4773
  }
4759
4774
  }
4760
4775
  async #visitDescribe(describe, runMode, parentResult) {
4776
+ runMode = await this.#resolveRunMode(runMode, describe);
4777
+ if (runMode & RunMode.Void) {
4778
+ return;
4779
+ }
4761
4780
  const describeResult = new DescribeResult(describe, parentResult);
4762
4781
  EventEmitter.dispatch(["describe:start", { result: describeResult }]);
4763
- runMode = await this.#resolveRunMode(runMode, describe);
4764
4782
  if (!(runMode & RunMode.Skip || (this.#hasOnly && !(runMode & RunMode.Only)) || runMode & RunMode.Todo) &&
4765
4783
  describe.diagnostics.size > 0) {
4766
4784
  this.#onTaskDiagnostics(Diagnostic.fromDiagnostics([...describe.diagnostics]));
@@ -4771,9 +4789,12 @@ class TestTreeWalker {
4771
4789
  EventEmitter.dispatch(["describe:end", { result: describeResult }]);
4772
4790
  }
4773
4791
  async #visitTest(test, runMode, parentResult) {
4792
+ runMode = await this.#resolveRunMode(runMode, test);
4793
+ if (runMode & RunMode.Void) {
4794
+ return;
4795
+ }
4774
4796
  const testResult = new TestResult(test, parentResult);
4775
4797
  EventEmitter.dispatch(["test:start", { result: testResult }]);
4776
- runMode = await this.#resolveRunMode(runMode, test);
4777
4798
  if (runMode & RunMode.Todo) {
4778
4799
  EventEmitter.dispatch(["test:todo", { result: testResult }]);
4779
4800
  return;
@@ -4894,7 +4915,7 @@ class TaskRunner {
4894
4915
  class Runner {
4895
4916
  #eventEmitter = new EventEmitter();
4896
4917
  #resolvedConfig;
4897
- static version = "4.0.0-rc.1";
4918
+ static version = "4.0.1";
4898
4919
  constructor(resolvedConfig) {
4899
4920
  this.#resolvedConfig = resolvedConfig;
4900
4921
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tstyche",
3
- "version": "4.0.0-rc.1",
4
- "description": "The Essential Type Testing Tool.",
3
+ "version": "4.0.1",
4
+ "description": "Everything You Need for Type Testing.",
5
5
  "keywords": [
6
6
  "typescript",
7
7
  "types",