tstyche 1.0.0-beta.6 → 1.0.0-beta.8
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/CHANGELOG.md +31 -0
- package/README.md +3 -3
- package/build/tstyche.d.ts +160 -90
- package/build/tstyche.js +1145 -1044
- package/package.json +24 -25
package/build/tstyche.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { readFileSync, existsSync, writeFileSync, rmSync } from 'node:fs';
|
|
2
1
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import { existsSync, writeFileSync, rmSync } from 'node:fs';
|
|
5
6
|
import fs from 'node:fs/promises';
|
|
6
|
-
import
|
|
7
|
-
import { spawn } from 'node:child_process';
|
|
7
|
+
import vm from 'node:vm';
|
|
8
8
|
import https from 'node:https';
|
|
9
|
+
import { spawn } from 'node:child_process';
|
|
9
10
|
|
|
10
11
|
class EventEmitter {
|
|
11
12
|
static #handlers = new Set();
|
|
@@ -22,10 +23,42 @@ class EventEmitter {
|
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
class Path {
|
|
27
|
+
static basename(filePath) {
|
|
28
|
+
return Path.normalizeSlashes(path.basename(filePath));
|
|
29
|
+
}
|
|
30
|
+
static dirname(filePath) {
|
|
31
|
+
return Path.normalizeSlashes(path.dirname(filePath));
|
|
32
|
+
}
|
|
33
|
+
static join(...filePaths) {
|
|
34
|
+
return Path.normalizeSlashes(path.join(...filePaths));
|
|
35
|
+
}
|
|
36
|
+
static normalizeSlashes(filePath) {
|
|
37
|
+
if (path.sep === "/") {
|
|
38
|
+
return filePath;
|
|
39
|
+
}
|
|
40
|
+
return filePath.replaceAll("\\", "/");
|
|
41
|
+
}
|
|
42
|
+
static relative(from, to) {
|
|
43
|
+
let relativePath = path.relative(from, to);
|
|
44
|
+
if (!relativePath.startsWith(".")) {
|
|
45
|
+
relativePath = `./${relativePath}`;
|
|
46
|
+
}
|
|
47
|
+
return Path.normalizeSlashes(relativePath);
|
|
48
|
+
}
|
|
49
|
+
static resolve(...filePaths) {
|
|
50
|
+
return Path.normalizeSlashes(path.resolve(...filePaths));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
25
54
|
class Environment {
|
|
55
|
+
static #isTypeScriptInstalled = Environment.#resolveIsTypeScriptInstalled();
|
|
26
56
|
static #noColor = Environment.#resolveNoColor();
|
|
27
|
-
static #storePath = Environment.#
|
|
57
|
+
static #storePath = Environment.#resolveStorePath();
|
|
28
58
|
static #timeout = Environment.#resolveTimeout();
|
|
59
|
+
static get isTypeScriptInstalled() {
|
|
60
|
+
return Environment.#isTypeScriptInstalled;
|
|
61
|
+
}
|
|
29
62
|
static get noColor() {
|
|
30
63
|
return Environment.#noColor;
|
|
31
64
|
}
|
|
@@ -35,18 +68,21 @@ class Environment {
|
|
|
35
68
|
static get timeout() {
|
|
36
69
|
return Environment.#timeout;
|
|
37
70
|
}
|
|
38
|
-
static #normalizePath(value) {
|
|
39
|
-
if (path.sep === "/") {
|
|
40
|
-
return value;
|
|
41
|
-
}
|
|
42
|
-
return value.replace(/\\/g, "/");
|
|
43
|
-
}
|
|
44
71
|
static #parseBoolean(value) {
|
|
45
72
|
if (value != null) {
|
|
46
73
|
return ["1", "on", "t", "true", "y", "yes"].includes(value.toLowerCase());
|
|
47
74
|
}
|
|
48
75
|
return false;
|
|
49
76
|
}
|
|
77
|
+
static #resolveIsTypeScriptInstalled() {
|
|
78
|
+
try {
|
|
79
|
+
createRequire(import.meta.url).resolve("typescript");
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
50
86
|
static #resolveNoColor() {
|
|
51
87
|
if (process.env["TSTYCHE_NO_COLOR"] != null) {
|
|
52
88
|
return Environment.#parseBoolean(process.env["TSTYCHE_NO_COLOR"]);
|
|
@@ -58,18 +94,18 @@ class Environment {
|
|
|
58
94
|
}
|
|
59
95
|
static #resolveStorePath() {
|
|
60
96
|
if (process.env["TSTYCHE_STORE_PATH"] != null) {
|
|
61
|
-
return
|
|
97
|
+
return Path.resolve(process.env["TSTYCHE_STORE_PATH"]);
|
|
62
98
|
}
|
|
63
99
|
if (process.platform === "darwin") {
|
|
64
|
-
return
|
|
100
|
+
return Path.resolve(os.homedir(), "Library", "TSTyche");
|
|
65
101
|
}
|
|
66
102
|
if (process.env["LocalAppData"] != null) {
|
|
67
|
-
return
|
|
103
|
+
return Path.resolve(process.env["LocalAppData"], "TSTyche");
|
|
68
104
|
}
|
|
69
105
|
if (process.env["XDG_DATA_HOME"] != null) {
|
|
70
|
-
return
|
|
106
|
+
return Path.resolve(process.env["XDG_DATA_HOME"], "TSTyche");
|
|
71
107
|
}
|
|
72
|
-
return
|
|
108
|
+
return Path.resolve(os.homedir(), ".local", "share", "TSTyche");
|
|
73
109
|
}
|
|
74
110
|
static #resolveTimeout() {
|
|
75
111
|
if (process.env["TSTYCHE_TIMEOUT"] != null) {
|
|
@@ -105,12 +141,12 @@ class Scribbler {
|
|
|
105
141
|
};
|
|
106
142
|
}
|
|
107
143
|
#escapeSequence(attributes) {
|
|
108
|
-
return ["\
|
|
144
|
+
return ["\u001B[", Array.isArray(attributes) ? attributes.join(";") : attributes, "m"].join("");
|
|
109
145
|
}
|
|
110
146
|
#indentEachLine(lines, level) {
|
|
111
147
|
const indentStep = " ";
|
|
112
148
|
const notEmptyLineRegExp = /^(?!$)/gm;
|
|
113
|
-
return lines.
|
|
149
|
+
return lines.replaceAll(notEmptyLineRegExp, indentStep.repeat(level));
|
|
114
150
|
}
|
|
115
151
|
render(element) {
|
|
116
152
|
if (element != null) {
|
|
@@ -123,7 +159,7 @@ class Scribbler {
|
|
|
123
159
|
}
|
|
124
160
|
if (element.type === "ansi" && !this.#noColor) {
|
|
125
161
|
const flags = typeof element.props?.["escapes"] === "string" || Array.isArray(element.props?.["escapes"])
|
|
126
|
-
? element.props
|
|
162
|
+
? element.props["escapes"]
|
|
127
163
|
: undefined;
|
|
128
164
|
if (flags != null) {
|
|
129
165
|
return this.#escapeSequence(flags);
|
|
@@ -201,6 +237,58 @@ class Line {
|
|
|
201
237
|
}
|
|
202
238
|
}
|
|
203
239
|
|
|
240
|
+
class Logger {
|
|
241
|
+
#noColor;
|
|
242
|
+
#scribbler;
|
|
243
|
+
#stderr;
|
|
244
|
+
#stdout;
|
|
245
|
+
constructor(options) {
|
|
246
|
+
this.#noColor = options?.noColor ?? Environment.noColor;
|
|
247
|
+
this.#stderr = options?.stderr ?? process.stderr;
|
|
248
|
+
this.#stdout = options?.stdout ?? process.stdout;
|
|
249
|
+
this.#scribbler = new Scribbler({ noColors: this.#noColor });
|
|
250
|
+
}
|
|
251
|
+
eraseLastLine() {
|
|
252
|
+
if (!this.isInteractive()) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
this.#stdout.write("\u001B[1A\u001B[0K");
|
|
256
|
+
}
|
|
257
|
+
isInteractive() {
|
|
258
|
+
if ("isTTY" in this.#stdout && typeof this.#stdout.isTTY === "boolean") {
|
|
259
|
+
return this.#stdout.isTTY;
|
|
260
|
+
}
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
#write(stream, body) {
|
|
264
|
+
const elements = Array.isArray(body) ? body : [body];
|
|
265
|
+
for (const element of elements) {
|
|
266
|
+
if (element.$$typeof !== Symbol.for("tstyche:scribbler")) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
stream.write(this.#scribbler.render(element));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
writeError(body) {
|
|
273
|
+
this.#write(this.#stderr, body);
|
|
274
|
+
}
|
|
275
|
+
writeMessage(body) {
|
|
276
|
+
this.#write(this.#stdout, body);
|
|
277
|
+
}
|
|
278
|
+
writeWarning(body) {
|
|
279
|
+
this.#write(this.#stderr, body);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
class Reporter {
|
|
284
|
+
resolvedConfig;
|
|
285
|
+
logger;
|
|
286
|
+
constructor(resolvedConfig) {
|
|
287
|
+
this.resolvedConfig = resolvedConfig;
|
|
288
|
+
this.logger = new Logger();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
204
292
|
function addsPackageStepText(compilerVersion, installationPath) {
|
|
205
293
|
return (Scribbler.createElement(Line, null,
|
|
206
294
|
Scribbler.createElement(Text, { color: "90" }, "adds"),
|
|
@@ -215,23 +303,6 @@ function describeNameText(name, indent = 0) {
|
|
|
215
303
|
return Scribbler.createElement(Line, { indent: indent + 1 }, name);
|
|
216
304
|
}
|
|
217
305
|
|
|
218
|
-
class RelativePathText {
|
|
219
|
-
props;
|
|
220
|
-
constructor(props) {
|
|
221
|
-
this.props = props;
|
|
222
|
-
}
|
|
223
|
-
render() {
|
|
224
|
-
const relativePath = path.relative("", this.props.to).replace(/\\/g, "/");
|
|
225
|
-
if (relativePath === "") {
|
|
226
|
-
return Scribbler.createElement(Text, null, "./");
|
|
227
|
-
}
|
|
228
|
-
return (Scribbler.createElement(Text, null,
|
|
229
|
-
relativePath.startsWith(".") ? "" : "./",
|
|
230
|
-
relativePath,
|
|
231
|
-
this.props.isDirectory === true ? "/" : ""));
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
306
|
class CodeSpanText {
|
|
236
307
|
props;
|
|
237
308
|
constructor(props) {
|
|
@@ -244,12 +315,14 @@ class CodeSpanText {
|
|
|
244
315
|
const lastLine = Math.min(firstLine + 5, lastLineInFile);
|
|
245
316
|
const lineNumberMaxWidth = `${lastLine + 1}`.length;
|
|
246
317
|
const codeSpan = [];
|
|
247
|
-
for (let
|
|
248
|
-
const lineStart = this.props.file.getPositionOfLineAndCharacter(
|
|
249
|
-
const lineEnd =
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
318
|
+
for (let index = firstLine; index <= lastLine; index++) {
|
|
319
|
+
const lineStart = this.props.file.getPositionOfLineAndCharacter(index, 0);
|
|
320
|
+
const lineEnd = index === lastLineInFile
|
|
321
|
+
? this.props.file.text.length
|
|
322
|
+
: this.props.file.getPositionOfLineAndCharacter(index + 1, 0);
|
|
323
|
+
const lineNumberText = String(index + 1);
|
|
324
|
+
const lineText = this.props.file.text.slice(lineStart, lineEnd).trimEnd().replaceAll("\t", " ");
|
|
325
|
+
if (index === markedLine) {
|
|
253
326
|
codeSpan.push(Scribbler.createElement(Line, null,
|
|
254
327
|
Scribbler.createElement(Text, { color: "31" }, ">"),
|
|
255
328
|
" ",
|
|
@@ -257,8 +330,7 @@ class CodeSpanText {
|
|
|
257
330
|
" ",
|
|
258
331
|
Scribbler.createElement(Text, { color: "90" }, "|"),
|
|
259
332
|
" ",
|
|
260
|
-
lineText)
|
|
261
|
-
codeSpan.push(Scribbler.createElement(Line, null,
|
|
333
|
+
lineText), Scribbler.createElement(Line, null,
|
|
262
334
|
" ".repeat(lineNumberMaxWidth + 3),
|
|
263
335
|
Scribbler.createElement(Text, { color: "90" }, "|"),
|
|
264
336
|
" ".repeat(markedCharacter + 1),
|
|
@@ -281,8 +353,7 @@ class CodeSpanText {
|
|
|
281
353
|
" ".repeat(lineNumberMaxWidth + 5),
|
|
282
354
|
Scribbler.createElement(Text, { color: "90" }, "at"),
|
|
283
355
|
" ",
|
|
284
|
-
Scribbler.createElement(Text, { color: "36" },
|
|
285
|
-
Scribbler.createElement(RelativePathText, { to: this.props.file.fileName })),
|
|
356
|
+
Scribbler.createElement(Text, { color: "36" }, Path.relative("", this.props.file.fileName)),
|
|
286
357
|
Scribbler.createElement(Text, { color: "90" },
|
|
287
358
|
":",
|
|
288
359
|
String(markedLine + 1),
|
|
@@ -343,10 +414,13 @@ class FileNameText {
|
|
|
343
414
|
this.props = props;
|
|
344
415
|
}
|
|
345
416
|
render() {
|
|
417
|
+
const relativePath = Path.relative("", this.props.filePath);
|
|
418
|
+
const lastPathSeparator = relativePath.lastIndexOf("/");
|
|
419
|
+
const directoryNameText = relativePath.slice(0, lastPathSeparator + 1);
|
|
420
|
+
const fileNameText = relativePath.slice(lastPathSeparator + 1);
|
|
346
421
|
return (Scribbler.createElement(Text, null,
|
|
347
|
-
Scribbler.createElement(Text, { color: "90" },
|
|
348
|
-
|
|
349
|
-
path.basename(this.props.filePath)));
|
|
422
|
+
Scribbler.createElement(Text, { color: "90" }, directoryNameText),
|
|
423
|
+
fileNameText));
|
|
350
424
|
}
|
|
351
425
|
}
|
|
352
426
|
function fileStatusText(status, testFile) {
|
|
@@ -378,6 +452,160 @@ function fileViewText(lines, addEmptyFinalLine) {
|
|
|
378
452
|
addEmptyFinalLine ? Scribbler.createElement(Line, null) : undefined));
|
|
379
453
|
}
|
|
380
454
|
|
|
455
|
+
class JsonText {
|
|
456
|
+
props;
|
|
457
|
+
constructor(props) {
|
|
458
|
+
this.props = props;
|
|
459
|
+
}
|
|
460
|
+
render() {
|
|
461
|
+
return Scribbler.createElement(Line, null, JSON.stringify(this.#sortObject(this.props.input), null, 2));
|
|
462
|
+
}
|
|
463
|
+
#sortObject(target) {
|
|
464
|
+
if (Array.isArray(target)) {
|
|
465
|
+
return target;
|
|
466
|
+
}
|
|
467
|
+
return Object.keys(target)
|
|
468
|
+
.sort()
|
|
469
|
+
.reduce((result, key) => {
|
|
470
|
+
result[key] = target[key];
|
|
471
|
+
return result;
|
|
472
|
+
}, {});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
function formattedText(input) {
|
|
476
|
+
if (typeof input === "string") {
|
|
477
|
+
return Scribbler.createElement(Line, null, input);
|
|
478
|
+
}
|
|
479
|
+
return Scribbler.createElement(JsonText, { input: input });
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const usageExamples = [
|
|
483
|
+
["tstyche", "Run all tests."],
|
|
484
|
+
["tstyche path/to/first.test.ts", "Only run the test files with matching path."],
|
|
485
|
+
["tstyche --target 4.7,5.3.2,current", "Test on all specified versions of TypeScript."],
|
|
486
|
+
];
|
|
487
|
+
class HintText {
|
|
488
|
+
props;
|
|
489
|
+
constructor(props) {
|
|
490
|
+
this.props = props;
|
|
491
|
+
}
|
|
492
|
+
render() {
|
|
493
|
+
return (Scribbler.createElement(Text, { indent: 1, color: "90" }, this.props.children));
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
class HelpHeaderText {
|
|
497
|
+
props;
|
|
498
|
+
constructor(props) {
|
|
499
|
+
this.props = props;
|
|
500
|
+
}
|
|
501
|
+
render() {
|
|
502
|
+
const hint = (Scribbler.createElement(HintText, null,
|
|
503
|
+
Scribbler.createElement(Text, null, this.props.tstycheVersion)));
|
|
504
|
+
return (Scribbler.createElement(Line, null,
|
|
505
|
+
Scribbler.createElement(Text, null, "The TSTyche Type Test Runner"),
|
|
506
|
+
hint));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
class CommandText {
|
|
510
|
+
props;
|
|
511
|
+
constructor(props) {
|
|
512
|
+
this.props = props;
|
|
513
|
+
}
|
|
514
|
+
render() {
|
|
515
|
+
let hint;
|
|
516
|
+
if (this.props.hint != null) {
|
|
517
|
+
hint = Scribbler.createElement(HintText, null, this.props.hint);
|
|
518
|
+
}
|
|
519
|
+
return (Scribbler.createElement(Line, { indent: 1 },
|
|
520
|
+
Scribbler.createElement(Text, { color: "34" }, this.props.text),
|
|
521
|
+
hint));
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
class OptionDescriptionText {
|
|
525
|
+
props;
|
|
526
|
+
constructor(props) {
|
|
527
|
+
this.props = props;
|
|
528
|
+
}
|
|
529
|
+
render() {
|
|
530
|
+
return Scribbler.createElement(Line, { indent: 1 }, this.props.text);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
class CommandLineUsageText {
|
|
534
|
+
render() {
|
|
535
|
+
const usageText = usageExamples.map(([commandText, descriptionText]) => (Scribbler.createElement(Text, null,
|
|
536
|
+
Scribbler.createElement(CommandText, { text: commandText }),
|
|
537
|
+
Scribbler.createElement(OptionDescriptionText, { text: descriptionText }),
|
|
538
|
+
Scribbler.createElement(Line, null))));
|
|
539
|
+
return Scribbler.createElement(Text, null, usageText);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
class CommandLineOptionNameText {
|
|
543
|
+
props;
|
|
544
|
+
constructor(props) {
|
|
545
|
+
this.props = props;
|
|
546
|
+
}
|
|
547
|
+
render() {
|
|
548
|
+
return Scribbler.createElement(Text, null,
|
|
549
|
+
"--",
|
|
550
|
+
this.props.text);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
class CommandLineOptionHintText {
|
|
554
|
+
props;
|
|
555
|
+
constructor(props) {
|
|
556
|
+
this.props = props;
|
|
557
|
+
}
|
|
558
|
+
render() {
|
|
559
|
+
if (this.props.definition.brand === "list") {
|
|
560
|
+
return (Scribbler.createElement(Text, null,
|
|
561
|
+
this.props.definition.brand,
|
|
562
|
+
" of ",
|
|
563
|
+
this.props.definition.items.brand,
|
|
564
|
+
"s"));
|
|
565
|
+
}
|
|
566
|
+
return Scribbler.createElement(Text, null, this.props.definition.brand);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
class CommandLineOptionsText {
|
|
570
|
+
props;
|
|
571
|
+
constructor(props) {
|
|
572
|
+
this.props = props;
|
|
573
|
+
}
|
|
574
|
+
render() {
|
|
575
|
+
const definitions = [...this.props.optionDefinitions.values()];
|
|
576
|
+
const optionsText = definitions.map((definition) => {
|
|
577
|
+
let hint;
|
|
578
|
+
if (definition.brand !== "true") {
|
|
579
|
+
hint = Scribbler.createElement(CommandLineOptionHintText, { definition: definition });
|
|
580
|
+
}
|
|
581
|
+
return (Scribbler.createElement(Text, null,
|
|
582
|
+
Scribbler.createElement(CommandText, { text: Scribbler.createElement(CommandLineOptionNameText, { text: definition.name }), hint: hint }),
|
|
583
|
+
Scribbler.createElement(OptionDescriptionText, { text: definition.description }),
|
|
584
|
+
Scribbler.createElement(Line, null)));
|
|
585
|
+
});
|
|
586
|
+
return (Scribbler.createElement(Text, null,
|
|
587
|
+
Scribbler.createElement(Line, null, "Command Line Options"),
|
|
588
|
+
Scribbler.createElement(Line, null),
|
|
589
|
+
optionsText));
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
class HelpFooterText {
|
|
593
|
+
render() {
|
|
594
|
+
return Scribbler.createElement(Line, null, "To learn more, visit https://tstyche.org");
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
function helpText(optionDefinitions, tstycheVersion) {
|
|
598
|
+
return (Scribbler.createElement(Text, null,
|
|
599
|
+
Scribbler.createElement(HelpHeaderText, { tstycheVersion: tstycheVersion }),
|
|
600
|
+
Scribbler.createElement(Line, null),
|
|
601
|
+
Scribbler.createElement(CommandLineUsageText, null),
|
|
602
|
+
Scribbler.createElement(Line, null),
|
|
603
|
+
Scribbler.createElement(CommandLineOptionsText, { optionDefinitions: optionDefinitions }),
|
|
604
|
+
Scribbler.createElement(Line, null),
|
|
605
|
+
Scribbler.createElement(HelpFooterText, null),
|
|
606
|
+
Scribbler.createElement(Line, null)));
|
|
607
|
+
}
|
|
608
|
+
|
|
381
609
|
class RowText {
|
|
382
610
|
props;
|
|
383
611
|
constructor(props) {
|
|
@@ -550,11 +778,14 @@ class ProjectNameText {
|
|
|
550
778
|
render() {
|
|
551
779
|
return (Scribbler.createElement(Text, { color: "90" },
|
|
552
780
|
" with ",
|
|
553
|
-
|
|
781
|
+
Path.relative("", this.props.filePath)));
|
|
554
782
|
}
|
|
555
783
|
}
|
|
556
784
|
function usesCompilerStepText(compilerVersion, tsconfigFilePath, options) {
|
|
557
|
-
|
|
785
|
+
let projectPathText;
|
|
786
|
+
if (tsconfigFilePath != null) {
|
|
787
|
+
projectPathText = Scribbler.createElement(ProjectNameText, { filePath: tsconfigFilePath });
|
|
788
|
+
}
|
|
558
789
|
return (Scribbler.createElement(Text, null,
|
|
559
790
|
options?.prependEmptyLine === true ? Scribbler.createElement(Line, null) : undefined,
|
|
560
791
|
Scribbler.createElement(Line, null,
|
|
@@ -565,73 +796,21 @@ function usesCompilerStepText(compilerVersion, tsconfigFilePath, options) {
|
|
|
565
796
|
Scribbler.createElement(Line, null)));
|
|
566
797
|
}
|
|
567
798
|
|
|
568
|
-
class
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
this.#stdout.write("\x1b[1A\x1b[0K");
|
|
584
|
-
}
|
|
585
|
-
isInteractive() {
|
|
586
|
-
if ("isTTY" in this.#stdout && typeof this.#stdout.isTTY === "boolean") {
|
|
587
|
-
return this.#stdout.isTTY;
|
|
588
|
-
}
|
|
589
|
-
return false;
|
|
590
|
-
}
|
|
591
|
-
#write(stream, body) {
|
|
592
|
-
const elements = Array.isArray(body) ? body : [body];
|
|
593
|
-
for (const element of elements) {
|
|
594
|
-
if (element.$$typeof !== Symbol.for("tstyche:scribbler")) {
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
stream.write(this.#scribbler.render(element));
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
writeError(body) {
|
|
601
|
-
this.#write(this.#stderr, body);
|
|
602
|
-
}
|
|
603
|
-
writeMessage(body) {
|
|
604
|
-
this.#write(this.#stdout, body);
|
|
605
|
-
}
|
|
606
|
-
writeWarning(body) {
|
|
607
|
-
this.#write(this.#stderr, body);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
class Reporter {
|
|
612
|
-
resolvedConfig;
|
|
613
|
-
logger;
|
|
614
|
-
constructor(resolvedConfig) {
|
|
615
|
-
this.resolvedConfig = resolvedConfig;
|
|
616
|
-
this.logger = new Logger();
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
class SummaryReporter extends Reporter {
|
|
621
|
-
handleEvent([eventName, payload]) {
|
|
622
|
-
switch (eventName) {
|
|
623
|
-
case "end":
|
|
624
|
-
this.logger.writeMessage(summaryText({
|
|
625
|
-
duration: payload.result.timing.duration,
|
|
626
|
-
expectCount: payload.result.expectCount,
|
|
627
|
-
fileCount: payload.result.fileCount,
|
|
628
|
-
onlyMatch: payload.result.resolvedConfig.only,
|
|
629
|
-
pathMatch: payload.result.resolvedConfig.pathMatch,
|
|
630
|
-
skipMatch: payload.result.resolvedConfig.skip,
|
|
631
|
-
targetCount: payload.result.targetCount,
|
|
632
|
-
testCount: payload.result.testCount,
|
|
633
|
-
}));
|
|
634
|
-
break;
|
|
799
|
+
class SummaryReporter extends Reporter {
|
|
800
|
+
handleEvent([eventName, payload]) {
|
|
801
|
+
switch (eventName) {
|
|
802
|
+
case "end":
|
|
803
|
+
this.logger.writeMessage(summaryText({
|
|
804
|
+
duration: payload.result.timing.duration,
|
|
805
|
+
expectCount: payload.result.expectCount,
|
|
806
|
+
fileCount: payload.result.fileCount,
|
|
807
|
+
onlyMatch: payload.result.resolvedConfig.only,
|
|
808
|
+
pathMatch: payload.result.resolvedConfig.pathMatch,
|
|
809
|
+
skipMatch: payload.result.resolvedConfig.skip,
|
|
810
|
+
targetCount: payload.result.targetCount,
|
|
811
|
+
testCount: payload.result.testCount,
|
|
812
|
+
}));
|
|
813
|
+
break;
|
|
635
814
|
}
|
|
636
815
|
}
|
|
637
816
|
}
|
|
@@ -651,6 +830,9 @@ class Diagnostic {
|
|
|
651
830
|
if (options.code != null) {
|
|
652
831
|
this.code = options.code;
|
|
653
832
|
}
|
|
833
|
+
if (options.origin != null) {
|
|
834
|
+
this.origin = options.origin;
|
|
835
|
+
}
|
|
654
836
|
if (options.related != null) {
|
|
655
837
|
this.related = options.related;
|
|
656
838
|
}
|
|
@@ -727,10 +909,6 @@ class FileViewService {
|
|
|
727
909
|
return this.#messages.length > 0;
|
|
728
910
|
}
|
|
729
911
|
addMessage(message) {
|
|
730
|
-
if (Array.isArray(message)) {
|
|
731
|
-
this.#messages.push(...message);
|
|
732
|
-
return;
|
|
733
|
-
}
|
|
734
912
|
this.#messages.push(message);
|
|
735
913
|
}
|
|
736
914
|
addTest(status, name) {
|
|
@@ -1196,53 +1374,39 @@ class TestMember {
|
|
|
1196
1374
|
parent;
|
|
1197
1375
|
flags;
|
|
1198
1376
|
compiler;
|
|
1199
|
-
diagnostics;
|
|
1377
|
+
diagnostics = new Set();
|
|
1200
1378
|
members = [];
|
|
1201
|
-
name;
|
|
1202
|
-
typeChecker;
|
|
1379
|
+
name = "";
|
|
1203
1380
|
constructor(brand, node, parent, flags) {
|
|
1204
1381
|
this.brand = brand;
|
|
1205
1382
|
this.node = node;
|
|
1206
1383
|
this.parent = parent;
|
|
1207
1384
|
this.flags = flags;
|
|
1208
1385
|
this.compiler = parent.compiler;
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
this.name = this.#resolveName(node);
|
|
1212
|
-
}
|
|
1213
|
-
get ancestorNames() {
|
|
1214
|
-
const ancestorNames = [];
|
|
1215
|
-
let ancestor = this.parent;
|
|
1216
|
-
while ("name" in ancestor) {
|
|
1217
|
-
ancestorNames.unshift(ancestor.name);
|
|
1218
|
-
ancestor = ancestor.parent;
|
|
1386
|
+
if (node.arguments[0] != null && this.compiler.isStringLiteralLike(node.arguments[0])) {
|
|
1387
|
+
this.name = node.arguments[0].text;
|
|
1219
1388
|
}
|
|
1220
|
-
return ancestorNames;
|
|
1221
|
-
}
|
|
1222
|
-
#mapDiagnostics(node, parent) {
|
|
1223
|
-
const mapped = [];
|
|
1224
|
-
const unmapped = [];
|
|
1225
1389
|
if (node.arguments[1] != null &&
|
|
1226
1390
|
parent.compiler.isFunctionLike(node.arguments[1]) &&
|
|
1227
1391
|
parent.compiler.isBlock(node.arguments[1].body)) {
|
|
1228
1392
|
const blockStart = node.arguments[1].body.getStart();
|
|
1229
1393
|
const blockEnd = node.arguments[1].body.getEnd();
|
|
1230
|
-
parent.diagnostics
|
|
1394
|
+
for (const diagnostic of parent.diagnostics) {
|
|
1231
1395
|
if (diagnostic.start != null && diagnostic.start >= blockStart && diagnostic.start <= blockEnd) {
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
else {
|
|
1235
|
-
unmapped.push(diagnostic);
|
|
1396
|
+
this.diagnostics.add(diagnostic);
|
|
1397
|
+
parent.diagnostics.delete(diagnostic);
|
|
1236
1398
|
}
|
|
1237
|
-
}
|
|
1238
|
-
parent.diagnostics = unmapped;
|
|
1399
|
+
}
|
|
1239
1400
|
}
|
|
1240
|
-
return mapped;
|
|
1241
1401
|
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1402
|
+
get ancestorNames() {
|
|
1403
|
+
const ancestorNames = [];
|
|
1404
|
+
let ancestor = this.parent;
|
|
1405
|
+
while ("name" in ancestor) {
|
|
1406
|
+
ancestorNames.unshift(ancestor.name);
|
|
1407
|
+
ancestor = ancestor.parent;
|
|
1408
|
+
}
|
|
1409
|
+
return ancestorNames;
|
|
1246
1410
|
}
|
|
1247
1411
|
validate() {
|
|
1248
1412
|
const diagnostics = [];
|
|
@@ -1282,14 +1446,24 @@ class Assertion extends TestMember {
|
|
|
1282
1446
|
modifierNode;
|
|
1283
1447
|
notNode;
|
|
1284
1448
|
isNot;
|
|
1285
|
-
name = "";
|
|
1286
1449
|
constructor(brand, node, parent, flags, matcherNode, modifierNode, notNode) {
|
|
1287
1450
|
super(brand, node, parent, flags);
|
|
1288
1451
|
this.matcherNode = matcherNode;
|
|
1289
1452
|
this.modifierNode = modifierNode;
|
|
1290
1453
|
this.notNode = notNode;
|
|
1291
|
-
this.diagnostics = this.#mapDiagnostics();
|
|
1292
1454
|
this.isNot = notNode ? true : false;
|
|
1455
|
+
const argStart = this.source[0]?.getStart();
|
|
1456
|
+
const argEnd = this.source[0]?.getEnd();
|
|
1457
|
+
for (const diagnostic of parent.diagnostics) {
|
|
1458
|
+
if (diagnostic.start != null &&
|
|
1459
|
+
argStart != null &&
|
|
1460
|
+
argEnd != null &&
|
|
1461
|
+
diagnostic.start >= argStart &&
|
|
1462
|
+
diagnostic.start <= argEnd) {
|
|
1463
|
+
this.diagnostics.add(diagnostic);
|
|
1464
|
+
parent.diagnostics.delete(diagnostic);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1293
1467
|
}
|
|
1294
1468
|
get ancestorNames() {
|
|
1295
1469
|
const ancestorNames = [];
|
|
@@ -1304,80 +1478,20 @@ class Assertion extends TestMember {
|
|
|
1304
1478
|
get matcherName() {
|
|
1305
1479
|
return this.matcherNode.expression.name;
|
|
1306
1480
|
}
|
|
1307
|
-
get
|
|
1481
|
+
get source() {
|
|
1482
|
+
if (this.node.typeArguments != null) {
|
|
1483
|
+
return this.node.typeArguments;
|
|
1484
|
+
}
|
|
1308
1485
|
return this.node.arguments;
|
|
1309
1486
|
}
|
|
1310
|
-
get
|
|
1311
|
-
if (
|
|
1312
|
-
return;
|
|
1313
|
-
}
|
|
1314
|
-
if (this.node.typeArguments?.[0]) {
|
|
1315
|
-
return {
|
|
1316
|
-
source: 1,
|
|
1317
|
-
type: this.typeChecker.getTypeFromTypeNode(this.node.typeArguments[0]),
|
|
1318
|
-
};
|
|
1319
|
-
}
|
|
1320
|
-
if (this.node.arguments[0]) {
|
|
1321
|
-
return {
|
|
1322
|
-
source: 0,
|
|
1323
|
-
type: this.typeChecker.getTypeAtLocation(this.node.arguments[0]),
|
|
1324
|
-
};
|
|
1487
|
+
get target() {
|
|
1488
|
+
if (this.matcherNode.typeArguments != null) {
|
|
1489
|
+
return this.matcherNode.typeArguments;
|
|
1325
1490
|
}
|
|
1326
|
-
return;
|
|
1327
|
-
}
|
|
1328
|
-
get targetArguments() {
|
|
1329
1491
|
return this.matcherNode.arguments;
|
|
1330
1492
|
}
|
|
1331
|
-
get targetType() {
|
|
1332
|
-
if (!this.typeChecker) {
|
|
1333
|
-
return;
|
|
1334
|
-
}
|
|
1335
|
-
if (this.matcherNode.typeArguments?.[0]) {
|
|
1336
|
-
return {
|
|
1337
|
-
source: 1,
|
|
1338
|
-
type: this.typeChecker.getTypeFromTypeNode(this.matcherNode.typeArguments[0]),
|
|
1339
|
-
};
|
|
1340
|
-
}
|
|
1341
|
-
if (this.matcherNode.arguments[0]) {
|
|
1342
|
-
return {
|
|
1343
|
-
source: 0,
|
|
1344
|
-
type: this.typeChecker.getTypeAtLocation(this.matcherNode.arguments[0]),
|
|
1345
|
-
};
|
|
1346
|
-
}
|
|
1347
|
-
return;
|
|
1348
|
-
}
|
|
1349
|
-
#mapDiagnostics() {
|
|
1350
|
-
const mapped = [];
|
|
1351
|
-
const unmapped = [];
|
|
1352
|
-
let argStart;
|
|
1353
|
-
let argEnd;
|
|
1354
|
-
if (this.node.typeArguments?.[0]) {
|
|
1355
|
-
argStart = this.node.typeArguments[0].getStart();
|
|
1356
|
-
argEnd = this.node.typeArguments[0].getEnd();
|
|
1357
|
-
}
|
|
1358
|
-
else if (this.node.arguments[0]) {
|
|
1359
|
-
argStart = this.node.arguments[0].getStart();
|
|
1360
|
-
argEnd = this.node.arguments[0].getEnd();
|
|
1361
|
-
}
|
|
1362
|
-
this.parent.diagnostics.forEach((diagnostic) => {
|
|
1363
|
-
if (diagnostic.start != null && diagnostic.start >= argStart && diagnostic.start <= argEnd) {
|
|
1364
|
-
mapped.push(diagnostic);
|
|
1365
|
-
}
|
|
1366
|
-
else {
|
|
1367
|
-
unmapped.push(diagnostic);
|
|
1368
|
-
}
|
|
1369
|
-
});
|
|
1370
|
-
this.parent.diagnostics = unmapped;
|
|
1371
|
-
return mapped;
|
|
1372
|
-
}
|
|
1373
1493
|
}
|
|
1374
1494
|
|
|
1375
|
-
var AssertionSource;
|
|
1376
|
-
(function (AssertionSource) {
|
|
1377
|
-
AssertionSource[AssertionSource["Argument"] = 0] = "Argument";
|
|
1378
|
-
AssertionSource[AssertionSource["TypeArgument"] = 1] = "TypeArgument";
|
|
1379
|
-
})(AssertionSource || (AssertionSource = {}));
|
|
1380
|
-
|
|
1381
1495
|
class IdentifierLookup {
|
|
1382
1496
|
compiler;
|
|
1383
1497
|
#identifiers;
|
|
@@ -1478,13 +1592,11 @@ class TestTree {
|
|
|
1478
1592
|
compiler;
|
|
1479
1593
|
diagnostics;
|
|
1480
1594
|
sourceFile;
|
|
1481
|
-
typeChecker;
|
|
1482
1595
|
members = [];
|
|
1483
|
-
constructor(compiler, diagnostics, sourceFile
|
|
1596
|
+
constructor(compiler, diagnostics, sourceFile) {
|
|
1484
1597
|
this.compiler = compiler;
|
|
1485
1598
|
this.diagnostics = diagnostics;
|
|
1486
1599
|
this.sourceFile = sourceFile;
|
|
1487
|
-
this.typeChecker = typeChecker;
|
|
1488
1600
|
}
|
|
1489
1601
|
get hasOnly() {
|
|
1490
1602
|
function hasOnly(root) {
|
|
@@ -1561,8 +1673,8 @@ class CollectService {
|
|
|
1561
1673
|
this.#collectTestMembers(node, identifiers, parent);
|
|
1562
1674
|
});
|
|
1563
1675
|
}
|
|
1564
|
-
createTestTree(sourceFile, semanticDiagnostics = []
|
|
1565
|
-
const testTree = new TestTree(this.compiler, semanticDiagnostics, sourceFile
|
|
1676
|
+
createTestTree(sourceFile, semanticDiagnostics = []) {
|
|
1677
|
+
const testTree = new TestTree(this.compiler, new Set(semanticDiagnostics), sourceFile);
|
|
1566
1678
|
this.#collectTestMembers(sourceFile, new IdentifierLookup(this.compiler), testTree);
|
|
1567
1679
|
return testTree;
|
|
1568
1680
|
}
|
|
@@ -1593,408 +1705,551 @@ var TestMemberFlags;
|
|
|
1593
1705
|
TestMemberFlags[TestMemberFlags["Todo"] = 8] = "Todo";
|
|
1594
1706
|
})(TestMemberFlags || (TestMemberFlags = {}));
|
|
1595
1707
|
|
|
1596
|
-
class
|
|
1597
|
-
|
|
1598
|
-
#
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
loggingEnabled: returnFalse,
|
|
1617
|
-
msg: doNothing,
|
|
1618
|
-
perftrc: doNothing,
|
|
1619
|
-
startGroup: doNothing,
|
|
1620
|
-
};
|
|
1621
|
-
const host = {
|
|
1622
|
-
...this.compiler.sys,
|
|
1623
|
-
clearImmediate,
|
|
1624
|
-
clearTimeout,
|
|
1625
|
-
setImmediate,
|
|
1626
|
-
setTimeout,
|
|
1627
|
-
watchDirectory: () => noopWatcher,
|
|
1628
|
-
watchFile: () => noopWatcher,
|
|
1708
|
+
class PrimitiveTypeMatcher {
|
|
1709
|
+
typeChecker;
|
|
1710
|
+
#targetTypeFlag;
|
|
1711
|
+
#targetTypeText;
|
|
1712
|
+
constructor(typeChecker, targetTypeFlag, targetTypeText) {
|
|
1713
|
+
this.typeChecker = typeChecker;
|
|
1714
|
+
this.#targetTypeFlag = targetTypeFlag;
|
|
1715
|
+
this.#targetTypeText = targetTypeText;
|
|
1716
|
+
}
|
|
1717
|
+
#explain(sourceType, isNot) {
|
|
1718
|
+
const sourceTypeText = this.typeChecker.typeToString(sourceType);
|
|
1719
|
+
return isNot
|
|
1720
|
+
? [Diagnostic.error(`Type '${this.#targetTypeText}' is identical to type '${sourceTypeText}'.`)]
|
|
1721
|
+
: [Diagnostic.error(`Type '${this.#targetTypeText}' is not identical to type '${sourceTypeText}'.`)];
|
|
1722
|
+
}
|
|
1723
|
+
match(sourceType, isNot) {
|
|
1724
|
+
const isMatch = Boolean(sourceType.flags & this.#targetTypeFlag);
|
|
1725
|
+
return {
|
|
1726
|
+
explain: () => this.#explain(sourceType, isNot),
|
|
1727
|
+
isMatch,
|
|
1629
1728
|
};
|
|
1630
|
-
this.#service = new this.compiler.server.ProjectService({
|
|
1631
|
-
cancellationToken: this.compiler.server.nullCancellationToken,
|
|
1632
|
-
host,
|
|
1633
|
-
logger: noopLogger,
|
|
1634
|
-
session: undefined,
|
|
1635
|
-
useInferredProjectPerProjectRoot: true,
|
|
1636
|
-
useSingleInferredProject: false,
|
|
1637
|
-
});
|
|
1638
1729
|
}
|
|
1639
|
-
|
|
1640
|
-
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
class ToBeAssignable {
|
|
1733
|
+
typeChecker;
|
|
1734
|
+
constructor(typeChecker) {
|
|
1735
|
+
this.typeChecker = typeChecker;
|
|
1641
1736
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1737
|
+
#explain(sourceType, targetType, isNot) {
|
|
1738
|
+
const sourceTypeText = this.typeChecker.typeToString(sourceType);
|
|
1739
|
+
const targetTypeText = this.typeChecker.typeToString(targetType);
|
|
1740
|
+
return isNot
|
|
1741
|
+
? [Diagnostic.error(`Type '${targetTypeText}' is assignable to type '${sourceTypeText}'.`)]
|
|
1742
|
+
: [Diagnostic.error(`Type '${targetTypeText}' is not assignable to type '${sourceTypeText}'.`)];
|
|
1644
1743
|
}
|
|
1645
|
-
|
|
1646
|
-
const
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1744
|
+
match(sourceType, targetType, isNot) {
|
|
1745
|
+
const isMatch = this.typeChecker.isTypeAssignableTo(targetType, sourceType);
|
|
1746
|
+
return {
|
|
1747
|
+
explain: () => this.#explain(sourceType, targetType, isNot),
|
|
1748
|
+
isMatch,
|
|
1749
|
+
};
|
|
1651
1750
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
class ToEqual {
|
|
1754
|
+
typeChecker;
|
|
1755
|
+
constructor(typeChecker) {
|
|
1756
|
+
this.typeChecker = typeChecker;
|
|
1757
|
+
}
|
|
1758
|
+
#explain(sourceType, targetType, isNot) {
|
|
1759
|
+
const sourceTypeText = this.typeChecker.typeToString(sourceType);
|
|
1760
|
+
const targetTypeText = this.typeChecker.typeToString(targetType);
|
|
1761
|
+
return isNot
|
|
1762
|
+
? [Diagnostic.error(`Type '${targetTypeText}' is identical to type '${sourceTypeText}'.`)]
|
|
1763
|
+
: [Diagnostic.error(`Type '${targetTypeText}' is not identical to type '${sourceTypeText}'.`)];
|
|
1764
|
+
}
|
|
1765
|
+
match(sourceType, targetType, isNot) {
|
|
1766
|
+
const isMatch = this.typeChecker.isTypeIdenticalTo(sourceType, targetType);
|
|
1767
|
+
return {
|
|
1768
|
+
explain: () => this.#explain(sourceType, targetType, isNot),
|
|
1769
|
+
isMatch,
|
|
1770
|
+
};
|
|
1664
1771
|
}
|
|
1665
1772
|
}
|
|
1666
1773
|
|
|
1667
|
-
class
|
|
1774
|
+
class ToHaveProperty {
|
|
1668
1775
|
compiler;
|
|
1669
|
-
|
|
1776
|
+
typeChecker;
|
|
1777
|
+
constructor(compiler, typeChecker) {
|
|
1670
1778
|
this.compiler = compiler;
|
|
1779
|
+
this.typeChecker = typeChecker;
|
|
1671
1780
|
}
|
|
1672
|
-
#
|
|
1673
|
-
|
|
1674
|
-
|
|
1781
|
+
#explain(sourceType, targetType, isNot) {
|
|
1782
|
+
const sourceTypeText = this.typeChecker.typeToString(sourceType);
|
|
1783
|
+
let targetArgumentText;
|
|
1784
|
+
if (this.#isStringOrNumberLiteralType(targetType)) {
|
|
1785
|
+
targetArgumentText = String(targetType.value);
|
|
1675
1786
|
}
|
|
1787
|
+
else {
|
|
1788
|
+
targetArgumentText = `[${this.compiler.unescapeLeadingUnderscores(targetType.symbol.escapedName)}]`;
|
|
1789
|
+
}
|
|
1790
|
+
return isNot
|
|
1791
|
+
? [Diagnostic.error(`Property '${targetArgumentText}' exists on type '${sourceTypeText}'.`)]
|
|
1792
|
+
: [Diagnostic.error(`Property '${targetArgumentText}' does not exist on type '${sourceTypeText}'.`)];
|
|
1676
1793
|
}
|
|
1677
|
-
#
|
|
1678
|
-
|
|
1679
|
-
}
|
|
1680
|
-
#assertNonNullishTargetType(assertion) {
|
|
1681
|
-
this.#assertNonNullish(assertion.targetType, "An argument for 'target' was not provided.");
|
|
1794
|
+
#isStringOrNumberLiteralType(type) {
|
|
1795
|
+
return Boolean(type.flags & this.compiler.TypeFlags.StringOrNumberLiteral);
|
|
1682
1796
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1797
|
+
match(sourceType, targetType, isNot) {
|
|
1798
|
+
let targetArgumentText;
|
|
1799
|
+
if (this.#isStringOrNumberLiteralType(targetType)) {
|
|
1800
|
+
targetArgumentText = String(targetType.value);
|
|
1801
|
+
}
|
|
1802
|
+
else {
|
|
1803
|
+
targetArgumentText = this.compiler.unescapeLeadingUnderscores(targetType.escapedName);
|
|
1804
|
+
}
|
|
1805
|
+
const isMatch = sourceType.getProperties().some((property) => {
|
|
1806
|
+
return this.compiler.unescapeLeadingUnderscores(property.escapedName) === targetArgumentText;
|
|
1807
|
+
});
|
|
1808
|
+
return {
|
|
1809
|
+
explain: () => this.#explain(sourceType, targetType, isNot),
|
|
1810
|
+
isMatch,
|
|
1811
|
+
};
|
|
1685
1812
|
}
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
class ToMatch {
|
|
1816
|
+
typeChecker;
|
|
1817
|
+
constructor(typeChecker) {
|
|
1818
|
+
this.typeChecker = typeChecker;
|
|
1689
1819
|
}
|
|
1690
|
-
#
|
|
1691
|
-
|
|
1820
|
+
#explain(sourceType, targetType, isNot) {
|
|
1821
|
+
const sourceTypeText = this.typeChecker.typeToString(sourceType);
|
|
1822
|
+
const targetTypeText = this.typeChecker.typeToString(targetType);
|
|
1823
|
+
return isNot
|
|
1824
|
+
? [Diagnostic.error(`Type '${targetTypeText}' is a subtype of type '${sourceTypeText}'.`)]
|
|
1825
|
+
: [Diagnostic.error(`Type '${targetTypeText}' is not a subtype of type '${sourceTypeText}'.`)];
|
|
1692
1826
|
}
|
|
1693
|
-
|
|
1694
|
-
this
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
end: assertion.matcherName.getEnd(),
|
|
1699
|
-
file: assertion.matcherName.getSourceFile(),
|
|
1700
|
-
start: assertion.matcherName.getStart(),
|
|
1827
|
+
match(sourceType, targetType, isNot) {
|
|
1828
|
+
const isMatch = this.typeChecker.isTypeSubtypeOf(sourceType, targetType);
|
|
1829
|
+
return {
|
|
1830
|
+
explain: () => this.#explain(sourceType, targetType, isNot),
|
|
1831
|
+
isMatch,
|
|
1701
1832
|
};
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
class ToRaiseError {
|
|
1837
|
+
compiler;
|
|
1838
|
+
typeChecker;
|
|
1839
|
+
constructor(compiler, typeChecker) {
|
|
1840
|
+
this.compiler = compiler;
|
|
1841
|
+
this.typeChecker = typeChecker;
|
|
1842
|
+
}
|
|
1843
|
+
#explain(source, targetTypes, isNot) {
|
|
1844
|
+
const sourceText = this.compiler.isTypeNode(source.node) ? "Type expression" : "Expression";
|
|
1845
|
+
if (source.diagnostics.length === 0) {
|
|
1846
|
+
return [Diagnostic.error(`${sourceText} did not raise a type error.`)];
|
|
1847
|
+
}
|
|
1848
|
+
if (isNot && targetTypes.length === 0) {
|
|
1849
|
+
const related = [
|
|
1850
|
+
Diagnostic.error(`The raised type error${source.diagnostics.length === 1 ? "" : "s"}:`),
|
|
1851
|
+
...Diagnostic.fromDiagnostics(source.diagnostics, this.compiler),
|
|
1852
|
+
];
|
|
1853
|
+
const text = `${sourceText} raised ${source.diagnostics.length === 1 ? "a" : source.diagnostics.length} type error${source.diagnostics.length === 1 ? "" : "s"}.`;
|
|
1854
|
+
return [Diagnostic.error(text).add({ related })];
|
|
1855
|
+
}
|
|
1856
|
+
if (source.diagnostics.length !== targetTypes.length) {
|
|
1857
|
+
const expectedText = source.diagnostics.length > targetTypes.length
|
|
1858
|
+
? `only ${targetTypes.length} type error${targetTypes.length === 1 ? "" : "s"}`
|
|
1859
|
+
: `${targetTypes.length} type error${targetTypes.length === 1 ? "" : "s"}`;
|
|
1860
|
+
const foundText = source.diagnostics.length > targetTypes.length
|
|
1861
|
+
? `${source.diagnostics.length}`
|
|
1862
|
+
: `only ${source.diagnostics.length}`;
|
|
1863
|
+
const related = [
|
|
1864
|
+
Diagnostic.error(`The raised type error${source.diagnostics.length === 1 ? "" : "s"}:`),
|
|
1865
|
+
...Diagnostic.fromDiagnostics(source.diagnostics, this.compiler),
|
|
1866
|
+
];
|
|
1867
|
+
const text = `Expected ${expectedText}, but ${foundText} ${source.diagnostics.length === 1 ? "was" : "were"} raised.`;
|
|
1868
|
+
return [Diagnostic.error(text).add({ related })];
|
|
1869
|
+
}
|
|
1870
|
+
const diagnostics = [];
|
|
1871
|
+
targetTypes.forEach((argument, index) => {
|
|
1872
|
+
const diagnostic = source.diagnostics[index];
|
|
1873
|
+
if (!diagnostic) {
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
const isMatch = this.#matchExpectedError(diagnostic, argument);
|
|
1877
|
+
if (!isNot && !isMatch) {
|
|
1878
|
+
const expectedText = this.#isStringLiteralType(argument)
|
|
1879
|
+
? `matching substring '${argument.value}'`
|
|
1880
|
+
: `with code ${argument.value}`;
|
|
1881
|
+
const related = [
|
|
1882
|
+
Diagnostic.error("The raised type error:"),
|
|
1883
|
+
...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
|
|
1884
|
+
];
|
|
1885
|
+
const text = `${sourceText} did not raise a type error ${expectedText}.`;
|
|
1886
|
+
diagnostics.push(Diagnostic.error(text).add({ related }));
|
|
1887
|
+
}
|
|
1888
|
+
if (isNot && isMatch) {
|
|
1889
|
+
const expectedText = this.#isStringLiteralType(argument)
|
|
1890
|
+
? `matching substring '${argument.value}'`
|
|
1891
|
+
: `with code ${argument.value}`;
|
|
1892
|
+
const related = [
|
|
1893
|
+
Diagnostic.error("The raised type error:"),
|
|
1894
|
+
...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
|
|
1712
1895
|
];
|
|
1896
|
+
const text = `${sourceText} raised a type error ${expectedText}.`;
|
|
1897
|
+
diagnostics.push(Diagnostic.error(text).add({ related }));
|
|
1713
1898
|
}
|
|
1899
|
+
});
|
|
1900
|
+
return diagnostics;
|
|
1901
|
+
}
|
|
1902
|
+
#isStringLiteralType(type) {
|
|
1903
|
+
return Boolean(type.flags & this.compiler.TypeFlags.StringLiteral);
|
|
1904
|
+
}
|
|
1905
|
+
match(source, targetTypes, isNot) {
|
|
1906
|
+
const explain = () => this.#explain(source, targetTypes, isNot);
|
|
1907
|
+
if (targetTypes.length === 0) {
|
|
1908
|
+
return {
|
|
1909
|
+
explain,
|
|
1910
|
+
isMatch: source.diagnostics.length > 0,
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
if (source.diagnostics.length !== targetTypes.length) {
|
|
1914
|
+
return {
|
|
1915
|
+
explain,
|
|
1916
|
+
isMatch: false,
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
return {
|
|
1920
|
+
explain,
|
|
1921
|
+
isMatch: targetTypes.every((type, index) => this.#matchExpectedError(source.diagnostics[index], type)),
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
1924
|
+
#matchExpectedError(diagnostic, type) {
|
|
1925
|
+
if (this.#isStringLiteralType(type)) {
|
|
1926
|
+
return this.compiler.flattenDiagnosticMessageText(diagnostic?.messageText, " ", 0).includes(type.value);
|
|
1927
|
+
}
|
|
1928
|
+
return type.value === diagnostic?.code;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
class Expect {
|
|
1933
|
+
compiler;
|
|
1934
|
+
typeChecker;
|
|
1935
|
+
toBeAny;
|
|
1936
|
+
toBeAssignable;
|
|
1937
|
+
toBeBigInt;
|
|
1938
|
+
toBeBoolean;
|
|
1939
|
+
toBeNever;
|
|
1940
|
+
toBeNull;
|
|
1941
|
+
toBeNumber;
|
|
1942
|
+
toBeString;
|
|
1943
|
+
toBeSymbol;
|
|
1944
|
+
toBeUndefined;
|
|
1945
|
+
toBeUniqueSymbol;
|
|
1946
|
+
toBeUnknown;
|
|
1947
|
+
toBeVoid;
|
|
1948
|
+
toEqual;
|
|
1949
|
+
toHaveProperty;
|
|
1950
|
+
toMatch;
|
|
1951
|
+
toRaiseError;
|
|
1952
|
+
constructor(compiler, typeChecker) {
|
|
1953
|
+
this.compiler = compiler;
|
|
1954
|
+
this.typeChecker = typeChecker;
|
|
1955
|
+
this.toBeAny = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Any, "any");
|
|
1956
|
+
this.toBeAssignable = new ToBeAssignable(this.typeChecker);
|
|
1957
|
+
this.toBeBigInt = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.BigInt, "bigint");
|
|
1958
|
+
this.toBeBoolean = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Boolean, "boolean");
|
|
1959
|
+
this.toBeNever = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Never, "never");
|
|
1960
|
+
this.toBeNull = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Null, "null");
|
|
1961
|
+
this.toBeNumber = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Number, "number");
|
|
1962
|
+
this.toBeString = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.String, "string");
|
|
1963
|
+
this.toBeSymbol = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.ESSymbol, "symbol");
|
|
1964
|
+
this.toBeUndefined = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Undefined, "undefined");
|
|
1965
|
+
this.toBeUniqueSymbol = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.UniqueESSymbol, "unique symbol");
|
|
1966
|
+
this.toBeUnknown = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Unknown, "unknown");
|
|
1967
|
+
this.toBeVoid = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Void, "void");
|
|
1968
|
+
this.toEqual = new ToEqual(this.typeChecker);
|
|
1969
|
+
this.toHaveProperty = new ToHaveProperty(this.compiler, this.typeChecker);
|
|
1970
|
+
this.toMatch = new ToMatch(this.typeChecker);
|
|
1971
|
+
this.toRaiseError = new ToRaiseError(this.compiler, this.typeChecker);
|
|
1972
|
+
}
|
|
1973
|
+
static assertTypeChecker(typeChecker) {
|
|
1974
|
+
return ("isTypeAssignableTo" in typeChecker && "isTypeIdenticalTo" in typeChecker && "isTypeSubtypeOf" in typeChecker);
|
|
1975
|
+
}
|
|
1976
|
+
#getType(node) {
|
|
1977
|
+
return this.compiler.isExpression(node)
|
|
1978
|
+
? this.typeChecker.getTypeAtLocation(node)
|
|
1979
|
+
: this.typeChecker.getTypeFromTypeNode(node);
|
|
1980
|
+
}
|
|
1981
|
+
#getTypes(nodes) {
|
|
1982
|
+
return nodes.map((node) => this.#getType(node));
|
|
1983
|
+
}
|
|
1984
|
+
#isArrayOfStringOrNumberLiteralTypes(types) {
|
|
1985
|
+
return types.every((type) => this.#isStringOrNumberLiteralType(type));
|
|
1986
|
+
}
|
|
1987
|
+
#isStringOrNumberLiteralType(type) {
|
|
1988
|
+
return Boolean(type.flags & this.compiler.TypeFlags.StringOrNumberLiteral);
|
|
1989
|
+
}
|
|
1990
|
+
#isUniqueSymbolType(type) {
|
|
1991
|
+
return Boolean(type.flags & this.compiler.TypeFlags.UniqueESSymbol);
|
|
1992
|
+
}
|
|
1993
|
+
match(assertion, expectResult) {
|
|
1994
|
+
const matcherNameText = assertion.matcherName.getText();
|
|
1995
|
+
switch (matcherNameText) {
|
|
1996
|
+
case "toBeAssignable":
|
|
1997
|
+
case "toEqual":
|
|
1998
|
+
case "toMatch":
|
|
1999
|
+
if (assertion.source[0] == null) {
|
|
2000
|
+
this.#onSourceArgumentMustBeProvided(assertion, expectResult);
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
if (assertion.target[0] == null) {
|
|
2004
|
+
this.#onTargetArgumentMustBeProvided(assertion, expectResult);
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
return this[matcherNameText].match(this.#getType(assertion.source[0]), this.#getType(assertion.target[0]), assertion.isNot);
|
|
1714
2008
|
case "toBeAny":
|
|
1715
|
-
return this.#isType(assertion, "any");
|
|
1716
2009
|
case "toBeBigInt":
|
|
1717
|
-
return this.#isType(assertion, "bigint");
|
|
1718
2010
|
case "toBeBoolean":
|
|
1719
|
-
return this.#isType(assertion, "boolean");
|
|
1720
2011
|
case "toBeNever":
|
|
1721
|
-
return this.#isType(assertion, "never");
|
|
1722
2012
|
case "toBeNull":
|
|
1723
|
-
return this.#isType(assertion, "null");
|
|
1724
2013
|
case "toBeNumber":
|
|
1725
|
-
return this.#isType(assertion, "number");
|
|
1726
2014
|
case "toBeString":
|
|
1727
|
-
return this.#isType(assertion, "string");
|
|
1728
2015
|
case "toBeSymbol":
|
|
1729
|
-
return this.#isType(assertion, "symbol");
|
|
1730
2016
|
case "toBeUndefined":
|
|
1731
|
-
return this.#isType(assertion, "undefined");
|
|
1732
2017
|
case "toBeUniqueSymbol":
|
|
1733
|
-
return this.#isType(assertion, "unique symbol");
|
|
1734
2018
|
case "toBeUnknown":
|
|
1735
|
-
return this.#isType(assertion, "unknown");
|
|
1736
2019
|
case "toBeVoid":
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
const targetTypeText = assertion.typeChecker.typeToString(assertion.targetType.type);
|
|
1743
|
-
return [
|
|
1744
|
-
Diagnostic.error(assertion.isNot
|
|
1745
|
-
? `Type '${targetTypeText}' is identical to type '${sourceTypeText}'.`
|
|
1746
|
-
: `Type '${targetTypeText}' is not identical to type '${sourceTypeText}'.`, origin),
|
|
1747
|
-
];
|
|
1748
|
-
}
|
|
2020
|
+
if (assertion.source[0] == null) {
|
|
2021
|
+
this.#onSourceArgumentMustBeProvided(assertion, expectResult);
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
return this[matcherNameText].match(this.#getType(assertion.source[0]), assertion.isNot);
|
|
1749
2025
|
case "toHaveProperty": {
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
let targetArgumentText;
|
|
1754
|
-
if (assertion.targetType.type.flags & this.compiler.TypeFlags.StringOrNumberLiteral) {
|
|
1755
|
-
targetArgumentText = String(assertion.targetType.type.value);
|
|
2026
|
+
if (assertion.source[0] == null) {
|
|
2027
|
+
this.#onSourceArgumentMustBeProvided(assertion, expectResult);
|
|
2028
|
+
return;
|
|
1756
2029
|
}
|
|
1757
|
-
|
|
1758
|
-
|
|
2030
|
+
const sourceType = this.#getType(assertion.source[0]);
|
|
2031
|
+
const nonPrimitiveType = { flags: this.compiler.TypeFlags.NonPrimitive };
|
|
2032
|
+
if (sourceType.flags & (this.compiler.TypeFlags.Any | this.compiler.TypeFlags.Never) ||
|
|
2033
|
+
!this.typeChecker.isTypeAssignableTo(sourceType, nonPrimitiveType)) {
|
|
2034
|
+
this.#onSourceArgumentMustBeObjectType(assertion.source[0], expectResult);
|
|
2035
|
+
return;
|
|
1759
2036
|
}
|
|
1760
|
-
|
|
1761
|
-
|
|
2037
|
+
if (assertion.target[0] == null) {
|
|
2038
|
+
this.#onKeyArgumentMustBeProvided(assertion, expectResult);
|
|
2039
|
+
return;
|
|
1762
2040
|
}
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
case "toMatch": {
|
|
1770
|
-
this.#assertNonNullishSourceType(assertion);
|
|
1771
|
-
this.#assertNonNullishTargetType(assertion);
|
|
1772
|
-
const sourceTypeText = assertion.typeChecker.typeToString(assertion.sourceType.type);
|
|
1773
|
-
const targetTypeText = assertion.typeChecker.typeToString(assertion.targetType.type);
|
|
1774
|
-
return [
|
|
1775
|
-
Diagnostic.error(assertion.isNot
|
|
1776
|
-
? `Type '${targetTypeText}' is a subtype of type '${sourceTypeText}'.`
|
|
1777
|
-
: `Type '${targetTypeText}' is not a subtype of type '${sourceTypeText}'.`, origin),
|
|
1778
|
-
];
|
|
2041
|
+
const targetType = this.#getType(assertion.target[0]);
|
|
2042
|
+
if (!(this.#isStringOrNumberLiteralType(targetType) || this.#isUniqueSymbolType(targetType))) {
|
|
2043
|
+
this.#onKeyArgumentMustBeOfType(assertion.target[0], expectResult);
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
return this.toHaveProperty.match(sourceType, targetType, assertion.isNot);
|
|
1779
2047
|
}
|
|
1780
2048
|
case "toRaiseError": {
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
}
|
|
1785
|
-
const sourceText = assertion.sourceType.source === 0 ? "Expression" : "Type definition";
|
|
1786
|
-
if (assertion.diagnostics.length === 0) {
|
|
1787
|
-
return [Diagnostic.error(`${sourceText} did not raise a type error.`, origin)];
|
|
1788
|
-
}
|
|
1789
|
-
if (assertion.isNot && assertion.targetArguments.length === 0) {
|
|
1790
|
-
const related = [
|
|
1791
|
-
Diagnostic.error(`The raised type error${assertion.diagnostics.length === 1 ? "" : "s"}:`),
|
|
1792
|
-
...Diagnostic.fromDiagnostics(assertion.diagnostics, this.compiler),
|
|
1793
|
-
];
|
|
1794
|
-
return [
|
|
1795
|
-
Diagnostic.error(`${sourceText} raised ${assertion.diagnostics.length === 1 ? "a" : assertion.diagnostics.length} type error${assertion.diagnostics.length === 1 ? "" : "s"}.`, origin).add({ related }),
|
|
1796
|
-
];
|
|
1797
|
-
}
|
|
1798
|
-
if (assertion.diagnostics.length !== assertion.targetArguments.length) {
|
|
1799
|
-
const expectedText = assertion.diagnostics.length > assertion.targetArguments.length
|
|
1800
|
-
? `only ${assertion.targetArguments.length} type error${assertion.targetArguments.length === 1 ? "" : "s"}`
|
|
1801
|
-
: `${assertion.targetArguments.length} type error${assertion.targetArguments.length === 1 ? "" : "s"}`;
|
|
1802
|
-
const foundText = assertion.diagnostics.length > assertion.targetArguments.length
|
|
1803
|
-
? `${assertion.diagnostics.length}`
|
|
1804
|
-
: `only ${assertion.diagnostics.length}`;
|
|
1805
|
-
const related = [
|
|
1806
|
-
Diagnostic.error(`The raised type error${assertion.diagnostics.length === 1 ? "" : "s"}:`),
|
|
1807
|
-
...Diagnostic.fromDiagnostics(assertion.diagnostics, this.compiler),
|
|
1808
|
-
];
|
|
1809
|
-
const diagnostic = Diagnostic.error(`Expected ${expectedText}, but ${foundText} ${assertion.diagnostics.length === 1 ? "was" : "were"} raised.`, origin).add({
|
|
1810
|
-
related,
|
|
1811
|
-
});
|
|
1812
|
-
return [diagnostic];
|
|
2049
|
+
if (assertion.source[0] == null) {
|
|
2050
|
+
this.#onSourceArgumentMustBeProvided(assertion, expectResult);
|
|
2051
|
+
return;
|
|
1813
2052
|
}
|
|
1814
|
-
const
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
const isMatch = this.#matchExpectedError(diagnostic, argument);
|
|
1821
|
-
if (!assertion.isNot && !isMatch) {
|
|
1822
|
-
const expectedText = this.compiler.isStringLiteralLike(argument)
|
|
1823
|
-
? `matching substring '${argument.text}'`
|
|
1824
|
-
: `with code ${argument.text}`;
|
|
1825
|
-
const related = [
|
|
1826
|
-
Diagnostic.error("The raised type error:"),
|
|
1827
|
-
...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
|
|
1828
|
-
];
|
|
1829
|
-
diagnostics.push(Diagnostic.error(`${sourceText} did not raise a type error ${expectedText}.`, origin).add({ related }));
|
|
1830
|
-
}
|
|
1831
|
-
if (assertion.isNot && isMatch) {
|
|
1832
|
-
const expectedText = this.compiler.isStringLiteralLike(argument)
|
|
1833
|
-
? `matching substring '${argument.text}'`
|
|
1834
|
-
: `with code ${argument.text}`;
|
|
1835
|
-
const related = [
|
|
1836
|
-
Diagnostic.error("The raised type error:"),
|
|
1837
|
-
...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
|
|
1838
|
-
];
|
|
1839
|
-
diagnostics.push(Diagnostic.error(`${sourceText} raised a type error ${expectedText}.`, origin).add({ related }));
|
|
1840
|
-
}
|
|
1841
|
-
});
|
|
1842
|
-
return diagnostics;
|
|
2053
|
+
const targetTypes = this.#getTypes(assertion.target);
|
|
2054
|
+
if (!this.#isArrayOfStringOrNumberLiteralTypes(targetTypes)) {
|
|
2055
|
+
this.#onTargetArgumentsMustBeStringOrNumberLiteralTypes(assertion.target, expectResult);
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2058
|
+
return this.toRaiseError.match({ diagnostics: [...assertion.diagnostics], node: assertion.source[0] }, targetTypes, assertion.isNot);
|
|
1843
2059
|
}
|
|
1844
2060
|
default:
|
|
1845
|
-
|
|
2061
|
+
this.#onNotSupportedMatcherName(assertion, expectResult);
|
|
2062
|
+
return;
|
|
1846
2063
|
}
|
|
1847
2064
|
}
|
|
1848
|
-
#
|
|
1849
|
-
this.#
|
|
1850
|
-
|
|
2065
|
+
#onKeyArgumentMustBeOfType(node, expectResult) {
|
|
2066
|
+
const receivedTypeText = this.typeChecker.typeToString(this.#getType(node));
|
|
2067
|
+
const text = `An argument for 'key' must be of type 'string | number | symbol', received: '${receivedTypeText}'.`;
|
|
2068
|
+
const origin = {
|
|
2069
|
+
end: node.getEnd(),
|
|
2070
|
+
file: node.getSourceFile(),
|
|
2071
|
+
start: node.getStart(),
|
|
2072
|
+
};
|
|
2073
|
+
EventEmitter.dispatch(["expect:error", { diagnostics: [Diagnostic.error(text, origin)], result: expectResult }]);
|
|
1851
2074
|
}
|
|
1852
|
-
#
|
|
1853
|
-
this.#assertNonNullishSourceType(assertion);
|
|
1854
|
-
this.#assertNonNullishTypeChecker(assertion);
|
|
2075
|
+
#onKeyArgumentMustBeProvided(assertion, expectResult) {
|
|
1855
2076
|
const origin = {
|
|
1856
|
-
breadcrumbs: assertion.ancestorNames,
|
|
1857
2077
|
end: assertion.matcherName.getEnd(),
|
|
1858
2078
|
file: assertion.matcherName.getSourceFile(),
|
|
1859
2079
|
start: assertion.matcherName.getStart(),
|
|
1860
2080
|
};
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
:
|
|
1866
|
-
|
|
2081
|
+
EventEmitter.dispatch([
|
|
2082
|
+
"expect:error",
|
|
2083
|
+
{
|
|
2084
|
+
diagnostics: [Diagnostic.error("An argument for 'key' must be provided.", origin)],
|
|
2085
|
+
result: expectResult,
|
|
2086
|
+
},
|
|
2087
|
+
]);
|
|
1867
2088
|
}
|
|
1868
|
-
|
|
1869
|
-
const
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
});
|
|
1938
|
-
}
|
|
1939
|
-
case "toMatch": {
|
|
1940
|
-
this.#assertNonNullishSourceType(assertion);
|
|
1941
|
-
this.#assertNonNullishTargetType(assertion);
|
|
1942
|
-
this.#assertNonNullish(assertion.typeChecker?.isTypeSubtypeOf, "The 'isTypeSubtypeOf' method is missing in the provided type checker.");
|
|
1943
|
-
return assertion.typeChecker.isTypeSubtypeOf(assertion.sourceType.type, assertion.targetType.type);
|
|
1944
|
-
}
|
|
1945
|
-
case "toRaiseError": {
|
|
1946
|
-
if (!this.#assertStringsOrNumbers(assertion.targetArguments)) {
|
|
1947
|
-
throw new Error("An argument for 'target' must be of type 'string | number'.");
|
|
1948
|
-
}
|
|
1949
|
-
if (assertion.targetArguments.length === 0) {
|
|
1950
|
-
return assertion.diagnostics.length > 0;
|
|
1951
|
-
}
|
|
1952
|
-
if (assertion.diagnostics.length !== assertion.targetArguments.length) {
|
|
1953
|
-
return false;
|
|
1954
|
-
}
|
|
1955
|
-
return assertion.targetArguments.every((expectedArgument, index) => {
|
|
1956
|
-
if (this.compiler.isStringLiteralLike(expectedArgument)) {
|
|
1957
|
-
return this.compiler
|
|
1958
|
-
.flattenDiagnosticMessageText(assertion.diagnostics[index]?.messageText, " ", 0)
|
|
1959
|
-
.includes(expectedArgument.text);
|
|
1960
|
-
}
|
|
1961
|
-
if (this.compiler.isNumericLiteral(expectedArgument)) {
|
|
1962
|
-
return Number(expectedArgument.text) === assertion.diagnostics[index]?.code;
|
|
1963
|
-
}
|
|
1964
|
-
return false;
|
|
1965
|
-
});
|
|
2089
|
+
#onNotSupportedMatcherName(assertion, expectResult) {
|
|
2090
|
+
const matcherNameText = assertion.matcherName.getText();
|
|
2091
|
+
const origin = {
|
|
2092
|
+
end: assertion.matcherName.getEnd(),
|
|
2093
|
+
file: assertion.matcherName.getSourceFile(),
|
|
2094
|
+
start: assertion.matcherName.getStart(),
|
|
2095
|
+
};
|
|
2096
|
+
EventEmitter.dispatch([
|
|
2097
|
+
"expect:error",
|
|
2098
|
+
{
|
|
2099
|
+
diagnostics: [Diagnostic.error(`The '${matcherNameText}()' matcher is not supported.`, origin)],
|
|
2100
|
+
result: expectResult,
|
|
2101
|
+
},
|
|
2102
|
+
]);
|
|
2103
|
+
}
|
|
2104
|
+
#onSourceArgumentMustBeObjectType(node, expectResult) {
|
|
2105
|
+
const sourceText = this.compiler.isTypeNode(node) ? "A type argument for 'Source'" : "An argument for 'source'";
|
|
2106
|
+
const receivedTypeText = this.typeChecker.typeToString(this.#getType(node));
|
|
2107
|
+
const text = `${sourceText} must be of an object type, received: '${receivedTypeText}'.`;
|
|
2108
|
+
const origin = {
|
|
2109
|
+
end: node.getEnd(),
|
|
2110
|
+
file: node.getSourceFile(),
|
|
2111
|
+
start: node.getStart(),
|
|
2112
|
+
};
|
|
2113
|
+
EventEmitter.dispatch(["expect:error", { diagnostics: [Diagnostic.error(text, origin)], result: expectResult }]);
|
|
2114
|
+
}
|
|
2115
|
+
#onSourceArgumentMustBeProvided(assertion, expectResult) {
|
|
2116
|
+
const origin = {
|
|
2117
|
+
end: assertion.node.getEnd(),
|
|
2118
|
+
file: assertion.node.getSourceFile(),
|
|
2119
|
+
start: assertion.node.getStart(),
|
|
2120
|
+
};
|
|
2121
|
+
EventEmitter.dispatch([
|
|
2122
|
+
"expect:error",
|
|
2123
|
+
{
|
|
2124
|
+
diagnostics: [
|
|
2125
|
+
Diagnostic.error("An argument for 'source' or type argument for 'Source' must be provided.", origin),
|
|
2126
|
+
],
|
|
2127
|
+
result: expectResult,
|
|
2128
|
+
},
|
|
2129
|
+
]);
|
|
2130
|
+
}
|
|
2131
|
+
#onTargetArgumentMustBeProvided(assertion, expectResult) {
|
|
2132
|
+
const origin = {
|
|
2133
|
+
end: assertion.matcherName.getEnd(),
|
|
2134
|
+
file: assertion.matcherName.getSourceFile(),
|
|
2135
|
+
start: assertion.matcherName.getStart(),
|
|
2136
|
+
};
|
|
2137
|
+
EventEmitter.dispatch([
|
|
2138
|
+
"expect:error",
|
|
2139
|
+
{
|
|
2140
|
+
diagnostics: [
|
|
2141
|
+
Diagnostic.error("An argument for 'target' or type argument for 'Target' must be provided.", origin),
|
|
2142
|
+
],
|
|
2143
|
+
result: expectResult,
|
|
2144
|
+
},
|
|
2145
|
+
]);
|
|
2146
|
+
}
|
|
2147
|
+
#onTargetArgumentsMustBeStringOrNumberLiteralTypes(nodes, expectResult) {
|
|
2148
|
+
const diagnostics = [];
|
|
2149
|
+
for (const node of nodes) {
|
|
2150
|
+
const receivedType = this.#getType(node);
|
|
2151
|
+
if (!this.#isStringOrNumberLiteralType(receivedType)) {
|
|
2152
|
+
const receivedTypeText = this.typeChecker.typeToString(this.#getType(node));
|
|
2153
|
+
const origin = {
|
|
2154
|
+
end: node.getEnd(),
|
|
2155
|
+
file: node.getSourceFile(),
|
|
2156
|
+
start: node.getStart(),
|
|
2157
|
+
};
|
|
2158
|
+
diagnostics.push(Diagnostic.error(`An argument for 'target' must be of type 'string | number', received: '${receivedTypeText}'.`, origin));
|
|
1966
2159
|
}
|
|
1967
|
-
default:
|
|
1968
|
-
throw new Error(`The '${matcher}' matcher is not supported.`);
|
|
1969
2160
|
}
|
|
2161
|
+
EventEmitter.dispatch(["expect:error", { diagnostics, result: expectResult }]);
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
class ProjectService {
|
|
2166
|
+
compiler;
|
|
2167
|
+
#service;
|
|
2168
|
+
constructor(compiler) {
|
|
2169
|
+
this.compiler = compiler;
|
|
2170
|
+
function doNothing() {
|
|
2171
|
+
}
|
|
2172
|
+
function returnFalse() {
|
|
2173
|
+
return false;
|
|
2174
|
+
}
|
|
2175
|
+
function returnUndefined() {
|
|
2176
|
+
return undefined;
|
|
2177
|
+
}
|
|
2178
|
+
const noopWatcher = { close: doNothing };
|
|
2179
|
+
const noopLogger = {
|
|
2180
|
+
close: doNothing,
|
|
2181
|
+
endGroup: doNothing,
|
|
2182
|
+
getLogFileName: returnUndefined,
|
|
2183
|
+
hasLevel: returnFalse,
|
|
2184
|
+
info: doNothing,
|
|
2185
|
+
loggingEnabled: returnFalse,
|
|
2186
|
+
msg: doNothing,
|
|
2187
|
+
perftrc: doNothing,
|
|
2188
|
+
startGroup: doNothing,
|
|
2189
|
+
};
|
|
2190
|
+
const host = {
|
|
2191
|
+
...this.compiler.sys,
|
|
2192
|
+
clearImmediate,
|
|
2193
|
+
clearTimeout,
|
|
2194
|
+
setImmediate,
|
|
2195
|
+
setTimeout,
|
|
2196
|
+
watchDirectory: () => noopWatcher,
|
|
2197
|
+
watchFile: () => noopWatcher,
|
|
2198
|
+
};
|
|
2199
|
+
this.#service = new this.compiler.server.ProjectService({
|
|
2200
|
+
allowLocalPluginLoads: true,
|
|
2201
|
+
cancellationToken: this.compiler.server.nullCancellationToken,
|
|
2202
|
+
host,
|
|
2203
|
+
logger: noopLogger,
|
|
2204
|
+
session: undefined,
|
|
2205
|
+
useInferredProjectPerProjectRoot: true,
|
|
2206
|
+
useSingleInferredProject: false,
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
closeFile(filePath) {
|
|
2210
|
+
this.#service.closeClientFile(filePath);
|
|
2211
|
+
}
|
|
2212
|
+
getDefaultProject(filePath) {
|
|
2213
|
+
return this.#service.getDefaultProjectForFile(this.compiler.server.toNormalizedPath(filePath), true);
|
|
1970
2214
|
}
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
2215
|
+
getLanguageService(filePath) {
|
|
2216
|
+
const project = this.getDefaultProject(filePath);
|
|
2217
|
+
if (!project) {
|
|
2218
|
+
return;
|
|
1974
2219
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
2220
|
+
return project.getLanguageService(true);
|
|
2221
|
+
}
|
|
2222
|
+
openFile(filePath, sourceText, projectRootPath) {
|
|
2223
|
+
const { configFileErrors, configFileName } = this.#service.openClientFile(filePath, sourceText, undefined, projectRootPath);
|
|
2224
|
+
EventEmitter.dispatch([
|
|
2225
|
+
"project:info",
|
|
2226
|
+
{ compilerVersion: this.compiler.version, projectConfigFilePath: configFileName },
|
|
2227
|
+
]);
|
|
2228
|
+
if (configFileErrors && configFileErrors.length > 0) {
|
|
2229
|
+
EventEmitter.dispatch([
|
|
2230
|
+
"project:error",
|
|
2231
|
+
{ diagnostics: Diagnostic.fromDiagnostics(configFileErrors, this.compiler) },
|
|
2232
|
+
]);
|
|
1977
2233
|
}
|
|
1978
|
-
throw new Error("An argument for 'target' must be of type 'string | number'.");
|
|
1979
2234
|
}
|
|
1980
2235
|
}
|
|
1981
2236
|
|
|
1982
2237
|
class TestTreeWorker {
|
|
1983
2238
|
resolvedConfig;
|
|
1984
2239
|
compiler;
|
|
1985
|
-
#
|
|
2240
|
+
#expect;
|
|
1986
2241
|
#fileResult;
|
|
1987
2242
|
#hasOnly;
|
|
1988
2243
|
#position;
|
|
1989
2244
|
#signal;
|
|
1990
|
-
constructor(resolvedConfig, compiler, options) {
|
|
2245
|
+
constructor(resolvedConfig, compiler, expect, options) {
|
|
1991
2246
|
this.resolvedConfig = resolvedConfig;
|
|
1992
2247
|
this.compiler = compiler;
|
|
1993
|
-
this.#
|
|
2248
|
+
this.#expect = expect;
|
|
2249
|
+
this.#fileResult = options.fileResult;
|
|
1994
2250
|
this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
|
|
1995
2251
|
this.#position = options.position;
|
|
1996
2252
|
this.#signal = options.signal;
|
|
1997
|
-
this.#fileResult = options.fileResult;
|
|
1998
2253
|
}
|
|
1999
2254
|
#resolveRunMode(mode, member) {
|
|
2000
2255
|
if (member.flags & 1) {
|
|
@@ -2052,18 +2307,21 @@ class TestTreeWorker {
|
|
|
2052
2307
|
EventEmitter.dispatch(["expect:skip", { result: expectResult }]);
|
|
2053
2308
|
return;
|
|
2054
2309
|
}
|
|
2055
|
-
if (assertion.diagnostics.
|
|
2310
|
+
if (assertion.diagnostics.size > 0 && assertion.matcherName.getText() !== "toRaiseError") {
|
|
2056
2311
|
EventEmitter.dispatch([
|
|
2057
2312
|
"expect:error",
|
|
2058
2313
|
{
|
|
2059
|
-
diagnostics: Diagnostic.fromDiagnostics(assertion.diagnostics, this.compiler),
|
|
2314
|
+
diagnostics: Diagnostic.fromDiagnostics([...assertion.diagnostics], this.compiler),
|
|
2060
2315
|
result: expectResult,
|
|
2061
2316
|
},
|
|
2062
2317
|
]);
|
|
2063
2318
|
return;
|
|
2064
2319
|
}
|
|
2065
|
-
const
|
|
2066
|
-
if (
|
|
2320
|
+
const matchResult = this.#expect.match(assertion, expectResult);
|
|
2321
|
+
if (matchResult == null) {
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
if (assertion.isNot ? !matchResult.isMatch : matchResult.isMatch) {
|
|
2067
2325
|
if (runMode & 1) {
|
|
2068
2326
|
const text = ["The assertion was supposed to fail, but it passed.", "Consider removing the '.fail' flag."];
|
|
2069
2327
|
const origin = {
|
|
@@ -2073,10 +2331,7 @@ class TestTreeWorker {
|
|
|
2073
2331
|
};
|
|
2074
2332
|
EventEmitter.dispatch([
|
|
2075
2333
|
"expect:error",
|
|
2076
|
-
{
|
|
2077
|
-
diagnostics: [Diagnostic.error(text, origin)],
|
|
2078
|
-
result: expectResult,
|
|
2079
|
-
},
|
|
2334
|
+
{ diagnostics: [Diagnostic.error(text, origin)], result: expectResult },
|
|
2080
2335
|
]);
|
|
2081
2336
|
}
|
|
2082
2337
|
else {
|
|
@@ -2088,13 +2343,20 @@ class TestTreeWorker {
|
|
|
2088
2343
|
EventEmitter.dispatch(["expect:pass", { result: expectResult }]);
|
|
2089
2344
|
}
|
|
2090
2345
|
else {
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
]
|
|
2346
|
+
const origin = {
|
|
2347
|
+
breadcrumbs: assertion.ancestorNames,
|
|
2348
|
+
end: assertion.matcherName.getEnd(),
|
|
2349
|
+
file: assertion.matcherName.getSourceFile(),
|
|
2350
|
+
start: assertion.matcherName.getStart(),
|
|
2351
|
+
};
|
|
2352
|
+
const diagnostics = [];
|
|
2353
|
+
for (const diagnostic of matchResult.explain()) {
|
|
2354
|
+
if (diagnostic.origin == null) {
|
|
2355
|
+
diagnostic.add({ origin });
|
|
2356
|
+
}
|
|
2357
|
+
diagnostics.push(diagnostic);
|
|
2358
|
+
}
|
|
2359
|
+
EventEmitter.dispatch(["expect:fail", { diagnostics, result: expectResult }]);
|
|
2098
2360
|
}
|
|
2099
2361
|
}
|
|
2100
2362
|
}
|
|
@@ -2103,11 +2365,11 @@ class TestTreeWorker {
|
|
|
2103
2365
|
EventEmitter.dispatch(["describe:start", { result: describeResult }]);
|
|
2104
2366
|
runMode = this.#resolveRunMode(runMode, describe);
|
|
2105
2367
|
if (!(runMode & 4 || (this.#hasOnly && !(runMode & 2)) || runMode & 8) &&
|
|
2106
|
-
describe.diagnostics.
|
|
2368
|
+
describe.diagnostics.size > 0) {
|
|
2107
2369
|
EventEmitter.dispatch([
|
|
2108
2370
|
"file:error",
|
|
2109
2371
|
{
|
|
2110
|
-
diagnostics: Diagnostic.fromDiagnostics(describe.diagnostics, this.compiler),
|
|
2372
|
+
diagnostics: Diagnostic.fromDiagnostics([...describe.diagnostics], this.compiler),
|
|
2111
2373
|
result: this.#fileResult,
|
|
2112
2374
|
},
|
|
2113
2375
|
]);
|
|
@@ -2125,11 +2387,11 @@ class TestTreeWorker {
|
|
|
2125
2387
|
EventEmitter.dispatch(["test:todo", { result: testResult }]);
|
|
2126
2388
|
return;
|
|
2127
2389
|
}
|
|
2128
|
-
if (!(runMode & 4 || (this.#hasOnly && !(runMode & 2))) && test.diagnostics.
|
|
2390
|
+
if (!(runMode & 4 || (this.#hasOnly && !(runMode & 2))) && test.diagnostics.size > 0) {
|
|
2129
2391
|
EventEmitter.dispatch([
|
|
2130
2392
|
"test:error",
|
|
2131
2393
|
{
|
|
2132
|
-
diagnostics: Diagnostic.fromDiagnostics(test.diagnostics, this.compiler),
|
|
2394
|
+
diagnostics: Diagnostic.fromDiagnostics([...test.diagnostics], this.compiler),
|
|
2133
2395
|
result: testResult,
|
|
2134
2396
|
},
|
|
2135
2397
|
]);
|
|
@@ -2198,19 +2460,25 @@ class TestFileRunner {
|
|
|
2198
2460
|
if (!sourceFile) {
|
|
2199
2461
|
return;
|
|
2200
2462
|
}
|
|
2201
|
-
const
|
|
2202
|
-
|
|
2203
|
-
if (testTree.diagnostics.length > 0) {
|
|
2463
|
+
const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics);
|
|
2464
|
+
if (testTree.diagnostics.size > 0) {
|
|
2204
2465
|
EventEmitter.dispatch([
|
|
2205
2466
|
"file:error",
|
|
2206
2467
|
{
|
|
2207
|
-
diagnostics: Diagnostic.fromDiagnostics(testTree.diagnostics, this.compiler),
|
|
2468
|
+
diagnostics: Diagnostic.fromDiagnostics([...testTree.diagnostics], this.compiler),
|
|
2208
2469
|
result: fileResult,
|
|
2209
2470
|
},
|
|
2210
2471
|
]);
|
|
2211
2472
|
return;
|
|
2212
2473
|
}
|
|
2213
|
-
const
|
|
2474
|
+
const typeChecker = program.getTypeChecker();
|
|
2475
|
+
if (!Expect.assertTypeChecker(typeChecker)) {
|
|
2476
|
+
const text = "The required 'isTypeAssignableTo()', 'isTypeIdenticalTo()' and 'isTypeSubtypeOf()' methods are missing in the provided type checker.";
|
|
2477
|
+
EventEmitter.dispatch(["file:error", { diagnostics: [Diagnostic.error(text)], result: fileResult }]);
|
|
2478
|
+
return;
|
|
2479
|
+
}
|
|
2480
|
+
const expect = new Expect(this.compiler, typeChecker);
|
|
2481
|
+
const testTreeWorker = new TestTreeWorker(this.resolvedConfig, this.compiler, expect, {
|
|
2214
2482
|
fileResult,
|
|
2215
2483
|
hasOnly: testTree.hasOnly,
|
|
2216
2484
|
position,
|
|
@@ -2257,17 +2525,13 @@ class TSTyche {
|
|
|
2257
2525
|
#abortController = new AbortController();
|
|
2258
2526
|
#storeService;
|
|
2259
2527
|
#taskRunner;
|
|
2528
|
+
static version = "1.0.0-beta.8";
|
|
2260
2529
|
constructor(resolvedConfig, storeService) {
|
|
2261
2530
|
this.resolvedConfig = resolvedConfig;
|
|
2262
2531
|
this.#storeService = storeService;
|
|
2263
2532
|
this.#taskRunner = new TaskRunner(this.resolvedConfig, this.#storeService);
|
|
2264
2533
|
this.#addEventHandlers();
|
|
2265
2534
|
}
|
|
2266
|
-
static get version() {
|
|
2267
|
-
const packageConfig = readFileSync(new URL("../package.json", import.meta.url), { encoding: "utf8" });
|
|
2268
|
-
const { version } = JSON.parse(packageConfig);
|
|
2269
|
-
return version;
|
|
2270
|
-
}
|
|
2271
2535
|
#addEventHandlers() {
|
|
2272
2536
|
EventEmitter.addHandler(([eventName, payload]) => {
|
|
2273
2537
|
if (eventName.includes("error") || eventName.includes("fail")) {
|
|
@@ -2325,19 +2589,19 @@ class OptionDefinitionsMap {
|
|
|
2325
2589
|
name: "failFast",
|
|
2326
2590
|
},
|
|
2327
2591
|
{
|
|
2328
|
-
brand: "
|
|
2329
|
-
description: "Print the list of
|
|
2592
|
+
brand: "true",
|
|
2593
|
+
description: "Print the list of command line options with brief descriptions and exit.",
|
|
2330
2594
|
group: 2,
|
|
2331
2595
|
name: "help",
|
|
2332
2596
|
},
|
|
2333
2597
|
{
|
|
2334
|
-
brand: "
|
|
2598
|
+
brand: "true",
|
|
2335
2599
|
description: "Install specified versions of the 'typescript' package and exit.",
|
|
2336
2600
|
group: 2,
|
|
2337
2601
|
name: "install",
|
|
2338
2602
|
},
|
|
2339
2603
|
{
|
|
2340
|
-
brand: "
|
|
2604
|
+
brand: "true",
|
|
2341
2605
|
description: "Print the list of the selected test files and exit.",
|
|
2342
2606
|
group: 2,
|
|
2343
2607
|
name: "listFiles",
|
|
@@ -2349,7 +2613,7 @@ class OptionDefinitionsMap {
|
|
|
2349
2613
|
name: "only",
|
|
2350
2614
|
},
|
|
2351
2615
|
{
|
|
2352
|
-
brand: "
|
|
2616
|
+
brand: "true",
|
|
2353
2617
|
description: "Remove all installed versions of the 'typescript' package and exit.",
|
|
2354
2618
|
group: 2,
|
|
2355
2619
|
name: "prune",
|
|
@@ -2361,7 +2625,7 @@ class OptionDefinitionsMap {
|
|
|
2361
2625
|
name: "rootPath",
|
|
2362
2626
|
},
|
|
2363
2627
|
{
|
|
2364
|
-
brand: "
|
|
2628
|
+
brand: "true",
|
|
2365
2629
|
description: "Print the resolved configuration and exit.",
|
|
2366
2630
|
group: 2,
|
|
2367
2631
|
name: "showConfig",
|
|
@@ -2394,13 +2658,13 @@ class OptionDefinitionsMap {
|
|
|
2394
2658
|
name: "testFileMatch",
|
|
2395
2659
|
},
|
|
2396
2660
|
{
|
|
2397
|
-
brand: "
|
|
2661
|
+
brand: "true",
|
|
2398
2662
|
description: "Fetch the 'typescript' package metadata from the registry and exit.",
|
|
2399
2663
|
group: 2,
|
|
2400
2664
|
name: "update",
|
|
2401
2665
|
},
|
|
2402
2666
|
{
|
|
2403
|
-
brand: "
|
|
2667
|
+
brand: "true",
|
|
2404
2668
|
description: "Print the version number and exit.",
|
|
2405
2669
|
group: 2,
|
|
2406
2670
|
name: "version",
|
|
@@ -2467,15 +2731,15 @@ class OptionUsageText {
|
|
|
2467
2731
|
this.#optionDiagnosticText = new OptionDiagnosticText(this.#optionGroup);
|
|
2468
2732
|
this.#storeService = storeService;
|
|
2469
2733
|
}
|
|
2470
|
-
get(optionName, optionBrand) {
|
|
2734
|
+
async get(optionName, optionBrand) {
|
|
2471
2735
|
const usageText = [];
|
|
2472
2736
|
switch (optionName) {
|
|
2473
2737
|
case "target": {
|
|
2474
|
-
const
|
|
2738
|
+
const supportedTags = await this.#storeService.getSupportedTags();
|
|
2475
2739
|
const supportedTagsText = `Supported tags: ${["'", supportedTags.join("', '"), "'"].join("")}.`;
|
|
2476
2740
|
switch (this.#optionGroup) {
|
|
2477
2741
|
case 2:
|
|
2478
|
-
usageText.push("Argument for the '--target' option must be a single tag or a comma separated list.", "Usage examples: '--target 4.9', '--target 5.0.4', '--target 4.7,
|
|
2742
|
+
usageText.push("Argument for the '--target' option must be a single tag or a comma separated list.", "Usage examples: '--target 4.9', '--target 5.0.4', '--target 4.7,5.3.2,current'.", supportedTagsText);
|
|
2479
2743
|
break;
|
|
2480
2744
|
case 4:
|
|
2481
2745
|
usageText.push("Item of the 'target' list must be a supported version tag.", supportedTagsText);
|
|
@@ -2503,7 +2767,7 @@ class OptionValidator {
|
|
|
2503
2767
|
this.#optionDiagnosticText = new OptionDiagnosticText(this.#optionGroup);
|
|
2504
2768
|
this.#optionUsageText = new OptionUsageText(this.#optionGroup, this.#storeService);
|
|
2505
2769
|
}
|
|
2506
|
-
check(optionName, optionValue, optionBrand, origin) {
|
|
2770
|
+
async check(optionName, optionValue, optionBrand, origin) {
|
|
2507
2771
|
switch (optionName) {
|
|
2508
2772
|
case "config":
|
|
2509
2773
|
case "rootPath":
|
|
@@ -2513,13 +2777,11 @@ class OptionValidator {
|
|
|
2513
2777
|
}
|
|
2514
2778
|
break;
|
|
2515
2779
|
case "target":
|
|
2516
|
-
{
|
|
2517
|
-
|
|
2518
|
-
this.#
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
], origin));
|
|
2522
|
-
}
|
|
2780
|
+
if (!(await this.#storeService.validateTag(optionValue))) {
|
|
2781
|
+
this.#onDiagnostic(Diagnostic.error([
|
|
2782
|
+
this.#optionDiagnosticText.versionIsNotSupported(optionValue),
|
|
2783
|
+
...(await this.#optionUsageText.get(optionName, optionBrand)),
|
|
2784
|
+
], origin));
|
|
2523
2785
|
}
|
|
2524
2786
|
break;
|
|
2525
2787
|
}
|
|
@@ -2545,29 +2807,23 @@ class CommandLineOptionsWorker {
|
|
|
2545
2807
|
this.#optionUsageText = new OptionUsageText(2, this.#storeService);
|
|
2546
2808
|
this.#optionValidator = new OptionValidator(2, this.#storeService, this.#onDiagnostic);
|
|
2547
2809
|
}
|
|
2548
|
-
#
|
|
2549
|
-
if (path.sep === "/") {
|
|
2550
|
-
return filePath;
|
|
2551
|
-
}
|
|
2552
|
-
return filePath.replace(/\\/g, "/");
|
|
2553
|
-
}
|
|
2554
|
-
#onExpectsArgumentDiagnostic(optionDefinition) {
|
|
2810
|
+
async #onExpectsArgumentDiagnostic(optionDefinition) {
|
|
2555
2811
|
const text = [
|
|
2556
2812
|
this.#optionDiagnosticText.expectsArgument(optionDefinition.name),
|
|
2557
|
-
...this.#optionUsageText.get(optionDefinition.name, optionDefinition.brand),
|
|
2813
|
+
...(await this.#optionUsageText.get(optionDefinition.name, optionDefinition.brand)),
|
|
2558
2814
|
];
|
|
2559
2815
|
this.#onDiagnostic(Diagnostic.error(text));
|
|
2560
2816
|
}
|
|
2561
|
-
parse(commandLineArgs) {
|
|
2817
|
+
async parse(commandLineArgs) {
|
|
2562
2818
|
let index = 0;
|
|
2563
2819
|
let arg = commandLineArgs[index];
|
|
2564
|
-
while (arg
|
|
2820
|
+
while (arg != null) {
|
|
2565
2821
|
index++;
|
|
2566
2822
|
if (arg.startsWith("--")) {
|
|
2567
2823
|
const optionName = arg.slice(2);
|
|
2568
2824
|
const optionDefinition = this.#commandLineOptionDefinitions.get(optionName);
|
|
2569
2825
|
if (optionDefinition) {
|
|
2570
|
-
index = this.#parseOptionValue(commandLineArgs, index, optionDefinition);
|
|
2826
|
+
index = await this.#parseOptionValue(commandLineArgs, index, optionDefinition);
|
|
2571
2827
|
}
|
|
2572
2828
|
else {
|
|
2573
2829
|
this.#onDiagnostic(Diagnostic.error(this.#optionDiagnosticText.unknownOption(arg)));
|
|
@@ -2577,14 +2833,17 @@ class CommandLineOptionsWorker {
|
|
|
2577
2833
|
this.#onDiagnostic(Diagnostic.error(this.#optionDiagnosticText.unknownOption(arg)));
|
|
2578
2834
|
}
|
|
2579
2835
|
else {
|
|
2580
|
-
this.#pathMatch.push(arg);
|
|
2836
|
+
this.#pathMatch.push(Path.normalizeSlashes(arg));
|
|
2581
2837
|
}
|
|
2582
2838
|
arg = commandLineArgs[index];
|
|
2583
2839
|
}
|
|
2584
2840
|
}
|
|
2585
|
-
#parseOptionValue(commandLineArgs, index, optionDefinition) {
|
|
2841
|
+
async #parseOptionValue(commandLineArgs, index, optionDefinition) {
|
|
2586
2842
|
let optionValue = this.#resolveOptionValue(commandLineArgs[index]);
|
|
2587
2843
|
switch (optionDefinition.brand) {
|
|
2844
|
+
case "true":
|
|
2845
|
+
this.#commandLineOptions[optionDefinition.name] = true;
|
|
2846
|
+
break;
|
|
2588
2847
|
case "boolean":
|
|
2589
2848
|
this.#commandLineOptions[optionDefinition.name] = optionValue !== "false";
|
|
2590
2849
|
if (optionValue === "false" || optionValue === "true") {
|
|
@@ -2598,33 +2857,25 @@ class CommandLineOptionsWorker {
|
|
|
2598
2857
|
.map((value) => value.trim())
|
|
2599
2858
|
.filter((value) => value !== "");
|
|
2600
2859
|
for (const optionValue of optionValues) {
|
|
2601
|
-
this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
|
|
2860
|
+
await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
|
|
2602
2861
|
}
|
|
2603
2862
|
this.#commandLineOptions[optionDefinition.name] = optionValues;
|
|
2604
2863
|
index++;
|
|
2605
2864
|
break;
|
|
2606
2865
|
}
|
|
2607
|
-
this.#onExpectsArgumentDiagnostic(optionDefinition);
|
|
2608
|
-
break;
|
|
2609
|
-
case "number":
|
|
2610
|
-
if (optionValue != null) {
|
|
2611
|
-
this.#commandLineOptions[optionDefinition.name] = Number(optionValue);
|
|
2612
|
-
index++;
|
|
2613
|
-
break;
|
|
2614
|
-
}
|
|
2615
|
-
this.#onExpectsArgumentDiagnostic(optionDefinition);
|
|
2866
|
+
await this.#onExpectsArgumentDiagnostic(optionDefinition);
|
|
2616
2867
|
break;
|
|
2617
2868
|
case "string":
|
|
2618
2869
|
if (optionValue != null) {
|
|
2619
2870
|
if (optionDefinition.name === "config") {
|
|
2620
|
-
optionValue =
|
|
2871
|
+
optionValue = Path.resolve(optionValue);
|
|
2621
2872
|
}
|
|
2622
|
-
this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
|
|
2873
|
+
await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
|
|
2623
2874
|
this.#commandLineOptions[optionDefinition.name] = optionValue;
|
|
2624
2875
|
index++;
|
|
2625
2876
|
break;
|
|
2626
2877
|
}
|
|
2627
|
-
this.#onExpectsArgumentDiagnostic(optionDefinition);
|
|
2878
|
+
await this.#onExpectsArgumentDiagnostic(optionDefinition);
|
|
2628
2879
|
break;
|
|
2629
2880
|
}
|
|
2630
2881
|
return index;
|
|
@@ -2660,12 +2911,6 @@ class ConfigFileOptionsWorker {
|
|
|
2660
2911
|
return (node.kind === this.compiler.SyntaxKind.StringLiteral &&
|
|
2661
2912
|
sourceFile.text.slice(this.#skipTrivia(node.pos, sourceFile), node.end).startsWith('"'));
|
|
2662
2913
|
}
|
|
2663
|
-
#normalizePath(filePath) {
|
|
2664
|
-
if (path.sep === "/") {
|
|
2665
|
-
return filePath;
|
|
2666
|
-
}
|
|
2667
|
-
return filePath.replace(/\\/g, "/");
|
|
2668
|
-
}
|
|
2669
2914
|
async parse(sourceText) {
|
|
2670
2915
|
if (sourceText === "") {
|
|
2671
2916
|
return;
|
|
@@ -2744,22 +2989,17 @@ class ConfigFileOptionsWorker {
|
|
|
2744
2989
|
if (optionDefinition.brand === "string") {
|
|
2745
2990
|
let value = valueExpression.text;
|
|
2746
2991
|
if (optionDefinition.name === "rootPath") {
|
|
2747
|
-
value =
|
|
2992
|
+
value = Path.resolve(Path.dirname(this.#configFilePath), value);
|
|
2748
2993
|
}
|
|
2749
2994
|
const origin = {
|
|
2750
2995
|
end: valueExpression.end,
|
|
2751
2996
|
file: sourceFile,
|
|
2752
2997
|
start: this.#skipTrivia(valueExpression.pos, sourceFile),
|
|
2753
2998
|
};
|
|
2754
|
-
this.#optionValidator.check(optionDefinition.name, value, optionDefinition.brand, origin);
|
|
2999
|
+
await this.#optionValidator.check(optionDefinition.name, value, optionDefinition.brand, origin);
|
|
2755
3000
|
return value;
|
|
2756
3001
|
}
|
|
2757
3002
|
break;
|
|
2758
|
-
case this.compiler.SyntaxKind.NumericLiteral:
|
|
2759
|
-
if (optionDefinition.brand === "number") {
|
|
2760
|
-
return Number(valueExpression.text);
|
|
2761
|
-
}
|
|
2762
|
-
break;
|
|
2763
3003
|
case this.compiler.SyntaxKind.ArrayLiteralExpression:
|
|
2764
3004
|
if (optionDefinition.brand === "list") {
|
|
2765
3005
|
const value = [];
|
|
@@ -2866,7 +3106,7 @@ class ConfigService {
|
|
|
2866
3106
|
allowNoTestFiles: false,
|
|
2867
3107
|
failFast: false,
|
|
2868
3108
|
rootPath: "./",
|
|
2869
|
-
target: ["latest"],
|
|
3109
|
+
target: [Environment.isTypeScriptInstalled ? "current" : "latest"],
|
|
2870
3110
|
testFileMatch: ["**/*.tst.*", "**/__typetests__/*.test.*", "**/typetests/*.test.*"],
|
|
2871
3111
|
};
|
|
2872
3112
|
#pathMatch = [];
|
|
@@ -2884,25 +3124,19 @@ class ConfigService {
|
|
|
2884
3124
|
static get defaultOptions() {
|
|
2885
3125
|
return ConfigService.#defaultOptions;
|
|
2886
3126
|
}
|
|
2887
|
-
#normalizePath(filePath) {
|
|
2888
|
-
if (path.sep === "/") {
|
|
2889
|
-
return filePath;
|
|
2890
|
-
}
|
|
2891
|
-
return filePath.replace(/\\/g, "/");
|
|
2892
|
-
}
|
|
2893
3127
|
#onDiagnostic = (diagnostic) => {
|
|
2894
3128
|
EventEmitter.dispatch(["config:error", { diagnostics: [diagnostic] }]);
|
|
2895
3129
|
};
|
|
2896
|
-
parseCommandLine(commandLineArgs) {
|
|
3130
|
+
async parseCommandLine(commandLineArgs) {
|
|
2897
3131
|
this.#commandLineOptions = {};
|
|
2898
3132
|
this.#pathMatch = [];
|
|
2899
3133
|
const commandLineWorker = new CommandLineOptionsWorker(this.#commandLineOptions, this.#pathMatch, this.#storeService, this.#onDiagnostic);
|
|
2900
|
-
commandLineWorker.parse(commandLineArgs);
|
|
3134
|
+
await commandLineWorker.parse(commandLineArgs);
|
|
2901
3135
|
}
|
|
2902
3136
|
async readConfigFile(filePath, sourceText) {
|
|
2903
|
-
const configFilePath = filePath ?? this.#commandLineOptions.config ??
|
|
3137
|
+
const configFilePath = filePath ?? this.#commandLineOptions.config ?? Path.resolve("./tstyche.config.json");
|
|
2904
3138
|
this.#configFileOptions = {
|
|
2905
|
-
rootPath:
|
|
3139
|
+
rootPath: Path.dirname(configFilePath),
|
|
2906
3140
|
};
|
|
2907
3141
|
let configFileText = sourceText ?? "";
|
|
2908
3142
|
if (sourceText == null && existsSync(configFilePath)) {
|
|
@@ -2918,7 +3152,7 @@ class ConfigService {
|
|
|
2918
3152
|
...ConfigService.#defaultOptions,
|
|
2919
3153
|
...this.#configFileOptions,
|
|
2920
3154
|
...this.#commandLineOptions,
|
|
2921
|
-
pathMatch: this.#pathMatch
|
|
3155
|
+
pathMatch: this.#pathMatch,
|
|
2922
3156
|
};
|
|
2923
3157
|
return mergedOptions;
|
|
2924
3158
|
}
|
|
@@ -2927,7 +3161,7 @@ class ConfigService {
|
|
|
2927
3161
|
let testFilePaths = this.compiler.sys.readDirectory(rootPath, undefined, undefined, testFileMatch);
|
|
2928
3162
|
if (pathMatch.length > 0) {
|
|
2929
3163
|
testFilePaths = testFilePaths.filter((testFilePath) => pathMatch.some((match) => {
|
|
2930
|
-
const relativeTestFilePath =
|
|
3164
|
+
const relativeTestFilePath = Path.relative("", testFilePath);
|
|
2931
3165
|
return relativeTestFilePath.toLowerCase().includes(match.toLowerCase());
|
|
2932
3166
|
}));
|
|
2933
3167
|
}
|
|
@@ -2951,6 +3185,7 @@ var OptionBrand;
|
|
|
2951
3185
|
OptionBrand["String"] = "string";
|
|
2952
3186
|
OptionBrand["Number"] = "number";
|
|
2953
3187
|
OptionBrand["Boolean"] = "boolean";
|
|
3188
|
+
OptionBrand["True"] = "true";
|
|
2954
3189
|
OptionBrand["List"] = "list";
|
|
2955
3190
|
OptionBrand["Object"] = "object";
|
|
2956
3191
|
})(OptionBrand || (OptionBrand = {}));
|
|
@@ -2961,162 +3196,27 @@ var OptionGroup;
|
|
|
2961
3196
|
OptionGroup[OptionGroup["ConfigFile"] = 4] = "ConfigFile";
|
|
2962
3197
|
})(OptionGroup || (OptionGroup = {}));
|
|
2963
3198
|
|
|
2964
|
-
class
|
|
2965
|
-
#
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
3199
|
+
class ManifestWorker {
|
|
3200
|
+
#manifestFileName = "store-manifest.json";
|
|
3201
|
+
#manifestFilePath;
|
|
3202
|
+
#onDiagnostic;
|
|
3203
|
+
#prune;
|
|
3204
|
+
#registryUrl = new URL("https://registry.npmjs.org");
|
|
3205
|
+
#storePath;
|
|
3206
|
+
#timeout = Environment.timeout * 1000;
|
|
3207
|
+
#version = "1";
|
|
3208
|
+
constructor(storePath, onDiagnostic, prune) {
|
|
3209
|
+
this.#storePath = storePath;
|
|
3210
|
+
this.#onDiagnostic = onDiagnostic;
|
|
3211
|
+
this.#manifestFilePath = Path.join(storePath, this.#manifestFileName);
|
|
3212
|
+
this.#prune = prune;
|
|
2976
3213
|
}
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
if (
|
|
2980
|
-
|
|
2981
|
-
}
|
|
2982
|
-
if (options?.timeout == null) {
|
|
2983
|
-
return isLocked;
|
|
2984
|
-
}
|
|
2985
|
-
const waitStartTime = Date.now();
|
|
2986
|
-
while (isLocked) {
|
|
2987
|
-
if (options.signal?.aborted === true) {
|
|
2988
|
-
break;
|
|
2989
|
-
}
|
|
2990
|
-
if (Date.now() - waitStartTime > options.timeout) {
|
|
2991
|
-
options.onDiagnostic?.(`Lock wait timeout of ${options.timeout / 1000}s was exceeded.`);
|
|
2992
|
-
break;
|
|
2993
|
-
}
|
|
2994
|
-
await Lock.#sleep(1000);
|
|
2995
|
-
isLocked = existsSync(Lock.#getLockFilePath(targetPath));
|
|
3214
|
+
async #create(signal) {
|
|
3215
|
+
const manifest = await this.#load(signal);
|
|
3216
|
+
if (manifest != null) {
|
|
3217
|
+
await this.persist(manifest);
|
|
2996
3218
|
}
|
|
2997
|
-
return
|
|
2998
|
-
}
|
|
2999
|
-
release() {
|
|
3000
|
-
rmSync(this.#lockFilePath, { force: true });
|
|
3001
|
-
}
|
|
3002
|
-
static async #sleep(time) {
|
|
3003
|
-
return new Promise((resolve) => setTimeout(resolve, time));
|
|
3004
|
-
}
|
|
3005
|
-
}
|
|
3006
|
-
|
|
3007
|
-
class CompilerModuleWorker {
|
|
3008
|
-
#cachePath;
|
|
3009
|
-
#onDiagnostic;
|
|
3010
|
-
#readyFileName = "__ready__";
|
|
3011
|
-
#timeout = Environment.timeout * 1000;
|
|
3012
|
-
constructor(cachePath, onDiagnostic) {
|
|
3013
|
-
this.#cachePath = cachePath;
|
|
3014
|
-
this.#onDiagnostic = onDiagnostic;
|
|
3015
|
-
}
|
|
3016
|
-
async ensure(compilerVersion, signal) {
|
|
3017
|
-
const installationPath = path.join(this.#cachePath, compilerVersion);
|
|
3018
|
-
const readyFilePath = path.join(installationPath, this.#readyFileName);
|
|
3019
|
-
const tsserverFilePath = path.join(installationPath, "node_modules", "typescript", "lib", "tsserverlibrary.js");
|
|
3020
|
-
const typescriptFilePath = path.join(installationPath, "node_modules", "typescript", "lib", "typescript.js");
|
|
3021
|
-
if (await Lock.isLocked(installationPath, {
|
|
3022
|
-
onDiagnostic: (text) => {
|
|
3023
|
-
this.#onDiagnostic(Diagnostic.error([`Failed to install 'typescript@${compilerVersion}'.`, text]));
|
|
3024
|
-
},
|
|
3025
|
-
signal,
|
|
3026
|
-
timeout: this.#timeout,
|
|
3027
|
-
})) {
|
|
3028
|
-
return;
|
|
3029
|
-
}
|
|
3030
|
-
if (existsSync(readyFilePath)) {
|
|
3031
|
-
return tsserverFilePath;
|
|
3032
|
-
}
|
|
3033
|
-
EventEmitter.dispatch(["store:info", { compilerVersion, installationPath: this.#normalizePath(installationPath) }]);
|
|
3034
|
-
try {
|
|
3035
|
-
await fs.mkdir(installationPath, { recursive: true });
|
|
3036
|
-
const lock = new Lock(installationPath);
|
|
3037
|
-
await fs.writeFile(path.join(installationPath, "package.json"), this.#getPackageJson(compilerVersion));
|
|
3038
|
-
await this.#installPackage(installationPath, signal);
|
|
3039
|
-
await fs.writeFile(tsserverFilePath, await this.#getPatched(compilerVersion, tsserverFilePath));
|
|
3040
|
-
await fs.writeFile(typescriptFilePath, await this.#getPatched(compilerVersion, typescriptFilePath));
|
|
3041
|
-
await fs.writeFile(readyFilePath, "");
|
|
3042
|
-
lock.release();
|
|
3043
|
-
}
|
|
3044
|
-
catch (error) {
|
|
3045
|
-
this.#onDiagnostic(Diagnostic.fromError(`Failed to install 'typescript@${compilerVersion}'.`, error));
|
|
3046
|
-
}
|
|
3047
|
-
return tsserverFilePath;
|
|
3048
|
-
}
|
|
3049
|
-
#getPackageJson(version) {
|
|
3050
|
-
const packageJson = {
|
|
3051
|
-
name: "@tstyche/typescript",
|
|
3052
|
-
version,
|
|
3053
|
-
description: "Do not change. This package was generated by TSTyche",
|
|
3054
|
-
private: true,
|
|
3055
|
-
license: "MIT",
|
|
3056
|
-
dependencies: {
|
|
3057
|
-
typescript: version,
|
|
3058
|
-
},
|
|
3059
|
-
};
|
|
3060
|
-
return JSON.stringify(packageJson, null, 2);
|
|
3061
|
-
}
|
|
3062
|
-
async #getPatched(version, filePath) {
|
|
3063
|
-
function ts5Patch(match, indent) {
|
|
3064
|
-
return [match, indent, "isTypeIdenticalTo,", indent, "isTypeSubtypeOf,"].join("");
|
|
3065
|
-
}
|
|
3066
|
-
function ts4Patch(match, indent) {
|
|
3067
|
-
return [match, indent, "isTypeIdenticalTo: isTypeIdenticalTo,", indent, "isTypeSubtypeOf: isTypeSubtypeOf,"].join("");
|
|
3068
|
-
}
|
|
3069
|
-
const fileContent = await fs.readFile(filePath, { encoding: "utf8" });
|
|
3070
|
-
if (version.startsWith("5")) {
|
|
3071
|
-
return fileContent.replace(/(\s+)isTypeAssignableTo,/, ts5Patch);
|
|
3072
|
-
}
|
|
3073
|
-
else {
|
|
3074
|
-
return fileContent.replace(/(\s+)isTypeAssignableTo: isTypeAssignableTo,/, ts4Patch);
|
|
3075
|
-
}
|
|
3076
|
-
}
|
|
3077
|
-
async #installPackage(cwd, signal) {
|
|
3078
|
-
const args = ["install", "--ignore-scripts", "--no-bin-links", "--no-package-lock"];
|
|
3079
|
-
return new Promise((resolve, reject) => {
|
|
3080
|
-
const spawnedNpm = spawn("npm", args, {
|
|
3081
|
-
cwd,
|
|
3082
|
-
shell: true,
|
|
3083
|
-
signal,
|
|
3084
|
-
stdio: "ignore",
|
|
3085
|
-
timeout: this.#timeout,
|
|
3086
|
-
});
|
|
3087
|
-
spawnedNpm.on("error", (error) => {
|
|
3088
|
-
reject(error);
|
|
3089
|
-
});
|
|
3090
|
-
spawnedNpm.on("close", (code, signal) => {
|
|
3091
|
-
if (code === 0) {
|
|
3092
|
-
resolve();
|
|
3093
|
-
}
|
|
3094
|
-
if (signal != null) {
|
|
3095
|
-
reject(new Error(`Setup timeout of ${this.#timeout / 1000}s was exceeded.`));
|
|
3096
|
-
}
|
|
3097
|
-
reject(new Error(`Process exited with code ${String(code)}.`));
|
|
3098
|
-
});
|
|
3099
|
-
});
|
|
3100
|
-
}
|
|
3101
|
-
#normalizePath(filePath) {
|
|
3102
|
-
if (path.sep === "/") {
|
|
3103
|
-
return filePath;
|
|
3104
|
-
}
|
|
3105
|
-
return filePath.replace(/\\/g, "/");
|
|
3106
|
-
}
|
|
3107
|
-
}
|
|
3108
|
-
|
|
3109
|
-
class ManifestWorker {
|
|
3110
|
-
#cachePath;
|
|
3111
|
-
#manifestFileName = "store-manifest.json";
|
|
3112
|
-
#manifestFilePath;
|
|
3113
|
-
#prune;
|
|
3114
|
-
#registryUrl = new URL("https://registry.npmjs.org");
|
|
3115
|
-
#timeout = Environment.timeout * 1000;
|
|
3116
|
-
constructor(cachePath, prune) {
|
|
3117
|
-
this.#cachePath = cachePath;
|
|
3118
|
-
this.#manifestFilePath = path.join(cachePath, this.#manifestFileName);
|
|
3119
|
-
this.#prune = prune;
|
|
3219
|
+
return manifest;
|
|
3120
3220
|
}
|
|
3121
3221
|
async #fetch(signal) {
|
|
3122
3222
|
return new Promise((resolve, reject) => {
|
|
@@ -3154,8 +3254,9 @@ class ManifestWorker {
|
|
|
3154
3254
|
}
|
|
3155
3255
|
return false;
|
|
3156
3256
|
}
|
|
3157
|
-
async #load(signal,
|
|
3257
|
+
async #load(signal, options = { quite: false }) {
|
|
3158
3258
|
const manifest = {
|
|
3259
|
+
$version: this.#version,
|
|
3159
3260
|
lastUpdated: Date.now(),
|
|
3160
3261
|
resolutions: {},
|
|
3161
3262
|
versions: [],
|
|
@@ -3167,13 +3268,13 @@ class ManifestWorker {
|
|
|
3167
3268
|
abortController.abort(`Setup timeout of ${this.#timeout / 1000}s was exceeded.`);
|
|
3168
3269
|
}, { once: true });
|
|
3169
3270
|
signal?.addEventListener("abort", () => {
|
|
3170
|
-
abortController.abort(
|
|
3271
|
+
abortController.abort("Fetch got canceled by request.");
|
|
3171
3272
|
}, { once: true });
|
|
3172
3273
|
try {
|
|
3173
3274
|
packageMetadata = await this.#fetch(abortController.signal);
|
|
3174
3275
|
}
|
|
3175
3276
|
catch (error) {
|
|
3176
|
-
if (!
|
|
3277
|
+
if (!options.quite) {
|
|
3177
3278
|
const text = [`Failed to fetch metadata of the 'typescript' package from '${this.#registryUrl.href}'.`];
|
|
3178
3279
|
if (error instanceof Error && error.name !== "AbortError") {
|
|
3179
3280
|
text.push("Might be there is an issue with the registry or the network connection.");
|
|
@@ -3202,18 +3303,10 @@ class ManifestWorker {
|
|
|
3202
3303
|
}
|
|
3203
3304
|
return manifest;
|
|
3204
3305
|
}
|
|
3205
|
-
|
|
3206
|
-
EventEmitter.dispatch(["store:error", { diagnostics: [diagnostic] }]);
|
|
3207
|
-
}
|
|
3208
|
-
async open(signal) {
|
|
3306
|
+
async open(signal, options) {
|
|
3209
3307
|
let manifest;
|
|
3210
3308
|
if (!existsSync(this.#manifestFilePath)) {
|
|
3211
|
-
|
|
3212
|
-
if (manifest == null) {
|
|
3213
|
-
return;
|
|
3214
|
-
}
|
|
3215
|
-
await this.persist(manifest);
|
|
3216
|
-
return manifest;
|
|
3309
|
+
return this.#create(signal);
|
|
3217
3310
|
}
|
|
3218
3311
|
let manifestText;
|
|
3219
3312
|
try {
|
|
@@ -3228,85 +3321,248 @@ class ManifestWorker {
|
|
|
3228
3321
|
try {
|
|
3229
3322
|
manifest = JSON.parse(manifestText);
|
|
3230
3323
|
}
|
|
3231
|
-
catch
|
|
3232
|
-
this.#onDiagnostic(Diagnostic.fromError([
|
|
3233
|
-
`Failed to parse '${this.#manifestFilePath}'.`,
|
|
3234
|
-
"Cached files appeared to be corrupt and got removed. Try running 'tstyche' again.",
|
|
3235
|
-
], error));
|
|
3324
|
+
catch {
|
|
3236
3325
|
}
|
|
3237
|
-
if (manifest == null) {
|
|
3326
|
+
if (manifest == null || manifest.$version !== this.#version) {
|
|
3238
3327
|
await this.#prune();
|
|
3239
|
-
return;
|
|
3328
|
+
return this.#create(signal);
|
|
3240
3329
|
}
|
|
3241
|
-
if (this.isOutdated(manifest)) {
|
|
3242
|
-
|
|
3330
|
+
if (this.isOutdated(manifest) || options?.refresh === true) {
|
|
3331
|
+
const quite = options?.refresh !== true;
|
|
3332
|
+
const freshManifest = await this.#load(signal, { quite });
|
|
3333
|
+
if (freshManifest != null) {
|
|
3334
|
+
manifest = { ...manifest, ...freshManifest };
|
|
3335
|
+
await this.persist(manifest);
|
|
3336
|
+
}
|
|
3243
3337
|
}
|
|
3244
3338
|
return manifest;
|
|
3245
3339
|
}
|
|
3246
3340
|
async persist(manifest) {
|
|
3247
|
-
if (!existsSync(this.#
|
|
3248
|
-
await fs.mkdir(this.#
|
|
3341
|
+
if (!existsSync(this.#storePath)) {
|
|
3342
|
+
await fs.mkdir(this.#storePath, { recursive: true });
|
|
3249
3343
|
}
|
|
3250
3344
|
await fs.writeFile(this.#manifestFilePath, JSON.stringify(manifest));
|
|
3251
3345
|
}
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
class Lock {
|
|
3349
|
+
#lockFilePath;
|
|
3350
|
+
static #lockSuffix = "__lock__";
|
|
3351
|
+
constructor(targetPath) {
|
|
3352
|
+
this.#lockFilePath = Lock.#getLockFilePath(targetPath);
|
|
3353
|
+
writeFileSync(this.#lockFilePath, "");
|
|
3354
|
+
process.on("exit", () => {
|
|
3355
|
+
this.release();
|
|
3356
|
+
});
|
|
3357
|
+
}
|
|
3358
|
+
static #getLockFilePath(targetPath) {
|
|
3359
|
+
return `${targetPath}${Lock.#lockSuffix}`;
|
|
3360
|
+
}
|
|
3361
|
+
static async isLocked(targetPath, options) {
|
|
3362
|
+
let isLocked = existsSync(Lock.#getLockFilePath(targetPath));
|
|
3363
|
+
if (!isLocked) {
|
|
3364
|
+
return isLocked;
|
|
3257
3365
|
}
|
|
3258
|
-
|
|
3366
|
+
if (options?.timeout == null) {
|
|
3367
|
+
return isLocked;
|
|
3368
|
+
}
|
|
3369
|
+
const waitStartTime = Date.now();
|
|
3370
|
+
while (isLocked) {
|
|
3371
|
+
if (options.signal?.aborted === true) {
|
|
3372
|
+
break;
|
|
3373
|
+
}
|
|
3374
|
+
if (Date.now() - waitStartTime > options.timeout) {
|
|
3375
|
+
options.onDiagnostic?.(`Lock wait timeout of ${options.timeout / 1000}s was exceeded.`);
|
|
3376
|
+
break;
|
|
3377
|
+
}
|
|
3378
|
+
await Lock.#sleep(1000);
|
|
3379
|
+
isLocked = existsSync(Lock.#getLockFilePath(targetPath));
|
|
3380
|
+
}
|
|
3381
|
+
return isLocked;
|
|
3382
|
+
}
|
|
3383
|
+
release() {
|
|
3384
|
+
rmSync(this.#lockFilePath, { force: true });
|
|
3385
|
+
}
|
|
3386
|
+
static async #sleep(time) {
|
|
3387
|
+
return new Promise((resolve) => setTimeout(resolve, time));
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
class Version {
|
|
3392
|
+
static satisfies(source, target) {
|
|
3393
|
+
const sourceElements = source.split(/\.|-/);
|
|
3394
|
+
const targetElements = target.split(/\.|-/);
|
|
3395
|
+
function compare(index = 0) {
|
|
3396
|
+
const sourceElement = sourceElements[index];
|
|
3397
|
+
const targetElement = targetElements[index];
|
|
3398
|
+
if (sourceElement > targetElement) {
|
|
3399
|
+
return true;
|
|
3400
|
+
}
|
|
3401
|
+
if (sourceElement === targetElement) {
|
|
3402
|
+
if (index === targetElements.length - 1) {
|
|
3403
|
+
return true;
|
|
3404
|
+
}
|
|
3405
|
+
return compare(index + 1);
|
|
3406
|
+
}
|
|
3407
|
+
return false;
|
|
3408
|
+
}
|
|
3409
|
+
return compare();
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3413
|
+
class PackageInstaller {
|
|
3414
|
+
#onDiagnostic;
|
|
3415
|
+
#readyFileName = "__ready__";
|
|
3416
|
+
#storePath;
|
|
3417
|
+
#timeout = Environment.timeout * 1000;
|
|
3418
|
+
constructor(storePath, onDiagnostic) {
|
|
3419
|
+
this.#storePath = storePath;
|
|
3420
|
+
this.#onDiagnostic = onDiagnostic;
|
|
3421
|
+
}
|
|
3422
|
+
async ensure(compilerVersion, signal) {
|
|
3423
|
+
const installationPath = Path.join(this.#storePath, compilerVersion);
|
|
3424
|
+
const readyFilePath = Path.join(installationPath, this.#readyFileName);
|
|
3425
|
+
if (await Lock.isLocked(installationPath, {
|
|
3426
|
+
onDiagnostic: (text) => {
|
|
3427
|
+
this.#onDiagnostic(Diagnostic.error([`Failed to install 'typescript@${compilerVersion}'.`, text]));
|
|
3428
|
+
},
|
|
3429
|
+
signal,
|
|
3430
|
+
timeout: this.#timeout,
|
|
3431
|
+
})) {
|
|
3432
|
+
return;
|
|
3433
|
+
}
|
|
3434
|
+
if (!existsSync(readyFilePath)) {
|
|
3435
|
+
EventEmitter.dispatch(["store:info", { compilerVersion, installationPath }]);
|
|
3436
|
+
try {
|
|
3437
|
+
await fs.mkdir(installationPath, { recursive: true });
|
|
3438
|
+
const lock = new Lock(installationPath);
|
|
3439
|
+
const packageJson = {
|
|
3440
|
+
name: "tstyche-typescript",
|
|
3441
|
+
version: compilerVersion,
|
|
3442
|
+
description: "Do not change. This package was generated by TSTyche",
|
|
3443
|
+
private: true,
|
|
3444
|
+
license: "MIT",
|
|
3445
|
+
dependencies: {
|
|
3446
|
+
typescript: compilerVersion,
|
|
3447
|
+
},
|
|
3448
|
+
};
|
|
3449
|
+
await fs.writeFile(Path.join(installationPath, "package.json"), JSON.stringify(packageJson, null, 2));
|
|
3450
|
+
await this.#install(installationPath, signal);
|
|
3451
|
+
await fs.writeFile(readyFilePath, "");
|
|
3452
|
+
lock.release();
|
|
3453
|
+
}
|
|
3454
|
+
catch (error) {
|
|
3455
|
+
this.#onDiagnostic(Diagnostic.fromError(`Failed to install 'typescript@${compilerVersion}'.`, error));
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
if (Version.satisfies(compilerVersion, "5.3")) {
|
|
3459
|
+
return Path.join(installationPath, "node_modules", "typescript", "lib", "typescript.js");
|
|
3460
|
+
}
|
|
3461
|
+
return Path.join(installationPath, "node_modules", "typescript", "lib", "tsserverlibrary.js");
|
|
3462
|
+
}
|
|
3463
|
+
async #install(cwd, signal) {
|
|
3464
|
+
const args = ["install", "--ignore-scripts", "--no-bin-links", "--no-package-lock"];
|
|
3465
|
+
return new Promise((resolve, reject) => {
|
|
3466
|
+
const spawnedNpm = spawn("npm", args, {
|
|
3467
|
+
cwd,
|
|
3468
|
+
shell: true,
|
|
3469
|
+
signal,
|
|
3470
|
+
stdio: "ignore",
|
|
3471
|
+
timeout: this.#timeout,
|
|
3472
|
+
});
|
|
3473
|
+
spawnedNpm.on("error", (error) => {
|
|
3474
|
+
reject(error);
|
|
3475
|
+
});
|
|
3476
|
+
spawnedNpm.on("close", (code, signal) => {
|
|
3477
|
+
if (code === 0) {
|
|
3478
|
+
resolve();
|
|
3479
|
+
}
|
|
3480
|
+
if (signal != null) {
|
|
3481
|
+
reject(new Error(`Setup timeout of ${this.#timeout / 1000}s was exceeded.`));
|
|
3482
|
+
}
|
|
3483
|
+
reject(new Error(`Process exited with code ${String(code)}.`));
|
|
3484
|
+
});
|
|
3485
|
+
});
|
|
3259
3486
|
}
|
|
3260
3487
|
}
|
|
3261
3488
|
|
|
3262
3489
|
class StoreService {
|
|
3263
|
-
#
|
|
3264
|
-
#compilerModuleWorker;
|
|
3490
|
+
#compilerInstanceCache = new Map();
|
|
3265
3491
|
#manifest;
|
|
3266
3492
|
#manifestWorker;
|
|
3267
3493
|
#nodeRequire = createRequire(import.meta.url);
|
|
3494
|
+
#packageInstaller;
|
|
3495
|
+
#storePath;
|
|
3268
3496
|
constructor() {
|
|
3269
|
-
this.#
|
|
3270
|
-
this.#
|
|
3271
|
-
this.#manifestWorker = new ManifestWorker(this.#
|
|
3497
|
+
this.#storePath = Environment.storePath;
|
|
3498
|
+
this.#packageInstaller = new PackageInstaller(this.#storePath, this.#onDiagnostic);
|
|
3499
|
+
this.#manifestWorker = new ManifestWorker(this.#storePath, this.#onDiagnostic, this.prune);
|
|
3272
3500
|
}
|
|
3273
|
-
|
|
3501
|
+
async getSupportedTags(signal) {
|
|
3502
|
+
await this.open(signal);
|
|
3274
3503
|
if (!this.#manifest) {
|
|
3275
|
-
this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
|
|
3276
3504
|
return [];
|
|
3277
3505
|
}
|
|
3278
3506
|
return [...Object.keys(this.#manifest.resolutions), ...this.#manifest.versions, "current"].sort();
|
|
3279
3507
|
}
|
|
3280
3508
|
async install(tag, signal) {
|
|
3281
|
-
if (
|
|
3282
|
-
this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
|
|
3509
|
+
if (tag === "current") {
|
|
3283
3510
|
return;
|
|
3284
3511
|
}
|
|
3285
|
-
const version = this.resolveTag(tag);
|
|
3512
|
+
const version = await this.resolveTag(tag, signal);
|
|
3286
3513
|
if (version == null) {
|
|
3287
3514
|
this.#onDiagnostic(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
|
|
3288
3515
|
return;
|
|
3289
3516
|
}
|
|
3290
|
-
return this.#
|
|
3517
|
+
return this.#packageInstaller.ensure(version, signal);
|
|
3291
3518
|
}
|
|
3292
3519
|
async load(tag, signal) {
|
|
3520
|
+
let compilerInstance = this.#compilerInstanceCache.get(tag);
|
|
3521
|
+
if (compilerInstance != null) {
|
|
3522
|
+
return compilerInstance;
|
|
3523
|
+
}
|
|
3293
3524
|
let modulePath;
|
|
3294
|
-
if (tag === "
|
|
3525
|
+
if (tag === "current") {
|
|
3295
3526
|
try {
|
|
3296
|
-
modulePath = this.#nodeRequire.resolve("typescript
|
|
3527
|
+
modulePath = this.#nodeRequire.resolve("typescript");
|
|
3297
3528
|
}
|
|
3298
|
-
catch {
|
|
3299
|
-
|
|
3529
|
+
catch (error) {
|
|
3530
|
+
this.#onDiagnostic(Diagnostic.fromError("Failed to resolve locally installed 'typescript' package. It might be not installed.", error));
|
|
3300
3531
|
}
|
|
3301
3532
|
}
|
|
3302
3533
|
if (modulePath == null) {
|
|
3303
|
-
|
|
3534
|
+
if (tag === "current") {
|
|
3535
|
+
return;
|
|
3536
|
+
}
|
|
3537
|
+
const version = await this.resolveTag(tag, signal);
|
|
3538
|
+
if (version == null) {
|
|
3539
|
+
this.#onDiagnostic(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
|
|
3540
|
+
return;
|
|
3541
|
+
}
|
|
3542
|
+
compilerInstance = this.#compilerInstanceCache.get(version);
|
|
3543
|
+
if (compilerInstance != null) {
|
|
3544
|
+
return compilerInstance;
|
|
3545
|
+
}
|
|
3546
|
+
modulePath = await this.#packageInstaller.ensure(version, signal);
|
|
3304
3547
|
}
|
|
3305
3548
|
if (modulePath != null) {
|
|
3306
|
-
|
|
3549
|
+
compilerInstance = await this.#loadModule(modulePath);
|
|
3550
|
+
this.#compilerInstanceCache.set(tag, compilerInstance);
|
|
3551
|
+
this.#compilerInstanceCache.set(compilerInstance.version, compilerInstance);
|
|
3552
|
+
return compilerInstance;
|
|
3307
3553
|
}
|
|
3308
3554
|
return;
|
|
3309
3555
|
}
|
|
3556
|
+
async #loadModule(modulePath) {
|
|
3557
|
+
const exports = {};
|
|
3558
|
+
const require = createRequire(modulePath);
|
|
3559
|
+
const module = { exports };
|
|
3560
|
+
let sourceText = await fs.readFile(modulePath, { encoding: "utf8" });
|
|
3561
|
+
sourceText = sourceText.replace("isTypeAssignableTo,", "isTypeAssignableTo, isTypeIdenticalTo, isTypeSubtypeOf,");
|
|
3562
|
+
const compiledWrapper = vm.compileFunction(sourceText, ["exports", "require", "module", "__filename", "__dirname"], { filename: modulePath });
|
|
3563
|
+
compiledWrapper(exports, require, module, modulePath, Path.dirname(modulePath));
|
|
3564
|
+
return module.exports;
|
|
3565
|
+
}
|
|
3310
3566
|
#onDiagnostic = (diagnostic) => {
|
|
3311
3567
|
EventEmitter.dispatch(["store:error", { diagnostics: [diagnostic] }]);
|
|
3312
3568
|
};
|
|
@@ -3316,22 +3572,17 @@ class StoreService {
|
|
|
3316
3572
|
}
|
|
3317
3573
|
this.#manifest = await this.#manifestWorker.open(signal);
|
|
3318
3574
|
}
|
|
3319
|
-
async
|
|
3320
|
-
await fs.rm(this.#
|
|
3321
|
-
}
|
|
3322
|
-
resolveTag(tag) {
|
|
3575
|
+
prune = async () => {
|
|
3576
|
+
await fs.rm(this.#storePath, { force: true, recursive: true });
|
|
3577
|
+
};
|
|
3578
|
+
async resolveTag(tag, signal) {
|
|
3579
|
+
if (tag === "current") {
|
|
3580
|
+
return tag;
|
|
3581
|
+
}
|
|
3582
|
+
await this.open(signal);
|
|
3323
3583
|
if (!this.#manifest) {
|
|
3324
|
-
this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
|
|
3325
3584
|
return;
|
|
3326
3585
|
}
|
|
3327
|
-
if (tag === "current") {
|
|
3328
|
-
try {
|
|
3329
|
-
tag = this.#nodeRequire("typescript").version;
|
|
3330
|
-
}
|
|
3331
|
-
catch (error) {
|
|
3332
|
-
this.#onDiagnostic(Diagnostic.fromError("Failed to resolve tag 'current'. The 'typescript' package might be not installed.", error));
|
|
3333
|
-
}
|
|
3334
|
-
}
|
|
3335
3586
|
if (this.#manifest.versions.includes(tag)) {
|
|
3336
3587
|
return tag;
|
|
3337
3588
|
}
|
|
@@ -3346,15 +3597,14 @@ class StoreService {
|
|
|
3346
3597
|
return version;
|
|
3347
3598
|
}
|
|
3348
3599
|
async update(signal) {
|
|
3349
|
-
|
|
3350
|
-
this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
|
|
3351
|
-
return;
|
|
3352
|
-
}
|
|
3353
|
-
await this.#manifestWorker.update(this.#manifest, signal);
|
|
3600
|
+
await this.#manifestWorker.open(signal, { refresh: true });
|
|
3354
3601
|
}
|
|
3355
|
-
validateTag(tag) {
|
|
3602
|
+
async validateTag(tag, signal) {
|
|
3603
|
+
if (tag === "current") {
|
|
3604
|
+
return true;
|
|
3605
|
+
}
|
|
3606
|
+
await this.open(signal);
|
|
3356
3607
|
if (!this.#manifest) {
|
|
3357
|
-
this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
|
|
3358
3608
|
return false;
|
|
3359
3609
|
}
|
|
3360
3610
|
if (this.#manifest.versions.includes(tag) || tag in this.#manifest.resolutions || tag === "current") {
|
|
@@ -3371,154 +3621,6 @@ class StoreService {
|
|
|
3371
3621
|
}
|
|
3372
3622
|
}
|
|
3373
3623
|
|
|
3374
|
-
class JsonText {
|
|
3375
|
-
props;
|
|
3376
|
-
constructor(props) {
|
|
3377
|
-
this.props = props;
|
|
3378
|
-
}
|
|
3379
|
-
render() {
|
|
3380
|
-
return Scribbler.createElement(Line, null, JSON.stringify(this.#sortObject(this.props.input), null, 2));
|
|
3381
|
-
}
|
|
3382
|
-
#sortObject(target) {
|
|
3383
|
-
if (Array.isArray(target)) {
|
|
3384
|
-
return target;
|
|
3385
|
-
}
|
|
3386
|
-
return Object.keys(target)
|
|
3387
|
-
.sort()
|
|
3388
|
-
.reduce((result, key) => {
|
|
3389
|
-
result[key] = target[key];
|
|
3390
|
-
return result;
|
|
3391
|
-
}, {});
|
|
3392
|
-
}
|
|
3393
|
-
}
|
|
3394
|
-
function formattedText(input) {
|
|
3395
|
-
if (typeof input === "string") {
|
|
3396
|
-
return Scribbler.createElement(Line, null, input);
|
|
3397
|
-
}
|
|
3398
|
-
return Scribbler.createElement(JsonText, { input: input });
|
|
3399
|
-
}
|
|
3400
|
-
|
|
3401
|
-
const usageExamples = [
|
|
3402
|
-
["tstyche", "Run all tests."],
|
|
3403
|
-
["tstyche path/to/first.test.ts second", "Only run the test files with matching path."],
|
|
3404
|
-
["tstyche --target 4.7,4.8,latest", "Test on all specified versions of TypeScript."],
|
|
3405
|
-
];
|
|
3406
|
-
class HintText {
|
|
3407
|
-
props;
|
|
3408
|
-
constructor(props) {
|
|
3409
|
-
this.props = props;
|
|
3410
|
-
}
|
|
3411
|
-
render() {
|
|
3412
|
-
return (Scribbler.createElement(Text, { indent: 1, color: "90" }, this.props.children));
|
|
3413
|
-
}
|
|
3414
|
-
}
|
|
3415
|
-
class HelpHeaderText {
|
|
3416
|
-
props;
|
|
3417
|
-
constructor(props) {
|
|
3418
|
-
this.props = props;
|
|
3419
|
-
}
|
|
3420
|
-
render() {
|
|
3421
|
-
const hint = (Scribbler.createElement(HintText, null,
|
|
3422
|
-
Scribbler.createElement(Text, null, this.props.tstycheVersion)));
|
|
3423
|
-
return (Scribbler.createElement(Line, null,
|
|
3424
|
-
Scribbler.createElement(Text, null, "The TSTyche Type Test Runner"),
|
|
3425
|
-
hint));
|
|
3426
|
-
}
|
|
3427
|
-
}
|
|
3428
|
-
class CommandText {
|
|
3429
|
-
props;
|
|
3430
|
-
constructor(props) {
|
|
3431
|
-
this.props = props;
|
|
3432
|
-
}
|
|
3433
|
-
render() {
|
|
3434
|
-
let hint = undefined;
|
|
3435
|
-
if (this.props.hint != null) {
|
|
3436
|
-
hint = Scribbler.createElement(HintText, null, this.props.hint);
|
|
3437
|
-
}
|
|
3438
|
-
return (Scribbler.createElement(Line, { indent: 1 },
|
|
3439
|
-
Scribbler.createElement(Text, { color: "34" }, this.props.text),
|
|
3440
|
-
hint));
|
|
3441
|
-
}
|
|
3442
|
-
}
|
|
3443
|
-
class OptionDescriptionText {
|
|
3444
|
-
props;
|
|
3445
|
-
constructor(props) {
|
|
3446
|
-
this.props = props;
|
|
3447
|
-
}
|
|
3448
|
-
render() {
|
|
3449
|
-
return Scribbler.createElement(Line, { indent: 1 }, this.props.text);
|
|
3450
|
-
}
|
|
3451
|
-
}
|
|
3452
|
-
class CliUsageText {
|
|
3453
|
-
render() {
|
|
3454
|
-
const usageText = usageExamples.map(([commandText, descriptionText]) => (Scribbler.createElement(Text, null,
|
|
3455
|
-
Scribbler.createElement(CommandText, { text: commandText }),
|
|
3456
|
-
Scribbler.createElement(OptionDescriptionText, { text: descriptionText }),
|
|
3457
|
-
Scribbler.createElement(Line, null))));
|
|
3458
|
-
return Scribbler.createElement(Text, null, usageText);
|
|
3459
|
-
}
|
|
3460
|
-
}
|
|
3461
|
-
class OptionNameText {
|
|
3462
|
-
props;
|
|
3463
|
-
constructor(props) {
|
|
3464
|
-
this.props = props;
|
|
3465
|
-
}
|
|
3466
|
-
render() {
|
|
3467
|
-
return Scribbler.createElement(Text, null,
|
|
3468
|
-
"--",
|
|
3469
|
-
this.props.text);
|
|
3470
|
-
}
|
|
3471
|
-
}
|
|
3472
|
-
class OptionHintText {
|
|
3473
|
-
props;
|
|
3474
|
-
constructor(props) {
|
|
3475
|
-
this.props = props;
|
|
3476
|
-
}
|
|
3477
|
-
render() {
|
|
3478
|
-
if (this.props.definition.brand === "list") {
|
|
3479
|
-
return (Scribbler.createElement(Text, null,
|
|
3480
|
-
this.props.definition.brand,
|
|
3481
|
-
" of ",
|
|
3482
|
-
this.props.definition.items.brand,
|
|
3483
|
-
"s"));
|
|
3484
|
-
}
|
|
3485
|
-
return Scribbler.createElement(Text, null, this.props.definition.brand);
|
|
3486
|
-
}
|
|
3487
|
-
}
|
|
3488
|
-
class CliOptionsText {
|
|
3489
|
-
props;
|
|
3490
|
-
constructor(props) {
|
|
3491
|
-
this.props = props;
|
|
3492
|
-
}
|
|
3493
|
-
render() {
|
|
3494
|
-
const definitions = Array.from(this.props.optionDefinitions.values());
|
|
3495
|
-
const optionsText = definitions.map((definition) => (Scribbler.createElement(Text, null,
|
|
3496
|
-
Scribbler.createElement(CommandText, { text: Scribbler.createElement(OptionNameText, { text: definition.name }), hint: Scribbler.createElement(OptionHintText, { definition: definition }) }),
|
|
3497
|
-
Scribbler.createElement(OptionDescriptionText, { text: definition.description }),
|
|
3498
|
-
Scribbler.createElement(Line, null))));
|
|
3499
|
-
return (Scribbler.createElement(Text, null,
|
|
3500
|
-
Scribbler.createElement(Line, null, "CLI Options"),
|
|
3501
|
-
Scribbler.createElement(Line, null),
|
|
3502
|
-
optionsText));
|
|
3503
|
-
}
|
|
3504
|
-
}
|
|
3505
|
-
class HelpFooterText {
|
|
3506
|
-
render() {
|
|
3507
|
-
return Scribbler.createElement(Line, null, "To learn more, visit https://tstyche.org");
|
|
3508
|
-
}
|
|
3509
|
-
}
|
|
3510
|
-
function helpText(optionDefinitions, tstycheVersion) {
|
|
3511
|
-
return (Scribbler.createElement(Text, null,
|
|
3512
|
-
Scribbler.createElement(HelpHeaderText, { tstycheVersion: tstycheVersion }),
|
|
3513
|
-
Scribbler.createElement(Line, null),
|
|
3514
|
-
Scribbler.createElement(CliUsageText, null),
|
|
3515
|
-
Scribbler.createElement(Line, null),
|
|
3516
|
-
Scribbler.createElement(CliOptionsText, { optionDefinitions: optionDefinitions }),
|
|
3517
|
-
Scribbler.createElement(Line, null),
|
|
3518
|
-
Scribbler.createElement(HelpFooterText, null),
|
|
3519
|
-
Scribbler.createElement(Line, null)));
|
|
3520
|
-
}
|
|
3521
|
-
|
|
3522
3624
|
class Cli {
|
|
3523
3625
|
#abortController = new AbortController();
|
|
3524
3626
|
#logger;
|
|
@@ -3551,36 +3653,35 @@ class Cli {
|
|
|
3551
3653
|
break;
|
|
3552
3654
|
}
|
|
3553
3655
|
};
|
|
3554
|
-
async run(
|
|
3656
|
+
async run(commandLineArguments) {
|
|
3555
3657
|
EventEmitter.addHandler(this.#onStartupEvent);
|
|
3556
|
-
|
|
3557
|
-
|
|
3658
|
+
if (commandLineArguments.includes("--help")) {
|
|
3659
|
+
const commandLineOptionDefinitions = OptionDefinitionsMap.for(2);
|
|
3660
|
+
this.#logger.writeMessage(helpText(commandLineOptionDefinitions, TSTyche.version));
|
|
3558
3661
|
return;
|
|
3559
3662
|
}
|
|
3560
|
-
|
|
3561
|
-
|
|
3663
|
+
if (commandLineArguments.includes("--prune")) {
|
|
3664
|
+
await this.#storeService.prune();
|
|
3562
3665
|
return;
|
|
3563
3666
|
}
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
if (this.#process.exitCode === 1) {
|
|
3667
|
+
if (commandLineArguments.includes("--version")) {
|
|
3668
|
+
this.#logger.writeMessage(formattedText(TSTyche.version));
|
|
3567
3669
|
return;
|
|
3568
3670
|
}
|
|
3569
|
-
if (
|
|
3570
|
-
await this.#storeService.
|
|
3671
|
+
if (commandLineArguments.includes("--update")) {
|
|
3672
|
+
await this.#storeService.update(this.#abortController.signal);
|
|
3571
3673
|
return;
|
|
3572
3674
|
}
|
|
3573
|
-
if (
|
|
3574
|
-
const commandLineOptionDefinitions = OptionDefinitionsMap.for(2);
|
|
3575
|
-
this.#logger.writeMessage(helpText(commandLineOptionDefinitions, TSTyche.version));
|
|
3675
|
+
if (this.#process.exitCode === 1) {
|
|
3576
3676
|
return;
|
|
3577
3677
|
}
|
|
3578
|
-
|
|
3579
|
-
|
|
3678
|
+
const compiler = await this.#storeService.load(Environment.isTypeScriptInstalled ? "current" : "latest", this.#abortController.signal);
|
|
3679
|
+
if (!compiler) {
|
|
3580
3680
|
return;
|
|
3581
3681
|
}
|
|
3582
|
-
|
|
3583
|
-
|
|
3682
|
+
const configService = new ConfigService(compiler, this.#storeService);
|
|
3683
|
+
await configService.parseCommandLine(commandLineArguments);
|
|
3684
|
+
if (this.#process.exitCode === 1) {
|
|
3584
3685
|
return;
|
|
3585
3686
|
}
|
|
3586
3687
|
await configService.readConfigFile();
|
|
@@ -3617,4 +3718,4 @@ class Cli {
|
|
|
3617
3718
|
}
|
|
3618
3719
|
}
|
|
3619
3720
|
|
|
3620
|
-
export { Assertion,
|
|
3721
|
+
export { Assertion, Cli, CollectService, Color, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, Environment, EventEmitter, Expect, ExpectResult, FileResult, Line, Logger, OptionBrand, OptionDefinitionsMap, OptionGroup, Path, ProjectResult, ProjectService, Reporter, Result, ResultCount, ResultManager, ResultStatus, ResultTiming, Scribbler, StoreService, SummaryReporter, TSTyche, TargetResult, TaskRunner, TestMember, TestMemberBrand, TestMemberFlags, TestResult, TestTree, Text, ThoroughReporter, addsPackageStepText, describeNameText, diagnosticText, fileStatusText, fileViewText, formattedText, helpText, summaryText, testNameText, usesCompilerStepText };
|