tstyche 4.0.0-rc.0 → 4.0.0
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 +15 -7
- package/build/tstyche.d.ts +135 -102
- package/build/tstyche.js +336 -192
- package/package.json +2 -2
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
|
-
|
|
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
|
|
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
|
|
48
|
-
|
|
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("
|
|
52
|
-
|
|
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
|
-
|
|
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
|
|
package/build/tstyche.d.ts
CHANGED
|
@@ -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",
|
|
@@ -94,58 +40,13 @@ declare class ConfigDiagnosticText {
|
|
|
94
40
|
static requiresValueType(optionName: string, optionBrand: OptionBrand): string;
|
|
95
41
|
static seen(element: string): string;
|
|
96
42
|
static testFileMatchCannotStartWith(segment: string): Array<string>;
|
|
43
|
+
static unexpected(element: string): string;
|
|
97
44
|
static unknownOption(optionName: string): string;
|
|
98
45
|
static usage(optionName: string, optionBrand: OptionBrand): Array<string>;
|
|
99
46
|
static versionIsNotSupported(value: string): string;
|
|
100
47
|
static watchCannotBeEnabled(): string;
|
|
101
48
|
}
|
|
102
49
|
|
|
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
50
|
/**
|
|
150
51
|
* Options passed through the command line.
|
|
151
52
|
*/
|
|
@@ -220,6 +121,58 @@ interface CommandLineOptions {
|
|
|
220
121
|
watch?: boolean;
|
|
221
122
|
}
|
|
222
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Options loaded from the configuration file.
|
|
126
|
+
*/
|
|
127
|
+
interface ConfigFileOptions {
|
|
128
|
+
/**
|
|
129
|
+
* Enable type error reporting for source files.
|
|
130
|
+
*/
|
|
131
|
+
checkSourceFiles?: boolean;
|
|
132
|
+
/**
|
|
133
|
+
* Stop running tests after the first failed assertion.
|
|
134
|
+
*/
|
|
135
|
+
failFast?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* The list of plugins to use.
|
|
138
|
+
*/
|
|
139
|
+
plugins?: Array<string>;
|
|
140
|
+
/**
|
|
141
|
+
* Reject the 'any' type passed as an argument to the 'expect()' function or a matcher.
|
|
142
|
+
*/
|
|
143
|
+
rejectAnyType?: boolean;
|
|
144
|
+
/**
|
|
145
|
+
* Reject the 'never' type passed as an argument to the 'expect()' function or a matcher.
|
|
146
|
+
*/
|
|
147
|
+
rejectNeverType?: boolean;
|
|
148
|
+
/**
|
|
149
|
+
* The list of reporters to use.
|
|
150
|
+
*/
|
|
151
|
+
reporters?: Array<string>;
|
|
152
|
+
/**
|
|
153
|
+
* The path to a directory containing files of a test project.
|
|
154
|
+
*/
|
|
155
|
+
rootPath?: string;
|
|
156
|
+
/**
|
|
157
|
+
* The list of TypeScript versions to be tested on.
|
|
158
|
+
*/
|
|
159
|
+
target?: Array<string>;
|
|
160
|
+
/**
|
|
161
|
+
* The list of glob patterns matching the test files.
|
|
162
|
+
*/
|
|
163
|
+
testFileMatch?: Array<string>;
|
|
164
|
+
/**
|
|
165
|
+
* The look up strategy to be used to find the TSConfig file.
|
|
166
|
+
*/
|
|
167
|
+
tsconfig?: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
interface InlineConfig {
|
|
171
|
+
if?: {
|
|
172
|
+
target?: Array<string>;
|
|
173
|
+
};
|
|
174
|
+
template?: boolean;
|
|
175
|
+
}
|
|
223
176
|
interface ResolvedConfig extends Omit<CommandLineOptions, "config" | keyof ConfigFileOptions>, Required<ConfigFileOptions> {
|
|
224
177
|
/**
|
|
225
178
|
* The path to a TSTyche configuration file.
|
|
@@ -230,6 +183,7 @@ interface ResolvedConfig extends Omit<CommandLineOptions, "config" | keyof Confi
|
|
|
230
183
|
*/
|
|
231
184
|
pathMatch: Array<string>;
|
|
232
185
|
}
|
|
186
|
+
|
|
233
187
|
declare class Config {
|
|
234
188
|
#private;
|
|
235
189
|
static parseCommandLine(commandLine: Array<string>): Promise<{
|
|
@@ -249,6 +203,25 @@ declare class Config {
|
|
|
249
203
|
static resolveConfigFilePath(filePath?: string): string;
|
|
250
204
|
}
|
|
251
205
|
|
|
206
|
+
interface TextRange {
|
|
207
|
+
start: number;
|
|
208
|
+
end: number;
|
|
209
|
+
text: string;
|
|
210
|
+
}
|
|
211
|
+
interface DirectiveRange {
|
|
212
|
+
namespace: TextRange;
|
|
213
|
+
directive?: TextRange;
|
|
214
|
+
argument?: TextRange;
|
|
215
|
+
}
|
|
216
|
+
type DirectiveRanges = Array<DirectiveRange> & {
|
|
217
|
+
sourceFile: ts.SourceFile;
|
|
218
|
+
};
|
|
219
|
+
declare class Directive {
|
|
220
|
+
#private;
|
|
221
|
+
static getDirectiveRanges(compiler: typeof ts, sourceFile: ts.SourceFile, position?: number): DirectiveRanges | undefined;
|
|
222
|
+
static getInlineConfig(ranges: DirectiveRanges | undefined): Promise<InlineConfig | undefined>;
|
|
223
|
+
}
|
|
224
|
+
|
|
252
225
|
declare enum DiagnosticCategory {
|
|
253
226
|
Error = "error",
|
|
254
227
|
Warning = "warning"
|
|
@@ -304,6 +277,7 @@ type DiagnosticsHandler<T extends Diagnostic | Array<Diagnostic> = Diagnostic> =
|
|
|
304
277
|
declare enum OptionGroup {
|
|
305
278
|
CommandLine = 2,
|
|
306
279
|
ConfigFile = 4,
|
|
280
|
+
InlineConditions = 8,
|
|
307
281
|
ResolvedConfig = 6
|
|
308
282
|
}
|
|
309
283
|
|
|
@@ -334,6 +308,62 @@ declare class Options {
|
|
|
334
308
|
|
|
335
309
|
declare const defaultOptions: Required<ConfigFileOptions>;
|
|
336
310
|
|
|
311
|
+
declare enum TestTreeNodeBrand {
|
|
312
|
+
Describe = "describe",
|
|
313
|
+
Test = "test",
|
|
314
|
+
Expect = "expect",
|
|
315
|
+
When = "when"
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
declare enum TestTreeNodeFlags {
|
|
319
|
+
None = 0,
|
|
320
|
+
Fail = 1,
|
|
321
|
+
Only = 2,
|
|
322
|
+
Skip = 4,
|
|
323
|
+
Todo = 8
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
declare class WhenNode extends TestTreeNode {
|
|
327
|
+
actionNode: ts.CallExpression;
|
|
328
|
+
actionNameNode: ts.PropertyAccessExpression;
|
|
329
|
+
abilityDiagnostics: Set<ts.Diagnostic> | undefined;
|
|
330
|
+
target: ts.NodeArray<ts.Expression> | ts.NodeArray<ts.TypeNode>;
|
|
331
|
+
constructor(compiler: typeof ts, brand: TestTreeNodeBrand, node: ts.CallExpression, parent: TestTree | TestTreeNode, flags: TestTreeNodeFlags, actionNode: ts.CallExpression, actionNameNode: ts.PropertyAccessExpression);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
declare class TestTreeNode {
|
|
335
|
+
brand: TestTreeNodeBrand;
|
|
336
|
+
children: Array<TestTreeNode | AssertionNode | WhenNode>;
|
|
337
|
+
diagnostics: Set<ts.Diagnostic>;
|
|
338
|
+
flags: TestTreeNodeFlags;
|
|
339
|
+
name: string;
|
|
340
|
+
node: ts.CallExpression;
|
|
341
|
+
parent: TestTree | TestTreeNode;
|
|
342
|
+
constructor(compiler: typeof ts, brand: TestTreeNodeBrand, node: ts.CallExpression, parent: TestTree | TestTreeNode, flags: TestTreeNodeFlags);
|
|
343
|
+
getDirectiveRanges(compiler: typeof ts): DirectiveRanges | undefined;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
declare class TestTree {
|
|
347
|
+
children: Array<TestTreeNode | AssertionNode | WhenNode>;
|
|
348
|
+
diagnostics: Set<ts.Diagnostic>;
|
|
349
|
+
hasOnly: boolean;
|
|
350
|
+
sourceFile: ts.SourceFile;
|
|
351
|
+
constructor(diagnostics: Set<ts.Diagnostic>, sourceFile: ts.SourceFile);
|
|
352
|
+
getDirectiveRanges(compiler: typeof ts): DirectiveRanges | undefined;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
declare class AssertionNode extends TestTreeNode {
|
|
356
|
+
abilityDiagnostics: Set<ts.Diagnostic> | undefined;
|
|
357
|
+
isNot: boolean;
|
|
358
|
+
matcherNode: ts.CallExpression | ts.Decorator;
|
|
359
|
+
matcherNameNode: ts.PropertyAccessExpression;
|
|
360
|
+
modifierNode: ts.PropertyAccessExpression;
|
|
361
|
+
notNode: ts.PropertyAccessExpression | undefined;
|
|
362
|
+
source: ts.NodeArray<ts.Expression> | ts.NodeArray<ts.TypeNode>;
|
|
363
|
+
target: ts.NodeArray<ts.Expression> | ts.NodeArray<ts.TypeNode> | undefined;
|
|
364
|
+
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);
|
|
365
|
+
}
|
|
366
|
+
|
|
337
367
|
declare class ProjectService {
|
|
338
368
|
#private;
|
|
339
369
|
constructor(compiler: typeof ts, resolvedConfig: ResolvedConfig);
|
|
@@ -546,6 +576,8 @@ type Event = ["config:error", {
|
|
|
546
576
|
result: TaskResult;
|
|
547
577
|
}] | ["task:end", {
|
|
548
578
|
result: TaskResult;
|
|
579
|
+
}] | ["directive:error", {
|
|
580
|
+
diagnostics: Array<Diagnostic>;
|
|
549
581
|
}] | ["collect:start", {
|
|
550
582
|
tree: TestTree;
|
|
551
583
|
}] | ["collect:error", {
|
|
@@ -855,6 +887,7 @@ declare class Store {
|
|
|
855
887
|
declare class Version {
|
|
856
888
|
#private;
|
|
857
889
|
static isGreaterThan(source: string, target: string): boolean;
|
|
890
|
+
static isIncluded(source: string, range: Array<string>): boolean;
|
|
858
891
|
static isSatisfiedWith(source: string, target: string): boolean;
|
|
859
892
|
}
|
|
860
893
|
|
|
@@ -886,5 +919,5 @@ declare class WhenService {
|
|
|
886
919
|
action(when: WhenNode): void;
|
|
887
920
|
}
|
|
888
921
|
|
|
889
|
-
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 };
|
|
890
|
-
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 };
|
|
922
|
+
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 };
|
|
923
|
+
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
|
@@ -49,6 +49,9 @@ class ConfigDiagnosticText {
|
|
|
49
49
|
"The test files are only collected within the 'rootPath' directory.",
|
|
50
50
|
];
|
|
51
51
|
}
|
|
52
|
+
static unexpected(element) {
|
|
53
|
+
return `Unexpected ${element}.`;
|
|
54
|
+
}
|
|
52
55
|
static unknownOption(optionName) {
|
|
53
56
|
return `Unknown option '${optionName}'.`;
|
|
54
57
|
}
|
|
@@ -292,6 +295,7 @@ var OptionGroup;
|
|
|
292
295
|
(function (OptionGroup) {
|
|
293
296
|
OptionGroup[OptionGroup["CommandLine"] = 2] = "CommandLine";
|
|
294
297
|
OptionGroup[OptionGroup["ConfigFile"] = 4] = "ConfigFile";
|
|
298
|
+
OptionGroup[OptionGroup["InlineConditions"] = 8] = "InlineConditions";
|
|
295
299
|
OptionGroup[OptionGroup["ResolvedConfig"] = 6] = "ResolvedConfig";
|
|
296
300
|
})(OptionGroup || (OptionGroup = {}));
|
|
297
301
|
|
|
@@ -376,6 +380,9 @@ class Version {
|
|
|
376
380
|
static isGreaterThan(source, target) {
|
|
377
381
|
return !(source === target) && Version.#satisfies(source, target);
|
|
378
382
|
}
|
|
383
|
+
static isIncluded(source, range) {
|
|
384
|
+
return range.some((target) => source.startsWith(target));
|
|
385
|
+
}
|
|
379
386
|
static isSatisfiedWith(source, target) {
|
|
380
387
|
return source === target || Version.#satisfies(source, target);
|
|
381
388
|
}
|
|
@@ -1004,7 +1011,7 @@ class Options {
|
|
|
1004
1011
|
{
|
|
1005
1012
|
brand: OptionBrand.List,
|
|
1006
1013
|
description: "The list of TypeScript versions to be tested on.",
|
|
1007
|
-
group: OptionGroup.CommandLine | OptionGroup.ConfigFile,
|
|
1014
|
+
group: OptionGroup.CommandLine | OptionGroup.ConfigFile | OptionGroup.InlineConditions,
|
|
1008
1015
|
items: {
|
|
1009
1016
|
brand: OptionBrand.String,
|
|
1010
1017
|
name: "target",
|
|
@@ -1239,146 +1246,18 @@ class CommandLineParser {
|
|
|
1239
1246
|
}
|
|
1240
1247
|
}
|
|
1241
1248
|
|
|
1242
|
-
class
|
|
1243
|
-
origin;
|
|
1244
|
-
text;
|
|
1245
|
-
constructor(text, origin) {
|
|
1246
|
-
this.origin = origin;
|
|
1247
|
-
this.text = text;
|
|
1248
|
-
}
|
|
1249
|
-
getValue(options) {
|
|
1250
|
-
if (this.text == null) {
|
|
1251
|
-
return undefined;
|
|
1252
|
-
}
|
|
1253
|
-
if (/^['"]/.test(this.text)) {
|
|
1254
|
-
return this.text.slice(1, -1);
|
|
1255
|
-
}
|
|
1256
|
-
if (options?.expectsIdentifier) {
|
|
1257
|
-
return this.text;
|
|
1258
|
-
}
|
|
1259
|
-
if (this.text === "true") {
|
|
1260
|
-
return true;
|
|
1261
|
-
}
|
|
1262
|
-
if (this.text === "false") {
|
|
1263
|
-
return false;
|
|
1264
|
-
}
|
|
1265
|
-
if (/^\d/.test(this.text)) {
|
|
1266
|
-
return Number.parseFloat(this.text);
|
|
1267
|
-
}
|
|
1268
|
-
return undefined;
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
class JsonScanner {
|
|
1273
|
-
#currentPosition = 0;
|
|
1274
|
-
#previousPosition = 0;
|
|
1275
|
-
#sourceFile;
|
|
1276
|
-
constructor(sourceFile) {
|
|
1277
|
-
this.#sourceFile = sourceFile;
|
|
1278
|
-
}
|
|
1279
|
-
#getOrigin() {
|
|
1280
|
-
return new DiagnosticOrigin(this.#previousPosition, this.#currentPosition, this.#sourceFile);
|
|
1281
|
-
}
|
|
1282
|
-
isRead() {
|
|
1283
|
-
return !(this.#currentPosition < this.#sourceFile.text.length);
|
|
1284
|
-
}
|
|
1285
|
-
#peekCharacter() {
|
|
1286
|
-
return this.#sourceFile.text.charAt(this.#currentPosition);
|
|
1287
|
-
}
|
|
1288
|
-
#peekNextCharacter() {
|
|
1289
|
-
return this.#sourceFile.text.charAt(this.#currentPosition + 1);
|
|
1290
|
-
}
|
|
1291
|
-
peekToken(token) {
|
|
1292
|
-
this.#skipTrivia();
|
|
1293
|
-
return this.#peekCharacter() === token;
|
|
1294
|
-
}
|
|
1295
|
-
read() {
|
|
1296
|
-
this.#skipTrivia();
|
|
1297
|
-
this.#previousPosition = this.#currentPosition;
|
|
1298
|
-
if (/[\s,:\]}]/.test(this.#peekCharacter())) {
|
|
1299
|
-
return new JsonNode(undefined, this.#getOrigin());
|
|
1300
|
-
}
|
|
1301
|
-
let text = "";
|
|
1302
|
-
let closingTokenText = "";
|
|
1303
|
-
if (/[[{'"]/.test(this.#peekCharacter())) {
|
|
1304
|
-
text += this.#readCharacter();
|
|
1305
|
-
switch (text) {
|
|
1306
|
-
case "[":
|
|
1307
|
-
closingTokenText = "]";
|
|
1308
|
-
break;
|
|
1309
|
-
case "{":
|
|
1310
|
-
closingTokenText = "}";
|
|
1311
|
-
break;
|
|
1312
|
-
default:
|
|
1313
|
-
closingTokenText = text;
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
while (!this.isRead()) {
|
|
1317
|
-
text += this.#readCharacter();
|
|
1318
|
-
if (text.slice(-1) === closingTokenText || (!closingTokenText && /[\s,:\]}]/.test(this.#peekCharacter()))) {
|
|
1319
|
-
break;
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
return new JsonNode(text, this.#getOrigin());
|
|
1323
|
-
}
|
|
1324
|
-
#readCharacter() {
|
|
1325
|
-
return this.#sourceFile.text.charAt(this.#currentPosition++);
|
|
1326
|
-
}
|
|
1327
|
-
readToken(token) {
|
|
1328
|
-
this.#skipTrivia();
|
|
1329
|
-
this.#previousPosition = this.#currentPosition;
|
|
1330
|
-
if (this.#peekCharacter() === token) {
|
|
1331
|
-
this.#currentPosition++;
|
|
1332
|
-
return new JsonNode(token, this.#getOrigin());
|
|
1333
|
-
}
|
|
1334
|
-
return new JsonNode(undefined, this.#getOrigin());
|
|
1335
|
-
}
|
|
1336
|
-
#skipTrivia() {
|
|
1337
|
-
while (!this.isRead()) {
|
|
1338
|
-
if (/\s/.test(this.#peekCharacter())) {
|
|
1339
|
-
this.#currentPosition++;
|
|
1340
|
-
continue;
|
|
1341
|
-
}
|
|
1342
|
-
if (this.#peekCharacter() === "/") {
|
|
1343
|
-
if (this.#peekNextCharacter() === "/") {
|
|
1344
|
-
this.#currentPosition += 2;
|
|
1345
|
-
while (!this.isRead()) {
|
|
1346
|
-
if (this.#readCharacter() === "\n") {
|
|
1347
|
-
break;
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
continue;
|
|
1351
|
-
}
|
|
1352
|
-
if (this.#peekNextCharacter() === "*") {
|
|
1353
|
-
this.#currentPosition += 2;
|
|
1354
|
-
while (!this.isRead()) {
|
|
1355
|
-
if (this.#peekCharacter() === "*" && this.#peekNextCharacter() === "/") {
|
|
1356
|
-
this.#currentPosition += 2;
|
|
1357
|
-
break;
|
|
1358
|
-
}
|
|
1359
|
-
this.#currentPosition++;
|
|
1360
|
-
}
|
|
1361
|
-
continue;
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
break;
|
|
1365
|
-
}
|
|
1366
|
-
this.#previousPosition = this.#currentPosition;
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
class ConfigFileParser {
|
|
1249
|
+
class ConfigParser {
|
|
1371
1250
|
#configFileOptions;
|
|
1372
1251
|
#jsonScanner;
|
|
1373
1252
|
#onDiagnostics;
|
|
1374
1253
|
#options;
|
|
1375
1254
|
#sourceFile;
|
|
1376
|
-
constructor(
|
|
1377
|
-
this.#configFileOptions =
|
|
1378
|
-
this.#
|
|
1255
|
+
constructor(configOptions, optionGroup, sourceFile, jsonScanner, onDiagnostics) {
|
|
1256
|
+
this.#configFileOptions = configOptions;
|
|
1257
|
+
this.#jsonScanner = jsonScanner;
|
|
1379
1258
|
this.#onDiagnostics = onDiagnostics;
|
|
1380
|
-
this.#
|
|
1381
|
-
this.#
|
|
1259
|
+
this.#sourceFile = sourceFile;
|
|
1260
|
+
this.#options = Options.for(optionGroup);
|
|
1382
1261
|
}
|
|
1383
1262
|
#onRequiresValue(optionDefinition, jsonNode, isListItem) {
|
|
1384
1263
|
const text = isListItem
|
|
@@ -1471,9 +1350,8 @@ class ConfigFileParser {
|
|
|
1471
1350
|
if (!optionDefinition) {
|
|
1472
1351
|
const text = ConfigDiagnosticText.unknownOption(optionName);
|
|
1473
1352
|
this.#onDiagnostics(Diagnostic.error(text, optionNameNode.origin));
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
}
|
|
1353
|
+
this.#jsonScanner.readToken(":");
|
|
1354
|
+
this.#jsonScanner.read();
|
|
1477
1355
|
const commaToken = this.#jsonScanner.readToken(",");
|
|
1478
1356
|
if (!commaToken.text) {
|
|
1479
1357
|
break;
|
|
@@ -1500,6 +1378,13 @@ class ConfigFileParser {
|
|
|
1500
1378
|
related: [Diagnostic.error(relatedText, leftBraceToken.origin)],
|
|
1501
1379
|
});
|
|
1502
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);
|
|
1503
1388
|
}
|
|
1504
1389
|
}
|
|
1505
1390
|
async parse() {
|
|
@@ -1507,6 +1392,139 @@ class ConfigFileParser {
|
|
|
1507
1392
|
}
|
|
1508
1393
|
}
|
|
1509
1394
|
|
|
1395
|
+
class JsonNode {
|
|
1396
|
+
origin;
|
|
1397
|
+
text;
|
|
1398
|
+
constructor(text, origin) {
|
|
1399
|
+
this.origin = origin;
|
|
1400
|
+
this.text = text;
|
|
1401
|
+
}
|
|
1402
|
+
getValue(options) {
|
|
1403
|
+
if (this.text == null) {
|
|
1404
|
+
return undefined;
|
|
1405
|
+
}
|
|
1406
|
+
if (/^['"]/.test(this.text)) {
|
|
1407
|
+
return this.text.slice(1, -1);
|
|
1408
|
+
}
|
|
1409
|
+
if (options?.expectsIdentifier) {
|
|
1410
|
+
return this.text;
|
|
1411
|
+
}
|
|
1412
|
+
if (this.text === "true") {
|
|
1413
|
+
return true;
|
|
1414
|
+
}
|
|
1415
|
+
if (this.text === "false") {
|
|
1416
|
+
return false;
|
|
1417
|
+
}
|
|
1418
|
+
if (/^\d/.test(this.text)) {
|
|
1419
|
+
return Number.parseFloat(this.text);
|
|
1420
|
+
}
|
|
1421
|
+
return undefined;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
class JsonScanner {
|
|
1426
|
+
#end;
|
|
1427
|
+
#position;
|
|
1428
|
+
#previousPosition;
|
|
1429
|
+
#sourceFile;
|
|
1430
|
+
constructor(sourceFile, options) {
|
|
1431
|
+
this.#end = options?.end ?? sourceFile.text.length;
|
|
1432
|
+
this.#position = options?.start ?? 0;
|
|
1433
|
+
this.#previousPosition = options?.start ?? 0;
|
|
1434
|
+
this.#sourceFile = sourceFile;
|
|
1435
|
+
}
|
|
1436
|
+
#getOrigin() {
|
|
1437
|
+
return new DiagnosticOrigin(this.#previousPosition, this.#position, this.#sourceFile);
|
|
1438
|
+
}
|
|
1439
|
+
isRead() {
|
|
1440
|
+
return !(this.#position < this.#end);
|
|
1441
|
+
}
|
|
1442
|
+
#peekCharacter() {
|
|
1443
|
+
return this.#sourceFile.text.charAt(this.#position);
|
|
1444
|
+
}
|
|
1445
|
+
#peekNextCharacter() {
|
|
1446
|
+
return this.#sourceFile.text.charAt(this.#position + 1);
|
|
1447
|
+
}
|
|
1448
|
+
peekToken(token) {
|
|
1449
|
+
this.#skipTrivia();
|
|
1450
|
+
return this.#peekCharacter() === token;
|
|
1451
|
+
}
|
|
1452
|
+
read() {
|
|
1453
|
+
this.#skipTrivia();
|
|
1454
|
+
this.#previousPosition = this.#position;
|
|
1455
|
+
if (/[\s,:\]}]/.test(this.#peekCharacter())) {
|
|
1456
|
+
return new JsonNode(undefined, this.#getOrigin());
|
|
1457
|
+
}
|
|
1458
|
+
let text = "";
|
|
1459
|
+
let closingTokenText = "";
|
|
1460
|
+
if (/[[{'"]/.test(this.#peekCharacter())) {
|
|
1461
|
+
text += this.#readCharacter();
|
|
1462
|
+
switch (text) {
|
|
1463
|
+
case "[":
|
|
1464
|
+
closingTokenText = "]";
|
|
1465
|
+
break;
|
|
1466
|
+
case "{":
|
|
1467
|
+
closingTokenText = "}";
|
|
1468
|
+
break;
|
|
1469
|
+
default:
|
|
1470
|
+
closingTokenText = text;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
while (!this.isRead()) {
|
|
1474
|
+
text += this.#readCharacter();
|
|
1475
|
+
if (text.slice(-1) === closingTokenText || (!closingTokenText && /[\s,:\]}]/.test(this.#peekCharacter()))) {
|
|
1476
|
+
break;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
return new JsonNode(text, this.#getOrigin());
|
|
1480
|
+
}
|
|
1481
|
+
#readCharacter() {
|
|
1482
|
+
return this.#sourceFile.text.charAt(this.#position++);
|
|
1483
|
+
}
|
|
1484
|
+
readToken(token) {
|
|
1485
|
+
this.#skipTrivia();
|
|
1486
|
+
this.#previousPosition = this.#position;
|
|
1487
|
+
const character = this.#peekCharacter();
|
|
1488
|
+
if (typeof token === "string" ? token === character : token.test(character)) {
|
|
1489
|
+
this.#position++;
|
|
1490
|
+
return new JsonNode(character, this.#getOrigin());
|
|
1491
|
+
}
|
|
1492
|
+
return new JsonNode(undefined, this.#getOrigin());
|
|
1493
|
+
}
|
|
1494
|
+
#skipTrivia() {
|
|
1495
|
+
while (!this.isRead()) {
|
|
1496
|
+
if (/\s/.test(this.#peekCharacter())) {
|
|
1497
|
+
this.#position++;
|
|
1498
|
+
continue;
|
|
1499
|
+
}
|
|
1500
|
+
if (this.#peekCharacter() === "/") {
|
|
1501
|
+
if (this.#peekNextCharacter() === "/") {
|
|
1502
|
+
this.#position += 2;
|
|
1503
|
+
while (!this.isRead()) {
|
|
1504
|
+
if (this.#readCharacter() === "\n") {
|
|
1505
|
+
break;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
continue;
|
|
1509
|
+
}
|
|
1510
|
+
if (this.#peekNextCharacter() === "*") {
|
|
1511
|
+
this.#position += 2;
|
|
1512
|
+
while (!this.isRead()) {
|
|
1513
|
+
if (this.#peekCharacter() === "*" && this.#peekNextCharacter() === "/") {
|
|
1514
|
+
this.#position += 2;
|
|
1515
|
+
break;
|
|
1516
|
+
}
|
|
1517
|
+
this.#position++;
|
|
1518
|
+
}
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
break;
|
|
1523
|
+
}
|
|
1524
|
+
this.#previousPosition = this.#position;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1510
1528
|
const defaultOptions = {
|
|
1511
1529
|
checkSourceFiles: true,
|
|
1512
1530
|
failFast: false,
|
|
@@ -1544,7 +1562,7 @@ class Config {
|
|
|
1544
1562
|
encoding: "utf8",
|
|
1545
1563
|
});
|
|
1546
1564
|
const sourceFile = new SourceFile(configFilePath, configFileText);
|
|
1547
|
-
const configFileParser = new
|
|
1565
|
+
const configFileParser = new ConfigParser(configFileOptions, OptionGroup.ConfigFile, sourceFile, new JsonScanner(sourceFile), Config.#onDiagnostics);
|
|
1548
1566
|
await configFileParser.parse();
|
|
1549
1567
|
if (configFileOptions.target != null) {
|
|
1550
1568
|
configFileOptions.target = await Target.expand(configFileOptions.target);
|
|
@@ -1570,6 +1588,114 @@ class Config {
|
|
|
1570
1588
|
}
|
|
1571
1589
|
}
|
|
1572
1590
|
|
|
1591
|
+
class DirectiveDiagnosticText {
|
|
1592
|
+
static doesNotTakeArgument(directiveName) {
|
|
1593
|
+
return `Directive '${directiveName}' does not take an argument.`;
|
|
1594
|
+
}
|
|
1595
|
+
static isNotSupported(directive) {
|
|
1596
|
+
return `The '${directive}' directive is not supported.`;
|
|
1597
|
+
}
|
|
1598
|
+
static requiresArgument(directiveName) {
|
|
1599
|
+
return `Directive '${directiveName}' requires an argument.`;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
class Directive {
|
|
1604
|
+
static #commentSeparatorRegex = /--+/;
|
|
1605
|
+
static #directiveRegex = /^(\/\/\s*@tstyche)(\s*|-)?(\S*)?(\s*)?(.*)?/i;
|
|
1606
|
+
static getDirectiveRanges(compiler, sourceFile, position = 0) {
|
|
1607
|
+
const comments = compiler.getLeadingCommentRanges(sourceFile.text, position);
|
|
1608
|
+
if (!comments || comments.length === 0) {
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
const ranges = Object.assign([], { sourceFile });
|
|
1612
|
+
for (const comment of comments) {
|
|
1613
|
+
if (comment.kind !== compiler.SyntaxKind.SingleLineCommentTrivia) {
|
|
1614
|
+
continue;
|
|
1615
|
+
}
|
|
1616
|
+
const range = Directive.#getRange(sourceFile, comment);
|
|
1617
|
+
if (range != null) {
|
|
1618
|
+
ranges.push(range);
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
return ranges;
|
|
1622
|
+
}
|
|
1623
|
+
static async getInlineConfig(ranges) {
|
|
1624
|
+
if (!ranges) {
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
const inlineConfig = {};
|
|
1628
|
+
for (const range of ranges) {
|
|
1629
|
+
await Directive.#parse(inlineConfig, ranges.sourceFile, range);
|
|
1630
|
+
}
|
|
1631
|
+
return inlineConfig;
|
|
1632
|
+
}
|
|
1633
|
+
static #getRange(sourceFile, comment) {
|
|
1634
|
+
const [text] = sourceFile.text.substring(comment.pos, comment.end).split(Directive.#commentSeparatorRegex);
|
|
1635
|
+
const found = text?.match(Directive.#directiveRegex);
|
|
1636
|
+
const namespaceText = found?.[1];
|
|
1637
|
+
if (!namespaceText) {
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
const ranges = {
|
|
1641
|
+
namespace: { start: comment.pos, end: comment.pos + namespaceText.length, text: namespaceText },
|
|
1642
|
+
};
|
|
1643
|
+
const directiveSeparatorText = found?.[2];
|
|
1644
|
+
const directiveText = found?.[3];
|
|
1645
|
+
if (directiveText != null && directiveText.length > 0) {
|
|
1646
|
+
const start = ranges.namespace.end + (directiveSeparatorText?.length ?? 0);
|
|
1647
|
+
ranges.directive = { start, end: start + directiveText.length, text: directiveText };
|
|
1648
|
+
}
|
|
1649
|
+
const argumentSeparatorText = found?.[4];
|
|
1650
|
+
const argumentText = found?.[5]?.trimEnd();
|
|
1651
|
+
if (ranges.directive != null && argumentText != null && argumentText.length > 0) {
|
|
1652
|
+
const start = ranges.directive.end + (argumentSeparatorText?.length ?? 0);
|
|
1653
|
+
ranges.argument = { start, end: start + argumentText.length, text: argumentText };
|
|
1654
|
+
}
|
|
1655
|
+
return ranges;
|
|
1656
|
+
}
|
|
1657
|
+
static #onDiagnostics(diagnostic) {
|
|
1658
|
+
EventEmitter.dispatch(["directive:error", { diagnostics: [diagnostic] }]);
|
|
1659
|
+
}
|
|
1660
|
+
static async #parse(inlineConfig, sourceFile, ranges) {
|
|
1661
|
+
switch (ranges.directive?.text) {
|
|
1662
|
+
case "if":
|
|
1663
|
+
{
|
|
1664
|
+
if (!ranges.argument?.text) {
|
|
1665
|
+
const text = DirectiveDiagnosticText.requiresArgument(ranges.directive.text);
|
|
1666
|
+
const origin = new DiagnosticOrigin(ranges.directive.start, ranges.directive.end, sourceFile);
|
|
1667
|
+
Directive.#onDiagnostics(Diagnostic.error(text, origin));
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
const value = await Directive.#parseJson(sourceFile, ranges.argument.start, ranges.argument.end);
|
|
1671
|
+
inlineConfig.if = value;
|
|
1672
|
+
}
|
|
1673
|
+
return;
|
|
1674
|
+
case "template":
|
|
1675
|
+
if (ranges.argument?.text != null) {
|
|
1676
|
+
const text = DirectiveDiagnosticText.doesNotTakeArgument(ranges.directive.text);
|
|
1677
|
+
const origin = new DiagnosticOrigin(ranges.directive.start, ranges.directive.end, sourceFile);
|
|
1678
|
+
Directive.#onDiagnostics(Diagnostic.error(text, origin));
|
|
1679
|
+
}
|
|
1680
|
+
inlineConfig.template = true;
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
const target = ranges?.directive ?? ranges.namespace;
|
|
1684
|
+
const text = DirectiveDiagnosticText.isNotSupported(target.text);
|
|
1685
|
+
const origin = new DiagnosticOrigin(target.start, target.end, sourceFile);
|
|
1686
|
+
Directive.#onDiagnostics(Diagnostic.error(text, origin));
|
|
1687
|
+
}
|
|
1688
|
+
static async #parseJson(sourceFile, start, end) {
|
|
1689
|
+
const inlineOptions = {};
|
|
1690
|
+
const configParser = new ConfigParser(inlineOptions, OptionGroup.InlineConditions, sourceFile, new JsonScanner(sourceFile, { start, end }), Directive.#onDiagnostics);
|
|
1691
|
+
await configParser.parse();
|
|
1692
|
+
if ("target" in inlineOptions) {
|
|
1693
|
+
inlineOptions["target"] = await Target.expand(inlineOptions["target"]);
|
|
1694
|
+
}
|
|
1695
|
+
return inlineOptions;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1573
1699
|
class CancellationHandler {
|
|
1574
1700
|
#cancellationToken;
|
|
1575
1701
|
#cancellationReason;
|
|
@@ -1777,6 +1903,7 @@ class ResultHandler {
|
|
|
1777
1903
|
this.#taskResult.timing.start = Date.now();
|
|
1778
1904
|
break;
|
|
1779
1905
|
case "task:error":
|
|
1906
|
+
case "directive:error":
|
|
1780
1907
|
case "collect:error":
|
|
1781
1908
|
this.#targetResult.status = ResultStatus.Failed;
|
|
1782
1909
|
this.#taskResult.status = ResultStatus.Failed;
|
|
@@ -2380,6 +2507,7 @@ class ListReporter extends BaseReporter {
|
|
|
2380
2507
|
this.#hasReportedError = false;
|
|
2381
2508
|
break;
|
|
2382
2509
|
case "task:error":
|
|
2510
|
+
case "directive:error":
|
|
2383
2511
|
case "collect:error":
|
|
2384
2512
|
for (const diagnostic of payload.diagnostics) {
|
|
2385
2513
|
this.#fileView.addMessage(diagnosticText(diagnostic));
|
|
@@ -2895,6 +3023,9 @@ class TestTreeNode {
|
|
|
2895
3023
|
}
|
|
2896
3024
|
}
|
|
2897
3025
|
}
|
|
3026
|
+
getDirectiveRanges(compiler) {
|
|
3027
|
+
return Directive.getDirectiveRanges(compiler, this.node.getSourceFile(), this.node.getFullStart());
|
|
3028
|
+
}
|
|
2898
3029
|
}
|
|
2899
3030
|
|
|
2900
3031
|
class AssertionNode extends TestTreeNode {
|
|
@@ -2917,7 +3048,8 @@ class AssertionNode extends TestTreeNode {
|
|
|
2917
3048
|
this.target = this.matcherNode.typeArguments ?? this.matcherNode.arguments;
|
|
2918
3049
|
}
|
|
2919
3050
|
for (const diagnostic of parent.diagnostics) {
|
|
2920
|
-
if (diagnosticBelongsToNode(diagnostic, this.source)
|
|
3051
|
+
if (diagnosticBelongsToNode(diagnostic, this.source) ||
|
|
3052
|
+
(this.target != null && diagnosticBelongsToNode(diagnostic, this.target))) {
|
|
2921
3053
|
this.diagnostics.add(diagnostic);
|
|
2922
3054
|
parent.diagnostics.delete(diagnostic);
|
|
2923
3055
|
}
|
|
@@ -3185,6 +3317,9 @@ class TestTree {
|
|
|
3185
3317
|
this.diagnostics = diagnostics;
|
|
3186
3318
|
this.sourceFile = sourceFile;
|
|
3187
3319
|
}
|
|
3320
|
+
getDirectiveRanges(compiler) {
|
|
3321
|
+
return Directive.getDirectiveRanges(compiler, this.sourceFile);
|
|
3322
|
+
}
|
|
3188
3323
|
}
|
|
3189
3324
|
|
|
3190
3325
|
class WhenNode extends TestTreeNode {
|
|
@@ -4529,6 +4664,7 @@ class WhenService {
|
|
|
4529
4664
|
|
|
4530
4665
|
class TestTreeWalker {
|
|
4531
4666
|
#cancellationToken;
|
|
4667
|
+
#compiler;
|
|
4532
4668
|
#expectService;
|
|
4533
4669
|
#hasOnly;
|
|
4534
4670
|
#onTaskDiagnostics;
|
|
@@ -4536,6 +4672,7 @@ class TestTreeWalker {
|
|
|
4536
4672
|
#resolvedConfig;
|
|
4537
4673
|
#whenService;
|
|
4538
4674
|
constructor(compiler, typeChecker, resolvedConfig, onTaskDiagnostics, options) {
|
|
4675
|
+
this.#compiler = compiler;
|
|
4539
4676
|
this.#resolvedConfig = resolvedConfig;
|
|
4540
4677
|
this.#onTaskDiagnostics = onTaskDiagnostics;
|
|
4541
4678
|
this.#cancellationToken = options.cancellationToken;
|
|
@@ -4545,43 +4682,46 @@ class TestTreeWalker {
|
|
|
4545
4682
|
this.#expectService = new ExpectService(compiler, typeChecker, reject);
|
|
4546
4683
|
this.#whenService = new WhenService(reject, onTaskDiagnostics);
|
|
4547
4684
|
}
|
|
4548
|
-
#resolveRunMode(mode,
|
|
4549
|
-
|
|
4685
|
+
async #resolveRunMode(mode, node) {
|
|
4686
|
+
const directiveRanges = node.getDirectiveRanges(this.#compiler);
|
|
4687
|
+
const inlineConfig = await Directive.getInlineConfig(directiveRanges);
|
|
4688
|
+
if (inlineConfig?.if?.target != null && !Version.isIncluded(this.#compiler.version, inlineConfig.if.target)) {
|
|
4689
|
+
mode |= RunMode.Skip;
|
|
4690
|
+
}
|
|
4691
|
+
if (node.flags & TestTreeNodeFlags.Fail) {
|
|
4550
4692
|
mode |= RunMode.Fail;
|
|
4551
4693
|
}
|
|
4552
|
-
if (
|
|
4553
|
-
(this.#resolvedConfig.only != null &&
|
|
4554
|
-
testNode.name.toLowerCase().includes(this.#resolvedConfig.only.toLowerCase()))) {
|
|
4694
|
+
if (node.flags & TestTreeNodeFlags.Only ||
|
|
4695
|
+
(this.#resolvedConfig.only != null && node.name.toLowerCase().includes(this.#resolvedConfig.only.toLowerCase()))) {
|
|
4555
4696
|
mode |= RunMode.Only;
|
|
4556
4697
|
}
|
|
4557
|
-
if (
|
|
4558
|
-
(this.#resolvedConfig.skip != null &&
|
|
4559
|
-
testNode.name.toLowerCase().includes(this.#resolvedConfig.skip.toLowerCase()))) {
|
|
4698
|
+
if (node.flags & TestTreeNodeFlags.Skip ||
|
|
4699
|
+
(this.#resolvedConfig.skip != null && node.name.toLowerCase().includes(this.#resolvedConfig.skip.toLowerCase()))) {
|
|
4560
4700
|
mode |= RunMode.Skip;
|
|
4561
4701
|
}
|
|
4562
|
-
if (
|
|
4702
|
+
if (node.flags & TestTreeNodeFlags.Todo) {
|
|
4563
4703
|
mode |= RunMode.Todo;
|
|
4564
4704
|
}
|
|
4565
|
-
if (this.#position != null &&
|
|
4705
|
+
if (this.#position != null && node.node.getStart() === this.#position) {
|
|
4566
4706
|
mode |= RunMode.Only;
|
|
4567
4707
|
mode &= ~RunMode.Skip;
|
|
4568
4708
|
}
|
|
4569
4709
|
return mode;
|
|
4570
4710
|
}
|
|
4571
|
-
visit(nodes, runMode, parentResult) {
|
|
4711
|
+
async visit(nodes, runMode, parentResult) {
|
|
4572
4712
|
for (const node of nodes) {
|
|
4573
4713
|
if (this.#cancellationToken?.isCancellationRequested) {
|
|
4574
4714
|
break;
|
|
4575
4715
|
}
|
|
4576
4716
|
switch (node.brand) {
|
|
4577
4717
|
case TestTreeNodeBrand.Describe:
|
|
4578
|
-
this.#visitDescribe(node, runMode, parentResult);
|
|
4718
|
+
await this.#visitDescribe(node, runMode, parentResult);
|
|
4579
4719
|
break;
|
|
4580
4720
|
case TestTreeNodeBrand.Test:
|
|
4581
|
-
this.#visitTest(node, runMode, parentResult);
|
|
4721
|
+
await this.#visitTest(node, runMode, parentResult);
|
|
4582
4722
|
break;
|
|
4583
4723
|
case TestTreeNodeBrand.Expect:
|
|
4584
|
-
this.#visitAssertion(node, runMode, parentResult);
|
|
4724
|
+
await this.#visitAssertion(node, runMode, parentResult);
|
|
4585
4725
|
break;
|
|
4586
4726
|
case TestTreeNodeBrand.When:
|
|
4587
4727
|
this.#visitWhen(node);
|
|
@@ -4589,11 +4729,11 @@ class TestTreeWalker {
|
|
|
4589
4729
|
}
|
|
4590
4730
|
}
|
|
4591
4731
|
}
|
|
4592
|
-
#visitAssertion(assertion, runMode, parentResult) {
|
|
4593
|
-
this.visit(assertion.children, runMode, parentResult);
|
|
4732
|
+
async #visitAssertion(assertion, runMode, parentResult) {
|
|
4733
|
+
await this.visit(assertion.children, runMode, parentResult);
|
|
4594
4734
|
const expectResult = new ExpectResult(assertion, parentResult);
|
|
4595
4735
|
EventEmitter.dispatch(["expect:start", { result: expectResult }]);
|
|
4596
|
-
runMode = this.#resolveRunMode(runMode, assertion);
|
|
4736
|
+
runMode = await this.#resolveRunMode(runMode, assertion);
|
|
4597
4737
|
if (runMode & RunMode.Skip || (this.#hasOnly && !(runMode & RunMode.Only))) {
|
|
4598
4738
|
EventEmitter.dispatch(["expect:skip", { result: expectResult }]);
|
|
4599
4739
|
return;
|
|
@@ -4629,23 +4769,23 @@ class TestTreeWalker {
|
|
|
4629
4769
|
EventEmitter.dispatch(["expect:fail", { diagnostics: matchResult.explain(), result: expectResult }]);
|
|
4630
4770
|
}
|
|
4631
4771
|
}
|
|
4632
|
-
#visitDescribe(describe, runMode, parentResult) {
|
|
4772
|
+
async #visitDescribe(describe, runMode, parentResult) {
|
|
4633
4773
|
const describeResult = new DescribeResult(describe, parentResult);
|
|
4634
4774
|
EventEmitter.dispatch(["describe:start", { result: describeResult }]);
|
|
4635
|
-
runMode = this.#resolveRunMode(runMode, describe);
|
|
4775
|
+
runMode = await this.#resolveRunMode(runMode, describe);
|
|
4636
4776
|
if (!(runMode & RunMode.Skip || (this.#hasOnly && !(runMode & RunMode.Only)) || runMode & RunMode.Todo) &&
|
|
4637
4777
|
describe.diagnostics.size > 0) {
|
|
4638
4778
|
this.#onTaskDiagnostics(Diagnostic.fromDiagnostics([...describe.diagnostics]));
|
|
4639
4779
|
}
|
|
4640
4780
|
else {
|
|
4641
|
-
this.visit(describe.children, runMode, describeResult);
|
|
4781
|
+
await this.visit(describe.children, runMode, describeResult);
|
|
4642
4782
|
}
|
|
4643
4783
|
EventEmitter.dispatch(["describe:end", { result: describeResult }]);
|
|
4644
4784
|
}
|
|
4645
|
-
#visitTest(test, runMode, parentResult) {
|
|
4785
|
+
async #visitTest(test, runMode, parentResult) {
|
|
4646
4786
|
const testResult = new TestResult(test, parentResult);
|
|
4647
4787
|
EventEmitter.dispatch(["test:start", { result: testResult }]);
|
|
4648
|
-
runMode = this.#resolveRunMode(runMode, test);
|
|
4788
|
+
runMode = await this.#resolveRunMode(runMode, test);
|
|
4649
4789
|
if (runMode & RunMode.Todo) {
|
|
4650
4790
|
EventEmitter.dispatch(["test:todo", { result: testResult }]);
|
|
4651
4791
|
return;
|
|
@@ -4660,7 +4800,7 @@ class TestTreeWalker {
|
|
|
4660
4800
|
]);
|
|
4661
4801
|
return;
|
|
4662
4802
|
}
|
|
4663
|
-
this.visit(test.children, runMode, testResult);
|
|
4803
|
+
await this.visit(test.children, runMode, testResult);
|
|
4664
4804
|
if (runMode & RunMode.Skip || (this.#hasOnly && !(runMode & RunMode.Only))) {
|
|
4665
4805
|
EventEmitter.dispatch(["test:skip", { result: testResult }]);
|
|
4666
4806
|
return;
|
|
@@ -4702,21 +4842,27 @@ class TaskRunner {
|
|
|
4702
4842
|
EventEmitter.dispatch(["task:end", { result: taskResult }]);
|
|
4703
4843
|
this.#projectService.closeFile(task.filePath);
|
|
4704
4844
|
}
|
|
4705
|
-
async #
|
|
4706
|
-
|
|
4707
|
-
this.#onDiagnostics([Diagnostic.error(`Test file '${task.filePath}' does not exist.`)], taskResult);
|
|
4708
|
-
return;
|
|
4709
|
-
}
|
|
4710
|
-
let languageService = this.#projectService.getLanguageService(task.filePath);
|
|
4845
|
+
async #resolveTaskFacts(task, taskResult, runMode = RunMode.Normal) {
|
|
4846
|
+
const languageService = this.#projectService.getLanguageService(task.filePath);
|
|
4711
4847
|
const syntacticDiagnostics = languageService?.getSyntacticDiagnostics(task.filePath);
|
|
4712
4848
|
if (syntacticDiagnostics != null && syntacticDiagnostics.length > 0) {
|
|
4713
4849
|
this.#onDiagnostics(Diagnostic.fromDiagnostics(syntacticDiagnostics), taskResult);
|
|
4714
4850
|
return;
|
|
4715
4851
|
}
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4852
|
+
const semanticDiagnostics = languageService?.getSemanticDiagnostics(task.filePath);
|
|
4853
|
+
const program = languageService?.getProgram();
|
|
4854
|
+
const typeChecker = program?.getTypeChecker();
|
|
4855
|
+
const sourceFile = program?.getSourceFile(task.filePath);
|
|
4856
|
+
if (!sourceFile) {
|
|
4857
|
+
return;
|
|
4858
|
+
}
|
|
4859
|
+
const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics);
|
|
4860
|
+
const directiveRanges = testTree.getDirectiveRanges(this.#compiler);
|
|
4861
|
+
const inlineConfig = await Directive.getInlineConfig(directiveRanges);
|
|
4862
|
+
if (inlineConfig?.if?.target != null && !Version.isIncluded(this.#compiler.version, inlineConfig.if.target)) {
|
|
4863
|
+
runMode |= RunMode.Skip;
|
|
4864
|
+
}
|
|
4865
|
+
if (inlineConfig?.template) {
|
|
4720
4866
|
if (semanticDiagnostics != null && semanticDiagnostics.length > 0) {
|
|
4721
4867
|
this.#onDiagnostics(Diagnostic.fromDiagnostics(semanticDiagnostics), taskResult);
|
|
4722
4868
|
return;
|
|
@@ -4728,41 +4874,39 @@ class TaskRunner {
|
|
|
4728
4874
|
return;
|
|
4729
4875
|
}
|
|
4730
4876
|
this.#projectService.openFile(task.filePath, testText, this.#resolvedConfig.rootPath);
|
|
4731
|
-
|
|
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);
|
|
4877
|
+
return this.#resolveTaskFacts(task, taskResult, runMode);
|
|
4740
4878
|
}
|
|
4741
|
-
|
|
4879
|
+
return { runMode, testTree, typeChecker };
|
|
4880
|
+
}
|
|
4881
|
+
async #run(task, taskResult, cancellationToken) {
|
|
4882
|
+
if (!existsSync(task.filePath)) {
|
|
4883
|
+
this.#onDiagnostics([Diagnostic.error(`Test file '${task.filePath}' does not exist.`)], taskResult);
|
|
4742
4884
|
return;
|
|
4743
4885
|
}
|
|
4744
|
-
const
|
|
4745
|
-
if (
|
|
4746
|
-
|
|
4886
|
+
const facts = await this.#resolveTaskFacts(task, taskResult);
|
|
4887
|
+
if (!facts) {
|
|
4888
|
+
return;
|
|
4889
|
+
}
|
|
4890
|
+
if (facts.testTree.diagnostics.size > 0) {
|
|
4891
|
+
this.#onDiagnostics(Diagnostic.fromDiagnostics([...facts.testTree.diagnostics]), taskResult);
|
|
4747
4892
|
return;
|
|
4748
4893
|
}
|
|
4749
|
-
const typeChecker = program?.getTypeChecker();
|
|
4750
4894
|
const onTaskDiagnostics = (diagnostics) => {
|
|
4751
4895
|
this.#onDiagnostics(diagnostics, taskResult);
|
|
4752
4896
|
};
|
|
4753
|
-
const testTreeWalker = new TestTreeWalker(this.#compiler, typeChecker, this.#resolvedConfig, onTaskDiagnostics, {
|
|
4897
|
+
const testTreeWalker = new TestTreeWalker(this.#compiler, facts.typeChecker, this.#resolvedConfig, onTaskDiagnostics, {
|
|
4754
4898
|
cancellationToken,
|
|
4755
|
-
hasOnly: testTree.hasOnly,
|
|
4899
|
+
hasOnly: facts.testTree.hasOnly,
|
|
4756
4900
|
position: task.position,
|
|
4757
4901
|
});
|
|
4758
|
-
testTreeWalker.visit(testTree.children,
|
|
4902
|
+
await testTreeWalker.visit(facts.testTree.children, facts.runMode, undefined);
|
|
4759
4903
|
}
|
|
4760
4904
|
}
|
|
4761
4905
|
|
|
4762
4906
|
class Runner {
|
|
4763
4907
|
#eventEmitter = new EventEmitter();
|
|
4764
4908
|
#resolvedConfig;
|
|
4765
|
-
static version = "4.0.0
|
|
4909
|
+
static version = "4.0.0";
|
|
4766
4910
|
constructor(resolvedConfig) {
|
|
4767
4911
|
this.#resolvedConfig = resolvedConfig;
|
|
4768
4912
|
}
|
|
@@ -4962,4 +5106,4 @@ class Cli {
|
|
|
4962
5106
|
}
|
|
4963
5107
|
}
|
|
4964
5108
|
|
|
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 };
|
|
5109
|
+
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 };
|