tstyche 2.0.0 → 2.1.1
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 +188 -140
- package/build/tstyche.js +904 -660
- package/package.json +19 -18
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';
|
|
@@ -505,7 +505,7 @@ class Environment {
|
|
|
505
505
|
}
|
|
506
506
|
static #resolveTimeout() {
|
|
507
507
|
if (process.env["TSTYCHE_TIMEOUT"] != null) {
|
|
508
|
-
return Number(process.env["TSTYCHE_TIMEOUT"]);
|
|
508
|
+
return Number.parseFloat(process.env["TSTYCHE_TIMEOUT"]);
|
|
509
509
|
}
|
|
510
510
|
return 30;
|
|
511
511
|
}
|
|
@@ -589,7 +589,7 @@ class Scribbler {
|
|
|
589
589
|
#visitChildren(children) {
|
|
590
590
|
const text = [];
|
|
591
591
|
for (const child of children) {
|
|
592
|
-
if (typeof child === "string") {
|
|
592
|
+
if (typeof child === "string" || typeof child === "number") {
|
|
593
593
|
text.push(child);
|
|
594
594
|
continue;
|
|
595
595
|
}
|
|
@@ -613,41 +613,75 @@ 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.push(ancestor.name);
|
|
620
|
+
ancestor = ancestor.parent;
|
|
621
|
+
}
|
|
622
|
+
text.push("");
|
|
623
|
+
return jsx(Text, { color: "90", children: text.reverse().join(" ❭ ") });
|
|
624
|
+
}
|
|
625
|
+
function CodeLineText({ gutterWidth, lineNumber, lineNumberColor = "90", lineText }) {
|
|
626
|
+
return (jsx(Line, { children: [jsx(Text, { color: lineNumberColor, children: lineNumber.toString().padStart(gutterWidth) }), jsx(Text, { color: "90", children: " | " }), lineText] }));
|
|
627
|
+
}
|
|
628
|
+
function SquiggleLineText({ gutterWidth, indentWidth = 0, squiggleColor, squiggleWidth }) {
|
|
629
|
+
return (jsx(Line, { children: [" ".repeat(gutterWidth), jsx(Text, { color: "90", children: " | " }), " ".repeat(indentWidth), jsx(Text, { color: squiggleColor, children: "~".repeat(squiggleWidth === 0 ? 1 : squiggleWidth) })] }));
|
|
630
|
+
}
|
|
631
|
+
function CodeSpanText({ diagnosticCategory, diagnosticOrigin }) {
|
|
617
632
|
const lastLineInFile = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.sourceFile.text.length).line;
|
|
618
|
-
const { character:
|
|
619
|
-
const
|
|
633
|
+
const { character: firstMarkedLineCharacter, line: firstMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.start);
|
|
634
|
+
const { character: lastMarkedLineCharacter, line: lastMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.end);
|
|
635
|
+
const firstLine = Math.max(firstMarkedLine - 2, 0);
|
|
620
636
|
const lastLine = Math.min(firstLine + 5, lastLineInFile);
|
|
621
|
-
const
|
|
637
|
+
const gutterWidth = (lastLine + 1).toString().length + 2;
|
|
638
|
+
let highlightColor;
|
|
639
|
+
switch (diagnosticCategory) {
|
|
640
|
+
case "error": {
|
|
641
|
+
highlightColor = "31";
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
case "warning": {
|
|
645
|
+
highlightColor = "33";
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
622
649
|
const codeSpan = [];
|
|
623
650
|
for (let index = firstLine; index <= lastLine; index++) {
|
|
624
651
|
const lineStart = diagnosticOrigin.sourceFile.getPositionOfLineAndCharacter(index, 0);
|
|
625
652
|
const lineEnd = index === lastLineInFile
|
|
626
653
|
? diagnosticOrigin.sourceFile.text.length
|
|
627
654
|
: diagnosticOrigin.sourceFile.getPositionOfLineAndCharacter(index + 1, 0);
|
|
628
|
-
const lineNumberText = String(index + 1);
|
|
629
655
|
const lineText = diagnosticOrigin.sourceFile.text.slice(lineStart, lineEnd).trimEnd().replace(/\t/g, " ");
|
|
630
|
-
if (index
|
|
631
|
-
codeSpan.push(jsx(
|
|
656
|
+
if (index >= firstMarkedLine && index <= lastMarkedLine) {
|
|
657
|
+
codeSpan.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumber: index + 1, lineNumberColor: highlightColor, lineText: lineText }));
|
|
658
|
+
if (index === firstMarkedLine) {
|
|
659
|
+
const squiggleLength = index === lastMarkedLine
|
|
660
|
+
? lastMarkedLineCharacter - firstMarkedLineCharacter
|
|
661
|
+
: lineText.length - firstMarkedLineCharacter;
|
|
662
|
+
codeSpan.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, indentWidth: firstMarkedLineCharacter, squiggleColor: highlightColor, squiggleWidth: squiggleLength }));
|
|
663
|
+
}
|
|
664
|
+
else if (index === lastMarkedLine) {
|
|
665
|
+
codeSpan.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleColor: highlightColor, squiggleWidth: lastMarkedLineCharacter }));
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
codeSpan.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleColor: highlightColor, squiggleWidth: lineText.length }));
|
|
669
|
+
}
|
|
632
670
|
}
|
|
633
671
|
else {
|
|
634
|
-
codeSpan.push(jsx(
|
|
672
|
+
codeSpan.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumber: index + 1, lineText: lineText }));
|
|
635
673
|
}
|
|
636
674
|
}
|
|
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] }));
|
|
675
|
+
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: `:${firstMarkedLine + 1}:${firstMarkedLineCharacter + 1}` }), diagnosticOrigin.assertion && jsx(BreadcrumbsText, { ancestor: diagnosticOrigin.assertion.parent })] }));
|
|
642
676
|
return (jsx(Text, { children: [codeSpan, jsx(Line, {}), location] }));
|
|
643
677
|
}
|
|
644
678
|
|
|
645
679
|
function DiagnosticText({ diagnostic }) {
|
|
646
|
-
const code =
|
|
680
|
+
const code = diagnostic.code ? jsx(Text, { color: "90", children: [" ", diagnostic.code] }) : undefined;
|
|
647
681
|
const text = Array.isArray(diagnostic.text) ? diagnostic.text : [diagnostic.text];
|
|
648
682
|
const message = text.map((text, index) => (jsx(Text, { children: [index === 1 ? jsx(Line, {}) : undefined, jsx(Line, { children: [text, code] })] })));
|
|
649
683
|
const related = diagnostic.related?.map((relatedDiagnostic) => jsx(DiagnosticText, { diagnostic: relatedDiagnostic }));
|
|
650
|
-
const codeSpan = diagnostic.origin ? (jsx(Text, { children: [jsx(Line, {}), jsx(CodeSpanText, {
|
|
684
|
+
const codeSpan = diagnostic.origin ? (jsx(Text, { children: [jsx(Line, {}), jsx(CodeSpanText, { diagnosticCategory: diagnostic.category, diagnosticOrigin: diagnostic.origin })] })) : undefined;
|
|
651
685
|
return (jsx(Text, { children: [message, codeSpan, jsx(Line, {}), jsx(Text, { indent: 2, children: related })] }));
|
|
652
686
|
}
|
|
653
687
|
function diagnosticText(diagnostic) {
|
|
@@ -724,11 +758,7 @@ function HelpHeaderText({ tstycheVersion }) {
|
|
|
724
758
|
return (jsx(Line, { children: ["The TSTyche Type Test Runner", jsx(HintText, { children: tstycheVersion })] }));
|
|
725
759
|
}
|
|
726
760
|
function CommandText({ hint, text }) {
|
|
727
|
-
|
|
728
|
-
if (hint != null) {
|
|
729
|
-
hintText = jsx(HintText, { children: hint });
|
|
730
|
-
}
|
|
731
|
-
return (jsx(Line, { indent: 1, children: [jsx(Text, { color: "34", children: text }), hintText] }));
|
|
761
|
+
return (jsx(Line, { indent: 1, children: [jsx(Text, { color: "34", children: text }), hint && jsx(HintText, { children: hint })] }));
|
|
732
762
|
}
|
|
733
763
|
function OptionDescriptionText({ text }) {
|
|
734
764
|
return jsx(Line, { indent: 1, children: text });
|
|
@@ -743,11 +773,11 @@ function CommandLineUsageText() {
|
|
|
743
773
|
return jsx(Text, { children: usageText });
|
|
744
774
|
}
|
|
745
775
|
function CommandLineOptionNameText({ text }) {
|
|
746
|
-
return jsx(Text, { children:
|
|
776
|
+
return jsx(Text, { children: `--${text}` });
|
|
747
777
|
}
|
|
748
778
|
function CommandLineOptionHintText({ definition }) {
|
|
749
779
|
if (definition.brand === "list") {
|
|
750
|
-
return
|
|
780
|
+
return jsx(Text, { children: `${definition.brand} of ${definition.items.brand}s` });
|
|
751
781
|
}
|
|
752
782
|
return jsx(Text, { children: definition.brand });
|
|
753
783
|
}
|
|
@@ -790,21 +820,21 @@ class OutputService {
|
|
|
790
820
|
eraseLastLine() {
|
|
791
821
|
this.#stdout.write("\u001B[1A\u001B[0K");
|
|
792
822
|
}
|
|
793
|
-
#
|
|
794
|
-
const elements = Array.isArray(
|
|
823
|
+
#writeTo(stream, element) {
|
|
824
|
+
const elements = Array.isArray(element) ? element : [element];
|
|
795
825
|
for (const element of elements) {
|
|
796
826
|
stream.write(this.#scribbler.render(element));
|
|
797
827
|
}
|
|
798
828
|
this.#isClear = false;
|
|
799
829
|
}
|
|
800
|
-
writeError(
|
|
801
|
-
this.#
|
|
830
|
+
writeError(element) {
|
|
831
|
+
this.#writeTo(this.#stderr, element);
|
|
802
832
|
}
|
|
803
|
-
writeMessage(
|
|
804
|
-
this.#
|
|
833
|
+
writeMessage(element) {
|
|
834
|
+
this.#writeTo(this.#stdout, element);
|
|
805
835
|
}
|
|
806
|
-
writeWarning(
|
|
807
|
-
this.#
|
|
836
|
+
writeWarning(element) {
|
|
837
|
+
this.#writeTo(this.#stderr, element);
|
|
808
838
|
}
|
|
809
839
|
}
|
|
810
840
|
|
|
@@ -812,18 +842,16 @@ function RowText({ label, text }) {
|
|
|
812
842
|
return (jsx(Line, { children: [`${label}:`.padEnd(12), text] }));
|
|
813
843
|
}
|
|
814
844
|
function CountText({ failed, passed, skipped, todo, total }) {
|
|
815
|
-
return (jsx(Text, { children: [failed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "31", children: [
|
|
845
|
+
return (jsx(Text, { children: [failed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "31", children: [failed, " failed"] }), jsx(Text, { children: ", " })] })) : undefined, skipped > 0 ? (jsx(Text, { children: [jsx(Text, { color: "33", children: [skipped, " skipped"] }), jsx(Text, { children: ", " })] })) : undefined, todo > 0 ? (jsx(Text, { children: [jsx(Text, { color: "35", children: [todo, " todo"] }), jsx(Text, { children: ", " })] })) : undefined, passed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "32", children: [passed, " passed"] }), jsx(Text, { children: ", " })] })) : undefined, jsx(Text, { children: [total, " total"] })] }));
|
|
816
846
|
}
|
|
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`] }));
|
|
847
|
+
function DurationText({ seconds }) {
|
|
848
|
+
return jsx(Text, { children: `${Math.round(seconds * 10) / 10}s` });
|
|
821
849
|
}
|
|
822
850
|
function MatchText({ text }) {
|
|
823
851
|
if (typeof text === "string") {
|
|
824
852
|
return jsx(Text, { children: ["'", text, "'"] });
|
|
825
853
|
}
|
|
826
|
-
if (text.length
|
|
854
|
+
if (text.length === 1) {
|
|
827
855
|
return jsx(Text, { children: ["'", ...text, "'"] });
|
|
828
856
|
}
|
|
829
857
|
const lastItem = text.pop();
|
|
@@ -835,7 +863,7 @@ function RanFilesText({ onlyMatch, pathMatch, skipMatch }) {
|
|
|
835
863
|
testNameMatchText.push(jsx(Text, { children: [jsx(Text, { color: "90", children: "matching " }), jsx(MatchText, { text: onlyMatch })] }));
|
|
836
864
|
}
|
|
837
865
|
if (skipMatch != null) {
|
|
838
|
-
testNameMatchText.push(jsx(Text, { children: [onlyMatch
|
|
866
|
+
testNameMatchText.push(jsx(Text, { children: [onlyMatch && jsx(Text, { color: "90", children: " and " }), jsx(Text, { color: "90", children: "not matching " }), jsx(MatchText, { text: skipMatch })] }));
|
|
839
867
|
}
|
|
840
868
|
let pathMatchText;
|
|
841
869
|
if (pathMatch.length > 0) {
|
|
@@ -851,7 +879,7 @@ function summaryText({ duration, expectCount, fileCount, onlyMatch, pathMatch, s
|
|
|
851
879
|
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
880
|
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
881
|
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, {
|
|
882
|
+
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
883
|
}
|
|
856
884
|
|
|
857
885
|
function StatusText({ status }) {
|
|
@@ -1155,7 +1183,6 @@ class WatchReporter extends Reporter {
|
|
|
1155
1183
|
|
|
1156
1184
|
class CancellationToken {
|
|
1157
1185
|
#isCancelled = false;
|
|
1158
|
-
#handlers = new Set();
|
|
1159
1186
|
#reason;
|
|
1160
1187
|
get isCancellationRequested() {
|
|
1161
1188
|
return this.#isCancelled;
|
|
@@ -1165,16 +1192,10 @@ class CancellationToken {
|
|
|
1165
1192
|
}
|
|
1166
1193
|
cancel(reason) {
|
|
1167
1194
|
if (!this.#isCancelled) {
|
|
1168
|
-
for (const handler of this.#handlers) {
|
|
1169
|
-
handler(reason);
|
|
1170
|
-
}
|
|
1171
1195
|
this.#isCancelled = true;
|
|
1172
1196
|
this.#reason = reason;
|
|
1173
1197
|
}
|
|
1174
1198
|
}
|
|
1175
|
-
onCancellationRequested(handler) {
|
|
1176
|
-
this.#handlers.add(handler);
|
|
1177
|
-
}
|
|
1178
1199
|
reset() {
|
|
1179
1200
|
if (this.#isCancelled) {
|
|
1180
1201
|
this.#isCancelled = false;
|
|
@@ -1188,10 +1209,10 @@ var CancellationReason;
|
|
|
1188
1209
|
CancellationReason["ConfigChange"] = "configChange";
|
|
1189
1210
|
CancellationReason["ConfigError"] = "configError";
|
|
1190
1211
|
CancellationReason["FailFast"] = "failFast";
|
|
1212
|
+
CancellationReason["WatchClose"] = "watchClose";
|
|
1191
1213
|
})(CancellationReason || (CancellationReason = {}));
|
|
1192
1214
|
|
|
1193
1215
|
class Watcher {
|
|
1194
|
-
#abortController = new AbortController();
|
|
1195
1216
|
#onChanged;
|
|
1196
1217
|
#onRemoved;
|
|
1197
1218
|
#recursive;
|
|
@@ -1204,34 +1225,28 @@ class Watcher {
|
|
|
1204
1225
|
this.#recursive = options?.recursive;
|
|
1205
1226
|
}
|
|
1206
1227
|
close() {
|
|
1207
|
-
this.#
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
this.#watcher =
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
if (
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
else {
|
|
1219
|
-
await this.#onRemoved(filePath);
|
|
1220
|
-
}
|
|
1228
|
+
this.#watcher?.close();
|
|
1229
|
+
}
|
|
1230
|
+
watch() {
|
|
1231
|
+
this.#watcher = watch(this.#targetPath, { recursive: this.#recursive }, (_eventType, fileName) => {
|
|
1232
|
+
if (fileName != null) {
|
|
1233
|
+
const filePath = Path.resolve(this.#targetPath, fileName);
|
|
1234
|
+
if (existsSync(filePath)) {
|
|
1235
|
+
this.#onChanged(filePath);
|
|
1236
|
+
}
|
|
1237
|
+
else {
|
|
1238
|
+
this.#onRemoved(filePath);
|
|
1221
1239
|
}
|
|
1222
1240
|
}
|
|
1223
|
-
}
|
|
1224
|
-
catch (error) {
|
|
1225
|
-
if (error instanceof Error && error.name === "AbortError") ;
|
|
1226
|
-
}
|
|
1241
|
+
});
|
|
1227
1242
|
}
|
|
1228
1243
|
}
|
|
1229
1244
|
|
|
1230
1245
|
class FileWatcher extends Watcher {
|
|
1231
1246
|
constructor(targetPath, onChanged) {
|
|
1232
|
-
const onChangedFile =
|
|
1247
|
+
const onChangedFile = (filePath) => {
|
|
1233
1248
|
if (filePath === targetPath) {
|
|
1234
|
-
|
|
1249
|
+
onChanged();
|
|
1235
1250
|
}
|
|
1236
1251
|
};
|
|
1237
1252
|
super(Path.dirname(targetPath), onChangedFile);
|
|
@@ -1239,29 +1254,33 @@ class FileWatcher extends Watcher {
|
|
|
1239
1254
|
}
|
|
1240
1255
|
|
|
1241
1256
|
class DiagnosticOrigin {
|
|
1242
|
-
|
|
1257
|
+
assertion;
|
|
1243
1258
|
end;
|
|
1244
1259
|
sourceFile;
|
|
1245
1260
|
start;
|
|
1246
|
-
constructor(start, end, sourceFile,
|
|
1261
|
+
constructor(start, end, sourceFile, assertion) {
|
|
1247
1262
|
this.start = start;
|
|
1248
1263
|
this.end = end;
|
|
1249
1264
|
this.sourceFile = sourceFile;
|
|
1250
|
-
this.
|
|
1265
|
+
this.assertion = assertion;
|
|
1266
|
+
}
|
|
1267
|
+
static fromAssertion(assertion) {
|
|
1268
|
+
const node = assertion.matcherName;
|
|
1269
|
+
return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertion);
|
|
1251
1270
|
}
|
|
1252
1271
|
static fromJsonNode(node, sourceFile, skipTrivia) {
|
|
1253
1272
|
return new DiagnosticOrigin(skipTrivia(node.pos, sourceFile), node.end, sourceFile);
|
|
1254
1273
|
}
|
|
1255
|
-
static fromNode(node,
|
|
1256
|
-
return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(),
|
|
1274
|
+
static fromNode(node, assertion) {
|
|
1275
|
+
return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertion);
|
|
1257
1276
|
}
|
|
1258
1277
|
}
|
|
1259
1278
|
|
|
1260
1279
|
class Diagnostic {
|
|
1261
1280
|
category;
|
|
1262
1281
|
code;
|
|
1263
|
-
related;
|
|
1264
1282
|
origin;
|
|
1283
|
+
related;
|
|
1265
1284
|
text;
|
|
1266
1285
|
constructor(text, category, origin) {
|
|
1267
1286
|
this.text = text;
|
|
@@ -1272,9 +1291,6 @@ class Diagnostic {
|
|
|
1272
1291
|
if (options.code != null) {
|
|
1273
1292
|
this.code = options.code;
|
|
1274
1293
|
}
|
|
1275
|
-
if (options.origin != null) {
|
|
1276
|
-
this.origin = options.origin;
|
|
1277
|
-
}
|
|
1278
1294
|
if (options.related != null) {
|
|
1279
1295
|
this.related = options.related;
|
|
1280
1296
|
}
|
|
@@ -1283,16 +1299,22 @@ class Diagnostic {
|
|
|
1283
1299
|
static error(text, origin) {
|
|
1284
1300
|
return new Diagnostic(text, "error", origin);
|
|
1285
1301
|
}
|
|
1302
|
+
extendWith(text, origin) {
|
|
1303
|
+
return new Diagnostic([this.text, text].flat(), this.category, origin ?? this.origin);
|
|
1304
|
+
}
|
|
1286
1305
|
static fromDiagnostics(diagnostics, compiler) {
|
|
1287
1306
|
return diagnostics.map((diagnostic) => {
|
|
1288
|
-
const
|
|
1289
|
-
const code = `ts(${String(diagnostic.code)})`;
|
|
1307
|
+
const code = `ts(${diagnostic.code})`;
|
|
1290
1308
|
let origin;
|
|
1291
|
-
|
|
1292
|
-
if (Diagnostic.isTsDiagnosticWithLocation(diagnostic)) {
|
|
1309
|
+
if (Diagnostic.#isTsDiagnosticWithLocation(diagnostic)) {
|
|
1293
1310
|
origin = new DiagnosticOrigin(diagnostic.start, diagnostic.start + diagnostic.length, diagnostic.file);
|
|
1294
1311
|
}
|
|
1295
|
-
|
|
1312
|
+
let related;
|
|
1313
|
+
if (diagnostic.relatedInformation != null) {
|
|
1314
|
+
related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation, compiler);
|
|
1315
|
+
}
|
|
1316
|
+
const text = compiler.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
1317
|
+
return new Diagnostic(text, "error", origin).add({ code, related });
|
|
1296
1318
|
});
|
|
1297
1319
|
}
|
|
1298
1320
|
static fromError(text, error) {
|
|
@@ -1306,7 +1328,7 @@ class Diagnostic {
|
|
|
1306
1328
|
}
|
|
1307
1329
|
return Diagnostic.error(messageText);
|
|
1308
1330
|
}
|
|
1309
|
-
static isTsDiagnosticWithLocation(diagnostic) {
|
|
1331
|
+
static #isTsDiagnosticWithLocation(diagnostic) {
|
|
1310
1332
|
return diagnostic.file != null && diagnostic.start != null && diagnostic.length != null;
|
|
1311
1333
|
}
|
|
1312
1334
|
static warning(text, origin) {
|
|
@@ -1428,7 +1450,7 @@ class SelectService {
|
|
|
1428
1450
|
isTestFile(filePath) {
|
|
1429
1451
|
return this.#isFileIncluded(Path.relative(this.#resolvedConfig.rootPath, filePath));
|
|
1430
1452
|
}
|
|
1431
|
-
#
|
|
1453
|
+
#onDiagnostics(diagnostic) {
|
|
1432
1454
|
EventEmitter.dispatch(["select:error", { diagnostics: [diagnostic] }]);
|
|
1433
1455
|
}
|
|
1434
1456
|
async selectFiles() {
|
|
@@ -1436,18 +1458,21 @@ class SelectService {
|
|
|
1436
1458
|
const testFilePaths = [];
|
|
1437
1459
|
await this.#visitDirectory(currentPath, testFilePaths);
|
|
1438
1460
|
if (testFilePaths.length === 0) {
|
|
1439
|
-
this.#
|
|
1461
|
+
this.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereSelected(this.#resolvedConfig)));
|
|
1440
1462
|
}
|
|
1441
1463
|
return testFilePaths.sort();
|
|
1442
1464
|
}
|
|
1443
1465
|
async #visitDirectory(currentPath, testFilePaths) {
|
|
1444
1466
|
const targetPath = Path.join(this.#resolvedConfig.rootPath, currentPath);
|
|
1445
|
-
let entries
|
|
1467
|
+
let entries;
|
|
1446
1468
|
try {
|
|
1447
1469
|
entries = await fs.readdir(targetPath, { withFileTypes: true });
|
|
1448
1470
|
}
|
|
1449
1471
|
catch {
|
|
1450
1472
|
}
|
|
1473
|
+
if (!entries) {
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1451
1476
|
for (const entry of entries) {
|
|
1452
1477
|
let entryMeta;
|
|
1453
1478
|
if (entry.isSymbolicLink()) {
|
|
@@ -1455,12 +1480,14 @@ class SelectService {
|
|
|
1455
1480
|
entryMeta = await fs.stat([targetPath, entry.name].join("/"));
|
|
1456
1481
|
}
|
|
1457
1482
|
catch {
|
|
1458
|
-
continue;
|
|
1459
1483
|
}
|
|
1460
1484
|
}
|
|
1461
1485
|
else {
|
|
1462
1486
|
entryMeta = entry;
|
|
1463
1487
|
}
|
|
1488
|
+
if (!entryMeta) {
|
|
1489
|
+
continue;
|
|
1490
|
+
}
|
|
1464
1491
|
const entryPath = [currentPath, entry.name].join("/");
|
|
1465
1492
|
if (entryMeta.isDirectory() && this.#isDirectoryIncluded(entryPath)) {
|
|
1466
1493
|
await this.#visitDirectory(entryPath, testFilePaths);
|
|
@@ -1473,16 +1500,30 @@ class SelectService {
|
|
|
1473
1500
|
}
|
|
1474
1501
|
}
|
|
1475
1502
|
|
|
1476
|
-
class
|
|
1503
|
+
class Debounce {
|
|
1504
|
+
#delay;
|
|
1505
|
+
#onResolve;
|
|
1506
|
+
#resolve;
|
|
1477
1507
|
#timeout;
|
|
1478
|
-
|
|
1508
|
+
constructor(delay, onResolve) {
|
|
1509
|
+
this.#delay = delay;
|
|
1510
|
+
this.#onResolve = onResolve;
|
|
1511
|
+
}
|
|
1512
|
+
clearTimeout() {
|
|
1479
1513
|
clearTimeout(this.#timeout);
|
|
1480
1514
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1515
|
+
refreshTimeout() {
|
|
1516
|
+
this.clearTimeout();
|
|
1517
|
+
this.#timeout = setTimeout(() => {
|
|
1518
|
+
this.#resolve?.(this.#onResolve());
|
|
1519
|
+
}, this.#delay);
|
|
1520
|
+
}
|
|
1521
|
+
resolveWith(value) {
|
|
1522
|
+
this.#resolve?.(value);
|
|
1523
|
+
}
|
|
1524
|
+
setup() {
|
|
1525
|
+
return new Promise((resolve) => {
|
|
1526
|
+
this.#resolve = resolve;
|
|
1486
1527
|
});
|
|
1487
1528
|
}
|
|
1488
1529
|
}
|
|
@@ -1491,16 +1532,33 @@ class WatchService {
|
|
|
1491
1532
|
#changedTestFiles = new Map();
|
|
1492
1533
|
#inputService;
|
|
1493
1534
|
#resolvedConfig;
|
|
1494
|
-
#runCallback;
|
|
1495
1535
|
#selectService;
|
|
1496
|
-
#timer = new Timer();
|
|
1497
1536
|
#watchers = [];
|
|
1498
1537
|
#watchedTestFiles;
|
|
1499
|
-
constructor(resolvedConfig,
|
|
1538
|
+
constructor(resolvedConfig, selectService, testFiles) {
|
|
1500
1539
|
this.#resolvedConfig = resolvedConfig;
|
|
1501
|
-
this.#runCallback = runCallback;
|
|
1502
1540
|
this.#selectService = selectService;
|
|
1503
1541
|
this.#watchedTestFiles = new Map(testFiles.map((testFile) => [testFile.path, testFile]));
|
|
1542
|
+
}
|
|
1543
|
+
#onDiagnostics(diagnostic) {
|
|
1544
|
+
EventEmitter.dispatch(["watch:error", { diagnostics: [diagnostic] }]);
|
|
1545
|
+
}
|
|
1546
|
+
async *watch(cancellationToken) {
|
|
1547
|
+
const onResolve = () => {
|
|
1548
|
+
const testFiles = [...this.#changedTestFiles.values()];
|
|
1549
|
+
this.#changedTestFiles.clear();
|
|
1550
|
+
return testFiles;
|
|
1551
|
+
};
|
|
1552
|
+
const debounce = new Debounce(100, onResolve);
|
|
1553
|
+
const onClose = (reason) => {
|
|
1554
|
+
debounce.clearTimeout();
|
|
1555
|
+
this.#inputService?.close();
|
|
1556
|
+
for (const watcher of this.#watchers) {
|
|
1557
|
+
watcher.close();
|
|
1558
|
+
}
|
|
1559
|
+
cancellationToken.cancel(reason);
|
|
1560
|
+
debounce.resolveWith([]);
|
|
1561
|
+
};
|
|
1504
1562
|
const onInput = (chunk) => {
|
|
1505
1563
|
switch (chunk.toLowerCase()) {
|
|
1506
1564
|
case "\u0003":
|
|
@@ -1508,47 +1566,23 @@ class WatchService {
|
|
|
1508
1566
|
case "\u001B":
|
|
1509
1567
|
case "q":
|
|
1510
1568
|
case "x": {
|
|
1511
|
-
|
|
1569
|
+
onClose("watchClose");
|
|
1512
1570
|
break;
|
|
1513
1571
|
}
|
|
1514
1572
|
case "\u000D":
|
|
1515
1573
|
case "\u0020":
|
|
1516
1574
|
case "a": {
|
|
1517
|
-
|
|
1575
|
+
debounce.clearTimeout();
|
|
1576
|
+
if (this.#watchedTestFiles.size !== 0) {
|
|
1577
|
+
debounce.resolveWith([...this.#watchedTestFiles.values()]);
|
|
1578
|
+
}
|
|
1518
1579
|
break;
|
|
1519
1580
|
}
|
|
1520
1581
|
}
|
|
1521
1582
|
};
|
|
1522
1583
|
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
1584
|
const onChangedFile = (filePath) => {
|
|
1585
|
+
debounce.refreshTimeout();
|
|
1552
1586
|
let testFile = this.#watchedTestFiles.get(filePath);
|
|
1553
1587
|
if (testFile != null) {
|
|
1554
1588
|
this.#changedTestFiles.set(filePath, testFile);
|
|
@@ -1558,26 +1592,35 @@ class WatchService {
|
|
|
1558
1592
|
this.#changedTestFiles.set(filePath, testFile);
|
|
1559
1593
|
this.#watchedTestFiles.set(filePath, testFile);
|
|
1560
1594
|
}
|
|
1561
|
-
this.#runChanged();
|
|
1562
1595
|
};
|
|
1563
1596
|
const onRemovedFile = (filePath) => {
|
|
1564
1597
|
this.#changedTestFiles.delete(filePath);
|
|
1565
1598
|
this.#watchedTestFiles.delete(filePath);
|
|
1566
1599
|
if (this.#watchedTestFiles.size === 0) {
|
|
1567
|
-
|
|
1600
|
+
debounce.clearTimeout();
|
|
1601
|
+
this.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereLeft(this.#resolvedConfig)));
|
|
1568
1602
|
}
|
|
1569
1603
|
};
|
|
1570
1604
|
this.#watchers.push(new Watcher(this.#resolvedConfig.rootPath, onChangedFile, onRemovedFile, { recursive: true }));
|
|
1571
1605
|
const onChangedConfigFile = () => {
|
|
1572
|
-
|
|
1606
|
+
onClose("configChange");
|
|
1573
1607
|
};
|
|
1574
1608
|
this.#watchers.push(new FileWatcher(this.#resolvedConfig.configFilePath, onChangedConfigFile));
|
|
1575
|
-
|
|
1609
|
+
for (const watcher of this.#watchers) {
|
|
1610
|
+
watcher.watch();
|
|
1611
|
+
}
|
|
1612
|
+
while (!cancellationToken.isCancellationRequested) {
|
|
1613
|
+
const testFiles = await debounce.setup();
|
|
1614
|
+
if (testFiles.length > 0) {
|
|
1615
|
+
yield testFiles;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1576
1618
|
}
|
|
1577
1619
|
}
|
|
1578
1620
|
|
|
1579
1621
|
class TestMember {
|
|
1580
1622
|
brand;
|
|
1623
|
+
#compiler;
|
|
1581
1624
|
diagnostics = new Set();
|
|
1582
1625
|
flags;
|
|
1583
1626
|
members = [];
|
|
@@ -1586,6 +1629,7 @@ class TestMember {
|
|
|
1586
1629
|
parent;
|
|
1587
1630
|
constructor(compiler, brand, node, parent, flags) {
|
|
1588
1631
|
this.brand = brand;
|
|
1632
|
+
this.#compiler = compiler;
|
|
1589
1633
|
this.node = node;
|
|
1590
1634
|
this.parent = parent;
|
|
1591
1635
|
this.flags = flags;
|
|
@@ -1605,23 +1649,20 @@ class TestMember {
|
|
|
1605
1649
|
}
|
|
1606
1650
|
}
|
|
1607
1651
|
}
|
|
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
1652
|
validate() {
|
|
1618
1653
|
const diagnostics = [];
|
|
1619
1654
|
const getText = (node) => `'${node.expression.getText()}()' cannot be nested within '${this.node.expression.getText()}()'.`;
|
|
1655
|
+
const getParentCallExpression = (node) => {
|
|
1656
|
+
while (!this.#compiler.isCallExpression(node.parent)) {
|
|
1657
|
+
node = node.parent;
|
|
1658
|
+
}
|
|
1659
|
+
return node.parent;
|
|
1660
|
+
};
|
|
1620
1661
|
switch (this.brand) {
|
|
1621
1662
|
case "describe": {
|
|
1622
1663
|
for (const member of this.members) {
|
|
1623
1664
|
if (member.brand === "expect") {
|
|
1624
|
-
diagnostics.push(Diagnostic.error(getText(member.node), DiagnosticOrigin.fromNode(member.node)));
|
|
1665
|
+
diagnostics.push(Diagnostic.error(getText(member.node), DiagnosticOrigin.fromNode(getParentCallExpression(member.node))));
|
|
1625
1666
|
}
|
|
1626
1667
|
}
|
|
1627
1668
|
break;
|
|
@@ -1650,43 +1691,21 @@ class Assertion extends TestMember {
|
|
|
1650
1691
|
this.isNot = notNode != null;
|
|
1651
1692
|
this.matcherNode = matcherNode;
|
|
1652
1693
|
this.modifierNode = modifierNode;
|
|
1653
|
-
const argStart = this.source[0]?.getStart();
|
|
1654
|
-
const argEnd = this.source[0]?.getEnd();
|
|
1655
1694
|
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) {
|
|
1695
|
+
if (diagnostic.start != null && diagnostic.start >= this.source.pos && diagnostic.start <= this.source.end) {
|
|
1661
1696
|
this.diagnostics.add(diagnostic);
|
|
1662
1697
|
parent.diagnostics.delete(diagnostic);
|
|
1663
1698
|
}
|
|
1664
1699
|
}
|
|
1665
1700
|
}
|
|
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
1701
|
get matcherName() {
|
|
1677
1702
|
return this.matcherNode.expression.name;
|
|
1678
1703
|
}
|
|
1679
1704
|
get source() {
|
|
1680
|
-
|
|
1681
|
-
return this.node.typeArguments;
|
|
1682
|
-
}
|
|
1683
|
-
return this.node.arguments;
|
|
1705
|
+
return this.node.typeArguments ?? this.node.arguments;
|
|
1684
1706
|
}
|
|
1685
1707
|
get target() {
|
|
1686
|
-
|
|
1687
|
-
return this.matcherNode.typeArguments;
|
|
1688
|
-
}
|
|
1689
|
-
return this.matcherNode.arguments;
|
|
1708
|
+
return this.matcherNode.typeArguments ?? this.matcherNode.arguments;
|
|
1690
1709
|
}
|
|
1691
1710
|
}
|
|
1692
1711
|
|
|
@@ -1707,12 +1726,6 @@ class IdentifierLookup {
|
|
|
1707
1726
|
namespace: undefined,
|
|
1708
1727
|
};
|
|
1709
1728
|
}
|
|
1710
|
-
clone() {
|
|
1711
|
-
return {
|
|
1712
|
-
namedImports: { ...this.#identifiers.namedImports },
|
|
1713
|
-
namespace: this.#identifiers.namespace,
|
|
1714
|
-
};
|
|
1715
|
-
}
|
|
1716
1729
|
handleImportDeclaration(node) {
|
|
1717
1730
|
if (this.#moduleSpecifiers.includes(node.moduleSpecifier.getText()) &&
|
|
1718
1731
|
node.importClause?.isTypeOnly !== true &&
|
|
@@ -1774,7 +1787,7 @@ class IdentifierLookup {
|
|
|
1774
1787
|
else {
|
|
1775
1788
|
identifierName = Object.keys(this.#identifiers.namedImports).find((key) => this.#identifiers.namedImports[key] === expression.getText());
|
|
1776
1789
|
}
|
|
1777
|
-
if (identifierName
|
|
1790
|
+
if (!identifierName) {
|
|
1778
1791
|
return;
|
|
1779
1792
|
}
|
|
1780
1793
|
switch (identifierName) {
|
|
@@ -1808,40 +1821,10 @@ class TestTree {
|
|
|
1808
1821
|
|
|
1809
1822
|
class CollectService {
|
|
1810
1823
|
#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
1824
|
constructor(compiler) {
|
|
1836
1825
|
this.#compiler = compiler;
|
|
1837
1826
|
}
|
|
1838
1827
|
#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
1828
|
if (this.#compiler.isCallExpression(node)) {
|
|
1846
1829
|
const meta = identifiers.resolveTestMemberMeta(node);
|
|
1847
1830
|
if (meta != null && (meta.brand === "describe" || meta.brand === "test")) {
|
|
@@ -1853,13 +1836,13 @@ class CollectService {
|
|
|
1853
1836
|
return;
|
|
1854
1837
|
}
|
|
1855
1838
|
if (meta != null && meta.brand === "expect") {
|
|
1856
|
-
const modifierNode = this.#
|
|
1839
|
+
const modifierNode = this.#getChainedNode(node, "type");
|
|
1857
1840
|
if (!modifierNode) {
|
|
1858
1841
|
return;
|
|
1859
1842
|
}
|
|
1860
|
-
const notNode = this.#
|
|
1861
|
-
const matcherNode = this.#
|
|
1862
|
-
if (matcherNode
|
|
1843
|
+
const notNode = this.#getChainedNode(modifierNode, "not");
|
|
1844
|
+
const matcherNode = this.#getChainedNode(notNode ?? modifierNode)?.parent;
|
|
1845
|
+
if (!matcherNode || !this.#isMatcherNode(matcherNode)) {
|
|
1863
1846
|
return;
|
|
1864
1847
|
}
|
|
1865
1848
|
const assertion = new Assertion(this.#compiler, meta.brand, node, parent, meta.flags, matcherNode, modifierNode, notNode);
|
|
@@ -1874,8 +1857,6 @@ class CollectService {
|
|
|
1874
1857
|
identifiers.handleImportDeclaration(node);
|
|
1875
1858
|
return;
|
|
1876
1859
|
}
|
|
1877
|
-
if (this.#compiler.isVariableDeclaration(node)) ;
|
|
1878
|
-
if (this.#compiler.isBinaryExpression(node)) ;
|
|
1879
1860
|
this.#compiler.forEachChild(node, (node) => {
|
|
1880
1861
|
this.#collectTestMembers(node, identifiers, parent);
|
|
1881
1862
|
});
|
|
@@ -1885,11 +1866,14 @@ class CollectService {
|
|
|
1885
1866
|
this.#collectTestMembers(sourceFile, new IdentifierLookup(this.#compiler), testTree);
|
|
1886
1867
|
return testTree;
|
|
1887
1868
|
}
|
|
1888
|
-
#
|
|
1889
|
-
if (this.#compiler.isPropertyAccessExpression(parent)
|
|
1890
|
-
return
|
|
1869
|
+
#getChainedNode({ parent }, name) {
|
|
1870
|
+
if (!this.#compiler.isPropertyAccessExpression(parent)) {
|
|
1871
|
+
return;
|
|
1891
1872
|
}
|
|
1892
|
-
|
|
1873
|
+
if (name != null && name !== parent.name.getText()) {
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
return parent;
|
|
1893
1877
|
}
|
|
1894
1878
|
#isMatcherNode(node) {
|
|
1895
1879
|
return this.#compiler.isCallExpression(node) && this.#compiler.isPropertyAccessExpression(node.expression);
|
|
@@ -1911,220 +1895,599 @@ var TestMemberFlags;
|
|
|
1911
1895
|
TestMemberFlags[TestMemberFlags["Todo"] = 8] = "Todo";
|
|
1912
1896
|
})(TestMemberFlags || (TestMemberFlags = {}));
|
|
1913
1897
|
|
|
1898
|
+
class ExpectDiagnosticText {
|
|
1899
|
+
static argumentOrTypeArgumentMustBeProvided(argumentNameText, typeArgumentNameText) {
|
|
1900
|
+
return `An argument for '${argumentNameText}' or type argument for '${typeArgumentNameText}' must be provided.`;
|
|
1901
|
+
}
|
|
1902
|
+
static argumentMustBe(argumentNameText, expectedText) {
|
|
1903
|
+
return `An argument for '${argumentNameText}' must be ${expectedText}.`;
|
|
1904
|
+
}
|
|
1905
|
+
static argumentMustBeProvided(argumentNameText) {
|
|
1906
|
+
return `An argument for '${argumentNameText}' must be provided.`;
|
|
1907
|
+
}
|
|
1908
|
+
static componentAcceptsProps(isTypeNode) {
|
|
1909
|
+
return `${isTypeNode ? "Component type" : "Component"} accepts props of the given type.`;
|
|
1910
|
+
}
|
|
1911
|
+
static componentDoesNotAcceptProps(isTypeNode) {
|
|
1912
|
+
return `${isTypeNode ? "Component type" : "Component"} does not accept props of the given type.`;
|
|
1913
|
+
}
|
|
1914
|
+
static matcherIsDeprecated(matcherNameText) {
|
|
1915
|
+
return [
|
|
1916
|
+
`The '.${matcherNameText}()' matcher is deprecated and will be removed in TSTyche 3.`,
|
|
1917
|
+
"To learn more, visit https://tstyche.org/releases/tstyche-2",
|
|
1918
|
+
];
|
|
1919
|
+
}
|
|
1920
|
+
static matcherIsNotSupported(matcherNameText) {
|
|
1921
|
+
return `The '.${matcherNameText}()' matcher is not supported.`;
|
|
1922
|
+
}
|
|
1923
|
+
static overloadGaveTheFollowingError(index, count, signatureText) {
|
|
1924
|
+
return `Overload ${index} of ${count}, '${signatureText}', gave the following error.`;
|
|
1925
|
+
}
|
|
1926
|
+
static raisedTypeError(count = 1) {
|
|
1927
|
+
return `The raised type error${count === 1 ? "" : "s"}:`;
|
|
1928
|
+
}
|
|
1929
|
+
static typeArgumentMustBe(argumentNameText, expectedText) {
|
|
1930
|
+
return `A type argument for '${argumentNameText}' must be ${expectedText}.`;
|
|
1931
|
+
}
|
|
1932
|
+
static typeDidNotRaiseError(isTypeNode) {
|
|
1933
|
+
return `${isTypeNode ? "Type" : "Expression type"} did not raise a type error.`;
|
|
1934
|
+
}
|
|
1935
|
+
static typeDidNotRaiseMatchingError(isTypeNode) {
|
|
1936
|
+
return `${isTypeNode ? "Type" : "Expression type"} did not raise a matching type error.`;
|
|
1937
|
+
}
|
|
1938
|
+
static typeDoesNotHaveProperty(typeText, propertyNameText) {
|
|
1939
|
+
return `Type '${typeText}' does not have property '${propertyNameText}'.`;
|
|
1940
|
+
}
|
|
1941
|
+
static typeDoesMatch(sourceTypeText, targetTypeText) {
|
|
1942
|
+
return `Type '${sourceTypeText}' does match type '${targetTypeText}'.`;
|
|
1943
|
+
}
|
|
1944
|
+
static typeDoesNotMatch(sourceTypeText, targetTypeText) {
|
|
1945
|
+
return `Type '${sourceTypeText}' does not match type '${targetTypeText}'.`;
|
|
1946
|
+
}
|
|
1947
|
+
static typeHasProperty(typeText, propertyNameText) {
|
|
1948
|
+
return `Type '${typeText}' has property '${propertyNameText}'.`;
|
|
1949
|
+
}
|
|
1950
|
+
static typeIs(typeText) {
|
|
1951
|
+
return `Type is '${typeText}'.`;
|
|
1952
|
+
}
|
|
1953
|
+
static typeIsAssignableTo(sourceTypeText, targetTypeText) {
|
|
1954
|
+
return `Type '${sourceTypeText}' is assignable to type '${targetTypeText}'.`;
|
|
1955
|
+
}
|
|
1956
|
+
static typeIsAssignableWith(sourceTypeText, targetTypeText) {
|
|
1957
|
+
return `Type '${sourceTypeText}' is assignable with type '${targetTypeText}'.`;
|
|
1958
|
+
}
|
|
1959
|
+
static typeIsIdenticalTo(sourceTypeText, targetTypeText) {
|
|
1960
|
+
return `Type '${sourceTypeText}' is identical to type '${targetTypeText}'.`;
|
|
1961
|
+
}
|
|
1962
|
+
static typeIsNotAssignableTo(sourceTypeText, targetTypeText) {
|
|
1963
|
+
return `Type '${sourceTypeText}' is not assignable to type '${targetTypeText}'.`;
|
|
1964
|
+
}
|
|
1965
|
+
static typeIsNotAssignableWith(sourceTypeText, targetTypeText) {
|
|
1966
|
+
return `Type '${sourceTypeText}' is not assignable with type '${targetTypeText}'.`;
|
|
1967
|
+
}
|
|
1968
|
+
static typeIsNotCompatibleWith(sourceTypeText, targetTypeText) {
|
|
1969
|
+
return `Type '${sourceTypeText}' is not compatible with type '${targetTypeText}'.`;
|
|
1970
|
+
}
|
|
1971
|
+
static typeIsNotIdenticalTo(sourceTypeText, targetTypeText) {
|
|
1972
|
+
return `Type '${sourceTypeText}' is not identical to type '${targetTypeText}'.`;
|
|
1973
|
+
}
|
|
1974
|
+
static typeRaisedError(isTypeNode, count, targetCount) {
|
|
1975
|
+
let countText = "a";
|
|
1976
|
+
if (count > 1 || targetCount > 1) {
|
|
1977
|
+
countText = count > targetCount ? `${count}` : `only ${count}`;
|
|
1978
|
+
}
|
|
1979
|
+
return `${isTypeNode ? "Type" : "Expression type"} raised ${countText} type error${count === 1 ? "" : "s"}.`;
|
|
1980
|
+
}
|
|
1981
|
+
static typeRaisedMatchingError(isTypeNode) {
|
|
1982
|
+
return `${isTypeNode ? "Type" : "Expression type"} raised a matching type error.`;
|
|
1983
|
+
}
|
|
1984
|
+
static typeRequiresProperty(typeText, propertyNameText) {
|
|
1985
|
+
return `Type '${typeText}' requires property '${propertyNameText}'.`;
|
|
1986
|
+
}
|
|
1987
|
+
static typesOfPropertyAreNotCompatible(propertyNameText) {
|
|
1988
|
+
return `Types of property '${propertyNameText}' are not compatible.`;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
class MatchWorker {
|
|
1993
|
+
assertion;
|
|
1994
|
+
#compiler;
|
|
1995
|
+
#signatureCache = new Map();
|
|
1996
|
+
#typeCache = new Map();
|
|
1997
|
+
#typeChecker;
|
|
1998
|
+
constructor(compiler, typeChecker, assertion) {
|
|
1999
|
+
this.#compiler = compiler;
|
|
2000
|
+
this.#typeChecker = typeChecker;
|
|
2001
|
+
this.assertion = assertion;
|
|
2002
|
+
}
|
|
2003
|
+
checkIsAssignableTo(sourceNode, targetNode) {
|
|
2004
|
+
const relation = this.#typeChecker.relation.assignable;
|
|
2005
|
+
return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
|
|
2006
|
+
}
|
|
2007
|
+
checkIsAssignableWith(sourceNode, targetNode) {
|
|
2008
|
+
const relation = this.#typeChecker.relation.assignable;
|
|
2009
|
+
return this.#checkIsRelatedTo(targetNode, sourceNode, relation);
|
|
2010
|
+
}
|
|
2011
|
+
checkIsIdenticalTo(sourceNode, targetNode) {
|
|
2012
|
+
const relation = this.#typeChecker.relation.identity;
|
|
2013
|
+
return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
|
|
2014
|
+
}
|
|
2015
|
+
checkIsSubtype(sourceNode, targetNode) {
|
|
2016
|
+
const relation = this.#typeChecker.relation.subtype;
|
|
2017
|
+
return this.#checkIsRelatedTo(sourceNode, targetNode, relation);
|
|
2018
|
+
}
|
|
2019
|
+
#checkIsRelatedTo(sourceNode, targetNode, relation) {
|
|
2020
|
+
const sourceType = this.getType(sourceNode);
|
|
2021
|
+
const targetType = this.getType(targetNode);
|
|
2022
|
+
return this.#typeChecker.isTypeRelatedTo(sourceType, targetType, relation);
|
|
2023
|
+
}
|
|
2024
|
+
extendsObjectType(type) {
|
|
2025
|
+
const nonPrimitiveType = { flags: this.#compiler.TypeFlags.NonPrimitive };
|
|
2026
|
+
return this.#typeChecker.isTypeAssignableTo(type, nonPrimitiveType);
|
|
2027
|
+
}
|
|
2028
|
+
getParameterType(signature, index) {
|
|
2029
|
+
const parameter = signature.getDeclaration().parameters[index];
|
|
2030
|
+
if (!parameter) {
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
2033
|
+
return this.#getTypeOfNode(parameter);
|
|
2034
|
+
}
|
|
2035
|
+
getSignatures(node) {
|
|
2036
|
+
let signatures = this.#signatureCache.get(node);
|
|
2037
|
+
if (!signatures) {
|
|
2038
|
+
const type = this.getType(node);
|
|
2039
|
+
signatures = type.getCallSignatures();
|
|
2040
|
+
if (signatures.length === 0) {
|
|
2041
|
+
signatures = type.getConstructSignatures();
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
return signatures;
|
|
2045
|
+
}
|
|
2046
|
+
getTypeText(node) {
|
|
2047
|
+
const type = this.getType(node);
|
|
2048
|
+
return this.#typeChecker.typeToString(type);
|
|
2049
|
+
}
|
|
2050
|
+
getType(node) {
|
|
2051
|
+
return this.#compiler.isExpression(node) ? this.#getTypeOfNode(node) : this.#getTypeOfTypeNode(node);
|
|
2052
|
+
}
|
|
2053
|
+
#getTypeOfNode(node) {
|
|
2054
|
+
let type = this.#typeCache.get(node);
|
|
2055
|
+
if (!type) {
|
|
2056
|
+
type = this.#typeChecker.getTypeAtLocation(node);
|
|
2057
|
+
}
|
|
2058
|
+
return type;
|
|
2059
|
+
}
|
|
2060
|
+
#getTypeOfTypeNode(node) {
|
|
2061
|
+
let type = this.#typeCache.get(node);
|
|
2062
|
+
if (!type) {
|
|
2063
|
+
type = this.#typeChecker.getTypeFromTypeNode(node);
|
|
2064
|
+
}
|
|
2065
|
+
return type;
|
|
2066
|
+
}
|
|
2067
|
+
isAnyOrNeverType(type) {
|
|
2068
|
+
return !!(type.flags & (this.#compiler.TypeFlags.Any | this.#compiler.TypeFlags.Never));
|
|
2069
|
+
}
|
|
2070
|
+
isStringOrNumberLiteralType(type) {
|
|
2071
|
+
return !!(type.flags & this.#compiler.TypeFlags.StringOrNumberLiteral);
|
|
2072
|
+
}
|
|
2073
|
+
isObjectType(type) {
|
|
2074
|
+
return !!(type.flags & this.#compiler.TypeFlags.Object);
|
|
2075
|
+
}
|
|
2076
|
+
isUnionType(type) {
|
|
2077
|
+
return !!(type.flags & this.#compiler.TypeFlags.Union);
|
|
2078
|
+
}
|
|
2079
|
+
isUniqueSymbolType(type) {
|
|
2080
|
+
return !!(type.flags & this.#compiler.TypeFlags.UniqueESSymbol);
|
|
2081
|
+
}
|
|
2082
|
+
resolveDiagnosticOrigin(symbol, enclosingNode) {
|
|
2083
|
+
if (symbol.valueDeclaration != null &&
|
|
2084
|
+
(this.#compiler.isPropertySignature(symbol.valueDeclaration) ||
|
|
2085
|
+
this.#compiler.isPropertyAssignment(symbol.valueDeclaration) ||
|
|
2086
|
+
this.#compiler.isShorthandPropertyAssignment(symbol.valueDeclaration)) &&
|
|
2087
|
+
symbol.valueDeclaration.getStart() >= enclosingNode.getStart() &&
|
|
2088
|
+
symbol.valueDeclaration.getEnd() <= enclosingNode.getEnd()) {
|
|
2089
|
+
return DiagnosticOrigin.fromNode(symbol.valueDeclaration.name, this.assertion);
|
|
2090
|
+
}
|
|
2091
|
+
return DiagnosticOrigin.fromNode(enclosingNode, this.assertion);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
|
|
1914
2095
|
class PrimitiveTypeMatcher {
|
|
1915
2096
|
#targetTypeFlag;
|
|
1916
|
-
|
|
1917
|
-
constructor(typeChecker, targetTypeFlag) {
|
|
1918
|
-
this.typeChecker = typeChecker;
|
|
2097
|
+
constructor(targetTypeFlag) {
|
|
1919
2098
|
this.#targetTypeFlag = targetTypeFlag;
|
|
1920
2099
|
}
|
|
1921
|
-
#explain(
|
|
1922
|
-
const sourceTypeText =
|
|
1923
|
-
|
|
2100
|
+
#explain(matchWorker, sourceNode) {
|
|
2101
|
+
const sourceTypeText = matchWorker.getTypeText(sourceNode);
|
|
2102
|
+
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
2103
|
+
return [Diagnostic.error(ExpectDiagnosticText.typeIs(sourceTypeText), origin)];
|
|
1924
2104
|
}
|
|
1925
|
-
match(
|
|
1926
|
-
const
|
|
2105
|
+
match(matchWorker, sourceNode) {
|
|
2106
|
+
const sourceType = matchWorker.getType(sourceNode);
|
|
2107
|
+
const isMatch = !!(sourceType.flags & this.#targetTypeFlag);
|
|
1927
2108
|
return {
|
|
1928
|
-
explain: () => this.#explain(
|
|
2109
|
+
explain: () => this.#explain(matchWorker, sourceNode),
|
|
1929
2110
|
isMatch,
|
|
1930
2111
|
};
|
|
1931
2112
|
}
|
|
1932
2113
|
}
|
|
1933
2114
|
|
|
1934
|
-
class
|
|
1935
|
-
|
|
1936
|
-
typeChecker;
|
|
1937
|
-
constructor(typeChecker) {
|
|
1938
|
-
this
|
|
1939
|
-
|
|
1940
|
-
explain(sourceType, targetType, isNot) {
|
|
1941
|
-
const sourceTypeText = this.typeChecker.typeToString(sourceType);
|
|
1942
|
-
const targetTypeText = this.typeChecker.typeToString(targetType);
|
|
1943
|
-
return isNot
|
|
1944
|
-
? [
|
|
1945
|
-
Diagnostic.error(`Type '${sourceTypeText}' ${this.relationExplanationVerb} ${this.relationExplanationText} type '${targetTypeText}'.`),
|
|
1946
|
-
]
|
|
1947
|
-
: [
|
|
1948
|
-
Diagnostic.error(`Type '${sourceTypeText}' ${this.relationExplanationVerb} not ${this.relationExplanationText} type '${targetTypeText}'.`),
|
|
1949
|
-
];
|
|
2115
|
+
class ToAcceptProps {
|
|
2116
|
+
#compiler;
|
|
2117
|
+
#typeChecker;
|
|
2118
|
+
constructor(compiler, typeChecker) {
|
|
2119
|
+
this.#compiler = compiler;
|
|
2120
|
+
this.#typeChecker = typeChecker;
|
|
1950
2121
|
}
|
|
1951
|
-
|
|
1952
|
-
const
|
|
2122
|
+
#explain(matchWorker, sourceNode, targetNode) {
|
|
2123
|
+
const signatures = matchWorker.getSignatures(sourceNode);
|
|
2124
|
+
return signatures.reduce((accumulator, signature, index) => {
|
|
2125
|
+
let diagnostic;
|
|
2126
|
+
const introText = matchWorker.assertion.isNot
|
|
2127
|
+
? ExpectDiagnosticText.componentAcceptsProps(this.#compiler.isTypeNode(sourceNode))
|
|
2128
|
+
: ExpectDiagnosticText.componentDoesNotAcceptProps(this.#compiler.isTypeNode(sourceNode));
|
|
2129
|
+
const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
|
|
2130
|
+
if (signatures.length > 1) {
|
|
2131
|
+
const signatureText = this.#typeChecker.signatureToString(signature, sourceNode);
|
|
2132
|
+
const overloadText = ExpectDiagnosticText.overloadGaveTheFollowingError(index + 1, signatures.length, signatureText);
|
|
2133
|
+
diagnostic = Diagnostic.error([introText, overloadText], origin);
|
|
2134
|
+
}
|
|
2135
|
+
else {
|
|
2136
|
+
diagnostic = Diagnostic.error([introText], origin);
|
|
2137
|
+
}
|
|
2138
|
+
const { diagnostics, isMatch } = this.#explainProperties(matchWorker, signature, targetNode, diagnostic);
|
|
2139
|
+
if (matchWorker.assertion.isNot ? isMatch : !isMatch) {
|
|
2140
|
+
accumulator.push(...diagnostics);
|
|
2141
|
+
}
|
|
2142
|
+
return accumulator;
|
|
2143
|
+
}, []);
|
|
2144
|
+
}
|
|
2145
|
+
#isOptionalProperty(symbol) {
|
|
2146
|
+
return symbol.declarations?.every((declaration) => this.#compiler.isPropertySignature(declaration) && declaration.questionToken != null);
|
|
2147
|
+
}
|
|
2148
|
+
#checkProperties(matchWorker, sourceType, targetType) {
|
|
2149
|
+
const check = (sourceType, targetType) => {
|
|
2150
|
+
for (const targetProperty of targetType.getProperties()) {
|
|
2151
|
+
const targetPropertyName = targetProperty.getName();
|
|
2152
|
+
const sourceProperty = sourceType?.getProperty(targetPropertyName);
|
|
2153
|
+
if (!sourceProperty) {
|
|
2154
|
+
return false;
|
|
2155
|
+
}
|
|
2156
|
+
if (this.#isOptionalProperty(targetProperty) && !this.#isOptionalProperty(sourceProperty)) {
|
|
2157
|
+
return false;
|
|
2158
|
+
}
|
|
2159
|
+
const targetPropertyType = this.#typeChecker.getTypeOfSymbol(targetProperty);
|
|
2160
|
+
const sourcePropertyType = this.#typeChecker.getTypeOfSymbol(sourceProperty);
|
|
2161
|
+
if (!this.#typeChecker.isTypeAssignableTo(targetPropertyType, sourcePropertyType)) {
|
|
2162
|
+
return false;
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
if (sourceType != null) {
|
|
2166
|
+
const sourceProperties = sourceType.getProperties();
|
|
2167
|
+
for (const sourceProperty of sourceProperties) {
|
|
2168
|
+
const targetProperty = targetType.getProperty(sourceProperty.getName());
|
|
2169
|
+
if (!targetProperty && !this.#isOptionalProperty(sourceProperty)) {
|
|
2170
|
+
return false;
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
return true;
|
|
2175
|
+
};
|
|
2176
|
+
if (sourceType != null && matchWorker.isUnionType(sourceType)) {
|
|
2177
|
+
return sourceType.types.some((sourceType) => check(sourceType, targetType));
|
|
2178
|
+
}
|
|
2179
|
+
return check(sourceType, targetType);
|
|
2180
|
+
}
|
|
2181
|
+
#explainProperties(matchWorker, signature, targetNode, diagnostic) {
|
|
2182
|
+
const sourceType = matchWorker.getParameterType(signature, 0);
|
|
2183
|
+
const sourceTypeText = sourceType != null ? this.#typeChecker.typeToString(sourceType) : "{}";
|
|
2184
|
+
const targetType = matchWorker.getType(targetNode);
|
|
2185
|
+
const targetTypeText = this.#typeChecker.typeToString(targetType);
|
|
2186
|
+
const explain = (sourceType, targetType, diagnostic) => {
|
|
2187
|
+
const sourceTypeText = sourceType != null ? this.#typeChecker.typeToString(sourceType) : "{}";
|
|
2188
|
+
const diagnostics = [];
|
|
2189
|
+
for (const targetProperty of targetType.getProperties()) {
|
|
2190
|
+
const targetPropertyName = targetProperty.getName();
|
|
2191
|
+
const sourceProperty = sourceType?.getProperty(targetPropertyName);
|
|
2192
|
+
if (!sourceProperty) {
|
|
2193
|
+
const text = [
|
|
2194
|
+
ExpectDiagnosticText.typeIsNotCompatibleWith(sourceTypeText, targetTypeText),
|
|
2195
|
+
ExpectDiagnosticText.typeDoesNotHaveProperty(sourceTypeText, targetPropertyName),
|
|
2196
|
+
];
|
|
2197
|
+
const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
|
|
2198
|
+
diagnostics.push(diagnostic.extendWith(text, origin));
|
|
2199
|
+
continue;
|
|
2200
|
+
}
|
|
2201
|
+
if (this.#isOptionalProperty(targetProperty) && !this.#isOptionalProperty(sourceProperty)) {
|
|
2202
|
+
const text = [
|
|
2203
|
+
ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText),
|
|
2204
|
+
ExpectDiagnosticText.typeRequiresProperty(sourceTypeText, targetPropertyName),
|
|
2205
|
+
];
|
|
2206
|
+
const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
|
|
2207
|
+
diagnostics.push(diagnostic.extendWith(text, origin));
|
|
2208
|
+
continue;
|
|
2209
|
+
}
|
|
2210
|
+
const targetPropertyType = this.#typeChecker.getTypeOfSymbol(targetProperty);
|
|
2211
|
+
const sourcePropertyType = this.#typeChecker.getTypeOfSymbol(sourceProperty);
|
|
2212
|
+
if (!this.#typeChecker.isTypeAssignableTo(targetPropertyType, sourcePropertyType)) {
|
|
2213
|
+
const targetPropertyTypeText = this.#typeChecker.typeToString(targetPropertyType);
|
|
2214
|
+
const sourcePropertyTypeText = this.#typeChecker.typeToString(sourcePropertyType);
|
|
2215
|
+
const text = [
|
|
2216
|
+
ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText),
|
|
2217
|
+
ExpectDiagnosticText.typesOfPropertyAreNotCompatible(targetPropertyName),
|
|
2218
|
+
ExpectDiagnosticText.typeIsNotAssignableWith(sourcePropertyTypeText, targetPropertyTypeText),
|
|
2219
|
+
];
|
|
2220
|
+
const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
|
|
2221
|
+
diagnostics.push(diagnostic.extendWith(text, origin));
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
if (sourceType != null) {
|
|
2225
|
+
for (const sourceProperty of sourceType.getProperties()) {
|
|
2226
|
+
const sourcePropertyName = sourceProperty.getName();
|
|
2227
|
+
const targetProperty = targetType.getProperty(sourcePropertyName);
|
|
2228
|
+
if (!targetProperty && !this.#isOptionalProperty(sourceProperty)) {
|
|
2229
|
+
const text = [
|
|
2230
|
+
ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText),
|
|
2231
|
+
ExpectDiagnosticText.typeRequiresProperty(sourceTypeText, sourcePropertyName),
|
|
2232
|
+
];
|
|
2233
|
+
diagnostics.push(diagnostic.extendWith(text));
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
if (diagnostics.length === 0) {
|
|
2238
|
+
const text = ExpectDiagnosticText.typeIsAssignableWith(sourceTypeText, targetTypeText);
|
|
2239
|
+
diagnostics.push(diagnostic.extendWith(text));
|
|
2240
|
+
return { diagnostics, isMatch: true };
|
|
2241
|
+
}
|
|
2242
|
+
return { diagnostics, isMatch: false };
|
|
2243
|
+
};
|
|
2244
|
+
if (sourceType != null && matchWorker.isUnionType(sourceType)) {
|
|
2245
|
+
let accumulator = [];
|
|
2246
|
+
const isMatch = sourceType.types.some((sourceType) => {
|
|
2247
|
+
const text = matchWorker.assertion.isNot
|
|
2248
|
+
? ExpectDiagnosticText.typeIsAssignableWith(sourceTypeText, targetTypeText)
|
|
2249
|
+
: ExpectDiagnosticText.typeIsNotAssignableWith(sourceTypeText, targetTypeText);
|
|
2250
|
+
const { diagnostics, isMatch } = explain(sourceType, targetType, diagnostic.extendWith(text));
|
|
2251
|
+
if (isMatch) {
|
|
2252
|
+
accumulator = diagnostics;
|
|
2253
|
+
}
|
|
2254
|
+
else {
|
|
2255
|
+
accumulator.push(...diagnostics);
|
|
2256
|
+
}
|
|
2257
|
+
return isMatch;
|
|
2258
|
+
});
|
|
2259
|
+
return { diagnostics: accumulator, isMatch };
|
|
2260
|
+
}
|
|
2261
|
+
return explain(sourceType, targetType, diagnostic);
|
|
2262
|
+
}
|
|
2263
|
+
match(matchWorker, sourceNode, targetNode, onDiagnostics) {
|
|
2264
|
+
const diagnostics = [];
|
|
2265
|
+
const signatures = matchWorker.getSignatures(sourceNode);
|
|
2266
|
+
if (signatures.length === 0) {
|
|
2267
|
+
const expectedText = "of a function or class type";
|
|
2268
|
+
const text = this.#compiler.isTypeNode(sourceNode)
|
|
2269
|
+
? ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText)
|
|
2270
|
+
: ExpectDiagnosticText.argumentMustBe("source", expectedText);
|
|
2271
|
+
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
2272
|
+
diagnostics.push(Diagnostic.error(text, origin));
|
|
2273
|
+
}
|
|
2274
|
+
const targetType = matchWorker.getType(targetNode);
|
|
2275
|
+
if (!matchWorker.isObjectType(targetType)) {
|
|
2276
|
+
const expectedText = "of an object type";
|
|
2277
|
+
const text = this.#compiler.isTypeNode(targetNode)
|
|
2278
|
+
? ExpectDiagnosticText.typeArgumentMustBe("Target", expectedText)
|
|
2279
|
+
: ExpectDiagnosticText.argumentMustBe("target", expectedText);
|
|
2280
|
+
const origin = DiagnosticOrigin.fromNode(targetNode);
|
|
2281
|
+
diagnostics.push(Diagnostic.error(text, origin));
|
|
2282
|
+
}
|
|
2283
|
+
if (diagnostics.length > 0) {
|
|
2284
|
+
onDiagnostics(diagnostics);
|
|
2285
|
+
return;
|
|
2286
|
+
}
|
|
2287
|
+
const isMatch = signatures.some((signature) => {
|
|
2288
|
+
const sourceType = matchWorker.getParameterType(signature, 0);
|
|
2289
|
+
return this.#checkProperties(matchWorker, sourceType, targetType);
|
|
2290
|
+
});
|
|
1953
2291
|
return {
|
|
1954
|
-
explain: () => this
|
|
2292
|
+
explain: () => this.#explain(matchWorker, sourceNode, targetNode),
|
|
1955
2293
|
isMatch,
|
|
1956
2294
|
};
|
|
1957
2295
|
}
|
|
1958
2296
|
}
|
|
1959
2297
|
|
|
2298
|
+
class RelationMatcherBase {
|
|
2299
|
+
explain(matchWorker, sourceNode, targetNode) {
|
|
2300
|
+
const sourceTypeText = matchWorker.getTypeText(sourceNode);
|
|
2301
|
+
const targetTypeText = matchWorker.getTypeText(targetNode);
|
|
2302
|
+
const text = matchWorker.assertion.isNot
|
|
2303
|
+
? this.explainText(sourceTypeText, targetTypeText)
|
|
2304
|
+
: this.explainNotText(sourceTypeText, targetTypeText);
|
|
2305
|
+
const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
|
|
2306
|
+
return [Diagnostic.error(text, origin)];
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
|
|
1960
2310
|
class ToBe extends RelationMatcherBase {
|
|
1961
|
-
|
|
1962
|
-
|
|
2311
|
+
explainText = ExpectDiagnosticText.typeIsIdenticalTo;
|
|
2312
|
+
explainNotText = ExpectDiagnosticText.typeIsNotIdenticalTo;
|
|
2313
|
+
match(matchWorker, sourceNode, targetNode) {
|
|
2314
|
+
return {
|
|
2315
|
+
explain: () => this.explain(matchWorker, sourceNode, targetNode),
|
|
2316
|
+
isMatch: matchWorker.checkIsIdenticalTo(sourceNode, targetNode),
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
1963
2319
|
}
|
|
1964
2320
|
|
|
1965
2321
|
class ToBeAssignableTo extends RelationMatcherBase {
|
|
1966
|
-
|
|
1967
|
-
|
|
2322
|
+
explainText = ExpectDiagnosticText.typeIsAssignableTo;
|
|
2323
|
+
explainNotText = ExpectDiagnosticText.typeIsNotAssignableTo;
|
|
2324
|
+
match(matchWorker, sourceNode, targetNode) {
|
|
2325
|
+
return {
|
|
2326
|
+
explain: () => this.explain(matchWorker, sourceNode, targetNode),
|
|
2327
|
+
isMatch: matchWorker.checkIsAssignableTo(sourceNode, targetNode),
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
1968
2330
|
}
|
|
1969
2331
|
|
|
1970
2332
|
class ToBeAssignableWith extends RelationMatcherBase {
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
match(
|
|
1974
|
-
const isMatch = this.typeChecker.isTypeRelatedTo(targetType, sourceType, this.relation);
|
|
2333
|
+
explainText = ExpectDiagnosticText.typeIsAssignableWith;
|
|
2334
|
+
explainNotText = ExpectDiagnosticText.typeIsNotAssignableWith;
|
|
2335
|
+
match(matchWorker, sourceNode, targetNode) {
|
|
1975
2336
|
return {
|
|
1976
|
-
explain: () => this.explain(
|
|
1977
|
-
isMatch,
|
|
2337
|
+
explain: () => this.explain(matchWorker, sourceNode, targetNode),
|
|
2338
|
+
isMatch: matchWorker.checkIsAssignableWith(sourceNode, targetNode),
|
|
1978
2339
|
};
|
|
1979
2340
|
}
|
|
1980
2341
|
}
|
|
1981
2342
|
|
|
1982
2343
|
class ToHaveProperty {
|
|
1983
|
-
compiler;
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
this.compiler = compiler;
|
|
1987
|
-
this.typeChecker = typeChecker;
|
|
2344
|
+
#compiler;
|
|
2345
|
+
constructor(compiler) {
|
|
2346
|
+
this.#compiler = compiler;
|
|
1988
2347
|
}
|
|
1989
|
-
#explain(
|
|
1990
|
-
const sourceTypeText =
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
2348
|
+
#explain(matchWorker, sourceNode, targetNode) {
|
|
2349
|
+
const sourceTypeText = matchWorker.getTypeText(sourceNode);
|
|
2350
|
+
const targetType = matchWorker.getType(targetNode);
|
|
2351
|
+
let propertyNameText;
|
|
2352
|
+
if (matchWorker.isStringOrNumberLiteralType(targetType)) {
|
|
2353
|
+
propertyNameText = targetType.value.toString();
|
|
1994
2354
|
}
|
|
1995
2355
|
else {
|
|
1996
|
-
|
|
2356
|
+
propertyNameText = `[${this.#compiler.unescapeLeadingUnderscores(targetType.symbol.escapedName)}]`;
|
|
1997
2357
|
}
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
#isStringOrNumberLiteralType(type) {
|
|
2003
|
-
return Boolean(type.flags & this.compiler.TypeFlags.StringOrNumberLiteral);
|
|
2358
|
+
const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
|
|
2359
|
+
return matchWorker.assertion.isNot
|
|
2360
|
+
? [Diagnostic.error(ExpectDiagnosticText.typeHasProperty(sourceTypeText, propertyNameText), origin)]
|
|
2361
|
+
: [Diagnostic.error(ExpectDiagnosticText.typeDoesNotHaveProperty(sourceTypeText, propertyNameText), origin)];
|
|
2004
2362
|
}
|
|
2005
|
-
match(
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2363
|
+
match(matchWorker, sourceNode, targetNode, onDiagnostics) {
|
|
2364
|
+
const diagnostics = [];
|
|
2365
|
+
const sourceType = matchWorker.getType(sourceNode);
|
|
2366
|
+
if (matchWorker.isAnyOrNeverType(sourceType) || !matchWorker.extendsObjectType(sourceType)) {
|
|
2367
|
+
const expectedText = "of an object type";
|
|
2368
|
+
const text = this.#compiler.isTypeNode(sourceNode)
|
|
2369
|
+
? ExpectDiagnosticText.typeArgumentMustBe("Source", expectedText)
|
|
2370
|
+
: ExpectDiagnosticText.argumentMustBe("source", expectedText);
|
|
2371
|
+
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
2372
|
+
diagnostics.push(Diagnostic.error(text, origin));
|
|
2373
|
+
}
|
|
2374
|
+
const targetType = matchWorker.getType(targetNode);
|
|
2375
|
+
let propertyNameText;
|
|
2376
|
+
if (matchWorker.isStringOrNumberLiteralType(targetType)) {
|
|
2377
|
+
propertyNameText = targetType.value.toString();
|
|
2378
|
+
}
|
|
2379
|
+
else if (matchWorker.isUniqueSymbolType(targetType)) {
|
|
2380
|
+
propertyNameText = this.#compiler.unescapeLeadingUnderscores(targetType.escapedName);
|
|
2009
2381
|
}
|
|
2010
2382
|
else {
|
|
2011
|
-
|
|
2383
|
+
const expectedText = "of type 'string | number | symbol'";
|
|
2384
|
+
const text = ExpectDiagnosticText.argumentMustBe("key", expectedText);
|
|
2385
|
+
const origin = DiagnosticOrigin.fromNode(targetNode);
|
|
2386
|
+
diagnostics.push(Diagnostic.error(text, origin));
|
|
2387
|
+
}
|
|
2388
|
+
if (diagnostics.length > 0) {
|
|
2389
|
+
onDiagnostics(diagnostics);
|
|
2390
|
+
return;
|
|
2012
2391
|
}
|
|
2013
2392
|
const isMatch = sourceType.getProperties().some((property) => {
|
|
2014
|
-
return this
|
|
2393
|
+
return this.#compiler.unescapeLeadingUnderscores(property.escapedName) === propertyNameText;
|
|
2015
2394
|
});
|
|
2016
2395
|
return {
|
|
2017
|
-
explain: () => this.#explain(
|
|
2396
|
+
explain: () => this.#explain(matchWorker, sourceNode, targetNode),
|
|
2018
2397
|
isMatch,
|
|
2019
2398
|
};
|
|
2020
2399
|
}
|
|
2021
2400
|
}
|
|
2022
2401
|
|
|
2023
2402
|
class ToMatch extends RelationMatcherBase {
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2403
|
+
explainText = ExpectDiagnosticText.typeDoesMatch;
|
|
2404
|
+
explainNotText = ExpectDiagnosticText.typeDoesNotMatch;
|
|
2405
|
+
match(matchWorker, sourceNode, targetNode) {
|
|
2406
|
+
return {
|
|
2407
|
+
explain: () => this.explain(matchWorker, sourceNode, targetNode),
|
|
2408
|
+
isMatch: matchWorker.checkIsSubtype(sourceNode, targetNode),
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2027
2411
|
}
|
|
2028
2412
|
|
|
2029
2413
|
class ToRaiseError {
|
|
2030
|
-
compiler;
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
this.compiler = compiler;
|
|
2034
|
-
this.typeChecker = typeChecker;
|
|
2414
|
+
#compiler;
|
|
2415
|
+
constructor(compiler) {
|
|
2416
|
+
this.#compiler = compiler;
|
|
2035
2417
|
}
|
|
2036
|
-
#explain(
|
|
2037
|
-
const
|
|
2038
|
-
|
|
2039
|
-
|
|
2418
|
+
#explain(matchWorker, sourceNode, targetNodes) {
|
|
2419
|
+
const isTypeNode = this.#compiler.isTypeNode(sourceNode);
|
|
2420
|
+
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
2421
|
+
if (matchWorker.assertion.diagnostics.size === 0) {
|
|
2422
|
+
const text = ExpectDiagnosticText.typeDidNotRaiseError(isTypeNode);
|
|
2423
|
+
return [Diagnostic.error(text, origin)];
|
|
2040
2424
|
}
|
|
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)}`;
|
|
2425
|
+
if (matchWorker.assertion.diagnostics.size !== targetNodes.length) {
|
|
2426
|
+
const count = matchWorker.assertion.diagnostics.size;
|
|
2427
|
+
const text = ExpectDiagnosticText.typeRaisedError(isTypeNode, count, targetNodes.length);
|
|
2056
2428
|
const related = [
|
|
2057
|
-
Diagnostic.error(
|
|
2058
|
-
...Diagnostic.fromDiagnostics(
|
|
2429
|
+
Diagnostic.error(ExpectDiagnosticText.raisedTypeError(count)),
|
|
2430
|
+
...Diagnostic.fromDiagnostics([...matchWorker.assertion.diagnostics], this.#compiler),
|
|
2059
2431
|
];
|
|
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)}`;
|
|
2074
|
-
const related = [
|
|
2075
|
-
Diagnostic.error("The raised type error:"),
|
|
2076
|
-
...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
|
|
2077
|
-
];
|
|
2078
|
-
const text = `${sourceText} did not raise a type error ${expectedText}.`;
|
|
2079
|
-
diagnostics.push(Diagnostic.error(text).add({ related }));
|
|
2080
|
-
}
|
|
2081
|
-
if (isNot && isMatch) {
|
|
2082
|
-
const expectedText = this.#isStringLiteralType(argument)
|
|
2083
|
-
? `matching substring '${argument.value}'`
|
|
2084
|
-
: `with code ${String(argument.value)}`;
|
|
2432
|
+
return [Diagnostic.error(text, origin).add({ related })];
|
|
2433
|
+
}
|
|
2434
|
+
return [...matchWorker.assertion.diagnostics].reduce((accumulator, diagnostic, index) => {
|
|
2435
|
+
const targetNode = targetNodes[index];
|
|
2436
|
+
const isMatch = this.#matchExpectedError(diagnostic, targetNode);
|
|
2437
|
+
if (matchWorker.assertion.isNot ? isMatch : !isMatch) {
|
|
2438
|
+
const text = matchWorker.assertion.isNot
|
|
2439
|
+
? ExpectDiagnosticText.typeRaisedMatchingError(isTypeNode)
|
|
2440
|
+
: ExpectDiagnosticText.typeDidNotRaiseMatchingError(isTypeNode);
|
|
2441
|
+
const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
|
|
2085
2442
|
const related = [
|
|
2086
|
-
Diagnostic.error(
|
|
2087
|
-
...Diagnostic.fromDiagnostics([diagnostic], this
|
|
2443
|
+
Diagnostic.error(ExpectDiagnosticText.raisedTypeError()),
|
|
2444
|
+
...Diagnostic.fromDiagnostics([diagnostic], this.#compiler),
|
|
2088
2445
|
];
|
|
2089
|
-
|
|
2090
|
-
diagnostics.push(Diagnostic.error(text).add({ related }));
|
|
2446
|
+
accumulator.push(Diagnostic.error(text, origin).add({ related }));
|
|
2091
2447
|
}
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
}
|
|
2095
|
-
#isStringLiteralType(type) {
|
|
2096
|
-
return Boolean(type.flags & this.compiler.TypeFlags.StringLiteral);
|
|
2448
|
+
return accumulator;
|
|
2449
|
+
}, []);
|
|
2097
2450
|
}
|
|
2098
|
-
match(
|
|
2099
|
-
const
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2451
|
+
match(matchWorker, sourceNode, targetNodes, onDiagnostics) {
|
|
2452
|
+
const diagnostics = [];
|
|
2453
|
+
for (const targetNode of targetNodes) {
|
|
2454
|
+
if (!(this.#compiler.isStringLiteralLike(targetNode) || this.#compiler.isNumericLiteral(targetNode))) {
|
|
2455
|
+
const expectedText = "a string or number literal";
|
|
2456
|
+
const text = ExpectDiagnosticText.argumentMustBe("target", expectedText);
|
|
2457
|
+
const origin = DiagnosticOrigin.fromNode(targetNode);
|
|
2458
|
+
diagnostics.push(Diagnostic.error(text, origin));
|
|
2459
|
+
}
|
|
2105
2460
|
}
|
|
2106
|
-
if (
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2461
|
+
if (diagnostics.length > 0) {
|
|
2462
|
+
onDiagnostics(diagnostics);
|
|
2463
|
+
return;
|
|
2464
|
+
}
|
|
2465
|
+
let isMatch;
|
|
2466
|
+
if (targetNodes.length === 0) {
|
|
2467
|
+
isMatch = matchWorker.assertion.diagnostics.size > 0;
|
|
2468
|
+
}
|
|
2469
|
+
else {
|
|
2470
|
+
isMatch =
|
|
2471
|
+
matchWorker.assertion.diagnostics.size === targetNodes.length &&
|
|
2472
|
+
[...matchWorker.assertion.diagnostics].every((diagnostic, index) => this.#matchExpectedError(diagnostic, targetNodes[index]));
|
|
2111
2473
|
}
|
|
2112
2474
|
return {
|
|
2113
|
-
explain,
|
|
2114
|
-
isMatch
|
|
2475
|
+
explain: () => this.#explain(matchWorker, sourceNode, targetNodes),
|
|
2476
|
+
isMatch,
|
|
2115
2477
|
};
|
|
2116
2478
|
}
|
|
2117
|
-
#matchExpectedError(diagnostic,
|
|
2118
|
-
if (this.#
|
|
2119
|
-
return this
|
|
2479
|
+
#matchExpectedError(diagnostic, targetNode) {
|
|
2480
|
+
if (this.#compiler.isStringLiteralLike(targetNode)) {
|
|
2481
|
+
return this.#compiler.flattenDiagnosticMessageText(diagnostic.messageText, " ", 0).includes(targetNode.text);
|
|
2120
2482
|
}
|
|
2121
|
-
return
|
|
2483
|
+
return Number.parseInt(targetNode.text) === diagnostic.code;
|
|
2122
2484
|
}
|
|
2123
2485
|
}
|
|
2124
2486
|
|
|
2125
|
-
class
|
|
2487
|
+
class ExpectService {
|
|
2126
2488
|
#compiler;
|
|
2127
2489
|
#typeChecker;
|
|
2490
|
+
toAcceptProps;
|
|
2128
2491
|
toBe;
|
|
2129
2492
|
toBeAny;
|
|
2130
2493
|
toBeAssignable;
|
|
@@ -2148,66 +2511,63 @@ class Expect {
|
|
|
2148
2511
|
constructor(compiler, typeChecker) {
|
|
2149
2512
|
this.#compiler = compiler;
|
|
2150
2513
|
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.
|
|
2514
|
+
this.toAcceptProps = new ToAcceptProps(compiler, typeChecker);
|
|
2515
|
+
this.toBe = new ToBe();
|
|
2516
|
+
this.toBeAny = new PrimitiveTypeMatcher(compiler.TypeFlags.Any);
|
|
2517
|
+
this.toBeAssignable = new ToBeAssignableWith();
|
|
2518
|
+
this.toBeAssignableTo = new ToBeAssignableTo();
|
|
2519
|
+
this.toBeAssignableWith = new ToBeAssignableWith();
|
|
2520
|
+
this.toBeBigInt = new PrimitiveTypeMatcher(compiler.TypeFlags.BigInt);
|
|
2521
|
+
this.toBeBoolean = new PrimitiveTypeMatcher(compiler.TypeFlags.Boolean);
|
|
2522
|
+
this.toBeNever = new PrimitiveTypeMatcher(compiler.TypeFlags.Never);
|
|
2523
|
+
this.toBeNull = new PrimitiveTypeMatcher(compiler.TypeFlags.Null);
|
|
2524
|
+
this.toBeNumber = new PrimitiveTypeMatcher(compiler.TypeFlags.Number);
|
|
2525
|
+
this.toBeString = new PrimitiveTypeMatcher(compiler.TypeFlags.String);
|
|
2526
|
+
this.toBeSymbol = new PrimitiveTypeMatcher(compiler.TypeFlags.ESSymbol);
|
|
2527
|
+
this.toBeUndefined = new PrimitiveTypeMatcher(compiler.TypeFlags.Undefined);
|
|
2528
|
+
this.toBeUniqueSymbol = new PrimitiveTypeMatcher(compiler.TypeFlags.UniqueESSymbol);
|
|
2529
|
+
this.toBeUnknown = new PrimitiveTypeMatcher(compiler.TypeFlags.Unknown);
|
|
2530
|
+
this.toBeVoid = new PrimitiveTypeMatcher(compiler.TypeFlags.Void);
|
|
2531
|
+
this.toEqual = new ToBe();
|
|
2532
|
+
this.toHaveProperty = new ToHaveProperty(compiler);
|
|
2533
|
+
this.toMatch = new ToMatch();
|
|
2534
|
+
this.toRaiseError = new ToRaiseError(compiler);
|
|
2171
2535
|
}
|
|
2172
2536
|
static assertTypeChecker(typeChecker) {
|
|
2173
2537
|
return "isTypeRelatedTo" in typeChecker && "relation" in typeChecker;
|
|
2174
2538
|
}
|
|
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);
|
|
2539
|
+
#handleDeprecated(matcherNameText, assertion) {
|
|
2540
|
+
switch (matcherNameText) {
|
|
2541
|
+
case "toBeAssignable":
|
|
2542
|
+
case "toEqual": {
|
|
2543
|
+
const text = ExpectDiagnosticText.matcherIsDeprecated(matcherNameText);
|
|
2544
|
+
const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
|
|
2545
|
+
EventEmitter.dispatch(["deprecation:info", { diagnostics: [Diagnostic.warning(text, origin)] }]);
|
|
2546
|
+
break;
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2191
2549
|
}
|
|
2192
|
-
match(assertion,
|
|
2550
|
+
match(assertion, onDiagnostics) {
|
|
2193
2551
|
const matcherNameText = assertion.matcherName.getText();
|
|
2552
|
+
this.#handleDeprecated(matcherNameText, assertion);
|
|
2553
|
+
if (!assertion.source[0]) {
|
|
2554
|
+
this.#onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
const matchWorker = new MatchWorker(this.#compiler, this.#typeChecker, assertion);
|
|
2194
2558
|
switch (matcherNameText) {
|
|
2195
|
-
case "
|
|
2196
|
-
case "toEqual":
|
|
2197
|
-
this.#onDeprecatedMatcher(assertion);
|
|
2559
|
+
case "toAcceptProps":
|
|
2198
2560
|
case "toBe":
|
|
2561
|
+
case "toBeAssignable":
|
|
2199
2562
|
case "toBeAssignableTo":
|
|
2200
2563
|
case "toBeAssignableWith":
|
|
2564
|
+
case "toEqual":
|
|
2201
2565
|
case "toMatch": {
|
|
2202
|
-
if (assertion.
|
|
2203
|
-
this.#
|
|
2566
|
+
if (!assertion.target[0]) {
|
|
2567
|
+
this.#onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
|
|
2204
2568
|
return;
|
|
2205
2569
|
}
|
|
2206
|
-
|
|
2207
|
-
this.#onTargetArgumentMustBeProvided(assertion, expectResult);
|
|
2208
|
-
return;
|
|
2209
|
-
}
|
|
2210
|
-
return this[matcherNameText].match(this.#getType(assertion.source[0]), this.#getType(assertion.target[0]), assertion.isNot);
|
|
2570
|
+
return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
|
|
2211
2571
|
}
|
|
2212
2572
|
case "toBeAny":
|
|
2213
2573
|
case "toBeBigInt":
|
|
@@ -2221,131 +2581,40 @@ class Expect {
|
|
|
2221
2581
|
case "toBeUniqueSymbol":
|
|
2222
2582
|
case "toBeUnknown":
|
|
2223
2583
|
case "toBeVoid": {
|
|
2224
|
-
|
|
2225
|
-
this.#onSourceArgumentMustBeProvided(assertion, expectResult);
|
|
2226
|
-
return;
|
|
2227
|
-
}
|
|
2228
|
-
return this[matcherNameText].match(this.#getType(assertion.source[0]));
|
|
2584
|
+
return this[matcherNameText].match(matchWorker, assertion.source[0]);
|
|
2229
2585
|
}
|
|
2230
2586
|
case "toHaveProperty": {
|
|
2231
|
-
if (assertion.
|
|
2232
|
-
this.#
|
|
2233
|
-
return;
|
|
2234
|
-
}
|
|
2235
|
-
const sourceType = this.#getType(assertion.source[0]);
|
|
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);
|
|
2587
|
+
if (!assertion.target[0]) {
|
|
2588
|
+
this.#onTargetArgumentMustBeProvided("key", assertion, onDiagnostics);
|
|
2240
2589
|
return;
|
|
2241
2590
|
}
|
|
2242
|
-
|
|
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);
|
|
2591
|
+
return this.toHaveProperty.match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
|
|
2252
2592
|
}
|
|
2253
2593
|
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);
|
|
2594
|
+
return this.toRaiseError.match(matchWorker, assertion.source[0], [...assertion.target], onDiagnostics);
|
|
2264
2595
|
}
|
|
2265
2596
|
default: {
|
|
2266
|
-
|
|
2267
|
-
|
|
2597
|
+
const text = ExpectDiagnosticText.matcherIsNotSupported(matcherNameText);
|
|
2598
|
+
const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
|
|
2599
|
+
onDiagnostics(Diagnostic.error(text, origin));
|
|
2268
2600
|
}
|
|
2269
2601
|
}
|
|
2602
|
+
return;
|
|
2270
2603
|
}
|
|
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 }]);
|
|
2604
|
+
#onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics) {
|
|
2605
|
+
const text = ExpectDiagnosticText.argumentOrTypeArgumentMustBeProvided("source", "Source");
|
|
2606
|
+
const origin = DiagnosticOrigin.fromNode(assertion.node.expression);
|
|
2607
|
+
onDiagnostics(Diagnostic.error(text, origin));
|
|
2285
2608
|
}
|
|
2286
|
-
#
|
|
2609
|
+
#onTargetArgumentMustBeProvided(argumentNameText, assertion, onDiagnostics) {
|
|
2610
|
+
const text = ExpectDiagnosticText.argumentMustBeProvided(argumentNameText);
|
|
2287
2611
|
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
|
-
]);
|
|
2295
|
-
}
|
|
2296
|
-
#onNotSupportedMatcherName(assertion, expectResult) {
|
|
2297
|
-
const matcherNameText = assertion.matcherName.getText();
|
|
2298
|
-
const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
|
|
2299
|
-
EventEmitter.dispatch([
|
|
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
|
-
]);
|
|
2612
|
+
onDiagnostics(Diagnostic.error(text, origin));
|
|
2325
2613
|
}
|
|
2326
|
-
#
|
|
2614
|
+
#onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics) {
|
|
2615
|
+
const text = ExpectDiagnosticText.argumentOrTypeArgumentMustBeProvided("target", "Target");
|
|
2327
2616
|
const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
|
|
2328
|
-
|
|
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 }]);
|
|
2617
|
+
onDiagnostics(Diagnostic.error(text, origin));
|
|
2349
2618
|
}
|
|
2350
2619
|
}
|
|
2351
2620
|
|
|
@@ -2445,7 +2714,7 @@ class ProjectService {
|
|
|
2445
2714
|
defaultCompilerOptions.module = "preserve";
|
|
2446
2715
|
}
|
|
2447
2716
|
if (Version.isSatisfiedWith(this.#compiler.version, "5.0")) {
|
|
2448
|
-
defaultCompilerOptions
|
|
2717
|
+
defaultCompilerOptions.allowImportingTsExtensions = true;
|
|
2449
2718
|
defaultCompilerOptions.moduleResolution = "bundler";
|
|
2450
2719
|
}
|
|
2451
2720
|
return defaultCompilerOptions;
|
|
@@ -2478,19 +2747,19 @@ class ProjectService {
|
|
|
2478
2747
|
class TestTreeWorker {
|
|
2479
2748
|
#compiler;
|
|
2480
2749
|
#cancellationToken;
|
|
2481
|
-
#
|
|
2750
|
+
#expectService;
|
|
2482
2751
|
#fileResult;
|
|
2483
2752
|
#hasOnly;
|
|
2484
2753
|
#position;
|
|
2485
2754
|
#resolvedConfig;
|
|
2486
|
-
constructor(resolvedConfig, compiler,
|
|
2755
|
+
constructor(resolvedConfig, compiler, typeChecker, options) {
|
|
2487
2756
|
this.#resolvedConfig = resolvedConfig;
|
|
2488
2757
|
this.#compiler = compiler;
|
|
2489
|
-
this.#expect = expect;
|
|
2490
2758
|
this.#cancellationToken = options.cancellationToken;
|
|
2491
2759
|
this.#fileResult = options.fileResult;
|
|
2492
2760
|
this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
|
|
2493
2761
|
this.#position = options.position;
|
|
2762
|
+
this.#expectService = new ExpectService(compiler, typeChecker);
|
|
2494
2763
|
}
|
|
2495
2764
|
#resolveRunMode(mode, member) {
|
|
2496
2765
|
if (member.flags & 1) {
|
|
@@ -2520,13 +2789,7 @@ class TestTreeWorker {
|
|
|
2520
2789
|
}
|
|
2521
2790
|
const validationError = member.validate();
|
|
2522
2791
|
if (validationError.length > 0) {
|
|
2523
|
-
EventEmitter.dispatch([
|
|
2524
|
-
"file:error",
|
|
2525
|
-
{
|
|
2526
|
-
diagnostics: validationError,
|
|
2527
|
-
result: this.#fileResult,
|
|
2528
|
-
},
|
|
2529
|
-
]);
|
|
2792
|
+
EventEmitter.dispatch(["file:error", { diagnostics: validationError, result: this.#fileResult }]);
|
|
2530
2793
|
break;
|
|
2531
2794
|
}
|
|
2532
2795
|
switch (member.brand) {
|
|
@@ -2554,28 +2817,25 @@ class TestTreeWorker {
|
|
|
2554
2817
|
EventEmitter.dispatch(["expect:skip", { result: expectResult }]);
|
|
2555
2818
|
return;
|
|
2556
2819
|
}
|
|
2557
|
-
|
|
2820
|
+
const onExpectDiagnostics = (diagnostics) => {
|
|
2558
2821
|
EventEmitter.dispatch([
|
|
2559
2822
|
"expect:error",
|
|
2560
|
-
{
|
|
2561
|
-
diagnostics: Diagnostic.fromDiagnostics([...assertion.diagnostics], this.#compiler),
|
|
2562
|
-
result: expectResult,
|
|
2563
|
-
},
|
|
2823
|
+
{ diagnostics: Array.isArray(diagnostics) ? diagnostics : [diagnostics], result: expectResult },
|
|
2564
2824
|
]);
|
|
2825
|
+
};
|
|
2826
|
+
if (assertion.diagnostics.size > 0 && assertion.matcherName.getText() !== "toRaiseError") {
|
|
2827
|
+
onExpectDiagnostics(Diagnostic.fromDiagnostics([...assertion.diagnostics], this.#compiler));
|
|
2565
2828
|
return;
|
|
2566
2829
|
}
|
|
2567
|
-
const matchResult = this.#
|
|
2568
|
-
if (matchResult
|
|
2830
|
+
const matchResult = this.#expectService.match(assertion, onExpectDiagnostics);
|
|
2831
|
+
if (!matchResult) {
|
|
2569
2832
|
return;
|
|
2570
2833
|
}
|
|
2571
2834
|
if (assertion.isNot ? !matchResult.isMatch : matchResult.isMatch) {
|
|
2572
2835
|
if (runMode & 1) {
|
|
2573
2836
|
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
|
-
]);
|
|
2837
|
+
const origin = DiagnosticOrigin.fromNode(assertion.node.expression.name);
|
|
2838
|
+
onExpectDiagnostics(Diagnostic.error(text, origin));
|
|
2579
2839
|
}
|
|
2580
2840
|
else {
|
|
2581
2841
|
EventEmitter.dispatch(["expect:pass", { result: expectResult }]);
|
|
@@ -2585,14 +2845,7 @@ class TestTreeWorker {
|
|
|
2585
2845
|
EventEmitter.dispatch(["expect:pass", { result: expectResult }]);
|
|
2586
2846
|
}
|
|
2587
2847
|
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 }]);
|
|
2848
|
+
EventEmitter.dispatch(["expect:fail", { diagnostics: matchResult.explain(), result: expectResult }]);
|
|
2596
2849
|
}
|
|
2597
2850
|
}
|
|
2598
2851
|
#visitDescribe(describe, runMode, parentResult) {
|
|
@@ -2705,13 +2958,12 @@ class TestFileRunner {
|
|
|
2705
2958
|
return;
|
|
2706
2959
|
}
|
|
2707
2960
|
const typeChecker = program.getTypeChecker();
|
|
2708
|
-
if (!
|
|
2961
|
+
if (!ExpectService.assertTypeChecker(typeChecker)) {
|
|
2709
2962
|
const text = "The required 'isTypeRelatedTo()' method is missing in the provided type checker.";
|
|
2710
2963
|
EventEmitter.dispatch(["file:error", { diagnostics: [Diagnostic.error(text)], result: fileResult }]);
|
|
2711
2964
|
return;
|
|
2712
2965
|
}
|
|
2713
|
-
const
|
|
2714
|
-
const testTreeWorker = new TestTreeWorker(this.#resolvedConfig, this.#compiler, expect, {
|
|
2966
|
+
const testTreeWorker = new TestTreeWorker(this.#resolvedConfig, this.#compiler, typeChecker, {
|
|
2715
2967
|
cancellationToken,
|
|
2716
2968
|
fileResult,
|
|
2717
2969
|
hasOnly: testTree.hasOnly,
|
|
@@ -2742,6 +2994,7 @@ class TaskRunner {
|
|
|
2742
2994
|
this.#eventEmitter.addHandler(cancellationHandler);
|
|
2743
2995
|
}
|
|
2744
2996
|
if (this.#resolvedConfig.watch === true) {
|
|
2997
|
+
await this.#run(testFiles, cancellationToken);
|
|
2745
2998
|
await this.#watch(testFiles, cancellationToken);
|
|
2746
2999
|
}
|
|
2747
3000
|
else {
|
|
@@ -2767,22 +3020,15 @@ class TaskRunner {
|
|
|
2767
3020
|
EventEmitter.dispatch(["target:end", { result: targetResult }]);
|
|
2768
3021
|
}
|
|
2769
3022
|
EventEmitter.dispatch(["run:end", { result }]);
|
|
2770
|
-
if (cancellationToken
|
|
3023
|
+
if (cancellationToken.reason === "failFast") {
|
|
2771
3024
|
cancellationToken.reset();
|
|
2772
3025
|
}
|
|
2773
3026
|
}
|
|
2774
3027
|
async #watch(testFiles, cancellationToken) {
|
|
2775
|
-
|
|
2776
|
-
const
|
|
3028
|
+
const watchService = new WatchService(this.#resolvedConfig, this.#selectService, testFiles);
|
|
3029
|
+
for await (const testFiles of watchService.watch(cancellationToken)) {
|
|
2777
3030
|
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);
|
|
3031
|
+
}
|
|
2786
3032
|
}
|
|
2787
3033
|
}
|
|
2788
3034
|
|
|
@@ -2793,7 +3039,7 @@ class TSTyche {
|
|
|
2793
3039
|
#selectService;
|
|
2794
3040
|
#storeService;
|
|
2795
3041
|
#taskRunner;
|
|
2796
|
-
static version = "2.
|
|
3042
|
+
static version = "2.1.1";
|
|
2797
3043
|
constructor(resolvedConfig, outputService, selectService, storeService) {
|
|
2798
3044
|
this.#resolvedConfig = resolvedConfig;
|
|
2799
3045
|
this.#outputService = outputService;
|
|
@@ -3009,14 +3255,14 @@ class OptionUsageText {
|
|
|
3009
3255
|
}
|
|
3010
3256
|
|
|
3011
3257
|
class OptionValidator {
|
|
3012
|
-
#
|
|
3258
|
+
#onDiagnostics;
|
|
3013
3259
|
#optionGroup;
|
|
3014
3260
|
#optionUsageText;
|
|
3015
3261
|
#storeService;
|
|
3016
|
-
constructor(optionGroup, storeService,
|
|
3262
|
+
constructor(optionGroup, storeService, onDiagnostics) {
|
|
3017
3263
|
this.#optionGroup = optionGroup;
|
|
3018
3264
|
this.#storeService = storeService;
|
|
3019
|
-
this.#
|
|
3265
|
+
this.#onDiagnostics = onDiagnostics;
|
|
3020
3266
|
this.#optionUsageText = new OptionUsageText(this.#optionGroup, this.#storeService);
|
|
3021
3267
|
}
|
|
3022
3268
|
async check(optionName, optionValue, optionBrand, origin) {
|
|
@@ -3024,13 +3270,13 @@ class OptionValidator {
|
|
|
3024
3270
|
case "config":
|
|
3025
3271
|
case "rootPath": {
|
|
3026
3272
|
if (!existsSync(optionValue)) {
|
|
3027
|
-
this.#
|
|
3273
|
+
this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.fileDoesNotExist(optionValue), origin));
|
|
3028
3274
|
}
|
|
3029
3275
|
break;
|
|
3030
3276
|
}
|
|
3031
3277
|
case "target": {
|
|
3032
3278
|
if ((await this.#storeService.validateTag(optionValue)) === false) {
|
|
3033
|
-
this.#
|
|
3279
|
+
this.#onDiagnostics(Diagnostic.error([
|
|
3034
3280
|
ConfigDiagnosticText.versionIsNotSupported(optionValue),
|
|
3035
3281
|
...(await this.#optionUsageText.get(optionName, optionBrand)),
|
|
3036
3282
|
], origin));
|
|
@@ -3040,14 +3286,14 @@ class OptionValidator {
|
|
|
3040
3286
|
case "testFileMatch": {
|
|
3041
3287
|
for (const segment of ["/", "../"]) {
|
|
3042
3288
|
if (optionValue.startsWith(segment)) {
|
|
3043
|
-
this.#
|
|
3289
|
+
this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.testFileMatchCannotStartWith(segment), origin));
|
|
3044
3290
|
}
|
|
3045
3291
|
}
|
|
3046
3292
|
break;
|
|
3047
3293
|
}
|
|
3048
3294
|
case "watch": {
|
|
3049
3295
|
if (Environment.isCi) {
|
|
3050
|
-
this.#
|
|
3296
|
+
this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.watchCannotBeEnabled(), origin));
|
|
3051
3297
|
}
|
|
3052
3298
|
break;
|
|
3053
3299
|
}
|
|
@@ -3058,27 +3304,27 @@ class OptionValidator {
|
|
|
3058
3304
|
class CommandLineOptionsWorker {
|
|
3059
3305
|
#commandLineOptionDefinitions;
|
|
3060
3306
|
#commandLineOptions;
|
|
3061
|
-
#
|
|
3307
|
+
#onDiagnostics;
|
|
3062
3308
|
#optionGroup = 2;
|
|
3063
3309
|
#optionUsageText;
|
|
3064
3310
|
#optionValidator;
|
|
3065
3311
|
#pathMatch;
|
|
3066
3312
|
#storeService;
|
|
3067
|
-
constructor(commandLineOptions, pathMatch, storeService,
|
|
3313
|
+
constructor(commandLineOptions, pathMatch, storeService, onDiagnostics) {
|
|
3068
3314
|
this.#commandLineOptions = commandLineOptions;
|
|
3069
3315
|
this.#pathMatch = pathMatch;
|
|
3070
3316
|
this.#storeService = storeService;
|
|
3071
|
-
this.#
|
|
3317
|
+
this.#onDiagnostics = onDiagnostics;
|
|
3072
3318
|
this.#commandLineOptionDefinitions = OptionDefinitionsMap.for(this.#optionGroup);
|
|
3073
3319
|
this.#optionUsageText = new OptionUsageText(this.#optionGroup, this.#storeService);
|
|
3074
|
-
this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#
|
|
3320
|
+
this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostics);
|
|
3075
3321
|
}
|
|
3076
3322
|
async #onExpectsValue(optionDefinition) {
|
|
3077
3323
|
const text = [
|
|
3078
3324
|
ConfigDiagnosticText.expectsValue(optionDefinition.name, this.#optionGroup),
|
|
3079
3325
|
...(await this.#optionUsageText.get(optionDefinition.name, optionDefinition.brand)),
|
|
3080
3326
|
];
|
|
3081
|
-
this.#
|
|
3327
|
+
this.#onDiagnostics(Diagnostic.error(text));
|
|
3082
3328
|
}
|
|
3083
3329
|
async parse(commandLineArgs) {
|
|
3084
3330
|
let index = 0;
|
|
@@ -3092,11 +3338,11 @@ class CommandLineOptionsWorker {
|
|
|
3092
3338
|
index = await this.#parseOptionValue(commandLineArgs, index, optionDefinition);
|
|
3093
3339
|
}
|
|
3094
3340
|
else {
|
|
3095
|
-
this.#
|
|
3341
|
+
this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.unknownOption(arg)));
|
|
3096
3342
|
}
|
|
3097
3343
|
}
|
|
3098
3344
|
else if (arg.startsWith("-")) {
|
|
3099
|
-
this.#
|
|
3345
|
+
this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.unknownOption(arg)));
|
|
3100
3346
|
}
|
|
3101
3347
|
else {
|
|
3102
3348
|
this.#pathMatch.push(Path.normalizeSlashes(arg));
|
|
@@ -3162,18 +3408,18 @@ class ConfigFileOptionsWorker {
|
|
|
3162
3408
|
#configFileOptionDefinitions;
|
|
3163
3409
|
#configFileOptions;
|
|
3164
3410
|
#configFilePath;
|
|
3165
|
-
#
|
|
3411
|
+
#onDiagnostics;
|
|
3166
3412
|
#optionGroup = 4;
|
|
3167
3413
|
#optionValidator;
|
|
3168
3414
|
#storeService;
|
|
3169
|
-
constructor(compiler, configFileOptions, configFilePath, storeService,
|
|
3415
|
+
constructor(compiler, configFileOptions, configFilePath, storeService, onDiagnostics) {
|
|
3170
3416
|
this.#compiler = compiler;
|
|
3171
3417
|
this.#configFileOptions = configFileOptions;
|
|
3172
3418
|
this.#configFilePath = configFilePath;
|
|
3173
3419
|
this.#storeService = storeService;
|
|
3174
|
-
this.#
|
|
3420
|
+
this.#onDiagnostics = onDiagnostics;
|
|
3175
3421
|
this.#configFileOptionDefinitions = OptionDefinitionsMap.for(this.#optionGroup);
|
|
3176
|
-
this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#
|
|
3422
|
+
this.#optionValidator = new OptionValidator(this.#optionGroup, this.#storeService, this.#onDiagnostics);
|
|
3177
3423
|
}
|
|
3178
3424
|
#isDoubleQuotedString(node, sourceFile) {
|
|
3179
3425
|
return (node.kind === this.#compiler.SyntaxKind.StringLiteral &&
|
|
@@ -3182,25 +3428,26 @@ class ConfigFileOptionsWorker {
|
|
|
3182
3428
|
async parse(sourceText) {
|
|
3183
3429
|
const sourceFile = this.#compiler.parseJsonText(this.#configFilePath, sourceText);
|
|
3184
3430
|
if (sourceFile.parseDiagnostics.length > 0) {
|
|
3185
|
-
|
|
3186
|
-
this.#onDiagnostic(diagnostic);
|
|
3187
|
-
}
|
|
3431
|
+
this.#onDiagnostics(Diagnostic.fromDiagnostics(sourceFile.parseDiagnostics, this.#compiler));
|
|
3188
3432
|
return;
|
|
3189
3433
|
}
|
|
3190
3434
|
const rootExpression = sourceFile.statements[0]?.expression;
|
|
3191
|
-
if (
|
|
3192
|
-
|
|
3193
|
-
|
|
3435
|
+
if (!rootExpression) {
|
|
3436
|
+
return;
|
|
3437
|
+
}
|
|
3438
|
+
if (!this.#compiler.isObjectLiteralExpression(rootExpression)) {
|
|
3439
|
+
const origin = DiagnosticOrigin.fromJsonNode(rootExpression, sourceFile, this.#skipTrivia);
|
|
3440
|
+
this.#onDiagnostics(Diagnostic.error("The root value of a configuration file must be an object literal.", origin));
|
|
3194
3441
|
return;
|
|
3195
3442
|
}
|
|
3196
3443
|
for (const property of rootExpression.properties) {
|
|
3197
3444
|
if (this.#compiler.isPropertyAssignment(property)) {
|
|
3198
3445
|
if (!this.#isDoubleQuotedString(property.name, sourceFile)) {
|
|
3199
|
-
const origin = DiagnosticOrigin.fromJsonNode(property, sourceFile, this.#skipTrivia);
|
|
3200
|
-
this.#
|
|
3446
|
+
const origin = DiagnosticOrigin.fromJsonNode(property.name, sourceFile, this.#skipTrivia);
|
|
3447
|
+
this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.doubleQuotesExpected(), origin));
|
|
3201
3448
|
continue;
|
|
3202
3449
|
}
|
|
3203
|
-
const optionName =
|
|
3450
|
+
const optionName = property.name.text;
|
|
3204
3451
|
if (optionName === "$schema") {
|
|
3205
3452
|
continue;
|
|
3206
3453
|
}
|
|
@@ -3209,8 +3456,8 @@ class ConfigFileOptionsWorker {
|
|
|
3209
3456
|
this.#configFileOptions[optionDefinition.name] = await this.#parseOptionValue(sourceFile, property.initializer, optionDefinition);
|
|
3210
3457
|
}
|
|
3211
3458
|
else {
|
|
3212
|
-
const origin = DiagnosticOrigin.fromJsonNode(property, sourceFile, this.#skipTrivia);
|
|
3213
|
-
this.#
|
|
3459
|
+
const origin = DiagnosticOrigin.fromJsonNode(property.name, sourceFile, this.#skipTrivia);
|
|
3460
|
+
this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.unknownOption(optionName), origin));
|
|
3214
3461
|
}
|
|
3215
3462
|
}
|
|
3216
3463
|
}
|
|
@@ -3233,7 +3480,7 @@ class ConfigFileOptionsWorker {
|
|
|
3233
3480
|
case this.#compiler.SyntaxKind.StringLiteral: {
|
|
3234
3481
|
if (!this.#isDoubleQuotedString(valueExpression, sourceFile)) {
|
|
3235
3482
|
const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
|
|
3236
|
-
this.#
|
|
3483
|
+
this.#onDiagnostics(Diagnostic.error(ConfigDiagnosticText.doubleQuotesExpected(), origin));
|
|
3237
3484
|
return;
|
|
3238
3485
|
}
|
|
3239
3486
|
if (optionDefinition.brand === "string") {
|
|
@@ -3262,15 +3509,9 @@ class ConfigFileOptionsWorker {
|
|
|
3262
3509
|
? ConfigDiagnosticText.expectsListItemType(optionDefinition.name, optionDefinition.brand)
|
|
3263
3510
|
: ConfigDiagnosticText.requiresValueType(optionDefinition.name, optionDefinition.brand, this.#optionGroup);
|
|
3264
3511
|
const origin = DiagnosticOrigin.fromJsonNode(valueExpression, sourceFile, this.#skipTrivia);
|
|
3265
|
-
this.#
|
|
3512
|
+
this.#onDiagnostics(Diagnostic.error(text, origin));
|
|
3266
3513
|
return;
|
|
3267
3514
|
}
|
|
3268
|
-
#resolvePropertyName({ name }) {
|
|
3269
|
-
if ("text" in name) {
|
|
3270
|
-
return name.text;
|
|
3271
|
-
}
|
|
3272
|
-
return "";
|
|
3273
|
-
}
|
|
3274
3515
|
#skipTrivia(position, sourceFile) {
|
|
3275
3516
|
const { text } = sourceFile.getSourceFile();
|
|
3276
3517
|
while (position < text.length) {
|
|
@@ -3312,7 +3553,7 @@ class ConfigFileOptionsWorker {
|
|
|
3312
3553
|
const defaultOptions = {
|
|
3313
3554
|
failFast: false,
|
|
3314
3555
|
rootPath: "./",
|
|
3315
|
-
target: [Environment.typescriptPath
|
|
3556
|
+
target: [Environment.typescriptPath != null ? "current" : "latest"],
|
|
3316
3557
|
testFileMatch: ["**/*.tst.*", "**/__typetests__/*.test.*", "**/typetests/*.test.*"],
|
|
3317
3558
|
};
|
|
3318
3559
|
class ConfigService {
|
|
@@ -3326,13 +3567,13 @@ class ConfigService {
|
|
|
3326
3567
|
this.#compiler = compiler;
|
|
3327
3568
|
this.#storeService = storeService;
|
|
3328
3569
|
}
|
|
3329
|
-
#
|
|
3330
|
-
EventEmitter.dispatch(["config:error", { diagnostics: [
|
|
3570
|
+
#onDiagnostics(diagnostics) {
|
|
3571
|
+
EventEmitter.dispatch(["config:error", { diagnostics: Array.isArray(diagnostics) ? diagnostics : [diagnostics] }]);
|
|
3331
3572
|
}
|
|
3332
3573
|
async parseCommandLine(commandLineArgs) {
|
|
3333
3574
|
this.#commandLineOptions = {};
|
|
3334
3575
|
this.#pathMatch = [];
|
|
3335
|
-
const commandLineWorker = new CommandLineOptionsWorker(this.#commandLineOptions, this.#pathMatch, this.#storeService, this.#
|
|
3576
|
+
const commandLineWorker = new CommandLineOptionsWorker(this.#commandLineOptions, this.#pathMatch, this.#storeService, this.#onDiagnostics);
|
|
3336
3577
|
await commandLineWorker.parse(commandLineArgs);
|
|
3337
3578
|
if (this.#commandLineOptions.config != null) {
|
|
3338
3579
|
this.#configFilePath = this.#commandLineOptions.config;
|
|
@@ -3349,7 +3590,7 @@ class ConfigService {
|
|
|
3349
3590
|
const configFileText = await fs.readFile(this.#configFilePath, {
|
|
3350
3591
|
encoding: "utf8",
|
|
3351
3592
|
});
|
|
3352
|
-
const configFileWorker = new ConfigFileOptionsWorker(this.#compiler, this.#configFileOptions, this.#configFilePath, this.#storeService, this.#
|
|
3593
|
+
const configFileWorker = new ConfigFileOptionsWorker(this.#compiler, this.#configFileOptions, this.#configFilePath, this.#storeService, this.#onDiagnostics);
|
|
3353
3594
|
await configFileWorker.parse(configFileText);
|
|
3354
3595
|
}
|
|
3355
3596
|
resolveConfig() {
|
|
@@ -3382,27 +3623,27 @@ class StoreDiagnosticText {
|
|
|
3382
3623
|
return `Failed to fetch metadata of the 'typescript' package from '${registryUrl.toString()}'.`;
|
|
3383
3624
|
}
|
|
3384
3625
|
static failedWithStatusCode(code) {
|
|
3385
|
-
return `Request failed with status code ${
|
|
3626
|
+
return `Request failed with status code ${code}.`;
|
|
3386
3627
|
}
|
|
3387
3628
|
static maybeNetworkConnectionIssue() {
|
|
3388
3629
|
return "Might be there is an issue with the registry or the network connection.";
|
|
3389
3630
|
}
|
|
3390
3631
|
static setupTimeoutExceeded(timeout) {
|
|
3391
|
-
return `Setup timeout of ${
|
|
3632
|
+
return `Setup timeout of ${timeout / 1000}s was exceeded.`;
|
|
3392
3633
|
}
|
|
3393
3634
|
}
|
|
3394
3635
|
|
|
3395
3636
|
class ManifestWorker {
|
|
3396
3637
|
#manifestFileName = "store-manifest.json";
|
|
3397
3638
|
#manifestFilePath;
|
|
3398
|
-
#
|
|
3639
|
+
#onDiagnostics;
|
|
3399
3640
|
#registryUrl = new URL("https://registry.npmjs.org");
|
|
3400
3641
|
#storePath;
|
|
3401
3642
|
#timeout = Environment.timeout * 1000;
|
|
3402
3643
|
#version = "1";
|
|
3403
|
-
constructor(storePath,
|
|
3644
|
+
constructor(storePath, onDiagnostics) {
|
|
3404
3645
|
this.#storePath = storePath;
|
|
3405
|
-
this.#
|
|
3646
|
+
this.#onDiagnostics = onDiagnostics;
|
|
3406
3647
|
this.#manifestFilePath = Path.join(storePath, this.#manifestFileName);
|
|
3407
3648
|
}
|
|
3408
3649
|
async #create() {
|
|
@@ -3432,7 +3673,7 @@ class ManifestWorker {
|
|
|
3432
3673
|
signal: AbortSignal.timeout(this.#timeout),
|
|
3433
3674
|
});
|
|
3434
3675
|
if (!response.ok) {
|
|
3435
|
-
this.#
|
|
3676
|
+
this.#onDiagnostics(Diagnostic.error([
|
|
3436
3677
|
StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
|
|
3437
3678
|
StoreDiagnosticText.failedWithStatusCode(response.status),
|
|
3438
3679
|
]));
|
|
@@ -3445,13 +3686,13 @@ class ManifestWorker {
|
|
|
3445
3686
|
return;
|
|
3446
3687
|
}
|
|
3447
3688
|
if (error instanceof Error && error.name === "TimeoutError") {
|
|
3448
|
-
this.#
|
|
3689
|
+
this.#onDiagnostics(Diagnostic.error([
|
|
3449
3690
|
StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
|
|
3450
3691
|
StoreDiagnosticText.setupTimeoutExceeded(this.#timeout),
|
|
3451
3692
|
]));
|
|
3452
3693
|
}
|
|
3453
3694
|
else {
|
|
3454
|
-
this.#
|
|
3695
|
+
this.#onDiagnostics(Diagnostic.error([
|
|
3455
3696
|
StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
|
|
3456
3697
|
StoreDiagnosticText.maybeNetworkConnectionIssue(),
|
|
3457
3698
|
]));
|
|
@@ -3486,9 +3727,9 @@ class ManifestWorker {
|
|
|
3486
3727
|
manifestText = await fs.readFile(this.#manifestFilePath, { encoding: "utf8" });
|
|
3487
3728
|
}
|
|
3488
3729
|
catch (error) {
|
|
3489
|
-
this.#
|
|
3730
|
+
this.#onDiagnostics(Diagnostic.fromError("Failed to open store manifest.", error));
|
|
3490
3731
|
}
|
|
3491
|
-
if (manifestText
|
|
3732
|
+
if (!manifestText) {
|
|
3492
3733
|
return;
|
|
3493
3734
|
}
|
|
3494
3735
|
try {
|
|
@@ -3496,7 +3737,7 @@ class ManifestWorker {
|
|
|
3496
3737
|
}
|
|
3497
3738
|
catch {
|
|
3498
3739
|
}
|
|
3499
|
-
if (manifest
|
|
3740
|
+
if (!manifest || manifest.$version !== this.#version) {
|
|
3500
3741
|
await fs.rm(this.#storePath, { force: true, recursive: true });
|
|
3501
3742
|
return this.#create();
|
|
3502
3743
|
}
|
|
@@ -3536,7 +3777,7 @@ class Lock {
|
|
|
3536
3777
|
if (!isLocked) {
|
|
3537
3778
|
return isLocked;
|
|
3538
3779
|
}
|
|
3539
|
-
if (options?.timeout
|
|
3780
|
+
if (!options?.timeout) {
|
|
3540
3781
|
return isLocked;
|
|
3541
3782
|
}
|
|
3542
3783
|
const waitStartTime = Date.now();
|
|
@@ -3545,7 +3786,7 @@ class Lock {
|
|
|
3545
3786
|
break;
|
|
3546
3787
|
}
|
|
3547
3788
|
if (Date.now() - waitStartTime > options.timeout) {
|
|
3548
|
-
options.
|
|
3789
|
+
options.onDiagnostics?.(`Lock wait timeout of ${options.timeout / 1000}s was exceeded.`);
|
|
3549
3790
|
break;
|
|
3550
3791
|
}
|
|
3551
3792
|
await Lock.#sleep(1000);
|
|
@@ -3562,13 +3803,13 @@ class Lock {
|
|
|
3562
3803
|
}
|
|
3563
3804
|
|
|
3564
3805
|
class PackageInstaller {
|
|
3565
|
-
#
|
|
3806
|
+
#onDiagnostics;
|
|
3566
3807
|
#readyFileName = "__ready__";
|
|
3567
3808
|
#storePath;
|
|
3568
3809
|
#timeout = Environment.timeout * 1000;
|
|
3569
|
-
constructor(storePath,
|
|
3810
|
+
constructor(storePath, onDiagnostics) {
|
|
3570
3811
|
this.#storePath = storePath;
|
|
3571
|
-
this.#
|
|
3812
|
+
this.#onDiagnostics = onDiagnostics;
|
|
3572
3813
|
}
|
|
3573
3814
|
async ensure(compilerVersion, cancellationToken) {
|
|
3574
3815
|
const installationPath = Path.join(this.#storePath, compilerVersion);
|
|
@@ -3579,8 +3820,8 @@ class PackageInstaller {
|
|
|
3579
3820
|
}
|
|
3580
3821
|
if (await Lock.isLocked(installationPath, {
|
|
3581
3822
|
cancellationToken,
|
|
3582
|
-
|
|
3583
|
-
this.#
|
|
3823
|
+
onDiagnostics: (text) => {
|
|
3824
|
+
this.#onDiagnostics(Diagnostic.error([`Failed to install 'typescript@${compilerVersion}'.`, text]));
|
|
3584
3825
|
},
|
|
3585
3826
|
timeout: this.#timeout,
|
|
3586
3827
|
})) {
|
|
@@ -3606,7 +3847,7 @@ class PackageInstaller {
|
|
|
3606
3847
|
return modulePath;
|
|
3607
3848
|
}
|
|
3608
3849
|
catch (error) {
|
|
3609
|
-
this.#
|
|
3850
|
+
this.#onDiagnostics(Diagnostic.fromError(`Failed to install 'typescript@${compilerVersion}'.`, error));
|
|
3610
3851
|
}
|
|
3611
3852
|
finally {
|
|
3612
3853
|
lock.release();
|
|
@@ -3630,9 +3871,9 @@ class PackageInstaller {
|
|
|
3630
3871
|
resolve();
|
|
3631
3872
|
}
|
|
3632
3873
|
if (signal != null) {
|
|
3633
|
-
reject(new Error(`setup timeout of ${
|
|
3874
|
+
reject(new Error(`setup timeout of ${this.#timeout / 1000}s was exceeded`));
|
|
3634
3875
|
}
|
|
3635
|
-
reject(new Error(`process exited with code ${
|
|
3876
|
+
reject(new Error(`process exited with code ${code}`));
|
|
3636
3877
|
});
|
|
3637
3878
|
});
|
|
3638
3879
|
}
|
|
@@ -3646,8 +3887,8 @@ class StoreService {
|
|
|
3646
3887
|
#storePath;
|
|
3647
3888
|
constructor() {
|
|
3648
3889
|
this.#storePath = Environment.storePath;
|
|
3649
|
-
this.#packageInstaller = new PackageInstaller(this.#storePath, this.#
|
|
3650
|
-
this.#manifestWorker = new ManifestWorker(this.#storePath, this.#
|
|
3890
|
+
this.#packageInstaller = new PackageInstaller(this.#storePath, this.#onDiagnostics);
|
|
3891
|
+
this.#manifestWorker = new ManifestWorker(this.#storePath, this.#onDiagnostics);
|
|
3651
3892
|
}
|
|
3652
3893
|
async getSupportedTags() {
|
|
3653
3894
|
await this.open();
|
|
@@ -3660,9 +3901,9 @@ class StoreService {
|
|
|
3660
3901
|
if (tag === "current") {
|
|
3661
3902
|
return;
|
|
3662
3903
|
}
|
|
3663
|
-
const version = await this
|
|
3664
|
-
if (version
|
|
3665
|
-
this.#
|
|
3904
|
+
const version = await this.#resolveTag(tag);
|
|
3905
|
+
if (!version) {
|
|
3906
|
+
this.#onDiagnostics(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
|
|
3666
3907
|
return;
|
|
3667
3908
|
}
|
|
3668
3909
|
return this.#packageInstaller.ensure(version, cancellationToken);
|
|
@@ -3677,9 +3918,9 @@ class StoreService {
|
|
|
3677
3918
|
modulePath = Environment.typescriptPath;
|
|
3678
3919
|
}
|
|
3679
3920
|
else {
|
|
3680
|
-
const version = await this
|
|
3681
|
-
if (version
|
|
3682
|
-
this.#
|
|
3921
|
+
const version = await this.#resolveTag(tag);
|
|
3922
|
+
if (!version) {
|
|
3923
|
+
this.#onDiagnostics(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
|
|
3683
3924
|
return;
|
|
3684
3925
|
}
|
|
3685
3926
|
compilerInstance = this.#compilerInstanceCache.get(version);
|
|
@@ -3705,6 +3946,7 @@ class StoreService {
|
|
|
3705
3946
|
continue;
|
|
3706
3947
|
}
|
|
3707
3948
|
const toExpose = [
|
|
3949
|
+
"getTypeOfSymbol",
|
|
3708
3950
|
"isTypeRelatedTo",
|
|
3709
3951
|
"relation: { assignable: assignableRelation, identity: identityRelation, subtype: strictSubtypeRelation }",
|
|
3710
3952
|
];
|
|
@@ -3715,7 +3957,7 @@ class StoreService {
|
|
|
3715
3957
|
}
|
|
3716
3958
|
return module.exports;
|
|
3717
3959
|
}
|
|
3718
|
-
#
|
|
3960
|
+
#onDiagnostics(diagnostic) {
|
|
3719
3961
|
EventEmitter.dispatch(["store:error", { diagnostics: [diagnostic] }]);
|
|
3720
3962
|
}
|
|
3721
3963
|
async open() {
|
|
@@ -3724,10 +3966,7 @@ class StoreService {
|
|
|
3724
3966
|
}
|
|
3725
3967
|
this.#manifest = await this.#manifestWorker.open();
|
|
3726
3968
|
}
|
|
3727
|
-
async resolveTag(tag) {
|
|
3728
|
-
if (tag === "current") {
|
|
3729
|
-
return tag;
|
|
3730
|
-
}
|
|
3969
|
+
async #resolveTag(tag) {
|
|
3731
3970
|
await this.open();
|
|
3732
3971
|
if (!this.#manifest) {
|
|
3733
3972
|
return;
|
|
@@ -3752,7 +3991,7 @@ class StoreService {
|
|
|
3752
3991
|
(!Version.isVersionTag(tag) ||
|
|
3753
3992
|
(this.#manifest.resolutions["latest"] != null &&
|
|
3754
3993
|
Version.isGreaterThan(tag, this.#manifest.resolutions["latest"])))) {
|
|
3755
|
-
this.#
|
|
3994
|
+
this.#onDiagnostics(Diagnostic.warning([
|
|
3756
3995
|
"Failed to update metadata of the 'typescript' package from the registry.",
|
|
3757
3996
|
`The resolution of the '${tag}' tag may be outdated.`,
|
|
3758
3997
|
]));
|
|
@@ -3789,7 +4028,7 @@ class Cli {
|
|
|
3789
4028
|
await this.#storeService.update();
|
|
3790
4029
|
return;
|
|
3791
4030
|
}
|
|
3792
|
-
const compiler = await this.#storeService.load(Environment.typescriptPath
|
|
4031
|
+
const compiler = await this.#storeService.load(Environment.typescriptPath != null ? "current" : "latest");
|
|
3793
4032
|
if (!compiler) {
|
|
3794
4033
|
return;
|
|
3795
4034
|
}
|
|
@@ -3855,28 +4094,33 @@ class Cli {
|
|
|
3855
4094
|
this.#eventEmitter.removeHandlers();
|
|
3856
4095
|
}
|
|
3857
4096
|
#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();
|
|
4097
|
+
return new Promise((resolve) => {
|
|
4098
|
+
const watchers = [];
|
|
4099
|
+
cancellationToken.reset();
|
|
4100
|
+
this.#outputService.writeMessage(waitingForFileChangesText());
|
|
4101
|
+
const onChanged = () => {
|
|
4102
|
+
cancellationToken.cancel("configChange");
|
|
4103
|
+
for (const watcher of watchers) {
|
|
4104
|
+
watcher.close();
|
|
3872
4105
|
}
|
|
4106
|
+
resolve();
|
|
3873
4107
|
};
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
4108
|
+
watchers.push(new FileWatcher(resolvedConfig.configFilePath, onChanged));
|
|
4109
|
+
if (selectService != null) {
|
|
4110
|
+
const onChangedTestFile = (filePath) => {
|
|
4111
|
+
if (selectService.isTestFile(filePath)) {
|
|
4112
|
+
onChanged();
|
|
4113
|
+
}
|
|
4114
|
+
};
|
|
4115
|
+
const onRemoved = () => {
|
|
4116
|
+
};
|
|
4117
|
+
watchers.push(new Watcher(resolvedConfig.rootPath, onChangedTestFile, onRemoved, { recursive: true }));
|
|
4118
|
+
}
|
|
4119
|
+
for (const watcher of watchers) {
|
|
4120
|
+
watcher.watch();
|
|
4121
|
+
}
|
|
4122
|
+
});
|
|
3879
4123
|
}
|
|
3880
4124
|
}
|
|
3881
4125
|
|
|
3882
|
-
export { Assertion, CancellationHandler, CancellationReason, CancellationToken, Cli, CollectService, Color, ConfigDiagnosticText, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, DiagnosticOrigin, Environment, EventEmitter, ExitCodeHandler,
|
|
4126
|
+
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 };
|