tstyche 1.0.0 → 2.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/build/index.d.cts +109 -50
- package/build/index.d.ts +109 -50
- package/build/tstyche.d.ts +101 -83
- package/build/tstyche.js +719 -388
- package/package.json +18 -23
- package/CHANGELOG.md +0 -156
package/build/tstyche.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
3
|
+
import path from 'node:path';
|
|
3
4
|
import { createRequire } from 'node:module';
|
|
4
5
|
import os from 'node:os';
|
|
5
|
-
import path from 'node:path';
|
|
6
6
|
import { existsSync, writeFileSync, rmSync } from 'node:fs';
|
|
7
7
|
import fs from 'node:fs/promises';
|
|
8
8
|
import vm from 'node:vm';
|
|
@@ -24,103 +24,6 @@ class EventEmitter {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
class Path {
|
|
28
|
-
static dirname(filePath) {
|
|
29
|
-
return Path.normalizeSlashes(path.dirname(filePath));
|
|
30
|
-
}
|
|
31
|
-
static join(...filePaths) {
|
|
32
|
-
return Path.normalizeSlashes(path.join(...filePaths));
|
|
33
|
-
}
|
|
34
|
-
static normalizeSlashes(filePath) {
|
|
35
|
-
if (path.sep === "/") {
|
|
36
|
-
return filePath;
|
|
37
|
-
}
|
|
38
|
-
return filePath.replaceAll("\\", "/");
|
|
39
|
-
}
|
|
40
|
-
static relative(from, to) {
|
|
41
|
-
let relativePath = path.relative(from, to);
|
|
42
|
-
if (!relativePath.startsWith(".")) {
|
|
43
|
-
relativePath = `./${relativePath}`;
|
|
44
|
-
}
|
|
45
|
-
return Path.normalizeSlashes(relativePath);
|
|
46
|
-
}
|
|
47
|
-
static resolve(...filePaths) {
|
|
48
|
-
return Path.normalizeSlashes(path.resolve(...filePaths));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
class Environment {
|
|
53
|
-
static #noColor = Environment.#resolveNoColor();
|
|
54
|
-
static #noInteractive = Environment.#resolveNoInteractive();
|
|
55
|
-
static #storePath = Environment.#resolveStorePath();
|
|
56
|
-
static #timeout = Environment.#resolveTimeout();
|
|
57
|
-
static #typescriptPath = Environment.#resolveTypeScriptPath();
|
|
58
|
-
static get noColor() {
|
|
59
|
-
return Environment.#noColor;
|
|
60
|
-
}
|
|
61
|
-
static get noInteractive() {
|
|
62
|
-
return Environment.#noInteractive;
|
|
63
|
-
}
|
|
64
|
-
static get storePath() {
|
|
65
|
-
return Environment.#storePath;
|
|
66
|
-
}
|
|
67
|
-
static get timeout() {
|
|
68
|
-
return Environment.#timeout;
|
|
69
|
-
}
|
|
70
|
-
static get typescriptPath() {
|
|
71
|
-
return Environment.#typescriptPath;
|
|
72
|
-
}
|
|
73
|
-
static #resolveNoColor() {
|
|
74
|
-
if (process.env["TSTYCHE_NO_COLOR"] != null) {
|
|
75
|
-
return process.env["TSTYCHE_NO_COLOR"] !== "";
|
|
76
|
-
}
|
|
77
|
-
if (process.env["NO_COLOR"] != null) {
|
|
78
|
-
return process.env["NO_COLOR"] !== "";
|
|
79
|
-
}
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
static #resolveNoInteractive() {
|
|
83
|
-
if (process.env["TSTYCHE_NO_INTERACTIVE"] != null) {
|
|
84
|
-
return process.env["TSTYCHE_NO_INTERACTIVE"] !== "";
|
|
85
|
-
}
|
|
86
|
-
return !process.stdout.isTTY;
|
|
87
|
-
}
|
|
88
|
-
static #resolveStorePath() {
|
|
89
|
-
if (process.env["TSTYCHE_STORE_PATH"] != null) {
|
|
90
|
-
return Path.resolve(process.env["TSTYCHE_STORE_PATH"]);
|
|
91
|
-
}
|
|
92
|
-
if (process.platform === "darwin") {
|
|
93
|
-
return Path.resolve(os.homedir(), "Library", "TSTyche");
|
|
94
|
-
}
|
|
95
|
-
if (process.env["LocalAppData"] != null) {
|
|
96
|
-
return Path.resolve(process.env["LocalAppData"], "TSTyche");
|
|
97
|
-
}
|
|
98
|
-
if (process.env["XDG_DATA_HOME"] != null) {
|
|
99
|
-
return Path.resolve(process.env["XDG_DATA_HOME"], "TSTyche");
|
|
100
|
-
}
|
|
101
|
-
return Path.resolve(os.homedir(), ".local", "share", "TSTyche");
|
|
102
|
-
}
|
|
103
|
-
static #resolveTimeout() {
|
|
104
|
-
if (process.env["TSTYCHE_TIMEOUT"] != null) {
|
|
105
|
-
return Number(process.env["TSTYCHE_TIMEOUT"]);
|
|
106
|
-
}
|
|
107
|
-
return 30;
|
|
108
|
-
}
|
|
109
|
-
static #resolveTypeScriptPath() {
|
|
110
|
-
let moduleId = "typescript";
|
|
111
|
-
if (process.env["TSTYCHE_TYPESCRIPT_PATH"] != null) {
|
|
112
|
-
moduleId = process.env["TSTYCHE_TYPESCRIPT_PATH"];
|
|
113
|
-
}
|
|
114
|
-
let resolvedPath;
|
|
115
|
-
try {
|
|
116
|
-
resolvedPath = createRequire(import.meta.url).resolve(moduleId);
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
}
|
|
120
|
-
return resolvedPath;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
27
|
var Color;
|
|
125
28
|
(function (Color) {
|
|
126
29
|
Color["Reset"] = "0";
|
|
@@ -134,8 +37,10 @@ var Color;
|
|
|
134
37
|
})(Color || (Color = {}));
|
|
135
38
|
|
|
136
39
|
class Scribbler {
|
|
40
|
+
#indentStep = " ";
|
|
137
41
|
#newLine;
|
|
138
42
|
#noColor;
|
|
43
|
+
#notEmptyLineRegex = /^(?!$)/gm;
|
|
139
44
|
constructor(options) {
|
|
140
45
|
this.#newLine = options?.newLine ?? "\n";
|
|
141
46
|
this.#noColor = options?.noColor ?? false;
|
|
@@ -152,9 +57,7 @@ class Scribbler {
|
|
|
152
57
|
return ["\u001B[", Array.isArray(attributes) ? attributes.join(";") : attributes, "m"].join("");
|
|
153
58
|
}
|
|
154
59
|
#indentEachLine(lines, level) {
|
|
155
|
-
|
|
156
|
-
const notEmptyLineRegExp = /^(?!$)/gm;
|
|
157
|
-
return lines.replaceAll(notEmptyLineRegExp, indentStep.repeat(level));
|
|
60
|
+
return lines.replace(this.#notEmptyLineRegex, this.#indentStep.repeat(level));
|
|
158
61
|
}
|
|
159
62
|
render(element) {
|
|
160
63
|
if (element != null) {
|
|
@@ -245,49 +148,6 @@ class Line {
|
|
|
245
148
|
}
|
|
246
149
|
}
|
|
247
150
|
|
|
248
|
-
class Logger {
|
|
249
|
-
#noColor;
|
|
250
|
-
#scribbler;
|
|
251
|
-
#stderr;
|
|
252
|
-
#stdout;
|
|
253
|
-
constructor(options) {
|
|
254
|
-
this.#noColor = options?.noColor ?? Environment.noColor;
|
|
255
|
-
this.#stderr = options?.stderr ?? process.stderr;
|
|
256
|
-
this.#stdout = options?.stdout ?? process.stdout;
|
|
257
|
-
this.#scribbler = new Scribbler({ noColor: this.#noColor });
|
|
258
|
-
}
|
|
259
|
-
eraseLastLine() {
|
|
260
|
-
this.#stdout.write("\u001B[1A\u001B[0K");
|
|
261
|
-
}
|
|
262
|
-
#write(stream, body) {
|
|
263
|
-
const elements = Array.isArray(body) ? body : [body];
|
|
264
|
-
for (const element of elements) {
|
|
265
|
-
if (element.$$typeof !== Symbol.for("tstyche:scribbler")) {
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
stream.write(this.#scribbler.render(element));
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
writeError(body) {
|
|
272
|
-
this.#write(this.#stderr, body);
|
|
273
|
-
}
|
|
274
|
-
writeMessage(body) {
|
|
275
|
-
this.#write(this.#stdout, body);
|
|
276
|
-
}
|
|
277
|
-
writeWarning(body) {
|
|
278
|
-
this.#write(this.#stderr, body);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
class Reporter {
|
|
283
|
-
resolvedConfig;
|
|
284
|
-
logger;
|
|
285
|
-
constructor(resolvedConfig) {
|
|
286
|
-
this.resolvedConfig = resolvedConfig;
|
|
287
|
-
this.logger = new Logger();
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
151
|
function addsPackageStepText(compilerVersion, installationPath) {
|
|
292
152
|
return (Scribbler.createElement(Line, null,
|
|
293
153
|
Scribbler.createElement(Text, { color: "90" }, "adds"),
|
|
@@ -302,6 +162,31 @@ function describeNameText(name, indent = 0) {
|
|
|
302
162
|
return Scribbler.createElement(Line, { indent: indent + 1 }, name);
|
|
303
163
|
}
|
|
304
164
|
|
|
165
|
+
class Path {
|
|
166
|
+
static dirname(filePath) {
|
|
167
|
+
return Path.normalizeSlashes(path.dirname(filePath));
|
|
168
|
+
}
|
|
169
|
+
static join(...filePaths) {
|
|
170
|
+
return Path.normalizeSlashes(path.join(...filePaths));
|
|
171
|
+
}
|
|
172
|
+
static normalizeSlashes(filePath) {
|
|
173
|
+
if (path.sep === "/") {
|
|
174
|
+
return filePath;
|
|
175
|
+
}
|
|
176
|
+
return filePath.replace(/\\/g, "/");
|
|
177
|
+
}
|
|
178
|
+
static relative(from, to) {
|
|
179
|
+
let relativePath = path.relative(from, to);
|
|
180
|
+
if (!relativePath.startsWith("./")) {
|
|
181
|
+
relativePath = `./${relativePath}`;
|
|
182
|
+
}
|
|
183
|
+
return Path.normalizeSlashes(relativePath);
|
|
184
|
+
}
|
|
185
|
+
static resolve(...filePaths) {
|
|
186
|
+
return Path.normalizeSlashes(path.resolve(...filePaths));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
305
190
|
class CodeSpanText {
|
|
306
191
|
props;
|
|
307
192
|
constructor(props) {
|
|
@@ -312,7 +197,7 @@ class CodeSpanText {
|
|
|
312
197
|
const { character: markedCharacter, line: markedLine } = this.props.file.getLineAndCharacterOfPosition(this.props.start);
|
|
313
198
|
const firstLine = Math.max(markedLine - 2, 0);
|
|
314
199
|
const lastLine = Math.min(firstLine + 5, lastLineInFile);
|
|
315
|
-
const lineNumberMaxWidth =
|
|
200
|
+
const lineNumberMaxWidth = String(lastLine + 1).length;
|
|
316
201
|
const codeSpan = [];
|
|
317
202
|
for (let index = firstLine; index <= lastLine; index++) {
|
|
318
203
|
const lineStart = this.props.file.getPositionOfLineAndCharacter(index, 0);
|
|
@@ -320,7 +205,7 @@ class CodeSpanText {
|
|
|
320
205
|
? this.props.file.text.length
|
|
321
206
|
: this.props.file.getPositionOfLineAndCharacter(index + 1, 0);
|
|
322
207
|
const lineNumberText = String(index + 1);
|
|
323
|
-
const lineText = this.props.file.text.slice(lineStart, lineEnd).trimEnd().
|
|
208
|
+
const lineText = this.props.file.text.slice(lineStart, lineEnd).trimEnd().replace(/\t/g, " ");
|
|
324
209
|
if (index === markedLine) {
|
|
325
210
|
codeSpan.push(Scribbler.createElement(Line, null,
|
|
326
211
|
Scribbler.createElement(Text, { color: "31" }, ">"),
|
|
@@ -609,6 +494,125 @@ function helpText(optionDefinitions, tstycheVersion) {
|
|
|
609
494
|
Scribbler.createElement(Line, null)));
|
|
610
495
|
}
|
|
611
496
|
|
|
497
|
+
class Environment {
|
|
498
|
+
static #isCi = Environment.#resolveIsCi();
|
|
499
|
+
static #noColor = Environment.#resolveNoColor();
|
|
500
|
+
static #noInteractive = Environment.#resolveNoInteractive();
|
|
501
|
+
static #storePath = Environment.#resolveStorePath();
|
|
502
|
+
static #timeout = Environment.#resolveTimeout();
|
|
503
|
+
static #typescriptPath = Environment.#resolveTypeScriptPath();
|
|
504
|
+
static get isCi() {
|
|
505
|
+
return Environment.#isCi;
|
|
506
|
+
}
|
|
507
|
+
static get noColor() {
|
|
508
|
+
return Environment.#noColor;
|
|
509
|
+
}
|
|
510
|
+
static get noInteractive() {
|
|
511
|
+
return Environment.#noInteractive;
|
|
512
|
+
}
|
|
513
|
+
static get storePath() {
|
|
514
|
+
return Environment.#storePath;
|
|
515
|
+
}
|
|
516
|
+
static get timeout() {
|
|
517
|
+
return Environment.#timeout;
|
|
518
|
+
}
|
|
519
|
+
static get typescriptPath() {
|
|
520
|
+
return Environment.#typescriptPath;
|
|
521
|
+
}
|
|
522
|
+
static #resolveIsCi() {
|
|
523
|
+
if (process.env["CI"] != null) {
|
|
524
|
+
return process.env["CI"] !== "";
|
|
525
|
+
}
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
static #resolveNoColor() {
|
|
529
|
+
if (process.env["TSTYCHE_NO_COLOR"] != null) {
|
|
530
|
+
return process.env["TSTYCHE_NO_COLOR"] !== "";
|
|
531
|
+
}
|
|
532
|
+
if (process.env["NO_COLOR"] != null) {
|
|
533
|
+
return process.env["NO_COLOR"] !== "";
|
|
534
|
+
}
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
static #resolveNoInteractive() {
|
|
538
|
+
if (process.env["TSTYCHE_NO_INTERACTIVE"] != null) {
|
|
539
|
+
return process.env["TSTYCHE_NO_INTERACTIVE"] !== "";
|
|
540
|
+
}
|
|
541
|
+
return !process.stdout.isTTY;
|
|
542
|
+
}
|
|
543
|
+
static #resolveStorePath() {
|
|
544
|
+
if (process.env["TSTYCHE_STORE_PATH"] != null) {
|
|
545
|
+
return Path.resolve(process.env["TSTYCHE_STORE_PATH"]);
|
|
546
|
+
}
|
|
547
|
+
if (process.platform === "darwin") {
|
|
548
|
+
return Path.resolve(os.homedir(), "Library", "TSTyche");
|
|
549
|
+
}
|
|
550
|
+
if (process.env["LocalAppData"] != null) {
|
|
551
|
+
return Path.resolve(process.env["LocalAppData"], "TSTyche");
|
|
552
|
+
}
|
|
553
|
+
if (process.env["XDG_DATA_HOME"] != null) {
|
|
554
|
+
return Path.resolve(process.env["XDG_DATA_HOME"], "TSTyche");
|
|
555
|
+
}
|
|
556
|
+
return Path.resolve(os.homedir(), ".local", "share", "TSTyche");
|
|
557
|
+
}
|
|
558
|
+
static #resolveTimeout() {
|
|
559
|
+
if (process.env["TSTYCHE_TIMEOUT"] != null) {
|
|
560
|
+
return Number(process.env["TSTYCHE_TIMEOUT"]);
|
|
561
|
+
}
|
|
562
|
+
return 30;
|
|
563
|
+
}
|
|
564
|
+
static #resolveTypeScriptPath() {
|
|
565
|
+
let moduleId = "typescript";
|
|
566
|
+
if (process.env["TSTYCHE_TYPESCRIPT_PATH"] != null) {
|
|
567
|
+
moduleId = process.env["TSTYCHE_TYPESCRIPT_PATH"];
|
|
568
|
+
}
|
|
569
|
+
let resolvedPath;
|
|
570
|
+
try {
|
|
571
|
+
resolvedPath = Path.normalizeSlashes(createRequire(import.meta.url).resolve(moduleId));
|
|
572
|
+
}
|
|
573
|
+
catch {
|
|
574
|
+
}
|
|
575
|
+
return resolvedPath;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
class OutputService {
|
|
580
|
+
#noColor;
|
|
581
|
+
#scribbler;
|
|
582
|
+
#stderr;
|
|
583
|
+
#stdout;
|
|
584
|
+
constructor(options) {
|
|
585
|
+
this.#noColor = options?.noColor ?? Environment.noColor;
|
|
586
|
+
this.#stderr = options?.stderr ?? process.stderr;
|
|
587
|
+
this.#stdout = options?.stdout ?? process.stdout;
|
|
588
|
+
this.#scribbler = new Scribbler({ noColor: this.#noColor });
|
|
589
|
+
}
|
|
590
|
+
clearTerminal() {
|
|
591
|
+
this.#stdout.write("\u001B[2J\u001B[3J\u001B[H");
|
|
592
|
+
}
|
|
593
|
+
eraseLastLine() {
|
|
594
|
+
this.#stdout.write("\u001B[1A\u001B[0K");
|
|
595
|
+
}
|
|
596
|
+
#write(stream, body) {
|
|
597
|
+
const elements = Array.isArray(body) ? body : [body];
|
|
598
|
+
for (const element of elements) {
|
|
599
|
+
if (element.$$typeof !== Symbol.for("tstyche:scribbler")) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
stream.write(this.#scribbler.render(element));
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
writeError(body) {
|
|
606
|
+
this.#write(this.#stderr, body);
|
|
607
|
+
}
|
|
608
|
+
writeMessage(body) {
|
|
609
|
+
this.#write(this.#stdout, body);
|
|
610
|
+
}
|
|
611
|
+
writeWarning(body) {
|
|
612
|
+
this.#write(this.#stderr, body);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
612
616
|
class RowText {
|
|
613
617
|
props;
|
|
614
618
|
constructor(props) {
|
|
@@ -670,8 +674,8 @@ class DurationText {
|
|
|
670
674
|
const minutes = Math.floor(duration / 60);
|
|
671
675
|
const seconds = duration % 60;
|
|
672
676
|
return (Scribbler.createElement(Text, null,
|
|
673
|
-
minutes > 0 ? `${minutes}m ` : undefined,
|
|
674
|
-
`${Math.round(seconds * 10) / 10}s`));
|
|
677
|
+
minutes > 0 ? `${String(minutes)}m ` : undefined,
|
|
678
|
+
`${String(Math.round(seconds * 10) / 10)}s`));
|
|
675
679
|
}
|
|
676
680
|
}
|
|
677
681
|
class MatchText {
|
|
@@ -807,11 +811,30 @@ function usesCompilerStepText(compilerVersion, tsconfigFilePath, options) {
|
|
|
807
811
|
Scribbler.createElement(Line, null)));
|
|
808
812
|
}
|
|
809
813
|
|
|
814
|
+
function watchModeUsageText() {
|
|
815
|
+
const usageText = Object.entries({ a: "run all tests", x: "exit" }).map(([key, action]) => {
|
|
816
|
+
return (Scribbler.createElement(Line, null,
|
|
817
|
+
Scribbler.createElement(Text, { color: "90" }, "Press"),
|
|
818
|
+
Scribbler.createElement(Text, null, ` ${key} `),
|
|
819
|
+
Scribbler.createElement(Text, { color: "90" }, `to ${action}.`)));
|
|
820
|
+
});
|
|
821
|
+
return (Scribbler.createElement(Text, null, usageText));
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
class Reporter {
|
|
825
|
+
resolvedConfig;
|
|
826
|
+
outputService;
|
|
827
|
+
constructor(resolvedConfig) {
|
|
828
|
+
this.resolvedConfig = resolvedConfig;
|
|
829
|
+
this.outputService = new OutputService();
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
810
833
|
class SummaryReporter extends Reporter {
|
|
811
834
|
handleEvent([eventName, payload]) {
|
|
812
835
|
switch (eventName) {
|
|
813
|
-
case "end":
|
|
814
|
-
this.
|
|
836
|
+
case "run:end":
|
|
837
|
+
this.outputService.writeMessage(summaryText({
|
|
815
838
|
duration: payload.result.timing.duration,
|
|
816
839
|
expectCount: payload.result.expectCount,
|
|
817
840
|
fileCount: payload.result.fileCount,
|
|
@@ -867,21 +890,30 @@ class ThoroughReporter extends Reporter {
|
|
|
867
890
|
#hasReportedAdds = false;
|
|
868
891
|
#hasReportedError = false;
|
|
869
892
|
#isFileViewExpanded = false;
|
|
893
|
+
#seenDeprecations = new Set();
|
|
870
894
|
get #isLastFile() {
|
|
871
895
|
return this.#fileCount === 0;
|
|
872
896
|
}
|
|
873
897
|
handleEvent([eventName, payload]) {
|
|
874
898
|
switch (eventName) {
|
|
875
|
-
case "
|
|
876
|
-
|
|
899
|
+
case "deprecation:info":
|
|
900
|
+
for (const diagnostic of payload.diagnostics) {
|
|
901
|
+
if (!this.#seenDeprecations.has(diagnostic.text.toString())) {
|
|
902
|
+
this.#fileView.addMessage(diagnosticText(diagnostic));
|
|
903
|
+
this.#seenDeprecations.add(diagnostic.text.toString());
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
break;
|
|
907
|
+
case "run:start":
|
|
908
|
+
this.#isFileViewExpanded = payload.result.testFiles.length === 1 && this.resolvedConfig.watch !== true;
|
|
877
909
|
break;
|
|
878
910
|
case "store:info":
|
|
879
|
-
this.
|
|
911
|
+
this.outputService.writeMessage(addsPackageStepText(payload.compilerVersion, payload.installationPath));
|
|
880
912
|
this.#hasReportedAdds = true;
|
|
881
913
|
break;
|
|
882
914
|
case "store:error":
|
|
883
915
|
for (const diagnostic of payload.diagnostics) {
|
|
884
|
-
this.
|
|
916
|
+
this.outputService.writeError(diagnosticText(diagnostic));
|
|
885
917
|
}
|
|
886
918
|
break;
|
|
887
919
|
case "target:start":
|
|
@@ -894,7 +926,7 @@ class ThoroughReporter extends Reporter {
|
|
|
894
926
|
case "project:info":
|
|
895
927
|
if (this.#currentCompilerVersion !== payload.compilerVersion
|
|
896
928
|
|| this.#currentProjectConfigFilePath !== payload.projectConfigFilePath) {
|
|
897
|
-
this.
|
|
929
|
+
this.outputService.writeMessage(usesCompilerStepText(payload.compilerVersion, payload.projectConfigFilePath, {
|
|
898
930
|
prependEmptyLine: this.#currentCompilerVersion != null && !this.#hasReportedAdds
|
|
899
931
|
&& !this.#hasReportedError,
|
|
900
932
|
}));
|
|
@@ -905,12 +937,12 @@ class ThoroughReporter extends Reporter {
|
|
|
905
937
|
break;
|
|
906
938
|
case "project:error":
|
|
907
939
|
for (const diagnostic of payload.diagnostics) {
|
|
908
|
-
this.
|
|
940
|
+
this.outputService.writeError(diagnosticText(diagnostic));
|
|
909
941
|
}
|
|
910
942
|
break;
|
|
911
943
|
case "file:start":
|
|
912
944
|
if (!Environment.noInteractive) {
|
|
913
|
-
this.
|
|
945
|
+
this.outputService.writeMessage(fileStatusText(payload.result.status, payload.result.testFile));
|
|
914
946
|
}
|
|
915
947
|
this.#fileCount--;
|
|
916
948
|
this.#hasReportedError = false;
|
|
@@ -922,12 +954,12 @@ class ThoroughReporter extends Reporter {
|
|
|
922
954
|
break;
|
|
923
955
|
case "file:end":
|
|
924
956
|
if (!Environment.noInteractive) {
|
|
925
|
-
this.
|
|
957
|
+
this.outputService.eraseLastLine();
|
|
926
958
|
}
|
|
927
|
-
this.
|
|
928
|
-
this.
|
|
959
|
+
this.outputService.writeMessage(fileStatusText(payload.result.status, payload.result.testFile));
|
|
960
|
+
this.outputService.writeMessage(this.#fileView.getViewText({ appendEmptyLine: this.#isLastFile }));
|
|
929
961
|
if (this.#fileView.hasErrors) {
|
|
930
|
-
this.
|
|
962
|
+
this.outputService.writeError(this.#fileView.getMessages());
|
|
931
963
|
this.#hasReportedError = true;
|
|
932
964
|
}
|
|
933
965
|
this.#fileView.reset();
|
|
@@ -980,6 +1012,19 @@ class ThoroughReporter extends Reporter {
|
|
|
980
1012
|
}
|
|
981
1013
|
}
|
|
982
1014
|
|
|
1015
|
+
class WatchModeReporter extends Reporter {
|
|
1016
|
+
handleEvent([eventName]) {
|
|
1017
|
+
switch (eventName) {
|
|
1018
|
+
case "run:start":
|
|
1019
|
+
this.outputService.clearTerminal();
|
|
1020
|
+
break;
|
|
1021
|
+
case "run:end":
|
|
1022
|
+
this.outputService.writeMessage(watchModeUsageText());
|
|
1023
|
+
break;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
983
1028
|
class ResultTiming {
|
|
984
1029
|
end = Date.now();
|
|
985
1030
|
start = Date.now();
|
|
@@ -1079,11 +1124,11 @@ class ResultManager {
|
|
|
1079
1124
|
#testResult;
|
|
1080
1125
|
handleEvent([eventName, payload]) {
|
|
1081
1126
|
switch (eventName) {
|
|
1082
|
-
case "start":
|
|
1127
|
+
case "run:start":
|
|
1083
1128
|
this.#result = payload.result;
|
|
1084
1129
|
this.#result.timing.start = Date.now();
|
|
1085
1130
|
break;
|
|
1086
|
-
case "end":
|
|
1131
|
+
case "run:end":
|
|
1087
1132
|
this.#result.timing.end = Date.now();
|
|
1088
1133
|
this.#result = undefined;
|
|
1089
1134
|
break;
|
|
@@ -1318,7 +1363,7 @@ class Diagnostic {
|
|
|
1318
1363
|
static fromDiagnostics(diagnostics, compiler) {
|
|
1319
1364
|
return diagnostics.map((diagnostic) => {
|
|
1320
1365
|
const category = "error";
|
|
1321
|
-
const code = `ts(${diagnostic.code})`;
|
|
1366
|
+
const code = `ts(${String(diagnostic.code)})`;
|
|
1322
1367
|
let origin;
|
|
1323
1368
|
const text = compiler.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
1324
1369
|
if (Diagnostic.isTsDiagnosticWithLocation(diagnostic)) {
|
|
@@ -1333,30 +1378,20 @@ class Diagnostic {
|
|
|
1333
1378
|
}
|
|
1334
1379
|
static fromError(text, error) {
|
|
1335
1380
|
const messageText = Array.isArray(text) ? text : [text];
|
|
1336
|
-
if (error instanceof Error) {
|
|
1337
|
-
if (
|
|
1338
|
-
messageText.push(
|
|
1339
|
-
}
|
|
1340
|
-
messageText.push(this.#normalizeMessage(String(error.message)));
|
|
1341
|
-
if (error.stack != null) {
|
|
1342
|
-
const stackLines = error.stack
|
|
1343
|
-
.split("\n")
|
|
1344
|
-
.slice(1)
|
|
1345
|
-
.map((line) => line.trimStart());
|
|
1346
|
-
messageText.push(...stackLines);
|
|
1381
|
+
if (error instanceof Error && error.stack != null) {
|
|
1382
|
+
if (messageText.length > 1) {
|
|
1383
|
+
messageText.push("");
|
|
1347
1384
|
}
|
|
1385
|
+
const stackLines = error.stack
|
|
1386
|
+
.split("\n")
|
|
1387
|
+
.map((line) => line.trimStart());
|
|
1388
|
+
messageText.push(...stackLines);
|
|
1348
1389
|
}
|
|
1349
1390
|
return Diagnostic.error(messageText);
|
|
1350
1391
|
}
|
|
1351
1392
|
static isTsDiagnosticWithLocation(diagnostic) {
|
|
1352
1393
|
return diagnostic.file != null && diagnostic.start != null && diagnostic.length != null;
|
|
1353
1394
|
}
|
|
1354
|
-
static #normalizeMessage(text) {
|
|
1355
|
-
if (text.endsWith(".")) {
|
|
1356
|
-
return text;
|
|
1357
|
-
}
|
|
1358
|
-
return `${text}.`;
|
|
1359
|
-
}
|
|
1360
1395
|
static warning(text, origin) {
|
|
1361
1396
|
return new Diagnostic(text, "warning", origin);
|
|
1362
1397
|
}
|
|
@@ -1410,10 +1445,8 @@ class TestMember {
|
|
|
1410
1445
|
}
|
|
1411
1446
|
validate() {
|
|
1412
1447
|
const diagnostics = [];
|
|
1413
|
-
const getText = (node) => `'${node.expression.getText()}()' cannot be nested within '${this.node.expression.getText()}()'
|
|
1448
|
+
const getText = (node) => `'${node.expression.getText()}()' cannot be nested within '${this.node.expression.getText()}()'.`;
|
|
1414
1449
|
switch (this.brand) {
|
|
1415
|
-
case "expect":
|
|
1416
|
-
break;
|
|
1417
1450
|
case "describe":
|
|
1418
1451
|
for (const member of this.members) {
|
|
1419
1452
|
if (member.brand === "expect") {
|
|
@@ -1426,6 +1459,7 @@ class TestMember {
|
|
|
1426
1459
|
}
|
|
1427
1460
|
break;
|
|
1428
1461
|
case "test":
|
|
1462
|
+
case "expect":
|
|
1429
1463
|
for (const member of this.members) {
|
|
1430
1464
|
if (member.brand !== "expect") {
|
|
1431
1465
|
diagnostics.push(Diagnostic.error(getText(member.node), {
|
|
@@ -1609,8 +1643,11 @@ class TestTree {
|
|
|
1609
1643
|
class CollectService {
|
|
1610
1644
|
compiler;
|
|
1611
1645
|
matcherIdentifiers = [
|
|
1646
|
+
"toBe",
|
|
1612
1647
|
"toBeAny",
|
|
1613
1648
|
"toBeAssignable",
|
|
1649
|
+
"toBeAssignableTo",
|
|
1650
|
+
"toBeAssignableWith",
|
|
1614
1651
|
"toBeBigInt",
|
|
1615
1652
|
"toBeBoolean",
|
|
1616
1653
|
"toBeNever",
|
|
@@ -1659,7 +1696,11 @@ class CollectService {
|
|
|
1659
1696
|
if (matcherNode == null || !this.#isMatcherNode(matcherNode)) {
|
|
1660
1697
|
return;
|
|
1661
1698
|
}
|
|
1662
|
-
|
|
1699
|
+
const assertion = new Assertion(meta.brand, node, parent, meta.flags, matcherNode, modifierNode, notNode);
|
|
1700
|
+
parent.members.push(assertion);
|
|
1701
|
+
this.compiler.forEachChild(node, (node) => {
|
|
1702
|
+
this.#collectTestMembers(node, identifiers, assertion);
|
|
1703
|
+
});
|
|
1663
1704
|
return;
|
|
1664
1705
|
}
|
|
1665
1706
|
}
|
|
@@ -1726,6 +1767,7 @@ class PrimitiveTypeMatcher {
|
|
|
1726
1767
|
|
|
1727
1768
|
class RelationMatcherBase {
|
|
1728
1769
|
typeChecker;
|
|
1770
|
+
relationExplanationVerb = "is";
|
|
1729
1771
|
constructor(typeChecker) {
|
|
1730
1772
|
this.typeChecker = typeChecker;
|
|
1731
1773
|
}
|
|
@@ -1733,8 +1775,12 @@ class RelationMatcherBase {
|
|
|
1733
1775
|
const sourceTypeText = this.typeChecker.typeToString(sourceType);
|
|
1734
1776
|
const targetTypeText = this.typeChecker.typeToString(targetType);
|
|
1735
1777
|
return isNot
|
|
1736
|
-
? [
|
|
1737
|
-
|
|
1778
|
+
? [
|
|
1779
|
+
Diagnostic.error(`Type '${sourceTypeText}' ${this.relationExplanationVerb} ${this.relationExplanationText} type '${targetTypeText}'.`),
|
|
1780
|
+
]
|
|
1781
|
+
: [
|
|
1782
|
+
Diagnostic.error(`Type '${sourceTypeText}' ${this.relationExplanationVerb} not ${this.relationExplanationText} type '${targetTypeText}'.`),
|
|
1783
|
+
];
|
|
1738
1784
|
}
|
|
1739
1785
|
match(sourceType, targetType, isNot) {
|
|
1740
1786
|
const isMatch = this.typeChecker.isTypeRelatedTo(sourceType, targetType, this.relation);
|
|
@@ -1745,9 +1791,19 @@ class RelationMatcherBase {
|
|
|
1745
1791
|
}
|
|
1746
1792
|
}
|
|
1747
1793
|
|
|
1748
|
-
class
|
|
1794
|
+
class ToBe extends RelationMatcherBase {
|
|
1795
|
+
relation = this.typeChecker.relation.identity;
|
|
1796
|
+
relationExplanationText = "identical to";
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
class ToBeAssignableTo extends RelationMatcherBase {
|
|
1749
1800
|
relation = this.typeChecker.relation.assignable;
|
|
1750
1801
|
relationExplanationText = "assignable to";
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
class ToBeAssignableWith extends RelationMatcherBase {
|
|
1805
|
+
relation = this.typeChecker.relation.assignable;
|
|
1806
|
+
relationExplanationText = "assignable with";
|
|
1751
1807
|
match(sourceType, targetType, isNot) {
|
|
1752
1808
|
const isMatch = this.typeChecker.isTypeRelatedTo(targetType, sourceType, this.relation);
|
|
1753
1809
|
return {
|
|
@@ -1757,11 +1813,6 @@ class ToBeAssignable extends RelationMatcherBase {
|
|
|
1757
1813
|
}
|
|
1758
1814
|
}
|
|
1759
1815
|
|
|
1760
|
-
class ToEqual extends RelationMatcherBase {
|
|
1761
|
-
relation = this.typeChecker.relation.identity;
|
|
1762
|
-
relationExplanationText = "identical to";
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
1816
|
class ToHaveProperty {
|
|
1766
1817
|
compiler;
|
|
1767
1818
|
typeChecker;
|
|
@@ -1805,7 +1856,8 @@ class ToHaveProperty {
|
|
|
1805
1856
|
|
|
1806
1857
|
class ToMatch extends RelationMatcherBase {
|
|
1807
1858
|
relation = this.typeChecker.relation.subtype;
|
|
1808
|
-
relationExplanationText = "
|
|
1859
|
+
relationExplanationText = "match";
|
|
1860
|
+
relationExplanationVerb = "does";
|
|
1809
1861
|
}
|
|
1810
1862
|
|
|
1811
1863
|
class ToRaiseError {
|
|
@@ -1825,16 +1877,16 @@ class ToRaiseError {
|
|
|
1825
1877
|
Diagnostic.error(`The raised type error${source.diagnostics.length === 1 ? "" : "s"}:`),
|
|
1826
1878
|
...Diagnostic.fromDiagnostics(source.diagnostics, this.compiler),
|
|
1827
1879
|
];
|
|
1828
|
-
const text = `${sourceText} raised ${source.diagnostics.length === 1 ? "a" : source.diagnostics.length} type error${source.diagnostics.length === 1 ? "" : "s"}.`;
|
|
1880
|
+
const text = `${sourceText} raised ${source.diagnostics.length === 1 ? "a" : String(source.diagnostics.length)} type error${source.diagnostics.length === 1 ? "" : "s"}.`;
|
|
1829
1881
|
return [Diagnostic.error(text).add({ related })];
|
|
1830
1882
|
}
|
|
1831
1883
|
if (source.diagnostics.length !== targetTypes.length) {
|
|
1832
1884
|
const expectedText = source.diagnostics.length > targetTypes.length
|
|
1833
|
-
? `only ${targetTypes.length} type error${targetTypes.length === 1 ? "" : "s"}`
|
|
1834
|
-
: `${targetTypes.length} type error${targetTypes.length === 1 ? "" : "s"}`;
|
|
1885
|
+
? `only ${String(targetTypes.length)} type error${targetTypes.length === 1 ? "" : "s"}`
|
|
1886
|
+
: `${String(targetTypes.length)} type error${targetTypes.length === 1 ? "" : "s"}`;
|
|
1835
1887
|
const foundText = source.diagnostics.length > targetTypes.length
|
|
1836
|
-
?
|
|
1837
|
-
: `only ${source.diagnostics.length}`;
|
|
1888
|
+
? String(source.diagnostics.length)
|
|
1889
|
+
: `only ${String(source.diagnostics.length)}`;
|
|
1838
1890
|
const related = [
|
|
1839
1891
|
Diagnostic.error(`The raised type error${source.diagnostics.length === 1 ? "" : "s"}:`),
|
|
1840
1892
|
...Diagnostic.fromDiagnostics(source.diagnostics, this.compiler),
|
|
@@ -1852,7 +1904,7 @@ class ToRaiseError {
|
|
|
1852
1904
|
if (!isNot && !isMatch) {
|
|
1853
1905
|
const expectedText = this.#isStringLiteralType(argument)
|
|
1854
1906
|
? `matching substring '${argument.value}'`
|
|
1855
|
-
: `with code ${argument.value}`;
|
|
1907
|
+
: `with code ${String(argument.value)}`;
|
|
1856
1908
|
const related = [
|
|
1857
1909
|
Diagnostic.error("The raised type error:"),
|
|
1858
1910
|
...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
|
|
@@ -1863,7 +1915,7 @@ class ToRaiseError {
|
|
|
1863
1915
|
if (isNot && isMatch) {
|
|
1864
1916
|
const expectedText = this.#isStringLiteralType(argument)
|
|
1865
1917
|
? `matching substring '${argument.value}'`
|
|
1866
|
-
: `with code ${argument.value}`;
|
|
1918
|
+
: `with code ${String(argument.value)}`;
|
|
1867
1919
|
const related = [
|
|
1868
1920
|
Diagnostic.error("The raised type error:"),
|
|
1869
1921
|
...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
|
|
@@ -1907,8 +1959,11 @@ class ToRaiseError {
|
|
|
1907
1959
|
class Expect {
|
|
1908
1960
|
compiler;
|
|
1909
1961
|
typeChecker;
|
|
1962
|
+
toBe;
|
|
1910
1963
|
toBeAny;
|
|
1911
1964
|
toBeAssignable;
|
|
1965
|
+
toBeAssignableTo;
|
|
1966
|
+
toBeAssignableWith;
|
|
1912
1967
|
toBeBigInt;
|
|
1913
1968
|
toBeBoolean;
|
|
1914
1969
|
toBeNever;
|
|
@@ -1927,8 +1982,11 @@ class Expect {
|
|
|
1927
1982
|
constructor(compiler, typeChecker) {
|
|
1928
1983
|
this.compiler = compiler;
|
|
1929
1984
|
this.typeChecker = typeChecker;
|
|
1985
|
+
this.toBe = new ToBe(this.typeChecker);
|
|
1930
1986
|
this.toBeAny = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Any);
|
|
1931
|
-
this.toBeAssignable = new
|
|
1987
|
+
this.toBeAssignable = new ToBeAssignableWith(this.typeChecker);
|
|
1988
|
+
this.toBeAssignableTo = new ToBeAssignableTo(this.typeChecker);
|
|
1989
|
+
this.toBeAssignableWith = new ToBeAssignableWith(this.typeChecker);
|
|
1932
1990
|
this.toBeBigInt = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.BigInt);
|
|
1933
1991
|
this.toBeBoolean = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Boolean);
|
|
1934
1992
|
this.toBeNever = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Never);
|
|
@@ -1940,7 +1998,7 @@ class Expect {
|
|
|
1940
1998
|
this.toBeUniqueSymbol = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.UniqueESSymbol);
|
|
1941
1999
|
this.toBeUnknown = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Unknown);
|
|
1942
2000
|
this.toBeVoid = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Void);
|
|
1943
|
-
this.toEqual = new
|
|
2001
|
+
this.toEqual = new ToBe(this.typeChecker);
|
|
1944
2002
|
this.toHaveProperty = new ToHaveProperty(this.compiler, this.typeChecker);
|
|
1945
2003
|
this.toMatch = new ToMatch(this.typeChecker);
|
|
1946
2004
|
this.toRaiseError = new ToRaiseError(this.compiler, this.typeChecker);
|
|
@@ -1970,6 +2028,10 @@ class Expect {
|
|
|
1970
2028
|
switch (matcherNameText) {
|
|
1971
2029
|
case "toBeAssignable":
|
|
1972
2030
|
case "toEqual":
|
|
2031
|
+
this.#onDeprecatedMatcher(assertion);
|
|
2032
|
+
case "toBe":
|
|
2033
|
+
case "toBeAssignableTo":
|
|
2034
|
+
case "toBeAssignableWith":
|
|
1973
2035
|
case "toMatch":
|
|
1974
2036
|
if (assertion.source[0] == null) {
|
|
1975
2037
|
this.#onSourceArgumentMustBeProvided(assertion, expectResult);
|
|
@@ -2037,6 +2099,22 @@ class Expect {
|
|
|
2037
2099
|
return;
|
|
2038
2100
|
}
|
|
2039
2101
|
}
|
|
2102
|
+
#onDeprecatedMatcher(assertion) {
|
|
2103
|
+
const matcherNameText = assertion.matcherName.getText();
|
|
2104
|
+
const text = [
|
|
2105
|
+
`'.${matcherNameText}()' is deprecated and will be removed in TSTyche 3.`,
|
|
2106
|
+
"To learn more, visit https://tstyche.org/release-notes/tstyche-2",
|
|
2107
|
+
];
|
|
2108
|
+
const origin = {
|
|
2109
|
+
end: assertion.matcherName.getEnd(),
|
|
2110
|
+
file: assertion.matcherName.getSourceFile(),
|
|
2111
|
+
start: assertion.matcherName.getStart(),
|
|
2112
|
+
};
|
|
2113
|
+
EventEmitter.dispatch([
|
|
2114
|
+
"deprecation:info",
|
|
2115
|
+
{ diagnostics: [Diagnostic.warning(text, origin)] },
|
|
2116
|
+
]);
|
|
2117
|
+
}
|
|
2040
2118
|
#onKeyArgumentMustBeOfType(node, expectResult) {
|
|
2041
2119
|
const receivedTypeText = this.typeChecker.typeToString(this.#getType(node));
|
|
2042
2120
|
const text = `An argument for 'key' must be of type 'string | number | symbol', received: '${receivedTypeText}'.`;
|
|
@@ -2266,19 +2344,19 @@ class ProjectService {
|
|
|
2266
2344
|
class TestTreeWorker {
|
|
2267
2345
|
resolvedConfig;
|
|
2268
2346
|
compiler;
|
|
2347
|
+
#cancellationToken;
|
|
2269
2348
|
#expect;
|
|
2270
2349
|
#fileResult;
|
|
2271
2350
|
#hasOnly;
|
|
2272
2351
|
#position;
|
|
2273
|
-
#signal;
|
|
2274
2352
|
constructor(resolvedConfig, compiler, expect, options) {
|
|
2275
2353
|
this.resolvedConfig = resolvedConfig;
|
|
2276
2354
|
this.compiler = compiler;
|
|
2355
|
+
this.#cancellationToken = options.cancellationToken;
|
|
2277
2356
|
this.#expect = expect;
|
|
2278
2357
|
this.#fileResult = options.fileResult;
|
|
2279
2358
|
this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
|
|
2280
2359
|
this.#position = options.position;
|
|
2281
|
-
this.#signal = options.signal;
|
|
2282
2360
|
}
|
|
2283
2361
|
#resolveRunMode(mode, member) {
|
|
2284
2362
|
if (member.flags & 1) {
|
|
@@ -2302,7 +2380,7 @@ class TestTreeWorker {
|
|
|
2302
2380
|
}
|
|
2303
2381
|
visit(members, runMode, parentResult) {
|
|
2304
2382
|
for (const member of members) {
|
|
2305
|
-
if (this.#
|
|
2383
|
+
if (this.#cancellationToken?.isCancellationRequested === true) {
|
|
2306
2384
|
break;
|
|
2307
2385
|
}
|
|
2308
2386
|
const validationError = member.validate();
|
|
@@ -2330,6 +2408,7 @@ class TestTreeWorker {
|
|
|
2330
2408
|
}
|
|
2331
2409
|
}
|
|
2332
2410
|
#visitAssertion(assertion, runMode, parentResult) {
|
|
2411
|
+
this.visit(assertion.members, runMode, parentResult);
|
|
2333
2412
|
const expectResult = new ExpectResult(assertion, parentResult);
|
|
2334
2413
|
EventEmitter.dispatch(["expect:start", { result: expectResult }]);
|
|
2335
2414
|
runMode = this.#resolveRunMode(runMode, assertion);
|
|
@@ -2452,8 +2531,8 @@ class TestFileRunner {
|
|
|
2452
2531
|
this.#collectService = new CollectService(compiler);
|
|
2453
2532
|
this.#projectService = new ProjectService(compiler);
|
|
2454
2533
|
}
|
|
2455
|
-
run(testFile,
|
|
2456
|
-
if (
|
|
2534
|
+
run(testFile, cancellationToken) {
|
|
2535
|
+
if (cancellationToken?.isCancellationRequested === true) {
|
|
2457
2536
|
return;
|
|
2458
2537
|
}
|
|
2459
2538
|
const testFilePath = fileURLToPath(testFile);
|
|
@@ -2461,11 +2540,11 @@ class TestFileRunner {
|
|
|
2461
2540
|
this.#projectService.openFile(testFilePath, undefined, this.resolvedConfig.rootPath);
|
|
2462
2541
|
const fileResult = new FileResult(testFile);
|
|
2463
2542
|
EventEmitter.dispatch(["file:start", { result: fileResult }]);
|
|
2464
|
-
this.#runFile(testFilePath, fileResult, position,
|
|
2543
|
+
this.#runFile(testFilePath, fileResult, position, cancellationToken);
|
|
2465
2544
|
EventEmitter.dispatch(["file:end", { result: fileResult }]);
|
|
2466
2545
|
this.#projectService.closeFile(testFilePath);
|
|
2467
2546
|
}
|
|
2468
|
-
#runFile(testFilePath, fileResult, position,
|
|
2547
|
+
#runFile(testFilePath, fileResult, position, cancellationToken) {
|
|
2469
2548
|
const languageService = this.#projectService.getLanguageService(testFilePath);
|
|
2470
2549
|
if (!languageService) {
|
|
2471
2550
|
return;
|
|
@@ -2509,10 +2588,10 @@ class TestFileRunner {
|
|
|
2509
2588
|
}
|
|
2510
2589
|
const expect = new Expect(this.compiler, typeChecker);
|
|
2511
2590
|
const testTreeWorker = new TestTreeWorker(this.resolvedConfig, this.compiler, expect, {
|
|
2591
|
+
cancellationToken,
|
|
2512
2592
|
fileResult,
|
|
2513
2593
|
hasOnly: testTree.hasOnly,
|
|
2514
2594
|
position,
|
|
2515
|
-
signal,
|
|
2516
2595
|
});
|
|
2517
2596
|
testTreeWorker.visit(testTree.members, 0, undefined);
|
|
2518
2597
|
}
|
|
@@ -2530,32 +2609,160 @@ class TaskRunner {
|
|
|
2530
2609
|
this.#resultManager.handleEvent(event);
|
|
2531
2610
|
});
|
|
2532
2611
|
}
|
|
2533
|
-
async run(testFiles, target,
|
|
2612
|
+
async run(testFiles, target, cancellationToken) {
|
|
2534
2613
|
const result = new Result(this.resolvedConfig, testFiles);
|
|
2535
|
-
EventEmitter.dispatch(["start", { result }]);
|
|
2614
|
+
EventEmitter.dispatch(["run:start", { result }]);
|
|
2536
2615
|
for (const versionTag of target) {
|
|
2537
2616
|
const targetResult = new TargetResult(versionTag, testFiles);
|
|
2538
2617
|
EventEmitter.dispatch(["target:start", { result: targetResult }]);
|
|
2539
|
-
const compiler = await this.#storeService.load(versionTag,
|
|
2618
|
+
const compiler = await this.#storeService.load(versionTag, cancellationToken);
|
|
2540
2619
|
if (compiler) {
|
|
2541
2620
|
const testFileRunner = new TestFileRunner(this.resolvedConfig, compiler);
|
|
2542
2621
|
for (const testFile of testFiles) {
|
|
2543
|
-
testFileRunner.run(testFile,
|
|
2622
|
+
testFileRunner.run(testFile, cancellationToken);
|
|
2544
2623
|
}
|
|
2545
2624
|
}
|
|
2546
2625
|
EventEmitter.dispatch(["target:end", { result: targetResult }]);
|
|
2547
2626
|
}
|
|
2548
|
-
EventEmitter.dispatch(["end", { result }]);
|
|
2627
|
+
EventEmitter.dispatch(["run:end", { result }]);
|
|
2549
2628
|
return result;
|
|
2550
2629
|
}
|
|
2551
2630
|
}
|
|
2552
2631
|
|
|
2553
|
-
class
|
|
2632
|
+
class CancellationToken {
|
|
2633
|
+
#isCancelled = false;
|
|
2634
|
+
get isCancellationRequested() {
|
|
2635
|
+
return this.#isCancelled;
|
|
2636
|
+
}
|
|
2637
|
+
cancel() {
|
|
2638
|
+
if (!this.#isCancelled) {
|
|
2639
|
+
this.#isCancelled = true;
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
class InputService {
|
|
2645
|
+
#stdin;
|
|
2646
|
+
constructor(options) {
|
|
2647
|
+
this.#stdin = options?.stdin ?? process.stdin;
|
|
2648
|
+
this.#stdin.setRawMode?.(true);
|
|
2649
|
+
this.#stdin.setEncoding("utf8");
|
|
2650
|
+
this.#stdin.on("data", (key) => {
|
|
2651
|
+
EventEmitter.dispatch(["input:info", { key }]);
|
|
2652
|
+
});
|
|
2653
|
+
this.#stdin.unref();
|
|
2654
|
+
}
|
|
2655
|
+
dispose() {
|
|
2656
|
+
this.#stdin.setRawMode?.(false);
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
class Watcher {
|
|
2554
2661
|
resolvedConfig;
|
|
2555
2662
|
#abortController = new AbortController();
|
|
2663
|
+
#changedTestFiles = new Set();
|
|
2664
|
+
#inputService;
|
|
2665
|
+
#runCallback;
|
|
2666
|
+
#runChangedDebounced;
|
|
2667
|
+
#selectService;
|
|
2668
|
+
#testFiles;
|
|
2669
|
+
#watcher;
|
|
2670
|
+
constructor(resolvedConfig, runCallback, selectService, testFiles) {
|
|
2671
|
+
this.resolvedConfig = resolvedConfig;
|
|
2672
|
+
this.#inputService = new InputService();
|
|
2673
|
+
this.#runCallback = runCallback;
|
|
2674
|
+
this.#runChangedDebounced = this.#debounce(this.#runChanged, 100);
|
|
2675
|
+
this.#selectService = selectService;
|
|
2676
|
+
this.#testFiles = new Set(testFiles);
|
|
2677
|
+
EventEmitter.addHandler(([eventName, payload]) => {
|
|
2678
|
+
if (eventName === "input:info") {
|
|
2679
|
+
switch (payload.key) {
|
|
2680
|
+
case "\u000D":
|
|
2681
|
+
case "\u0041":
|
|
2682
|
+
case "\u0061":
|
|
2683
|
+
void this.#runAll();
|
|
2684
|
+
break;
|
|
2685
|
+
case "\u0003":
|
|
2686
|
+
case "\u0004":
|
|
2687
|
+
case "\u001B":
|
|
2688
|
+
case "\u0058":
|
|
2689
|
+
case "\u0078":
|
|
2690
|
+
this.#abortController.abort();
|
|
2691
|
+
break;
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
});
|
|
2695
|
+
}
|
|
2696
|
+
#debounce(target, delay) {
|
|
2697
|
+
let timeout;
|
|
2698
|
+
return function (...args) {
|
|
2699
|
+
clearTimeout(timeout);
|
|
2700
|
+
timeout = setTimeout(() => {
|
|
2701
|
+
target.apply(this, args);
|
|
2702
|
+
}, delay);
|
|
2703
|
+
};
|
|
2704
|
+
}
|
|
2705
|
+
#onChanged(fileName) {
|
|
2706
|
+
if (this.#testFiles.has(fileName)) {
|
|
2707
|
+
this.#changedTestFiles.add(fileName);
|
|
2708
|
+
return;
|
|
2709
|
+
}
|
|
2710
|
+
if (this.#selectService.isTestFile(fileName)) {
|
|
2711
|
+
this.#changedTestFiles.add(fileName);
|
|
2712
|
+
this.#testFiles.add(fileName);
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
#onExit() {
|
|
2716
|
+
this.#inputService.dispose();
|
|
2717
|
+
this.#watcher = undefined;
|
|
2718
|
+
}
|
|
2719
|
+
#onRemoved(fileName) {
|
|
2720
|
+
this.#changedTestFiles.delete(fileName);
|
|
2721
|
+
this.#testFiles.delete(fileName);
|
|
2722
|
+
}
|
|
2723
|
+
async #runAll() {
|
|
2724
|
+
await this.#runCallback([...this.#testFiles].sort());
|
|
2725
|
+
}
|
|
2726
|
+
async #runChanged() {
|
|
2727
|
+
await this.#runCallback([...this.#changedTestFiles].sort());
|
|
2728
|
+
this.#changedTestFiles.clear();
|
|
2729
|
+
}
|
|
2730
|
+
async watch() {
|
|
2731
|
+
this.#watcher = fs.watch(this.resolvedConfig.rootPath, { recursive: true, signal: this.#abortController.signal });
|
|
2732
|
+
await this.#runAll();
|
|
2733
|
+
try {
|
|
2734
|
+
for await (const event of this.#watcher) {
|
|
2735
|
+
if (event.filename != null) {
|
|
2736
|
+
const filePath = Path.resolve(this.resolvedConfig.rootPath, event.filename);
|
|
2737
|
+
if (!existsSync(filePath)) {
|
|
2738
|
+
this.#onRemoved(filePath);
|
|
2739
|
+
continue;
|
|
2740
|
+
}
|
|
2741
|
+
this.#onChanged(filePath);
|
|
2742
|
+
}
|
|
2743
|
+
if (this.#changedTestFiles.size !== 0) {
|
|
2744
|
+
await this.#runChangedDebounced();
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
catch (error) {
|
|
2749
|
+
if (error != null && typeof error === "object" && "name" in error && error.name === "AbortError") ;
|
|
2750
|
+
else {
|
|
2751
|
+
throw error;
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
finally {
|
|
2755
|
+
this.#onExit();
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
class TSTyche {
|
|
2761
|
+
resolvedConfig;
|
|
2762
|
+
#cancellationToken = new CancellationToken();
|
|
2556
2763
|
#storeService;
|
|
2557
2764
|
#taskRunner;
|
|
2558
|
-
static version = "
|
|
2765
|
+
static version = "2.0.0-beta.0";
|
|
2559
2766
|
constructor(resolvedConfig, storeService) {
|
|
2560
2767
|
this.resolvedConfig = resolvedConfig;
|
|
2561
2768
|
this.#storeService = storeService;
|
|
@@ -2567,17 +2774,27 @@ class TSTyche {
|
|
|
2567
2774
|
if (eventName.includes("error") || eventName.includes("fail")) {
|
|
2568
2775
|
if ("diagnostics" in payload
|
|
2569
2776
|
&& payload.diagnostics.some((diagnostic) => diagnostic.category === "error")) {
|
|
2570
|
-
|
|
2777
|
+
if (this.resolvedConfig.watch !== true) {
|
|
2778
|
+
process.exitCode = 1;
|
|
2779
|
+
}
|
|
2571
2780
|
if (this.resolvedConfig.failFast) {
|
|
2572
|
-
this.#
|
|
2781
|
+
this.#cancellationToken.cancel();
|
|
2573
2782
|
}
|
|
2574
2783
|
}
|
|
2575
2784
|
}
|
|
2576
2785
|
});
|
|
2577
|
-
const
|
|
2578
|
-
|
|
2786
|
+
const reporters = [
|
|
2787
|
+
new ThoroughReporter(this.resolvedConfig),
|
|
2788
|
+
];
|
|
2789
|
+
if (this.resolvedConfig.watch === true) {
|
|
2790
|
+
reporters.push(new WatchModeReporter(this.resolvedConfig));
|
|
2791
|
+
}
|
|
2792
|
+
else {
|
|
2793
|
+
reporters.push(new SummaryReporter(this.resolvedConfig));
|
|
2794
|
+
}
|
|
2795
|
+
for (const reporter of reporters) {
|
|
2579
2796
|
EventEmitter.addHandler((event) => {
|
|
2580
|
-
|
|
2797
|
+
reporter.handleEvent(event);
|
|
2581
2798
|
});
|
|
2582
2799
|
}
|
|
2583
2800
|
}
|
|
@@ -2593,7 +2810,12 @@ class TSTyche {
|
|
|
2593
2810
|
});
|
|
2594
2811
|
}
|
|
2595
2812
|
async run(testFiles) {
|
|
2596
|
-
await this.#taskRunner.run(this.#normalizePaths(testFiles), this.resolvedConfig.target, this.#
|
|
2813
|
+
await this.#taskRunner.run(this.#normalizePaths(testFiles), this.resolvedConfig.target, this.#cancellationToken);
|
|
2814
|
+
}
|
|
2815
|
+
async watch(testFiles, selectService) {
|
|
2816
|
+
const runCallback = async (testFiles) => this.run(testFiles);
|
|
2817
|
+
const watcher = new Watcher(this.resolvedConfig, runCallback, selectService, testFiles);
|
|
2818
|
+
await watcher.watch();
|
|
2597
2819
|
}
|
|
2598
2820
|
}
|
|
2599
2821
|
|
|
@@ -2692,6 +2914,12 @@ class OptionDefinitionsMap {
|
|
|
2692
2914
|
group: 2,
|
|
2693
2915
|
name: "version",
|
|
2694
2916
|
},
|
|
2917
|
+
{
|
|
2918
|
+
brand: "boolean",
|
|
2919
|
+
description: "Watch for changes and rerun related tests files.",
|
|
2920
|
+
group: 2,
|
|
2921
|
+
name: "watch",
|
|
2922
|
+
},
|
|
2695
2923
|
];
|
|
2696
2924
|
static for(optionGroup) {
|
|
2697
2925
|
const definitionMap = new Map();
|
|
@@ -2712,13 +2940,13 @@ class OptionDiagnosticText {
|
|
|
2712
2940
|
doubleQuotesExpected() {
|
|
2713
2941
|
return "String literal with double quotes expected.";
|
|
2714
2942
|
}
|
|
2715
|
-
expectsArgument(optionName) {
|
|
2716
|
-
optionName = this.#optionName(optionName);
|
|
2717
|
-
return `Option '${optionName}' expects an argument.`;
|
|
2718
|
-
}
|
|
2719
2943
|
expectsListItemType(optionName, optionBrand) {
|
|
2720
2944
|
return `Item of the '${optionName}' list must be of type ${optionBrand}.`;
|
|
2721
2945
|
}
|
|
2946
|
+
expectsValue(optionName) {
|
|
2947
|
+
optionName = this.#optionName(optionName);
|
|
2948
|
+
return `Option '${optionName}' expects a value.`;
|
|
2949
|
+
}
|
|
2722
2950
|
fileDoesNotExist(filePath) {
|
|
2723
2951
|
return `The specified path '${filePath}' does not exist.`;
|
|
2724
2952
|
}
|
|
@@ -2730,9 +2958,9 @@ class OptionDiagnosticText {
|
|
|
2730
2958
|
return optionName;
|
|
2731
2959
|
}
|
|
2732
2960
|
}
|
|
2733
|
-
|
|
2961
|
+
requiresValueType(optionName, optionBrand) {
|
|
2734
2962
|
optionName = this.#optionName(optionName);
|
|
2735
|
-
return `Option '${optionName}' requires
|
|
2963
|
+
return `Option '${optionName}' requires a value of type ${optionBrand}.`;
|
|
2736
2964
|
}
|
|
2737
2965
|
unknownOption(optionName) {
|
|
2738
2966
|
return `Unknown option '${optionName}'.`;
|
|
@@ -2743,6 +2971,9 @@ class OptionDiagnosticText {
|
|
|
2743
2971
|
}
|
|
2744
2972
|
return `TypeScript version '${value}' is not supported.`;
|
|
2745
2973
|
}
|
|
2974
|
+
watchCannotBeEnabledInCiEnvironment() {
|
|
2975
|
+
return "The watch mode cannot be enabled in a continuous integration environment.";
|
|
2976
|
+
}
|
|
2746
2977
|
}
|
|
2747
2978
|
|
|
2748
2979
|
class OptionUsageText {
|
|
@@ -2762,7 +2993,7 @@ class OptionUsageText {
|
|
|
2762
2993
|
const supportedTagsText = `Supported tags: ${["'", supportedTags.join("', '"), "'"].join("")}.`;
|
|
2763
2994
|
switch (this.#optionGroup) {
|
|
2764
2995
|
case 2:
|
|
2765
|
-
usageText.push("
|
|
2996
|
+
usageText.push("Value for the '--target' option must be a single tag or a comma separated list.", "Usage examples: '--target 4.9', '--target latest', '--target 4.9,5.3.2,current'.", supportedTagsText);
|
|
2766
2997
|
break;
|
|
2767
2998
|
case 4:
|
|
2768
2999
|
usageText.push("Item of the 'target' list must be a supported version tag.", supportedTagsText);
|
|
@@ -2771,7 +3002,7 @@ class OptionUsageText {
|
|
|
2771
3002
|
break;
|
|
2772
3003
|
}
|
|
2773
3004
|
default:
|
|
2774
|
-
usageText.push(this.#optionDiagnosticText.
|
|
3005
|
+
usageText.push(this.#optionDiagnosticText.requiresValueType(optionName, optionBrand));
|
|
2775
3006
|
}
|
|
2776
3007
|
return usageText;
|
|
2777
3008
|
}
|
|
@@ -2795,18 +3026,26 @@ class OptionValidator {
|
|
|
2795
3026
|
case "config":
|
|
2796
3027
|
case "rootPath":
|
|
2797
3028
|
if (!existsSync(optionValue)) {
|
|
2798
|
-
|
|
2799
|
-
|
|
3029
|
+
this.#onDiagnostic(Diagnostic.error([
|
|
3030
|
+
this.#optionDiagnosticText.fileDoesNotExist(optionValue),
|
|
3031
|
+
], origin));
|
|
2800
3032
|
}
|
|
2801
3033
|
break;
|
|
2802
3034
|
case "target":
|
|
2803
|
-
if (
|
|
3035
|
+
if (await this.#storeService.validateTag(optionValue) === false) {
|
|
2804
3036
|
this.#onDiagnostic(Diagnostic.error([
|
|
2805
3037
|
this.#optionDiagnosticText.versionIsNotSupported(optionValue),
|
|
2806
3038
|
...(await this.#optionUsageText.get(optionName, optionBrand)),
|
|
2807
3039
|
], origin));
|
|
2808
3040
|
}
|
|
2809
3041
|
break;
|
|
3042
|
+
case "watch":
|
|
3043
|
+
if (Environment.isCi) {
|
|
3044
|
+
this.#onDiagnostic(Diagnostic.error([
|
|
3045
|
+
this.#optionDiagnosticText.watchCannotBeEnabledInCiEnvironment(),
|
|
3046
|
+
], origin));
|
|
3047
|
+
}
|
|
3048
|
+
break;
|
|
2810
3049
|
}
|
|
2811
3050
|
}
|
|
2812
3051
|
}
|
|
@@ -2830,9 +3069,9 @@ class CommandLineOptionsWorker {
|
|
|
2830
3069
|
this.#optionUsageText = new OptionUsageText(2, this.#storeService);
|
|
2831
3070
|
this.#optionValidator = new OptionValidator(2, this.#storeService, this.#onDiagnostic);
|
|
2832
3071
|
}
|
|
2833
|
-
async #
|
|
3072
|
+
async #onExpectsValue(optionDefinition) {
|
|
2834
3073
|
const text = [
|
|
2835
|
-
this.#optionDiagnosticText.
|
|
3074
|
+
this.#optionDiagnosticText.expectsValue(optionDefinition.name),
|
|
2836
3075
|
...(await this.#optionUsageText.get(optionDefinition.name, optionDefinition.brand)),
|
|
2837
3076
|
];
|
|
2838
3077
|
this.#onDiagnostic(Diagnostic.error(text));
|
|
@@ -2868,13 +3107,14 @@ class CommandLineOptionsWorker {
|
|
|
2868
3107
|
this.#commandLineOptions[optionDefinition.name] = true;
|
|
2869
3108
|
break;
|
|
2870
3109
|
case "boolean":
|
|
3110
|
+
await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
|
|
2871
3111
|
this.#commandLineOptions[optionDefinition.name] = optionValue !== "false";
|
|
2872
3112
|
if (optionValue === "false" || optionValue === "true") {
|
|
2873
3113
|
index++;
|
|
2874
3114
|
}
|
|
2875
3115
|
break;
|
|
2876
3116
|
case "list":
|
|
2877
|
-
if (optionValue
|
|
3117
|
+
if (optionValue !== "") {
|
|
2878
3118
|
const optionValues = optionValue
|
|
2879
3119
|
.split(",")
|
|
2880
3120
|
.map((value) => value.trim())
|
|
@@ -2886,10 +3126,10 @@ class CommandLineOptionsWorker {
|
|
|
2886
3126
|
index++;
|
|
2887
3127
|
break;
|
|
2888
3128
|
}
|
|
2889
|
-
await this.#
|
|
3129
|
+
await this.#onExpectsValue(optionDefinition);
|
|
2890
3130
|
break;
|
|
2891
3131
|
case "string":
|
|
2892
|
-
if (optionValue
|
|
3132
|
+
if (optionValue !== "") {
|
|
2893
3133
|
if (optionDefinition.name === "config") {
|
|
2894
3134
|
optionValue = Path.resolve(optionValue);
|
|
2895
3135
|
}
|
|
@@ -2898,16 +3138,13 @@ class CommandLineOptionsWorker {
|
|
|
2898
3138
|
index++;
|
|
2899
3139
|
break;
|
|
2900
3140
|
}
|
|
2901
|
-
await this.#
|
|
3141
|
+
await this.#onExpectsValue(optionDefinition);
|
|
2902
3142
|
break;
|
|
2903
3143
|
}
|
|
2904
3144
|
return index;
|
|
2905
3145
|
}
|
|
2906
|
-
#resolveOptionValue(
|
|
2907
|
-
|
|
2908
|
-
return;
|
|
2909
|
-
}
|
|
2910
|
-
return optionValue;
|
|
3146
|
+
#resolveOptionValue(target = "") {
|
|
3147
|
+
return target.startsWith("-") ? "" : target;
|
|
2911
3148
|
}
|
|
2912
3149
|
}
|
|
2913
3150
|
|
|
@@ -3032,7 +3269,7 @@ class ConfigFileOptionsWorker {
|
|
|
3032
3269
|
};
|
|
3033
3270
|
const text = isListItem
|
|
3034
3271
|
? this.#optionDiagnosticText.expectsListItemType(optionDefinition.name, optionDefinition.brand)
|
|
3035
|
-
: this.#optionDiagnosticText.
|
|
3272
|
+
: this.#optionDiagnosticText.requiresValueType(optionDefinition.name, optionDefinition.brand);
|
|
3036
3273
|
this.#onDiagnostic(Diagnostic.error(text, origin));
|
|
3037
3274
|
return;
|
|
3038
3275
|
}
|
|
@@ -3105,9 +3342,9 @@ class ConfigService {
|
|
|
3105
3342
|
static get defaultOptions() {
|
|
3106
3343
|
return ConfigService.#defaultOptions;
|
|
3107
3344
|
}
|
|
3108
|
-
#onDiagnostic
|
|
3345
|
+
#onDiagnostic(diagnostic) {
|
|
3109
3346
|
EventEmitter.dispatch(["config:error", { diagnostics: [diagnostic] }]);
|
|
3110
|
-
}
|
|
3347
|
+
}
|
|
3111
3348
|
async parseCommandLine(commandLineArgs) {
|
|
3112
3349
|
this.#commandLineOptions = {};
|
|
3113
3350
|
this.#pathMatch = [];
|
|
@@ -3137,28 +3374,6 @@ class ConfigService {
|
|
|
3137
3374
|
};
|
|
3138
3375
|
return mergedOptions;
|
|
3139
3376
|
}
|
|
3140
|
-
selectTestFiles() {
|
|
3141
|
-
const { pathMatch, rootPath, testFileMatch } = this.resolveConfig();
|
|
3142
|
-
let testFilePaths = this.compiler.sys.readDirectory(rootPath, ["ts", "tsx", "mts", "cts", "js", "jsx", "mjs", "cjs"], undefined, testFileMatch);
|
|
3143
|
-
if (pathMatch.length > 0) {
|
|
3144
|
-
testFilePaths = testFilePaths.filter((testFilePath) => pathMatch.some((match) => {
|
|
3145
|
-
const relativeTestFilePath = Path.relative("", testFilePath);
|
|
3146
|
-
return relativeTestFilePath.toLowerCase().includes(match.toLowerCase());
|
|
3147
|
-
}));
|
|
3148
|
-
}
|
|
3149
|
-
if (testFilePaths.length === 0) {
|
|
3150
|
-
const text = [
|
|
3151
|
-
"No test files were selected using current configuration.",
|
|
3152
|
-
`Root path: ${rootPath}`,
|
|
3153
|
-
`Test file match: ${testFileMatch.join(", ")}`,
|
|
3154
|
-
];
|
|
3155
|
-
if (pathMatch.length > 0) {
|
|
3156
|
-
text.push(`Path match: ${pathMatch.join(", ")}`);
|
|
3157
|
-
}
|
|
3158
|
-
this.#onDiagnostic(Diagnostic.error(text));
|
|
3159
|
-
}
|
|
3160
|
-
return testFilePaths;
|
|
3161
|
-
}
|
|
3162
3377
|
}
|
|
3163
3378
|
|
|
3164
3379
|
var OptionBrand;
|
|
@@ -3175,57 +3390,169 @@ var OptionGroup;
|
|
|
3175
3390
|
OptionGroup[OptionGroup["ConfigFile"] = 4] = "ConfigFile";
|
|
3176
3391
|
})(OptionGroup || (OptionGroup = {}));
|
|
3177
3392
|
|
|
3393
|
+
class GlobPattern {
|
|
3394
|
+
static #reservedCharacterRegex = /[^\w\s/]/g;
|
|
3395
|
+
static #parse(pattern, usageTarget) {
|
|
3396
|
+
const segments = pattern.split("/");
|
|
3397
|
+
let resultPattern = "\\.";
|
|
3398
|
+
let optionalSegmentCount = 0;
|
|
3399
|
+
for (const segment of segments) {
|
|
3400
|
+
if (segment === "**") {
|
|
3401
|
+
resultPattern += "(\\/(?!(node_modules)(\\/|$))[^./][^/]*)*?";
|
|
3402
|
+
continue;
|
|
3403
|
+
}
|
|
3404
|
+
if (usageTarget === "directories") {
|
|
3405
|
+
resultPattern += "(";
|
|
3406
|
+
optionalSegmentCount++;
|
|
3407
|
+
}
|
|
3408
|
+
resultPattern += `\\/`;
|
|
3409
|
+
const segmentPattern = segment.replace(GlobPattern.#reservedCharacterRegex, GlobPattern.#replaceReservedCharacter);
|
|
3410
|
+
if (segmentPattern !== segment) {
|
|
3411
|
+
resultPattern += "(?!(node_modules)(\\/|$))";
|
|
3412
|
+
}
|
|
3413
|
+
resultPattern += segmentPattern;
|
|
3414
|
+
}
|
|
3415
|
+
resultPattern += ")?".repeat(optionalSegmentCount);
|
|
3416
|
+
return resultPattern;
|
|
3417
|
+
}
|
|
3418
|
+
static #replaceReservedCharacter(match, offset) {
|
|
3419
|
+
switch (match) {
|
|
3420
|
+
case "*":
|
|
3421
|
+
return (offset === 0) ? "([^./][^/]*)?" : "([^/]*)?";
|
|
3422
|
+
case "?":
|
|
3423
|
+
return (offset === 0) ? "[^./]" : "[^/]";
|
|
3424
|
+
default:
|
|
3425
|
+
return `\\${match}`;
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
static toRegex(patterns, usageTarget) {
|
|
3429
|
+
const patternText = patterns.map((pattern) => `(${GlobPattern.#parse(pattern, usageTarget)})`).join("|");
|
|
3430
|
+
return new RegExp(`^(${patternText})$`);
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
|
|
3434
|
+
class SelectService {
|
|
3435
|
+
resolvedConfig;
|
|
3436
|
+
#includeDirectoryRegex;
|
|
3437
|
+
#includeFileRegex;
|
|
3438
|
+
constructor(resolvedConfig) {
|
|
3439
|
+
this.resolvedConfig = resolvedConfig;
|
|
3440
|
+
this.#includeDirectoryRegex = GlobPattern.toRegex(resolvedConfig.testFileMatch, "directories");
|
|
3441
|
+
this.#includeFileRegex = GlobPattern.toRegex(resolvedConfig.testFileMatch, "files");
|
|
3442
|
+
}
|
|
3443
|
+
#isDirectoryIncluded(directoryPath) {
|
|
3444
|
+
return this.#includeDirectoryRegex.test(directoryPath);
|
|
3445
|
+
}
|
|
3446
|
+
#isFileIncluded(filePath) {
|
|
3447
|
+
if (this.resolvedConfig.pathMatch.length > 0
|
|
3448
|
+
&& !this.resolvedConfig.pathMatch.some((match) => filePath.toLowerCase().includes(match.toLowerCase()))) {
|
|
3449
|
+
return false;
|
|
3450
|
+
}
|
|
3451
|
+
return this.#includeFileRegex.test(filePath);
|
|
3452
|
+
}
|
|
3453
|
+
isTestFile(filePath) {
|
|
3454
|
+
return this.#isFileIncluded(Path.relative(this.resolvedConfig.rootPath, filePath));
|
|
3455
|
+
}
|
|
3456
|
+
#onDiagnostic(diagnostic) {
|
|
3457
|
+
EventEmitter.dispatch(["select:error", { diagnostics: [diagnostic] }]);
|
|
3458
|
+
}
|
|
3459
|
+
async selectFiles() {
|
|
3460
|
+
const currentPath = ".";
|
|
3461
|
+
const testFilePaths = [];
|
|
3462
|
+
await this.#visitDirectory(currentPath, testFilePaths);
|
|
3463
|
+
if (testFilePaths.length === 0) {
|
|
3464
|
+
const text = [
|
|
3465
|
+
"No test files were selected using current configuration.",
|
|
3466
|
+
`Root path: ${this.resolvedConfig.rootPath}`,
|
|
3467
|
+
`Test file match: ${this.resolvedConfig.testFileMatch.join(", ")}`,
|
|
3468
|
+
];
|
|
3469
|
+
if (this.resolvedConfig.pathMatch.length > 0) {
|
|
3470
|
+
text.push(`Path match: ${this.resolvedConfig.pathMatch.join(", ")}`);
|
|
3471
|
+
}
|
|
3472
|
+
this.#onDiagnostic(Diagnostic.error(text));
|
|
3473
|
+
}
|
|
3474
|
+
return testFilePaths.sort();
|
|
3475
|
+
}
|
|
3476
|
+
async #visitDirectory(currentPath, testFilePaths) {
|
|
3477
|
+
const targetPath = Path.join(this.resolvedConfig.rootPath, currentPath);
|
|
3478
|
+
let entries = [];
|
|
3479
|
+
try {
|
|
3480
|
+
entries = await fs.readdir(targetPath, { withFileTypes: true });
|
|
3481
|
+
}
|
|
3482
|
+
catch {
|
|
3483
|
+
}
|
|
3484
|
+
for (const entry of entries) {
|
|
3485
|
+
let entryMeta;
|
|
3486
|
+
if (entry.isSymbolicLink()) {
|
|
3487
|
+
try {
|
|
3488
|
+
entryMeta = await fs.stat([targetPath, entry.name].join("/"));
|
|
3489
|
+
}
|
|
3490
|
+
catch {
|
|
3491
|
+
continue;
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
else {
|
|
3495
|
+
entryMeta = entry;
|
|
3496
|
+
}
|
|
3497
|
+
const entryPath = [currentPath, entry.name].join("/");
|
|
3498
|
+
if (entryMeta.isDirectory() && this.#isDirectoryIncluded(entryPath)) {
|
|
3499
|
+
await this.#visitDirectory(entryPath, testFilePaths);
|
|
3500
|
+
continue;
|
|
3501
|
+
}
|
|
3502
|
+
if (entryMeta.isFile() && this.#isFileIncluded(entryPath)) {
|
|
3503
|
+
testFilePaths.push([targetPath, entry.name].join("/"));
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3178
3509
|
class ManifestWorker {
|
|
3179
3510
|
#manifestFileName = "store-manifest.json";
|
|
3180
3511
|
#manifestFilePath;
|
|
3181
3512
|
#onDiagnostic;
|
|
3182
|
-
#prune;
|
|
3183
3513
|
#registryUrl = new URL("https://registry.npmjs.org");
|
|
3184
3514
|
#storePath;
|
|
3185
3515
|
#timeout = Environment.timeout * 1000;
|
|
3186
3516
|
#version = "1";
|
|
3187
|
-
constructor(storePath, onDiagnostic
|
|
3517
|
+
constructor(storePath, onDiagnostic) {
|
|
3188
3518
|
this.#storePath = storePath;
|
|
3189
3519
|
this.#onDiagnostic = onDiagnostic;
|
|
3190
3520
|
this.#manifestFilePath = Path.join(storePath, this.#manifestFileName);
|
|
3191
|
-
this.#prune = prune;
|
|
3192
3521
|
}
|
|
3193
|
-
async #create(
|
|
3194
|
-
const manifest = await this.#load(
|
|
3522
|
+
async #create() {
|
|
3523
|
+
const manifest = await this.#load();
|
|
3195
3524
|
if (manifest != null) {
|
|
3196
3525
|
await this.persist(manifest);
|
|
3197
3526
|
}
|
|
3198
3527
|
return manifest;
|
|
3199
3528
|
}
|
|
3200
|
-
async #fetch(
|
|
3529
|
+
async #fetch() {
|
|
3201
3530
|
return new Promise((resolve, reject) => {
|
|
3202
3531
|
const request = https.get(new URL("typescript", this.#registryUrl), {
|
|
3203
3532
|
headers: { accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*" },
|
|
3204
|
-
|
|
3205
|
-
}, (
|
|
3206
|
-
if (
|
|
3207
|
-
reject(new Error(`Request failed with status code ${String(
|
|
3208
|
-
|
|
3533
|
+
timeout: this.#timeout,
|
|
3534
|
+
}, (response) => {
|
|
3535
|
+
if (response.statusCode !== 200) {
|
|
3536
|
+
reject(new Error(`Request failed with status code ${String(response.statusCode)}.`));
|
|
3537
|
+
response.resume();
|
|
3209
3538
|
return;
|
|
3210
3539
|
}
|
|
3211
|
-
|
|
3540
|
+
response.setEncoding("utf8");
|
|
3212
3541
|
let rawData = "";
|
|
3213
|
-
|
|
3542
|
+
response.on("data", (chunk) => {
|
|
3214
3543
|
rawData += chunk;
|
|
3215
3544
|
});
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
resolve(packageMetadata);
|
|
3220
|
-
}
|
|
3221
|
-
catch (error) {
|
|
3222
|
-
reject(error);
|
|
3223
|
-
}
|
|
3545
|
+
response.on("end", () => {
|
|
3546
|
+
const packageMetadata = JSON.parse(rawData);
|
|
3547
|
+
resolve(packageMetadata);
|
|
3224
3548
|
});
|
|
3225
3549
|
});
|
|
3226
3550
|
request.on("error", (error) => {
|
|
3227
3551
|
reject(error);
|
|
3228
3552
|
});
|
|
3553
|
+
request.on("timeout", () => {
|
|
3554
|
+
request.destroy();
|
|
3555
|
+
});
|
|
3229
3556
|
});
|
|
3230
3557
|
}
|
|
3231
3558
|
isOutdated(manifest, ageTolerance = 0) {
|
|
@@ -3234,7 +3561,7 @@ class ManifestWorker {
|
|
|
3234
3561
|
}
|
|
3235
3562
|
return false;
|
|
3236
3563
|
}
|
|
3237
|
-
async #load(
|
|
3564
|
+
async #load(options) {
|
|
3238
3565
|
const manifest = {
|
|
3239
3566
|
$version: this.#version,
|
|
3240
3567
|
lastUpdated: Date.now(),
|
|
@@ -3242,27 +3569,21 @@ class ManifestWorker {
|
|
|
3242
3569
|
versions: [],
|
|
3243
3570
|
};
|
|
3244
3571
|
let packageMetadata;
|
|
3245
|
-
const abortController = new AbortController();
|
|
3246
|
-
const timeoutSignal = AbortSignal.timeout(this.#timeout);
|
|
3247
|
-
timeoutSignal.addEventListener("abort", () => {
|
|
3248
|
-
abortController.abort(`Setup timeout of ${this.#timeout / 1000}s was exceeded.`);
|
|
3249
|
-
}, { once: true });
|
|
3250
|
-
signal?.addEventListener("abort", () => {
|
|
3251
|
-
abortController.abort("Fetch got canceled by request.");
|
|
3252
|
-
}, { once: true });
|
|
3253
3572
|
try {
|
|
3254
|
-
packageMetadata = await this.#fetch(
|
|
3573
|
+
packageMetadata = await this.#fetch();
|
|
3255
3574
|
}
|
|
3256
3575
|
catch (error) {
|
|
3257
|
-
if (
|
|
3258
|
-
|
|
3259
|
-
if (error instanceof Error && error.name !== "AbortError") {
|
|
3260
|
-
text.push("Might be there is an issue with the registry or the network connection.");
|
|
3261
|
-
}
|
|
3262
|
-
this.#onDiagnostic(Diagnostic.fromError(text, error));
|
|
3576
|
+
if (options?.quite === true) {
|
|
3577
|
+
return;
|
|
3263
3578
|
}
|
|
3264
|
-
|
|
3265
|
-
|
|
3579
|
+
const text = [`Failed to fetch metadata of the 'typescript' package from '${this.#registryUrl.toString()}'.`];
|
|
3580
|
+
if (error instanceof Error && "code" in error && error.code === "ECONNRESET") {
|
|
3581
|
+
text.push(`Setup timeout of ${String(this.#timeout / 1000)}s was exceeded.`);
|
|
3582
|
+
}
|
|
3583
|
+
else {
|
|
3584
|
+
text.push("Might be there is an issue with the registry or the network connection.");
|
|
3585
|
+
}
|
|
3586
|
+
this.#onDiagnostic(Diagnostic.fromError(text, error));
|
|
3266
3587
|
return;
|
|
3267
3588
|
}
|
|
3268
3589
|
manifest.versions = Object.keys(packageMetadata.versions)
|
|
@@ -3283,10 +3604,10 @@ class ManifestWorker {
|
|
|
3283
3604
|
}
|
|
3284
3605
|
return manifest;
|
|
3285
3606
|
}
|
|
3286
|
-
async open(
|
|
3607
|
+
async open(options) {
|
|
3287
3608
|
let manifest;
|
|
3288
3609
|
if (!existsSync(this.#manifestFilePath)) {
|
|
3289
|
-
return this.#create(
|
|
3610
|
+
return this.#create();
|
|
3290
3611
|
}
|
|
3291
3612
|
let manifestText;
|
|
3292
3613
|
try {
|
|
@@ -3304,12 +3625,12 @@ class ManifestWorker {
|
|
|
3304
3625
|
catch {
|
|
3305
3626
|
}
|
|
3306
3627
|
if (manifest == null || manifest.$version !== this.#version) {
|
|
3307
|
-
await this.#
|
|
3308
|
-
return this.#create(
|
|
3628
|
+
await fs.rm(this.#storePath, { force: true, recursive: true });
|
|
3629
|
+
return this.#create();
|
|
3309
3630
|
}
|
|
3310
3631
|
if (this.isOutdated(manifest) || options?.refresh === true) {
|
|
3311
3632
|
const quite = options?.refresh !== true;
|
|
3312
|
-
const freshManifest = await this.#load(
|
|
3633
|
+
const freshManifest = await this.#load({ quite });
|
|
3313
3634
|
if (freshManifest != null) {
|
|
3314
3635
|
await this.persist(freshManifest);
|
|
3315
3636
|
return freshManifest;
|
|
@@ -3348,11 +3669,11 @@ class Lock {
|
|
|
3348
3669
|
}
|
|
3349
3670
|
const waitStartTime = Date.now();
|
|
3350
3671
|
while (isLocked) {
|
|
3351
|
-
if (options.
|
|
3672
|
+
if (options.cancellationToken?.isCancellationRequested === true) {
|
|
3352
3673
|
break;
|
|
3353
3674
|
}
|
|
3354
3675
|
if (Date.now() - waitStartTime > options.timeout) {
|
|
3355
|
-
options.onDiagnostic?.(`Lock wait timeout of ${options.timeout / 1000}s was exceeded.`);
|
|
3676
|
+
options.onDiagnostic?.(`Lock wait timeout of ${String(options.timeout / 1000)}s was exceeded.`);
|
|
3356
3677
|
break;
|
|
3357
3678
|
}
|
|
3358
3679
|
await Lock.#sleep(1000);
|
|
@@ -3377,51 +3698,55 @@ class PackageInstaller {
|
|
|
3377
3698
|
this.#storePath = storePath;
|
|
3378
3699
|
this.#onDiagnostic = onDiagnostic;
|
|
3379
3700
|
}
|
|
3380
|
-
async ensure(compilerVersion,
|
|
3701
|
+
async ensure(compilerVersion, cancellationToken) {
|
|
3381
3702
|
const installationPath = Path.join(this.#storePath, compilerVersion);
|
|
3382
3703
|
const readyFilePath = Path.join(installationPath, this.#readyFileName);
|
|
3704
|
+
const modulePath = Path.join(installationPath, "node_modules", "typescript", "lib", "typescript.js");
|
|
3705
|
+
if (existsSync(readyFilePath)) {
|
|
3706
|
+
return modulePath;
|
|
3707
|
+
}
|
|
3383
3708
|
if (await Lock.isLocked(installationPath, {
|
|
3709
|
+
cancellationToken,
|
|
3384
3710
|
onDiagnostic: (text) => {
|
|
3385
3711
|
this.#onDiagnostic(Diagnostic.error([`Failed to install 'typescript@${compilerVersion}'.`, text]));
|
|
3386
3712
|
},
|
|
3387
|
-
signal,
|
|
3388
3713
|
timeout: this.#timeout,
|
|
3389
3714
|
})) {
|
|
3390
3715
|
return;
|
|
3391
3716
|
}
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3717
|
+
const lock = new Lock(installationPath);
|
|
3718
|
+
EventEmitter.dispatch(["store:info", { compilerVersion, installationPath }]);
|
|
3719
|
+
try {
|
|
3720
|
+
await fs.mkdir(installationPath, { recursive: true });
|
|
3721
|
+
const packageJson = {
|
|
3722
|
+
name: "tstyche-typescript",
|
|
3723
|
+
version: compilerVersion,
|
|
3724
|
+
description: "Do not change. This package was generated by TSTyche",
|
|
3725
|
+
private: true,
|
|
3726
|
+
license: "MIT",
|
|
3727
|
+
dependencies: {
|
|
3728
|
+
typescript: compilerVersion,
|
|
3729
|
+
},
|
|
3730
|
+
};
|
|
3731
|
+
await fs.writeFile(Path.join(installationPath, "package.json"), JSON.stringify(packageJson, null, 2));
|
|
3732
|
+
await this.#install(installationPath);
|
|
3733
|
+
await fs.writeFile(readyFilePath, "");
|
|
3734
|
+
return modulePath;
|
|
3735
|
+
}
|
|
3736
|
+
catch (error) {
|
|
3737
|
+
this.#onDiagnostic(Diagnostic.fromError(`Failed to install 'typescript@${compilerVersion}'.`, error));
|
|
3738
|
+
}
|
|
3739
|
+
finally {
|
|
3740
|
+
lock.release();
|
|
3415
3741
|
}
|
|
3416
|
-
return
|
|
3742
|
+
return;
|
|
3417
3743
|
}
|
|
3418
|
-
async #install(cwd
|
|
3744
|
+
async #install(cwd) {
|
|
3419
3745
|
const args = ["install", "--ignore-scripts", "--no-bin-links", "--no-package-lock"];
|
|
3420
3746
|
return new Promise((resolve, reject) => {
|
|
3421
3747
|
const spawnedNpm = spawn("npm", args, {
|
|
3422
3748
|
cwd,
|
|
3423
3749
|
shell: true,
|
|
3424
|
-
signal,
|
|
3425
3750
|
stdio: "ignore",
|
|
3426
3751
|
timeout: this.#timeout,
|
|
3427
3752
|
});
|
|
@@ -3433,9 +3758,9 @@ class PackageInstaller {
|
|
|
3433
3758
|
resolve();
|
|
3434
3759
|
}
|
|
3435
3760
|
if (signal != null) {
|
|
3436
|
-
reject(new Error(`
|
|
3761
|
+
reject(new Error(`setup timeout of ${String(this.#timeout / 1000)}s was exceeded`));
|
|
3437
3762
|
}
|
|
3438
|
-
reject(new Error(`
|
|
3763
|
+
reject(new Error(`process exited with code ${String(code)}`));
|
|
3439
3764
|
});
|
|
3440
3765
|
});
|
|
3441
3766
|
}
|
|
@@ -3450,27 +3775,27 @@ class StoreService {
|
|
|
3450
3775
|
constructor() {
|
|
3451
3776
|
this.#storePath = Environment.storePath;
|
|
3452
3777
|
this.#packageInstaller = new PackageInstaller(this.#storePath, this.#onDiagnostic);
|
|
3453
|
-
this.#manifestWorker = new ManifestWorker(this.#storePath, this.#onDiagnostic
|
|
3778
|
+
this.#manifestWorker = new ManifestWorker(this.#storePath, this.#onDiagnostic);
|
|
3454
3779
|
}
|
|
3455
|
-
async getSupportedTags(
|
|
3456
|
-
await this.open(
|
|
3780
|
+
async getSupportedTags() {
|
|
3781
|
+
await this.open();
|
|
3457
3782
|
if (!this.#manifest) {
|
|
3458
3783
|
return [];
|
|
3459
3784
|
}
|
|
3460
3785
|
return [...Object.keys(this.#manifest.resolutions), ...this.#manifest.versions, "current"].sort();
|
|
3461
3786
|
}
|
|
3462
|
-
async install(tag,
|
|
3787
|
+
async install(tag, cancellationToken) {
|
|
3463
3788
|
if (tag === "current") {
|
|
3464
3789
|
return;
|
|
3465
3790
|
}
|
|
3466
|
-
const version = await this.resolveTag(tag
|
|
3791
|
+
const version = await this.resolveTag(tag);
|
|
3467
3792
|
if (version == null) {
|
|
3468
3793
|
this.#onDiagnostic(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
|
|
3469
3794
|
return;
|
|
3470
3795
|
}
|
|
3471
|
-
return this.#packageInstaller.ensure(version,
|
|
3796
|
+
return this.#packageInstaller.ensure(version, cancellationToken);
|
|
3472
3797
|
}
|
|
3473
|
-
async load(tag,
|
|
3798
|
+
async load(tag, cancellationToken) {
|
|
3474
3799
|
let compilerInstance = this.#compilerInstanceCache.get(tag);
|
|
3475
3800
|
if (compilerInstance != null) {
|
|
3476
3801
|
return compilerInstance;
|
|
@@ -3480,7 +3805,7 @@ class StoreService {
|
|
|
3480
3805
|
modulePath = Environment.typescriptPath;
|
|
3481
3806
|
}
|
|
3482
3807
|
else {
|
|
3483
|
-
const version = await this.resolveTag(tag
|
|
3808
|
+
const version = await this.resolveTag(tag);
|
|
3484
3809
|
if (version == null) {
|
|
3485
3810
|
this.#onDiagnostic(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
|
|
3486
3811
|
return;
|
|
@@ -3489,7 +3814,7 @@ class StoreService {
|
|
|
3489
3814
|
if (compilerInstance != null) {
|
|
3490
3815
|
return compilerInstance;
|
|
3491
3816
|
}
|
|
3492
|
-
modulePath = await this.#packageInstaller.ensure(version,
|
|
3817
|
+
modulePath = await this.#packageInstaller.ensure(version, cancellationToken);
|
|
3493
3818
|
}
|
|
3494
3819
|
if (modulePath != null) {
|
|
3495
3820
|
compilerInstance = await this.#loadModule(modulePath);
|
|
@@ -3518,23 +3843,20 @@ class StoreService {
|
|
|
3518
3843
|
}
|
|
3519
3844
|
return module.exports;
|
|
3520
3845
|
}
|
|
3521
|
-
#onDiagnostic
|
|
3846
|
+
#onDiagnostic(diagnostic) {
|
|
3522
3847
|
EventEmitter.dispatch(["store:error", { diagnostics: [diagnostic] }]);
|
|
3523
|
-
}
|
|
3524
|
-
async open(
|
|
3848
|
+
}
|
|
3849
|
+
async open() {
|
|
3525
3850
|
if (this.#manifest) {
|
|
3526
3851
|
return;
|
|
3527
3852
|
}
|
|
3528
|
-
this.#manifest = await this.#manifestWorker.open(
|
|
3853
|
+
this.#manifest = await this.#manifestWorker.open();
|
|
3529
3854
|
}
|
|
3530
|
-
|
|
3531
|
-
await fs.rm(this.#storePath, { force: true, recursive: true });
|
|
3532
|
-
};
|
|
3533
|
-
async resolveTag(tag, signal) {
|
|
3855
|
+
async resolveTag(tag) {
|
|
3534
3856
|
if (tag === "current") {
|
|
3535
3857
|
return tag;
|
|
3536
3858
|
}
|
|
3537
|
-
await this.open(
|
|
3859
|
+
await this.open();
|
|
3538
3860
|
if (!this.#manifest) {
|
|
3539
3861
|
return;
|
|
3540
3862
|
}
|
|
@@ -3543,16 +3865,16 @@ class StoreService {
|
|
|
3543
3865
|
}
|
|
3544
3866
|
return this.#manifest.resolutions[tag];
|
|
3545
3867
|
}
|
|
3546
|
-
async update(
|
|
3547
|
-
await this.#manifestWorker.open(
|
|
3868
|
+
async update() {
|
|
3869
|
+
await this.#manifestWorker.open({ refresh: true });
|
|
3548
3870
|
}
|
|
3549
|
-
async validateTag(tag
|
|
3871
|
+
async validateTag(tag) {
|
|
3550
3872
|
if (tag === "current") {
|
|
3551
3873
|
return Environment.typescriptPath != null;
|
|
3552
3874
|
}
|
|
3553
|
-
await this.open(
|
|
3875
|
+
await this.open();
|
|
3554
3876
|
if (!this.#manifest) {
|
|
3555
|
-
return
|
|
3877
|
+
return undefined;
|
|
3556
3878
|
}
|
|
3557
3879
|
if (this.#manifestWorker.isOutdated(this.#manifest, 60)
|
|
3558
3880
|
&& (!Version.isVersionTag(tag)
|
|
@@ -3568,27 +3890,30 @@ class StoreService {
|
|
|
3568
3890
|
}
|
|
3569
3891
|
|
|
3570
3892
|
class Cli {
|
|
3571
|
-
#
|
|
3893
|
+
#cancellationToken = new CancellationToken();
|
|
3894
|
+
#outputService;
|
|
3572
3895
|
#storeService;
|
|
3573
3896
|
constructor() {
|
|
3574
|
-
this.#
|
|
3897
|
+
this.#outputService = new OutputService();
|
|
3575
3898
|
this.#storeService = new StoreService();
|
|
3576
3899
|
}
|
|
3577
3900
|
#onStartupEvent = ([eventName, payload]) => {
|
|
3578
3901
|
switch (eventName) {
|
|
3579
3902
|
case "store:info":
|
|
3580
|
-
this.#
|
|
3903
|
+
this.#outputService.writeMessage(addsPackageStepText(payload.compilerVersion, payload.installationPath));
|
|
3581
3904
|
break;
|
|
3582
3905
|
case "config:error":
|
|
3906
|
+
case "select:error":
|
|
3583
3907
|
case "store:error":
|
|
3584
3908
|
for (const diagnostic of payload.diagnostics) {
|
|
3585
3909
|
switch (diagnostic.category) {
|
|
3586
3910
|
case "error":
|
|
3911
|
+
this.#cancellationToken.cancel();
|
|
3912
|
+
this.#outputService.writeError(diagnosticText(diagnostic));
|
|
3587
3913
|
process.exitCode = 1;
|
|
3588
|
-
this.#logger.writeError(diagnosticText(diagnostic));
|
|
3589
3914
|
break;
|
|
3590
3915
|
case "warning":
|
|
3591
|
-
this.#
|
|
3916
|
+
this.#outputService.writeWarning(diagnosticText(diagnostic));
|
|
3592
3917
|
break;
|
|
3593
3918
|
}
|
|
3594
3919
|
}
|
|
@@ -3599,15 +3924,15 @@ class Cli {
|
|
|
3599
3924
|
EventEmitter.addHandler(this.#onStartupEvent);
|
|
3600
3925
|
if (commandLineArguments.includes("--help")) {
|
|
3601
3926
|
const commandLineOptionDefinitions = OptionDefinitionsMap.for(2);
|
|
3602
|
-
this.#
|
|
3927
|
+
this.#outputService.writeMessage(helpText(commandLineOptionDefinitions, TSTyche.version));
|
|
3603
3928
|
return;
|
|
3604
3929
|
}
|
|
3605
3930
|
if (commandLineArguments.includes("--prune")) {
|
|
3606
|
-
await
|
|
3931
|
+
await fs.rm(Environment.storePath, { force: true, recursive: true });
|
|
3607
3932
|
return;
|
|
3608
3933
|
}
|
|
3609
3934
|
if (commandLineArguments.includes("--version")) {
|
|
3610
|
-
this.#
|
|
3935
|
+
this.#outputService.writeMessage(formattedText(TSTyche.version));
|
|
3611
3936
|
return;
|
|
3612
3937
|
}
|
|
3613
3938
|
if (commandLineArguments.includes("--update")) {
|
|
@@ -3620,16 +3945,16 @@ class Cli {
|
|
|
3620
3945
|
}
|
|
3621
3946
|
const configService = new ConfigService(compiler, this.#storeService);
|
|
3622
3947
|
await configService.parseCommandLine(commandLineArguments);
|
|
3623
|
-
if (
|
|
3948
|
+
if (this.#cancellationToken.isCancellationRequested) {
|
|
3624
3949
|
return;
|
|
3625
3950
|
}
|
|
3626
3951
|
await configService.readConfigFile();
|
|
3627
|
-
if (
|
|
3952
|
+
if (this.#cancellationToken.isCancellationRequested) {
|
|
3628
3953
|
return;
|
|
3629
3954
|
}
|
|
3630
3955
|
const resolvedConfig = configService.resolveConfig();
|
|
3631
|
-
if (
|
|
3632
|
-
this.#
|
|
3956
|
+
if (commandLineArguments.includes("--showConfig")) {
|
|
3957
|
+
this.#outputService.writeMessage(formattedText({
|
|
3633
3958
|
noColor: Environment.noColor,
|
|
3634
3959
|
noInteractive: Environment.noInteractive,
|
|
3635
3960
|
storePath: Environment.storePath,
|
|
@@ -3639,27 +3964,33 @@ class Cli {
|
|
|
3639
3964
|
}));
|
|
3640
3965
|
return;
|
|
3641
3966
|
}
|
|
3642
|
-
if (
|
|
3967
|
+
if (commandLineArguments.includes("--install")) {
|
|
3643
3968
|
for (const tag of resolvedConfig.target) {
|
|
3644
3969
|
await this.#storeService.install(tag);
|
|
3645
3970
|
}
|
|
3646
3971
|
return;
|
|
3647
3972
|
}
|
|
3973
|
+
const selectService = new SelectService(resolvedConfig);
|
|
3648
3974
|
let testFiles = [];
|
|
3649
3975
|
if (resolvedConfig.testFileMatch.length !== 0) {
|
|
3650
|
-
testFiles =
|
|
3976
|
+
testFiles = await selectService.selectFiles();
|
|
3651
3977
|
if (testFiles.length === 0) {
|
|
3652
3978
|
return;
|
|
3653
3979
|
}
|
|
3654
|
-
if (
|
|
3655
|
-
this.#
|
|
3980
|
+
if (commandLineArguments.includes("--listFiles")) {
|
|
3981
|
+
this.#outputService.writeMessage(formattedText(testFiles));
|
|
3656
3982
|
return;
|
|
3657
3983
|
}
|
|
3658
3984
|
}
|
|
3659
3985
|
EventEmitter.removeHandler(this.#onStartupEvent);
|
|
3660
3986
|
const tstyche = new TSTyche(resolvedConfig, this.#storeService);
|
|
3661
|
-
|
|
3987
|
+
if (resolvedConfig.watch === true) {
|
|
3988
|
+
await tstyche.watch(testFiles, selectService);
|
|
3989
|
+
}
|
|
3990
|
+
else {
|
|
3991
|
+
await tstyche.run(testFiles);
|
|
3992
|
+
}
|
|
3662
3993
|
}
|
|
3663
3994
|
}
|
|
3664
3995
|
|
|
3665
|
-
export { Assertion, Cli, CollectService, Color, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, Environment, EventEmitter, Expect, ExpectResult, FileResult,
|
|
3996
|
+
export { Assertion, CancellationToken, Cli, CollectService, Color, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, Environment, EventEmitter, Expect, ExpectResult, FileResult, InputService, Line, OptionBrand, OptionDefinitionsMap, OptionGroup, OutputService, Path, ProjectResult, ProjectService, Reporter, Result, ResultCount, ResultManager, ResultStatus, ResultTiming, Scribbler, SelectService, StoreService, SummaryReporter, TSTyche, TargetResult, TaskRunner, TestMember, TestMemberBrand, TestMemberFlags, TestResult, TestTree, Text, ThoroughReporter, Version, WatchModeReporter, Watcher, addsPackageStepText, describeNameText, diagnosticText, fileStatusText, fileViewText, formattedText, helpText, summaryText, testNameText, usesCompilerStepText, watchModeUsageText };
|