tstyche 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/build/index.d.cts +25 -0
- package/build/index.d.ts +25 -0
- package/build/tstyche.d.ts +186 -138
- package/build/tstyche.js +877 -641
- package/package.json +17 -14
package/build/tstyche.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import process from 'node:process';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
5
|
import os from 'node:os';
|
|
6
|
-
import { existsSync, writeFileSync, rmSync } from 'node:fs';
|
|
6
|
+
import { watch, existsSync, writeFileSync, rmSync } from 'node:fs';
|
|
7
7
|
import fs from 'node:fs/promises';
|
|
8
8
|
import vm from 'node:vm';
|
|
9
9
|
import { spawn } from 'node:child_process';
|
|
@@ -613,12 +613,27 @@ function describeNameText(name, indent = 0) {
|
|
|
613
613
|
return jsx(Line, { indent: indent + 1, children: name });
|
|
614
614
|
}
|
|
615
615
|
|
|
616
|
-
function
|
|
616
|
+
function BreadcrumbsText({ ancestor }) {
|
|
617
|
+
const text = [];
|
|
618
|
+
while ("name" in ancestor) {
|
|
619
|
+
text.unshift(" ❭ ", ancestor.name);
|
|
620
|
+
ancestor = ancestor.parent;
|
|
621
|
+
}
|
|
622
|
+
return jsx(Text, { color: "90", children: text.join("") });
|
|
623
|
+
}
|
|
624
|
+
function CodeLineText({ lineText, gutterWidth, lineNumberColor = "90", lineNumberText }) {
|
|
625
|
+
return (jsx(Line, { children: [jsx(Text, { color: lineNumberColor, children: lineNumberText.padStart(gutterWidth) }), jsx(Text, { color: "90", children: " | " }), lineText] }));
|
|
626
|
+
}
|
|
627
|
+
function SquiggleLineText({ gutterWidth, indentWidth = 0, squiggleWidth }) {
|
|
628
|
+
return (jsx(Line, { children: [" ".repeat(gutterWidth), jsx(Text, { color: "90", children: " | " }), " ".repeat(indentWidth), jsx(Text, { color: "31", children: "~".repeat(squiggleWidth === 0 ? 1 : squiggleWidth) })] }));
|
|
629
|
+
}
|
|
630
|
+
function CodeSpanText({ diagnosticOrigin }) {
|
|
617
631
|
const lastLineInFile = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.sourceFile.text.length).line;
|
|
618
|
-
const { character:
|
|
619
|
-
const
|
|
632
|
+
const { character: firstMarkedLineCharacter, line: firstMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.start);
|
|
633
|
+
const { character: lastMarkedLineCharacter, line: lastMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.end);
|
|
634
|
+
const firstLine = Math.max(firstMarkedLine - 2, 0);
|
|
620
635
|
const lastLine = Math.min(firstLine + 5, lastLineInFile);
|
|
621
|
-
const
|
|
636
|
+
const gutterWidth = String(lastLine + 1).length + 2;
|
|
622
637
|
const codeSpan = [];
|
|
623
638
|
for (let index = firstLine; index <= lastLine; index++) {
|
|
624
639
|
const lineStart = diagnosticOrigin.sourceFile.getPositionOfLineAndCharacter(index, 0);
|
|
@@ -627,18 +642,26 @@ function CodeSpanText(diagnosticOrigin) {
|
|
|
627
642
|
: diagnosticOrigin.sourceFile.getPositionOfLineAndCharacter(index + 1, 0);
|
|
628
643
|
const lineNumberText = String(index + 1);
|
|
629
644
|
const lineText = diagnosticOrigin.sourceFile.text.slice(lineStart, lineEnd).trimEnd().replace(/\t/g, " ");
|
|
630
|
-
if (index
|
|
631
|
-
codeSpan.push(jsx(
|
|
645
|
+
if (index >= firstMarkedLine && index <= lastMarkedLine) {
|
|
646
|
+
codeSpan.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumberColor: "31", lineNumberText: lineNumberText, lineText: lineText }));
|
|
647
|
+
if (index === firstMarkedLine) {
|
|
648
|
+
const squiggleLength = index === lastMarkedLine
|
|
649
|
+
? lastMarkedLineCharacter - firstMarkedLineCharacter
|
|
650
|
+
: lineText.length - firstMarkedLineCharacter;
|
|
651
|
+
codeSpan.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, indentWidth: firstMarkedLineCharacter, squiggleWidth: squiggleLength }));
|
|
652
|
+
}
|
|
653
|
+
else if (index === lastMarkedLine) {
|
|
654
|
+
codeSpan.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleWidth: lastMarkedLineCharacter }));
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
codeSpan.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleWidth: lineText.length }));
|
|
658
|
+
}
|
|
632
659
|
}
|
|
633
660
|
else {
|
|
634
|
-
codeSpan.push(jsx(
|
|
661
|
+
codeSpan.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumberText: lineNumberText, lineText: lineText }));
|
|
635
662
|
}
|
|
636
663
|
}
|
|
637
|
-
const
|
|
638
|
-
jsx(Text, { color: "90", children: " ❭ " }),
|
|
639
|
-
jsx(Text, { children: ancestor }),
|
|
640
|
-
]);
|
|
641
|
-
const location = (jsx(Line, { children: [" ".repeat(lineNumberMaxWidth + 5), jsx(Text, { color: "90", children: "at" }), jsx(Text, { children: " " }), jsx(Text, { color: "36", children: Path.relative("", diagnosticOrigin.sourceFile.fileName) }), jsx(Text, { color: "90", children: [":", String(markedLine + 1), ":", String(markedCharacter + 1)] }), breadcrumbs] }));
|
|
664
|
+
const location = (jsx(Line, { children: [" ".repeat(gutterWidth + 2), jsx(Text, { color: "90", children: " at " }), jsx(Text, { color: "36", children: Path.relative("", diagnosticOrigin.sourceFile.fileName) }), jsx(Text, { color: "90", children: [":", String(firstMarkedLine + 1), ":", String(firstMarkedLineCharacter + 1)] }), diagnosticOrigin.assertion && jsx(BreadcrumbsText, { ancestor: diagnosticOrigin.assertion.parent })] }));
|
|
642
665
|
return (jsx(Text, { children: [codeSpan, jsx(Line, {}), location] }));
|
|
643
666
|
}
|
|
644
667
|
|
|
@@ -647,7 +670,7 @@ function DiagnosticText({ diagnostic }) {
|
|
|
647
670
|
const text = Array.isArray(diagnostic.text) ? diagnostic.text : [diagnostic.text];
|
|
648
671
|
const message = text.map((text, index) => (jsx(Text, { children: [index === 1 ? jsx(Line, {}) : undefined, jsx(Line, { children: [text, code] })] })));
|
|
649
672
|
const related = diagnostic.related?.map((relatedDiagnostic) => jsx(DiagnosticText, { diagnostic: relatedDiagnostic }));
|
|
650
|
-
const codeSpan = diagnostic.origin ? (jsx(Text, { children: [jsx(Line, {}), jsx(CodeSpanText, {
|
|
673
|
+
const codeSpan = diagnostic.origin ? (jsx(Text, { children: [jsx(Line, {}), jsx(CodeSpanText, { diagnosticOrigin: diagnostic.origin })] })) : undefined;
|
|
651
674
|
return (jsx(Text, { children: [message, codeSpan, jsx(Line, {}), jsx(Text, { indent: 2, children: related })] }));
|
|
652
675
|
}
|
|
653
676
|
function diagnosticText(diagnostic) {
|
|
@@ -790,21 +813,21 @@ class OutputService {
|
|
|
790
813
|
eraseLastLine() {
|
|
791
814
|
this.#stdout.write("\u001B[1A\u001B[0K");
|
|
792
815
|
}
|
|
793
|
-
#
|
|
794
|
-
const elements = Array.isArray(
|
|
816
|
+
#writeTo(stream, element) {
|
|
817
|
+
const elements = Array.isArray(element) ? element : [element];
|
|
795
818
|
for (const element of elements) {
|
|
796
819
|
stream.write(this.#scribbler.render(element));
|
|
797
820
|
}
|
|
798
821
|
this.#isClear = false;
|
|
799
822
|
}
|
|
800
|
-
writeError(
|
|
801
|
-
this.#
|
|
823
|
+
writeError(element) {
|
|
824
|
+
this.#writeTo(this.#stderr, element);
|
|
802
825
|
}
|
|
803
|
-
writeMessage(
|
|
804
|
-
this.#
|
|
826
|
+
writeMessage(element) {
|
|
827
|
+
this.#writeTo(this.#stdout, element);
|
|
805
828
|
}
|
|
806
|
-
writeWarning(
|
|
807
|
-
this.#
|
|
829
|
+
writeWarning(element) {
|
|
830
|
+
this.#writeTo(this.#stderr, element);
|
|
808
831
|
}
|
|
809
832
|
}
|
|
810
833
|
|
|
@@ -814,10 +837,8 @@ function RowText({ label, text }) {
|
|
|
814
837
|
function CountText({ failed, passed, skipped, todo, total }) {
|
|
815
838
|
return (jsx(Text, { children: [failed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "31", children: [String(failed), " failed"] }), jsx(Text, { children: ", " })] })) : undefined, skipped > 0 ? (jsx(Text, { children: [jsx(Text, { color: "33", children: [String(skipped), " skipped"] }), jsx(Text, { children: ", " })] })) : undefined, todo > 0 ? (jsx(Text, { children: [jsx(Text, { color: "35", children: [String(todo), " todo"] }), jsx(Text, { children: ", " })] })) : undefined, passed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "32", children: [String(passed), " passed"] }), jsx(Text, { children: ", " })] })) : undefined, jsx(Text, { children: [String(total), jsx(Text, { children: " total" })] })] }));
|
|
816
839
|
}
|
|
817
|
-
function DurationText({
|
|
818
|
-
|
|
819
|
-
const seconds = duration % 60;
|
|
820
|
-
return (jsx(Text, { children: [minutes > 0 ? `${String(minutes)}m ` : undefined, `${String(Math.round(seconds * 10) / 10)}s`] }));
|
|
840
|
+
function DurationText({ seconds }) {
|
|
841
|
+
return jsx(Text, { children: `${String(Math.round(seconds * 10) / 10)}s` });
|
|
821
842
|
}
|
|
822
843
|
function MatchText({ text }) {
|
|
823
844
|
if (typeof text === "string") {
|
|
@@ -835,7 +856,7 @@ function RanFilesText({ onlyMatch, pathMatch, skipMatch }) {
|
|
|
835
856
|
testNameMatchText.push(jsx(Text, { children: [jsx(Text, { color: "90", children: "matching " }), jsx(MatchText, { text: onlyMatch })] }));
|
|
836
857
|
}
|
|
837
858
|
if (skipMatch != null) {
|
|
838
|
-
testNameMatchText.push(jsx(Text, { children: [onlyMatch
|
|
859
|
+
testNameMatchText.push(jsx(Text, { children: [onlyMatch != null ? jsx(Text, { color: "90", children: " and " }) : undefined, jsx(Text, { color: "90", children: "not matching " }), jsx(MatchText, { text: skipMatch })] }));
|
|
839
860
|
}
|
|
840
861
|
let pathMatchText;
|
|
841
862
|
if (pathMatch.length > 0) {
|
|
@@ -851,7 +872,7 @@ function summaryText({ duration, expectCount, fileCount, onlyMatch, pathMatch, s
|
|
|
851
872
|
const fileCountText = (jsx(RowText, { label: "Test files", text: jsx(CountText, { failed: fileCount.failed, passed: fileCount.passed, skipped: fileCount.skipped, todo: fileCount.todo, total: fileCount.total }) }));
|
|
852
873
|
const testCountText = (jsx(RowText, { label: "Tests", text: jsx(CountText, { failed: testCount.failed, passed: testCount.passed, skipped: testCount.skipped, todo: testCount.todo, total: testCount.total }) }));
|
|
853
874
|
const assertionCountText = (jsx(RowText, { label: "Assertions", text: jsx(CountText, { failed: expectCount.failed, passed: expectCount.passed, skipped: expectCount.skipped, todo: expectCount.todo, total: expectCount.total }) }));
|
|
854
|
-
return (jsx(Text, { children: [targetCountText, fileCountText, testCount.total > 0 ? testCountText : undefined, expectCount.total > 0 ? assertionCountText : undefined, jsx(RowText, { label: "Duration", text: jsx(DurationText, {
|
|
875
|
+
return (jsx(Text, { children: [targetCountText, fileCountText, testCount.total > 0 ? testCountText : undefined, expectCount.total > 0 ? assertionCountText : undefined, jsx(RowText, { label: "Duration", text: jsx(DurationText, { seconds: duration / 1000 }) }), jsx(Line, {}), jsx(RanFilesText, { onlyMatch: onlyMatch, pathMatch: pathMatch, skipMatch: skipMatch })] }));
|
|
855
876
|
}
|
|
856
877
|
|
|
857
878
|
function StatusText({ status }) {
|
|
@@ -1155,7 +1176,6 @@ class WatchReporter extends Reporter {
|
|
|
1155
1176
|
|
|
1156
1177
|
class CancellationToken {
|
|
1157
1178
|
#isCancelled = false;
|
|
1158
|
-
#handlers = new Set();
|
|
1159
1179
|
#reason;
|
|
1160
1180
|
get isCancellationRequested() {
|
|
1161
1181
|
return this.#isCancelled;
|
|
@@ -1165,16 +1185,10 @@ class CancellationToken {
|
|
|
1165
1185
|
}
|
|
1166
1186
|
cancel(reason) {
|
|
1167
1187
|
if (!this.#isCancelled) {
|
|
1168
|
-
for (const handler of this.#handlers) {
|
|
1169
|
-
handler(reason);
|
|
1170
|
-
}
|
|
1171
1188
|
this.#isCancelled = true;
|
|
1172
1189
|
this.#reason = reason;
|
|
1173
1190
|
}
|
|
1174
1191
|
}
|
|
1175
|
-
onCancellationRequested(handler) {
|
|
1176
|
-
this.#handlers.add(handler);
|
|
1177
|
-
}
|
|
1178
1192
|
reset() {
|
|
1179
1193
|
if (this.#isCancelled) {
|
|
1180
1194
|
this.#isCancelled = false;
|
|
@@ -1188,10 +1202,10 @@ var CancellationReason;
|
|
|
1188
1202
|
CancellationReason["ConfigChange"] = "configChange";
|
|
1189
1203
|
CancellationReason["ConfigError"] = "configError";
|
|
1190
1204
|
CancellationReason["FailFast"] = "failFast";
|
|
1205
|
+
CancellationReason["WatchClose"] = "watchClose";
|
|
1191
1206
|
})(CancellationReason || (CancellationReason = {}));
|
|
1192
1207
|
|
|
1193
1208
|
class Watcher {
|
|
1194
|
-
#abortController = new AbortController();
|
|
1195
1209
|
#onChanged;
|
|
1196
1210
|
#onRemoved;
|
|
1197
1211
|
#recursive;
|
|
@@ -1204,34 +1218,28 @@ class Watcher {
|
|
|
1204
1218
|
this.#recursive = options?.recursive;
|
|
1205
1219
|
}
|
|
1206
1220
|
close() {
|
|
1207
|
-
this.#
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
this.#watcher =
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
if (
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
else {
|
|
1219
|
-
await this.#onRemoved(filePath);
|
|
1220
|
-
}
|
|
1221
|
+
this.#watcher?.close();
|
|
1222
|
+
}
|
|
1223
|
+
watch() {
|
|
1224
|
+
this.#watcher = watch(this.#targetPath, { recursive: this.#recursive }, (_eventType, fileName) => {
|
|
1225
|
+
if (fileName != null) {
|
|
1226
|
+
const filePath = Path.resolve(this.#targetPath, fileName);
|
|
1227
|
+
if (existsSync(filePath)) {
|
|
1228
|
+
this.#onChanged(filePath);
|
|
1229
|
+
}
|
|
1230
|
+
else {
|
|
1231
|
+
this.#onRemoved(filePath);
|
|
1221
1232
|
}
|
|
1222
1233
|
}
|
|
1223
|
-
}
|
|
1224
|
-
catch (error) {
|
|
1225
|
-
if (error instanceof Error && error.name === "AbortError") ;
|
|
1226
|
-
}
|
|
1234
|
+
});
|
|
1227
1235
|
}
|
|
1228
1236
|
}
|
|
1229
1237
|
|
|
1230
1238
|
class FileWatcher extends Watcher {
|
|
1231
1239
|
constructor(targetPath, onChanged) {
|
|
1232
|
-
const onChangedFile =
|
|
1240
|
+
const onChangedFile = (filePath) => {
|
|
1233
1241
|
if (filePath === targetPath) {
|
|
1234
|
-
|
|
1242
|
+
onChanged();
|
|
1235
1243
|
}
|
|
1236
1244
|
};
|
|
1237
1245
|
super(Path.dirname(targetPath), onChangedFile);
|
|
@@ -1239,29 +1247,33 @@ class FileWatcher extends Watcher {
|
|
|
1239
1247
|
}
|
|
1240
1248
|
|
|
1241
1249
|
class DiagnosticOrigin {
|
|
1242
|
-
|
|
1250
|
+
assertion;
|
|
1243
1251
|
end;
|
|
1244
1252
|
sourceFile;
|
|
1245
1253
|
start;
|
|
1246
|
-
constructor(start, end, sourceFile,
|
|
1254
|
+
constructor(start, end, sourceFile, assertion) {
|
|
1247
1255
|
this.start = start;
|
|
1248
1256
|
this.end = end;
|
|
1249
1257
|
this.sourceFile = sourceFile;
|
|
1250
|
-
this.
|
|
1258
|
+
this.assertion = assertion;
|
|
1259
|
+
}
|
|
1260
|
+
static fromAssertion(assertion) {
|
|
1261
|
+
const node = assertion.matcherName;
|
|
1262
|
+
return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertion);
|
|
1251
1263
|
}
|
|
1252
1264
|
static fromJsonNode(node, sourceFile, skipTrivia) {
|
|
1253
1265
|
return new DiagnosticOrigin(skipTrivia(node.pos, sourceFile), node.end, sourceFile);
|
|
1254
1266
|
}
|
|
1255
|
-
static fromNode(node,
|
|
1256
|
-
return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(),
|
|
1267
|
+
static fromNode(node, assertion) {
|
|
1268
|
+
return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertion);
|
|
1257
1269
|
}
|
|
1258
1270
|
}
|
|
1259
1271
|
|
|
1260
1272
|
class Diagnostic {
|
|
1261
1273
|
category;
|
|
1262
1274
|
code;
|
|
1263
|
-
related;
|
|
1264
1275
|
origin;
|
|
1276
|
+
related;
|
|
1265
1277
|
text;
|
|
1266
1278
|
constructor(text, category, origin) {
|
|
1267
1279
|
this.text = text;
|
|
@@ -1272,9 +1284,6 @@ class Diagnostic {
|
|
|
1272
1284
|
if (options.code != null) {
|
|
1273
1285
|
this.code = options.code;
|
|
1274
1286
|
}
|
|
1275
|
-
if (options.origin != null) {
|
|
1276
|
-
this.origin = options.origin;
|
|
1277
|
-
}
|
|
1278
1287
|
if (options.related != null) {
|
|
1279
1288
|
this.related = options.related;
|
|
1280
1289
|
}
|
|
@@ -1283,16 +1292,22 @@ class Diagnostic {
|
|
|
1283
1292
|
static error(text, origin) {
|
|
1284
1293
|
return new Diagnostic(text, "error", origin);
|
|
1285
1294
|
}
|
|
1295
|
+
extendWith(text, origin) {
|
|
1296
|
+
return new Diagnostic([this.text, text].flat(), this.category, origin ?? this.origin);
|
|
1297
|
+
}
|
|
1286
1298
|
static fromDiagnostics(diagnostics, compiler) {
|
|
1287
1299
|
return diagnostics.map((diagnostic) => {
|
|
1288
|
-
const category = "error";
|
|
1289
1300
|
const code = `ts(${String(diagnostic.code)})`;
|
|
1290
1301
|
let origin;
|
|
1291
|
-
|
|
1292
|
-
if (Diagnostic.isTsDiagnosticWithLocation(diagnostic)) {
|
|
1302
|
+
if (Diagnostic.#isTsDiagnosticWithLocation(diagnostic)) {
|
|
1293
1303
|
origin = new DiagnosticOrigin(diagnostic.start, diagnostic.start + diagnostic.length, diagnostic.file);
|
|
1294
1304
|
}
|
|
1295
|
-
|
|
1305
|
+
let related;
|
|
1306
|
+
if (diagnostic.relatedInformation != null) {
|
|
1307
|
+
related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation, compiler);
|
|
1308
|
+
}
|
|
1309
|
+
const text = compiler.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
1310
|
+
return new Diagnostic(text, "error", origin).add({ code, related });
|
|
1296
1311
|
});
|
|
1297
1312
|
}
|
|
1298
1313
|
static fromError(text, error) {
|
|
@@ -1306,7 +1321,7 @@ class Diagnostic {
|
|
|
1306
1321
|
}
|
|
1307
1322
|
return Diagnostic.error(messageText);
|
|
1308
1323
|
}
|
|
1309
|
-
static isTsDiagnosticWithLocation(diagnostic) {
|
|
1324
|
+
static #isTsDiagnosticWithLocation(diagnostic) {
|
|
1310
1325
|
return diagnostic.file != null && diagnostic.start != null && diagnostic.length != null;
|
|
1311
1326
|
}
|
|
1312
1327
|
static warning(text, origin) {
|
|
@@ -1428,7 +1443,7 @@ class SelectService {
|
|
|
1428
1443
|
isTestFile(filePath) {
|
|
1429
1444
|
return this.#isFileIncluded(Path.relative(this.#resolvedConfig.rootPath, filePath));
|
|
1430
1445
|
}
|
|
1431
|
-
#
|
|
1446
|
+
#onDiagnostics(diagnostic) {
|
|
1432
1447
|
EventEmitter.dispatch(["select:error", { diagnostics: [diagnostic] }]);
|
|
1433
1448
|
}
|
|
1434
1449
|
async selectFiles() {
|
|
@@ -1436,18 +1451,21 @@ class SelectService {
|
|
|
1436
1451
|
const testFilePaths = [];
|
|
1437
1452
|
await this.#visitDirectory(currentPath, testFilePaths);
|
|
1438
1453
|
if (testFilePaths.length === 0) {
|
|
1439
|
-
this.#
|
|
1454
|
+
this.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereSelected(this.#resolvedConfig)));
|
|
1440
1455
|
}
|
|
1441
1456
|
return testFilePaths.sort();
|
|
1442
1457
|
}
|
|
1443
1458
|
async #visitDirectory(currentPath, testFilePaths) {
|
|
1444
1459
|
const targetPath = Path.join(this.#resolvedConfig.rootPath, currentPath);
|
|
1445
|
-
let entries
|
|
1460
|
+
let entries;
|
|
1446
1461
|
try {
|
|
1447
1462
|
entries = await fs.readdir(targetPath, { withFileTypes: true });
|
|
1448
1463
|
}
|
|
1449
1464
|
catch {
|
|
1450
1465
|
}
|
|
1466
|
+
if (!entries) {
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1451
1469
|
for (const entry of entries) {
|
|
1452
1470
|
let entryMeta;
|
|
1453
1471
|
if (entry.isSymbolicLink()) {
|
|
@@ -1455,12 +1473,14 @@ class SelectService {
|
|
|
1455
1473
|
entryMeta = await fs.stat([targetPath, entry.name].join("/"));
|
|
1456
1474
|
}
|
|
1457
1475
|
catch {
|
|
1458
|
-
continue;
|
|
1459
1476
|
}
|
|
1460
1477
|
}
|
|
1461
1478
|
else {
|
|
1462
1479
|
entryMeta = entry;
|
|
1463
1480
|
}
|
|
1481
|
+
if (!entryMeta) {
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1464
1484
|
const entryPath = [currentPath, entry.name].join("/");
|
|
1465
1485
|
if (entryMeta.isDirectory() && this.#isDirectoryIncluded(entryPath)) {
|
|
1466
1486
|
await this.#visitDirectory(entryPath, testFilePaths);
|
|
@@ -1473,16 +1493,30 @@ class SelectService {
|
|
|
1473
1493
|
}
|
|
1474
1494
|
}
|
|
1475
1495
|
|
|
1476
|
-
class
|
|
1496
|
+
class Debounce {
|
|
1497
|
+
#delay;
|
|
1498
|
+
#onResolve;
|
|
1499
|
+
#resolve;
|
|
1477
1500
|
#timeout;
|
|
1478
|
-
|
|
1501
|
+
constructor(delay, onResolve) {
|
|
1502
|
+
this.#delay = delay;
|
|
1503
|
+
this.#onResolve = onResolve;
|
|
1504
|
+
}
|
|
1505
|
+
clearTimeout() {
|
|
1479
1506
|
clearTimeout(this.#timeout);
|
|
1480
1507
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1508
|
+
refreshTimeout() {
|
|
1509
|
+
this.clearTimeout();
|
|
1510
|
+
this.#timeout = setTimeout(() => {
|
|
1511
|
+
this.#resolve?.(this.#onResolve());
|
|
1512
|
+
}, this.#delay);
|
|
1513
|
+
}
|
|
1514
|
+
resolveWith(value) {
|
|
1515
|
+
this.#resolve?.(value);
|
|
1516
|
+
}
|
|
1517
|
+
setup() {
|
|
1518
|
+
return new Promise((resolve) => {
|
|
1519
|
+
this.#resolve = resolve;
|
|
1486
1520
|
});
|
|
1487
1521
|
}
|
|
1488
1522
|
}
|
|
@@ -1491,16 +1525,33 @@ class WatchService {
|
|
|
1491
1525
|
#changedTestFiles = new Map();
|
|
1492
1526
|
#inputService;
|
|
1493
1527
|
#resolvedConfig;
|
|
1494
|
-
#runCallback;
|
|
1495
1528
|
#selectService;
|
|
1496
|
-
#timer = new Timer();
|
|
1497
1529
|
#watchers = [];
|
|
1498
1530
|
#watchedTestFiles;
|
|
1499
|
-
constructor(resolvedConfig,
|
|
1531
|
+
constructor(resolvedConfig, selectService, testFiles) {
|
|
1500
1532
|
this.#resolvedConfig = resolvedConfig;
|
|
1501
|
-
this.#runCallback = runCallback;
|
|
1502
1533
|
this.#selectService = selectService;
|
|
1503
1534
|
this.#watchedTestFiles = new Map(testFiles.map((testFile) => [testFile.path, testFile]));
|
|
1535
|
+
}
|
|
1536
|
+
#onDiagnostics(diagnostic) {
|
|
1537
|
+
EventEmitter.dispatch(["watch:error", { diagnostics: [diagnostic] }]);
|
|
1538
|
+
}
|
|
1539
|
+
async *watch(cancellationToken) {
|
|
1540
|
+
const onResolve = () => {
|
|
1541
|
+
const testFiles = [...this.#changedTestFiles.values()];
|
|
1542
|
+
this.#changedTestFiles.clear();
|
|
1543
|
+
return testFiles;
|
|
1544
|
+
};
|
|
1545
|
+
const debounce = new Debounce(100, onResolve);
|
|
1546
|
+
const onClose = (reason) => {
|
|
1547
|
+
debounce.clearTimeout();
|
|
1548
|
+
this.#inputService?.close();
|
|
1549
|
+
for (const watcher of this.#watchers) {
|
|
1550
|
+
watcher.close();
|
|
1551
|
+
}
|
|
1552
|
+
cancellationToken.cancel(reason);
|
|
1553
|
+
debounce.resolveWith([]);
|
|
1554
|
+
};
|
|
1504
1555
|
const onInput = (chunk) => {
|
|
1505
1556
|
switch (chunk.toLowerCase()) {
|
|
1506
1557
|
case "\u0003":
|
|
@@ -1508,47 +1559,23 @@ class WatchService {
|
|
|
1508
1559
|
case "\u001B":
|
|
1509
1560
|
case "q":
|
|
1510
1561
|
case "x": {
|
|
1511
|
-
|
|
1562
|
+
onClose("watchClose");
|
|
1512
1563
|
break;
|
|
1513
1564
|
}
|
|
1514
1565
|
case "\u000D":
|
|
1515
1566
|
case "\u0020":
|
|
1516
1567
|
case "a": {
|
|
1517
|
-
|
|
1568
|
+
debounce.clearTimeout();
|
|
1569
|
+
if (this.#watchedTestFiles.size !== 0) {
|
|
1570
|
+
debounce.resolveWith([...this.#watchedTestFiles.values()]);
|
|
1571
|
+
}
|
|
1518
1572
|
break;
|
|
1519
1573
|
}
|
|
1520
1574
|
}
|
|
1521
1575
|
};
|
|
1522
1576
|
this.#inputService = new InputService(onInput);
|
|
1523
|
-
}
|
|
1524
|
-
close() {
|
|
1525
|
-
this.#inputService.close();
|
|
1526
|
-
this.#timer.clear();
|
|
1527
|
-
for (const watcher of this.#watchers) {
|
|
1528
|
-
watcher.close();
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
#onDiagnostic(diagnostic) {
|
|
1532
|
-
EventEmitter.dispatch(["watch:error", { diagnostics: [diagnostic] }]);
|
|
1533
|
-
}
|
|
1534
|
-
#runAll() {
|
|
1535
|
-
if (this.#watchedTestFiles.size !== 0) {
|
|
1536
|
-
this.#runCallback([...this.#watchedTestFiles.values()]);
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
#runChanged() {
|
|
1540
|
-
this.#timer.clear();
|
|
1541
|
-
if (this.#changedTestFiles.size !== 0) {
|
|
1542
|
-
const runCallback = async (changedTestFiles) => {
|
|
1543
|
-
const testFiles = [...changedTestFiles.values()];
|
|
1544
|
-
this.#changedTestFiles.clear();
|
|
1545
|
-
await this.#runCallback(testFiles);
|
|
1546
|
-
};
|
|
1547
|
-
this.#timer.set(runCallback, 100, this.#changedTestFiles);
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
watch(cancellationToken) {
|
|
1551
1577
|
const onChangedFile = (filePath) => {
|
|
1578
|
+
debounce.refreshTimeout();
|
|
1552
1579
|
let testFile = this.#watchedTestFiles.get(filePath);
|
|
1553
1580
|
if (testFile != null) {
|
|
1554
1581
|
this.#changedTestFiles.set(filePath, testFile);
|
|
@@ -1558,26 +1585,35 @@ class WatchService {
|
|
|
1558
1585
|
this.#changedTestFiles.set(filePath, testFile);
|
|
1559
1586
|
this.#watchedTestFiles.set(filePath, testFile);
|
|
1560
1587
|
}
|
|
1561
|
-
this.#runChanged();
|
|
1562
1588
|
};
|
|
1563
1589
|
const onRemovedFile = (filePath) => {
|
|
1564
1590
|
this.#changedTestFiles.delete(filePath);
|
|
1565
1591
|
this.#watchedTestFiles.delete(filePath);
|
|
1566
1592
|
if (this.#watchedTestFiles.size === 0) {
|
|
1567
|
-
|
|
1593
|
+
debounce.clearTimeout();
|
|
1594
|
+
this.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereLeft(this.#resolvedConfig)));
|
|
1568
1595
|
}
|
|
1569
1596
|
};
|
|
1570
1597
|
this.#watchers.push(new Watcher(this.#resolvedConfig.rootPath, onChangedFile, onRemovedFile, { recursive: true }));
|
|
1571
1598
|
const onChangedConfigFile = () => {
|
|
1572
|
-
|
|
1599
|
+
onClose("configChange");
|
|
1573
1600
|
};
|
|
1574
1601
|
this.#watchers.push(new FileWatcher(this.#resolvedConfig.configFilePath, onChangedConfigFile));
|
|
1575
|
-
|
|
1602
|
+
for (const watcher of this.#watchers) {
|
|
1603
|
+
watcher.watch();
|
|
1604
|
+
}
|
|
1605
|
+
while (!cancellationToken.isCancellationRequested) {
|
|
1606
|
+
const testFiles = await debounce.setup();
|
|
1607
|
+
if (testFiles.length > 0) {
|
|
1608
|
+
yield testFiles;
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1576
1611
|
}
|
|
1577
1612
|
}
|
|
1578
1613
|
|
|
1579
1614
|
class TestMember {
|
|
1580
1615
|
brand;
|
|
1616
|
+
#compiler;
|
|
1581
1617
|
diagnostics = new Set();
|
|
1582
1618
|
flags;
|
|
1583
1619
|
members = [];
|
|
@@ -1586,6 +1622,7 @@ class TestMember {
|
|
|
1586
1622
|
parent;
|
|
1587
1623
|
constructor(compiler, brand, node, parent, flags) {
|
|
1588
1624
|
this.brand = brand;
|
|
1625
|
+
this.#compiler = compiler;
|
|
1589
1626
|
this.node = node;
|
|
1590
1627
|
this.parent = parent;
|
|
1591
1628
|
this.flags = flags;
|
|
@@ -1605,23 +1642,20 @@ class TestMember {
|
|
|
1605
1642
|
}
|
|
1606
1643
|
}
|
|
1607
1644
|
}
|
|
1608
|
-
get ancestorNames() {
|
|
1609
|
-
const ancestorNames = [];
|
|
1610
|
-
let ancestor = this.parent;
|
|
1611
|
-
while ("name" in ancestor) {
|
|
1612
|
-
ancestorNames.unshift(ancestor.name);
|
|
1613
|
-
ancestor = ancestor.parent;
|
|
1614
|
-
}
|
|
1615
|
-
return ancestorNames;
|
|
1616
|
-
}
|
|
1617
1645
|
validate() {
|
|
1618
1646
|
const diagnostics = [];
|
|
1619
1647
|
const getText = (node) => `'${node.expression.getText()}()' cannot be nested within '${this.node.expression.getText()}()'.`;
|
|
1648
|
+
const getParentCallExpression = (node) => {
|
|
1649
|
+
while (!this.#compiler.isCallExpression(node.parent)) {
|
|
1650
|
+
node = node.parent;
|
|
1651
|
+
}
|
|
1652
|
+
return node.parent;
|
|
1653
|
+
};
|
|
1620
1654
|
switch (this.brand) {
|
|
1621
1655
|
case "describe": {
|
|
1622
1656
|
for (const member of this.members) {
|
|
1623
1657
|
if (member.brand === "expect") {
|
|
1624
|
-
diagnostics.push(Diagnostic.error(getText(member.node), DiagnosticOrigin.fromNode(member.node)));
|
|
1658
|
+
diagnostics.push(Diagnostic.error(getText(member.node), DiagnosticOrigin.fromNode(getParentCallExpression(member.node))));
|
|
1625
1659
|
}
|
|
1626
1660
|
}
|
|
1627
1661
|
break;
|
|
@@ -1650,43 +1684,21 @@ class Assertion extends TestMember {
|
|
|
1650
1684
|
this.isNot = notNode != null;
|
|
1651
1685
|
this.matcherNode = matcherNode;
|
|
1652
1686
|
this.modifierNode = modifierNode;
|
|
1653
|
-
const argStart = this.source[0]?.getStart();
|
|
1654
|
-
const argEnd = this.source[0]?.getEnd();
|
|
1655
1687
|
for (const diagnostic of parent.diagnostics) {
|
|
1656
|
-
if (diagnostic.start != null &&
|
|
1657
|
-
argStart != null &&
|
|
1658
|
-
argEnd != null &&
|
|
1659
|
-
diagnostic.start >= argStart &&
|
|
1660
|
-
diagnostic.start <= argEnd) {
|
|
1688
|
+
if (diagnostic.start != null && diagnostic.start >= this.source.pos && diagnostic.start <= this.source.end) {
|
|
1661
1689
|
this.diagnostics.add(diagnostic);
|
|
1662
1690
|
parent.diagnostics.delete(diagnostic);
|
|
1663
1691
|
}
|
|
1664
1692
|
}
|
|
1665
1693
|
}
|
|
1666
|
-
get ancestorNames() {
|
|
1667
|
-
const ancestorNames = [];
|
|
1668
|
-
if ("ancestorNames" in this.parent) {
|
|
1669
|
-
ancestorNames.push(...this.parent.ancestorNames);
|
|
1670
|
-
}
|
|
1671
|
-
if ("name" in this.parent) {
|
|
1672
|
-
ancestorNames.push(this.parent.name);
|
|
1673
|
-
}
|
|
1674
|
-
return ancestorNames;
|
|
1675
|
-
}
|
|
1676
1694
|
get matcherName() {
|
|
1677
1695
|
return this.matcherNode.expression.name;
|
|
1678
1696
|
}
|
|
1679
1697
|
get source() {
|
|
1680
|
-
|
|
1681
|
-
return this.node.typeArguments;
|
|
1682
|
-
}
|
|
1683
|
-
return this.node.arguments;
|
|
1698
|
+
return this.node.typeArguments ?? this.node.arguments;
|
|
1684
1699
|
}
|
|
1685
1700
|
get target() {
|
|
1686
|
-
|
|
1687
|
-
return this.matcherNode.typeArguments;
|
|
1688
|
-
}
|
|
1689
|
-
return this.matcherNode.arguments;
|
|
1701
|
+
return this.matcherNode.typeArguments ?? this.matcherNode.arguments;
|
|
1690
1702
|
}
|
|
1691
1703
|
}
|
|
1692
1704
|
|
|
@@ -1707,12 +1719,6 @@ class IdentifierLookup {
|
|
|
1707
1719
|
namespace: undefined,
|
|
1708
1720
|
};
|
|
1709
1721
|
}
|
|
1710
|
-
clone() {
|
|
1711
|
-
return {
|
|
1712
|
-
namedImports: { ...this.#identifiers.namedImports },
|
|
1713
|
-
namespace: this.#identifiers.namespace,
|
|
1714
|
-
};
|
|
1715
|
-
}
|
|
1716
1722
|
handleImportDeclaration(node) {
|
|
1717
1723
|
if (this.#moduleSpecifiers.includes(node.moduleSpecifier.getText()) &&
|
|
1718
1724
|
node.importClause?.isTypeOnly !== true &&
|
|
@@ -1774,7 +1780,7 @@ class IdentifierLookup {
|
|
|
1774
1780
|
else {
|
|
1775
1781
|
identifierName = Object.keys(this.#identifiers.namedImports).find((key) => this.#identifiers.namedImports[key] === expression.getText());
|
|
1776
1782
|
}
|
|
1777
|
-
if (identifierName
|
|
1783
|
+
if (!identifierName) {
|
|
1778
1784
|
return;
|
|
1779
1785
|
}
|
|
1780
1786
|
switch (identifierName) {
|
|
@@ -1808,40 +1814,10 @@ class TestTree {
|
|
|
1808
1814
|
|
|
1809
1815
|
class CollectService {
|
|
1810
1816
|
#compiler;
|
|
1811
|
-
#matcherIdentifiers = [
|
|
1812
|
-
"toBe",
|
|
1813
|
-
"toBeAny",
|
|
1814
|
-
"toBeAssignable",
|
|
1815
|
-
"toBeAssignableTo",
|
|
1816
|
-
"toBeAssignableWith",
|
|
1817
|
-
"toBeBigInt",
|
|
1818
|
-
"toBeBoolean",
|
|
1819
|
-
"toBeNever",
|
|
1820
|
-
"toBeNull",
|
|
1821
|
-
"toBeNumber",
|
|
1822
|
-
"toBeString",
|
|
1823
|
-
"toBeSymbol",
|
|
1824
|
-
"toBeUndefined",
|
|
1825
|
-
"toBeUniqueSymbol",
|
|
1826
|
-
"toBeUnknown",
|
|
1827
|
-
"toBeVoid",
|
|
1828
|
-
"toEqual",
|
|
1829
|
-
"toHaveProperty",
|
|
1830
|
-
"toMatch",
|
|
1831
|
-
"toRaiseError",
|
|
1832
|
-
];
|
|
1833
|
-
#modifierIdentifiers = ["type"];
|
|
1834
|
-
#notIdentifier = "not";
|
|
1835
1817
|
constructor(compiler) {
|
|
1836
1818
|
this.#compiler = compiler;
|
|
1837
1819
|
}
|
|
1838
1820
|
#collectTestMembers(node, identifiers, parent) {
|
|
1839
|
-
if (this.#compiler.isBlock(node)) {
|
|
1840
|
-
this.#compiler.forEachChild(node, (node) => {
|
|
1841
|
-
this.#collectTestMembers(node, new IdentifierLookup(this.#compiler, identifiers.clone()), parent);
|
|
1842
|
-
});
|
|
1843
|
-
return;
|
|
1844
|
-
}
|
|
1845
1821
|
if (this.#compiler.isCallExpression(node)) {
|
|
1846
1822
|
const meta = identifiers.resolveTestMemberMeta(node);
|
|
1847
1823
|
if (meta != null && (meta.brand === "describe" || meta.brand === "test")) {
|
|
@@ -1853,13 +1829,13 @@ class CollectService {
|
|
|
1853
1829
|
return;
|
|
1854
1830
|
}
|
|
1855
1831
|
if (meta != null && meta.brand === "expect") {
|
|
1856
|
-
const modifierNode = this.#
|
|
1832
|
+
const modifierNode = this.#getChainedNode(node, "type");
|
|
1857
1833
|
if (!modifierNode) {
|
|
1858
1834
|
return;
|
|
1859
1835
|
}
|
|
1860
|
-
const notNode = this.#
|
|
1861
|
-
const matcherNode = this.#
|
|
1862
|
-
if (matcherNode
|
|
1836
|
+
const notNode = this.#getChainedNode(modifierNode, "not");
|
|
1837
|
+
const matcherNode = this.#getChainedNode(notNode ?? modifierNode)?.parent;
|
|
1838
|
+
if (!matcherNode || !this.#isMatcherNode(matcherNode)) {
|
|
1863
1839
|
return;
|
|
1864
1840
|
}
|
|
1865
1841
|
const assertion = new Assertion(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, modifierNode, notNode);
|
|
@@ -1874,8 +1850,6 @@ class CollectService {
|
|
|
1874
1850
|
identifiers.handleImportDeclaration(node);
|
|
1875
1851
|
return;
|
|
1876
1852
|
}
|
|
1877
|
-
if (this.#compiler.isVariableDeclaration(node)) ;
|
|
1878
|
-
if (this.#compiler.isBinaryExpression(node)) ;
|
|
1879
1853
|
this.#compiler.forEachChild(node, (node) => {
|
|
1880
1854
|
this.#collectTestMembers(node, identifiers, parent);
|
|
1881
1855
|
});
|
|
@@ -1885,11 +1859,14 @@ class CollectService {
|
|
|
1885
1859
|
this.#collectTestMembers(sourceFile, new IdentifierLookup(this.#compiler), testTree);
|
|
1886
1860
|
return testTree;
|
|
1887
1861
|
}
|
|
1888
|
-
#
|
|
1889
|
-
if (this.#compiler.isPropertyAccessExpression(parent)
|
|
1890
|
-
return
|
|
1862
|
+
#getChainedNode({ parent }, name) {
|
|
1863
|
+
if (!this.#compiler.isPropertyAccessExpression(parent)) {
|
|
1864
|
+
return;
|
|
1891
1865
|
}
|
|
1892
|
-
|
|
1866
|
+
if (name != null && name !== parent.name.getText()) {
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
return parent;
|
|
1893
1870
|
}
|
|
1894
1871
|
#isMatcherNode(node) {
|
|
1895
1872
|
return this.#compiler.isCallExpression(node) && this.#compiler.isPropertyAccessExpression(node.expression);
|
|
@@ -1911,220 +1888,599 @@ var TestMemberFlags;
|
|
|
1911
1888
|
TestMemberFlags[TestMemberFlags["Todo"] = 8] = "Todo";
|
|
1912
1889
|
})(TestMemberFlags || (TestMemberFlags = {}));
|
|
1913
1890
|
|
|
1891
|
+
class ExpectDiagnosticText {
|
|
1892
|
+
static argumentOrTypeArgumentMustBeProvided(argumentNameText, typeArgumentNameText) {
|
|
1893
|
+
return `An argument for '${argumentNameText}' or type argument for '${typeArgumentNameText}' must be provided.`;
|
|
1894
|
+
}
|
|
1895
|
+
static argumentMustBe(argumentNameText, expectedText) {
|
|
1896
|
+
return `An argument for '${argumentNameText}' must be ${expectedText}.`;
|
|
1897
|
+
}
|
|
1898
|
+
static argumentMustBeProvided(argumentNameText) {
|
|
1899
|
+
return `An argument for '${argumentNameText}' must be provided.`;
|
|
1900
|
+
}
|
|
1901
|
+
static componentAcceptsProps(isTypeNode) {
|
|
1902
|
+
return `${isTypeNode ? "Component type" : "Component"} accepts props of the given type.`;
|
|
1903
|
+
}
|
|
1904
|
+
static componentDoesNotAcceptProps(isTypeNode) {
|
|
1905
|
+
return `${isTypeNode ? "Component type" : "Component"} does not accept props of the given type.`;
|
|
1906
|
+
}
|
|
1907
|
+
static matcherIsDeprecated(matcherNameText) {
|
|
1908
|
+
return [
|
|
1909
|
+
`The '.${matcherNameText}()' matcher is deprecated and will be removed in TSTyche 3.`,
|
|
1910
|
+
"To learn more, visit https://tstyche.org/releases/tstyche-2",
|
|
1911
|
+
];
|
|
1912
|
+
}
|
|
1913
|
+
static matcherIsNotSupported(matcherNameText) {
|
|
1914
|
+
return `The '.${matcherNameText}()' matcher is not supported.`;
|
|
1915
|
+
}
|
|
1916
|
+
static overloadGaveTheFollowingError(indexText, countText, signatureText) {
|
|
1917
|
+
return `Overload ${indexText} of ${countText}, '${signatureText}', gave the following error.`;
|
|
1918
|
+
}
|
|
1919
|
+
static raisedTypeError(count = 1) {
|
|
1920
|
+
return `The raised type error${count === 1 ? "" : "s"}:`;
|
|
1921
|
+
}
|
|
1922
|
+
static typeArgumentMustBe(argumentNameText, expectedText) {
|
|
1923
|
+
return `A type argument for '${argumentNameText}' must be ${expectedText}.`;
|
|
1924
|
+
}
|
|
1925
|
+
static typeDidNotRaiseError(isTypeNode) {
|
|
1926
|
+
return `${isTypeNode ? "Type" : "Expression type"} did not raise a type error.`;
|
|
1927
|
+
}
|
|
1928
|
+
static typeDidNotRaiseMatchingError(isTypeNode) {
|
|
1929
|
+
return `${isTypeNode ? "Type" : "Expression type"} did not raise a matching type error.`;
|
|
1930
|
+
}
|
|
1931
|
+
static typeDoesNotHaveProperty(typeText, propertyNameText) {
|
|
1932
|
+
return `Type '${typeText}' does not have property '${propertyNameText}'.`;
|
|
1933
|
+
}
|
|
1934
|
+
static typeDoesMatch(sourceTypeText, targetTypeText) {
|
|
1935
|
+
return `Type '${sourceTypeText}' does match type '${targetTypeText}'.`;
|
|
1936
|
+
}
|
|
1937
|
+
static typeDoesNotMatch(sourceTypeText, targetTypeText) {
|
|
1938
|
+
return `Type '${sourceTypeText}' does not match type '${targetTypeText}'.`;
|
|
1939
|
+
}
|
|
1940
|
+
static typeHasProperty(typeText, propertyNameText) {
|
|
1941
|
+
return `Type '${typeText}' has property '${propertyNameText}'.`;
|
|
1942
|
+
}
|
|
1943
|
+
static typeIs(typeText) {
|
|
1944
|
+
return `Type is '${typeText}'.`;
|
|
1945
|
+
}
|
|
1946
|
+
static typeIsAssignableTo(sourceTypeText, targetTypeText) {
|
|
1947
|
+
return `Type '${sourceTypeText}' is assignable to type '${targetTypeText}'.`;
|
|
1948
|
+
}
|
|
1949
|
+
static typeIsAssignableWith(sourceTypeText, targetTypeText) {
|
|
1950
|
+
return `Type '${sourceTypeText}' is assignable with type '${targetTypeText}'.`;
|
|
1951
|
+
}
|
|
1952
|
+
static typeIsIdenticalTo(sourceTypeText, targetTypeText) {
|
|
1953
|
+
return `Type '${sourceTypeText}' is identical to type '${targetTypeText}'.`;
|
|
1954
|
+
}
|
|
1955
|
+
static typeIsNotAssignableTo(sourceTypeText, targetTypeText) {
|
|
1956
|
+
return `Type '${sourceTypeText}' is not assignable to type '${targetTypeText}'.`;
|
|
1957
|
+
}
|
|
1958
|
+
static typeIsNotAssignableWith(sourceTypeText, targetTypeText) {
|
|
1959
|
+
return `Type '${sourceTypeText}' is not assignable with type '${targetTypeText}'.`;
|
|
1960
|
+
}
|
|
1961
|
+
static typeIsNotCompatibleWith(sourceTypeText, targetTypeText) {
|
|
1962
|
+
return `Type '${sourceTypeText}' is not compatible with type '${targetTypeText}'.`;
|
|
1963
|
+
}
|
|
1964
|
+
static typeIsNotIdenticalTo(sourceTypeText, targetTypeText) {
|
|
1965
|
+
return `Type '${sourceTypeText}' is not identical to type '${targetTypeText}'.`;
|
|
1966
|
+
}
|
|
1967
|
+
static typeRaisedError(isTypeNode, count, targetCount) {
|
|
1968
|
+
let countText = "a";
|
|
1969
|
+
if (count > 1 || targetCount > 1) {
|
|
1970
|
+
countText = count > targetCount ? String(count) : `only ${String(count)}`;
|
|
1971
|
+
}
|
|
1972
|
+
return `${isTypeNode ? "Type" : "Expression type"} raised ${countText} type error${count === 1 ? "" : "s"}.`;
|
|
1973
|
+
}
|
|
1974
|
+
static typeRaisedMatchingError(isTypeNode) {
|
|
1975
|
+
return `${isTypeNode ? "Type" : "Expression type"} raised a matching type error.`;
|
|
1976
|
+
}
|
|
1977
|
+
static typeRequiresProperty(typeText, propertyNameText) {
|
|
1978
|
+
return `Type '${typeText}' requires property '${propertyNameText}'.`;
|
|
1979
|
+
}
|
|
1980
|
+
static typesOfPropertyAreNotCompatible(propertyNameText) {
|
|
1981
|
+
return `Types of property '${propertyNameText}' are not compatible.`;
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
class MatchWorker {
|
|
1986
|
+
assertion;
|
|
1987
|
+
#compiler;
|
|
1988
|
+
#signatureCache = new Map();
|
|
1989
|
+
#typeCache = new Map();
|
|
1990
|
+
#typeChecker;
|
|
1991
|
+
constructor(compiler, typeChecker, assertion) {
|
|
1992
|
+
this.#compiler = compiler;
|
|
1993
|
+
this.#typeChecker = typeChecker;
|
|
1994
|
+
this.assertion = assertion;
|
|
1995
|
+
}
|
|
1996
|
+
checkIsAssignableTo(sourceNode, targetNode) {
|
|
1997
|
+
const relation = this.#typeChecker.relation.assignable;
|
|
1998
|
+
return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
|
|
1999
|
+
}
|
|
2000
|
+
checkIsAssignableWith(sourceNode, targetNode) {
|
|
2001
|
+
const relation = this.#typeChecker.relation.assignable;
|
|
2002
|
+
return this.#checkIsRelatedTo(targetNode, sourceNode, relation);
|
|
2003
|
+
}
|
|
2004
|
+
checkIsIdenticalTo(sourceNode, targetNode) {
|
|
2005
|
+
const relation = this.#typeChecker.relation.identity;
|
|
2006
|
+
return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
|
|
2007
|
+
}
|
|
2008
|
+
checkIsSubtype(sourceNode, targetNode) {
|
|
2009
|
+
const relation = this.#typeChecker.relation.subtype;
|
|
2010
|
+
return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
|
|
2011
|
+
}
|
|
2012
|
+
#checkIsRelatedTo(sourceNode, targetNode, relation) {
|
|
2013
|
+
const sourceType = this.getType(sourceNode);
|
|
2014
|
+
const targetType = this.getType(targetNode);
|
|
2015
|
+
return this.#typeChecker.isTypeRelatedTo(sourceType, targetType, relation);
|
|
2016
|
+
}
|
|
2017
|
+
extendsObjectType(type) {
|
|
2018
|
+
const nonPrimitiveType = { flags: this.#compiler.TypeFlags.NonPrimitive };
|
|
2019
|
+
return this.#typeChecker.isTypeAssignableTo(type, nonPrimitiveType);
|
|
2020
|
+
}
|
|
2021
|
+
getParameterType(signature, index) {
|
|
2022
|
+
const parameter = signature.getDeclaration().parameters[index];
|
|
2023
|
+
if (!parameter) {
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
return this.#getTypeOfNode(parameter);
|
|
2027
|
+
}
|
|
2028
|
+
getSignatures(node) {
|
|
2029
|
+
let signatures = this.#signatureCache.get(node);
|
|
2030
|
+
if (!signatures) {
|
|
2031
|
+
const type = this.getType(node);
|
|
2032
|
+
signatures = type.getCallSignatures();
|
|
2033
|
+
if (signatures.length === 0) {
|
|
2034
|
+
signatures = type.getConstructSignatures();
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
return signatures;
|
|
2038
|
+
}
|
|
2039
|
+
getTypeText(node) {
|
|
2040
|
+
const type = this.getType(node);
|
|
2041
|
+
return this.#typeChecker.typeToString(type);
|
|
2042
|
+
}
|
|
2043
|
+
getType(node) {
|
|
2044
|
+
return this.#compiler.isExpression(node) ? this.#getTypeOfNode(node) : this.#getTypeOfTypeNode(node);
|
|
2045
|
+
}
|
|
2046
|
+
#getTypeOfNode(node) {
|
|
2047
|
+
let type = this.#typeCache.get(node);
|
|
2048
|
+
if (!type) {
|
|
2049
|
+
type = this.#typeChecker.getTypeAtLocation(node);
|
|
2050
|
+
}
|
|
2051
|
+
return type;
|
|
2052
|
+
}
|
|
2053
|
+
#getTypeOfTypeNode(node) {
|
|
2054
|
+
let type = this.#typeCache.get(node);
|
|
2055
|
+
if (!type) {
|
|
2056
|
+
type = this.#typeChecker.getTypeFromTypeNode(node);
|
|
2057
|
+
}
|
|
2058
|
+
return type;
|
|
2059
|
+
}
|
|
2060
|
+
isAnyOrNeverType(type) {
|
|
2061
|
+
return Boolean(type.flags & (this.#compiler.TypeFlags.Any | this.#compiler.TypeFlags.Never));
|
|
2062
|
+
}
|
|
2063
|
+
isStringOrNumberLiteralType(type) {
|
|
2064
|
+
return Boolean(type.flags & this.#compiler.TypeFlags.StringOrNumberLiteral);
|
|
2065
|
+
}
|
|
2066
|
+
isObjectType(type) {
|
|
2067
|
+
return Boolean(type.flags & this.#compiler.TypeFlags.Object);
|
|
2068
|
+
}
|
|
2069
|
+
isUnionType(type) {
|
|
2070
|
+
return Boolean(type.flags & this.#compiler.TypeFlags.Union);
|
|
2071
|
+
}
|
|
2072
|
+
isUniqueSymbolType(type) {
|
|
2073
|
+
return Boolean(type.flags & this.#compiler.TypeFlags.UniqueESSymbol);
|
|
2074
|
+
}
|
|
2075
|
+
resolveDiagnosticOrigin(symbol, enclosingNode) {
|
|
2076
|
+
if (symbol.valueDeclaration != null &&
|
|
2077
|
+
(this.#compiler.isPropertySignature(symbol.valueDeclaration) ||
|
|
2078
|
+
this.#compiler.isPropertyAssignment(symbol.valueDeclaration) ||
|
|
2079
|
+
this.#compiler.isShorthandPropertyAssignment(symbol.valueDeclaration)) &&
|
|
2080
|
+
symbol.valueDeclaration.getStart() >= enclosingNode.getStart() &&
|
|
2081
|
+
symbol.valueDeclaration.getEnd() <= enclosingNode.getEnd()) {
|
|
2082
|
+
return DiagnosticOrigin.fromNode(symbol.valueDeclaration.name, this.assertion);
|
|
2083
|
+
}
|
|
2084
|
+
return DiagnosticOrigin.fromNode(enclosingNode, this.assertion);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
|
|
1914
2088
|
class PrimitiveTypeMatcher {
|
|
1915
2089
|
#targetTypeFlag;
|
|
1916
|
-
|
|
1917
|
-
constructor(typeChecker, targetTypeFlag) {
|
|
1918
|
-
this.typeChecker = typeChecker;
|
|
2090
|
+
constructor(targetTypeFlag) {
|
|
1919
2091
|
this.#targetTypeFlag = targetTypeFlag;
|
|
1920
2092
|
}
|
|
1921
|
-
#explain(
|
|
1922
|
-
const sourceTypeText =
|
|
1923
|
-
|
|
2093
|
+
#explain(matchWorker, sourceNode) {
|
|
2094
|
+
const sourceTypeText = matchWorker.getTypeText(sourceNode);
|
|
2095
|
+
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
2096
|
+
return [Diagnostic.error(ExpectDiagnosticText.typeIs(sourceTypeText), origin)];
|
|
1924
2097
|
}
|
|
1925
|
-
match(
|
|
2098
|
+
match(matchWorker, sourceNode) {
|
|
2099
|
+
const sourceType = matchWorker.getType(sourceNode);
|
|
1926
2100
|
const isMatch = Boolean(sourceType.flags & this.#targetTypeFlag);
|
|
1927
2101
|
return {
|
|
1928
|
-
explain: () => this.#explain(
|
|
2102
|
+
explain: () => this.#explain(matchWorker, sourceNode),
|
|
1929
2103
|
isMatch,
|
|
1930
2104
|
};
|
|
1931
2105
|
}
|
|
1932
2106
|
}
|
|
1933
2107
|
|
|
1934
|
-
class
|
|
1935
|
-
|
|
1936
|
-
typeChecker;
|
|
1937
|
-
constructor(typeChecker) {
|
|
1938
|
-
this
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
const
|
|
1943
|
-
return
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
2108
|
+
class ToAcceptProps {
|
|
2109
|
+
#compiler;
|
|
2110
|
+
#typeChecker;
|
|
2111
|
+
constructor(compiler, typeChecker) {
|
|
2112
|
+
this.#compiler = compiler;
|
|
2113
|
+
this.#typeChecker = typeChecker;
|
|
2114
|
+
}
|
|
2115
|
+
#explain(matchWorker, sourceNode, targetNode) {
|
|
2116
|
+
const signatures = matchWorker.getSignatures(sourceNode);
|
|
2117
|
+
return signatures.reduce((accumulator, signature, index) => {
|
|
2118
|
+
let diagnostic;
|
|
2119
|
+
const introText = matchWorker.assertion.isNot
|
|
2120
|
+
? ExpectDiagnosticText.componentAcceptsProps(this.#compiler.isTypeNode(sourceNode))
|
|
2121
|
+
: ExpectDiagnosticText.componentDoesNotAcceptProps(this.#compiler.isTypeNode(sourceNode));
|
|
2122
|
+
const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
|
|
2123
|
+
if (signatures.length > 1) {
|
|
2124
|
+
const signatureText = this.#typeChecker.signatureToString(signature, sourceNode);
|
|
2125
|
+
const overloadText = ExpectDiagnosticText.overloadGaveTheFollowingError(String(index + 1), String(signatures.length), signatureText);
|
|
2126
|
+
diagnostic = Diagnostic.error([introText, overloadText], origin);
|
|
2127
|
+
}
|
|
2128
|
+
else {
|
|
2129
|
+
diagnostic = Diagnostic.error([introText], origin);
|
|
2130
|
+
}
|
|
2131
|
+
const { diagnostics, isMatch } = this.#explainProperties(matchWorker, signature, targetNode, diagnostic);
|
|
2132
|
+
if (matchWorker.assertion.isNot ? isMatch : !isMatch) {
|
|
2133
|
+
accumulator.push(...diagnostics);
|
|
2134
|
+
}
|
|
2135
|
+
return accumulator;
|
|
2136
|
+
}, []);
|
|
2137
|
+
}
|
|
2138
|
+
#isOptionalProperty(symbol) {
|
|
2139
|
+
return symbol.declarations?.every((declaration) => this.#compiler.isPropertySignature(declaration) && declaration.questionToken != null);
|
|
2140
|
+
}
|
|
2141
|
+
#checkProperties(matchWorker, sourceType, targetType) {
|
|
2142
|
+
const check = (sourceType, targetType) => {
|
|
2143
|
+
for (const targetProperty of targetType.getProperties()) {
|
|
2144
|
+
const targetPropertyName = targetProperty.getName();
|
|
2145
|
+
const sourceProperty = sourceType?.getProperty(targetPropertyName);
|
|
2146
|
+
if (!sourceProperty) {
|
|
2147
|
+
return false;
|
|
2148
|
+
}
|
|
2149
|
+
if (this.#isOptionalProperty(targetProperty) && !this.#isOptionalProperty(sourceProperty)) {
|
|
2150
|
+
return false;
|
|
2151
|
+
}
|
|
2152
|
+
const targetPropertyType = this.#typeChecker.getTypeOfSymbol(targetProperty);
|
|
2153
|
+
const sourcePropertyType = this.#typeChecker.getTypeOfSymbol(sourceProperty);
|
|
2154
|
+
if (!this.#typeChecker.isTypeAssignableTo(targetPropertyType, sourcePropertyType)) {
|
|
2155
|
+
return false;
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
if (sourceType != null) {
|
|
2159
|
+
const sourceProperties = sourceType.getProperties();
|
|
2160
|
+
for (const sourceProperty of sourceProperties) {
|
|
2161
|
+
const targetProperty = targetType.getProperty(sourceProperty.getName());
|
|
2162
|
+
if (!targetProperty && !this.#isOptionalProperty(sourceProperty)) {
|
|
2163
|
+
return false;
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
return true;
|
|
2168
|
+
};
|
|
2169
|
+
if (sourceType != null && matchWorker.isUnionType(sourceType)) {
|
|
2170
|
+
return sourceType.types.some((sourceType) => check(sourceType, targetType));
|
|
2171
|
+
}
|
|
2172
|
+
return check(sourceType, targetType);
|
|
2173
|
+
}
|
|
2174
|
+
#explainProperties(matchWorker, signature, targetNode, diagnostic) {
|
|
2175
|
+
const sourceType = matchWorker.getParameterType(signature, 0);
|
|
2176
|
+
const sourceTypeText = sourceType != null ? this.#typeChecker.typeToString(sourceType) : "{}";
|
|
2177
|
+
const targetType = matchWorker.getType(targetNode);
|
|
2178
|
+
const targetTypeText = this.#typeChecker.typeToString(targetType);
|
|
2179
|
+
const explain = (sourceType, targetType, diagnostic) => {
|
|
2180
|
+
const sourceTypeText = sourceType != null ? this.#typeChecker.typeToString(sourceType) : "{}";
|
|
2181
|
+
const diagnostics = [];
|
|
2182
|
+
for (const targetProperty of targetType.getProperties()) {
|
|
2183
|
+
const targetPropertyName = targetProperty.getName();
|
|
2184
|
+
const sourceProperty = sourceType?.getProperty(targetPropertyName);
|
|
2185
|
+
if (!sourceProperty) {
|
|
2186
|
+
const text = [
|
|
2187
|
+
ExpectDiagnosticText.typeIsNotCompatibleWith(sourceTypeText, targetTypeText),
|
|
2188
|
+
ExpectDiagnosticText.typeDoesNotHaveProperty(sourceTypeText, targetPropertyName),
|
|
2189
|
+
];
|
|
2190
|
+
const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
|
|
2191
|
+
diagnostics.push(diagnostic.extendWith(text, origin));
|
|
2192
|
+
continue;
|
|
2193
|
+
}
|
|
2194
|
+
if (this.#isOptionalProperty(targetProperty) && !this.#isOptionalProperty(sourceProperty)) {
|
|
2195
|
+
const text = [
|
|
2196
|
+
ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText),
|
|
2197
|
+
ExpectDiagnosticText.typeRequiresProperty(sourceTypeText, targetPropertyName),
|
|
2198
|
+
];
|
|
2199
|
+
const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
|
|
2200
|
+
diagnostics.push(diagnostic.extendWith(text, origin));
|
|
2201
|
+
continue;
|
|
2202
|
+
}
|
|
2203
|
+
const targetPropertyType = this.#typeChecker.getTypeOfSymbol(targetProperty);
|
|
2204
|
+
const sourcePropertyType = this.#typeChecker.getTypeOfSymbol(sourceProperty);
|
|
2205
|
+
if (!this.#typeChecker.isTypeAssignableTo(targetPropertyType, sourcePropertyType)) {
|
|
2206
|
+
const targetPropertyTypeText = this.#typeChecker.typeToString(targetPropertyType);
|
|
2207
|
+
const sourcePropertyTypeText = this.#typeChecker.typeToString(sourcePropertyType);
|
|
2208
|
+
const text = [
|
|
2209
|
+
ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText),
|
|
2210
|
+
ExpectDiagnosticText.typesOfPropertyAreNotCompatible(targetPropertyName),
|
|
2211
|
+
ExpectDiagnosticText.typeIsNotAssignableWith(sourcePropertyTypeText, targetPropertyTypeText),
|
|
2212
|
+
];
|
|
2213
|
+
const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
|
|
2214
|
+
diagnostics.push(diagnostic.extendWith(text, origin));
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
if (sourceType != null) {
|
|
2218
|
+
for (const sourceProperty of sourceType.getProperties()) {
|
|
2219
|
+
const sourcePropertyName = sourceProperty.getName();
|
|
2220
|
+
const targetProperty = targetType.getProperty(sourcePropertyName);
|
|
2221
|
+
if (!targetProperty && !this.#isOptionalProperty(sourceProperty)) {
|
|
2222
|
+
const text = [
|
|
2223
|
+
ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText),
|
|
2224
|
+
ExpectDiagnosticText.typeRequiresProperty(sourceTypeText, sourcePropertyName),
|
|
2225
|
+
];
|
|
2226
|
+
diagnostics.push(diagnostic.extendWith(text));
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
if (diagnostics.length === 0) {
|
|
2231
|
+
const text = ExpectDiagnosticText.typeIsAssignableWith(sourceTypeText, targetTypeText);
|
|
2232
|
+
diagnostics.push(diagnostic.extendWith(text));
|
|
2233
|
+
return { diagnostics, isMatch: true };
|
|
2234
|
+
}
|
|
2235
|
+
return { diagnostics, isMatch: false };
|
|
2236
|
+
};
|
|
2237
|
+
if (sourceType != null && matchWorker.isUnionType(sourceType)) {
|
|
2238
|
+
let accumulator = [];
|
|
2239
|
+
const isMatch = sourceType.types.some((sourceType) => {
|
|
2240
|
+
const text = matchWorker.assertion.isNot
|
|
2241
|
+
? ExpectDiagnosticText.typeIsAssignableWith(sourceTypeText, targetTypeText)
|
|
2242
|
+
: ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText);
|
|
2243
|
+
const { diagnostics, isMatch } = explain(sourceType, targetType, diagnostic.extendWith(text));
|
|
2244
|
+
if (isMatch) {
|
|
2245
|
+
accumulator = diagnostics;
|
|
2246
|
+
}
|
|
2247
|
+
else {
|
|
2248
|
+
accumulator.push(...diagnostics);
|
|
2249
|
+
}
|
|
2250
|
+
return isMatch;
|
|
2251
|
+
});
|
|
2252
|
+
return { diagnostics: accumulator, isMatch };
|
|
2253
|
+
}
|
|
2254
|
+
return explain(sourceType, targetType, diagnostic);
|
|
1950
2255
|
}
|
|
1951
|
-
match(
|
|
1952
|
-
const
|
|
2256
|
+
match(matchWorker, sourceNode, targetNode, onDiagnostics) {
|
|
2257
|
+
const diagnostics = [];
|
|
2258
|
+
const signatures = matchWorker.getSignatures(sourceNode);
|
|
2259
|
+
if (signatures.length === 0) {
|
|
2260
|
+
const expectedText = "of a function or class type";
|
|
2261
|
+
const text = this.#compiler.isTypeNode(sourceNode)
|
|
2262
|
+
? ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText)
|
|
2263
|
+
: ExpectDiagnosticText.argumentMustBe("source", expectedText);
|
|
2264
|
+
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
2265
|
+
diagnostics.push(Diagnostic.error(text, origin));
|
|
2266
|
+
}
|
|
2267
|
+
const targetType = matchWorker.getType(targetNode);
|
|
2268
|
+
if (!matchWorker.isObjectType(targetType)) {
|
|
2269
|
+
const expectedText = "of an object type";
|
|
2270
|
+
const text = this.#compiler.isTypeNode(targetNode)
|
|
2271
|
+
? ExpectDiagnosticText.typeArgumentMustBe("Target", expectedText)
|
|
2272
|
+
: ExpectDiagnosticText.argumentMustBe("target", expectedText);
|
|
2273
|
+
const origin = DiagnosticOrigin.fromNode(targetNode);
|
|
2274
|
+
diagnostics.push(Diagnostic.error(text, origin));
|
|
2275
|
+
}
|
|
2276
|
+
if (diagnostics.length > 0) {
|
|
2277
|
+
onDiagnostics(diagnostics);
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
const isMatch = signatures.some((signature) => {
|
|
2281
|
+
const sourceType = matchWorker.getParameterType(signature, 0);
|
|
2282
|
+
return this.#checkProperties(matchWorker, sourceType, targetType);
|
|
2283
|
+
});
|
|
1953
2284
|
return {
|
|
1954
|
-
explain: () => this
|
|
2285
|
+
explain: () => this.#explain(matchWorker, sourceNode, targetNode),
|
|
1955
2286
|
isMatch,
|
|
1956
2287
|
};
|
|
1957
2288
|
}
|
|
1958
2289
|
}
|
|
1959
2290
|
|
|
2291
|
+
class RelationMatcherBase {
|
|
2292
|
+
explain(matchWorker, sourceNode, targetNode) {
|
|
2293
|
+
const sourceTypeText = matchWorker.getTypeText(sourceNode);
|
|
2294
|
+
const targetTypeText = matchWorker.getTypeText(targetNode);
|
|
2295
|
+
const text = matchWorker.assertion.isNot
|
|
2296
|
+
? this.explainText(sourceTypeText, targetTypeText)
|
|
2297
|
+
: this.explainNotText(sourceTypeText, targetTypeText);
|
|
2298
|
+
const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
|
|
2299
|
+
return [Diagnostic.error(text, origin)];
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
|
|
1960
2303
|
class ToBe extends RelationMatcherBase {
|
|
1961
|
-
|
|
1962
|
-
|
|
2304
|
+
explainText = ExpectDiagnosticText.typeIsIdenticalTo;
|
|
2305
|
+
explainNotText = ExpectDiagnosticText.typeIsNotIdenticalTo;
|
|
2306
|
+
match(matchWorker, sourceNode, targetNode) {
|
|
2307
|
+
return {
|
|
2308
|
+
explain: () => this.explain(matchWorker, sourceNode, targetNode),
|
|
2309
|
+
isMatch: matchWorker.checkIsIdenticalTo(sourceNode, targetNode),
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
1963
2312
|
}
|
|
1964
2313
|
|
|
1965
2314
|
class ToBeAssignableTo extends RelationMatcherBase {
|
|
1966
|
-
|
|
1967
|
-
|
|
2315
|
+
explainText = ExpectDiagnosticText.typeIsAssignableTo;
|
|
2316
|
+
explainNotText = ExpectDiagnosticText.typeIsNotAssignableTo;
|
|
2317
|
+
match(matchWorker, sourceNode, targetNode) {
|
|
2318
|
+
return {
|
|
2319
|
+
explain: () => this.explain(matchWorker, sourceNode, targetNode),
|
|
2320
|
+
isMatch: matchWorker.checkIsAssignableTo(sourceNode, targetNode),
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
1968
2323
|
}
|
|
1969
2324
|
|
|
1970
2325
|
class ToBeAssignableWith extends RelationMatcherBase {
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
match(
|
|
1974
|
-
const isMatch = this.typeChecker.isTypeRelatedTo(targetType, sourceType, this.relation);
|
|
2326
|
+
explainText = ExpectDiagnosticText.typeIsAssignableWith;
|
|
2327
|
+
explainNotText = ExpectDiagnosticText.typeIsNotAssignableWith;
|
|
2328
|
+
match(matchWorker, sourceNode, targetNode) {
|
|
1975
2329
|
return {
|
|
1976
|
-
explain: () => this.explain(
|
|
1977
|
-
isMatch,
|
|
2330
|
+
explain: () => this.explain(matchWorker, sourceNode, targetNode),
|
|
2331
|
+
isMatch: matchWorker.checkIsAssignableWith(sourceNode, targetNode),
|
|
1978
2332
|
};
|
|
1979
2333
|
}
|
|
1980
2334
|
}
|
|
1981
2335
|
|
|
1982
2336
|
class ToHaveProperty {
|
|
1983
|
-
compiler;
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
this.compiler = compiler;
|
|
1987
|
-
this.typeChecker = typeChecker;
|
|
2337
|
+
#compiler;
|
|
2338
|
+
constructor(compiler) {
|
|
2339
|
+
this.#compiler = compiler;
|
|
1988
2340
|
}
|
|
1989
|
-
#explain(
|
|
1990
|
-
const sourceTypeText =
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
2341
|
+
#explain(matchWorker, sourceNode, targetNode) {
|
|
2342
|
+
const sourceTypeText = matchWorker.getTypeText(sourceNode);
|
|
2343
|
+
const targetType = matchWorker.getType(targetNode);
|
|
2344
|
+
let propertyNameText;
|
|
2345
|
+
if (matchWorker.isStringOrNumberLiteralType(targetType)) {
|
|
2346
|
+
propertyNameText = String(targetType.value);
|
|
1994
2347
|
}
|
|
1995
2348
|
else {
|
|
1996
|
-
|
|
2349
|
+
propertyNameText = `[${this.#compiler.unescapeLeadingUnderscores(targetType.symbol.escapedName)}]`;
|
|
1997
2350
|
}
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
#isStringOrNumberLiteralType(type) {
|
|
2003
|
-
return Boolean(type.flags & this.compiler.TypeFlags.StringOrNumberLiteral);
|
|
2351
|
+
const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
|
|
2352
|
+
return matchWorker.assertion.isNot
|
|
2353
|
+
? [Diagnostic.error(ExpectDiagnosticText.typeHasProperty(sourceTypeText, propertyNameText), origin)]
|
|
2354
|
+
: [Diagnostic.error(ExpectDiagnosticText.typeDoesNotHaveProperty(sourceTypeText, propertyNameText), origin)];
|
|
2004
2355
|
}
|
|
2005
|
-
match(
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2356
|
+
match(matchWorker, sourceNode, targetNode, onDiagnostics) {
|
|
2357
|
+
const diagnostics = [];
|
|
2358
|
+
const sourceType = matchWorker.getType(sourceNode);
|
|
2359
|
+
if (matchWorker.isAnyOrNeverType(sourceType) || !matchWorker.extendsObjectType(sourceType)) {
|
|
2360
|
+
const expectedText = "of an object type";
|
|
2361
|
+
const text = this.#compiler.isTypeNode(sourceNode)
|
|
2362
|
+
? ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText)
|
|
2363
|
+
: ExpectDiagnosticText.argumentMustBe("source", expectedText);
|
|
2364
|
+
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
2365
|
+
diagnostics.push(Diagnostic.error(text, origin));
|
|
2366
|
+
}
|
|
2367
|
+
const targetType = matchWorker.getType(targetNode);
|
|
2368
|
+
let propertyNameText;
|
|
2369
|
+
if (matchWorker.isStringOrNumberLiteralType(targetType)) {
|
|
2370
|
+
propertyNameText = String(targetType.value);
|
|
2371
|
+
}
|
|
2372
|
+
else if (matchWorker.isUniqueSymbolType(targetType)) {
|
|
2373
|
+
propertyNameText = this.#compiler.unescapeLeadingUnderscores(targetType.escapedName);
|
|
2009
2374
|
}
|
|
2010
2375
|
else {
|
|
2011
|
-
|
|
2376
|
+
const expectedText = "of type 'string | number | symbol'";
|
|
2377
|
+
const text = ExpectDiagnosticText.argumentMustBe("key", expectedText);
|
|
2378
|
+
const origin = DiagnosticOrigin.fromNode(targetNode);
|
|
2379
|
+
diagnostics.push(Diagnostic.error(text, origin));
|
|
2380
|
+
}
|
|
2381
|
+
if (diagnostics.length > 0) {
|
|
2382
|
+
onDiagnostics(diagnostics);
|
|
2383
|
+
return;
|
|
2012
2384
|
}
|
|
2013
2385
|
const isMatch = sourceType.getProperties().some((property) => {
|
|
2014
|
-
return this
|
|
2386
|
+
return this.#compiler.unescapeLeadingUnderscores(property.escapedName) === propertyNameText;
|
|
2015
2387
|
});
|
|
2016
2388
|
return {
|
|
2017
|
-
explain: () => this.#explain(
|
|
2389
|
+
explain: () => this.#explain(matchWorker, sourceNode, targetNode),
|
|
2018
2390
|
isMatch,
|
|
2019
2391
|
};
|
|
2020
2392
|
}
|
|
2021
2393
|
}
|
|
2022
2394
|
|
|
2023
2395
|
class ToMatch extends RelationMatcherBase {
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2396
|
+
explainText = ExpectDiagnosticText.typeDoesMatch;
|
|
2397
|
+
explainNotText = ExpectDiagnosticText.typeDoesNotMatch;
|
|
2398
|
+
match(matchWorker, sourceNode, targetNode) {
|
|
2399
|
+
return {
|
|
2400
|
+
explain: () => this.explain(matchWorker, sourceNode, targetNode),
|
|
2401
|
+
isMatch: matchWorker.checkIsSubtype(sourceNode, targetNode),
|
|
2402
|
+
};
|
|
2403
|
+
}
|
|
2027
2404
|
}
|
|
2028
2405
|
|
|
2029
2406
|
class ToRaiseError {
|
|
2030
|
-
compiler;
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
this.compiler = compiler;
|
|
2034
|
-
this.typeChecker = typeChecker;
|
|
2407
|
+
#compiler;
|
|
2408
|
+
constructor(compiler) {
|
|
2409
|
+
this.#compiler = compiler;
|
|
2035
2410
|
}
|
|
2036
|
-
#explain(
|
|
2037
|
-
const
|
|
2038
|
-
|
|
2039
|
-
|
|
2411
|
+
#explain(matchWorker, sourceNode, targetNodes) {
|
|
2412
|
+
const isTypeNode = this.#compiler.isTypeNode(sourceNode);
|
|
2413
|
+
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
2414
|
+
if (matchWorker.assertion.diagnostics.size === 0) {
|
|
2415
|
+
const text = ExpectDiagnosticText.typeDidNotRaiseError(isTypeNode);
|
|
2416
|
+
return [Diagnostic.error(text, origin)];
|
|
2040
2417
|
}
|
|
2041
|
-
if (
|
|
2042
|
-
const
|
|
2043
|
-
|
|
2044
|
-
...Diagnostic.fromDiagnostics(source.diagnostics, this.compiler),
|
|
2045
|
-
];
|
|
2046
|
-
const text = `${sourceText} raised ${source.diagnostics.length === 1 ? "a" : String(source.diagnostics.length)} type error${source.diagnostics.length === 1 ? "" : "s"}.`;
|
|
2047
|
-
return [Diagnostic.error(text).add({ related })];
|
|
2048
|
-
}
|
|
2049
|
-
if (source.diagnostics.length !== targetTypes.length) {
|
|
2050
|
-
const expectedText = source.diagnostics.length > targetTypes.length
|
|
2051
|
-
? `only ${String(targetTypes.length)} type error${targetTypes.length === 1 ? "" : "s"}`
|
|
2052
|
-
: `${String(targetTypes.length)} type error${targetTypes.length === 1 ? "" : "s"}`;
|
|
2053
|
-
const foundText = source.diagnostics.length > targetTypes.length
|
|
2054
|
-
? String(source.diagnostics.length)
|
|
2055
|
-
: `only ${String(source.diagnostics.length)}`;
|
|
2418
|
+
if (matchWorker.assertion.diagnostics.size !== targetNodes.length) {
|
|
2419
|
+
const count = matchWorker.assertion.diagnostics.size;
|
|
2420
|
+
const text = ExpectDiagnosticText.typeRaisedError(isTypeNode, count, targetNodes.length);
|
|
2056
2421
|
const related = [
|
|
2057
|
-
Diagnostic.error(
|
|
2058
|
-
...Diagnostic.fromDiagnostics(
|
|
2422
|
+
Diagnostic.error(ExpectDiagnosticText.raisedTypeError(count)),
|
|
2423
|
+
...Diagnostic.fromDiagnostics([...matchWorker.assertion.diagnostics], this.#compiler),
|
|
2059
2424
|
];
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
if (!isNot && !isMatch) {
|
|
2071
|
-
const expectedText = this.#isStringLiteralType(argument)
|
|
2072
|
-
? `matching substring '${argument.value}'`
|
|
2073
|
-
: `with code ${String(argument.value)}`;
|
|
2425
|
+
return [Diagnostic.error(text, origin).add({ related })];
|
|
2426
|
+
}
|
|
2427
|
+
return [...matchWorker.assertion.diagnostics].reduce((accumulator, diagnostic, index) => {
|
|
2428
|
+
const targetNode = targetNodes[index];
|
|
2429
|
+
const isMatch = this.#matchExpectedError(diagnostic, targetNode);
|
|
2430
|
+
if (matchWorker.assertion.isNot ? isMatch : !isMatch) {
|
|
2431
|
+
const text = matchWorker.assertion.isNot
|
|
2432
|
+
? ExpectDiagnosticText.typeRaisedMatchingError(isTypeNode)
|
|
2433
|
+
: ExpectDiagnosticText.typeDidNotRaiseMatchingError(isTypeNode);
|
|
2434
|
+
const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
|
|
2074
2435
|
const related = [
|
|
2075
|
-
Diagnostic.error(
|
|
2076
|
-
...Diagnostic.fromDiagnostics([diagnostic], this
|
|
2436
|
+
Diagnostic.error(ExpectDiagnosticText.raisedTypeError()),
|
|
2437
|
+
...Diagnostic.fromDiagnostics([diagnostic], this.#compiler),
|
|
2077
2438
|
];
|
|
2078
|
-
|
|
2079
|
-
diagnostics.push(Diagnostic.error(text).add({ related }));
|
|
2439
|
+
accumulator.push(Diagnostic.error(text, origin).add({ related }));
|
|
2080
2440
|
}
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
? `matching substring '${argument.value}'`
|
|
2084
|
-
: `with code ${String(argument.value)}`;
|
|
2085
|
-
const related = [
|
|
2086
|
-
Diagnostic.error("The raised type error:"),
|
|
2087
|
-
...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
|
|
2088
|
-
];
|
|
2089
|
-
const text = `${sourceText} raised a type error ${expectedText}.`;
|
|
2090
|
-
diagnostics.push(Diagnostic.error(text).add({ related }));
|
|
2091
|
-
}
|
|
2092
|
-
});
|
|
2093
|
-
return diagnostics;
|
|
2094
|
-
}
|
|
2095
|
-
#isStringLiteralType(type) {
|
|
2096
|
-
return Boolean(type.flags & this.compiler.TypeFlags.StringLiteral);
|
|
2441
|
+
return accumulator;
|
|
2442
|
+
}, []);
|
|
2097
2443
|
}
|
|
2098
|
-
match(
|
|
2099
|
-
const
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2444
|
+
match(matchWorker, sourceNode, targetNodes, onDiagnostics) {
|
|
2445
|
+
const diagnostics = [];
|
|
2446
|
+
for (const targetNode of targetNodes) {
|
|
2447
|
+
if (!(this.#compiler.isStringLiteralLike(targetNode) || this.#compiler.isNumericLiteral(targetNode))) {
|
|
2448
|
+
const expectedText = "a string or number literal";
|
|
2449
|
+
const text = ExpectDiagnosticText.argumentMustBe("target", expectedText);
|
|
2450
|
+
const origin = DiagnosticOrigin.fromNode(targetNode);
|
|
2451
|
+
diagnostics.push(Diagnostic.error(text, origin));
|
|
2452
|
+
}
|
|
2105
2453
|
}
|
|
2106
|
-
if (
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2454
|
+
if (diagnostics.length > 0) {
|
|
2455
|
+
onDiagnostics(diagnostics);
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
2458
|
+
let isMatch;
|
|
2459
|
+
if (targetNodes.length === 0) {
|
|
2460
|
+
isMatch = matchWorker.assertion.diagnostics.size > 0;
|
|
2461
|
+
}
|
|
2462
|
+
else {
|
|
2463
|
+
isMatch =
|
|
2464
|
+
matchWorker.assertion.diagnostics.size === targetNodes.length &&
|
|
2465
|
+
[...matchWorker.assertion.diagnostics].every((diagnostic, index) => this.#matchExpectedError(diagnostic, targetNodes[index]));
|
|
2111
2466
|
}
|
|
2112
2467
|
return {
|
|
2113
|
-
explain,
|
|
2114
|
-
isMatch
|
|
2468
|
+
explain: () => this.#explain(matchWorker, sourceNode, targetNodes),
|
|
2469
|
+
isMatch,
|
|
2115
2470
|
};
|
|
2116
2471
|
}
|
|
2117
|
-
#matchExpectedError(diagnostic,
|
|
2118
|
-
if (this.#
|
|
2119
|
-
return this
|
|
2472
|
+
#matchExpectedError(diagnostic, targetNode) {
|
|
2473
|
+
if (this.#compiler.isStringLiteralLike(targetNode)) {
|
|
2474
|
+
return this.#compiler.flattenDiagnosticMessageText(diagnostic.messageText, " ", 0).includes(targetNode.text);
|
|
2120
2475
|
}
|
|
2121
|
-
return
|
|
2476
|
+
return Number(targetNode.text) === diagnostic.code;
|
|
2122
2477
|
}
|
|
2123
2478
|
}
|
|
2124
2479
|
|
|
2125
|
-
class
|
|
2480
|
+
class ExpectService {
|
|
2126
2481
|
#compiler;
|
|
2127
2482
|
#typeChecker;
|
|
2483
|
+
toAcceptProps;
|
|
2128
2484
|
toBe;
|
|
2129
2485
|
toBeAny;
|
|
2130
2486
|
toBeAssignable;
|
|
@@ -2148,66 +2504,63 @@ class Expect {
|
|
|
2148
2504
|
constructor(compiler, typeChecker) {
|
|
2149
2505
|
this.#compiler = compiler;
|
|
2150
2506
|
this.#typeChecker = typeChecker;
|
|
2151
|
-
this.
|
|
2152
|
-
this.
|
|
2153
|
-
this.
|
|
2154
|
-
this.
|
|
2155
|
-
this.
|
|
2156
|
-
this.
|
|
2157
|
-
this.
|
|
2158
|
-
this.
|
|
2159
|
-
this.
|
|
2160
|
-
this.
|
|
2161
|
-
this.
|
|
2162
|
-
this.
|
|
2163
|
-
this.
|
|
2164
|
-
this.
|
|
2165
|
-
this.
|
|
2166
|
-
this.
|
|
2167
|
-
this.
|
|
2168
|
-
this.
|
|
2169
|
-
this.
|
|
2170
|
-
this.
|
|
2507
|
+
this.toAcceptProps = new ToAcceptProps(compiler, typeChecker);
|
|
2508
|
+
this.toBe = new ToBe();
|
|
2509
|
+
this.toBeAny = new PrimitiveTypeMatcher(compiler.TypeFlags.Any);
|
|
2510
|
+
this.toBeAssignable = new ToBeAssignableWith();
|
|
2511
|
+
this.toBeAssignableTo = new ToBeAssignableTo();
|
|
2512
|
+
this.toBeAssignableWith = new ToBeAssignableWith();
|
|
2513
|
+
this.toBeBigInt = new PrimitiveTypeMatcher(compiler.TypeFlags.BigInt);
|
|
2514
|
+
this.toBeBoolean = new PrimitiveTypeMatcher(compiler.TypeFlags.Boolean);
|
|
2515
|
+
this.toBeNever = new PrimitiveTypeMatcher(compiler.TypeFlags.Never);
|
|
2516
|
+
this.toBeNull = new PrimitiveTypeMatcher(compiler.TypeFlags.Null);
|
|
2517
|
+
this.toBeNumber = new PrimitiveTypeMatcher(compiler.TypeFlags.Number);
|
|
2518
|
+
this.toBeString = new PrimitiveTypeMatcher(compiler.TypeFlags.String);
|
|
2519
|
+
this.toBeSymbol = new PrimitiveTypeMatcher(compiler.TypeFlags.ESSymbol);
|
|
2520
|
+
this.toBeUndefined = new PrimitiveTypeMatcher(compiler.TypeFlags.Undefined);
|
|
2521
|
+
this.toBeUniqueSymbol = new PrimitiveTypeMatcher(compiler.TypeFlags.UniqueESSymbol);
|
|
2522
|
+
this.toBeUnknown = new PrimitiveTypeMatcher(compiler.TypeFlags.Unknown);
|
|
2523
|
+
this.toBeVoid = new PrimitiveTypeMatcher(compiler.TypeFlags.Void);
|
|
2524
|
+
this.toEqual = new ToBe();
|
|
2525
|
+
this.toHaveProperty = new ToHaveProperty(compiler);
|
|
2526
|
+
this.toMatch = new ToMatch();
|
|
2527
|
+
this.toRaiseError = new ToRaiseError(compiler);
|
|
2171
2528
|
}
|
|
2172
2529
|
static assertTypeChecker(typeChecker) {
|
|
2173
2530
|
return "isTypeRelatedTo" in typeChecker && "relation" in typeChecker;
|
|
2174
2531
|
}
|
|
2175
|
-
#
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
:
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
}
|
|
2186
|
-
#isStringOrNumberLiteralType(type) {
|
|
2187
|
-
return Boolean(type.flags & this.#compiler.TypeFlags.StringOrNumberLiteral);
|
|
2188
|
-
}
|
|
2189
|
-
#isUniqueSymbolType(type) {
|
|
2190
|
-
return Boolean(type.flags & this.#compiler.TypeFlags.UniqueESSymbol);
|
|
2532
|
+
#handleDeprecated(matcherNameText, assertion) {
|
|
2533
|
+
switch (matcherNameText) {
|
|
2534
|
+
case "toBeAssignable":
|
|
2535
|
+
case "toEqual": {
|
|
2536
|
+
const text = ExpectDiagnosticText.matcherIsDeprecated(matcherNameText);
|
|
2537
|
+
const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
|
|
2538
|
+
EventEmitter.dispatch(["deprecation:info", { diagnostics: [Diagnostic.warning(text, origin)] }]);
|
|
2539
|
+
break;
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2191
2542
|
}
|
|
2192
|
-
match(assertion,
|
|
2543
|
+
match(assertion, onDiagnostics) {
|
|
2193
2544
|
const matcherNameText = assertion.matcherName.getText();
|
|
2545
|
+
this.#handleDeprecated(matcherNameText, assertion);
|
|
2546
|
+
if (!assertion.source[0]) {
|
|
2547
|
+
this.#onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
|
|
2548
|
+
return;
|
|
2549
|
+
}
|
|
2550
|
+
const matchWorker = new MatchWorker(this.#compiler, this.#typeChecker, assertion);
|
|
2194
2551
|
switch (matcherNameText) {
|
|
2195
|
-
case "
|
|
2196
|
-
case "toEqual":
|
|
2197
|
-
this.#onDeprecatedMatcher(assertion);
|
|
2552
|
+
case "toAcceptProps":
|
|
2198
2553
|
case "toBe":
|
|
2554
|
+
case "toBeAssignable":
|
|
2199
2555
|
case "toBeAssignableTo":
|
|
2200
2556
|
case "toBeAssignableWith":
|
|
2557
|
+
case "toEqual":
|
|
2201
2558
|
case "toMatch": {
|
|
2202
|
-
if (assertion.
|
|
2203
|
-
this.#
|
|
2204
|
-
return;
|
|
2205
|
-
}
|
|
2206
|
-
if (assertion.target[0] == null) {
|
|
2207
|
-
this.#onTargetArgumentMustBeProvided(assertion, expectResult);
|
|
2559
|
+
if (!assertion.target[0]) {
|
|
2560
|
+
this.#onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
|
|
2208
2561
|
return;
|
|
2209
2562
|
}
|
|
2210
|
-
return this[matcherNameText].match(
|
|
2563
|
+
return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
|
|
2211
2564
|
}
|
|
2212
2565
|
case "toBeAny":
|
|
2213
2566
|
case "toBeBigInt":
|
|
@@ -2221,131 +2574,40 @@ class Expect {
|
|
|
2221
2574
|
case "toBeUniqueSymbol":
|
|
2222
2575
|
case "toBeUnknown":
|
|
2223
2576
|
case "toBeVoid": {
|
|
2224
|
-
|
|
2225
|
-
this.#onSourceArgumentMustBeProvided(assertion, expectResult);
|
|
2226
|
-
return;
|
|
2227
|
-
}
|
|
2228
|
-
return this[matcherNameText].match(this.#getType(assertion.source[0]));
|
|
2577
|
+
return this[matcherNameText].match(matchWorker, assertion.source[0]);
|
|
2229
2578
|
}
|
|
2230
2579
|
case "toHaveProperty": {
|
|
2231
|
-
if (assertion.
|
|
2232
|
-
this.#
|
|
2580
|
+
if (!assertion.target[0]) {
|
|
2581
|
+
this.#onTargetArgumentMustBeProvided("key", assertion, onDiagnostics);
|
|
2233
2582
|
return;
|
|
2234
2583
|
}
|
|
2235
|
-
|
|
2236
|
-
const nonPrimitiveType = { flags: this.#compiler.TypeFlags.NonPrimitive };
|
|
2237
|
-
if (sourceType.flags & (this.#compiler.TypeFlags.Any | this.#compiler.TypeFlags.Never) ||
|
|
2238
|
-
!this.#typeChecker.isTypeRelatedTo(sourceType, nonPrimitiveType, this.#typeChecker.relation.assignable)) {
|
|
2239
|
-
this.#onSourceArgumentMustBeObjectType(assertion.source[0], expectResult);
|
|
2240
|
-
return;
|
|
2241
|
-
}
|
|
2242
|
-
if (assertion.target[0] == null) {
|
|
2243
|
-
this.#onKeyArgumentMustBeProvided(assertion, expectResult);
|
|
2244
|
-
return;
|
|
2245
|
-
}
|
|
2246
|
-
const targetType = this.#getType(assertion.target[0]);
|
|
2247
|
-
if (!(this.#isStringOrNumberLiteralType(targetType) || this.#isUniqueSymbolType(targetType))) {
|
|
2248
|
-
this.#onKeyArgumentMustBeOfType(assertion.target[0], expectResult);
|
|
2249
|
-
return;
|
|
2250
|
-
}
|
|
2251
|
-
return this.toHaveProperty.match(sourceType, targetType, assertion.isNot);
|
|
2584
|
+
return this.toHaveProperty.match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
|
|
2252
2585
|
}
|
|
2253
2586
|
case "toRaiseError": {
|
|
2254
|
-
|
|
2255
|
-
this.#onSourceArgumentMustBeProvided(assertion, expectResult);
|
|
2256
|
-
return;
|
|
2257
|
-
}
|
|
2258
|
-
const targetTypes = this.#getTypes(assertion.target);
|
|
2259
|
-
if (!this.#isArrayOfStringOrNumberLiteralTypes(targetTypes)) {
|
|
2260
|
-
this.#onTargetArgumentsMustBeStringOrNumberLiteralTypes(assertion.target, expectResult);
|
|
2261
|
-
return;
|
|
2262
|
-
}
|
|
2263
|
-
return this.toRaiseError.match({ diagnostics: [...assertion.diagnostics], node: assertion.source[0] }, targetTypes, assertion.isNot);
|
|
2587
|
+
return this.toRaiseError.match(matchWorker, assertion.source[0], [...assertion.target], onDiagnostics);
|
|
2264
2588
|
}
|
|
2265
2589
|
default: {
|
|
2266
|
-
|
|
2267
|
-
|
|
2590
|
+
const text = ExpectDiagnosticText.matcherIsNotSupported(matcherNameText);
|
|
2591
|
+
const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
|
|
2592
|
+
onDiagnostics(Diagnostic.error(text, origin));
|
|
2268
2593
|
}
|
|
2269
2594
|
}
|
|
2595
|
+
return;
|
|
2270
2596
|
}
|
|
2271
|
-
#
|
|
2272
|
-
const
|
|
2273
|
-
const
|
|
2274
|
-
|
|
2275
|
-
"To learn more, visit https://tstyche.org/release-notes/tstyche-2",
|
|
2276
|
-
];
|
|
2277
|
-
const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
|
|
2278
|
-
EventEmitter.dispatch(["deprecation:info", { diagnostics: [Diagnostic.warning(text, origin)] }]);
|
|
2279
|
-
}
|
|
2280
|
-
#onKeyArgumentMustBeOfType(node, expectResult) {
|
|
2281
|
-
const receivedTypeText = this.#typeChecker.typeToString(this.#getType(node));
|
|
2282
|
-
const text = `An argument for 'key' must be of type 'string | number | symbol', received: '${receivedTypeText}'.`;
|
|
2283
|
-
const origin = DiagnosticOrigin.fromNode(node);
|
|
2284
|
-
EventEmitter.dispatch(["expect:error", { diagnostics: [Diagnostic.error(text, origin)], result: expectResult }]);
|
|
2597
|
+
#onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics) {
|
|
2598
|
+
const text = ExpectDiagnosticText.argumentOrTypeArgumentMustBeProvided("source", "Source");
|
|
2599
|
+
const origin = DiagnosticOrigin.fromNode(assertion.node.expression);
|
|
2600
|
+
onDiagnostics(Diagnostic.error(text, origin));
|
|
2285
2601
|
}
|
|
2286
|
-
#
|
|
2602
|
+
#onTargetArgumentMustBeProvided(argumentNameText, assertion, onDiagnostics) {
|
|
2603
|
+
const text = ExpectDiagnosticText.argumentMustBeProvided(argumentNameText);
|
|
2287
2604
|
const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
|
|
2288
|
-
|
|
2289
|
-
"expect:error",
|
|
2290
|
-
{
|
|
2291
|
-
diagnostics: [Diagnostic.error("An argument for 'key' must be provided.", origin)],
|
|
2292
|
-
result: expectResult,
|
|
2293
|
-
},
|
|
2294
|
-
]);
|
|
2605
|
+
onDiagnostics(Diagnostic.error(text, origin));
|
|
2295
2606
|
}
|
|
2296
|
-
#
|
|
2297
|
-
const
|
|
2607
|
+
#onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics) {
|
|
2608
|
+
const text = ExpectDiagnosticText.argumentOrTypeArgumentMustBeProvided("target", "Target");
|
|
2298
2609
|
const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
|
|
2299
|
-
|
|
2300
|
-
"expect:error",
|
|
2301
|
-
{
|
|
2302
|
-
diagnostics: [Diagnostic.error(`The '${matcherNameText}()' matcher is not supported.`, origin)],
|
|
2303
|
-
result: expectResult,
|
|
2304
|
-
},
|
|
2305
|
-
]);
|
|
2306
|
-
}
|
|
2307
|
-
#onSourceArgumentMustBeObjectType(node, expectResult) {
|
|
2308
|
-
const sourceText = this.#compiler.isTypeNode(node) ? "A type argument for 'Source'" : "An argument for 'source'";
|
|
2309
|
-
const receivedTypeText = this.#typeChecker.typeToString(this.#getType(node));
|
|
2310
|
-
const text = `${sourceText} must be of an object type, received: '${receivedTypeText}'.`;
|
|
2311
|
-
const origin = DiagnosticOrigin.fromNode(node);
|
|
2312
|
-
EventEmitter.dispatch(["expect:error", { diagnostics: [Diagnostic.error(text, origin)], result: expectResult }]);
|
|
2313
|
-
}
|
|
2314
|
-
#onSourceArgumentMustBeProvided(assertion, expectResult) {
|
|
2315
|
-
const origin = DiagnosticOrigin.fromNode(assertion.node);
|
|
2316
|
-
EventEmitter.dispatch([
|
|
2317
|
-
"expect:error",
|
|
2318
|
-
{
|
|
2319
|
-
diagnostics: [
|
|
2320
|
-
Diagnostic.error("An argument for 'source' or type argument for 'Source' must be provided.", origin),
|
|
2321
|
-
],
|
|
2322
|
-
result: expectResult,
|
|
2323
|
-
},
|
|
2324
|
-
]);
|
|
2325
|
-
}
|
|
2326
|
-
#onTargetArgumentMustBeProvided(assertion, expectResult) {
|
|
2327
|
-
const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
|
|
2328
|
-
EventEmitter.dispatch([
|
|
2329
|
-
"expect:error",
|
|
2330
|
-
{
|
|
2331
|
-
diagnostics: [
|
|
2332
|
-
Diagnostic.error("An argument for 'target' or type argument for 'Target' must be provided.", origin),
|
|
2333
|
-
],
|
|
2334
|
-
result: expectResult,
|
|
2335
|
-
},
|
|
2336
|
-
]);
|
|
2337
|
-
}
|
|
2338
|
-
#onTargetArgumentsMustBeStringOrNumberLiteralTypes(nodes, expectResult) {
|
|
2339
|
-
const diagnostics = [];
|
|
2340
|
-
for (const node of nodes) {
|
|
2341
|
-
const receivedType = this.#getType(node);
|
|
2342
|
-
if (!this.#isStringOrNumberLiteralType(receivedType)) {
|
|
2343
|
-
const receivedTypeText = this.#typeChecker.typeToString(this.#getType(node));
|
|
2344
|
-
const origin = DiagnosticOrigin.fromNode(node);
|
|
2345
|
-
diagnostics.push(Diagnostic.error(`An argument for 'target' must be of type 'string | number', received: '${receivedTypeText}'.`, origin));
|
|
2346
|
-
}
|
|
2347
|
-
}
|
|
2348
|
-
EventEmitter.dispatch(["expect:error", { diagnostics, result: expectResult }]);
|
|
2610
|
+
onDiagnostics(Diagnostic.error(text, origin));
|
|
2349
2611
|
}
|
|
2350
2612
|
}
|
|
2351
2613
|
|
|
@@ -2445,7 +2707,7 @@ class ProjectService {
|
|
|
2445
2707
|
defaultCompilerOptions.module = "preserve";
|
|
2446
2708
|
}
|
|
2447
2709
|
if (Version.isSatisfiedWith(this.#compiler.version, "5.0")) {
|
|
2448
|
-
defaultCompilerOptions
|
|
2710
|
+
defaultCompilerOptions.allowImportingTsExtensions = true;
|
|
2449
2711
|
defaultCompilerOptions.moduleResolution = "bundler";
|
|
2450
2712
|
}
|
|
2451
2713
|
return defaultCompilerOptions;
|
|
@@ -2478,19 +2740,19 @@ class ProjectService {
|
|
|
2478
2740
|
class TestTreeWorker {
|
|
2479
2741
|
#compiler;
|
|
2480
2742
|
#cancellationToken;
|
|
2481
|
-
#
|
|
2743
|
+
#expectService;
|
|
2482
2744
|
#fileResult;
|
|
2483
2745
|
#hasOnly;
|
|
2484
2746
|
#position;
|
|
2485
2747
|
#resolvedConfig;
|
|
2486
|
-
constructor(resolvedConfig, compiler,
|
|
2748
|
+
constructor(resolvedConfig, compiler, typeChecker, options) {
|
|
2487
2749
|
this.#resolvedConfig = resolvedConfig;
|
|
2488
2750
|
this.#compiler = compiler;
|
|
2489
|
-
this.#expect = expect;
|
|
2490
2751
|
this.#cancellationToken = options.cancellationToken;
|
|
2491
2752
|
this.#fileResult = options.fileResult;
|
|
2492
2753
|
this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
|
|
2493
2754
|
this.#position = options.position;
|
|
2755
|
+
this.#expectService = new ExpectService(compiler, typeChecker);
|
|
2494
2756
|
}
|
|
2495
2757
|
#resolveRunMode(mode, member) {
|
|
2496
2758
|
if (member.flags & 1) {
|
|
@@ -2520,13 +2782,7 @@ class TestTreeWorker {
|
|
|
2520
2782
|
}
|
|
2521
2783
|
const validationError = member.validate();
|
|
2522
2784
|
if (validationError.length > 0) {
|
|
2523
|
-
EventEmitter.dispatch([
|
|
2524
|
-
"file:error",
|
|
2525
|
-
{
|
|
2526
|
-
diagnostics: validationError,
|
|
2527
|
-
result: this.#fileResult,
|
|
2528
|
-
},
|
|
2529
|
-
]);
|
|
2785
|
+
EventEmitter.dispatch(["file:error", { diagnostics: validationError, result: this.#fileResult }]);
|
|
2530
2786
|
break;
|
|
2531
2787
|
}
|
|
2532
2788
|
switch (member.brand) {
|
|
@@ -2554,28 +2810,25 @@ class TestTreeWorker {
|
|
|
2554
2810
|
EventEmitter.dispatch(["expect:skip", { result: expectResult }]);
|
|
2555
2811
|
return;
|
|
2556
2812
|
}
|
|
2557
|
-
|
|
2813
|
+
const onExpectDiagnostics = (diagnostics) => {
|
|
2558
2814
|
EventEmitter.dispatch([
|
|
2559
2815
|
"expect:error",
|
|
2560
|
-
{
|
|
2561
|
-
diagnostics: Diagnostic.fromDiagnostics([...assertion.diagnostics], this.#compiler),
|
|
2562
|
-
result: expectResult,
|
|
2563
|
-
},
|
|
2816
|
+
{ diagnostics: Array.isArray(diagnostics) ? diagnostics : [diagnostics], result: expectResult },
|
|
2564
2817
|
]);
|
|
2818
|
+
};
|
|
2819
|
+
if (assertion.diagnostics.size > 0 && assertion.matcherName.getText() !== "toRaiseError") {
|
|
2820
|
+
onExpectDiagnostics(Diagnostic.fromDiagnostics([...assertion.diagnostics], this.#compiler));
|
|
2565
2821
|
return;
|
|
2566
2822
|
}
|
|
2567
|
-
const matchResult = this.#
|
|
2568
|
-
if (matchResult
|
|
2823
|
+
const matchResult = this.#expectService.match(assertion, onExpectDiagnostics);
|
|
2824
|
+
if (!matchResult) {
|
|
2569
2825
|
return;
|
|
2570
2826
|
}
|
|
2571
2827
|
if (assertion.isNot ? !matchResult.isMatch : matchResult.isMatch) {
|
|
2572
2828
|
if (runMode & 1) {
|
|
2573
2829
|
const text = ["The assertion was supposed to fail, but it passed.", "Consider removing the '.fail' flag."];
|
|
2574
|
-
const origin = DiagnosticOrigin.fromNode(assertion.node);
|
|
2575
|
-
|
|
2576
|
-
"expect:error",
|
|
2577
|
-
{ diagnostics: [Diagnostic.error(text, origin)], result: expectResult },
|
|
2578
|
-
]);
|
|
2830
|
+
const origin = DiagnosticOrigin.fromNode(assertion.node.expression.name);
|
|
2831
|
+
onExpectDiagnostics(Diagnostic.error(text, origin));
|
|
2579
2832
|
}
|
|
2580
2833
|
else {
|
|
2581
2834
|
EventEmitter.dispatch(["expect:pass", { result: expectResult }]);
|
|
@@ -2585,14 +2838,7 @@ class TestTreeWorker {
|
|
|
2585
2838
|
EventEmitter.dispatch(["expect:pass", { result: expectResult }]);
|
|
2586
2839
|
}
|
|
2587
2840
|
else {
|
|
2588
|
-
|
|
2589
|
-
const diagnostics = matchResult.explain().map((diagnostic) => {
|
|
2590
|
-
if (diagnostic.origin == null) {
|
|
2591
|
-
return diagnostic.add({ origin });
|
|
2592
|
-
}
|
|
2593
|
-
return diagnostic;
|
|
2594
|
-
});
|
|
2595
|
-
EventEmitter.dispatch(["expect:fail", { diagnostics, result: expectResult }]);
|
|
2841
|
+
EventEmitter.dispatch(["expect:fail", { diagnostics: matchResult.explain(), result: expectResult }]);
|
|
2596
2842
|
}
|
|
2597
2843
|
}
|
|
2598
2844
|
#visitDescribe(describe, runMode, parentResult) {
|
|
@@ -2705,13 +2951,12 @@ class TestFileRunner {
|
|
|
2705
2951
|
return;
|
|
2706
2952
|
}
|
|
2707
2953
|
const typeChecker = program.getTypeChecker();
|
|
2708
|
-
if (!
|
|
2954
|
+
if (!ExpectService.assertTypeChecker(typeChecker)) {
|
|
2709
2955
|
const text = "The required 'isTypeRelatedTo()' method is missing in the provided type checker.";
|
|
2710
2956
|
EventEmitter.dispatch(["file:error", { diagnostics: [Diagnostic.error(text)], result: fileResult }]);
|
|
2711
2957
|
return;
|
|
2712
2958
|
}
|
|
2713
|
-
const
|
|
2714
|
-
const testTreeWorker = new TestTreeWorker(this.#resolvedConfig, this.#compiler, expect, {
|
|
2959
|
+
const testTreeWorker = new TestTreeWorker(this.#resolvedConfig, this.#compiler, typeChecker, {
|
|
2715
2960
|
cancellationToken,
|
|
2716
2961
|
fileResult,
|
|
2717
2962
|
hasOnly: testTree.hasOnly,
|
|
@@ -2742,6 +2987,7 @@ class TaskRunner {
|
|
|
2742
2987
|
this.#eventEmitter.addHandler(cancellationHandler);
|
|
2743
2988
|
}
|
|
2744
2989
|
if (this.#resolvedConfig.watch === true) {
|
|
2990
|
+
await this.#run(testFiles, cancellationToken);
|
|
2745
2991
|
await this.#watch(testFiles, cancellationToken);
|
|
2746
2992
|
}
|
|
2747
2993
|
else {
|
|
@@ -2767,22 +3013,15 @@ class TaskRunner {
|
|
|
2767
3013
|
EventEmitter.dispatch(["target:end", { result: targetResult }]);
|
|
2768
3014
|
}
|
|
2769
3015
|
EventEmitter.dispatch(["run:end", { result }]);
|
|
2770
|
-
if (cancellationToken
|
|
3016
|
+
if (cancellationToken.reason === "failFast") {
|
|
2771
3017
|
cancellationToken.reset();
|
|
2772
3018
|
}
|
|
2773
3019
|
}
|
|
2774
3020
|
async #watch(testFiles, cancellationToken) {
|
|
2775
|
-
|
|
2776
|
-
const
|
|
3021
|
+
const watchService = new WatchService(this.#resolvedConfig, this.#selectService, testFiles);
|
|
3022
|
+
for await (const testFiles of watchService.watch(cancellationToken)) {
|
|
2777
3023
|
await this.#run(testFiles, cancellationToken);
|
|
2778
|
-
}
|
|
2779
|
-
const watchModeManager = new WatchService(this.#resolvedConfig, runCallback, this.#selectService, testFiles);
|
|
2780
|
-
cancellationToken?.onCancellationRequested((reason) => {
|
|
2781
|
-
if (reason !== "failFast") {
|
|
2782
|
-
watchModeManager.close();
|
|
2783
|
-
}
|
|
2784
|
-
});
|
|
2785
|
-
await watchModeManager.watch(cancellationToken);
|
|
3024
|
+
}
|
|
2786
3025
|
}
|
|
2787
3026
|
}
|
|
2788
3027
|
|
|
@@ -2793,7 +3032,7 @@ class TSTyche {
|
|
|
2793
3032
|
#selectService;
|
|
2794
3033
|
#storeService;
|
|
2795
3034
|
#taskRunner;
|
|
2796
|
-
static version = "2.
|
|
3035
|
+
static version = "2.1.0";
|
|
2797
3036
|
constructor(resolvedConfig, outputService, selectService, storeService) {
|
|
2798
3037
|
this.#resolvedConfig = resolvedConfig;
|
|
2799
3038
|
this.#outputService = outputService;
|
|
@@ -3009,14 +3248,14 @@ class OptionUsageText {
|
|
|
3009
3248
|
}
|
|
3010
3249
|
|
|
3011
3250
|
class OptionValidator {
|
|
3012
|
-
#
|
|
3251
|
+
#onDiagnostics;
|
|
3013
3252
|
#optionGroup;
|
|
3014
3253
|
#optionUsageText;
|
|
3015
3254
|
#storeService;
|
|
3016
|
-
constructor(optionGroup, storeService,
|
|
3255
|
+
constructor(optionGroup, storeService, onDiagnostics) {
|
|
3017
3256
|
this.#optionGroup = optionGroup;
|
|
3018
3257
|
this.#storeService = storeService;
|
|
3019
|
-
this.#
|
|
3258
|
+
this.#onDiagnostics = onDiagnostics;
|
|
3020
3259
|
this.#optionUsageText = new OptionUsageText(this.#optionGroup, this.#storeService);
|
|
3021
3260
|
}
|
|
3022
3261
|
async check(optionName, optionValue, optionBrand, origin) {
|
|
@@ -3024,13 +3263,13 @@ class OptionValidator {
|
|
|
3024
3263
|
case "config":
|
|
3025
3264
|
case "rootPath": {
|
|
3026
3265
|
if (!existsSync(optionValue)) {
|
|
3027
|
-
this.#
|
|
3266
|
+
this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.fileDoesNotExist(optionValue), origin));
|
|
3028
3267
|
}
|
|
3029
3268
|
break;
|
|
3030
3269
|
}
|
|
3031
3270
|
case "target": {
|
|
3032
3271
|
if ((await this.#storeService.validateTag(optionValue)) === false) {
|
|
3033
|
-
this.#
|
|
3272
|
+
this.#onDiagnostics(Diagnostic.error([
|
|
3034
3273
|
ConfigDiagnosticText.versionIsNotSupported(optionValue),
|
|
3035
3274
|
...(await this.#optionUsageText.get(optionName, optionBrand)),
|
|
3036
3275
|
], origin));
|
|
@@ -3040,14 +3279,14 @@ class OptionValidator {
|
|
|
3040
3279
|
case "testFileMatch": {
|
|
3041
3280
|
for (const segment of ["/", "../"]) {
|
|
3042
3281
|
if (optionValue.startsWith(segment)) {
|
|
3043
|
-
this.#
|
|
3282
|
+
this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.testFileMatchCannotStartWith(segment), origin));
|
|
3044
3283
|
}
|
|
3045
3284
|
}
|
|
3046
3285
|
break;
|
|
3047
3286
|
}
|
|
3048
3287
|
case "watch": {
|
|
3049
3288
|
if (Environment.isCi) {
|
|
3050
|
-
this.#
|
|
3289
|
+
this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.watchCannotBeEnabled(), origin));
|
|
3051
3290
|
}
|
|
3052
3291
|
break;
|
|
3053
3292
|
}
|
|
@@ -3058,27 +3297,27 @@ class OptionValidator {
|
|
|
3058
3297
|
class CommandLineOptionsWorker {
|
|
3059
3298
|
#commandLineOptionDefinitions;
|
|
3060
3299
|
#commandLineOptions;
|
|
3061
|
-
#
|
|
3300
|
+
#onDiagnostics;
|
|
3062
3301
|
#optionGroup = 2;
|
|
3063
3302
|
#optionUsageText;
|
|
3064
3303
|
#optionValidator;
|
|
3065
3304
|
#pathMatch;
|
|
3066
3305
|
#storeService;
|
|
3067
|
-
constructor(commandLineOptions, pathMatch, storeService,
|
|
3306
|
+
constructor(commandLineOptions, pathMatch, storeService, onDiagnostics) {
|
|
3068
3307
|
this.#commandLineOptions = commandLineOptions;
|
|
3069
3308
|
this.#pathMatch = pathMatch;
|
|
3070
3309
|
this.#storeService = storeService;
|
|
3071
|
-
this.#
|
|
3310
|
+
this.#onDiagnostics = onDiagnostics;
|
|
3072
3311
|
this.#commandLineOptionDefinitions = OptionDefinitionsMap.for(this.#optionGroup);
|
|
3073
3312
|
this.#optionUsageText = new OptionUsageText(this.#optionGroup, this.#storeService);
|
|
3074
|
-
this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#
|
|
3313
|
+
this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostics);
|
|
3075
3314
|
}
|
|
3076
3315
|
async #onExpectsValue(optionDefinition) {
|
|
3077
3316
|
const text = [
|
|
3078
3317
|
ConfigDiagnosticText.expectsValue(optionDefinition.name, this.#optionGroup),
|
|
3079
3318
|
...(await this.#optionUsageText.get(optionDefinition.name, optionDefinition.brand)),
|
|
3080
3319
|
];
|
|
3081
|
-
this.#
|
|
3320
|
+
this.#onDiagnostics(Diagnostic.error(text));
|
|
3082
3321
|
}
|
|
3083
3322
|
async parse(commandLineArgs) {
|
|
3084
3323
|
let index = 0;
|
|
@@ -3092,11 +3331,11 @@ class CommandLineOptionsWorker {
|
|
|
3092
3331
|
index = await this.#parseOptionValue(commandLineArgs, index, optionDefinition);
|
|
3093
3332
|
}
|
|
3094
3333
|
else {
|
|
3095
|
-
this.#
|
|
3334
|
+
this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.unknownOption(arg)));
|
|
3096
3335
|
}
|
|
3097
3336
|
}
|
|
3098
3337
|
else if (arg.startsWith("-")) {
|
|
3099
|
-
this.#
|
|
3338
|
+
this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.unknownOption(arg)));
|
|
3100
3339
|
}
|
|
3101
3340
|
else {
|
|
3102
3341
|
this.#pathMatch.push(Path.normalizeSlashes(arg));
|
|
@@ -3162,18 +3401,18 @@ class ConfigFileOptionsWorker {
|
|
|
3162
3401
|
#configFileOptionDefinitions;
|
|
3163
3402
|
#configFileOptions;
|
|
3164
3403
|
#configFilePath;
|
|
3165
|
-
#
|
|
3404
|
+
#onDiagnostics;
|
|
3166
3405
|
#optionGroup = 4;
|
|
3167
3406
|
#optionValidator;
|
|
3168
3407
|
#storeService;
|
|
3169
|
-
constructor(compiler, configFileOptions, configFilePath, storeService,
|
|
3408
|
+
constructor(compiler, configFileOptions, configFilePath, storeService, onDiagnostics) {
|
|
3170
3409
|
this.#compiler = compiler;
|
|
3171
3410
|
this.#configFileOptions = configFileOptions;
|
|
3172
3411
|
this.#configFilePath = configFilePath;
|
|
3173
3412
|
this.#storeService = storeService;
|
|
3174
|
-
this.#
|
|
3413
|
+
this.#onDiagnostics = onDiagnostics;
|
|
3175
3414
|
this.#configFileOptionDefinitions = OptionDefinitionsMap.for(this.#optionGroup);
|
|
3176
|
-
this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#
|
|
3415
|
+
this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostics);
|
|
3177
3416
|
}
|
|
3178
3417
|
#isDoubleQuotedString(node, sourceFile) {
|
|
3179
3418
|
return (node.kind === this.#compiler.SyntaxKind.StringLiteral &&
|
|
@@ -3182,25 +3421,26 @@ class ConfigFileOptionsWorker {
|
|
|
3182
3421
|
async parse(sourceText) {
|
|
3183
3422
|
const sourceFile = this.#compiler.parseJsonText(this.#configFilePath, sourceText);
|
|
3184
3423
|
if (sourceFile.parseDiagnostics.length > 0) {
|
|
3185
|
-
|
|
3186
|
-
this.#onDiagnostic(diagnostic);
|
|
3187
|
-
}
|
|
3424
|
+
this.#onDiagnostics(Diagnostic.fromDiagnostics(sourceFile.parseDiagnostics, this.#compiler));
|
|
3188
3425
|
return;
|
|
3189
3426
|
}
|
|
3190
3427
|
const rootExpression = sourceFile.statements[0]?.expression;
|
|
3191
|
-
if (
|
|
3192
|
-
|
|
3193
|
-
|
|
3428
|
+
if (!rootExpression) {
|
|
3429
|
+
return;
|
|
3430
|
+
}
|
|
3431
|
+
if (!this.#compiler.isObjectLiteralExpression(rootExpression)) {
|
|
3432
|
+
const origin = DiagnosticOrigin.fromJsonNode(rootExpression, sourceFile, this.#skipTrivia);
|
|
3433
|
+
this.#onDiagnostics(Diagnostic.error("The root value of a configuration file must be an object literal.", origin));
|
|
3194
3434
|
return;
|
|
3195
3435
|
}
|
|
3196
3436
|
for (const property of rootExpression.properties) {
|
|
3197
3437
|
if (this.#compiler.isPropertyAssignment(property)) {
|
|
3198
3438
|
if (!this.#isDoubleQuotedString(property.name, sourceFile)) {
|
|
3199
|
-
const origin = DiagnosticOrigin.fromJsonNode(property, sourceFile, this.#skipTrivia);
|
|
3200
|
-
this.#
|
|
3439
|
+
const origin = DiagnosticOrigin.fromJsonNode(property.name, sourceFile, this.#skipTrivia);
|
|
3440
|
+
this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.doubleQuotesExpected(), origin));
|
|
3201
3441
|
continue;
|
|
3202
3442
|
}
|
|
3203
|
-
const optionName =
|
|
3443
|
+
const optionName = property.name.text;
|
|
3204
3444
|
if (optionName === "$schema") {
|
|
3205
3445
|
continue;
|
|
3206
3446
|
}
|
|
@@ -3209,8 +3449,8 @@ class ConfigFileOptionsWorker {
|
|
|
3209
3449
|
this.#configFileOptions[optionDefinition.name] = await this.#parseOptionValue(sourceFile, property.initializer, optionDefinition);
|
|
3210
3450
|
}
|
|
3211
3451
|
else {
|
|
3212
|
-
const origin = DiagnosticOrigin.fromJsonNode(property, sourceFile, this.#skipTrivia);
|
|
3213
|
-
this.#
|
|
3452
|
+
const origin = DiagnosticOrigin.fromJsonNode(property.name, sourceFile, this.#skipTrivia);
|
|
3453
|
+
this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.unknownOption(optionName), origin));
|
|
3214
3454
|
}
|
|
3215
3455
|
}
|
|
3216
3456
|
}
|
|
@@ -3233,7 +3473,7 @@ class ConfigFileOptionsWorker {
|
|
|
3233
3473
|
case this.#compiler.SyntaxKind.StringLiteral: {
|
|
3234
3474
|
if (!this.#isDoubleQuotedString(valueExpression, sourceFile)) {
|
|
3235
3475
|
const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
|
|
3236
|
-
this.#
|
|
3476
|
+
this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.doubleQuotesExpected(), origin));
|
|
3237
3477
|
return;
|
|
3238
3478
|
}
|
|
3239
3479
|
if (optionDefinition.brand === "string") {
|
|
@@ -3262,15 +3502,9 @@ class ConfigFileOptionsWorker {
|
|
|
3262
3502
|
? ConfigDiagnosticText.expectsListItemType(optionDefinition.name, optionDefinition.brand)
|
|
3263
3503
|
: ConfigDiagnosticText.requiresValueType(optionDefinition.name, optionDefinition.brand, this.#optionGroup);
|
|
3264
3504
|
const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
|
|
3265
|
-
this.#
|
|
3505
|
+
this.#onDiagnostics(Diagnostic.error(text, origin));
|
|
3266
3506
|
return;
|
|
3267
3507
|
}
|
|
3268
|
-
#resolvePropertyName({ name }) {
|
|
3269
|
-
if ("text" in name) {
|
|
3270
|
-
return name.text;
|
|
3271
|
-
}
|
|
3272
|
-
return "";
|
|
3273
|
-
}
|
|
3274
3508
|
#skipTrivia(position, sourceFile) {
|
|
3275
3509
|
const { text } = sourceFile.getSourceFile();
|
|
3276
3510
|
while (position < text.length) {
|
|
@@ -3312,7 +3546,7 @@ class ConfigFileOptionsWorker {
|
|
|
3312
3546
|
const defaultOptions = {
|
|
3313
3547
|
failFast: false,
|
|
3314
3548
|
rootPath: "./",
|
|
3315
|
-
target: [Environment.typescriptPath
|
|
3549
|
+
target: [Environment.typescriptPath != null ? "current" : "latest"],
|
|
3316
3550
|
testFileMatch: ["**/*.tst.*", "**/__typetests__/*.test.*", "**/typetests/*.test.*"],
|
|
3317
3551
|
};
|
|
3318
3552
|
class ConfigService {
|
|
@@ -3326,13 +3560,13 @@ class ConfigService {
|
|
|
3326
3560
|
this.#compiler = compiler;
|
|
3327
3561
|
this.#storeService = storeService;
|
|
3328
3562
|
}
|
|
3329
|
-
#
|
|
3330
|
-
EventEmitter.dispatch(["config:error", { diagnostics: [
|
|
3563
|
+
#onDiagnostics(diagnostics) {
|
|
3564
|
+
EventEmitter.dispatch(["config:error", { diagnostics: Array.isArray(diagnostics) ? diagnostics : [diagnostics] }]);
|
|
3331
3565
|
}
|
|
3332
3566
|
async parseCommandLine(commandLineArgs) {
|
|
3333
3567
|
this.#commandLineOptions = {};
|
|
3334
3568
|
this.#pathMatch = [];
|
|
3335
|
-
const commandLineWorker = new CommandLineOptionsWorker(this.#commandLineOptions, this.#pathMatch, this.#storeService, this.#
|
|
3569
|
+
const commandLineWorker = new CommandLineOptionsWorker(this.#commandLineOptions, this.#pathMatch, this.#storeService, this.#onDiagnostics);
|
|
3336
3570
|
await commandLineWorker.parse(commandLineArgs);
|
|
3337
3571
|
if (this.#commandLineOptions.config != null) {
|
|
3338
3572
|
this.#configFilePath = this.#commandLineOptions.config;
|
|
@@ -3349,7 +3583,7 @@ class ConfigService {
|
|
|
3349
3583
|
const configFileText = await fs.readFile(this.#configFilePath, {
|
|
3350
3584
|
encoding: "utf8",
|
|
3351
3585
|
});
|
|
3352
|
-
const configFileWorker = new ConfigFileOptionsWorker(this.#compiler, this.#configFileOptions, this.#configFilePath, this.#storeService, this.#
|
|
3586
|
+
const configFileWorker = new ConfigFileOptionsWorker(this.#compiler, this.#configFileOptions, this.#configFilePath, this.#storeService, this.#onDiagnostics);
|
|
3353
3587
|
await configFileWorker.parse(configFileText);
|
|
3354
3588
|
}
|
|
3355
3589
|
resolveConfig() {
|
|
@@ -3395,14 +3629,14 @@ class StoreDiagnosticText {
|
|
|
3395
3629
|
class ManifestWorker {
|
|
3396
3630
|
#manifestFileName = "store-manifest.json";
|
|
3397
3631
|
#manifestFilePath;
|
|
3398
|
-
#
|
|
3632
|
+
#onDiagnostics;
|
|
3399
3633
|
#registryUrl = new URL("https://registry.npmjs.org");
|
|
3400
3634
|
#storePath;
|
|
3401
3635
|
#timeout = Environment.timeout * 1000;
|
|
3402
3636
|
#version = "1";
|
|
3403
|
-
constructor(storePath,
|
|
3637
|
+
constructor(storePath, onDiagnostics) {
|
|
3404
3638
|
this.#storePath = storePath;
|
|
3405
|
-
this.#
|
|
3639
|
+
this.#onDiagnostics = onDiagnostics;
|
|
3406
3640
|
this.#manifestFilePath = Path.join(storePath, this.#manifestFileName);
|
|
3407
3641
|
}
|
|
3408
3642
|
async #create() {
|
|
@@ -3432,7 +3666,7 @@ class ManifestWorker {
|
|
|
3432
3666
|
signal: AbortSignal.timeout(this.#timeout),
|
|
3433
3667
|
});
|
|
3434
3668
|
if (!response.ok) {
|
|
3435
|
-
this.#
|
|
3669
|
+
this.#onDiagnostics(Diagnostic.error([
|
|
3436
3670
|
StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
|
|
3437
3671
|
StoreDiagnosticText.failedWithStatusCode(response.status),
|
|
3438
3672
|
]));
|
|
@@ -3445,13 +3679,13 @@ class ManifestWorker {
|
|
|
3445
3679
|
return;
|
|
3446
3680
|
}
|
|
3447
3681
|
if (error instanceof Error && error.name === "TimeoutError") {
|
|
3448
|
-
this.#
|
|
3682
|
+
this.#onDiagnostics(Diagnostic.error([
|
|
3449
3683
|
StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
|
|
3450
3684
|
StoreDiagnosticText.setupTimeoutExceeded(this.#timeout),
|
|
3451
3685
|
]));
|
|
3452
3686
|
}
|
|
3453
3687
|
else {
|
|
3454
|
-
this.#
|
|
3688
|
+
this.#onDiagnostics(Diagnostic.error([
|
|
3455
3689
|
StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
|
|
3456
3690
|
StoreDiagnosticText.maybeNetworkConnectionIssue(),
|
|
3457
3691
|
]));
|
|
@@ -3486,9 +3720,9 @@ class ManifestWorker {
|
|
|
3486
3720
|
manifestText = await fs.readFile(this.#manifestFilePath, { encoding: "utf8" });
|
|
3487
3721
|
}
|
|
3488
3722
|
catch (error) {
|
|
3489
|
-
this.#
|
|
3723
|
+
this.#onDiagnostics(Diagnostic.fromError("Failed to open store manifest.", error));
|
|
3490
3724
|
}
|
|
3491
|
-
if (manifestText
|
|
3725
|
+
if (!manifestText) {
|
|
3492
3726
|
return;
|
|
3493
3727
|
}
|
|
3494
3728
|
try {
|
|
@@ -3496,7 +3730,7 @@ class ManifestWorker {
|
|
|
3496
3730
|
}
|
|
3497
3731
|
catch {
|
|
3498
3732
|
}
|
|
3499
|
-
if (manifest
|
|
3733
|
+
if (!manifest || manifest.$version !== this.#version) {
|
|
3500
3734
|
await fs.rm(this.#storePath, { force: true, recursive: true });
|
|
3501
3735
|
return this.#create();
|
|
3502
3736
|
}
|
|
@@ -3536,7 +3770,7 @@ class Lock {
|
|
|
3536
3770
|
if (!isLocked) {
|
|
3537
3771
|
return isLocked;
|
|
3538
3772
|
}
|
|
3539
|
-
if (options?.timeout
|
|
3773
|
+
if (!options?.timeout) {
|
|
3540
3774
|
return isLocked;
|
|
3541
3775
|
}
|
|
3542
3776
|
const waitStartTime = Date.now();
|
|
@@ -3545,7 +3779,7 @@ class Lock {
|
|
|
3545
3779
|
break;
|
|
3546
3780
|
}
|
|
3547
3781
|
if (Date.now() - waitStartTime > options.timeout) {
|
|
3548
|
-
options.
|
|
3782
|
+
options.onDiagnostics?.(`Lock wait timeout of ${String(options.timeout / 1000)}s was exceeded.`);
|
|
3549
3783
|
break;
|
|
3550
3784
|
}
|
|
3551
3785
|
await Lock.#sleep(1000);
|
|
@@ -3562,13 +3796,13 @@ class Lock {
|
|
|
3562
3796
|
}
|
|
3563
3797
|
|
|
3564
3798
|
class PackageInstaller {
|
|
3565
|
-
#
|
|
3799
|
+
#onDiagnostics;
|
|
3566
3800
|
#readyFileName = "__ready__";
|
|
3567
3801
|
#storePath;
|
|
3568
3802
|
#timeout = Environment.timeout * 1000;
|
|
3569
|
-
constructor(storePath,
|
|
3803
|
+
constructor(storePath, onDiagnostics) {
|
|
3570
3804
|
this.#storePath = storePath;
|
|
3571
|
-
this.#
|
|
3805
|
+
this.#onDiagnostics = onDiagnostics;
|
|
3572
3806
|
}
|
|
3573
3807
|
async ensure(compilerVersion, cancellationToken) {
|
|
3574
3808
|
const installationPath = Path.join(this.#storePath, compilerVersion);
|
|
@@ -3579,8 +3813,8 @@ class PackageInstaller {
|
|
|
3579
3813
|
}
|
|
3580
3814
|
if (await Lock.isLocked(installationPath, {
|
|
3581
3815
|
cancellationToken,
|
|
3582
|
-
|
|
3583
|
-
this.#
|
|
3816
|
+
onDiagnostics: (text) => {
|
|
3817
|
+
this.#onDiagnostics(Diagnostic.error([`Failed to install 'typescript@${compilerVersion}'.`, text]));
|
|
3584
3818
|
},
|
|
3585
3819
|
timeout: this.#timeout,
|
|
3586
3820
|
})) {
|
|
@@ -3606,7 +3840,7 @@ class PackageInstaller {
|
|
|
3606
3840
|
return modulePath;
|
|
3607
3841
|
}
|
|
3608
3842
|
catch (error) {
|
|
3609
|
-
this.#
|
|
3843
|
+
this.#onDiagnostics(Diagnostic.fromError(`Failed to install 'typescript@${compilerVersion}'.`, error));
|
|
3610
3844
|
}
|
|
3611
3845
|
finally {
|
|
3612
3846
|
lock.release();
|
|
@@ -3646,8 +3880,8 @@ class StoreService {
|
|
|
3646
3880
|
#storePath;
|
|
3647
3881
|
constructor() {
|
|
3648
3882
|
this.#storePath = Environment.storePath;
|
|
3649
|
-
this.#packageInstaller = new PackageInstaller(this.#storePath, this.#
|
|
3650
|
-
this.#manifestWorker = new ManifestWorker(this.#storePath, this.#
|
|
3883
|
+
this.#packageInstaller = new PackageInstaller(this.#storePath, this.#onDiagnostics);
|
|
3884
|
+
this.#manifestWorker = new ManifestWorker(this.#storePath, this.#onDiagnostics);
|
|
3651
3885
|
}
|
|
3652
3886
|
async getSupportedTags() {
|
|
3653
3887
|
await this.open();
|
|
@@ -3660,9 +3894,9 @@ class StoreService {
|
|
|
3660
3894
|
if (tag === "current") {
|
|
3661
3895
|
return;
|
|
3662
3896
|
}
|
|
3663
|
-
const version = await this
|
|
3664
|
-
if (version
|
|
3665
|
-
this.#
|
|
3897
|
+
const version = await this.#resolveTag(tag);
|
|
3898
|
+
if (!version) {
|
|
3899
|
+
this.#onDiagnostics(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
|
|
3666
3900
|
return;
|
|
3667
3901
|
}
|
|
3668
3902
|
return this.#packageInstaller.ensure(version, cancellationToken);
|
|
@@ -3677,9 +3911,9 @@ class StoreService {
|
|
|
3677
3911
|
modulePath = Environment.typescriptPath;
|
|
3678
3912
|
}
|
|
3679
3913
|
else {
|
|
3680
|
-
const version = await this
|
|
3681
|
-
if (version
|
|
3682
|
-
this.#
|
|
3914
|
+
const version = await this.#resolveTag(tag);
|
|
3915
|
+
if (!version) {
|
|
3916
|
+
this.#onDiagnostics(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
|
|
3683
3917
|
return;
|
|
3684
3918
|
}
|
|
3685
3919
|
compilerInstance = this.#compilerInstanceCache.get(version);
|
|
@@ -3715,7 +3949,7 @@ class StoreService {
|
|
|
3715
3949
|
}
|
|
3716
3950
|
return module.exports;
|
|
3717
3951
|
}
|
|
3718
|
-
#
|
|
3952
|
+
#onDiagnostics(diagnostic) {
|
|
3719
3953
|
EventEmitter.dispatch(["store:error", { diagnostics: [diagnostic] }]);
|
|
3720
3954
|
}
|
|
3721
3955
|
async open() {
|
|
@@ -3724,10 +3958,7 @@ class StoreService {
|
|
|
3724
3958
|
}
|
|
3725
3959
|
this.#manifest = await this.#manifestWorker.open();
|
|
3726
3960
|
}
|
|
3727
|
-
async resolveTag(tag) {
|
|
3728
|
-
if (tag === "current") {
|
|
3729
|
-
return tag;
|
|
3730
|
-
}
|
|
3961
|
+
async #resolveTag(tag) {
|
|
3731
3962
|
await this.open();
|
|
3732
3963
|
if (!this.#manifest) {
|
|
3733
3964
|
return;
|
|
@@ -3752,7 +3983,7 @@ class StoreService {
|
|
|
3752
3983
|
(!Version.isVersionTag(tag) ||
|
|
3753
3984
|
(this.#manifest.resolutions["latest"] != null &&
|
|
3754
3985
|
Version.isGreaterThan(tag, this.#manifest.resolutions["latest"])))) {
|
|
3755
|
-
this.#
|
|
3986
|
+
this.#onDiagnostics(Diagnostic.warning([
|
|
3756
3987
|
"Failed to update metadata of the 'typescript' package from the registry.",
|
|
3757
3988
|
`The resolution of the '${tag}' tag may be outdated.`,
|
|
3758
3989
|
]));
|
|
@@ -3789,7 +4020,7 @@ class Cli {
|
|
|
3789
4020
|
await this.#storeService.update();
|
|
3790
4021
|
return;
|
|
3791
4022
|
}
|
|
3792
|
-
const compiler = await this.#storeService.load(Environment.typescriptPath
|
|
4023
|
+
const compiler = await this.#storeService.load(Environment.typescriptPath != null ? "current" : "latest");
|
|
3793
4024
|
if (!compiler) {
|
|
3794
4025
|
return;
|
|
3795
4026
|
}
|
|
@@ -3855,28 +4086,33 @@ class Cli {
|
|
|
3855
4086
|
this.#eventEmitter.removeHandlers();
|
|
3856
4087
|
}
|
|
3857
4088
|
#waitForChangedFiles(resolvedConfig, selectService, cancellationToken) {
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
watcher
|
|
3865
|
-
|
|
3866
|
-
};
|
|
3867
|
-
watchers.push(new FileWatcher(resolvedConfig.configFilePath, onChanged));
|
|
3868
|
-
if (selectService != null) {
|
|
3869
|
-
const onChangedTestFile = (filePath) => {
|
|
3870
|
-
if (selectService.isTestFile(filePath)) {
|
|
3871
|
-
onChanged();
|
|
4089
|
+
return new Promise((resolve) => {
|
|
4090
|
+
const watchers = [];
|
|
4091
|
+
cancellationToken.reset();
|
|
4092
|
+
this.#outputService.writeMessage(waitingForFileChangesText());
|
|
4093
|
+
const onChanged = () => {
|
|
4094
|
+
cancellationToken.cancel("configChange");
|
|
4095
|
+
for (const watcher of watchers) {
|
|
4096
|
+
watcher.close();
|
|
3872
4097
|
}
|
|
4098
|
+
resolve();
|
|
3873
4099
|
};
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
4100
|
+
watchers.push(new FileWatcher(resolvedConfig.configFilePath, onChanged));
|
|
4101
|
+
if (selectService != null) {
|
|
4102
|
+
const onChangedTestFile = (filePath) => {
|
|
4103
|
+
if (selectService.isTestFile(filePath)) {
|
|
4104
|
+
onChanged();
|
|
4105
|
+
}
|
|
4106
|
+
};
|
|
4107
|
+
const onRemoved = () => {
|
|
4108
|
+
};
|
|
4109
|
+
watchers.push(new Watcher(resolvedConfig.rootPath, onChangedTestFile, onRemoved, { recursive: true }));
|
|
4110
|
+
}
|
|
4111
|
+
for (const watcher of watchers) {
|
|
4112
|
+
watcher.watch();
|
|
4113
|
+
}
|
|
4114
|
+
});
|
|
3879
4115
|
}
|
|
3880
4116
|
}
|
|
3881
4117
|
|
|
3882
|
-
export { Assertion, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, ConfigDiagnosticText, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Environment, EventEmitter, ExitCodeHandler,
|
|
4118
|
+
export { Assertion, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, ConfigDiagnosticText, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Environment, EventEmitter, ExitCodeHandler, ExpectResult, ExpectService, FileResult, FileWatcher, InputService, Line, OptionBrand, OptionDefinitionsMap, OptionGroup, OutputService, Path, ProjectResult, ProjectService, Result, ResultCount, ResultHandler, ResultStatus, ResultTiming, RunReporter, Scribbler, SelectDiagnosticText, SelectService, SetupReporter, StoreService, SummaryReporter, TSTyche, TargetResult, TaskRunner, TestFile, TestMember, TestMemberBrand, TestMemberFlags, TestResult, TestTree, Text, Version, WatchReporter, WatchService, Watcher, addsPackageStepText, defaultOptions, describeNameText, diagnosticText, fileStatusText, fileViewText, formattedText, helpText, summaryText, testNameText, usesCompilerStepText, waitingForFileChangesText, watchUsageText };
|