tstyche 2.0.0-rc.1 → 2.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 +1 -1
- package/build/tstyche.d.ts +122 -133
- package/build/tstyche.js +1665 -1800
- package/package.json +7 -7
package/build/tstyche.js
CHANGED
|
@@ -111,938 +111,118 @@ class ExitCodeHandler {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
class
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
static #timeout = Environment.#resolveTimeout();
|
|
120
|
-
static #typescriptPath = Environment.#resolveTypeScriptPath();
|
|
121
|
-
static get isCi() {
|
|
122
|
-
return Environment.#isCi;
|
|
123
|
-
}
|
|
124
|
-
static get noColor() {
|
|
125
|
-
return Environment.#noColor;
|
|
126
|
-
}
|
|
127
|
-
static get noInteractive() {
|
|
128
|
-
return Environment.#noInteractive;
|
|
129
|
-
}
|
|
130
|
-
static get storePath() {
|
|
131
|
-
return Environment.#storePath;
|
|
132
|
-
}
|
|
133
|
-
static get timeout() {
|
|
134
|
-
return Environment.#timeout;
|
|
135
|
-
}
|
|
136
|
-
static get typescriptPath() {
|
|
137
|
-
return Environment.#typescriptPath;
|
|
138
|
-
}
|
|
139
|
-
static #resolveIsCi() {
|
|
140
|
-
if (process.env["CI"] != null) {
|
|
141
|
-
return process.env["CI"] !== "";
|
|
142
|
-
}
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
|
-
static #resolveNoColor() {
|
|
146
|
-
if (process.env["TSTYCHE_NO_COLOR"] != null) {
|
|
147
|
-
return process.env["TSTYCHE_NO_COLOR"] !== "";
|
|
148
|
-
}
|
|
149
|
-
if (process.env["NO_COLOR"] != null) {
|
|
150
|
-
return process.env["NO_COLOR"] !== "";
|
|
151
|
-
}
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
static #resolveNoInteractive() {
|
|
155
|
-
if (process.env["TSTYCHE_NO_INTERACTIVE"] != null) {
|
|
156
|
-
return process.env["TSTYCHE_NO_INTERACTIVE"] !== "";
|
|
157
|
-
}
|
|
158
|
-
return !process.stdout.isTTY;
|
|
159
|
-
}
|
|
160
|
-
static #resolveStorePath() {
|
|
161
|
-
if (process.env["TSTYCHE_STORE_PATH"] != null) {
|
|
162
|
-
return Path.resolve(process.env["TSTYCHE_STORE_PATH"]);
|
|
163
|
-
}
|
|
164
|
-
if (process.platform === "darwin") {
|
|
165
|
-
return Path.resolve(os.homedir(), "Library", "TSTyche");
|
|
166
|
-
}
|
|
167
|
-
if (process.env["LocalAppData"] != null) {
|
|
168
|
-
return Path.resolve(process.env["LocalAppData"], "TSTyche");
|
|
169
|
-
}
|
|
170
|
-
if (process.env["XDG_DATA_HOME"] != null) {
|
|
171
|
-
return Path.resolve(process.env["XDG_DATA_HOME"], "TSTyche");
|
|
172
|
-
}
|
|
173
|
-
return Path.resolve(os.homedir(), ".local", "share", "TSTyche");
|
|
174
|
-
}
|
|
175
|
-
static #resolveTimeout() {
|
|
176
|
-
if (process.env["TSTYCHE_TIMEOUT"] != null) {
|
|
177
|
-
return Number(process.env["TSTYCHE_TIMEOUT"]);
|
|
178
|
-
}
|
|
179
|
-
return 30;
|
|
180
|
-
}
|
|
181
|
-
static #resolveTypeScriptPath() {
|
|
182
|
-
let moduleId = "typescript";
|
|
183
|
-
if (process.env["TSTYCHE_TYPESCRIPT_PATH"] != null) {
|
|
184
|
-
moduleId = process.env["TSTYCHE_TYPESCRIPT_PATH"];
|
|
185
|
-
}
|
|
186
|
-
let resolvedPath;
|
|
187
|
-
try {
|
|
188
|
-
resolvedPath = Path.normalizeSlashes(createRequire(import.meta.url).resolve(moduleId));
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
}
|
|
192
|
-
return resolvedPath;
|
|
114
|
+
class ResultTiming {
|
|
115
|
+
end = Number.NaN;
|
|
116
|
+
start = Number.NaN;
|
|
117
|
+
get duration() {
|
|
118
|
+
return this.end - this.start;
|
|
193
119
|
}
|
|
194
120
|
}
|
|
195
121
|
|
|
196
|
-
|
|
197
|
-
|
|
122
|
+
class DescribeResult {
|
|
123
|
+
describe;
|
|
124
|
+
parent;
|
|
125
|
+
results = [];
|
|
126
|
+
timing = new ResultTiming();
|
|
127
|
+
constructor(describe, parent) {
|
|
128
|
+
this.describe = describe;
|
|
129
|
+
this.parent = parent;
|
|
130
|
+
}
|
|
198
131
|
}
|
|
199
132
|
|
|
200
|
-
var
|
|
201
|
-
(function (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
Color["Cyan"] = "36";
|
|
209
|
-
Color["Gray"] = "90";
|
|
210
|
-
})(Color || (Color = {}));
|
|
133
|
+
var ResultStatus;
|
|
134
|
+
(function (ResultStatus) {
|
|
135
|
+
ResultStatus["Runs"] = "runs";
|
|
136
|
+
ResultStatus["Passed"] = "passed";
|
|
137
|
+
ResultStatus["Failed"] = "failed";
|
|
138
|
+
ResultStatus["Skipped"] = "skipped";
|
|
139
|
+
ResultStatus["Todo"] = "todo";
|
|
140
|
+
})(ResultStatus || (ResultStatus = {}));
|
|
211
141
|
|
|
212
|
-
class
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
return (jsx("text", { indent: this.props.indent ?? 0, children: [ansiEscapes.length > 0 ? jsx("ansi", { escapes: ansiEscapes }) : undefined, this.props.children, ansiEscapes.length > 0 ? jsx("ansi", { escapes: "0" }) : undefined] }));
|
|
142
|
+
class ExpectResult {
|
|
143
|
+
assertion;
|
|
144
|
+
diagnostics = [];
|
|
145
|
+
parent;
|
|
146
|
+
status = "runs";
|
|
147
|
+
timing = new ResultTiming();
|
|
148
|
+
constructor(assertion, parent) {
|
|
149
|
+
this.assertion = assertion;
|
|
150
|
+
this.parent = parent;
|
|
223
151
|
}
|
|
224
152
|
}
|
|
225
153
|
|
|
226
|
-
class
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
return
|
|
154
|
+
class ResultCount {
|
|
155
|
+
failed = 0;
|
|
156
|
+
passed = 0;
|
|
157
|
+
skipped = 0;
|
|
158
|
+
todo = 0;
|
|
159
|
+
get total() {
|
|
160
|
+
return this.failed + this.passed + this.skipped + this.todo;
|
|
233
161
|
}
|
|
234
162
|
}
|
|
235
163
|
|
|
236
|
-
class
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
return ["\u001B[", Array.isArray(attributes) ? attributes.join(";") : attributes, "m"].join("");
|
|
247
|
-
}
|
|
248
|
-
#indentEachLine(lines, level) {
|
|
249
|
-
if (level === 0) {
|
|
250
|
-
return lines;
|
|
251
|
-
}
|
|
252
|
-
return lines.replace(this.#notEmptyLineRegex, this.#indentStep.repeat(level));
|
|
253
|
-
}
|
|
254
|
-
render(element) {
|
|
255
|
-
if (typeof element.type === "function") {
|
|
256
|
-
const instance = new element.type({ ...element.props });
|
|
257
|
-
return this.render(instance.render());
|
|
258
|
-
}
|
|
259
|
-
if (element.type === "ansi" && !this.#noColor) {
|
|
260
|
-
return this.#escapeSequence(element.props.escapes);
|
|
261
|
-
}
|
|
262
|
-
if (element.type === "newLine") {
|
|
263
|
-
return this.#newLine;
|
|
264
|
-
}
|
|
265
|
-
if (element.type === "text") {
|
|
266
|
-
const text = this.#visitChildren(element.props.children);
|
|
267
|
-
return this.#indentEachLine(text, element.props.indent);
|
|
268
|
-
}
|
|
269
|
-
return "";
|
|
270
|
-
}
|
|
271
|
-
#visitChildren(children) {
|
|
272
|
-
const text = [];
|
|
273
|
-
for (const child of children) {
|
|
274
|
-
if (typeof child === "string") {
|
|
275
|
-
text.push(child);
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
if (Array.isArray(child)) {
|
|
279
|
-
text.push(this.#visitChildren(child));
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
if (child != null && typeof child === "object") {
|
|
283
|
-
text.push(this.render(child));
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
return text.join("");
|
|
164
|
+
class FileResult {
|
|
165
|
+
diagnostics = [];
|
|
166
|
+
expectCount = new ResultCount();
|
|
167
|
+
results = [];
|
|
168
|
+
status = "runs";
|
|
169
|
+
testCount = new ResultCount();
|
|
170
|
+
testFile;
|
|
171
|
+
timing = new ResultTiming();
|
|
172
|
+
constructor(testFile) {
|
|
173
|
+
this.testFile = testFile;
|
|
287
174
|
}
|
|
288
175
|
}
|
|
289
176
|
|
|
290
|
-
|
|
291
|
-
|
|
177
|
+
class ProjectResult {
|
|
178
|
+
compilerVersion;
|
|
179
|
+
diagnostics = [];
|
|
180
|
+
projectConfigFilePath;
|
|
181
|
+
results = [];
|
|
182
|
+
constructor(compilerVersion, projectConfigFilePath) {
|
|
183
|
+
this.compilerVersion = compilerVersion;
|
|
184
|
+
this.projectConfigFilePath = projectConfigFilePath;
|
|
185
|
+
}
|
|
292
186
|
}
|
|
293
187
|
|
|
294
|
-
|
|
295
|
-
|
|
188
|
+
class Result {
|
|
189
|
+
expectCount = new ResultCount();
|
|
190
|
+
fileCount = new ResultCount();
|
|
191
|
+
resolvedConfig;
|
|
192
|
+
results = [];
|
|
193
|
+
targetCount = new ResultCount();
|
|
194
|
+
testCount = new ResultCount();
|
|
195
|
+
testFiles;
|
|
196
|
+
timing = new ResultTiming();
|
|
197
|
+
constructor(resolvedConfig, testFiles) {
|
|
198
|
+
this.resolvedConfig = resolvedConfig;
|
|
199
|
+
this.testFiles = testFiles;
|
|
200
|
+
}
|
|
296
201
|
}
|
|
297
202
|
|
|
298
|
-
class
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const lastLine = Math.min(firstLine + 5, lastLineInFile);
|
|
308
|
-
const lineNumberMaxWidth = String(lastLine + 1).length;
|
|
309
|
-
const codeSpan = [];
|
|
310
|
-
for (let index = firstLine; index <= lastLine; index++) {
|
|
311
|
-
const lineStart = this.props.sourceFile.getPositionOfLineAndCharacter(index, 0);
|
|
312
|
-
const lineEnd = index === lastLineInFile
|
|
313
|
-
? this.props.sourceFile.text.length
|
|
314
|
-
: this.props.sourceFile.getPositionOfLineAndCharacter(index + 1, 0);
|
|
315
|
-
const lineNumberText = String(index + 1);
|
|
316
|
-
const lineText = this.props.sourceFile.text.slice(lineStart, lineEnd).trimEnd().replace(/\t/g, " ");
|
|
317
|
-
if (index === markedLine) {
|
|
318
|
-
codeSpan.push(jsx(Line, { children: [jsx(Text, { color: "31", children: ">" }), jsx(Text, { children: " " }), lineNumberText.padStart(lineNumberMaxWidth), jsx(Text, { children: " " }), jsx(Text, { color: "90", children: "|" }), " ", lineText] }), jsx(Line, { children: [" ".repeat(lineNumberMaxWidth + 3), jsx(Text, { color: "90", children: "|" }), " ".repeat(markedCharacter + 1), jsx(Text, { color: "31", children: "^" })] }));
|
|
319
|
-
}
|
|
320
|
-
else {
|
|
321
|
-
codeSpan.push(jsx(Line, { children: [" ".repeat(2), jsx(Text, { color: "90", children: [lineNumberText.padStart(lineNumberMaxWidth), " | ", lineText || ""] })] }));
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
const breadcrumbs = this.props.breadcrumbs?.flatMap((ancestor) => [
|
|
325
|
-
jsx(Text, { color: "90", children: " ❭ " }),
|
|
326
|
-
jsx(Text, { children: ancestor }),
|
|
327
|
-
]);
|
|
328
|
-
const location = (jsx(Line, { children: [" ".repeat(lineNumberMaxWidth + 5), jsx(Text, { color: "90", children: "at" }), jsx(Text, { children: " " }), jsx(Text, { color: "36", children: Path.relative("", this.props.sourceFile.fileName) }), jsx(Text, { color: "90", children: [":", String(markedLine + 1), ":", String(markedCharacter + 1)] }), breadcrumbs] }));
|
|
329
|
-
return (jsx(Text, { children: [codeSpan, jsx(Line, {}), location] }));
|
|
203
|
+
class TargetResult {
|
|
204
|
+
results = new Map();
|
|
205
|
+
status = "runs";
|
|
206
|
+
testFiles;
|
|
207
|
+
timing = new ResultTiming();
|
|
208
|
+
versionTag;
|
|
209
|
+
constructor(versionTag, testFiles) {
|
|
210
|
+
this.versionTag = versionTag;
|
|
211
|
+
this.testFiles = testFiles;
|
|
330
212
|
}
|
|
331
213
|
}
|
|
332
214
|
|
|
333
|
-
class
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
return (jsx(Text, { children: [message, codeSpan, jsx(Line, {}), jsx(Text, { indent: 2, children: related })] }));
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
function diagnosticText(diagnostic) {
|
|
348
|
-
let prefix;
|
|
349
|
-
switch (diagnostic.category) {
|
|
350
|
-
case "error": {
|
|
351
|
-
prefix = jsx(Text, { color: "31", children: "Error: " });
|
|
352
|
-
break;
|
|
353
|
-
}
|
|
354
|
-
case "warning": {
|
|
355
|
-
prefix = jsx(Text, { color: "33", children: "Warning: " });
|
|
356
|
-
break;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
return (jsx(Text, { children: [prefix, jsx(DiagnosticText, { diagnostic: diagnostic })] }));
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
class FileNameText {
|
|
363
|
-
props;
|
|
364
|
-
constructor(props) {
|
|
365
|
-
this.props = props;
|
|
366
|
-
}
|
|
367
|
-
render() {
|
|
368
|
-
const relativePath = Path.relative("", this.props.filePath);
|
|
369
|
-
const lastPathSeparator = relativePath.lastIndexOf("/");
|
|
370
|
-
const directoryNameText = relativePath.slice(0, lastPathSeparator + 1);
|
|
371
|
-
const fileNameText = relativePath.slice(lastPathSeparator + 1);
|
|
372
|
-
return (jsx(Text, { children: [jsx(Text, { color: "90", children: directoryNameText }), fileNameText] }));
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
function fileStatusText(status, testFile) {
|
|
376
|
-
let statusColor;
|
|
377
|
-
let statusText;
|
|
378
|
-
switch (status) {
|
|
379
|
-
case "runs": {
|
|
380
|
-
statusColor = "33";
|
|
381
|
-
statusText = "runs";
|
|
382
|
-
break;
|
|
383
|
-
}
|
|
384
|
-
case "passed": {
|
|
385
|
-
statusColor = "32";
|
|
386
|
-
statusText = "pass";
|
|
387
|
-
break;
|
|
388
|
-
}
|
|
389
|
-
case "failed": {
|
|
390
|
-
statusColor = "31";
|
|
391
|
-
statusText = "fail";
|
|
392
|
-
break;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
return (jsx(Line, { children: [jsx(Text, { color: statusColor, children: statusText }), " ", jsx(FileNameText, { filePath: testFile.path })] }));
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function fileViewText(lines, addEmptyFinalLine) {
|
|
399
|
-
return (jsx(Text, { children: [[...lines], addEmptyFinalLine ? jsx(Line, {}) : undefined] }));
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
class JsonText {
|
|
403
|
-
props;
|
|
404
|
-
constructor(props) {
|
|
405
|
-
this.props = props;
|
|
406
|
-
}
|
|
407
|
-
render() {
|
|
408
|
-
return jsx(Line, { children: JSON.stringify(this.#sortObject(this.props.input), null, 2) });
|
|
409
|
-
}
|
|
410
|
-
#sortObject(target) {
|
|
411
|
-
if (Array.isArray(target)) {
|
|
412
|
-
return target;
|
|
413
|
-
}
|
|
414
|
-
return Object.keys(target)
|
|
415
|
-
.sort()
|
|
416
|
-
.reduce((result, key) => {
|
|
417
|
-
result[key] = target[key];
|
|
418
|
-
return result;
|
|
419
|
-
}, {});
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
function formattedText(input) {
|
|
423
|
-
if (typeof input === "string") {
|
|
424
|
-
return jsx(Line, { children: input });
|
|
425
|
-
}
|
|
426
|
-
return jsx(JsonText, { input: input });
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
const usageExamples = [
|
|
430
|
-
["tstyche", "Run all tests."],
|
|
431
|
-
["tstyche path/to/first.test.ts", "Only run the test files with matching path."],
|
|
432
|
-
["tstyche --target 4.9,5.3.2,current", "Test on all specified versions of TypeScript."],
|
|
433
|
-
];
|
|
434
|
-
class HintText {
|
|
435
|
-
props;
|
|
436
|
-
constructor(props) {
|
|
437
|
-
this.props = props;
|
|
438
|
-
}
|
|
439
|
-
render() {
|
|
440
|
-
return (jsx(Text, { indent: 1, color: "90", children: this.props.children }));
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
class HelpHeaderText {
|
|
444
|
-
props;
|
|
445
|
-
constructor(props) {
|
|
446
|
-
this.props = props;
|
|
447
|
-
}
|
|
448
|
-
render() {
|
|
449
|
-
const hint = (jsx(HintText, { children: jsx(Text, { children: this.props.tstycheVersion }) }));
|
|
450
|
-
return (jsx(Line, { children: [jsx(Text, { children: "The TSTyche Type Test Runner" }), hint] }));
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
class CommandText {
|
|
454
|
-
props;
|
|
455
|
-
constructor(props) {
|
|
456
|
-
this.props = props;
|
|
457
|
-
}
|
|
458
|
-
render() {
|
|
459
|
-
let hint;
|
|
460
|
-
if (this.props.hint != null) {
|
|
461
|
-
hint = jsx(HintText, { children: this.props.hint });
|
|
462
|
-
}
|
|
463
|
-
return (jsx(Line, { indent: 1, children: [jsx(Text, { color: "34", children: this.props.text }), hint] }));
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
class OptionDescriptionText {
|
|
467
|
-
props;
|
|
468
|
-
constructor(props) {
|
|
469
|
-
this.props = props;
|
|
470
|
-
}
|
|
471
|
-
render() {
|
|
472
|
-
return jsx(Line, { indent: 1, children: this.props.text });
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
class CommandLineUsageText {
|
|
476
|
-
render() {
|
|
477
|
-
const usageText = usageExamples.map(([commandText, descriptionText]) => (jsx(Text, { children: [jsx(CommandText, { text: commandText }), jsx(OptionDescriptionText, { text: descriptionText }), jsx(Line, {})] })));
|
|
478
|
-
return jsx(Text, { children: usageText });
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
class CommandLineOptionNameText {
|
|
482
|
-
props;
|
|
483
|
-
constructor(props) {
|
|
484
|
-
this.props = props;
|
|
485
|
-
}
|
|
486
|
-
render() {
|
|
487
|
-
return jsx(Text, { children: ["--", this.props.text] });
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
class CommandLineOptionHintText {
|
|
491
|
-
props;
|
|
492
|
-
constructor(props) {
|
|
493
|
-
this.props = props;
|
|
494
|
-
}
|
|
495
|
-
render() {
|
|
496
|
-
if (this.props.definition.brand === "list") {
|
|
497
|
-
return (jsx(Text, { children: [this.props.definition.brand, " of ", this.props.definition.items.brand, "s"] }));
|
|
498
|
-
}
|
|
499
|
-
return jsx(Text, { children: this.props.definition.brand });
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
class CommandLineOptionsText {
|
|
503
|
-
props;
|
|
504
|
-
constructor(props) {
|
|
505
|
-
this.props = props;
|
|
506
|
-
}
|
|
507
|
-
render() {
|
|
508
|
-
const definitions = [...this.props.optionDefinitions.values()];
|
|
509
|
-
const optionsText = definitions.map((definition) => {
|
|
510
|
-
let hint;
|
|
511
|
-
if (definition.brand !== "bareTrue") {
|
|
512
|
-
hint = jsx(CommandLineOptionHintText, { definition: definition });
|
|
513
|
-
}
|
|
514
|
-
return (jsx(Text, { children: [jsx(CommandText, { text: jsx(CommandLineOptionNameText, { text: definition.name }), hint: hint }), jsx(OptionDescriptionText, { text: definition.description }), jsx(Line, {})] }));
|
|
515
|
-
});
|
|
516
|
-
return (jsx(Text, { children: [jsx(Line, { children: "Command Line Options" }), jsx(Line, {}), optionsText] }));
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
class HelpFooterText {
|
|
520
|
-
render() {
|
|
521
|
-
return jsx(Line, { children: "To learn more, visit https://tstyche.org" });
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
function helpText(optionDefinitions, tstycheVersion) {
|
|
525
|
-
return (jsx(Text, { children: [jsx(HelpHeaderText, { tstycheVersion: tstycheVersion }), jsx(Line, {}), jsx(CommandLineUsageText, {}), jsx(Line, {}), jsx(CommandLineOptionsText, { optionDefinitions: optionDefinitions }), jsx(Line, {}), jsx(HelpFooterText, {}), jsx(Line, {})] }));
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
class OutputService {
|
|
529
|
-
#isClear = false;
|
|
530
|
-
#noColor;
|
|
531
|
-
#scribbler;
|
|
532
|
-
#stderr;
|
|
533
|
-
#stdout;
|
|
534
|
-
constructor(options) {
|
|
535
|
-
this.#noColor = options?.noColor ?? Environment.noColor;
|
|
536
|
-
this.#stderr = options?.stderr ?? process.stderr;
|
|
537
|
-
this.#stdout = options?.stdout ?? process.stdout;
|
|
538
|
-
this.#scribbler = new Scribbler({ noColor: this.#noColor });
|
|
539
|
-
}
|
|
540
|
-
clearTerminal() {
|
|
541
|
-
if (!this.#isClear) {
|
|
542
|
-
this.#stdout.write("\u001B[2J\u001B[3J\u001B[H");
|
|
543
|
-
this.#isClear = true;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
eraseLastLine() {
|
|
547
|
-
this.#stdout.write("\u001B[1A\u001B[0K");
|
|
548
|
-
}
|
|
549
|
-
#write(stream, body) {
|
|
550
|
-
const elements = Array.isArray(body) ? body : [body];
|
|
551
|
-
for (const element of elements) {
|
|
552
|
-
stream.write(this.#scribbler.render(element));
|
|
553
|
-
}
|
|
554
|
-
this.#isClear = false;
|
|
555
|
-
}
|
|
556
|
-
writeError(body) {
|
|
557
|
-
this.#write(this.#stderr, body);
|
|
558
|
-
}
|
|
559
|
-
writeMessage(body) {
|
|
560
|
-
this.#write(this.#stdout, body);
|
|
561
|
-
}
|
|
562
|
-
writeWarning(body) {
|
|
563
|
-
this.#write(this.#stderr, body);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
class RowText {
|
|
568
|
-
props;
|
|
569
|
-
constructor(props) {
|
|
570
|
-
this.props = props;
|
|
571
|
-
}
|
|
572
|
-
render() {
|
|
573
|
-
return (jsx(Line, { children: [`${this.props.label}:`.padEnd(12), this.props.text] }));
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
class CountText {
|
|
577
|
-
props;
|
|
578
|
-
constructor(props) {
|
|
579
|
-
this.props = props;
|
|
580
|
-
}
|
|
581
|
-
render() {
|
|
582
|
-
return (jsx(Text, { children: [this.props.failed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "31", children: [String(this.props.failed), " failed"] }), jsx(Text, { children: ", " })] })) : undefined, this.props.skipped > 0 ? (jsx(Text, { children: [jsx(Text, { color: "33", children: [String(this.props.skipped), " skipped"] }), jsx(Text, { children: ", " })] })) : undefined, this.props.todo > 0 ? (jsx(Text, { children: [jsx(Text, { color: "35", children: [String(this.props.todo), " todo"] }), jsx(Text, { children: ", " })] })) : undefined, this.props.passed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "32", children: [String(this.props.passed), " passed"] }), jsx(Text, { children: ", " })] })) : undefined, jsx(Text, { children: [String(this.props.total), jsx(Text, { children: " total" })] })] }));
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
class DurationText {
|
|
586
|
-
props;
|
|
587
|
-
constructor(props) {
|
|
588
|
-
this.props = props;
|
|
589
|
-
}
|
|
590
|
-
render() {
|
|
591
|
-
const duration = this.props.duration / 1000;
|
|
592
|
-
const minutes = Math.floor(duration / 60);
|
|
593
|
-
const seconds = duration % 60;
|
|
594
|
-
return (jsx(Text, { children: [minutes > 0 ? `${String(minutes)}m ` : undefined, `${String(Math.round(seconds * 10) / 10)}s`] }));
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
class MatchText {
|
|
598
|
-
props;
|
|
599
|
-
constructor(props) {
|
|
600
|
-
this.props = props;
|
|
601
|
-
}
|
|
602
|
-
render() {
|
|
603
|
-
if (typeof this.props.text === "string") {
|
|
604
|
-
return jsx(Text, { children: ["'", this.props.text, "'"] });
|
|
605
|
-
}
|
|
606
|
-
if (this.props.text.length <= 1) {
|
|
607
|
-
return jsx(Text, { children: ["'", ...this.props.text, "'"] });
|
|
608
|
-
}
|
|
609
|
-
const lastItem = this.props.text.pop();
|
|
610
|
-
return (jsx(Text, { children: [this.props.text.map((match, index, list) => (jsx(Text, { children: ["'", match, "'", index === list.length - 1 ? jsx(Text, { children: " " }) : jsx(Text, { color: "90", children: ", " })] }))), jsx(Text, { color: "90", children: "or" }), " '", lastItem, "'"] }));
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
class RanFilesText {
|
|
614
|
-
props;
|
|
615
|
-
constructor(props) {
|
|
616
|
-
this.props = props;
|
|
617
|
-
}
|
|
618
|
-
render() {
|
|
619
|
-
const testNameMatchText = [];
|
|
620
|
-
if (this.props.onlyMatch != null) {
|
|
621
|
-
testNameMatchText.push(jsx(Text, { children: [jsx(Text, { color: "90", children: "matching " }), jsx(MatchText, { text: this.props.onlyMatch })] }));
|
|
622
|
-
}
|
|
623
|
-
if (this.props.skipMatch != null) {
|
|
624
|
-
testNameMatchText.push(jsx(Text, { children: [this.props.onlyMatch == null ? undefined : jsx(Text, { color: "90", children: " and " }), jsx(Text, { color: "90", children: "not matching " }), jsx(MatchText, { text: this.props.skipMatch })] }));
|
|
625
|
-
}
|
|
626
|
-
let pathMatchText;
|
|
627
|
-
if (this.props.pathMatch.length > 0) {
|
|
628
|
-
pathMatchText = (jsx(Text, { children: [jsx(Text, { color: "90", children: "test files matching " }), jsx(MatchText, { text: this.props.pathMatch }), jsx(Text, { color: "90", children: "." })] }));
|
|
629
|
-
}
|
|
630
|
-
else {
|
|
631
|
-
pathMatchText = jsx(Text, { color: "90", children: "all test files." });
|
|
632
|
-
}
|
|
633
|
-
return (jsx(Line, { children: [jsx(Text, { color: "90", children: "Ran " }), testNameMatchText.length > 0 ? jsx(Text, { color: "90", children: "tests " }) : undefined, testNameMatchText, testNameMatchText.length > 0 ? jsx(Text, { color: "90", children: " in " }) : undefined, pathMatchText] }));
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
function summaryText({ duration, expectCount, fileCount, onlyMatch, pathMatch, skipMatch, targetCount, testCount, }) {
|
|
637
|
-
const targetCountText = (jsx(RowText, { label: "Targets", text: jsx(CountText, { failed: targetCount.failed, passed: targetCount.passed, skipped: targetCount.skipped, todo: targetCount.todo, total: targetCount.total }) }));
|
|
638
|
-
const fileCountText = (jsx(RowText, { label: "Test files", text: jsx(CountText, { failed: fileCount.failed, passed: fileCount.passed, skipped: fileCount.skipped, todo: fileCount.todo, total: fileCount.total }) }));
|
|
639
|
-
const testCountText = (jsx(RowText, { label: "Tests", text: jsx(CountText, { failed: testCount.failed, passed: testCount.passed, skipped: testCount.skipped, todo: testCount.todo, total: testCount.total }) }));
|
|
640
|
-
const assertionCountText = (jsx(RowText, { label: "Assertions", text: jsx(CountText, { failed: expectCount.failed, passed: expectCount.passed, skipped: expectCount.skipped, todo: expectCount.todo, total: expectCount.total }) }));
|
|
641
|
-
return (jsx(Text, { children: [targetCountText, fileCountText, testCount.total > 0 ? testCountText : undefined, expectCount.total > 0 ? assertionCountText : undefined, jsx(RowText, { label: "Duration", text: jsx(DurationText, { duration: duration }) }), jsx(Line, {}), jsx(RanFilesText, { onlyMatch: onlyMatch, pathMatch: pathMatch, skipMatch: skipMatch })] }));
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
class StatusText {
|
|
645
|
-
props;
|
|
646
|
-
constructor(props) {
|
|
647
|
-
this.props = props;
|
|
648
|
-
}
|
|
649
|
-
render() {
|
|
650
|
-
switch (this.props.status) {
|
|
651
|
-
case "fail":
|
|
652
|
-
return jsx(Text, { color: "31", children: "\u00D7" });
|
|
653
|
-
case "pass":
|
|
654
|
-
return jsx(Text, { color: "32", children: "+" });
|
|
655
|
-
case "skip":
|
|
656
|
-
return jsx(Text, { color: "33", children: "- skip" });
|
|
657
|
-
case "todo":
|
|
658
|
-
return jsx(Text, { color: "35", children: "- todo" });
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
function testNameText(status, name, indent = 0) {
|
|
663
|
-
return (jsx(Line, { indent: indent + 1, children: [jsx(StatusText, { status: status }), " ", jsx(Text, { color: "90", children: name })] }));
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
class ProjectNameText {
|
|
667
|
-
props;
|
|
668
|
-
constructor(props) {
|
|
669
|
-
this.props = props;
|
|
670
|
-
}
|
|
671
|
-
render() {
|
|
672
|
-
return (jsx(Text, { color: "90", children: [" with ", Path.relative("", this.props.filePath)] }));
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
function usesCompilerStepText(compilerVersion, tsconfigFilePath, options) {
|
|
676
|
-
let projectPathText;
|
|
677
|
-
if (tsconfigFilePath != null) {
|
|
678
|
-
projectPathText = jsx(ProjectNameText, { filePath: tsconfigFilePath });
|
|
679
|
-
}
|
|
680
|
-
return (jsx(Text, { children: [options?.prependEmptyLine === true ? jsx(Line, {}) : undefined, jsx(Line, { children: [jsx(Text, { color: "34", children: "uses" }), " TypeScript ", compilerVersion, projectPathText] }), jsx(Line, {})] }));
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
function waitingForFileChangesText() {
|
|
684
|
-
return jsx(Line, { children: "Waiting for file changes." });
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
function watchUsageText() {
|
|
688
|
-
const usageText = Object.entries({ a: "run all tests", x: "exit" }).map(([key, action]) => {
|
|
689
|
-
return (jsx(Line, { children: [jsx(Text, { color: "90", children: "Press" }), jsx(Text, { children: ` ${key} ` }), jsx(Text, { color: "90", children: `to ${action}.` })] }));
|
|
690
|
-
});
|
|
691
|
-
return jsx(Text, { children: usageText });
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
class FileViewService {
|
|
695
|
-
#indent = 0;
|
|
696
|
-
#lines = [];
|
|
697
|
-
#messages = [];
|
|
698
|
-
get hasErrors() {
|
|
699
|
-
return this.#messages.length > 0;
|
|
700
|
-
}
|
|
701
|
-
addMessage(message) {
|
|
702
|
-
this.#messages.push(message);
|
|
703
|
-
}
|
|
704
|
-
addTest(status, name) {
|
|
705
|
-
this.#lines.push(testNameText(status, name, this.#indent));
|
|
706
|
-
}
|
|
707
|
-
beginDescribe(name) {
|
|
708
|
-
this.#lines.push(describeNameText(name, this.#indent));
|
|
709
|
-
this.#indent++;
|
|
710
|
-
}
|
|
711
|
-
clear() {
|
|
712
|
-
this.#indent = 0;
|
|
713
|
-
this.#lines = [];
|
|
714
|
-
this.#messages = [];
|
|
715
|
-
}
|
|
716
|
-
endDescribe() {
|
|
717
|
-
this.#indent--;
|
|
718
|
-
}
|
|
719
|
-
getMessages() {
|
|
720
|
-
return this.#messages;
|
|
721
|
-
}
|
|
722
|
-
getViewText(options) {
|
|
723
|
-
return fileViewText(this.#lines, options?.appendEmptyLine === true || this.hasErrors);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
class RuntimeReporter {
|
|
728
|
-
#currentCompilerVersion;
|
|
729
|
-
#currentProjectConfigFilePath;
|
|
730
|
-
#fileCount = 0;
|
|
731
|
-
#fileView = new FileViewService();
|
|
732
|
-
#hasReportedAdds = false;
|
|
733
|
-
#hasReportedError = false;
|
|
734
|
-
#isFileViewExpanded = false;
|
|
735
|
-
#resolvedConfig;
|
|
736
|
-
#outputService;
|
|
737
|
-
#seenDeprecations = new Set();
|
|
738
|
-
constructor(resolvedConfig, outputService) {
|
|
739
|
-
this.#resolvedConfig = resolvedConfig;
|
|
740
|
-
this.#outputService = outputService;
|
|
741
|
-
}
|
|
742
|
-
get #isLastFile() {
|
|
743
|
-
return this.#fileCount === 0;
|
|
744
|
-
}
|
|
745
|
-
handleEvent([eventName, payload]) {
|
|
746
|
-
switch (eventName) {
|
|
747
|
-
case "deprecation:info": {
|
|
748
|
-
for (const diagnostic of payload.diagnostics) {
|
|
749
|
-
if (!this.#seenDeprecations.has(diagnostic.text.toString())) {
|
|
750
|
-
this.#fileView.addMessage(diagnosticText(diagnostic));
|
|
751
|
-
this.#seenDeprecations.add(diagnostic.text.toString());
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
break;
|
|
755
|
-
}
|
|
756
|
-
case "run:start": {
|
|
757
|
-
this.#isFileViewExpanded = payload.result.testFiles.length === 1 && this.#resolvedConfig.watch !== true;
|
|
758
|
-
break;
|
|
759
|
-
}
|
|
760
|
-
case "store:info": {
|
|
761
|
-
this.#outputService.writeMessage(addsPackageStepText(payload.compilerVersion, payload.installationPath));
|
|
762
|
-
this.#hasReportedAdds = true;
|
|
763
|
-
break;
|
|
764
|
-
}
|
|
765
|
-
case "store:error": {
|
|
766
|
-
for (const diagnostic of payload.diagnostics) {
|
|
767
|
-
this.#outputService.writeError(diagnosticText(diagnostic));
|
|
768
|
-
}
|
|
769
|
-
break;
|
|
770
|
-
}
|
|
771
|
-
case "target:start": {
|
|
772
|
-
this.#fileCount = payload.result.testFiles.length;
|
|
773
|
-
break;
|
|
774
|
-
}
|
|
775
|
-
case "target:end": {
|
|
776
|
-
this.#currentCompilerVersion = undefined;
|
|
777
|
-
this.#currentProjectConfigFilePath = undefined;
|
|
778
|
-
break;
|
|
779
|
-
}
|
|
780
|
-
case "project:info": {
|
|
781
|
-
if (this.#currentCompilerVersion !== payload.compilerVersion ||
|
|
782
|
-
this.#currentProjectConfigFilePath !== payload.projectConfigFilePath) {
|
|
783
|
-
this.#outputService.writeMessage(usesCompilerStepText(payload.compilerVersion, payload.projectConfigFilePath, {
|
|
784
|
-
prependEmptyLine: this.#currentCompilerVersion != null && !this.#hasReportedAdds && !this.#hasReportedError,
|
|
785
|
-
}));
|
|
786
|
-
this.#hasReportedAdds = false;
|
|
787
|
-
this.#currentCompilerVersion = payload.compilerVersion;
|
|
788
|
-
this.#currentProjectConfigFilePath = payload.projectConfigFilePath;
|
|
789
|
-
}
|
|
790
|
-
break;
|
|
791
|
-
}
|
|
792
|
-
case "project:error": {
|
|
793
|
-
for (const diagnostic of payload.diagnostics) {
|
|
794
|
-
this.#outputService.writeError(diagnosticText(diagnostic));
|
|
795
|
-
}
|
|
796
|
-
break;
|
|
797
|
-
}
|
|
798
|
-
case "file:start": {
|
|
799
|
-
if (!Environment.noInteractive) {
|
|
800
|
-
this.#outputService.writeMessage(fileStatusText(payload.result.status, payload.result.testFile));
|
|
801
|
-
}
|
|
802
|
-
this.#fileCount--;
|
|
803
|
-
this.#hasReportedError = false;
|
|
804
|
-
break;
|
|
805
|
-
}
|
|
806
|
-
case "file:error": {
|
|
807
|
-
for (const diagnostic of payload.diagnostics) {
|
|
808
|
-
this.#fileView.addMessage(diagnosticText(diagnostic));
|
|
809
|
-
}
|
|
810
|
-
break;
|
|
811
|
-
}
|
|
812
|
-
case "file:end": {
|
|
813
|
-
if (!Environment.noInteractive) {
|
|
814
|
-
this.#outputService.eraseLastLine();
|
|
815
|
-
}
|
|
816
|
-
this.#outputService.writeMessage(fileStatusText(payload.result.status, payload.result.testFile));
|
|
817
|
-
this.#outputService.writeMessage(this.#fileView.getViewText({ appendEmptyLine: this.#isLastFile }));
|
|
818
|
-
if (this.#fileView.hasErrors) {
|
|
819
|
-
this.#outputService.writeError(this.#fileView.getMessages());
|
|
820
|
-
this.#hasReportedError = true;
|
|
821
|
-
}
|
|
822
|
-
this.#fileView.clear();
|
|
823
|
-
this.#seenDeprecations.clear();
|
|
824
|
-
break;
|
|
825
|
-
}
|
|
826
|
-
case "describe:start": {
|
|
827
|
-
if (this.#isFileViewExpanded) {
|
|
828
|
-
this.#fileView.beginDescribe(payload.result.describe.name);
|
|
829
|
-
}
|
|
830
|
-
break;
|
|
831
|
-
}
|
|
832
|
-
case "describe:end": {
|
|
833
|
-
if (this.#isFileViewExpanded) {
|
|
834
|
-
this.#fileView.endDescribe();
|
|
835
|
-
}
|
|
836
|
-
break;
|
|
837
|
-
}
|
|
838
|
-
case "test:skip": {
|
|
839
|
-
if (this.#isFileViewExpanded) {
|
|
840
|
-
this.#fileView.addTest("skip", payload.result.test.name);
|
|
841
|
-
}
|
|
842
|
-
break;
|
|
843
|
-
}
|
|
844
|
-
case "test:todo": {
|
|
845
|
-
if (this.#isFileViewExpanded) {
|
|
846
|
-
this.#fileView.addTest("todo", payload.result.test.name);
|
|
847
|
-
}
|
|
848
|
-
break;
|
|
849
|
-
}
|
|
850
|
-
case "test:error": {
|
|
851
|
-
if (this.#isFileViewExpanded) {
|
|
852
|
-
this.#fileView.addTest("fail", payload.result.test.name);
|
|
853
|
-
}
|
|
854
|
-
for (const diagnostic of payload.diagnostics) {
|
|
855
|
-
this.#fileView.addMessage(diagnosticText(diagnostic));
|
|
856
|
-
}
|
|
857
|
-
break;
|
|
858
|
-
}
|
|
859
|
-
case "test:fail": {
|
|
860
|
-
if (this.#isFileViewExpanded) {
|
|
861
|
-
this.#fileView.addTest("fail", payload.result.test.name);
|
|
862
|
-
}
|
|
863
|
-
break;
|
|
864
|
-
}
|
|
865
|
-
case "test:pass": {
|
|
866
|
-
if (this.#isFileViewExpanded) {
|
|
867
|
-
this.#fileView.addTest("pass", payload.result.test.name);
|
|
868
|
-
}
|
|
869
|
-
break;
|
|
870
|
-
}
|
|
871
|
-
case "expect:error":
|
|
872
|
-
case "expect:fail": {
|
|
873
|
-
for (const diagnostic of payload.diagnostics) {
|
|
874
|
-
this.#fileView.addMessage(diagnosticText(diagnostic));
|
|
875
|
-
}
|
|
876
|
-
break;
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
class SetupReporter {
|
|
883
|
-
#outputService;
|
|
884
|
-
constructor(outputService) {
|
|
885
|
-
this.#outputService = outputService;
|
|
886
|
-
}
|
|
887
|
-
handleEvent([eventName, payload]) {
|
|
888
|
-
if (eventName === "store:info") {
|
|
889
|
-
this.#outputService.writeMessage(addsPackageStepText(payload.compilerVersion, payload.installationPath));
|
|
890
|
-
return;
|
|
891
|
-
}
|
|
892
|
-
if ("diagnostics" in payload) {
|
|
893
|
-
for (const diagnostic of payload.diagnostics) {
|
|
894
|
-
switch (diagnostic.category) {
|
|
895
|
-
case "error": {
|
|
896
|
-
this.#outputService.writeError(diagnosticText(diagnostic));
|
|
897
|
-
break;
|
|
898
|
-
}
|
|
899
|
-
case "warning": {
|
|
900
|
-
this.#outputService.writeWarning(diagnosticText(diagnostic));
|
|
901
|
-
break;
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
class SummaryReporter {
|
|
910
|
-
#outputService;
|
|
911
|
-
constructor(outputService) {
|
|
912
|
-
this.#outputService = outputService;
|
|
913
|
-
}
|
|
914
|
-
handleEvent([eventName, payload]) {
|
|
915
|
-
switch (eventName) {
|
|
916
|
-
case "run:end": {
|
|
917
|
-
this.#outputService.writeMessage(summaryText({
|
|
918
|
-
duration: payload.result.timing.duration,
|
|
919
|
-
expectCount: payload.result.expectCount,
|
|
920
|
-
fileCount: payload.result.fileCount,
|
|
921
|
-
onlyMatch: payload.result.resolvedConfig.only,
|
|
922
|
-
pathMatch: payload.result.resolvedConfig.pathMatch,
|
|
923
|
-
skipMatch: payload.result.resolvedConfig.skip,
|
|
924
|
-
targetCount: payload.result.targetCount,
|
|
925
|
-
testCount: payload.result.testCount,
|
|
926
|
-
}));
|
|
927
|
-
break;
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
class WatchReporter {
|
|
934
|
-
#outputService;
|
|
935
|
-
constructor(outputService) {
|
|
936
|
-
this.#outputService = outputService;
|
|
937
|
-
}
|
|
938
|
-
handleEvent([eventName, payload]) {
|
|
939
|
-
switch (eventName) {
|
|
940
|
-
case "run:start": {
|
|
941
|
-
this.#outputService.clearTerminal();
|
|
942
|
-
break;
|
|
943
|
-
}
|
|
944
|
-
case "run:end": {
|
|
945
|
-
this.#outputService.writeMessage(watchUsageText());
|
|
946
|
-
break;
|
|
947
|
-
}
|
|
948
|
-
case "watch:error": {
|
|
949
|
-
this.#outputService.clearTerminal();
|
|
950
|
-
for (const diagnostic of payload.diagnostics) {
|
|
951
|
-
this.#outputService.writeError(diagnosticText(diagnostic));
|
|
952
|
-
}
|
|
953
|
-
this.#outputService.writeMessage(waitingForFileChangesText());
|
|
954
|
-
break;
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
class ResultTiming {
|
|
961
|
-
end = Number.NaN;
|
|
962
|
-
start = Number.NaN;
|
|
963
|
-
get duration() {
|
|
964
|
-
return this.end - this.start;
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
class DescribeResult {
|
|
969
|
-
describe;
|
|
970
|
-
parent;
|
|
971
|
-
results = [];
|
|
972
|
-
timing = new ResultTiming();
|
|
973
|
-
constructor(describe, parent) {
|
|
974
|
-
this.describe = describe;
|
|
975
|
-
this.parent = parent;
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
var ResultStatus;
|
|
980
|
-
(function (ResultStatus) {
|
|
981
|
-
ResultStatus["Runs"] = "runs";
|
|
982
|
-
ResultStatus["Passed"] = "passed";
|
|
983
|
-
ResultStatus["Failed"] = "failed";
|
|
984
|
-
ResultStatus["Skipped"] = "skipped";
|
|
985
|
-
ResultStatus["Todo"] = "todo";
|
|
986
|
-
})(ResultStatus || (ResultStatus = {}));
|
|
987
|
-
|
|
988
|
-
class ExpectResult {
|
|
989
|
-
assertion;
|
|
990
|
-
diagnostics = [];
|
|
991
|
-
parent;
|
|
992
|
-
status = "runs";
|
|
993
|
-
timing = new ResultTiming();
|
|
994
|
-
constructor(assertion, parent) {
|
|
995
|
-
this.assertion = assertion;
|
|
996
|
-
this.parent = parent;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
class ResultCount {
|
|
1001
|
-
failed = 0;
|
|
1002
|
-
passed = 0;
|
|
1003
|
-
skipped = 0;
|
|
1004
|
-
todo = 0;
|
|
1005
|
-
get total() {
|
|
1006
|
-
return this.failed + this.passed + this.skipped + this.todo;
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
class FileResult {
|
|
1011
|
-
diagnostics = [];
|
|
1012
|
-
expectCount = new ResultCount();
|
|
1013
|
-
results = [];
|
|
1014
|
-
status = "runs";
|
|
1015
|
-
testCount = new ResultCount();
|
|
1016
|
-
testFile;
|
|
1017
|
-
timing = new ResultTiming();
|
|
1018
|
-
constructor(testFile) {
|
|
1019
|
-
this.testFile = testFile;
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
class ProjectResult {
|
|
1024
|
-
compilerVersion;
|
|
1025
|
-
diagnostics = [];
|
|
1026
|
-
projectConfigFilePath;
|
|
1027
|
-
results = [];
|
|
1028
|
-
constructor(compilerVersion, projectConfigFilePath) {
|
|
1029
|
-
this.compilerVersion = compilerVersion;
|
|
1030
|
-
this.projectConfigFilePath = projectConfigFilePath;
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
class Result {
|
|
1035
|
-
expectCount = new ResultCount();
|
|
1036
|
-
fileCount = new ResultCount();
|
|
1037
|
-
resolvedConfig;
|
|
1038
|
-
results = [];
|
|
1039
|
-
targetCount = new ResultCount();
|
|
1040
|
-
testCount = new ResultCount();
|
|
1041
|
-
testFiles;
|
|
1042
|
-
timing = new ResultTiming();
|
|
1043
|
-
constructor(resolvedConfig, testFiles) {
|
|
1044
|
-
this.resolvedConfig = resolvedConfig;
|
|
1045
|
-
this.testFiles = testFiles;
|
|
215
|
+
class TestResult {
|
|
216
|
+
diagnostics = [];
|
|
217
|
+
expectCount = new ResultCount();
|
|
218
|
+
parent;
|
|
219
|
+
results = [];
|
|
220
|
+
status = "runs";
|
|
221
|
+
test;
|
|
222
|
+
timing = new ResultTiming();
|
|
223
|
+
constructor(test, parent) {
|
|
224
|
+
this.test = test;
|
|
225
|
+
this.parent = parent;
|
|
1046
226
|
}
|
|
1047
227
|
}
|
|
1048
228
|
|
|
@@ -1262,783 +442,883 @@ class ResultHandler {
|
|
|
1262
442
|
}
|
|
1263
443
|
}
|
|
1264
444
|
|
|
1265
|
-
class
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
445
|
+
class Environment {
|
|
446
|
+
static #isCi = Environment.#resolveIsCi();
|
|
447
|
+
static #noColor = Environment.#resolveNoColor();
|
|
448
|
+
static #noInteractive = Environment.#resolveNoInteractive();
|
|
449
|
+
static #storePath = Environment.#resolveStorePath();
|
|
450
|
+
static #timeout = Environment.#resolveTimeout();
|
|
451
|
+
static #typescriptPath = Environment.#resolveTypeScriptPath();
|
|
452
|
+
static get isCi() {
|
|
453
|
+
return Environment.#isCi;
|
|
1274
454
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
class TestResult {
|
|
1278
|
-
diagnostics = [];
|
|
1279
|
-
expectCount = new ResultCount();
|
|
1280
|
-
parent;
|
|
1281
|
-
results = [];
|
|
1282
|
-
status = "runs";
|
|
1283
|
-
test;
|
|
1284
|
-
timing = new ResultTiming();
|
|
1285
|
-
constructor(test, parent) {
|
|
1286
|
-
this.test = test;
|
|
1287
|
-
this.parent = parent;
|
|
455
|
+
static get noColor() {
|
|
456
|
+
return Environment.#noColor;
|
|
1288
457
|
}
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
class CancellationToken {
|
|
1292
|
-
#isCancelled = false;
|
|
1293
|
-
#handlers = new Set();
|
|
1294
|
-
#reason;
|
|
1295
|
-
get isCancellationRequested() {
|
|
1296
|
-
return this.#isCancelled;
|
|
458
|
+
static get noInteractive() {
|
|
459
|
+
return Environment.#noInteractive;
|
|
1297
460
|
}
|
|
1298
|
-
get
|
|
1299
|
-
return
|
|
461
|
+
static get storePath() {
|
|
462
|
+
return Environment.#storePath;
|
|
1300
463
|
}
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
464
|
+
static get timeout() {
|
|
465
|
+
return Environment.#timeout;
|
|
466
|
+
}
|
|
467
|
+
static get typescriptPath() {
|
|
468
|
+
return Environment.#typescriptPath;
|
|
469
|
+
}
|
|
470
|
+
static #resolveIsCi() {
|
|
471
|
+
if (process.env["CI"] != null) {
|
|
472
|
+
return process.env["CI"] !== "";
|
|
1308
473
|
}
|
|
474
|
+
return false;
|
|
1309
475
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
476
|
+
static #resolveNoColor() {
|
|
477
|
+
if (process.env["TSTYCHE_NO_COLOR"] != null) {
|
|
478
|
+
return process.env["TSTYCHE_NO_COLOR"] !== "";
|
|
479
|
+
}
|
|
480
|
+
if (process.env["NO_COLOR"] != null) {
|
|
481
|
+
return process.env["NO_COLOR"] !== "";
|
|
482
|
+
}
|
|
483
|
+
return false;
|
|
1312
484
|
}
|
|
1313
|
-
|
|
1314
|
-
if (
|
|
1315
|
-
|
|
1316
|
-
|
|
485
|
+
static #resolveNoInteractive() {
|
|
486
|
+
if (process.env["TSTYCHE_NO_INTERACTIVE"] != null) {
|
|
487
|
+
return process.env["TSTYCHE_NO_INTERACTIVE"] !== "";
|
|
488
|
+
}
|
|
489
|
+
return !process.stdout.isTTY;
|
|
490
|
+
}
|
|
491
|
+
static #resolveStorePath() {
|
|
492
|
+
if (process.env["TSTYCHE_STORE_PATH"] != null) {
|
|
493
|
+
return Path.resolve(process.env["TSTYCHE_STORE_PATH"]);
|
|
494
|
+
}
|
|
495
|
+
if (process.platform === "darwin") {
|
|
496
|
+
return Path.resolve(os.homedir(), "Library", "TSTyche");
|
|
497
|
+
}
|
|
498
|
+
if (process.env["LocalAppData"] != null) {
|
|
499
|
+
return Path.resolve(process.env["LocalAppData"], "TSTyche");
|
|
500
|
+
}
|
|
501
|
+
if (process.env["XDG_DATA_HOME"] != null) {
|
|
502
|
+
return Path.resolve(process.env["XDG_DATA_HOME"], "TSTyche");
|
|
503
|
+
}
|
|
504
|
+
return Path.resolve(os.homedir(), ".local", "share", "TSTyche");
|
|
505
|
+
}
|
|
506
|
+
static #resolveTimeout() {
|
|
507
|
+
if (process.env["TSTYCHE_TIMEOUT"] != null) {
|
|
508
|
+
return Number(process.env["TSTYCHE_TIMEOUT"]);
|
|
509
|
+
}
|
|
510
|
+
return 30;
|
|
511
|
+
}
|
|
512
|
+
static #resolveTypeScriptPath() {
|
|
513
|
+
let moduleId = "typescript";
|
|
514
|
+
if (process.env["TSTYCHE_TYPESCRIPT_PATH"] != null) {
|
|
515
|
+
moduleId = process.env["TSTYCHE_TYPESCRIPT_PATH"];
|
|
516
|
+
}
|
|
517
|
+
let resolvedPath;
|
|
518
|
+
try {
|
|
519
|
+
resolvedPath = Path.normalizeSlashes(createRequire(import.meta.url).resolve(moduleId));
|
|
520
|
+
}
|
|
521
|
+
catch {
|
|
1317
522
|
}
|
|
523
|
+
return resolvedPath;
|
|
1318
524
|
}
|
|
1319
525
|
}
|
|
1320
526
|
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
CancellationReason["ConfigError"] = "configError";
|
|
1325
|
-
CancellationReason["FailFast"] = "failFast";
|
|
1326
|
-
})(CancellationReason || (CancellationReason = {}));
|
|
527
|
+
function jsx(type, props) {
|
|
528
|
+
return { props, type };
|
|
529
|
+
}
|
|
1327
530
|
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
531
|
+
var Color;
|
|
532
|
+
(function (Color) {
|
|
533
|
+
Color["Reset"] = "0";
|
|
534
|
+
Color["Red"] = "31";
|
|
535
|
+
Color["Green"] = "32";
|
|
536
|
+
Color["Yellow"] = "33";
|
|
537
|
+
Color["Blue"] = "34";
|
|
538
|
+
Color["Magenta"] = "35";
|
|
539
|
+
Color["Cyan"] = "36";
|
|
540
|
+
Color["Gray"] = "90";
|
|
541
|
+
})(Color || (Color = {}));
|
|
542
|
+
|
|
543
|
+
function Text({ children, color, indent }) {
|
|
544
|
+
const ansiEscapes = [];
|
|
545
|
+
if (color != null) {
|
|
546
|
+
ansiEscapes.push(color);
|
|
1340
547
|
}
|
|
1341
|
-
|
|
1342
|
-
|
|
548
|
+
return (jsx("text", { indent: indent ?? 0, children: [ansiEscapes.length > 0 ? jsx("ansi", { escapes: ansiEscapes }) : undefined, children, ansiEscapes.length > 0 ? jsx("ansi", { escapes: "0" }) : undefined] }));
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function Line({ children, color, indent }) {
|
|
552
|
+
return (jsx(Text, { color: color, indent: indent, children: [children, jsx("newLine", {})] }));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
class Scribbler {
|
|
556
|
+
#indentStep = " ";
|
|
557
|
+
#newLine;
|
|
558
|
+
#noColor;
|
|
559
|
+
#notEmptyLineRegex = /^(?!$)/gm;
|
|
560
|
+
constructor(options) {
|
|
561
|
+
this.#newLine = options?.newLine ?? "\n";
|
|
562
|
+
this.#noColor = options?.noColor ?? false;
|
|
1343
563
|
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
564
|
+
#escapeSequence(attributes) {
|
|
565
|
+
return ["\u001B[", Array.isArray(attributes) ? attributes.join(";") : attributes, "m"].join("");
|
|
566
|
+
}
|
|
567
|
+
#indentEachLine(lines, level) {
|
|
568
|
+
if (level === 0) {
|
|
569
|
+
return lines;
|
|
570
|
+
}
|
|
571
|
+
return lines.replace(this.#notEmptyLineRegex, this.#indentStep.repeat(level));
|
|
572
|
+
}
|
|
573
|
+
render(element) {
|
|
574
|
+
if (typeof element.type === "function") {
|
|
575
|
+
return this.render(element.type({ ...element.props }));
|
|
576
|
+
}
|
|
577
|
+
if (element.type === "ansi" && !this.#noColor) {
|
|
578
|
+
return this.#escapeSequence(element.props.escapes);
|
|
579
|
+
}
|
|
580
|
+
if (element.type === "newLine") {
|
|
581
|
+
return this.#newLine;
|
|
582
|
+
}
|
|
583
|
+
if (element.type === "text") {
|
|
584
|
+
const text = this.#visitChildren(element.props.children);
|
|
585
|
+
return this.#indentEachLine(text, element.props.indent);
|
|
586
|
+
}
|
|
587
|
+
return "";
|
|
588
|
+
}
|
|
589
|
+
#visitChildren(children) {
|
|
590
|
+
const text = [];
|
|
591
|
+
for (const child of children) {
|
|
592
|
+
if (typeof child === "string") {
|
|
593
|
+
text.push(child);
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
if (Array.isArray(child)) {
|
|
597
|
+
text.push(this.#visitChildren(child));
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
if (child != null && typeof child === "object") {
|
|
601
|
+
text.push(this.render(child));
|
|
1357
602
|
}
|
|
1358
603
|
}
|
|
1359
|
-
|
|
1360
|
-
if (error instanceof Error && error.name === "AbortError") ;
|
|
1361
|
-
}
|
|
604
|
+
return text.join("");
|
|
1362
605
|
}
|
|
1363
606
|
}
|
|
1364
607
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
const onChangedFile = async (filePath) => {
|
|
1368
|
-
if (filePath === targetPath) {
|
|
1369
|
-
await onChanged();
|
|
1370
|
-
}
|
|
1371
|
-
};
|
|
1372
|
-
super(Path.dirname(targetPath), onChangedFile);
|
|
1373
|
-
}
|
|
608
|
+
function addsPackageStepText(compilerVersion, installationPath) {
|
|
609
|
+
return (jsx(Line, { children: [jsx(Text, { color: "90", children: "adds" }), " TypeScript ", compilerVersion, jsx(Text, { color: "90", children: [" to ", installationPath] })] }));
|
|
1374
610
|
}
|
|
1375
611
|
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
612
|
+
function describeNameText(name, indent = 0) {
|
|
613
|
+
return jsx(Line, { indent: indent + 1, children: name });
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function CodeSpanText(diagnosticOrigin) {
|
|
617
|
+
const lastLineInFile = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.sourceFile.text.length).line;
|
|
618
|
+
const { character: markedCharacter, line: markedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.start);
|
|
619
|
+
const firstLine = Math.max(markedLine - 2, 0);
|
|
620
|
+
const lastLine = Math.min(firstLine + 5, lastLineInFile);
|
|
621
|
+
const lineNumberMaxWidth = String(lastLine + 1).length;
|
|
622
|
+
const codeSpan = [];
|
|
623
|
+
for (let index = firstLine; index <= lastLine; index++) {
|
|
624
|
+
const lineStart = diagnosticOrigin.sourceFile.getPositionOfLineAndCharacter(index, 0);
|
|
625
|
+
const lineEnd = index === lastLineInFile
|
|
626
|
+
? diagnosticOrigin.sourceFile.text.length
|
|
627
|
+
: diagnosticOrigin.sourceFile.getPositionOfLineAndCharacter(index + 1, 0);
|
|
628
|
+
const lineNumberText = String(index + 1);
|
|
629
|
+
const lineText = diagnosticOrigin.sourceFile.text.slice(lineStart, lineEnd).trimEnd().replace(/\t/g, " ");
|
|
630
|
+
if (index === markedLine) {
|
|
631
|
+
codeSpan.push(jsx(Line, { children: [jsx(Text, { color: "31", children: ">" }), jsx(Text, { children: " " }), lineNumberText.padStart(lineNumberMaxWidth), jsx(Text, { children: " " }), jsx(Text, { color: "90", children: "|" }), " ", lineText] }), jsx(Line, { children: [" ".repeat(lineNumberMaxWidth + 3), jsx(Text, { color: "90", children: "|" }), " ".repeat(markedCharacter + 1), jsx(Text, { color: "31", children: "^" })] }));
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
codeSpan.push(jsx(Line, { children: [" ".repeat(2), jsx(Text, { color: "90", children: [lineNumberText.padStart(lineNumberMaxWidth), " | ", lineText || ""] })] }));
|
|
635
|
+
}
|
|
1392
636
|
}
|
|
637
|
+
const breadcrumbs = diagnosticOrigin.breadcrumbs?.flatMap((ancestor) => [
|
|
638
|
+
jsx(Text, { color: "90", children: " ❭ " }),
|
|
639
|
+
jsx(Text, { children: ancestor }),
|
|
640
|
+
]);
|
|
641
|
+
const location = (jsx(Line, { children: [" ".repeat(lineNumberMaxWidth + 5), jsx(Text, { color: "90", children: "at" }), jsx(Text, { children: " " }), jsx(Text, { color: "36", children: Path.relative("", diagnosticOrigin.sourceFile.fileName) }), jsx(Text, { color: "90", children: [":", String(markedLine + 1), ":", String(markedCharacter + 1)] }), breadcrumbs] }));
|
|
642
|
+
return (jsx(Text, { children: [codeSpan, jsx(Line, {}), location] }));
|
|
1393
643
|
}
|
|
1394
644
|
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
645
|
+
function DiagnosticText({ diagnostic }) {
|
|
646
|
+
const code = typeof diagnostic.code === "string" ? jsx(Text, { color: "90", children: [" ", diagnostic.code] }) : undefined;
|
|
647
|
+
const text = Array.isArray(diagnostic.text) ? diagnostic.text : [diagnostic.text];
|
|
648
|
+
const message = text.map((text, index) => (jsx(Text, { children: [index === 1 ? jsx(Line, {}) : undefined, jsx(Line, { children: [text, code] })] })));
|
|
649
|
+
const related = diagnostic.related?.map((relatedDiagnostic) => jsx(DiagnosticText, { diagnostic: relatedDiagnostic }));
|
|
650
|
+
const codeSpan = diagnostic.origin ? (jsx(Text, { children: [jsx(Line, {}), jsx(CodeSpanText, { ...diagnostic.origin })] })) : undefined;
|
|
651
|
+
return (jsx(Text, { children: [message, codeSpan, jsx(Line, {}), jsx(Text, { indent: 2, children: related })] }));
|
|
652
|
+
}
|
|
653
|
+
function diagnosticText(diagnostic) {
|
|
654
|
+
let prefix;
|
|
655
|
+
switch (diagnostic.category) {
|
|
656
|
+
case "error": {
|
|
657
|
+
prefix = jsx(Text, { color: "31", children: "Error: " });
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
case "warning": {
|
|
661
|
+
prefix = jsx(Text, { color: "33", children: "Warning: " });
|
|
662
|
+
break;
|
|
663
|
+
}
|
|
1405
664
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
665
|
+
return (jsx(Text, { children: [prefix, jsx(DiagnosticText, { diagnostic: diagnostic })] }));
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function FileNameText({ filePath }) {
|
|
669
|
+
const relativePath = Path.relative("", filePath);
|
|
670
|
+
const lastPathSeparator = relativePath.lastIndexOf("/");
|
|
671
|
+
const directoryNameText = relativePath.slice(0, lastPathSeparator + 1);
|
|
672
|
+
const fileNameText = relativePath.slice(lastPathSeparator + 1);
|
|
673
|
+
return (jsx(Text, { children: [jsx(Text, { color: "90", children: directoryNameText }), fileNameText] }));
|
|
674
|
+
}
|
|
675
|
+
function fileStatusText(status, testFile) {
|
|
676
|
+
let statusColor;
|
|
677
|
+
let statusText;
|
|
678
|
+
switch (status) {
|
|
679
|
+
case "runs": {
|
|
680
|
+
statusColor = "33";
|
|
681
|
+
statusText = "runs";
|
|
682
|
+
break;
|
|
1409
683
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
684
|
+
case "passed": {
|
|
685
|
+
statusColor = "32";
|
|
686
|
+
statusText = "pass";
|
|
687
|
+
break;
|
|
1412
688
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
689
|
+
case "failed": {
|
|
690
|
+
statusColor = "31";
|
|
691
|
+
statusText = "fail";
|
|
692
|
+
break;
|
|
1415
693
|
}
|
|
1416
|
-
return this;
|
|
1417
|
-
}
|
|
1418
|
-
static error(text, origin) {
|
|
1419
|
-
return new Diagnostic(text, "error", origin);
|
|
1420
|
-
}
|
|
1421
|
-
static fromDiagnostics(diagnostics, compiler) {
|
|
1422
|
-
return diagnostics.map((diagnostic) => {
|
|
1423
|
-
const category = "error";
|
|
1424
|
-
const code = `ts(${String(diagnostic.code)})`;
|
|
1425
|
-
let origin;
|
|
1426
|
-
const text = compiler.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
1427
|
-
if (Diagnostic.isTsDiagnosticWithLocation(diagnostic)) {
|
|
1428
|
-
origin = new DiagnosticOrigin(diagnostic.start, diagnostic.start + diagnostic.length, diagnostic.file);
|
|
1429
|
-
}
|
|
1430
|
-
return new Diagnostic(text, category, origin).add({ code });
|
|
1431
|
-
});
|
|
1432
694
|
}
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
return
|
|
695
|
+
return (jsx(Line, { children: [jsx(Text, { color: statusColor, children: statusText }), " ", jsx(FileNameText, { filePath: testFile.path })] }));
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function fileViewText(lines, addEmptyFinalLine) {
|
|
699
|
+
return (jsx(Text, { children: [[...lines], addEmptyFinalLine ? jsx(Line, {}) : undefined] }));
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function formattedText(input) {
|
|
703
|
+
if (typeof input === "string") {
|
|
704
|
+
return jsx(Line, { children: input });
|
|
1443
705
|
}
|
|
1444
|
-
|
|
1445
|
-
return
|
|
706
|
+
if (Array.isArray(input)) {
|
|
707
|
+
return jsx(Line, { children: JSON.stringify(input, null, 2) });
|
|
1446
708
|
}
|
|
1447
|
-
|
|
1448
|
-
return
|
|
709
|
+
function sortObject(target) {
|
|
710
|
+
return Object.keys(target)
|
|
711
|
+
.sort()
|
|
712
|
+
.reduce((result, key) => {
|
|
713
|
+
result[key] = target[key];
|
|
714
|
+
return result;
|
|
715
|
+
}, {});
|
|
1449
716
|
}
|
|
717
|
+
return jsx(Line, { children: JSON.stringify(sortObject(input), null, 2) });
|
|
1450
718
|
}
|
|
1451
719
|
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
{
|
|
1491
|
-
|
|
1492
|
-
description: "Only run tests with matching name.",
|
|
1493
|
-
group: 2,
|
|
1494
|
-
name: "only",
|
|
1495
|
-
},
|
|
1496
|
-
{
|
|
1497
|
-
brand: "bareTrue",
|
|
1498
|
-
description: "Remove all installed versions of the 'typescript' package and exit.",
|
|
1499
|
-
group: 2,
|
|
1500
|
-
name: "prune",
|
|
1501
|
-
},
|
|
1502
|
-
{
|
|
1503
|
-
brand: "string",
|
|
1504
|
-
description: "The path to a directory containing files of a test project.",
|
|
1505
|
-
group: 4,
|
|
1506
|
-
name: "rootPath",
|
|
1507
|
-
},
|
|
1508
|
-
{
|
|
1509
|
-
brand: "bareTrue",
|
|
1510
|
-
description: "Print the resolved configuration and exit.",
|
|
1511
|
-
group: 2,
|
|
1512
|
-
name: "showConfig",
|
|
1513
|
-
},
|
|
1514
|
-
{
|
|
1515
|
-
brand: "string",
|
|
1516
|
-
description: "Skip tests with matching name.",
|
|
1517
|
-
group: 2,
|
|
1518
|
-
name: "skip",
|
|
1519
|
-
},
|
|
1520
|
-
{
|
|
1521
|
-
brand: "list",
|
|
1522
|
-
description: "The list of TypeScript versions to be tested on.",
|
|
1523
|
-
group: 2 | 4,
|
|
1524
|
-
items: {
|
|
1525
|
-
brand: "string",
|
|
1526
|
-
name: "target",
|
|
1527
|
-
pattern: "^([45]\\.[0-9](\\.[0-9])?)|beta|current|latest|next|rc$",
|
|
1528
|
-
},
|
|
1529
|
-
name: "target",
|
|
1530
|
-
},
|
|
1531
|
-
{
|
|
1532
|
-
brand: "list",
|
|
1533
|
-
description: "The list of glob patterns matching the test files.",
|
|
1534
|
-
group: 4,
|
|
1535
|
-
items: {
|
|
1536
|
-
brand: "string",
|
|
1537
|
-
name: "testFileMatch",
|
|
1538
|
-
},
|
|
1539
|
-
name: "testFileMatch",
|
|
1540
|
-
},
|
|
1541
|
-
{
|
|
1542
|
-
brand: "bareTrue",
|
|
1543
|
-
description: "Fetch the 'typescript' package metadata from the registry and exit.",
|
|
1544
|
-
group: 2,
|
|
1545
|
-
name: "update",
|
|
1546
|
-
},
|
|
1547
|
-
{
|
|
1548
|
-
brand: "bareTrue",
|
|
1549
|
-
description: "Print the version number and exit.",
|
|
1550
|
-
group: 2,
|
|
1551
|
-
name: "version",
|
|
1552
|
-
},
|
|
1553
|
-
{
|
|
1554
|
-
brand: "bareTrue",
|
|
1555
|
-
description: "Watch for changes and rerun related test files.",
|
|
1556
|
-
group: 2,
|
|
1557
|
-
name: "watch",
|
|
1558
|
-
},
|
|
1559
|
-
];
|
|
1560
|
-
static for(optionGroup) {
|
|
1561
|
-
const definitionMap = new Map();
|
|
1562
|
-
for (const definition of OptionDefinitionsMap.#definitions) {
|
|
1563
|
-
if (definition.group & optionGroup) {
|
|
1564
|
-
definitionMap.set(definition.name, definition);
|
|
1565
|
-
}
|
|
720
|
+
function HintText({ children }) {
|
|
721
|
+
return (jsx(Text, { indent: 1, color: "90", children: children }));
|
|
722
|
+
}
|
|
723
|
+
function HelpHeaderText({ tstycheVersion }) {
|
|
724
|
+
return (jsx(Line, { children: ["The TSTyche Type Test Runner", jsx(HintText, { children: tstycheVersion })] }));
|
|
725
|
+
}
|
|
726
|
+
function CommandText({ hint, text }) {
|
|
727
|
+
let hintText;
|
|
728
|
+
if (hint != null) {
|
|
729
|
+
hintText = jsx(HintText, { children: hint });
|
|
730
|
+
}
|
|
731
|
+
return (jsx(Line, { indent: 1, children: [jsx(Text, { color: "34", children: text }), hintText] }));
|
|
732
|
+
}
|
|
733
|
+
function OptionDescriptionText({ text }) {
|
|
734
|
+
return jsx(Line, { indent: 1, children: text });
|
|
735
|
+
}
|
|
736
|
+
function CommandLineUsageText() {
|
|
737
|
+
const usage = [
|
|
738
|
+
["tstyche", "Run all tests."],
|
|
739
|
+
["tstyche path/to/first.test.ts", "Only run the test files with matching path."],
|
|
740
|
+
["tstyche --target 4.9,5.3.2,current", "Test on all specified versions of TypeScript."],
|
|
741
|
+
];
|
|
742
|
+
const usageText = usage.map(([commandText, descriptionText]) => (jsx(Line, { children: [jsx(CommandText, { text: commandText }), jsx(OptionDescriptionText, { text: descriptionText })] })));
|
|
743
|
+
return jsx(Text, { children: usageText });
|
|
744
|
+
}
|
|
745
|
+
function CommandLineOptionNameText({ text }) {
|
|
746
|
+
return jsx(Text, { children: ["--", text] });
|
|
747
|
+
}
|
|
748
|
+
function CommandLineOptionHintText({ definition }) {
|
|
749
|
+
if (definition.brand === "list") {
|
|
750
|
+
return (jsx(Text, { children: [definition.brand, " of ", definition.items.brand, "s"] }));
|
|
751
|
+
}
|
|
752
|
+
return jsx(Text, { children: definition.brand });
|
|
753
|
+
}
|
|
754
|
+
function CommandLineOptionsText({ optionDefinitions }) {
|
|
755
|
+
const definitions = [...optionDefinitions.values()];
|
|
756
|
+
const optionsText = definitions.map((definition) => {
|
|
757
|
+
let hint;
|
|
758
|
+
if (definition.brand !== "bareTrue") {
|
|
759
|
+
hint = jsx(CommandLineOptionHintText, { definition: definition });
|
|
1566
760
|
}
|
|
1567
|
-
return
|
|
761
|
+
return (jsx(Text, { children: [jsx(CommandText, { text: jsx(CommandLineOptionNameText, { text: definition.name }), hint: hint }), jsx(OptionDescriptionText, { text: definition.description }), jsx(Line, {})] }));
|
|
762
|
+
});
|
|
763
|
+
return (jsx(Text, { children: [jsx(Line, { children: "Command Line Options" }), jsx(Line, {}), optionsText] }));
|
|
764
|
+
}
|
|
765
|
+
function HelpFooterText() {
|
|
766
|
+
return jsx(Line, { children: "To learn more, visit https://tstyche.org" });
|
|
767
|
+
}
|
|
768
|
+
function helpText(optionDefinitions, tstycheVersion) {
|
|
769
|
+
return (jsx(Text, { children: [jsx(HelpHeaderText, { tstycheVersion: tstycheVersion }), jsx(Line, {}), jsx(CommandLineUsageText, {}), jsx(Line, {}), jsx(CommandLineOptionsText, { optionDefinitions: optionDefinitions }), jsx(Line, {}), jsx(HelpFooterText, {}), jsx(Line, {})] }));
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
class OutputService {
|
|
773
|
+
#isClear = false;
|
|
774
|
+
#noColor;
|
|
775
|
+
#scribbler;
|
|
776
|
+
#stderr;
|
|
777
|
+
#stdout;
|
|
778
|
+
constructor(options) {
|
|
779
|
+
this.#noColor = options?.noColor ?? Environment.noColor;
|
|
780
|
+
this.#stderr = options?.stderr ?? process.stderr;
|
|
781
|
+
this.#stdout = options?.stdout ?? process.stdout;
|
|
782
|
+
this.#scribbler = new Scribbler({ noColor: this.#noColor });
|
|
783
|
+
}
|
|
784
|
+
clearTerminal() {
|
|
785
|
+
if (!this.#isClear) {
|
|
786
|
+
this.#stdout.write("\u001B[2J\u001B[3J\u001B[H");
|
|
787
|
+
this.#isClear = true;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
eraseLastLine() {
|
|
791
|
+
this.#stdout.write("\u001B[1A\u001B[0K");
|
|
792
|
+
}
|
|
793
|
+
#write(stream, body) {
|
|
794
|
+
const elements = Array.isArray(body) ? body : [body];
|
|
795
|
+
for (const element of elements) {
|
|
796
|
+
stream.write(this.#scribbler.render(element));
|
|
797
|
+
}
|
|
798
|
+
this.#isClear = false;
|
|
799
|
+
}
|
|
800
|
+
writeError(body) {
|
|
801
|
+
this.#write(this.#stderr, body);
|
|
802
|
+
}
|
|
803
|
+
writeMessage(body) {
|
|
804
|
+
this.#write(this.#stdout, body);
|
|
805
|
+
}
|
|
806
|
+
writeWarning(body) {
|
|
807
|
+
this.#write(this.#stderr, body);
|
|
1568
808
|
}
|
|
1569
809
|
}
|
|
1570
810
|
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
811
|
+
function RowText({ label, text }) {
|
|
812
|
+
return (jsx(Line, { children: [`${label}:`.padEnd(12), text] }));
|
|
813
|
+
}
|
|
814
|
+
function CountText({ failed, passed, skipped, todo, total }) {
|
|
815
|
+
return (jsx(Text, { children: [failed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "31", children: [String(failed), " failed"] }), jsx(Text, { children: ", " })] })) : undefined, skipped > 0 ? (jsx(Text, { children: [jsx(Text, { color: "33", children: [String(skipped), " skipped"] }), jsx(Text, { children: ", " })] })) : undefined, todo > 0 ? (jsx(Text, { children: [jsx(Text, { color: "35", children: [String(todo), " todo"] }), jsx(Text, { children: ", " })] })) : undefined, passed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "32", children: [String(passed), " passed"] }), jsx(Text, { children: ", " })] })) : undefined, jsx(Text, { children: [String(total), jsx(Text, { children: " total" })] })] }));
|
|
816
|
+
}
|
|
817
|
+
function DurationText({ duration }) {
|
|
818
|
+
const minutes = Math.floor(duration / 60);
|
|
819
|
+
const seconds = duration % 60;
|
|
820
|
+
return (jsx(Text, { children: [minutes > 0 ? `${String(minutes)}m ` : undefined, `${String(Math.round(seconds * 10) / 10)}s`] }));
|
|
821
|
+
}
|
|
822
|
+
function MatchText({ text }) {
|
|
823
|
+
if (typeof text === "string") {
|
|
824
|
+
return jsx(Text, { children: ["'", text, "'"] });
|
|
1574
825
|
}
|
|
1575
|
-
|
|
1576
|
-
return
|
|
826
|
+
if (text.length <= 1) {
|
|
827
|
+
return jsx(Text, { children: ["'", ...text, "'"] });
|
|
1577
828
|
}
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
829
|
+
const lastItem = text.pop();
|
|
830
|
+
return (jsx(Text, { children: [text.map((match, index, list) => (jsx(Text, { children: ["'", match, "'", index === list.length - 1 ? jsx(Text, { children: " " }) : jsx(Text, { color: "90", children: ", " })] }))), jsx(Text, { color: "90", children: "or" }), " '", lastItem, "'"] }));
|
|
831
|
+
}
|
|
832
|
+
function RanFilesText({ onlyMatch, pathMatch, skipMatch }) {
|
|
833
|
+
const testNameMatchText = [];
|
|
834
|
+
if (onlyMatch != null) {
|
|
835
|
+
testNameMatchText.push(jsx(Text, { children: [jsx(Text, { color: "90", children: "matching " }), jsx(MatchText, { text: onlyMatch })] }));
|
|
1581
836
|
}
|
|
1582
|
-
|
|
1583
|
-
|
|
837
|
+
if (skipMatch != null) {
|
|
838
|
+
testNameMatchText.push(jsx(Text, { children: [onlyMatch == null ? undefined : jsx(Text, { color: "90", children: " and " }), jsx(Text, { color: "90", children: "not matching " }), jsx(MatchText, { text: skipMatch })] }));
|
|
1584
839
|
}
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
`Test file match: ${resolvedConfig.testFileMatch.join(", ")}`,
|
|
1589
|
-
];
|
|
1590
|
-
if (resolvedConfig.pathMatch.length > 0) {
|
|
1591
|
-
text.push(`Path match: ${resolvedConfig.pathMatch.join(", ")}`);
|
|
1592
|
-
}
|
|
1593
|
-
return text;
|
|
840
|
+
let pathMatchText;
|
|
841
|
+
if (pathMatch.length > 0) {
|
|
842
|
+
pathMatchText = (jsx(Text, { children: [jsx(Text, { color: "90", children: "test files matching " }), jsx(MatchText, { text: pathMatch }), jsx(Text, { color: "90", children: "." })] }));
|
|
1594
843
|
}
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
"No test files were left to run using current configuration.",
|
|
1598
|
-
...OptionDiagnosticText.#pathSelectOptions(resolvedConfig),
|
|
1599
|
-
];
|
|
844
|
+
else {
|
|
845
|
+
pathMatchText = jsx(Text, { color: "90", children: "all test files." });
|
|
1600
846
|
}
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
847
|
+
return (jsx(Line, { children: [jsx(Text, { color: "90", children: "Ran " }), testNameMatchText.length > 0 ? jsx(Text, { color: "90", children: "tests " }) : undefined, testNameMatchText, testNameMatchText.length > 0 ? jsx(Text, { color: "90", children: " in " }) : undefined, pathMatchText] }));
|
|
848
|
+
}
|
|
849
|
+
function summaryText({ duration, expectCount, fileCount, onlyMatch, pathMatch, skipMatch, targetCount, testCount, }) {
|
|
850
|
+
const targetCountText = (jsx(RowText, { label: "Targets", text: jsx(CountText, { failed: targetCount.failed, passed: targetCount.passed, skipped: targetCount.skipped, todo: targetCount.todo, total: targetCount.total }) }));
|
|
851
|
+
const fileCountText = (jsx(RowText, { label: "Test files", text: jsx(CountText, { failed: fileCount.failed, passed: fileCount.passed, skipped: fileCount.skipped, todo: fileCount.todo, total: fileCount.total }) }));
|
|
852
|
+
const testCountText = (jsx(RowText, { label: "Tests", text: jsx(CountText, { failed: testCount.failed, passed: testCount.passed, skipped: testCount.skipped, todo: testCount.todo, total: testCount.total }) }));
|
|
853
|
+
const assertionCountText = (jsx(RowText, { label: "Assertions", text: jsx(CountText, { failed: expectCount.failed, passed: expectCount.passed, skipped: expectCount.skipped, todo: expectCount.todo, total: expectCount.total }) }));
|
|
854
|
+
return (jsx(Text, { children: [targetCountText, fileCountText, testCount.total > 0 ? testCountText : undefined, expectCount.total > 0 ? assertionCountText : undefined, jsx(RowText, { label: "Duration", text: jsx(DurationText, { duration: duration / 1000 }) }), jsx(Line, {}), jsx(RanFilesText, { onlyMatch: onlyMatch, pathMatch: pathMatch, skipMatch: skipMatch })] }));
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function StatusText({ status }) {
|
|
858
|
+
switch (status) {
|
|
859
|
+
case "fail":
|
|
860
|
+
return jsx(Text, { color: "31", children: "\u00D7" });
|
|
861
|
+
case "pass":
|
|
862
|
+
return jsx(Text, { color: "32", children: "+" });
|
|
863
|
+
case "skip":
|
|
864
|
+
return jsx(Text, { color: "33", children: "- skip" });
|
|
865
|
+
case "todo":
|
|
866
|
+
return jsx(Text, { color: "35", children: "- todo" });
|
|
1606
867
|
}
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
868
|
+
}
|
|
869
|
+
function testNameText(status, name, indent = 0) {
|
|
870
|
+
return (jsx(Line, { indent: indent + 1, children: [jsx(StatusText, { status: status }), " ", jsx(Text, { color: "90", children: name })] }));
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function usesCompilerStepText(compilerVersion, tsconfigFilePath, options) {
|
|
874
|
+
let projectPathText;
|
|
875
|
+
if (tsconfigFilePath != null) {
|
|
876
|
+
projectPathText = (jsx(Text, { color: "90", children: [" with ", Path.relative("", tsconfigFilePath)] }));
|
|
1614
877
|
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
878
|
+
return (jsx(Text, { children: [options?.prependEmptyLine === true ? jsx(Line, {}) : undefined, jsx(Line, { children: [jsx(Text, { color: "34", children: "uses" }), " TypeScript ", compilerVersion, projectPathText] }), jsx(Line, {})] }));
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function waitingForFileChangesText() {
|
|
882
|
+
return jsx(Line, { children: "Waiting for file changes." });
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function watchUsageText() {
|
|
886
|
+
const usage = [
|
|
887
|
+
["a", "to run all tests."],
|
|
888
|
+
["x", "to exit."],
|
|
889
|
+
];
|
|
890
|
+
const usageText = usage.map(([keyText, actionText]) => {
|
|
891
|
+
return (jsx(Line, { children: [jsx(Text, { color: "90", children: "Press" }), jsx(Text, { children: ` ${keyText} ` }), jsx(Text, { color: "90", children: actionText })] }));
|
|
892
|
+
});
|
|
893
|
+
return jsx(Text, { children: usageText });
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
class FileViewService {
|
|
897
|
+
#indent = 0;
|
|
898
|
+
#lines = [];
|
|
899
|
+
#messages = [];
|
|
900
|
+
get hasErrors() {
|
|
901
|
+
return this.#messages.length > 0;
|
|
1620
902
|
}
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
return `Option '${optionName}' requires a value of type ${optionBrand}.`;
|
|
903
|
+
addMessage(message) {
|
|
904
|
+
this.#messages.push(message);
|
|
1624
905
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
906
|
+
addTest(status, name) {
|
|
907
|
+
this.#lines.push(testNameText(status, name, this.#indent));
|
|
1627
908
|
}
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
}
|
|
1632
|
-
return `TypeScript version '${value}' is not supported.`;
|
|
909
|
+
beginDescribe(name) {
|
|
910
|
+
this.#lines.push(describeNameText(name, this.#indent));
|
|
911
|
+
this.#indent++;
|
|
1633
912
|
}
|
|
1634
|
-
|
|
1635
|
-
|
|
913
|
+
clear() {
|
|
914
|
+
this.#indent = 0;
|
|
915
|
+
this.#lines = [];
|
|
916
|
+
this.#messages = [];
|
|
917
|
+
}
|
|
918
|
+
endDescribe() {
|
|
919
|
+
this.#indent--;
|
|
920
|
+
}
|
|
921
|
+
getMessages() {
|
|
922
|
+
return this.#messages;
|
|
1636
923
|
}
|
|
1637
|
-
|
|
1638
|
-
return
|
|
924
|
+
getViewText(options) {
|
|
925
|
+
return fileViewText(this.#lines, options?.appendEmptyLine === true || this.hasErrors);
|
|
1639
926
|
}
|
|
1640
927
|
}
|
|
1641
928
|
|
|
1642
|
-
class
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
this.#optionGroup = optionGroup;
|
|
1647
|
-
this.#storeService = storeService;
|
|
929
|
+
class Reporter {
|
|
930
|
+
outputService;
|
|
931
|
+
constructor(outputService) {
|
|
932
|
+
this.outputService = outputService;
|
|
1648
933
|
}
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
class RunReporter extends Reporter {
|
|
937
|
+
#currentCompilerVersion;
|
|
938
|
+
#currentProjectConfigFilePath;
|
|
939
|
+
#fileCount = 0;
|
|
940
|
+
#fileView = new FileViewService();
|
|
941
|
+
#hasReportedAdds = false;
|
|
942
|
+
#hasReportedError = false;
|
|
943
|
+
#isFileViewExpanded = false;
|
|
944
|
+
#resolvedConfig;
|
|
945
|
+
#seenDeprecations = new Set();
|
|
946
|
+
constructor(resolvedConfig, outputService) {
|
|
947
|
+
super(outputService);
|
|
948
|
+
this.#resolvedConfig = resolvedConfig;
|
|
949
|
+
}
|
|
950
|
+
get #isLastFile() {
|
|
951
|
+
return this.#fileCount === 0;
|
|
952
|
+
}
|
|
953
|
+
handleEvent([eventName, payload]) {
|
|
954
|
+
switch (eventName) {
|
|
955
|
+
case "deprecation:info": {
|
|
956
|
+
for (const diagnostic of payload.diagnostics) {
|
|
957
|
+
if (!this.#seenDeprecations.has(diagnostic.text.toString())) {
|
|
958
|
+
this.#fileView.addMessage(diagnosticText(diagnostic));
|
|
959
|
+
this.#seenDeprecations.add(diagnostic.text.toString());
|
|
1663
960
|
}
|
|
1664
961
|
}
|
|
1665
962
|
break;
|
|
1666
963
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
964
|
+
case "run:start": {
|
|
965
|
+
this.#isFileViewExpanded = payload.result.testFiles.length === 1 && this.#resolvedConfig.watch !== true;
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
case "store:info": {
|
|
969
|
+
this.outputService.writeMessage(addsPackageStepText(payload.compilerVersion, payload.installationPath));
|
|
970
|
+
this.#hasReportedAdds = true;
|
|
971
|
+
break;
|
|
972
|
+
}
|
|
973
|
+
case "store:error": {
|
|
974
|
+
for (const diagnostic of payload.diagnostics) {
|
|
975
|
+
this.outputService.writeError(diagnosticText(diagnostic));
|
|
976
|
+
}
|
|
977
|
+
break;
|
|
978
|
+
}
|
|
979
|
+
case "target:start": {
|
|
980
|
+
this.#fileCount = payload.result.testFiles.length;
|
|
981
|
+
break;
|
|
982
|
+
}
|
|
983
|
+
case "target:end": {
|
|
984
|
+
this.#currentCompilerVersion = undefined;
|
|
985
|
+
this.#currentProjectConfigFilePath = undefined;
|
|
986
|
+
break;
|
|
987
|
+
}
|
|
988
|
+
case "project:info": {
|
|
989
|
+
if (this.#currentCompilerVersion !== payload.compilerVersion ||
|
|
990
|
+
this.#currentProjectConfigFilePath !== payload.projectConfigFilePath) {
|
|
991
|
+
this.outputService.writeMessage(usesCompilerStepText(payload.compilerVersion, payload.projectConfigFilePath, {
|
|
992
|
+
prependEmptyLine: this.#currentCompilerVersion != null && !this.#hasReportedAdds && !this.#hasReportedError,
|
|
993
|
+
}));
|
|
994
|
+
this.#hasReportedAdds = false;
|
|
995
|
+
this.#currentCompilerVersion = payload.compilerVersion;
|
|
996
|
+
this.#currentProjectConfigFilePath = payload.projectConfigFilePath;
|
|
1691
997
|
}
|
|
1692
998
|
break;
|
|
1693
999
|
}
|
|
1694
|
-
case "
|
|
1695
|
-
|
|
1696
|
-
this
|
|
1697
|
-
OptionDiagnosticText.versionIsNotSupported(optionValue),
|
|
1698
|
-
...(await this.#optionUsageText.get(optionName, optionBrand)),
|
|
1699
|
-
], origin));
|
|
1000
|
+
case "project:error": {
|
|
1001
|
+
for (const diagnostic of payload.diagnostics) {
|
|
1002
|
+
this.outputService.writeError(diagnosticText(diagnostic));
|
|
1700
1003
|
}
|
|
1701
1004
|
break;
|
|
1702
1005
|
}
|
|
1703
|
-
case "
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
this.#onDiagnostic(Diagnostic.error(OptionDiagnosticText.testFileMatchCannotStartWith(segment), origin));
|
|
1707
|
-
}
|
|
1006
|
+
case "file:start": {
|
|
1007
|
+
if (!Environment.noInteractive) {
|
|
1008
|
+
this.outputService.writeMessage(fileStatusText(payload.result.status, payload.result.testFile));
|
|
1708
1009
|
}
|
|
1010
|
+
this.#fileCount--;
|
|
1011
|
+
this.#hasReportedError = false;
|
|
1709
1012
|
break;
|
|
1710
1013
|
}
|
|
1711
|
-
case "
|
|
1712
|
-
|
|
1713
|
-
this.#
|
|
1014
|
+
case "file:error": {
|
|
1015
|
+
for (const diagnostic of payload.diagnostics) {
|
|
1016
|
+
this.#fileView.addMessage(diagnosticText(diagnostic));
|
|
1714
1017
|
}
|
|
1715
1018
|
break;
|
|
1716
1019
|
}
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
class CommandLineOptionsWorker {
|
|
1722
|
-
#commandLineOptionDefinitions;
|
|
1723
|
-
#commandLineOptions;
|
|
1724
|
-
#onDiagnostic;
|
|
1725
|
-
#optionGroup = 2;
|
|
1726
|
-
#optionUsageText;
|
|
1727
|
-
#optionValidator;
|
|
1728
|
-
#pathMatch;
|
|
1729
|
-
#storeService;
|
|
1730
|
-
constructor(commandLineOptions, pathMatch, storeService, onDiagnostic) {
|
|
1731
|
-
this.#commandLineOptions = commandLineOptions;
|
|
1732
|
-
this.#pathMatch = pathMatch;
|
|
1733
|
-
this.#storeService = storeService;
|
|
1734
|
-
this.#onDiagnostic = onDiagnostic;
|
|
1735
|
-
this.#commandLineOptionDefinitions = OptionDefinitionsMap.for(this.#optionGroup);
|
|
1736
|
-
this.#optionUsageText = new OptionUsageText(this.#optionGroup, this.#storeService);
|
|
1737
|
-
this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostic);
|
|
1738
|
-
}
|
|
1739
|
-
async #onExpectsValue(optionDefinition) {
|
|
1740
|
-
const text = [
|
|
1741
|
-
OptionDiagnosticText.expectsValue(optionDefinition.name, this.#optionGroup),
|
|
1742
|
-
...(await this.#optionUsageText.get(optionDefinition.name, optionDefinition.brand)),
|
|
1743
|
-
];
|
|
1744
|
-
this.#onDiagnostic(Diagnostic.error(text));
|
|
1745
|
-
}
|
|
1746
|
-
async parse(commandLineArgs) {
|
|
1747
|
-
let index = 0;
|
|
1748
|
-
let arg = commandLineArgs[index];
|
|
1749
|
-
while (arg != null) {
|
|
1750
|
-
index++;
|
|
1751
|
-
if (arg.startsWith("--")) {
|
|
1752
|
-
const optionName = arg.slice(2);
|
|
1753
|
-
const optionDefinition = this.#commandLineOptionDefinitions.get(optionName);
|
|
1754
|
-
if (optionDefinition) {
|
|
1755
|
-
index = await this.#parseOptionValue(commandLineArgs, index, optionDefinition);
|
|
1020
|
+
case "file:end": {
|
|
1021
|
+
if (!Environment.noInteractive) {
|
|
1022
|
+
this.outputService.eraseLastLine();
|
|
1756
1023
|
}
|
|
1757
|
-
|
|
1758
|
-
|
|
1024
|
+
this.outputService.writeMessage(fileStatusText(payload.result.status, payload.result.testFile));
|
|
1025
|
+
this.outputService.writeMessage(this.#fileView.getViewText({ appendEmptyLine: this.#isLastFile }));
|
|
1026
|
+
if (this.#fileView.hasErrors) {
|
|
1027
|
+
this.outputService.writeError(this.#fileView.getMessages());
|
|
1028
|
+
this.#hasReportedError = true;
|
|
1759
1029
|
}
|
|
1030
|
+
this.#fileView.clear();
|
|
1031
|
+
this.#seenDeprecations.clear();
|
|
1032
|
+
break;
|
|
1760
1033
|
}
|
|
1761
|
-
|
|
1762
|
-
this.#
|
|
1034
|
+
case "describe:start": {
|
|
1035
|
+
if (this.#isFileViewExpanded) {
|
|
1036
|
+
this.#fileView.beginDescribe(payload.result.describe.name);
|
|
1037
|
+
}
|
|
1038
|
+
break;
|
|
1763
1039
|
}
|
|
1764
|
-
|
|
1765
|
-
this.#
|
|
1040
|
+
case "describe:end": {
|
|
1041
|
+
if (this.#isFileViewExpanded) {
|
|
1042
|
+
this.#fileView.endDescribe();
|
|
1043
|
+
}
|
|
1044
|
+
break;
|
|
1766
1045
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
let optionValue = this.#resolveOptionValue(commandLineArgs[index]);
|
|
1772
|
-
switch (optionDefinition.brand) {
|
|
1773
|
-
case "bareTrue": {
|
|
1774
|
-
await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
|
|
1775
|
-
this.#commandLineOptions[optionDefinition.name] = true;
|
|
1046
|
+
case "test:skip": {
|
|
1047
|
+
if (this.#isFileViewExpanded) {
|
|
1048
|
+
this.#fileView.addTest("skip", payload.result.test.name);
|
|
1049
|
+
}
|
|
1776
1050
|
break;
|
|
1777
1051
|
}
|
|
1778
|
-
case "
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
if (optionValue === "false" || optionValue === "true") {
|
|
1782
|
-
index++;
|
|
1052
|
+
case "test:todo": {
|
|
1053
|
+
if (this.#isFileViewExpanded) {
|
|
1054
|
+
this.#fileView.addTest("todo", payload.result.test.name);
|
|
1783
1055
|
}
|
|
1784
1056
|
break;
|
|
1785
1057
|
}
|
|
1786
|
-
case "
|
|
1787
|
-
if (
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
for (const optionValue of optionValues) {
|
|
1793
|
-
await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
|
|
1794
|
-
}
|
|
1795
|
-
this.#commandLineOptions[optionDefinition.name] = optionValues;
|
|
1796
|
-
index++;
|
|
1797
|
-
break;
|
|
1058
|
+
case "test:error": {
|
|
1059
|
+
if (this.#isFileViewExpanded) {
|
|
1060
|
+
this.#fileView.addTest("fail", payload.result.test.name);
|
|
1061
|
+
}
|
|
1062
|
+
for (const diagnostic of payload.diagnostics) {
|
|
1063
|
+
this.#fileView.addMessage(diagnosticText(diagnostic));
|
|
1798
1064
|
}
|
|
1799
|
-
await this.#onExpectsValue(optionDefinition);
|
|
1800
1065
|
break;
|
|
1801
1066
|
}
|
|
1802
|
-
case "
|
|
1803
|
-
if (
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1067
|
+
case "test:fail": {
|
|
1068
|
+
if (this.#isFileViewExpanded) {
|
|
1069
|
+
this.#fileView.addTest("fail", payload.result.test.name);
|
|
1070
|
+
}
|
|
1071
|
+
break;
|
|
1072
|
+
}
|
|
1073
|
+
case "test:pass": {
|
|
1074
|
+
if (this.#isFileViewExpanded) {
|
|
1075
|
+
this.#fileView.addTest("pass", payload.result.test.name);
|
|
1076
|
+
}
|
|
1077
|
+
break;
|
|
1078
|
+
}
|
|
1079
|
+
case "expect:error":
|
|
1080
|
+
case "expect:fail": {
|
|
1081
|
+
for (const diagnostic of payload.diagnostics) {
|
|
1082
|
+
this.#fileView.addMessage(diagnosticText(diagnostic));
|
|
1811
1083
|
}
|
|
1812
|
-
await this.#onExpectsValue(optionDefinition);
|
|
1813
1084
|
break;
|
|
1814
1085
|
}
|
|
1815
1086
|
}
|
|
1816
|
-
return index;
|
|
1817
|
-
}
|
|
1818
|
-
#resolveOptionValue(target = "") {
|
|
1819
|
-
return target.startsWith("-") ? "" : target;
|
|
1820
1087
|
}
|
|
1821
1088
|
}
|
|
1822
1089
|
|
|
1823
|
-
class
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
#configFilePath;
|
|
1828
|
-
#onDiagnostic;
|
|
1829
|
-
#optionGroup = 4;
|
|
1830
|
-
#optionValidator;
|
|
1831
|
-
#storeService;
|
|
1832
|
-
constructor(compiler, configFileOptions, configFilePath, storeService, onDiagnostic) {
|
|
1833
|
-
this.#compiler = compiler;
|
|
1834
|
-
this.#configFileOptions = configFileOptions;
|
|
1835
|
-
this.#configFilePath = configFilePath;
|
|
1836
|
-
this.#storeService = storeService;
|
|
1837
|
-
this.#onDiagnostic = onDiagnostic;
|
|
1838
|
-
this.#configFileOptionDefinitions = OptionDefinitionsMap.for(this.#optionGroup);
|
|
1839
|
-
this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostic);
|
|
1840
|
-
}
|
|
1841
|
-
#isDoubleQuotedString(node, sourceFile) {
|
|
1842
|
-
return (node.kind === this.#compiler.SyntaxKind.StringLiteral &&
|
|
1843
|
-
sourceFile.text.slice(this.#skipTrivia(node.pos, sourceFile), node.end).startsWith('"'));
|
|
1844
|
-
}
|
|
1845
|
-
async parse(sourceText) {
|
|
1846
|
-
const sourceFile = this.#compiler.parseJsonText(this.#configFilePath, sourceText);
|
|
1847
|
-
if (sourceFile.parseDiagnostics.length > 0) {
|
|
1848
|
-
for (const diagnostic of Diagnostic.fromDiagnostics(sourceFile.parseDiagnostics, this.#compiler)) {
|
|
1849
|
-
this.#onDiagnostic(diagnostic);
|
|
1850
|
-
}
|
|
1851
|
-
return;
|
|
1852
|
-
}
|
|
1853
|
-
const rootExpression = sourceFile.statements[0]?.expression;
|
|
1854
|
-
if (rootExpression == null || !this.#compiler.isObjectLiteralExpression(rootExpression)) {
|
|
1855
|
-
const origin = new DiagnosticOrigin(0, 0, sourceFile);
|
|
1856
|
-
this.#onDiagnostic(Diagnostic.error("The root value of a configuration file must be an object literal.", origin));
|
|
1090
|
+
class SetupReporter extends Reporter {
|
|
1091
|
+
handleEvent([eventName, payload]) {
|
|
1092
|
+
if (eventName === "store:info") {
|
|
1093
|
+
this.outputService.writeMessage(addsPackageStepText(payload.compilerVersion, payload.installationPath));
|
|
1857
1094
|
return;
|
|
1858
1095
|
}
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
const optionDefinition = this.#configFileOptionDefinitions.get(optionName);
|
|
1871
|
-
if (optionDefinition) {
|
|
1872
|
-
this.#configFileOptions[optionDefinition.name] = await this.#parseOptionValue(sourceFile, property.initializer, optionDefinition);
|
|
1873
|
-
}
|
|
1874
|
-
else {
|
|
1875
|
-
const origin = DiagnosticOrigin.fromJsonNode(property, sourceFile, this.#skipTrivia);
|
|
1876
|
-
this.#onDiagnostic(Diagnostic.error(OptionDiagnosticText.unknownOption(optionName), origin));
|
|
1096
|
+
if ("diagnostics" in payload) {
|
|
1097
|
+
for (const diagnostic of payload.diagnostics) {
|
|
1098
|
+
switch (diagnostic.category) {
|
|
1099
|
+
case "error": {
|
|
1100
|
+
this.outputService.writeError(diagnosticText(diagnostic));
|
|
1101
|
+
break;
|
|
1102
|
+
}
|
|
1103
|
+
case "warning": {
|
|
1104
|
+
this.outputService.writeWarning(diagnosticText(diagnostic));
|
|
1105
|
+
break;
|
|
1106
|
+
}
|
|
1877
1107
|
}
|
|
1878
1108
|
}
|
|
1879
1109
|
}
|
|
1880
|
-
return;
|
|
1881
1110
|
}
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
class SummaryReporter extends Reporter {
|
|
1114
|
+
handleEvent([eventName, payload]) {
|
|
1115
|
+
switch (eventName) {
|
|
1116
|
+
case "run:end": {
|
|
1117
|
+
this.outputService.writeMessage(summaryText({
|
|
1118
|
+
duration: payload.result.timing.duration,
|
|
1119
|
+
expectCount: payload.result.expectCount,
|
|
1120
|
+
fileCount: payload.result.fileCount,
|
|
1121
|
+
onlyMatch: payload.result.resolvedConfig.only,
|
|
1122
|
+
pathMatch: payload.result.resolvedConfig.pathMatch,
|
|
1123
|
+
skipMatch: payload.result.resolvedConfig.skip,
|
|
1124
|
+
targetCount: payload.result.targetCount,
|
|
1125
|
+
testCount: payload.result.testCount,
|
|
1126
|
+
}));
|
|
1894
1127
|
break;
|
|
1895
1128
|
}
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
value = Path.resolve(Path.dirname(this.#configFilePath), value);
|
|
1906
|
-
}
|
|
1907
|
-
const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
|
|
1908
|
-
await this.#optionValidator.check(optionDefinition.name, value, optionDefinition.brand, origin);
|
|
1909
|
-
return value;
|
|
1910
|
-
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
class WatchReporter extends Reporter {
|
|
1134
|
+
handleEvent([eventName, payload]) {
|
|
1135
|
+
switch (eventName) {
|
|
1136
|
+
case "run:start": {
|
|
1137
|
+
this.outputService.clearTerminal();
|
|
1911
1138
|
break;
|
|
1912
1139
|
}
|
|
1913
|
-
case
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1140
|
+
case "run:end": {
|
|
1141
|
+
this.outputService.writeMessage(watchUsageText());
|
|
1142
|
+
break;
|
|
1143
|
+
}
|
|
1144
|
+
case "watch:error": {
|
|
1145
|
+
this.outputService.clearTerminal();
|
|
1146
|
+
for (const diagnostic of payload.diagnostics) {
|
|
1147
|
+
this.outputService.writeError(diagnosticText(diagnostic));
|
|
1920
1148
|
}
|
|
1149
|
+
this.outputService.writeMessage(waitingForFileChangesText());
|
|
1921
1150
|
break;
|
|
1922
1151
|
}
|
|
1923
1152
|
}
|
|
1924
|
-
const text = isListItem
|
|
1925
|
-
? OptionDiagnosticText.expectsListItemType(optionDefinition.name, optionDefinition.brand)
|
|
1926
|
-
: OptionDiagnosticText.requiresValueType(optionDefinition.name, optionDefinition.brand, this.#optionGroup);
|
|
1927
|
-
const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
|
|
1928
|
-
this.#onDiagnostic(Diagnostic.error(text, origin));
|
|
1929
|
-
return;
|
|
1930
1153
|
}
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
class CancellationToken {
|
|
1157
|
+
#isCancelled = false;
|
|
1158
|
+
#handlers = new Set();
|
|
1159
|
+
#reason;
|
|
1160
|
+
get isCancellationRequested() {
|
|
1161
|
+
return this.#isCancelled;
|
|
1936
1162
|
}
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1163
|
+
get reason() {
|
|
1164
|
+
return this.#reason;
|
|
1165
|
+
}
|
|
1166
|
+
cancel(reason) {
|
|
1167
|
+
if (!this.#isCancelled) {
|
|
1168
|
+
for (const handler of this.#handlers) {
|
|
1169
|
+
handler(reason);
|
|
1943
1170
|
}
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1171
|
+
this.#isCancelled = true;
|
|
1172
|
+
this.#reason = reason;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
onCancellationRequested(handler) {
|
|
1176
|
+
this.#handlers.add(handler);
|
|
1177
|
+
}
|
|
1178
|
+
reset() {
|
|
1179
|
+
if (this.#isCancelled) {
|
|
1180
|
+
this.#isCancelled = false;
|
|
1181
|
+
this.#reason = undefined;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
var CancellationReason;
|
|
1187
|
+
(function (CancellationReason) {
|
|
1188
|
+
CancellationReason["ConfigChange"] = "configChange";
|
|
1189
|
+
CancellationReason["ConfigError"] = "configError";
|
|
1190
|
+
CancellationReason["FailFast"] = "failFast";
|
|
1191
|
+
})(CancellationReason || (CancellationReason = {}));
|
|
1192
|
+
|
|
1193
|
+
class Watcher {
|
|
1194
|
+
#abortController = new AbortController();
|
|
1195
|
+
#onChanged;
|
|
1196
|
+
#onRemoved;
|
|
1197
|
+
#recursive;
|
|
1198
|
+
#targetPath;
|
|
1199
|
+
#watcher;
|
|
1200
|
+
constructor(targetPath, onChanged, onRemoved, options) {
|
|
1201
|
+
this.#targetPath = targetPath;
|
|
1202
|
+
this.#onChanged = onChanged;
|
|
1203
|
+
this.#onRemoved = onRemoved ?? onChanged;
|
|
1204
|
+
this.#recursive = options?.recursive;
|
|
1205
|
+
}
|
|
1206
|
+
close() {
|
|
1207
|
+
this.#abortController.abort();
|
|
1208
|
+
}
|
|
1209
|
+
async watch() {
|
|
1210
|
+
this.#watcher = fs.watch(this.#targetPath, { recursive: this.#recursive, signal: this.#abortController.signal });
|
|
1211
|
+
try {
|
|
1212
|
+
for await (const event of this.#watcher) {
|
|
1213
|
+
if (event.filename != null) {
|
|
1214
|
+
const filePath = Path.resolve(this.#targetPath, event.filename);
|
|
1215
|
+
if (existsSync(filePath)) {
|
|
1216
|
+
await this.#onChanged(filePath);
|
|
1952
1217
|
}
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
if (text.charAt(position + 1) === "*") {
|
|
1956
|
-
position += 2;
|
|
1957
|
-
while (position < text.length) {
|
|
1958
|
-
if (text.charAt(position) === "*" && text.charAt(position + 1) === "/") {
|
|
1959
|
-
position += 2;
|
|
1960
|
-
break;
|
|
1961
|
-
}
|
|
1962
|
-
position++;
|
|
1218
|
+
else {
|
|
1219
|
+
await this.#onRemoved(filePath);
|
|
1963
1220
|
}
|
|
1964
|
-
continue;
|
|
1965
1221
|
}
|
|
1966
|
-
position++;
|
|
1967
|
-
continue;
|
|
1968
1222
|
}
|
|
1969
|
-
break;
|
|
1970
1223
|
}
|
|
1971
|
-
|
|
1224
|
+
catch (error) {
|
|
1225
|
+
if (error instanceof Error && error.name === "AbortError") ;
|
|
1226
|
+
}
|
|
1972
1227
|
}
|
|
1973
1228
|
}
|
|
1974
1229
|
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
#compiler;
|
|
1984
|
-
#configFileOptions = {};
|
|
1985
|
-
#configFilePath = Path.resolve(defaultOptions.rootPath, "./tstyche.config.json");
|
|
1986
|
-
#pathMatch = [];
|
|
1987
|
-
#storeService;
|
|
1988
|
-
constructor(compiler, storeService) {
|
|
1989
|
-
this.#compiler = compiler;
|
|
1990
|
-
this.#storeService = storeService;
|
|
1230
|
+
class FileWatcher extends Watcher {
|
|
1231
|
+
constructor(targetPath, onChanged) {
|
|
1232
|
+
const onChangedFile = async (filePath) => {
|
|
1233
|
+
if (filePath === targetPath) {
|
|
1234
|
+
await onChanged();
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
super(Path.dirname(targetPath), onChangedFile);
|
|
1991
1238
|
}
|
|
1992
|
-
|
|
1993
|
-
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
class DiagnosticOrigin {
|
|
1242
|
+
breadcrumbs;
|
|
1243
|
+
end;
|
|
1244
|
+
sourceFile;
|
|
1245
|
+
start;
|
|
1246
|
+
constructor(start, end, sourceFile, breadcrumbs) {
|
|
1247
|
+
this.start = start;
|
|
1248
|
+
this.end = end;
|
|
1249
|
+
this.sourceFile = sourceFile;
|
|
1250
|
+
this.breadcrumbs = breadcrumbs;
|
|
1994
1251
|
}
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
this.#pathMatch = [];
|
|
1998
|
-
const commandLineWorker = new CommandLineOptionsWorker(this.#commandLineOptions, this.#pathMatch, this.#storeService, this.#onDiagnostic);
|
|
1999
|
-
await commandLineWorker.parse(commandLineArgs);
|
|
2000
|
-
if (this.#commandLineOptions.config != null) {
|
|
2001
|
-
this.#configFilePath = this.#commandLineOptions.config;
|
|
2002
|
-
delete this.#commandLineOptions.config;
|
|
2003
|
-
}
|
|
1252
|
+
static fromJsonNode(node, sourceFile, skipTrivia) {
|
|
1253
|
+
return new DiagnosticOrigin(skipTrivia(node.pos, sourceFile), node.end, sourceFile);
|
|
2004
1254
|
}
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
1255
|
+
static fromNode(node, breadcrumbs) {
|
|
1256
|
+
return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), breadcrumbs);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
class Diagnostic {
|
|
1261
|
+
category;
|
|
1262
|
+
code;
|
|
1263
|
+
related;
|
|
1264
|
+
origin;
|
|
1265
|
+
text;
|
|
1266
|
+
constructor(text, category, origin) {
|
|
1267
|
+
this.text = text;
|
|
1268
|
+
this.category = category;
|
|
1269
|
+
this.origin = origin;
|
|
1270
|
+
}
|
|
1271
|
+
add(options) {
|
|
1272
|
+
if (options.code != null) {
|
|
1273
|
+
this.code = options.code;
|
|
2011
1274
|
}
|
|
2012
|
-
|
|
2013
|
-
|
|
1275
|
+
if (options.origin != null) {
|
|
1276
|
+
this.origin = options.origin;
|
|
1277
|
+
}
|
|
1278
|
+
if (options.related != null) {
|
|
1279
|
+
this.related = options.related;
|
|
1280
|
+
}
|
|
1281
|
+
return this;
|
|
1282
|
+
}
|
|
1283
|
+
static error(text, origin) {
|
|
1284
|
+
return new Diagnostic(text, "error", origin);
|
|
1285
|
+
}
|
|
1286
|
+
static fromDiagnostics(diagnostics, compiler) {
|
|
1287
|
+
return diagnostics.map((diagnostic) => {
|
|
1288
|
+
const category = "error";
|
|
1289
|
+
const code = `ts(${String(diagnostic.code)})`;
|
|
1290
|
+
let origin;
|
|
1291
|
+
const text = compiler.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
1292
|
+
if (Diagnostic.isTsDiagnosticWithLocation(diagnostic)) {
|
|
1293
|
+
origin = new DiagnosticOrigin(diagnostic.start, diagnostic.start + diagnostic.length, diagnostic.file);
|
|
1294
|
+
}
|
|
1295
|
+
return new Diagnostic(text, category, origin).add({ code });
|
|
2014
1296
|
});
|
|
2015
|
-
const configFileWorker = new ConfigFileOptionsWorker(this.#compiler, this.#configFileOptions, this.#configFilePath, this.#storeService, this.#onDiagnostic);
|
|
2016
|
-
await configFileWorker.parse(configFileText);
|
|
2017
1297
|
}
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
1298
|
+
static fromError(text, error) {
|
|
1299
|
+
const messageText = Array.isArray(text) ? text : [text];
|
|
1300
|
+
if (error instanceof Error && error.stack != null) {
|
|
1301
|
+
if (messageText.length > 1) {
|
|
1302
|
+
messageText.push("");
|
|
1303
|
+
}
|
|
1304
|
+
const stackLines = error.stack.split("\n").map((line) => line.trimStart());
|
|
1305
|
+
messageText.push(...stackLines);
|
|
1306
|
+
}
|
|
1307
|
+
return Diagnostic.error(messageText);
|
|
1308
|
+
}
|
|
1309
|
+
static isTsDiagnosticWithLocation(diagnostic) {
|
|
1310
|
+
return diagnostic.file != null && diagnostic.start != null && diagnostic.length != null;
|
|
1311
|
+
}
|
|
1312
|
+
static warning(text, origin) {
|
|
1313
|
+
return new Diagnostic(text, "warning", origin);
|
|
2026
1314
|
}
|
|
2027
1315
|
}
|
|
2028
1316
|
|
|
2029
|
-
var
|
|
2030
|
-
(function (
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
OptionBrand["BareTrue"] = "bareTrue";
|
|
2035
|
-
OptionBrand["List"] = "list";
|
|
2036
|
-
})(OptionBrand || (OptionBrand = {}));
|
|
2037
|
-
var OptionGroup;
|
|
2038
|
-
(function (OptionGroup) {
|
|
2039
|
-
OptionGroup[OptionGroup["CommandLine"] = 2] = "CommandLine";
|
|
2040
|
-
OptionGroup[OptionGroup["ConfigFile"] = 4] = "ConfigFile";
|
|
2041
|
-
})(OptionGroup || (OptionGroup = {}));
|
|
1317
|
+
var DiagnosticCategory;
|
|
1318
|
+
(function (DiagnosticCategory) {
|
|
1319
|
+
DiagnosticCategory["Error"] = "error";
|
|
1320
|
+
DiagnosticCategory["Warning"] = "warning";
|
|
1321
|
+
})(DiagnosticCategory || (DiagnosticCategory = {}));
|
|
2042
1322
|
|
|
2043
1323
|
class InputService {
|
|
2044
1324
|
#onInput;
|
|
@@ -2047,6 +1327,7 @@ class InputService {
|
|
|
2047
1327
|
this.#onInput = onInput;
|
|
2048
1328
|
this.#stdin = options?.stdin ?? process.stdin;
|
|
2049
1329
|
this.#stdin.setRawMode?.(true);
|
|
1330
|
+
this.#stdin.setEncoding("utf8");
|
|
2050
1331
|
this.#stdin.unref();
|
|
2051
1332
|
this.#stdin.addListener("data", this.#onInput);
|
|
2052
1333
|
}
|
|
@@ -2056,6 +1337,142 @@ class InputService {
|
|
|
2056
1337
|
}
|
|
2057
1338
|
}
|
|
2058
1339
|
|
|
1340
|
+
class SelectDiagnosticText {
|
|
1341
|
+
static #pathSelectOptions(resolvedConfig) {
|
|
1342
|
+
const text = [
|
|
1343
|
+
`Root path: ${resolvedConfig.rootPath}`,
|
|
1344
|
+
`Test file match: ${resolvedConfig.testFileMatch.join(", ")}`,
|
|
1345
|
+
];
|
|
1346
|
+
if (resolvedConfig.pathMatch.length > 0) {
|
|
1347
|
+
text.push(`Path match: ${resolvedConfig.pathMatch.join(", ")}`);
|
|
1348
|
+
}
|
|
1349
|
+
return text;
|
|
1350
|
+
}
|
|
1351
|
+
static noTestFilesWereLeft(resolvedConfig) {
|
|
1352
|
+
return [
|
|
1353
|
+
"No test files were left to run using current configuration.",
|
|
1354
|
+
...SelectDiagnosticText.#pathSelectOptions(resolvedConfig),
|
|
1355
|
+
];
|
|
1356
|
+
}
|
|
1357
|
+
static noTestFilesWereSelected(resolvedConfig) {
|
|
1358
|
+
return [
|
|
1359
|
+
"No test files were selected using current configuration.",
|
|
1360
|
+
...SelectDiagnosticText.#pathSelectOptions(resolvedConfig),
|
|
1361
|
+
];
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
class GlobPattern {
|
|
1366
|
+
static #reservedCharacterRegex = /[^\w\s/]/g;
|
|
1367
|
+
static #parse(pattern, usageTarget) {
|
|
1368
|
+
const segments = pattern.split("/");
|
|
1369
|
+
let resultPattern = "\\.";
|
|
1370
|
+
let optionalSegmentCount = 0;
|
|
1371
|
+
for (const segment of segments) {
|
|
1372
|
+
if (segment === ".") {
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
if (segment === "**") {
|
|
1376
|
+
resultPattern += "(\\/(?!(node_modules)(\\/|$))[^./][^/]*)*?";
|
|
1377
|
+
continue;
|
|
1378
|
+
}
|
|
1379
|
+
if (usageTarget === "directories") {
|
|
1380
|
+
resultPattern += "(";
|
|
1381
|
+
optionalSegmentCount++;
|
|
1382
|
+
}
|
|
1383
|
+
resultPattern += "\\/";
|
|
1384
|
+
const segmentPattern = segment.replace(GlobPattern.#reservedCharacterRegex, GlobPattern.#replaceReservedCharacter);
|
|
1385
|
+
if (segmentPattern !== segment) {
|
|
1386
|
+
resultPattern += "(?!(node_modules)(\\/|$))";
|
|
1387
|
+
}
|
|
1388
|
+
resultPattern += segmentPattern;
|
|
1389
|
+
}
|
|
1390
|
+
resultPattern += ")?".repeat(optionalSegmentCount);
|
|
1391
|
+
return resultPattern;
|
|
1392
|
+
}
|
|
1393
|
+
static #replaceReservedCharacter(match, offset) {
|
|
1394
|
+
switch (match) {
|
|
1395
|
+
case "*":
|
|
1396
|
+
return offset === 0 ? "([^./][^/]*)?" : "([^/]*)?";
|
|
1397
|
+
case "?":
|
|
1398
|
+
return offset === 0 ? "[^./]" : "[^/]";
|
|
1399
|
+
default:
|
|
1400
|
+
return `\\${match}`;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
static toRegex(patterns, usageTarget) {
|
|
1404
|
+
const patternText = patterns.map((pattern) => `(${GlobPattern.#parse(pattern, usageTarget)})`).join("|");
|
|
1405
|
+
return new RegExp(`^(${patternText})$`);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
class SelectService {
|
|
1410
|
+
#includeDirectoryRegex;
|
|
1411
|
+
#includeFileRegex;
|
|
1412
|
+
#resolvedConfig;
|
|
1413
|
+
constructor(resolvedConfig) {
|
|
1414
|
+
this.#resolvedConfig = resolvedConfig;
|
|
1415
|
+
this.#includeDirectoryRegex = GlobPattern.toRegex(resolvedConfig.testFileMatch, "directories");
|
|
1416
|
+
this.#includeFileRegex = GlobPattern.toRegex(resolvedConfig.testFileMatch, "files");
|
|
1417
|
+
}
|
|
1418
|
+
#isDirectoryIncluded(directoryPath) {
|
|
1419
|
+
return this.#includeDirectoryRegex.test(directoryPath);
|
|
1420
|
+
}
|
|
1421
|
+
#isFileIncluded(filePath) {
|
|
1422
|
+
if (this.#resolvedConfig.pathMatch.length > 0 &&
|
|
1423
|
+
!this.#resolvedConfig.pathMatch.some((match) => filePath.toLowerCase().includes(match.toLowerCase()))) {
|
|
1424
|
+
return false;
|
|
1425
|
+
}
|
|
1426
|
+
return this.#includeFileRegex.test(filePath);
|
|
1427
|
+
}
|
|
1428
|
+
isTestFile(filePath) {
|
|
1429
|
+
return this.#isFileIncluded(Path.relative(this.#resolvedConfig.rootPath, filePath));
|
|
1430
|
+
}
|
|
1431
|
+
#onDiagnostic(diagnostic) {
|
|
1432
|
+
EventEmitter.dispatch(["select:error", { diagnostics: [diagnostic] }]);
|
|
1433
|
+
}
|
|
1434
|
+
async selectFiles() {
|
|
1435
|
+
const currentPath = ".";
|
|
1436
|
+
const testFilePaths = [];
|
|
1437
|
+
await this.#visitDirectory(currentPath, testFilePaths);
|
|
1438
|
+
if (testFilePaths.length === 0) {
|
|
1439
|
+
this.#onDiagnostic(Diagnostic.error(SelectDiagnosticText.noTestFilesWereSelected(this.#resolvedConfig)));
|
|
1440
|
+
}
|
|
1441
|
+
return testFilePaths.sort();
|
|
1442
|
+
}
|
|
1443
|
+
async #visitDirectory(currentPath, testFilePaths) {
|
|
1444
|
+
const targetPath = Path.join(this.#resolvedConfig.rootPath, currentPath);
|
|
1445
|
+
let entries = [];
|
|
1446
|
+
try {
|
|
1447
|
+
entries = await fs.readdir(targetPath, { withFileTypes: true });
|
|
1448
|
+
}
|
|
1449
|
+
catch {
|
|
1450
|
+
}
|
|
1451
|
+
for (const entry of entries) {
|
|
1452
|
+
let entryMeta;
|
|
1453
|
+
if (entry.isSymbolicLink()) {
|
|
1454
|
+
try {
|
|
1455
|
+
entryMeta = await fs.stat([targetPath, entry.name].join("/"));
|
|
1456
|
+
}
|
|
1457
|
+
catch {
|
|
1458
|
+
continue;
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
else {
|
|
1462
|
+
entryMeta = entry;
|
|
1463
|
+
}
|
|
1464
|
+
const entryPath = [currentPath, entry.name].join("/");
|
|
1465
|
+
if (entryMeta.isDirectory() && this.#isDirectoryIncluded(entryPath)) {
|
|
1466
|
+
await this.#visitDirectory(entryPath, testFilePaths);
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1469
|
+
if (entryMeta.isFile() && this.#isFileIncluded(entryPath)) {
|
|
1470
|
+
testFilePaths.push([targetPath, entry.name].join("/"));
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
|
|
2059
1476
|
class Timer {
|
|
2060
1477
|
#timeout;
|
|
2061
1478
|
clear() {
|
|
@@ -2085,19 +1502,18 @@ class WatchService {
|
|
|
2085
1502
|
this.#selectService = selectService;
|
|
2086
1503
|
this.#watchedTestFiles = new Map(testFiles.map((testFile) => [testFile.path, testFile]));
|
|
2087
1504
|
const onInput = (chunk) => {
|
|
2088
|
-
switch (chunk.
|
|
1505
|
+
switch (chunk.toLowerCase()) {
|
|
2089
1506
|
case "\u0003":
|
|
2090
1507
|
case "\u0004":
|
|
2091
1508
|
case "\u001B":
|
|
2092
|
-
case "
|
|
2093
|
-
case "
|
|
1509
|
+
case "q":
|
|
1510
|
+
case "x": {
|
|
2094
1511
|
this.close();
|
|
2095
1512
|
break;
|
|
2096
1513
|
}
|
|
2097
1514
|
case "\u000D":
|
|
2098
1515
|
case "\u0020":
|
|
2099
|
-
case "
|
|
2100
|
-
case "\u0061": {
|
|
1516
|
+
case "a": {
|
|
2101
1517
|
this.#runAll();
|
|
2102
1518
|
break;
|
|
2103
1519
|
}
|
|
@@ -2148,7 +1564,7 @@ class WatchService {
|
|
|
2148
1564
|
this.#changedTestFiles.delete(filePath);
|
|
2149
1565
|
this.#watchedTestFiles.delete(filePath);
|
|
2150
1566
|
if (this.#watchedTestFiles.size === 0) {
|
|
2151
|
-
this.#onDiagnostic(Diagnostic.error(
|
|
1567
|
+
this.#onDiagnostic(Diagnostic.error(SelectDiagnosticText.noTestFilesWereLeft(this.#resolvedConfig)));
|
|
2152
1568
|
}
|
|
2153
1569
|
};
|
|
2154
1570
|
this.#watchers.push(new Watcher(this.#resolvedConfig.rootPath, onChangedFile, onRemovedFile, { recursive: true }));
|
|
@@ -3224,294 +2640,743 @@ class TestTreeWorker {
|
|
|
3224
2640
|
if (testResult.expectCount.failed > 0) {
|
|
3225
2641
|
EventEmitter.dispatch(["test:fail", { result: testResult }]);
|
|
3226
2642
|
}
|
|
3227
|
-
else {
|
|
3228
|
-
EventEmitter.dispatch(["test:pass", { result: testResult }]);
|
|
2643
|
+
else {
|
|
2644
|
+
EventEmitter.dispatch(["test:pass", { result: testResult }]);
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
class TestFileRunner {
|
|
2650
|
+
#compiler;
|
|
2651
|
+
#collectService;
|
|
2652
|
+
#resolvedConfig;
|
|
2653
|
+
#projectService;
|
|
2654
|
+
constructor(resolvedConfig, compiler) {
|
|
2655
|
+
this.#resolvedConfig = resolvedConfig;
|
|
2656
|
+
this.#compiler = compiler;
|
|
2657
|
+
this.#collectService = new CollectService(compiler);
|
|
2658
|
+
this.#projectService = new ProjectService(compiler);
|
|
2659
|
+
}
|
|
2660
|
+
run(testFile, cancellationToken) {
|
|
2661
|
+
if (cancellationToken?.isCancellationRequested === true) {
|
|
2662
|
+
return;
|
|
2663
|
+
}
|
|
2664
|
+
this.#projectService.openFile(testFile.path, undefined, this.#resolvedConfig.rootPath);
|
|
2665
|
+
const fileResult = new FileResult(testFile);
|
|
2666
|
+
EventEmitter.dispatch(["file:start", { result: fileResult }]);
|
|
2667
|
+
this.#runFile(testFile, fileResult, cancellationToken);
|
|
2668
|
+
EventEmitter.dispatch(["file:end", { result: fileResult }]);
|
|
2669
|
+
this.#projectService.closeFile(testFile.path);
|
|
2670
|
+
}
|
|
2671
|
+
#runFile(testFile, fileResult, cancellationToken) {
|
|
2672
|
+
const languageService = this.#projectService.getLanguageService(testFile.path);
|
|
2673
|
+
if (!languageService) {
|
|
2674
|
+
return;
|
|
2675
|
+
}
|
|
2676
|
+
const syntacticDiagnostics = languageService.getSyntacticDiagnostics(testFile.path);
|
|
2677
|
+
if (syntacticDiagnostics.length > 0) {
|
|
2678
|
+
EventEmitter.dispatch([
|
|
2679
|
+
"file:error",
|
|
2680
|
+
{
|
|
2681
|
+
diagnostics: Diagnostic.fromDiagnostics(syntacticDiagnostics, this.#compiler),
|
|
2682
|
+
result: fileResult,
|
|
2683
|
+
},
|
|
2684
|
+
]);
|
|
2685
|
+
return;
|
|
2686
|
+
}
|
|
2687
|
+
const semanticDiagnostics = languageService.getSemanticDiagnostics(testFile.path);
|
|
2688
|
+
const program = languageService.getProgram();
|
|
2689
|
+
if (!program) {
|
|
2690
|
+
return;
|
|
2691
|
+
}
|
|
2692
|
+
const sourceFile = program.getSourceFile(testFile.path);
|
|
2693
|
+
if (!sourceFile) {
|
|
2694
|
+
return;
|
|
2695
|
+
}
|
|
2696
|
+
const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics);
|
|
2697
|
+
if (testTree.diagnostics.size > 0) {
|
|
2698
|
+
EventEmitter.dispatch([
|
|
2699
|
+
"file:error",
|
|
2700
|
+
{
|
|
2701
|
+
diagnostics: Diagnostic.fromDiagnostics([...testTree.diagnostics], this.#compiler),
|
|
2702
|
+
result: fileResult,
|
|
2703
|
+
},
|
|
2704
|
+
]);
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2707
|
+
const typeChecker = program.getTypeChecker();
|
|
2708
|
+
if (!Expect.assertTypeChecker(typeChecker)) {
|
|
2709
|
+
const text = "The required 'isTypeRelatedTo()' method is missing in the provided type checker.";
|
|
2710
|
+
EventEmitter.dispatch(["file:error", { diagnostics: [Diagnostic.error(text)], result: fileResult }]);
|
|
2711
|
+
return;
|
|
2712
|
+
}
|
|
2713
|
+
const expect = new Expect(this.#compiler, typeChecker);
|
|
2714
|
+
const testTreeWorker = new TestTreeWorker(this.#resolvedConfig, this.#compiler, expect, {
|
|
2715
|
+
cancellationToken,
|
|
2716
|
+
fileResult,
|
|
2717
|
+
hasOnly: testTree.hasOnly,
|
|
2718
|
+
position: testFile.position,
|
|
2719
|
+
});
|
|
2720
|
+
testTreeWorker.visit(testTree.members, 0, undefined);
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
class TaskRunner {
|
|
2725
|
+
#eventEmitter = new EventEmitter();
|
|
2726
|
+
#resolvedConfig;
|
|
2727
|
+
#selectService;
|
|
2728
|
+
#storeService;
|
|
2729
|
+
constructor(resolvedConfig, selectService, storeService) {
|
|
2730
|
+
this.#resolvedConfig = resolvedConfig;
|
|
2731
|
+
this.#selectService = selectService;
|
|
2732
|
+
this.#storeService = storeService;
|
|
2733
|
+
this.#eventEmitter.addHandler(new ResultHandler());
|
|
2734
|
+
}
|
|
2735
|
+
close() {
|
|
2736
|
+
this.#eventEmitter.removeHandlers();
|
|
2737
|
+
}
|
|
2738
|
+
async run(testFiles, cancellationToken = new CancellationToken()) {
|
|
2739
|
+
let cancellationHandler;
|
|
2740
|
+
if (this.#resolvedConfig.failFast) {
|
|
2741
|
+
cancellationHandler = new CancellationHandler(cancellationToken, "failFast");
|
|
2742
|
+
this.#eventEmitter.addHandler(cancellationHandler);
|
|
2743
|
+
}
|
|
2744
|
+
if (this.#resolvedConfig.watch === true) {
|
|
2745
|
+
await this.#watch(testFiles, cancellationToken);
|
|
2746
|
+
}
|
|
2747
|
+
else {
|
|
2748
|
+
await this.#run(testFiles, cancellationToken);
|
|
2749
|
+
}
|
|
2750
|
+
if (cancellationHandler != null) {
|
|
2751
|
+
this.#eventEmitter.removeHandler(cancellationHandler);
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
async #run(testFiles, cancellationToken) {
|
|
2755
|
+
const result = new Result(this.#resolvedConfig, testFiles);
|
|
2756
|
+
EventEmitter.dispatch(["run:start", { result }]);
|
|
2757
|
+
for (const versionTag of this.#resolvedConfig.target) {
|
|
2758
|
+
const targetResult = new TargetResult(versionTag, testFiles);
|
|
2759
|
+
EventEmitter.dispatch(["target:start", { result: targetResult }]);
|
|
2760
|
+
const compiler = await this.#storeService.load(versionTag, cancellationToken);
|
|
2761
|
+
if (compiler) {
|
|
2762
|
+
const testFileRunner = new TestFileRunner(this.#resolvedConfig, compiler);
|
|
2763
|
+
for (const testFile of testFiles) {
|
|
2764
|
+
testFileRunner.run(testFile, cancellationToken);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
EventEmitter.dispatch(["target:end", { result: targetResult }]);
|
|
2768
|
+
}
|
|
2769
|
+
EventEmitter.dispatch(["run:end", { result }]);
|
|
2770
|
+
if (cancellationToken?.reason === "failFast") {
|
|
2771
|
+
cancellationToken.reset();
|
|
3229
2772
|
}
|
|
3230
2773
|
}
|
|
2774
|
+
async #watch(testFiles, cancellationToken) {
|
|
2775
|
+
await this.#run(testFiles, cancellationToken);
|
|
2776
|
+
const runCallback = async (testFiles) => {
|
|
2777
|
+
await this.#run(testFiles, cancellationToken);
|
|
2778
|
+
};
|
|
2779
|
+
const watchModeManager = new WatchService(this.#resolvedConfig, runCallback, this.#selectService, testFiles);
|
|
2780
|
+
cancellationToken?.onCancellationRequested((reason) => {
|
|
2781
|
+
if (reason !== "failFast") {
|
|
2782
|
+
watchModeManager.close();
|
|
2783
|
+
}
|
|
2784
|
+
});
|
|
2785
|
+
await watchModeManager.watch(cancellationToken);
|
|
2786
|
+
}
|
|
3231
2787
|
}
|
|
3232
2788
|
|
|
3233
|
-
class
|
|
3234
|
-
#
|
|
3235
|
-
#
|
|
2789
|
+
class TSTyche {
|
|
2790
|
+
#eventEmitter = new EventEmitter();
|
|
2791
|
+
#outputService;
|
|
3236
2792
|
#resolvedConfig;
|
|
3237
|
-
#
|
|
3238
|
-
|
|
2793
|
+
#selectService;
|
|
2794
|
+
#storeService;
|
|
2795
|
+
#taskRunner;
|
|
2796
|
+
static version = "2.0.0";
|
|
2797
|
+
constructor(resolvedConfig, outputService, selectService, storeService) {
|
|
3239
2798
|
this.#resolvedConfig = resolvedConfig;
|
|
3240
|
-
this.#
|
|
3241
|
-
this.#
|
|
3242
|
-
this.#
|
|
2799
|
+
this.#outputService = outputService;
|
|
2800
|
+
this.#selectService = selectService;
|
|
2801
|
+
this.#storeService = storeService;
|
|
2802
|
+
this.#taskRunner = new TaskRunner(this.#resolvedConfig, this.#selectService, this.#storeService);
|
|
3243
2803
|
}
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
return;
|
|
3247
|
-
}
|
|
3248
|
-
this.#projectService.openFile(testFile.path, undefined, this.#resolvedConfig.rootPath);
|
|
3249
|
-
const fileResult = new FileResult(testFile);
|
|
3250
|
-
EventEmitter.dispatch(["file:start", { result: fileResult }]);
|
|
3251
|
-
this.#runFile(testFile, fileResult, cancellationToken);
|
|
3252
|
-
EventEmitter.dispatch(["file:end", { result: fileResult }]);
|
|
3253
|
-
this.#projectService.closeFile(testFile.path);
|
|
2804
|
+
close() {
|
|
2805
|
+
this.#taskRunner.close();
|
|
3254
2806
|
}
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
if (
|
|
3258
|
-
|
|
2807
|
+
async run(testFiles, cancellationToken = new CancellationToken()) {
|
|
2808
|
+
this.#eventEmitter.addHandler(new RunReporter(this.#resolvedConfig, this.#outputService));
|
|
2809
|
+
if (this.#resolvedConfig.watch === true) {
|
|
2810
|
+
this.#eventEmitter.addHandler(new WatchReporter(this.#outputService));
|
|
3259
2811
|
}
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
EventEmitter.dispatch([
|
|
3263
|
-
"file:error",
|
|
3264
|
-
{
|
|
3265
|
-
diagnostics: Diagnostic.fromDiagnostics(syntacticDiagnostics, this.#compiler),
|
|
3266
|
-
result: fileResult,
|
|
3267
|
-
},
|
|
3268
|
-
]);
|
|
3269
|
-
return;
|
|
2812
|
+
else {
|
|
2813
|
+
this.#eventEmitter.addHandler(new SummaryReporter(this.#outputService));
|
|
3270
2814
|
}
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
2815
|
+
await this.#taskRunner.run(testFiles.map((testFile) => new TestFile(testFile)), cancellationToken);
|
|
2816
|
+
this.#eventEmitter.removeHandlers();
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2820
|
+
class ConfigDiagnosticText {
|
|
2821
|
+
static doubleQuotesExpected() {
|
|
2822
|
+
return "String literal with double quotes expected.";
|
|
2823
|
+
}
|
|
2824
|
+
static expectsListItemType(optionName, optionBrand) {
|
|
2825
|
+
return `Item of the '${optionName}' list must be of type ${optionBrand}.`;
|
|
2826
|
+
}
|
|
2827
|
+
static expectsValue(optionName, optionGroup) {
|
|
2828
|
+
optionName = ConfigDiagnosticText.#optionName(optionName, optionGroup);
|
|
2829
|
+
return `Option '${optionName}' expects a value.`;
|
|
2830
|
+
}
|
|
2831
|
+
static fileDoesNotExist(filePath) {
|
|
2832
|
+
return `The specified path '${filePath}' does not exist.`;
|
|
2833
|
+
}
|
|
2834
|
+
static #optionName(optionName, optionGroup) {
|
|
2835
|
+
switch (optionGroup) {
|
|
2836
|
+
case 2:
|
|
2837
|
+
return `--${optionName}`;
|
|
2838
|
+
case 4:
|
|
2839
|
+
return optionName;
|
|
3275
2840
|
}
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
2841
|
+
}
|
|
2842
|
+
static testFileMatchCannotStartWith(segment) {
|
|
2843
|
+
return [
|
|
2844
|
+
`A test file match pattern cannot start with '${segment}'.`,
|
|
2845
|
+
"The test files are only collected within the 'rootPath' directory.",
|
|
2846
|
+
];
|
|
2847
|
+
}
|
|
2848
|
+
static requiresValueType(optionName, optionBrand, optionGroup) {
|
|
2849
|
+
optionName = ConfigDiagnosticText.#optionName(optionName, optionGroup);
|
|
2850
|
+
return `Option '${optionName}' requires a value of type ${optionBrand}.`;
|
|
2851
|
+
}
|
|
2852
|
+
static unknownOption(optionName) {
|
|
2853
|
+
return `Unknown option '${optionName}'.`;
|
|
2854
|
+
}
|
|
2855
|
+
static versionIsNotSupported(value) {
|
|
2856
|
+
if (value === "current") {
|
|
2857
|
+
return "Cannot use 'current' as a target. Failed to resolve the path to the currently installed TypeScript module.";
|
|
3279
2858
|
}
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
2859
|
+
return `TypeScript version '${value}' is not supported.`;
|
|
2860
|
+
}
|
|
2861
|
+
static watchCannotBeEnabled() {
|
|
2862
|
+
return "The watch mode cannot be enabled in a continuous integration environment.";
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
class OptionDefinitionsMap {
|
|
2867
|
+
static #definitions = [
|
|
2868
|
+
{
|
|
2869
|
+
brand: "string",
|
|
2870
|
+
description: "The path to a TSTyche configuration file.",
|
|
2871
|
+
group: 2,
|
|
2872
|
+
name: "config",
|
|
2873
|
+
},
|
|
2874
|
+
{
|
|
2875
|
+
brand: "boolean",
|
|
2876
|
+
description: "Stop running tests after the first failed assertion.",
|
|
2877
|
+
group: 4 | 2,
|
|
2878
|
+
name: "failFast",
|
|
2879
|
+
},
|
|
2880
|
+
{
|
|
2881
|
+
brand: "bareTrue",
|
|
2882
|
+
description: "Print the list of command line options with brief descriptions and exit.",
|
|
2883
|
+
group: 2,
|
|
2884
|
+
name: "help",
|
|
2885
|
+
},
|
|
2886
|
+
{
|
|
2887
|
+
brand: "bareTrue",
|
|
2888
|
+
description: "Install specified versions of the 'typescript' package and exit.",
|
|
2889
|
+
group: 2,
|
|
2890
|
+
name: "install",
|
|
2891
|
+
},
|
|
2892
|
+
{
|
|
2893
|
+
brand: "bareTrue",
|
|
2894
|
+
description: "Print the list of the selected test files and exit.",
|
|
2895
|
+
group: 2,
|
|
2896
|
+
name: "listFiles",
|
|
2897
|
+
},
|
|
2898
|
+
{
|
|
2899
|
+
brand: "string",
|
|
2900
|
+
description: "Only run tests with matching name.",
|
|
2901
|
+
group: 2,
|
|
2902
|
+
name: "only",
|
|
2903
|
+
},
|
|
2904
|
+
{
|
|
2905
|
+
brand: "bareTrue",
|
|
2906
|
+
description: "Remove all installed versions of the 'typescript' package and exit.",
|
|
2907
|
+
group: 2,
|
|
2908
|
+
name: "prune",
|
|
2909
|
+
},
|
|
2910
|
+
{
|
|
2911
|
+
brand: "string",
|
|
2912
|
+
description: "The path to a directory containing files of a test project.",
|
|
2913
|
+
group: 4,
|
|
2914
|
+
name: "rootPath",
|
|
2915
|
+
},
|
|
2916
|
+
{
|
|
2917
|
+
brand: "bareTrue",
|
|
2918
|
+
description: "Print the resolved configuration and exit.",
|
|
2919
|
+
group: 2,
|
|
2920
|
+
name: "showConfig",
|
|
2921
|
+
},
|
|
2922
|
+
{
|
|
2923
|
+
brand: "string",
|
|
2924
|
+
description: "Skip tests with matching name.",
|
|
2925
|
+
group: 2,
|
|
2926
|
+
name: "skip",
|
|
2927
|
+
},
|
|
2928
|
+
{
|
|
2929
|
+
brand: "list",
|
|
2930
|
+
description: "The list of TypeScript versions to be tested on.",
|
|
2931
|
+
group: 2 | 4,
|
|
2932
|
+
items: {
|
|
2933
|
+
brand: "string",
|
|
2934
|
+
name: "target",
|
|
2935
|
+
pattern: "^([45]\\.[0-9](\\.[0-9])?)|beta|current|latest|next|rc$",
|
|
2936
|
+
},
|
|
2937
|
+
name: "target",
|
|
2938
|
+
},
|
|
2939
|
+
{
|
|
2940
|
+
brand: "list",
|
|
2941
|
+
description: "The list of glob patterns matching the test files.",
|
|
2942
|
+
group: 4,
|
|
2943
|
+
items: {
|
|
2944
|
+
brand: "string",
|
|
2945
|
+
name: "testFileMatch",
|
|
2946
|
+
},
|
|
2947
|
+
name: "testFileMatch",
|
|
2948
|
+
},
|
|
2949
|
+
{
|
|
2950
|
+
brand: "bareTrue",
|
|
2951
|
+
description: "Fetch the 'typescript' package metadata from the registry and exit.",
|
|
2952
|
+
group: 2,
|
|
2953
|
+
name: "update",
|
|
2954
|
+
},
|
|
2955
|
+
{
|
|
2956
|
+
brand: "bareTrue",
|
|
2957
|
+
description: "Print the version number and exit.",
|
|
2958
|
+
group: 2,
|
|
2959
|
+
name: "version",
|
|
2960
|
+
},
|
|
2961
|
+
{
|
|
2962
|
+
brand: "bareTrue",
|
|
2963
|
+
description: "Watch for changes and rerun related test files.",
|
|
2964
|
+
group: 2,
|
|
2965
|
+
name: "watch",
|
|
2966
|
+
},
|
|
2967
|
+
];
|
|
2968
|
+
static for(optionGroup) {
|
|
2969
|
+
const definitionMap = new Map();
|
|
2970
|
+
for (const definition of OptionDefinitionsMap.#definitions) {
|
|
2971
|
+
if (definition.group & optionGroup) {
|
|
2972
|
+
definitionMap.set(definition.name, definition);
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
return definitionMap;
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
class OptionUsageText {
|
|
2980
|
+
#optionGroup;
|
|
2981
|
+
#storeService;
|
|
2982
|
+
constructor(optionGroup, storeService) {
|
|
2983
|
+
this.#optionGroup = optionGroup;
|
|
2984
|
+
this.#storeService = storeService;
|
|
2985
|
+
}
|
|
2986
|
+
async get(optionName, optionBrand) {
|
|
2987
|
+
const usageText = [];
|
|
2988
|
+
switch (optionName) {
|
|
2989
|
+
case "target": {
|
|
2990
|
+
const supportedTags = await this.#storeService.getSupportedTags();
|
|
2991
|
+
const supportedTagsText = `Supported tags: ${["'", supportedTags.join("', '"), "'"].join("")}.`;
|
|
2992
|
+
switch (this.#optionGroup) {
|
|
2993
|
+
case 2: {
|
|
2994
|
+
usageText.push("Value for the '--target' option must be a single tag or a comma separated list.", "Usage examples: '--target 4.9', '--target latest', '--target 4.9,5.3.2,current'.", supportedTagsText);
|
|
2995
|
+
break;
|
|
2996
|
+
}
|
|
2997
|
+
case 4: {
|
|
2998
|
+
usageText.push("Item of the 'target' list must be a supported version tag.", supportedTagsText);
|
|
2999
|
+
break;
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
break;
|
|
3003
|
+
}
|
|
3004
|
+
default:
|
|
3005
|
+
usageText.push(ConfigDiagnosticText.requiresValueType(optionName, optionBrand, this.#optionGroup));
|
|
3290
3006
|
}
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3007
|
+
return usageText;
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
|
|
3011
|
+
class OptionValidator {
|
|
3012
|
+
#onDiagnostic;
|
|
3013
|
+
#optionGroup;
|
|
3014
|
+
#optionUsageText;
|
|
3015
|
+
#storeService;
|
|
3016
|
+
constructor(optionGroup, storeService, onDiagnostic) {
|
|
3017
|
+
this.#optionGroup = optionGroup;
|
|
3018
|
+
this.#storeService = storeService;
|
|
3019
|
+
this.#onDiagnostic = onDiagnostic;
|
|
3020
|
+
this.#optionUsageText = new OptionUsageText(this.#optionGroup, this.#storeService);
|
|
3021
|
+
}
|
|
3022
|
+
async check(optionName, optionValue, optionBrand, origin) {
|
|
3023
|
+
switch (optionName) {
|
|
3024
|
+
case "config":
|
|
3025
|
+
case "rootPath": {
|
|
3026
|
+
if (!existsSync(optionValue)) {
|
|
3027
|
+
this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.fileDoesNotExist(optionValue), origin));
|
|
3028
|
+
}
|
|
3029
|
+
break;
|
|
3030
|
+
}
|
|
3031
|
+
case "target": {
|
|
3032
|
+
if ((await this.#storeService.validateTag(optionValue)) === false) {
|
|
3033
|
+
this.#onDiagnostic(Diagnostic.error([
|
|
3034
|
+
ConfigDiagnosticText.versionIsNotSupported(optionValue),
|
|
3035
|
+
...(await this.#optionUsageText.get(optionName, optionBrand)),
|
|
3036
|
+
], origin));
|
|
3037
|
+
}
|
|
3038
|
+
break;
|
|
3039
|
+
}
|
|
3040
|
+
case "testFileMatch": {
|
|
3041
|
+
for (const segment of ["/", "../"]) {
|
|
3042
|
+
if (optionValue.startsWith(segment)) {
|
|
3043
|
+
this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.testFileMatchCannotStartWith(segment), origin));
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
break;
|
|
3047
|
+
}
|
|
3048
|
+
case "watch": {
|
|
3049
|
+
if (Environment.isCi) {
|
|
3050
|
+
this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.watchCannotBeEnabled(), origin));
|
|
3051
|
+
}
|
|
3052
|
+
break;
|
|
3053
|
+
}
|
|
3296
3054
|
}
|
|
3297
|
-
const expect = new Expect(this.#compiler, typeChecker);
|
|
3298
|
-
const testTreeWorker = new TestTreeWorker(this.#resolvedConfig, this.#compiler, expect, {
|
|
3299
|
-
cancellationToken,
|
|
3300
|
-
fileResult,
|
|
3301
|
-
hasOnly: testTree.hasOnly,
|
|
3302
|
-
position: testFile.position,
|
|
3303
|
-
});
|
|
3304
|
-
testTreeWorker.visit(testTree.members, 0, undefined);
|
|
3305
3055
|
}
|
|
3306
3056
|
}
|
|
3307
3057
|
|
|
3308
|
-
class
|
|
3309
|
-
#
|
|
3310
|
-
#
|
|
3311
|
-
#
|
|
3058
|
+
class CommandLineOptionsWorker {
|
|
3059
|
+
#commandLineOptionDefinitions;
|
|
3060
|
+
#commandLineOptions;
|
|
3061
|
+
#onDiagnostic;
|
|
3062
|
+
#optionGroup = 2;
|
|
3063
|
+
#optionUsageText;
|
|
3064
|
+
#optionValidator;
|
|
3065
|
+
#pathMatch;
|
|
3312
3066
|
#storeService;
|
|
3313
|
-
constructor(
|
|
3314
|
-
this.#
|
|
3315
|
-
this.#
|
|
3067
|
+
constructor(commandLineOptions, pathMatch, storeService, onDiagnostic) {
|
|
3068
|
+
this.#commandLineOptions = commandLineOptions;
|
|
3069
|
+
this.#pathMatch = pathMatch;
|
|
3316
3070
|
this.#storeService = storeService;
|
|
3317
|
-
this.#
|
|
3071
|
+
this.#onDiagnostic = onDiagnostic;
|
|
3072
|
+
this.#commandLineOptionDefinitions = OptionDefinitionsMap.for(this.#optionGroup);
|
|
3073
|
+
this.#optionUsageText = new OptionUsageText(this.#optionGroup, this.#storeService);
|
|
3074
|
+
this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostic);
|
|
3318
3075
|
}
|
|
3319
|
-
|
|
3320
|
-
|
|
3076
|
+
async #onExpectsValue(optionDefinition) {
|
|
3077
|
+
const text = [
|
|
3078
|
+
ConfigDiagnosticText.expectsValue(optionDefinition.name, this.#optionGroup),
|
|
3079
|
+
...(await this.#optionUsageText.get(optionDefinition.name, optionDefinition.brand)),
|
|
3080
|
+
];
|
|
3081
|
+
this.#onDiagnostic(Diagnostic.error(text));
|
|
3321
3082
|
}
|
|
3322
|
-
async
|
|
3323
|
-
let
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3083
|
+
async parse(commandLineArgs) {
|
|
3084
|
+
let index = 0;
|
|
3085
|
+
let arg = commandLineArgs[index];
|
|
3086
|
+
while (arg != null) {
|
|
3087
|
+
index++;
|
|
3088
|
+
if (arg.startsWith("--")) {
|
|
3089
|
+
const optionName = arg.slice(2);
|
|
3090
|
+
const optionDefinition = this.#commandLineOptionDefinitions.get(optionName);
|
|
3091
|
+
if (optionDefinition) {
|
|
3092
|
+
index = await this.#parseOptionValue(commandLineArgs, index, optionDefinition);
|
|
3093
|
+
}
|
|
3094
|
+
else {
|
|
3095
|
+
this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.unknownOption(arg)));
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
else if (arg.startsWith("-")) {
|
|
3099
|
+
this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.unknownOption(arg)));
|
|
3100
|
+
}
|
|
3101
|
+
else {
|
|
3102
|
+
this.#pathMatch.push(Path.normalizeSlashes(arg));
|
|
3103
|
+
}
|
|
3104
|
+
arg = commandLineArgs[index];
|
|
3336
3105
|
}
|
|
3337
3106
|
}
|
|
3338
|
-
async #
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3107
|
+
async #parseOptionValue(commandLineArgs, index, optionDefinition) {
|
|
3108
|
+
let optionValue = this.#resolveOptionValue(commandLineArgs[index]);
|
|
3109
|
+
switch (optionDefinition.brand) {
|
|
3110
|
+
case "bareTrue": {
|
|
3111
|
+
await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
|
|
3112
|
+
this.#commandLineOptions[optionDefinition.name] = true;
|
|
3113
|
+
break;
|
|
3114
|
+
}
|
|
3115
|
+
case "boolean": {
|
|
3116
|
+
await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
|
|
3117
|
+
this.#commandLineOptions[optionDefinition.name] = optionValue !== "false";
|
|
3118
|
+
if (optionValue === "false" || optionValue === "true") {
|
|
3119
|
+
index++;
|
|
3349
3120
|
}
|
|
3121
|
+
break;
|
|
3122
|
+
}
|
|
3123
|
+
case "list": {
|
|
3124
|
+
if (optionValue !== "") {
|
|
3125
|
+
const optionValues = optionValue
|
|
3126
|
+
.split(",")
|
|
3127
|
+
.map((value) => value.trim())
|
|
3128
|
+
.filter((value) => value !== "");
|
|
3129
|
+
for (const optionValue of optionValues) {
|
|
3130
|
+
await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
|
|
3131
|
+
}
|
|
3132
|
+
this.#commandLineOptions[optionDefinition.name] = optionValues;
|
|
3133
|
+
index++;
|
|
3134
|
+
break;
|
|
3135
|
+
}
|
|
3136
|
+
await this.#onExpectsValue(optionDefinition);
|
|
3137
|
+
break;
|
|
3138
|
+
}
|
|
3139
|
+
case "string": {
|
|
3140
|
+
if (optionValue !== "") {
|
|
3141
|
+
if (optionDefinition.name === "config") {
|
|
3142
|
+
optionValue = Path.resolve(optionValue);
|
|
3143
|
+
}
|
|
3144
|
+
await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
|
|
3145
|
+
this.#commandLineOptions[optionDefinition.name] = optionValue;
|
|
3146
|
+
index++;
|
|
3147
|
+
break;
|
|
3148
|
+
}
|
|
3149
|
+
await this.#onExpectsValue(optionDefinition);
|
|
3150
|
+
break;
|
|
3350
3151
|
}
|
|
3351
|
-
EventEmitter.dispatch(["target:end", { result: targetResult }]);
|
|
3352
|
-
}
|
|
3353
|
-
EventEmitter.dispatch(["run:end", { result }]);
|
|
3354
|
-
if (cancellationToken?.reason === "failFast") {
|
|
3355
|
-
cancellationToken.reset();
|
|
3356
3152
|
}
|
|
3153
|
+
return index;
|
|
3357
3154
|
}
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
const runCallback = async (testFiles) => {
|
|
3361
|
-
await this.#run(testFiles, cancellationToken);
|
|
3362
|
-
};
|
|
3363
|
-
const watchModeManager = new WatchService(this.#resolvedConfig, runCallback, this.#selectService, testFiles);
|
|
3364
|
-
cancellationToken?.onCancellationRequested((reason) => {
|
|
3365
|
-
if (reason !== "failFast") {
|
|
3366
|
-
watchModeManager.close();
|
|
3367
|
-
}
|
|
3368
|
-
});
|
|
3369
|
-
await watchModeManager.watch(cancellationToken);
|
|
3155
|
+
#resolveOptionValue(target = "") {
|
|
3156
|
+
return target.startsWith("-") ? "" : target;
|
|
3370
3157
|
}
|
|
3371
3158
|
}
|
|
3372
3159
|
|
|
3373
|
-
class
|
|
3374
|
-
#
|
|
3375
|
-
#
|
|
3376
|
-
#
|
|
3377
|
-
#
|
|
3160
|
+
class ConfigFileOptionsWorker {
|
|
3161
|
+
#compiler;
|
|
3162
|
+
#configFileOptionDefinitions;
|
|
3163
|
+
#configFileOptions;
|
|
3164
|
+
#configFilePath;
|
|
3165
|
+
#onDiagnostic;
|
|
3166
|
+
#optionGroup = 4;
|
|
3167
|
+
#optionValidator;
|
|
3378
3168
|
#storeService;
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
this.#
|
|
3383
|
-
this.#outputService = outputService;
|
|
3384
|
-
this.#selectService = selectService;
|
|
3169
|
+
constructor(compiler, configFileOptions, configFilePath, storeService, onDiagnostic) {
|
|
3170
|
+
this.#compiler = compiler;
|
|
3171
|
+
this.#configFileOptions = configFileOptions;
|
|
3172
|
+
this.#configFilePath = configFilePath;
|
|
3385
3173
|
this.#storeService = storeService;
|
|
3386
|
-
this.#
|
|
3174
|
+
this.#onDiagnostic = onDiagnostic;
|
|
3175
|
+
this.#configFileOptionDefinitions = OptionDefinitionsMap.for(this.#optionGroup);
|
|
3176
|
+
this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostic);
|
|
3387
3177
|
}
|
|
3388
|
-
|
|
3389
|
-
this.#
|
|
3178
|
+
#isDoubleQuotedString(node, sourceFile) {
|
|
3179
|
+
return (node.kind === this.#compiler.SyntaxKind.StringLiteral &&
|
|
3180
|
+
sourceFile.text.slice(this.#skipTrivia(node.pos, sourceFile), node.end).startsWith('"'));
|
|
3390
3181
|
}
|
|
3391
|
-
async
|
|
3392
|
-
this.#
|
|
3393
|
-
if (
|
|
3394
|
-
|
|
3182
|
+
async parse(sourceText) {
|
|
3183
|
+
const sourceFile = this.#compiler.parseJsonText(this.#configFilePath, sourceText);
|
|
3184
|
+
if (sourceFile.parseDiagnostics.length > 0) {
|
|
3185
|
+
for (const diagnostic of Diagnostic.fromDiagnostics(sourceFile.parseDiagnostics, this.#compiler)) {
|
|
3186
|
+
this.#onDiagnostic(diagnostic);
|
|
3187
|
+
}
|
|
3188
|
+
return;
|
|
3189
|
+
}
|
|
3190
|
+
const rootExpression = sourceFile.statements[0]?.expression;
|
|
3191
|
+
if (rootExpression == null || !this.#compiler.isObjectLiteralExpression(rootExpression)) {
|
|
3192
|
+
const origin = new DiagnosticOrigin(0, 0, sourceFile);
|
|
3193
|
+
this.#onDiagnostic(Diagnostic.error("The root value of a configuration file must be an object literal.", origin));
|
|
3194
|
+
return;
|
|
3395
3195
|
}
|
|
3396
|
-
|
|
3397
|
-
this.#
|
|
3196
|
+
for (const property of rootExpression.properties) {
|
|
3197
|
+
if (this.#compiler.isPropertyAssignment(property)) {
|
|
3198
|
+
if (!this.#isDoubleQuotedString(property.name, sourceFile)) {
|
|
3199
|
+
const origin = DiagnosticOrigin.fromJsonNode(property, sourceFile, this.#skipTrivia);
|
|
3200
|
+
this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.doubleQuotesExpected(), origin));
|
|
3201
|
+
continue;
|
|
3202
|
+
}
|
|
3203
|
+
const optionName = this.#resolvePropertyName(property);
|
|
3204
|
+
if (optionName === "$schema") {
|
|
3205
|
+
continue;
|
|
3206
|
+
}
|
|
3207
|
+
const optionDefinition = this.#configFileOptionDefinitions.get(optionName);
|
|
3208
|
+
if (optionDefinition) {
|
|
3209
|
+
this.#configFileOptions[optionDefinition.name] = await this.#parseOptionValue(sourceFile, property.initializer, optionDefinition);
|
|
3210
|
+
}
|
|
3211
|
+
else {
|
|
3212
|
+
const origin = DiagnosticOrigin.fromJsonNode(property, sourceFile, this.#skipTrivia);
|
|
3213
|
+
this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.unknownOption(optionName), origin));
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3398
3216
|
}
|
|
3399
|
-
|
|
3400
|
-
this.#eventEmitter.removeHandlers();
|
|
3217
|
+
return;
|
|
3401
3218
|
}
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
let optionalSegmentCount = 0;
|
|
3410
|
-
for (const segment of segments) {
|
|
3411
|
-
if (segment === ".") {
|
|
3412
|
-
continue;
|
|
3219
|
+
async #parseOptionValue(sourceFile, valueExpression, optionDefinition, isListItem = false) {
|
|
3220
|
+
switch (valueExpression.kind) {
|
|
3221
|
+
case this.#compiler.SyntaxKind.TrueKeyword: {
|
|
3222
|
+
if (optionDefinition.brand === "boolean") {
|
|
3223
|
+
return true;
|
|
3224
|
+
}
|
|
3225
|
+
break;
|
|
3413
3226
|
}
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3227
|
+
case this.#compiler.SyntaxKind.FalseKeyword: {
|
|
3228
|
+
if (optionDefinition.brand === "boolean") {
|
|
3229
|
+
return false;
|
|
3230
|
+
}
|
|
3231
|
+
break;
|
|
3417
3232
|
}
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3233
|
+
case this.#compiler.SyntaxKind.StringLiteral: {
|
|
3234
|
+
if (!this.#isDoubleQuotedString(valueExpression, sourceFile)) {
|
|
3235
|
+
const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
|
|
3236
|
+
this.#onDiagnostic(Diagnostic.error(ConfigDiagnosticText.doubleQuotesExpected(), origin));
|
|
3237
|
+
return;
|
|
3238
|
+
}
|
|
3239
|
+
if (optionDefinition.brand === "string") {
|
|
3240
|
+
let value = valueExpression.text;
|
|
3241
|
+
if (optionDefinition.name === "rootPath") {
|
|
3242
|
+
value = Path.resolve(Path.dirname(this.#configFilePath), value);
|
|
3243
|
+
}
|
|
3244
|
+
const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
|
|
3245
|
+
await this.#optionValidator.check(optionDefinition.name, value, optionDefinition.brand, origin);
|
|
3246
|
+
return value;
|
|
3247
|
+
}
|
|
3248
|
+
break;
|
|
3421
3249
|
}
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3250
|
+
case this.#compiler.SyntaxKind.ArrayLiteralExpression: {
|
|
3251
|
+
if (optionDefinition.brand === "list") {
|
|
3252
|
+
const value = [];
|
|
3253
|
+
for (const element of valueExpression.elements) {
|
|
3254
|
+
value.push(await this.#parseOptionValue(sourceFile, element, optionDefinition.items, true));
|
|
3255
|
+
}
|
|
3256
|
+
return value;
|
|
3257
|
+
}
|
|
3258
|
+
break;
|
|
3426
3259
|
}
|
|
3427
|
-
resultPattern += segmentPattern;
|
|
3428
3260
|
}
|
|
3429
|
-
|
|
3430
|
-
|
|
3261
|
+
const text = isListItem
|
|
3262
|
+
? ConfigDiagnosticText.expectsListItemType(optionDefinition.name, optionDefinition.brand)
|
|
3263
|
+
: ConfigDiagnosticText.requiresValueType(optionDefinition.name, optionDefinition.brand, this.#optionGroup);
|
|
3264
|
+
const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
|
|
3265
|
+
this.#onDiagnostic(Diagnostic.error(text, origin));
|
|
3266
|
+
return;
|
|
3431
3267
|
}
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
return offset === 0 ? "([^./][^/]*)?" : "([^/]*)?";
|
|
3436
|
-
case "?":
|
|
3437
|
-
return offset === 0 ? "[^./]" : "[^/]";
|
|
3438
|
-
default:
|
|
3439
|
-
return `\\${match}`;
|
|
3268
|
+
#resolvePropertyName({ name }) {
|
|
3269
|
+
if ("text" in name) {
|
|
3270
|
+
return name.text;
|
|
3440
3271
|
}
|
|
3272
|
+
return "";
|
|
3441
3273
|
}
|
|
3442
|
-
|
|
3443
|
-
const
|
|
3444
|
-
|
|
3274
|
+
#skipTrivia(position, sourceFile) {
|
|
3275
|
+
const { text } = sourceFile.getSourceFile();
|
|
3276
|
+
while (position < text.length) {
|
|
3277
|
+
if (/\s/.test(text.charAt(position))) {
|
|
3278
|
+
position++;
|
|
3279
|
+
continue;
|
|
3280
|
+
}
|
|
3281
|
+
if (text.charAt(position) === "/") {
|
|
3282
|
+
if (text.charAt(position + 1) === "/") {
|
|
3283
|
+
position += 2;
|
|
3284
|
+
while (position < text.length) {
|
|
3285
|
+
if (text.charAt(position) === "\n") {
|
|
3286
|
+
break;
|
|
3287
|
+
}
|
|
3288
|
+
position++;
|
|
3289
|
+
}
|
|
3290
|
+
continue;
|
|
3291
|
+
}
|
|
3292
|
+
if (text.charAt(position + 1) === "*") {
|
|
3293
|
+
position += 2;
|
|
3294
|
+
while (position < text.length) {
|
|
3295
|
+
if (text.charAt(position) === "*" && text.charAt(position + 1) === "/") {
|
|
3296
|
+
position += 2;
|
|
3297
|
+
break;
|
|
3298
|
+
}
|
|
3299
|
+
position++;
|
|
3300
|
+
}
|
|
3301
|
+
continue;
|
|
3302
|
+
}
|
|
3303
|
+
position++;
|
|
3304
|
+
continue;
|
|
3305
|
+
}
|
|
3306
|
+
break;
|
|
3307
|
+
}
|
|
3308
|
+
return position;
|
|
3445
3309
|
}
|
|
3446
3310
|
}
|
|
3447
3311
|
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
#
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
#
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
}
|
|
3465
|
-
return this.#includeFileRegex.test(filePath);
|
|
3466
|
-
}
|
|
3467
|
-
isTestFile(filePath) {
|
|
3468
|
-
return this.#isFileIncluded(Path.relative(this.#resolvedConfig.rootPath, filePath));
|
|
3312
|
+
const defaultOptions = {
|
|
3313
|
+
failFast: false,
|
|
3314
|
+
rootPath: "./",
|
|
3315
|
+
target: [Environment.typescriptPath == null ? "latest" : "current"],
|
|
3316
|
+
testFileMatch: ["**/*.tst.*", "**/__typetests__/*.test.*", "**/typetests/*.test.*"],
|
|
3317
|
+
};
|
|
3318
|
+
class ConfigService {
|
|
3319
|
+
#commandLineOptions = {};
|
|
3320
|
+
#compiler;
|
|
3321
|
+
#configFileOptions = {};
|
|
3322
|
+
#configFilePath = Path.resolve(defaultOptions.rootPath, "./tstyche.config.json");
|
|
3323
|
+
#pathMatch = [];
|
|
3324
|
+
#storeService;
|
|
3325
|
+
constructor(compiler, storeService) {
|
|
3326
|
+
this.#compiler = compiler;
|
|
3327
|
+
this.#storeService = storeService;
|
|
3469
3328
|
}
|
|
3470
3329
|
#onDiagnostic(diagnostic) {
|
|
3471
|
-
EventEmitter.dispatch(["
|
|
3330
|
+
EventEmitter.dispatch(["config:error", { diagnostics: [diagnostic] }]);
|
|
3472
3331
|
}
|
|
3473
|
-
async
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3332
|
+
async parseCommandLine(commandLineArgs) {
|
|
3333
|
+
this.#commandLineOptions = {};
|
|
3334
|
+
this.#pathMatch = [];
|
|
3335
|
+
const commandLineWorker = new CommandLineOptionsWorker(this.#commandLineOptions, this.#pathMatch, this.#storeService, this.#onDiagnostic);
|
|
3336
|
+
await commandLineWorker.parse(commandLineArgs);
|
|
3337
|
+
if (this.#commandLineOptions.config != null) {
|
|
3338
|
+
this.#configFilePath = this.#commandLineOptions.config;
|
|
3339
|
+
delete this.#commandLineOptions.config;
|
|
3479
3340
|
}
|
|
3480
|
-
return testFilePaths.sort();
|
|
3481
3341
|
}
|
|
3482
|
-
async
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
catch {
|
|
3489
|
-
}
|
|
3490
|
-
for (const entry of entries) {
|
|
3491
|
-
let entryMeta;
|
|
3492
|
-
if (entry.isSymbolicLink()) {
|
|
3493
|
-
try {
|
|
3494
|
-
entryMeta = await fs.stat([targetPath, entry.name].join("/"));
|
|
3495
|
-
}
|
|
3496
|
-
catch {
|
|
3497
|
-
continue;
|
|
3498
|
-
}
|
|
3499
|
-
}
|
|
3500
|
-
else {
|
|
3501
|
-
entryMeta = entry;
|
|
3502
|
-
}
|
|
3503
|
-
const entryPath = [currentPath, entry.name].join("/");
|
|
3504
|
-
if (entryMeta.isDirectory() && this.#isDirectoryIncluded(entryPath)) {
|
|
3505
|
-
await this.#visitDirectory(entryPath, testFilePaths);
|
|
3506
|
-
continue;
|
|
3507
|
-
}
|
|
3508
|
-
if (entryMeta.isFile() && this.#isFileIncluded(entryPath)) {
|
|
3509
|
-
testFilePaths.push([targetPath, entry.name].join("/"));
|
|
3510
|
-
}
|
|
3342
|
+
async readConfigFile() {
|
|
3343
|
+
this.#configFileOptions = {
|
|
3344
|
+
rootPath: Path.dirname(this.#configFilePath),
|
|
3345
|
+
};
|
|
3346
|
+
if (!existsSync(this.#configFilePath)) {
|
|
3347
|
+
return;
|
|
3511
3348
|
}
|
|
3349
|
+
const configFileText = await fs.readFile(this.#configFilePath, {
|
|
3350
|
+
encoding: "utf8",
|
|
3351
|
+
});
|
|
3352
|
+
const configFileWorker = new ConfigFileOptionsWorker(this.#compiler, this.#configFileOptions, this.#configFilePath, this.#storeService, this.#onDiagnostic);
|
|
3353
|
+
await configFileWorker.parse(configFileText);
|
|
3354
|
+
}
|
|
3355
|
+
resolveConfig() {
|
|
3356
|
+
return {
|
|
3357
|
+
...defaultOptions,
|
|
3358
|
+
...this.#configFileOptions,
|
|
3359
|
+
...this.#commandLineOptions,
|
|
3360
|
+
configFilePath: this.#configFilePath,
|
|
3361
|
+
pathMatch: this.#pathMatch,
|
|
3362
|
+
};
|
|
3512
3363
|
}
|
|
3513
3364
|
}
|
|
3514
3365
|
|
|
3366
|
+
var OptionBrand;
|
|
3367
|
+
(function (OptionBrand) {
|
|
3368
|
+
OptionBrand["String"] = "string";
|
|
3369
|
+
OptionBrand["Number"] = "number";
|
|
3370
|
+
OptionBrand["Boolean"] = "boolean";
|
|
3371
|
+
OptionBrand["BareTrue"] = "bareTrue";
|
|
3372
|
+
OptionBrand["List"] = "list";
|
|
3373
|
+
})(OptionBrand || (OptionBrand = {}));
|
|
3374
|
+
var OptionGroup;
|
|
3375
|
+
(function (OptionGroup) {
|
|
3376
|
+
OptionGroup[OptionGroup["CommandLine"] = 2] = "CommandLine";
|
|
3377
|
+
OptionGroup[OptionGroup["ConfigFile"] = 4] = "ConfigFile";
|
|
3378
|
+
})(OptionGroup || (OptionGroup = {}));
|
|
3379
|
+
|
|
3515
3380
|
class StoreDiagnosticText {
|
|
3516
3381
|
static failedToFetchMetadata(registryUrl) {
|
|
3517
3382
|
return `Failed to fetch metadata of the 'typescript' package from '${registryUrl.toString()}'.`;
|
|
@@ -4014,4 +3879,4 @@ class Cli {
|
|
|
4014
3879
|
}
|
|
4015
3880
|
}
|
|
4016
3881
|
|
|
4017
|
-
export { Assertion, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Environment, EventEmitter, ExitCodeHandler, Expect, ExpectResult, FileResult, FileWatcher, InputService, Line, OptionBrand, OptionDefinitionsMap,
|
|
3882
|
+
export { Assertion, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, ConfigDiagnosticText, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Environment, EventEmitter, ExitCodeHandler, Expect, ExpectResult, FileResult, FileWatcher, InputService, Line, OptionBrand, OptionDefinitionsMap, OptionGroup, OutputService, Path, ProjectResult, ProjectService, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, RunReporter, Scribbler, SelectDiagnosticText, SelectService, SetupReporter, StoreService, SummaryReporter, TSTyche, TargetResult, TaskRunner, TestFile, TestMember, TestMemberBrand, TestMemberFlags, TestResult, TestTree, Text, Version, WatchReporter, WatchService, Watcher, addsPackageStepText, defaultOptions, describeNameText, diagnosticText, fileStatusText, fileViewText, formattedText, helpText, summaryText, testNameText, usesCompilerStepText, waitingForFileChangesText, watchUsageText };
|