tstyche 1.0.0-beta.6 → 1.0.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +3 -3
- package/build/tstyche.d.ts +108 -71
- package/build/tstyche.js +679 -577
- package/package.json +21 -22
package/build/tstyche.js
CHANGED
|
@@ -79,18 +79,6 @@ class Environment {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
var Color;
|
|
83
|
-
(function (Color) {
|
|
84
|
-
Color["Reset"] = "0";
|
|
85
|
-
Color["Red"] = "31";
|
|
86
|
-
Color["Green"] = "32";
|
|
87
|
-
Color["Yellow"] = "33";
|
|
88
|
-
Color["Blue"] = "34";
|
|
89
|
-
Color["Magenta"] = "35";
|
|
90
|
-
Color["Cyan"] = "36";
|
|
91
|
-
Color["Gray"] = "90";
|
|
92
|
-
})(Color || (Color = {}));
|
|
93
|
-
|
|
94
82
|
class Scribbler {
|
|
95
83
|
#noColor;
|
|
96
84
|
constructor(options) {
|
|
@@ -123,7 +111,7 @@ class Scribbler {
|
|
|
123
111
|
}
|
|
124
112
|
if (element.type === "ansi" && !this.#noColor) {
|
|
125
113
|
const flags = typeof element.props?.["escapes"] === "string" || Array.isArray(element.props?.["escapes"])
|
|
126
|
-
? element.props
|
|
114
|
+
? element.props["escapes"]
|
|
127
115
|
: undefined;
|
|
128
116
|
if (flags != null) {
|
|
129
117
|
return this.#escapeSequence(flags);
|
|
@@ -378,6 +366,154 @@ function fileViewText(lines, addEmptyFinalLine) {
|
|
|
378
366
|
addEmptyFinalLine ? Scribbler.createElement(Line, null) : undefined));
|
|
379
367
|
}
|
|
380
368
|
|
|
369
|
+
class JsonText {
|
|
370
|
+
props;
|
|
371
|
+
constructor(props) {
|
|
372
|
+
this.props = props;
|
|
373
|
+
}
|
|
374
|
+
render() {
|
|
375
|
+
return Scribbler.createElement(Line, null, JSON.stringify(this.#sortObject(this.props.input), null, 2));
|
|
376
|
+
}
|
|
377
|
+
#sortObject(target) {
|
|
378
|
+
if (Array.isArray(target)) {
|
|
379
|
+
return target;
|
|
380
|
+
}
|
|
381
|
+
return Object.keys(target)
|
|
382
|
+
.sort()
|
|
383
|
+
.reduce((result, key) => {
|
|
384
|
+
result[key] = target[key];
|
|
385
|
+
return result;
|
|
386
|
+
}, {});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function formattedText(input) {
|
|
390
|
+
if (typeof input === "string") {
|
|
391
|
+
return Scribbler.createElement(Line, null, input);
|
|
392
|
+
}
|
|
393
|
+
return Scribbler.createElement(JsonText, { input: input });
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const usageExamples = [
|
|
397
|
+
["tstyche", "Run all tests."],
|
|
398
|
+
["tstyche path/to/first.test.ts second", "Only run the test files with matching path."],
|
|
399
|
+
["tstyche --target 4.7,4.8,latest", "Test on all specified versions of TypeScript."],
|
|
400
|
+
];
|
|
401
|
+
class HintText {
|
|
402
|
+
props;
|
|
403
|
+
constructor(props) {
|
|
404
|
+
this.props = props;
|
|
405
|
+
}
|
|
406
|
+
render() {
|
|
407
|
+
return (Scribbler.createElement(Text, { indent: 1, color: "90" }, this.props.children));
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
class HelpHeaderText {
|
|
411
|
+
props;
|
|
412
|
+
constructor(props) {
|
|
413
|
+
this.props = props;
|
|
414
|
+
}
|
|
415
|
+
render() {
|
|
416
|
+
const hint = (Scribbler.createElement(HintText, null,
|
|
417
|
+
Scribbler.createElement(Text, null, this.props.tstycheVersion)));
|
|
418
|
+
return (Scribbler.createElement(Line, null,
|
|
419
|
+
Scribbler.createElement(Text, null, "The TSTyche Type Test Runner"),
|
|
420
|
+
hint));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
class CommandText {
|
|
424
|
+
props;
|
|
425
|
+
constructor(props) {
|
|
426
|
+
this.props = props;
|
|
427
|
+
}
|
|
428
|
+
render() {
|
|
429
|
+
let hint = undefined;
|
|
430
|
+
if (this.props.hint != null) {
|
|
431
|
+
hint = Scribbler.createElement(HintText, null, this.props.hint);
|
|
432
|
+
}
|
|
433
|
+
return (Scribbler.createElement(Line, { indent: 1 },
|
|
434
|
+
Scribbler.createElement(Text, { color: "34" }, this.props.text),
|
|
435
|
+
hint));
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
class OptionDescriptionText {
|
|
439
|
+
props;
|
|
440
|
+
constructor(props) {
|
|
441
|
+
this.props = props;
|
|
442
|
+
}
|
|
443
|
+
render() {
|
|
444
|
+
return Scribbler.createElement(Line, { indent: 1 }, this.props.text);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
class CliUsageText {
|
|
448
|
+
render() {
|
|
449
|
+
const usageText = usageExamples.map(([commandText, descriptionText]) => (Scribbler.createElement(Text, null,
|
|
450
|
+
Scribbler.createElement(CommandText, { text: commandText }),
|
|
451
|
+
Scribbler.createElement(OptionDescriptionText, { text: descriptionText }),
|
|
452
|
+
Scribbler.createElement(Line, null))));
|
|
453
|
+
return Scribbler.createElement(Text, null, usageText);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
class OptionNameText {
|
|
457
|
+
props;
|
|
458
|
+
constructor(props) {
|
|
459
|
+
this.props = props;
|
|
460
|
+
}
|
|
461
|
+
render() {
|
|
462
|
+
return Scribbler.createElement(Text, null,
|
|
463
|
+
"--",
|
|
464
|
+
this.props.text);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
class OptionHintText {
|
|
468
|
+
props;
|
|
469
|
+
constructor(props) {
|
|
470
|
+
this.props = props;
|
|
471
|
+
}
|
|
472
|
+
render() {
|
|
473
|
+
if (this.props.definition.brand === "list") {
|
|
474
|
+
return (Scribbler.createElement(Text, null,
|
|
475
|
+
this.props.definition.brand,
|
|
476
|
+
" of ",
|
|
477
|
+
this.props.definition.items.brand,
|
|
478
|
+
"s"));
|
|
479
|
+
}
|
|
480
|
+
return Scribbler.createElement(Text, null, this.props.definition.brand);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
class CliOptionsText {
|
|
484
|
+
props;
|
|
485
|
+
constructor(props) {
|
|
486
|
+
this.props = props;
|
|
487
|
+
}
|
|
488
|
+
render() {
|
|
489
|
+
const definitions = Array.from(this.props.optionDefinitions.values());
|
|
490
|
+
const optionsText = definitions.map((definition) => (Scribbler.createElement(Text, null,
|
|
491
|
+
Scribbler.createElement(CommandText, { text: Scribbler.createElement(OptionNameText, { text: definition.name }), hint: Scribbler.createElement(OptionHintText, { definition: definition }) }),
|
|
492
|
+
Scribbler.createElement(OptionDescriptionText, { text: definition.description }),
|
|
493
|
+
Scribbler.createElement(Line, null))));
|
|
494
|
+
return (Scribbler.createElement(Text, null,
|
|
495
|
+
Scribbler.createElement(Line, null, "CLI Options"),
|
|
496
|
+
Scribbler.createElement(Line, null),
|
|
497
|
+
optionsText));
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
class HelpFooterText {
|
|
501
|
+
render() {
|
|
502
|
+
return Scribbler.createElement(Line, null, "To learn more, visit https://tstyche.org");
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function helpText(optionDefinitions, tstycheVersion) {
|
|
506
|
+
return (Scribbler.createElement(Text, null,
|
|
507
|
+
Scribbler.createElement(HelpHeaderText, { tstycheVersion: tstycheVersion }),
|
|
508
|
+
Scribbler.createElement(Line, null),
|
|
509
|
+
Scribbler.createElement(CliUsageText, null),
|
|
510
|
+
Scribbler.createElement(Line, null),
|
|
511
|
+
Scribbler.createElement(CliOptionsText, { optionDefinitions: optionDefinitions }),
|
|
512
|
+
Scribbler.createElement(Line, null),
|
|
513
|
+
Scribbler.createElement(HelpFooterText, null),
|
|
514
|
+
Scribbler.createElement(Line, null)));
|
|
515
|
+
}
|
|
516
|
+
|
|
381
517
|
class RowText {
|
|
382
518
|
props;
|
|
383
519
|
constructor(props) {
|
|
@@ -651,6 +787,9 @@ class Diagnostic {
|
|
|
651
787
|
if (options.code != null) {
|
|
652
788
|
this.code = options.code;
|
|
653
789
|
}
|
|
790
|
+
if (options.origin != null) {
|
|
791
|
+
this.origin = options.origin;
|
|
792
|
+
}
|
|
654
793
|
if (options.related != null) {
|
|
655
794
|
this.related = options.related;
|
|
656
795
|
}
|
|
@@ -727,10 +866,6 @@ class FileViewService {
|
|
|
727
866
|
return this.#messages.length > 0;
|
|
728
867
|
}
|
|
729
868
|
addMessage(message) {
|
|
730
|
-
if (Array.isArray(message)) {
|
|
731
|
-
this.#messages.push(...message);
|
|
732
|
-
return;
|
|
733
|
-
}
|
|
734
869
|
this.#messages.push(message);
|
|
735
870
|
}
|
|
736
871
|
addTest(status, name) {
|
|
@@ -1199,14 +1334,12 @@ class TestMember {
|
|
|
1199
1334
|
diagnostics;
|
|
1200
1335
|
members = [];
|
|
1201
1336
|
name;
|
|
1202
|
-
typeChecker;
|
|
1203
1337
|
constructor(brand, node, parent, flags) {
|
|
1204
1338
|
this.brand = brand;
|
|
1205
1339
|
this.node = node;
|
|
1206
1340
|
this.parent = parent;
|
|
1207
1341
|
this.flags = flags;
|
|
1208
1342
|
this.compiler = parent.compiler;
|
|
1209
|
-
this.typeChecker = parent.typeChecker;
|
|
1210
1343
|
this.diagnostics = this.#mapDiagnostics(node, this.parent);
|
|
1211
1344
|
this.name = this.#resolveName(node);
|
|
1212
1345
|
}
|
|
@@ -1304,48 +1437,18 @@ class Assertion extends TestMember {
|
|
|
1304
1437
|
get matcherName() {
|
|
1305
1438
|
return this.matcherNode.expression.name;
|
|
1306
1439
|
}
|
|
1307
|
-
get
|
|
1440
|
+
get source() {
|
|
1441
|
+
if (this.node.typeArguments != null) {
|
|
1442
|
+
return this.node.typeArguments;
|
|
1443
|
+
}
|
|
1308
1444
|
return this.node.arguments;
|
|
1309
1445
|
}
|
|
1310
|
-
get
|
|
1311
|
-
if (
|
|
1312
|
-
return;
|
|
1313
|
-
}
|
|
1314
|
-
if (this.node.typeArguments?.[0]) {
|
|
1315
|
-
return {
|
|
1316
|
-
source: 1,
|
|
1317
|
-
type: this.typeChecker.getTypeFromTypeNode(this.node.typeArguments[0]),
|
|
1318
|
-
};
|
|
1319
|
-
}
|
|
1320
|
-
if (this.node.arguments[0]) {
|
|
1321
|
-
return {
|
|
1322
|
-
source: 0,
|
|
1323
|
-
type: this.typeChecker.getTypeAtLocation(this.node.arguments[0]),
|
|
1324
|
-
};
|
|
1446
|
+
get target() {
|
|
1447
|
+
if (this.matcherNode.typeArguments != null) {
|
|
1448
|
+
return this.matcherNode.typeArguments;
|
|
1325
1449
|
}
|
|
1326
|
-
return;
|
|
1327
|
-
}
|
|
1328
|
-
get targetArguments() {
|
|
1329
1450
|
return this.matcherNode.arguments;
|
|
1330
1451
|
}
|
|
1331
|
-
get targetType() {
|
|
1332
|
-
if (!this.typeChecker) {
|
|
1333
|
-
return;
|
|
1334
|
-
}
|
|
1335
|
-
if (this.matcherNode.typeArguments?.[0]) {
|
|
1336
|
-
return {
|
|
1337
|
-
source: 1,
|
|
1338
|
-
type: this.typeChecker.getTypeFromTypeNode(this.matcherNode.typeArguments[0]),
|
|
1339
|
-
};
|
|
1340
|
-
}
|
|
1341
|
-
if (this.matcherNode.arguments[0]) {
|
|
1342
|
-
return {
|
|
1343
|
-
source: 0,
|
|
1344
|
-
type: this.typeChecker.getTypeAtLocation(this.matcherNode.arguments[0]),
|
|
1345
|
-
};
|
|
1346
|
-
}
|
|
1347
|
-
return;
|
|
1348
|
-
}
|
|
1349
1452
|
#mapDiagnostics() {
|
|
1350
1453
|
const mapped = [];
|
|
1351
1454
|
const unmapped = [];
|
|
@@ -1372,12 +1475,6 @@ class Assertion extends TestMember {
|
|
|
1372
1475
|
}
|
|
1373
1476
|
}
|
|
1374
1477
|
|
|
1375
|
-
var AssertionSource;
|
|
1376
|
-
(function (AssertionSource) {
|
|
1377
|
-
AssertionSource[AssertionSource["Argument"] = 0] = "Argument";
|
|
1378
|
-
AssertionSource[AssertionSource["TypeArgument"] = 1] = "TypeArgument";
|
|
1379
|
-
})(AssertionSource || (AssertionSource = {}));
|
|
1380
|
-
|
|
1381
1478
|
class IdentifierLookup {
|
|
1382
1479
|
compiler;
|
|
1383
1480
|
#identifiers;
|
|
@@ -1478,13 +1575,11 @@ class TestTree {
|
|
|
1478
1575
|
compiler;
|
|
1479
1576
|
diagnostics;
|
|
1480
1577
|
sourceFile;
|
|
1481
|
-
typeChecker;
|
|
1482
1578
|
members = [];
|
|
1483
|
-
constructor(compiler, diagnostics, sourceFile
|
|
1579
|
+
constructor(compiler, diagnostics, sourceFile) {
|
|
1484
1580
|
this.compiler = compiler;
|
|
1485
1581
|
this.diagnostics = diagnostics;
|
|
1486
1582
|
this.sourceFile = sourceFile;
|
|
1487
|
-
this.typeChecker = typeChecker;
|
|
1488
1583
|
}
|
|
1489
1584
|
get hasOnly() {
|
|
1490
1585
|
function hasOnly(root) {
|
|
@@ -1561,8 +1656,8 @@ class CollectService {
|
|
|
1561
1656
|
this.#collectTestMembers(node, identifiers, parent);
|
|
1562
1657
|
});
|
|
1563
1658
|
}
|
|
1564
|
-
createTestTree(sourceFile, semanticDiagnostics = []
|
|
1565
|
-
const testTree = new TestTree(this.compiler, semanticDiagnostics, sourceFile
|
|
1659
|
+
createTestTree(sourceFile, semanticDiagnostics = []) {
|
|
1660
|
+
const testTree = new TestTree(this.compiler, semanticDiagnostics, sourceFile);
|
|
1566
1661
|
this.#collectTestMembers(sourceFile, new IdentifierLookup(this.compiler), testTree);
|
|
1567
1662
|
return testTree;
|
|
1568
1663
|
}
|
|
@@ -1593,408 +1688,551 @@ var TestMemberFlags;
|
|
|
1593
1688
|
TestMemberFlags[TestMemberFlags["Todo"] = 8] = "Todo";
|
|
1594
1689
|
})(TestMemberFlags || (TestMemberFlags = {}));
|
|
1595
1690
|
|
|
1596
|
-
class
|
|
1597
|
-
|
|
1598
|
-
#
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
return false;
|
|
1605
|
-
}
|
|
1606
|
-
function returnUndefined() {
|
|
1607
|
-
return undefined;
|
|
1608
|
-
}
|
|
1609
|
-
const noopWatcher = { close: doNothing };
|
|
1610
|
-
const noopLogger = {
|
|
1611
|
-
close: doNothing,
|
|
1612
|
-
endGroup: doNothing,
|
|
1613
|
-
getLogFileName: returnUndefined,
|
|
1614
|
-
hasLevel: returnFalse,
|
|
1615
|
-
info: doNothing,
|
|
1616
|
-
loggingEnabled: returnFalse,
|
|
1617
|
-
msg: doNothing,
|
|
1618
|
-
perftrc: doNothing,
|
|
1619
|
-
startGroup: doNothing,
|
|
1620
|
-
};
|
|
1621
|
-
const host = {
|
|
1622
|
-
...this.compiler.sys,
|
|
1623
|
-
clearImmediate,
|
|
1624
|
-
clearTimeout,
|
|
1625
|
-
setImmediate,
|
|
1626
|
-
setTimeout,
|
|
1627
|
-
watchDirectory: () => noopWatcher,
|
|
1628
|
-
watchFile: () => noopWatcher,
|
|
1629
|
-
};
|
|
1630
|
-
this.#service = new this.compiler.server.ProjectService({
|
|
1631
|
-
cancellationToken: this.compiler.server.nullCancellationToken,
|
|
1632
|
-
host,
|
|
1633
|
-
logger: noopLogger,
|
|
1634
|
-
session: undefined,
|
|
1635
|
-
useInferredProjectPerProjectRoot: true,
|
|
1636
|
-
useSingleInferredProject: false,
|
|
1637
|
-
});
|
|
1638
|
-
}
|
|
1639
|
-
closeFile(filePath) {
|
|
1640
|
-
this.#service.closeClientFile(filePath);
|
|
1641
|
-
}
|
|
1642
|
-
getDefaultProject(filePath) {
|
|
1643
|
-
return this.#service.getDefaultProjectForFile(this.compiler.server.toNormalizedPath(filePath), true);
|
|
1691
|
+
class PrimitiveTypeMatcher {
|
|
1692
|
+
typeChecker;
|
|
1693
|
+
#targetTypeFlag;
|
|
1694
|
+
#targetTypeText;
|
|
1695
|
+
constructor(typeChecker, targetTypeFlag, targetTypeText) {
|
|
1696
|
+
this.typeChecker = typeChecker;
|
|
1697
|
+
this.#targetTypeFlag = targetTypeFlag;
|
|
1698
|
+
this.#targetTypeText = targetTypeText;
|
|
1644
1699
|
}
|
|
1645
|
-
|
|
1646
|
-
const
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
return project.getLanguageService(true);
|
|
1700
|
+
#explain(sourceType, isNot) {
|
|
1701
|
+
const sourceTypeText = this.typeChecker.typeToString(sourceType);
|
|
1702
|
+
return isNot
|
|
1703
|
+
? [Diagnostic.error(`Type '${this.#targetTypeText}' is identical to type '${sourceTypeText}'.`)]
|
|
1704
|
+
: [Diagnostic.error(`Type '${this.#targetTypeText}' is not identical to type '${sourceTypeText}'.`)];
|
|
1651
1705
|
}
|
|
1652
|
-
|
|
1653
|
-
const
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
if (configFileErrors && configFileErrors.length > 0) {
|
|
1659
|
-
EventEmitter.dispatch([
|
|
1660
|
-
"project:error",
|
|
1661
|
-
{ diagnostics: Diagnostic.fromDiagnostics(configFileErrors, this.compiler) },
|
|
1662
|
-
]);
|
|
1663
|
-
}
|
|
1706
|
+
match(sourceType, isNot) {
|
|
1707
|
+
const isMatch = Boolean(sourceType.flags & this.#targetTypeFlag);
|
|
1708
|
+
return {
|
|
1709
|
+
explain: () => this.#explain(sourceType, isNot),
|
|
1710
|
+
isMatch,
|
|
1711
|
+
};
|
|
1664
1712
|
}
|
|
1665
1713
|
}
|
|
1666
1714
|
|
|
1667
|
-
class
|
|
1668
|
-
|
|
1669
|
-
constructor(
|
|
1670
|
-
this.
|
|
1671
|
-
}
|
|
1672
|
-
#assertNonNullish(value, message) {
|
|
1673
|
-
if (value == null) {
|
|
1674
|
-
throw Error(message);
|
|
1675
|
-
}
|
|
1715
|
+
class ToBeAssignable {
|
|
1716
|
+
typeChecker;
|
|
1717
|
+
constructor(typeChecker) {
|
|
1718
|
+
this.typeChecker = typeChecker;
|
|
1676
1719
|
}
|
|
1677
|
-
#
|
|
1678
|
-
|
|
1720
|
+
#explain(sourceType, targetType, isNot) {
|
|
1721
|
+
const sourceTypeText = this.typeChecker.typeToString(sourceType);
|
|
1722
|
+
const targetTypeText = this.typeChecker.typeToString(targetType);
|
|
1723
|
+
return isNot
|
|
1724
|
+
? [Diagnostic.error(`Type '${targetTypeText}' is assignable to type '${sourceTypeText}'.`)]
|
|
1725
|
+
: [Diagnostic.error(`Type '${targetTypeText}' is not assignable to type '${sourceTypeText}'.`)];
|
|
1679
1726
|
}
|
|
1680
|
-
|
|
1681
|
-
this
|
|
1727
|
+
match(sourceType, targetType, isNot) {
|
|
1728
|
+
const isMatch = this.typeChecker.isTypeAssignableTo(targetType, sourceType);
|
|
1729
|
+
return {
|
|
1730
|
+
explain: () => this.#explain(sourceType, targetType, isNot),
|
|
1731
|
+
isMatch,
|
|
1732
|
+
};
|
|
1682
1733
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
class ToEqual {
|
|
1737
|
+
typeChecker;
|
|
1738
|
+
constructor(typeChecker) {
|
|
1739
|
+
this.typeChecker = typeChecker;
|
|
1685
1740
|
}
|
|
1686
|
-
#
|
|
1687
|
-
|
|
1688
|
-
|
|
1741
|
+
#explain(sourceType, targetType, isNot) {
|
|
1742
|
+
const sourceTypeText = this.typeChecker.typeToString(sourceType);
|
|
1743
|
+
const targetTypeText = this.typeChecker.typeToString(targetType);
|
|
1744
|
+
return isNot
|
|
1745
|
+
? [Diagnostic.error(`Type '${targetTypeText}' is identical to type '${sourceTypeText}'.`)]
|
|
1746
|
+
: [Diagnostic.error(`Type '${targetTypeText}' is not identical to type '${sourceTypeText}'.`)];
|
|
1689
1747
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
1748
|
+
match(sourceType, targetType, isNot) {
|
|
1749
|
+
const isMatch = this.typeChecker.isTypeIdenticalTo(sourceType, targetType);
|
|
1750
|
+
return {
|
|
1751
|
+
explain: () => this.#explain(sourceType, targetType, isNot),
|
|
1752
|
+
isMatch,
|
|
1753
|
+
};
|
|
1692
1754
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
class ToHaveProperty {
|
|
1758
|
+
compiler;
|
|
1759
|
+
typeChecker;
|
|
1760
|
+
constructor(compiler, typeChecker) {
|
|
1761
|
+
this.compiler = compiler;
|
|
1762
|
+
this.typeChecker = typeChecker;
|
|
1763
|
+
}
|
|
1764
|
+
#explain(sourceType, targetType, isNot) {
|
|
1765
|
+
const sourceTypeText = this.typeChecker.typeToString(sourceType);
|
|
1766
|
+
let targetArgumentText;
|
|
1767
|
+
if (this.#isStringOrNumberLiteralType(targetType)) {
|
|
1768
|
+
targetArgumentText = String(targetType.value);
|
|
1769
|
+
}
|
|
1770
|
+
else {
|
|
1771
|
+
targetArgumentText = `[${this.compiler.unescapeLeadingUnderscores(targetType.symbol.escapedName)}]`;
|
|
1772
|
+
}
|
|
1773
|
+
return isNot
|
|
1774
|
+
? [Diagnostic.error(`Property '${targetArgumentText}' exists on type '${sourceTypeText}'.`)]
|
|
1775
|
+
: [Diagnostic.error(`Property '${targetArgumentText}' does not exist on type '${sourceTypeText}'.`)];
|
|
1776
|
+
}
|
|
1777
|
+
#isStringOrNumberLiteralType(type) {
|
|
1778
|
+
return Boolean(type.flags & this.compiler.TypeFlags.StringOrNumberLiteral);
|
|
1779
|
+
}
|
|
1780
|
+
match(sourceType, targetType, isNot) {
|
|
1781
|
+
let targetArgumentText;
|
|
1782
|
+
if (this.#isStringOrNumberLiteralType(targetType)) {
|
|
1783
|
+
targetArgumentText = String(targetType.value);
|
|
1784
|
+
}
|
|
1785
|
+
else {
|
|
1786
|
+
targetArgumentText = this.compiler.unescapeLeadingUnderscores(targetType.escapedName);
|
|
1787
|
+
}
|
|
1788
|
+
const isMatch = sourceType.getProperties().some((property) => {
|
|
1789
|
+
return this.compiler.unescapeLeadingUnderscores(property.escapedName) === targetArgumentText;
|
|
1790
|
+
});
|
|
1791
|
+
return {
|
|
1792
|
+
explain: () => this.#explain(sourceType, targetType, isNot),
|
|
1793
|
+
isMatch,
|
|
1701
1794
|
};
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
class ToMatch {
|
|
1799
|
+
typeChecker;
|
|
1800
|
+
constructor(typeChecker) {
|
|
1801
|
+
this.typeChecker = typeChecker;
|
|
1802
|
+
}
|
|
1803
|
+
#explain(sourceType, targetType, isNot) {
|
|
1804
|
+
const sourceTypeText = this.typeChecker.typeToString(sourceType);
|
|
1805
|
+
const targetTypeText = this.typeChecker.typeToString(targetType);
|
|
1806
|
+
return isNot
|
|
1807
|
+
? [Diagnostic.error(`Type '${targetTypeText}' is a subtype of type '${sourceTypeText}'.`)]
|
|
1808
|
+
: [Diagnostic.error(`Type '${targetTypeText}' is not a subtype of type '${sourceTypeText}'.`)];
|
|
1809
|
+
}
|
|
1810
|
+
match(sourceType, targetType, isNot) {
|
|
1811
|
+
const isMatch = this.typeChecker.isTypeSubtypeOf(sourceType, targetType);
|
|
1812
|
+
return {
|
|
1813
|
+
explain: () => this.#explain(sourceType, targetType, isNot),
|
|
1814
|
+
isMatch,
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
class ToRaiseError {
|
|
1820
|
+
compiler;
|
|
1821
|
+
typeChecker;
|
|
1822
|
+
constructor(compiler, typeChecker) {
|
|
1823
|
+
this.compiler = compiler;
|
|
1824
|
+
this.typeChecker = typeChecker;
|
|
1825
|
+
}
|
|
1826
|
+
#explain(source, targetTypes, isNot) {
|
|
1827
|
+
const sourceText = this.compiler.isTypeNode(source.node) ? "Type expression" : "Expression";
|
|
1828
|
+
if (source.diagnostics.length === 0) {
|
|
1829
|
+
return [Diagnostic.error(`${sourceText} did not raise a type error.`)];
|
|
1830
|
+
}
|
|
1831
|
+
if (isNot && targetTypes.length === 0) {
|
|
1832
|
+
const related = [
|
|
1833
|
+
Diagnostic.error(`The raised type error${source.diagnostics.length === 1 ? "" : "s"}:`),
|
|
1834
|
+
...Diagnostic.fromDiagnostics(source.diagnostics, this.compiler),
|
|
1835
|
+
];
|
|
1836
|
+
const text = `${sourceText} raised ${source.diagnostics.length === 1 ? "a" : source.diagnostics.length} type error${source.diagnostics.length === 1 ? "" : "s"}.`;
|
|
1837
|
+
return [Diagnostic.error(text).add({ related })];
|
|
1838
|
+
}
|
|
1839
|
+
if (source.diagnostics.length !== targetTypes.length) {
|
|
1840
|
+
const expectedText = source.diagnostics.length > targetTypes.length
|
|
1841
|
+
? `only ${targetTypes.length} type error${targetTypes.length === 1 ? "" : "s"}`
|
|
1842
|
+
: `${targetTypes.length} type error${targetTypes.length === 1 ? "" : "s"}`;
|
|
1843
|
+
const foundText = source.diagnostics.length > targetTypes.length
|
|
1844
|
+
? `${source.diagnostics.length}`
|
|
1845
|
+
: `only ${source.diagnostics.length}`;
|
|
1846
|
+
const related = [
|
|
1847
|
+
Diagnostic.error(`The raised type error${source.diagnostics.length === 1 ? "" : "s"}:`),
|
|
1848
|
+
...Diagnostic.fromDiagnostics(source.diagnostics, this.compiler),
|
|
1849
|
+
];
|
|
1850
|
+
const text = `Expected ${expectedText}, but ${foundText} ${source.diagnostics.length === 1 ? "was" : "were"} raised.`;
|
|
1851
|
+
return [Diagnostic.error(text).add({ related })];
|
|
1852
|
+
}
|
|
1853
|
+
const diagnostics = [];
|
|
1854
|
+
targetTypes.forEach((argument, index) => {
|
|
1855
|
+
const diagnostic = source.diagnostics[index];
|
|
1856
|
+
if (!diagnostic) {
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
const isMatch = this.#matchExpectedError(diagnostic, argument);
|
|
1860
|
+
if (!isNot && !isMatch) {
|
|
1861
|
+
const expectedText = this.#isStringLiteralType(argument)
|
|
1862
|
+
? `matching substring '${argument.value}'`
|
|
1863
|
+
: `with code ${argument.value}`;
|
|
1864
|
+
const related = [
|
|
1865
|
+
Diagnostic.error("The raised type error:"),
|
|
1866
|
+
...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
|
|
1867
|
+
];
|
|
1868
|
+
const text = `${sourceText} did not raise a type error ${expectedText}.`;
|
|
1869
|
+
diagnostics.push(Diagnostic.error(text).add({ related }));
|
|
1870
|
+
}
|
|
1871
|
+
if (isNot && isMatch) {
|
|
1872
|
+
const expectedText = this.#isStringLiteralType(argument)
|
|
1873
|
+
? `matching substring '${argument.value}'`
|
|
1874
|
+
: `with code ${argument.value}`;
|
|
1875
|
+
const related = [
|
|
1876
|
+
Diagnostic.error("The raised type error:"),
|
|
1877
|
+
...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
|
|
1712
1878
|
];
|
|
1879
|
+
const text = `${sourceText} raised a type error ${expectedText}.`;
|
|
1880
|
+
diagnostics.push(Diagnostic.error(text).add({ related }));
|
|
1713
1881
|
}
|
|
1882
|
+
});
|
|
1883
|
+
return diagnostics;
|
|
1884
|
+
}
|
|
1885
|
+
#isStringLiteralType(type) {
|
|
1886
|
+
return Boolean(type.flags & this.compiler.TypeFlags.StringLiteral);
|
|
1887
|
+
}
|
|
1888
|
+
match(source, targetTypes, isNot) {
|
|
1889
|
+
const explain = () => this.#explain(source, targetTypes, isNot);
|
|
1890
|
+
if (targetTypes.length === 0) {
|
|
1891
|
+
return {
|
|
1892
|
+
explain,
|
|
1893
|
+
isMatch: source.diagnostics.length > 0,
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
if (source.diagnostics.length !== targetTypes.length) {
|
|
1897
|
+
return {
|
|
1898
|
+
explain,
|
|
1899
|
+
isMatch: false,
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
return {
|
|
1903
|
+
explain,
|
|
1904
|
+
isMatch: targetTypes.every((type, index) => this.#matchExpectedError(source.diagnostics[index], type)),
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
#matchExpectedError(diagnostic, type) {
|
|
1908
|
+
if (this.#isStringLiteralType(type)) {
|
|
1909
|
+
return this.compiler.flattenDiagnosticMessageText(diagnostic?.messageText, " ", 0).includes(type.value);
|
|
1910
|
+
}
|
|
1911
|
+
return type.value === diagnostic?.code;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
class Expect {
|
|
1916
|
+
compiler;
|
|
1917
|
+
typeChecker;
|
|
1918
|
+
toBeAny;
|
|
1919
|
+
toBeAssignable;
|
|
1920
|
+
toBeBigInt;
|
|
1921
|
+
toBeBoolean;
|
|
1922
|
+
toBeNever;
|
|
1923
|
+
toBeNull;
|
|
1924
|
+
toBeNumber;
|
|
1925
|
+
toBeString;
|
|
1926
|
+
toBeSymbol;
|
|
1927
|
+
toBeUndefined;
|
|
1928
|
+
toBeUniqueSymbol;
|
|
1929
|
+
toBeUnknown;
|
|
1930
|
+
toBeVoid;
|
|
1931
|
+
toEqual;
|
|
1932
|
+
toHaveProperty;
|
|
1933
|
+
toMatch;
|
|
1934
|
+
toRaiseError;
|
|
1935
|
+
constructor(compiler, typeChecker) {
|
|
1936
|
+
this.compiler = compiler;
|
|
1937
|
+
this.typeChecker = typeChecker;
|
|
1938
|
+
this.toBeAny = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Any, "any");
|
|
1939
|
+
this.toBeAssignable = new ToBeAssignable(this.typeChecker);
|
|
1940
|
+
this.toBeBigInt = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.BigInt, "bigint");
|
|
1941
|
+
this.toBeBoolean = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Boolean, "boolean");
|
|
1942
|
+
this.toBeNever = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Never, "never");
|
|
1943
|
+
this.toBeNull = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Null, "null");
|
|
1944
|
+
this.toBeNumber = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Number, "number");
|
|
1945
|
+
this.toBeString = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.String, "string");
|
|
1946
|
+
this.toBeSymbol = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.ESSymbol, "symbol");
|
|
1947
|
+
this.toBeUndefined = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Undefined, "undefined");
|
|
1948
|
+
this.toBeUniqueSymbol = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.UniqueESSymbol, "unique symbol");
|
|
1949
|
+
this.toBeUnknown = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Unknown, "unknown");
|
|
1950
|
+
this.toBeVoid = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Void, "void");
|
|
1951
|
+
this.toEqual = new ToEqual(this.typeChecker);
|
|
1952
|
+
this.toHaveProperty = new ToHaveProperty(this.compiler, this.typeChecker);
|
|
1953
|
+
this.toMatch = new ToMatch(this.typeChecker);
|
|
1954
|
+
this.toRaiseError = new ToRaiseError(this.compiler, this.typeChecker);
|
|
1955
|
+
}
|
|
1956
|
+
static assertTypeChecker(typeChecker) {
|
|
1957
|
+
return ("isTypeAssignableTo" in typeChecker && "isTypeIdenticalTo" in typeChecker && "isTypeSubtypeOf" in typeChecker);
|
|
1958
|
+
}
|
|
1959
|
+
#getType(node) {
|
|
1960
|
+
return this.compiler.isExpression(node)
|
|
1961
|
+
? this.typeChecker.getTypeAtLocation(node)
|
|
1962
|
+
: this.typeChecker.getTypeFromTypeNode(node);
|
|
1963
|
+
}
|
|
1964
|
+
#getTypes(nodes) {
|
|
1965
|
+
return nodes.map((node) => this.#getType(node));
|
|
1966
|
+
}
|
|
1967
|
+
#isArrayOfStringOrNumberLiteralTypes(types) {
|
|
1968
|
+
return types.every((type) => this.#isStringOrNumberLiteralType(type));
|
|
1969
|
+
}
|
|
1970
|
+
#isStringOrNumberLiteralType(type) {
|
|
1971
|
+
return Boolean(type.flags & this.compiler.TypeFlags.StringOrNumberLiteral);
|
|
1972
|
+
}
|
|
1973
|
+
#isUniqueSymbolType(type) {
|
|
1974
|
+
return Boolean(type.flags & this.compiler.TypeFlags.UniqueESSymbol);
|
|
1975
|
+
}
|
|
1976
|
+
match(assertion, expectResult) {
|
|
1977
|
+
const matcherNameText = assertion.matcherName.getText();
|
|
1978
|
+
switch (matcherNameText) {
|
|
1979
|
+
case "toBeAssignable":
|
|
1980
|
+
case "toEqual":
|
|
1981
|
+
case "toMatch":
|
|
1982
|
+
if (assertion.source[0] == null) {
|
|
1983
|
+
this.#onSourceArgumentMustBeProvided(assertion, expectResult);
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
if (assertion.target[0] == null) {
|
|
1987
|
+
this.#onTargetArgumentMustBeProvided(assertion, expectResult);
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
return this[matcherNameText].match(this.#getType(assertion.source[0]), this.#getType(assertion.target[0]), assertion.isNot);
|
|
1714
1991
|
case "toBeAny":
|
|
1715
|
-
return this.#isType(assertion, "any");
|
|
1716
1992
|
case "toBeBigInt":
|
|
1717
|
-
return this.#isType(assertion, "bigint");
|
|
1718
1993
|
case "toBeBoolean":
|
|
1719
|
-
return this.#isType(assertion, "boolean");
|
|
1720
1994
|
case "toBeNever":
|
|
1721
|
-
return this.#isType(assertion, "never");
|
|
1722
1995
|
case "toBeNull":
|
|
1723
|
-
return this.#isType(assertion, "null");
|
|
1724
1996
|
case "toBeNumber":
|
|
1725
|
-
return this.#isType(assertion, "number");
|
|
1726
1997
|
case "toBeString":
|
|
1727
|
-
return this.#isType(assertion, "string");
|
|
1728
1998
|
case "toBeSymbol":
|
|
1729
|
-
return this.#isType(assertion, "symbol");
|
|
1730
1999
|
case "toBeUndefined":
|
|
1731
|
-
return this.#isType(assertion, "undefined");
|
|
1732
2000
|
case "toBeUniqueSymbol":
|
|
1733
|
-
return this.#isType(assertion, "unique symbol");
|
|
1734
2001
|
case "toBeUnknown":
|
|
1735
|
-
return this.#isType(assertion, "unknown");
|
|
1736
2002
|
case "toBeVoid":
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
const targetTypeText = assertion.typeChecker.typeToString(assertion.targetType.type);
|
|
1743
|
-
return [
|
|
1744
|
-
Diagnostic.error(assertion.isNot
|
|
1745
|
-
? `Type '${targetTypeText}' is identical to type '${sourceTypeText}'.`
|
|
1746
|
-
: `Type '${targetTypeText}' is not identical to type '${sourceTypeText}'.`, origin),
|
|
1747
|
-
];
|
|
1748
|
-
}
|
|
2003
|
+
if (assertion.source[0] == null) {
|
|
2004
|
+
this.#onSourceArgumentMustBeProvided(assertion, expectResult);
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
return this[matcherNameText].match(this.#getType(assertion.source[0]), assertion.isNot);
|
|
1749
2008
|
case "toHaveProperty": {
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
let targetArgumentText;
|
|
1754
|
-
if (assertion.targetType.type.flags & this.compiler.TypeFlags.StringOrNumberLiteral) {
|
|
1755
|
-
targetArgumentText = String(assertion.targetType.type.value);
|
|
2009
|
+
if (assertion.source[0] == null) {
|
|
2010
|
+
this.#onSourceArgumentMustBeProvided(assertion, expectResult);
|
|
2011
|
+
return;
|
|
1756
2012
|
}
|
|
1757
|
-
|
|
1758
|
-
|
|
2013
|
+
const sourceType = this.#getType(assertion.source[0]);
|
|
2014
|
+
const nonPrimitiveType = { flags: this.compiler.TypeFlags.NonPrimitive };
|
|
2015
|
+
if (sourceType.flags & (this.compiler.TypeFlags.Any | this.compiler.TypeFlags.Never) ||
|
|
2016
|
+
!this.typeChecker.isTypeAssignableTo(sourceType, nonPrimitiveType)) {
|
|
2017
|
+
this.#onSourceArgumentMustBeObjectType(assertion.source[0], expectResult);
|
|
2018
|
+
return;
|
|
1759
2019
|
}
|
|
1760
|
-
|
|
1761
|
-
|
|
2020
|
+
if (assertion.target[0] == null) {
|
|
2021
|
+
this.#onKeyArgumentMustBeProvided(assertion, expectResult);
|
|
2022
|
+
return;
|
|
1762
2023
|
}
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
case "toMatch": {
|
|
1770
|
-
this.#assertNonNullishSourceType(assertion);
|
|
1771
|
-
this.#assertNonNullishTargetType(assertion);
|
|
1772
|
-
const sourceTypeText = assertion.typeChecker.typeToString(assertion.sourceType.type);
|
|
1773
|
-
const targetTypeText = assertion.typeChecker.typeToString(assertion.targetType.type);
|
|
1774
|
-
return [
|
|
1775
|
-
Diagnostic.error(assertion.isNot
|
|
1776
|
-
? `Type '${targetTypeText}' is a subtype of type '${sourceTypeText}'.`
|
|
1777
|
-
: `Type '${targetTypeText}' is not a subtype of type '${sourceTypeText}'.`, origin),
|
|
1778
|
-
];
|
|
2024
|
+
const targetType = this.#getType(assertion.target[0]);
|
|
2025
|
+
if (!(this.#isStringOrNumberLiteralType(targetType) || this.#isUniqueSymbolType(targetType))) {
|
|
2026
|
+
this.#onKeyArgumentMustBeOfType(assertion.target[0], expectResult);
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
return this.toHaveProperty.match(sourceType, targetType, assertion.isNot);
|
|
1779
2030
|
}
|
|
1780
2031
|
case "toRaiseError": {
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
}
|
|
1785
|
-
const sourceText = assertion.sourceType.source === 0 ? "Expression" : "Type definition";
|
|
1786
|
-
if (assertion.diagnostics.length === 0) {
|
|
1787
|
-
return [Diagnostic.error(`${sourceText} did not raise a type error.`, origin)];
|
|
1788
|
-
}
|
|
1789
|
-
if (assertion.isNot && assertion.targetArguments.length === 0) {
|
|
1790
|
-
const related = [
|
|
1791
|
-
Diagnostic.error(`The raised type error${assertion.diagnostics.length === 1 ? "" : "s"}:`),
|
|
1792
|
-
...Diagnostic.fromDiagnostics(assertion.diagnostics, this.compiler),
|
|
1793
|
-
];
|
|
1794
|
-
return [
|
|
1795
|
-
Diagnostic.error(`${sourceText} raised ${assertion.diagnostics.length === 1 ? "a" : assertion.diagnostics.length} type error${assertion.diagnostics.length === 1 ? "" : "s"}.`, origin).add({ related }),
|
|
1796
|
-
];
|
|
1797
|
-
}
|
|
1798
|
-
if (assertion.diagnostics.length !== assertion.targetArguments.length) {
|
|
1799
|
-
const expectedText = assertion.diagnostics.length > assertion.targetArguments.length
|
|
1800
|
-
? `only ${assertion.targetArguments.length} type error${assertion.targetArguments.length === 1 ? "" : "s"}`
|
|
1801
|
-
: `${assertion.targetArguments.length} type error${assertion.targetArguments.length === 1 ? "" : "s"}`;
|
|
1802
|
-
const foundText = assertion.diagnostics.length > assertion.targetArguments.length
|
|
1803
|
-
? `${assertion.diagnostics.length}`
|
|
1804
|
-
: `only ${assertion.diagnostics.length}`;
|
|
1805
|
-
const related = [
|
|
1806
|
-
Diagnostic.error(`The raised type error${assertion.diagnostics.length === 1 ? "" : "s"}:`),
|
|
1807
|
-
...Diagnostic.fromDiagnostics(assertion.diagnostics, this.compiler),
|
|
1808
|
-
];
|
|
1809
|
-
const diagnostic = Diagnostic.error(`Expected ${expectedText}, but ${foundText} ${assertion.diagnostics.length === 1 ? "was" : "were"} raised.`, origin).add({
|
|
1810
|
-
related,
|
|
1811
|
-
});
|
|
1812
|
-
return [diagnostic];
|
|
2032
|
+
if (assertion.source[0] == null) {
|
|
2033
|
+
this.#onSourceArgumentMustBeProvided(assertion, expectResult);
|
|
2034
|
+
return;
|
|
1813
2035
|
}
|
|
1814
|
-
const
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
const isMatch = this.#matchExpectedError(diagnostic, argument);
|
|
1821
|
-
if (!assertion.isNot && !isMatch) {
|
|
1822
|
-
const expectedText = this.compiler.isStringLiteralLike(argument)
|
|
1823
|
-
? `matching substring '${argument.text}'`
|
|
1824
|
-
: `with code ${argument.text}`;
|
|
1825
|
-
const related = [
|
|
1826
|
-
Diagnostic.error("The raised type error:"),
|
|
1827
|
-
...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
|
|
1828
|
-
];
|
|
1829
|
-
diagnostics.push(Diagnostic.error(`${sourceText} did not raise a type error ${expectedText}.`, origin).add({ related }));
|
|
1830
|
-
}
|
|
1831
|
-
if (assertion.isNot && isMatch) {
|
|
1832
|
-
const expectedText = this.compiler.isStringLiteralLike(argument)
|
|
1833
|
-
? `matching substring '${argument.text}'`
|
|
1834
|
-
: `with code ${argument.text}`;
|
|
1835
|
-
const related = [
|
|
1836
|
-
Diagnostic.error("The raised type error:"),
|
|
1837
|
-
...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
|
|
1838
|
-
];
|
|
1839
|
-
diagnostics.push(Diagnostic.error(`${sourceText} raised a type error ${expectedText}.`, origin).add({ related }));
|
|
1840
|
-
}
|
|
1841
|
-
});
|
|
1842
|
-
return diagnostics;
|
|
2036
|
+
const targetTypes = this.#getTypes(assertion.target);
|
|
2037
|
+
if (!this.#isArrayOfStringOrNumberLiteralTypes(targetTypes)) {
|
|
2038
|
+
this.#onTargetArgumentsMustBeStringOrNumberLiteralTypes(assertion.target, expectResult);
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2041
|
+
return this.toRaiseError.match({ diagnostics: assertion.diagnostics, node: assertion.source[0] }, targetTypes, assertion.isNot);
|
|
1843
2042
|
}
|
|
1844
2043
|
default:
|
|
1845
|
-
|
|
2044
|
+
this.#onNotSupportedMatcherName(assertion, expectResult);
|
|
2045
|
+
return;
|
|
1846
2046
|
}
|
|
1847
2047
|
}
|
|
1848
|
-
#
|
|
1849
|
-
this.#
|
|
1850
|
-
|
|
2048
|
+
#onKeyArgumentMustBeOfType(node, expectResult) {
|
|
2049
|
+
const receivedTypeText = this.typeChecker.typeToString(this.#getType(node));
|
|
2050
|
+
const text = `An argument for 'key' must be of type 'string | number | symbol', received: '${receivedTypeText}'.`;
|
|
2051
|
+
const origin = {
|
|
2052
|
+
end: node.getEnd(),
|
|
2053
|
+
file: node.getSourceFile(),
|
|
2054
|
+
start: node.getStart(),
|
|
2055
|
+
};
|
|
2056
|
+
EventEmitter.dispatch(["expect:error", { diagnostics: [Diagnostic.error(text, origin)], result: expectResult }]);
|
|
2057
|
+
}
|
|
2058
|
+
#onKeyArgumentMustBeProvided(assertion, expectResult) {
|
|
2059
|
+
const origin = {
|
|
2060
|
+
end: assertion.matcherName.getEnd(),
|
|
2061
|
+
file: assertion.matcherName.getSourceFile(),
|
|
2062
|
+
start: assertion.matcherName.getStart(),
|
|
2063
|
+
};
|
|
2064
|
+
EventEmitter.dispatch([
|
|
2065
|
+
"expect:error",
|
|
2066
|
+
{
|
|
2067
|
+
diagnostics: [Diagnostic.error("An argument for 'key' must be provided.", origin)],
|
|
2068
|
+
result: expectResult,
|
|
2069
|
+
},
|
|
2070
|
+
]);
|
|
1851
2071
|
}
|
|
1852
|
-
#
|
|
1853
|
-
|
|
1854
|
-
this.#assertNonNullishTypeChecker(assertion);
|
|
2072
|
+
#onNotSupportedMatcherName(assertion, expectResult) {
|
|
2073
|
+
const matcherNameText = assertion.matcherName.getText();
|
|
1855
2074
|
const origin = {
|
|
1856
|
-
breadcrumbs: assertion.ancestorNames,
|
|
1857
2075
|
end: assertion.matcherName.getEnd(),
|
|
1858
2076
|
file: assertion.matcherName.getSourceFile(),
|
|
1859
2077
|
start: assertion.matcherName.getStart(),
|
|
1860
2078
|
};
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
:
|
|
1866
|
-
|
|
2079
|
+
EventEmitter.dispatch([
|
|
2080
|
+
"expect:error",
|
|
2081
|
+
{
|
|
2082
|
+
diagnostics: [Diagnostic.error(`The '${matcherNameText}()' matcher is not supported.`, origin)],
|
|
2083
|
+
result: expectResult,
|
|
2084
|
+
},
|
|
2085
|
+
]);
|
|
1867
2086
|
}
|
|
1868
|
-
|
|
1869
|
-
const
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
}
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
throw new Error(`An argument for 'source' must be of object type, received: '${receivedText}'.`);
|
|
1924
|
-
}
|
|
1925
|
-
let targetArgumentText;
|
|
1926
|
-
if (assertion.targetType.type.flags & this.compiler.TypeFlags.StringOrNumberLiteral) {
|
|
1927
|
-
targetArgumentText = String(assertion.targetType.type.value);
|
|
1928
|
-
}
|
|
1929
|
-
else if (assertion.targetType.type.flags & this.compiler.TypeFlags.UniqueESSymbol) {
|
|
1930
|
-
targetArgumentText = this.compiler.unescapeLeadingUnderscores(assertion.targetType.type.escapedName);
|
|
1931
|
-
}
|
|
1932
|
-
else {
|
|
1933
|
-
throw new Error("An argument for 'key' must be of type 'string | number | symbol'.");
|
|
1934
|
-
}
|
|
1935
|
-
return assertion.sourceType.type.getProperties().some((property) => {
|
|
1936
|
-
return this.compiler.unescapeLeadingUnderscores(property.escapedName) === targetArgumentText;
|
|
1937
|
-
});
|
|
1938
|
-
}
|
|
1939
|
-
case "toMatch": {
|
|
1940
|
-
this.#assertNonNullishSourceType(assertion);
|
|
1941
|
-
this.#assertNonNullishTargetType(assertion);
|
|
1942
|
-
this.#assertNonNullish(assertion.typeChecker?.isTypeSubtypeOf, "The 'isTypeSubtypeOf' method is missing in the provided type checker.");
|
|
1943
|
-
return assertion.typeChecker.isTypeSubtypeOf(assertion.sourceType.type, assertion.targetType.type);
|
|
1944
|
-
}
|
|
1945
|
-
case "toRaiseError": {
|
|
1946
|
-
if (!this.#assertStringsOrNumbers(assertion.targetArguments)) {
|
|
1947
|
-
throw new Error("An argument for 'target' must be of type 'string | number'.");
|
|
1948
|
-
}
|
|
1949
|
-
if (assertion.targetArguments.length === 0) {
|
|
1950
|
-
return assertion.diagnostics.length > 0;
|
|
1951
|
-
}
|
|
1952
|
-
if (assertion.diagnostics.length !== assertion.targetArguments.length) {
|
|
1953
|
-
return false;
|
|
1954
|
-
}
|
|
1955
|
-
return assertion.targetArguments.every((expectedArgument, index) => {
|
|
1956
|
-
if (this.compiler.isStringLiteralLike(expectedArgument)) {
|
|
1957
|
-
return this.compiler
|
|
1958
|
-
.flattenDiagnosticMessageText(assertion.diagnostics[index]?.messageText, " ", 0)
|
|
1959
|
-
.includes(expectedArgument.text);
|
|
1960
|
-
}
|
|
1961
|
-
if (this.compiler.isNumericLiteral(expectedArgument)) {
|
|
1962
|
-
return Number(expectedArgument.text) === assertion.diagnostics[index]?.code;
|
|
1963
|
-
}
|
|
1964
|
-
return false;
|
|
1965
|
-
});
|
|
2087
|
+
#onSourceArgumentMustBeObjectType(node, expectResult) {
|
|
2088
|
+
const sourceText = this.compiler.isTypeNode(node) ? "A type argument for 'Source'" : "An argument for 'source'";
|
|
2089
|
+
const receivedTypeText = this.typeChecker.typeToString(this.#getType(node));
|
|
2090
|
+
const text = `${sourceText} must be of an object type, received: '${receivedTypeText}'.`;
|
|
2091
|
+
const origin = {
|
|
2092
|
+
end: node.getEnd(),
|
|
2093
|
+
file: node.getSourceFile(),
|
|
2094
|
+
start: node.getStart(),
|
|
2095
|
+
};
|
|
2096
|
+
EventEmitter.dispatch(["expect:error", { diagnostics: [Diagnostic.error(text, origin)], result: expectResult }]);
|
|
2097
|
+
}
|
|
2098
|
+
#onSourceArgumentMustBeProvided(assertion, expectResult) {
|
|
2099
|
+
const origin = {
|
|
2100
|
+
end: assertion.node.getEnd(),
|
|
2101
|
+
file: assertion.node.getSourceFile(),
|
|
2102
|
+
start: assertion.node.getStart(),
|
|
2103
|
+
};
|
|
2104
|
+
EventEmitter.dispatch([
|
|
2105
|
+
"expect:error",
|
|
2106
|
+
{
|
|
2107
|
+
diagnostics: [
|
|
2108
|
+
Diagnostic.error("An argument for 'source' or type argument for 'Source' must be provided.", origin),
|
|
2109
|
+
],
|
|
2110
|
+
result: expectResult,
|
|
2111
|
+
},
|
|
2112
|
+
]);
|
|
2113
|
+
}
|
|
2114
|
+
#onTargetArgumentMustBeProvided(assertion, expectResult) {
|
|
2115
|
+
const origin = {
|
|
2116
|
+
end: assertion.matcherName.getEnd(),
|
|
2117
|
+
file: assertion.matcherName.getSourceFile(),
|
|
2118
|
+
start: assertion.matcherName.getStart(),
|
|
2119
|
+
};
|
|
2120
|
+
EventEmitter.dispatch([
|
|
2121
|
+
"expect:error",
|
|
2122
|
+
{
|
|
2123
|
+
diagnostics: [
|
|
2124
|
+
Diagnostic.error("An argument for 'target' or type argument for 'Target' must be provided.", origin),
|
|
2125
|
+
],
|
|
2126
|
+
result: expectResult,
|
|
2127
|
+
},
|
|
2128
|
+
]);
|
|
2129
|
+
}
|
|
2130
|
+
#onTargetArgumentsMustBeStringOrNumberLiteralTypes(nodes, expectResult) {
|
|
2131
|
+
const diagnostics = [];
|
|
2132
|
+
for (const node of nodes) {
|
|
2133
|
+
const receivedType = this.#getType(node);
|
|
2134
|
+
if (!this.#isStringOrNumberLiteralType(receivedType)) {
|
|
2135
|
+
const receivedTypeText = this.typeChecker.typeToString(this.#getType(node));
|
|
2136
|
+
const origin = {
|
|
2137
|
+
end: node.getEnd(),
|
|
2138
|
+
file: node.getSourceFile(),
|
|
2139
|
+
start: node.getStart(),
|
|
2140
|
+
};
|
|
2141
|
+
diagnostics.push(Diagnostic.error(`An argument for 'target' must be of type 'string | number', received: '${receivedTypeText}'.`, origin));
|
|
1966
2142
|
}
|
|
1967
|
-
default:
|
|
1968
|
-
throw new Error(`The '${matcher}' matcher is not supported.`);
|
|
1969
2143
|
}
|
|
2144
|
+
EventEmitter.dispatch(["expect:error", { diagnostics, result: expectResult }]);
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
class ProjectService {
|
|
2149
|
+
compiler;
|
|
2150
|
+
#service;
|
|
2151
|
+
constructor(compiler) {
|
|
2152
|
+
this.compiler = compiler;
|
|
2153
|
+
function doNothing() {
|
|
2154
|
+
}
|
|
2155
|
+
function returnFalse() {
|
|
2156
|
+
return false;
|
|
2157
|
+
}
|
|
2158
|
+
function returnUndefined() {
|
|
2159
|
+
return undefined;
|
|
2160
|
+
}
|
|
2161
|
+
const noopWatcher = { close: doNothing };
|
|
2162
|
+
const noopLogger = {
|
|
2163
|
+
close: doNothing,
|
|
2164
|
+
endGroup: doNothing,
|
|
2165
|
+
getLogFileName: returnUndefined,
|
|
2166
|
+
hasLevel: returnFalse,
|
|
2167
|
+
info: doNothing,
|
|
2168
|
+
loggingEnabled: returnFalse,
|
|
2169
|
+
msg: doNothing,
|
|
2170
|
+
perftrc: doNothing,
|
|
2171
|
+
startGroup: doNothing,
|
|
2172
|
+
};
|
|
2173
|
+
const host = {
|
|
2174
|
+
...this.compiler.sys,
|
|
2175
|
+
clearImmediate,
|
|
2176
|
+
clearTimeout,
|
|
2177
|
+
setImmediate,
|
|
2178
|
+
setTimeout,
|
|
2179
|
+
watchDirectory: () => noopWatcher,
|
|
2180
|
+
watchFile: () => noopWatcher,
|
|
2181
|
+
};
|
|
2182
|
+
this.#service = new this.compiler.server.ProjectService({
|
|
2183
|
+
allowLocalPluginLoads: true,
|
|
2184
|
+
cancellationToken: this.compiler.server.nullCancellationToken,
|
|
2185
|
+
host,
|
|
2186
|
+
logger: noopLogger,
|
|
2187
|
+
session: undefined,
|
|
2188
|
+
useInferredProjectPerProjectRoot: true,
|
|
2189
|
+
useSingleInferredProject: false,
|
|
2190
|
+
});
|
|
2191
|
+
}
|
|
2192
|
+
closeFile(filePath) {
|
|
2193
|
+
this.#service.closeClientFile(filePath);
|
|
1970
2194
|
}
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
2195
|
+
getDefaultProject(filePath) {
|
|
2196
|
+
return this.#service.getDefaultProjectForFile(this.compiler.server.toNormalizedPath(filePath), true);
|
|
2197
|
+
}
|
|
2198
|
+
getLanguageService(filePath) {
|
|
2199
|
+
const project = this.getDefaultProject(filePath);
|
|
2200
|
+
if (!project) {
|
|
2201
|
+
return;
|
|
1974
2202
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
2203
|
+
return project.getLanguageService(true);
|
|
2204
|
+
}
|
|
2205
|
+
openFile(filePath, sourceText, projectRootPath) {
|
|
2206
|
+
const { configFileErrors, configFileName } = this.#service.openClientFile(filePath, sourceText, undefined, projectRootPath);
|
|
2207
|
+
EventEmitter.dispatch([
|
|
2208
|
+
"project:info",
|
|
2209
|
+
{ compilerVersion: this.compiler.version, projectConfigFilePath: configFileName },
|
|
2210
|
+
]);
|
|
2211
|
+
if (configFileErrors && configFileErrors.length > 0) {
|
|
2212
|
+
EventEmitter.dispatch([
|
|
2213
|
+
"project:error",
|
|
2214
|
+
{ diagnostics: Diagnostic.fromDiagnostics(configFileErrors, this.compiler) },
|
|
2215
|
+
]);
|
|
1977
2216
|
}
|
|
1978
|
-
throw new Error("An argument for 'target' must be of type 'string | number'.");
|
|
1979
2217
|
}
|
|
1980
2218
|
}
|
|
1981
2219
|
|
|
1982
2220
|
class TestTreeWorker {
|
|
1983
2221
|
resolvedConfig;
|
|
1984
2222
|
compiler;
|
|
1985
|
-
#
|
|
2223
|
+
#expect;
|
|
1986
2224
|
#fileResult;
|
|
1987
2225
|
#hasOnly;
|
|
1988
2226
|
#position;
|
|
1989
2227
|
#signal;
|
|
1990
|
-
constructor(resolvedConfig, compiler, options) {
|
|
2228
|
+
constructor(resolvedConfig, compiler, expect, options) {
|
|
1991
2229
|
this.resolvedConfig = resolvedConfig;
|
|
1992
2230
|
this.compiler = compiler;
|
|
1993
|
-
this.#
|
|
2231
|
+
this.#expect = expect;
|
|
2232
|
+
this.#fileResult = options.fileResult;
|
|
1994
2233
|
this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
|
|
1995
2234
|
this.#position = options.position;
|
|
1996
2235
|
this.#signal = options.signal;
|
|
1997
|
-
this.#fileResult = options.fileResult;
|
|
1998
2236
|
}
|
|
1999
2237
|
#resolveRunMode(mode, member) {
|
|
2000
2238
|
if (member.flags & 1) {
|
|
@@ -2062,8 +2300,11 @@ class TestTreeWorker {
|
|
|
2062
2300
|
]);
|
|
2063
2301
|
return;
|
|
2064
2302
|
}
|
|
2065
|
-
const
|
|
2066
|
-
if (
|
|
2303
|
+
const matchResult = this.#expect.match(assertion, expectResult);
|
|
2304
|
+
if (matchResult == null) {
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
if (assertion.isNot ? !matchResult.isMatch : matchResult.isMatch) {
|
|
2067
2308
|
if (runMode & 1) {
|
|
2068
2309
|
const text = ["The assertion was supposed to fail, but it passed.", "Consider removing the '.fail' flag."];
|
|
2069
2310
|
const origin = {
|
|
@@ -2073,10 +2314,7 @@ class TestTreeWorker {
|
|
|
2073
2314
|
};
|
|
2074
2315
|
EventEmitter.dispatch([
|
|
2075
2316
|
"expect:error",
|
|
2076
|
-
{
|
|
2077
|
-
diagnostics: [Diagnostic.error(text, origin)],
|
|
2078
|
-
result: expectResult,
|
|
2079
|
-
},
|
|
2317
|
+
{ diagnostics: [Diagnostic.error(text, origin)], result: expectResult },
|
|
2080
2318
|
]);
|
|
2081
2319
|
}
|
|
2082
2320
|
else {
|
|
@@ -2088,13 +2326,20 @@ class TestTreeWorker {
|
|
|
2088
2326
|
EventEmitter.dispatch(["expect:pass", { result: expectResult }]);
|
|
2089
2327
|
}
|
|
2090
2328
|
else {
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
]
|
|
2329
|
+
const origin = {
|
|
2330
|
+
breadcrumbs: assertion.ancestorNames,
|
|
2331
|
+
end: assertion.matcherName.getEnd(),
|
|
2332
|
+
file: assertion.matcherName.getSourceFile(),
|
|
2333
|
+
start: assertion.matcherName.getStart(),
|
|
2334
|
+
};
|
|
2335
|
+
const diagnostics = [];
|
|
2336
|
+
for (const diagnostic of matchResult.explain()) {
|
|
2337
|
+
if (diagnostic.origin == null) {
|
|
2338
|
+
diagnostic.add({ origin });
|
|
2339
|
+
}
|
|
2340
|
+
diagnostics.push(diagnostic);
|
|
2341
|
+
}
|
|
2342
|
+
EventEmitter.dispatch(["expect:fail", { diagnostics, result: expectResult }]);
|
|
2098
2343
|
}
|
|
2099
2344
|
}
|
|
2100
2345
|
}
|
|
@@ -2198,8 +2443,7 @@ class TestFileRunner {
|
|
|
2198
2443
|
if (!sourceFile) {
|
|
2199
2444
|
return;
|
|
2200
2445
|
}
|
|
2201
|
-
const
|
|
2202
|
-
const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics, typeChecker);
|
|
2446
|
+
const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics);
|
|
2203
2447
|
if (testTree.diagnostics.length > 0) {
|
|
2204
2448
|
EventEmitter.dispatch([
|
|
2205
2449
|
"file:error",
|
|
@@ -2210,7 +2454,14 @@ class TestFileRunner {
|
|
|
2210
2454
|
]);
|
|
2211
2455
|
return;
|
|
2212
2456
|
}
|
|
2213
|
-
const
|
|
2457
|
+
const typeChecker = program.getTypeChecker();
|
|
2458
|
+
if (!Expect.assertTypeChecker(typeChecker)) {
|
|
2459
|
+
const text = "The required 'isTypeAssignableTo()', 'isTypeIdenticalTo()' and 'isTypeSubtypeOf()' methods are missing in the provided type checker.";
|
|
2460
|
+
EventEmitter.dispatch(["file:error", { diagnostics: [Diagnostic.error(text)], result: fileResult }]);
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
const expect = new Expect(this.compiler, typeChecker);
|
|
2464
|
+
const testTreeWorker = new TestTreeWorker(this.resolvedConfig, this.compiler, expect, {
|
|
2214
2465
|
fileResult,
|
|
2215
2466
|
hasOnly: testTree.hasOnly,
|
|
2216
2467
|
position,
|
|
@@ -3110,11 +3361,13 @@ class ManifestWorker {
|
|
|
3110
3361
|
#cachePath;
|
|
3111
3362
|
#manifestFileName = "store-manifest.json";
|
|
3112
3363
|
#manifestFilePath;
|
|
3364
|
+
#onDiagnostic;
|
|
3113
3365
|
#prune;
|
|
3114
3366
|
#registryUrl = new URL("https://registry.npmjs.org");
|
|
3115
3367
|
#timeout = Environment.timeout * 1000;
|
|
3116
|
-
constructor(cachePath, prune) {
|
|
3368
|
+
constructor(cachePath, onDiagnostic, prune) {
|
|
3117
3369
|
this.#cachePath = cachePath;
|
|
3370
|
+
this.#onDiagnostic = onDiagnostic;
|
|
3118
3371
|
this.#manifestFilePath = path.join(cachePath, this.#manifestFileName);
|
|
3119
3372
|
this.#prune = prune;
|
|
3120
3373
|
}
|
|
@@ -3202,9 +3455,6 @@ class ManifestWorker {
|
|
|
3202
3455
|
}
|
|
3203
3456
|
return manifest;
|
|
3204
3457
|
}
|
|
3205
|
-
#onDiagnostic(diagnostic) {
|
|
3206
|
-
EventEmitter.dispatch(["store:error", { diagnostics: [diagnostic] }]);
|
|
3207
|
-
}
|
|
3208
3458
|
async open(signal) {
|
|
3209
3459
|
let manifest;
|
|
3210
3460
|
if (!existsSync(this.#manifestFilePath)) {
|
|
@@ -3268,7 +3518,7 @@ class StoreService {
|
|
|
3268
3518
|
constructor() {
|
|
3269
3519
|
this.#cachePath = Environment.storePath;
|
|
3270
3520
|
this.#compilerModuleWorker = new CompilerModuleWorker(this.#cachePath, this.#onDiagnostic);
|
|
3271
|
-
this.#manifestWorker = new ManifestWorker(this.#cachePath,
|
|
3521
|
+
this.#manifestWorker = new ManifestWorker(this.#cachePath, this.#onDiagnostic, this.prune);
|
|
3272
3522
|
}
|
|
3273
3523
|
get supportedTags() {
|
|
3274
3524
|
if (!this.#manifest) {
|
|
@@ -3316,9 +3566,9 @@ class StoreService {
|
|
|
3316
3566
|
}
|
|
3317
3567
|
this.#manifest = await this.#manifestWorker.open(signal);
|
|
3318
3568
|
}
|
|
3319
|
-
async
|
|
3569
|
+
prune = async () => {
|
|
3320
3570
|
await fs.rm(this.#cachePath, { force: true, recursive: true });
|
|
3321
|
-
}
|
|
3571
|
+
};
|
|
3322
3572
|
resolveTag(tag) {
|
|
3323
3573
|
if (!this.#manifest) {
|
|
3324
3574
|
this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
|
|
@@ -3371,154 +3621,6 @@ class StoreService {
|
|
|
3371
3621
|
}
|
|
3372
3622
|
}
|
|
3373
3623
|
|
|
3374
|
-
class JsonText {
|
|
3375
|
-
props;
|
|
3376
|
-
constructor(props) {
|
|
3377
|
-
this.props = props;
|
|
3378
|
-
}
|
|
3379
|
-
render() {
|
|
3380
|
-
return Scribbler.createElement(Line, null, JSON.stringify(this.#sortObject(this.props.input), null, 2));
|
|
3381
|
-
}
|
|
3382
|
-
#sortObject(target) {
|
|
3383
|
-
if (Array.isArray(target)) {
|
|
3384
|
-
return target;
|
|
3385
|
-
}
|
|
3386
|
-
return Object.keys(target)
|
|
3387
|
-
.sort()
|
|
3388
|
-
.reduce((result, key) => {
|
|
3389
|
-
result[key] = target[key];
|
|
3390
|
-
return result;
|
|
3391
|
-
}, {});
|
|
3392
|
-
}
|
|
3393
|
-
}
|
|
3394
|
-
function formattedText(input) {
|
|
3395
|
-
if (typeof input === "string") {
|
|
3396
|
-
return Scribbler.createElement(Line, null, input);
|
|
3397
|
-
}
|
|
3398
|
-
return Scribbler.createElement(JsonText, { input: input });
|
|
3399
|
-
}
|
|
3400
|
-
|
|
3401
|
-
const usageExamples = [
|
|
3402
|
-
["tstyche", "Run all tests."],
|
|
3403
|
-
["tstyche path/to/first.test.ts second", "Only run the test files with matching path."],
|
|
3404
|
-
["tstyche --target 4.7,4.8,latest", "Test on all specified versions of TypeScript."],
|
|
3405
|
-
];
|
|
3406
|
-
class HintText {
|
|
3407
|
-
props;
|
|
3408
|
-
constructor(props) {
|
|
3409
|
-
this.props = props;
|
|
3410
|
-
}
|
|
3411
|
-
render() {
|
|
3412
|
-
return (Scribbler.createElement(Text, { indent: 1, color: "90" }, this.props.children));
|
|
3413
|
-
}
|
|
3414
|
-
}
|
|
3415
|
-
class HelpHeaderText {
|
|
3416
|
-
props;
|
|
3417
|
-
constructor(props) {
|
|
3418
|
-
this.props = props;
|
|
3419
|
-
}
|
|
3420
|
-
render() {
|
|
3421
|
-
const hint = (Scribbler.createElement(HintText, null,
|
|
3422
|
-
Scribbler.createElement(Text, null, this.props.tstycheVersion)));
|
|
3423
|
-
return (Scribbler.createElement(Line, null,
|
|
3424
|
-
Scribbler.createElement(Text, null, "The TSTyche Type Test Runner"),
|
|
3425
|
-
hint));
|
|
3426
|
-
}
|
|
3427
|
-
}
|
|
3428
|
-
class CommandText {
|
|
3429
|
-
props;
|
|
3430
|
-
constructor(props) {
|
|
3431
|
-
this.props = props;
|
|
3432
|
-
}
|
|
3433
|
-
render() {
|
|
3434
|
-
let hint = undefined;
|
|
3435
|
-
if (this.props.hint != null) {
|
|
3436
|
-
hint = Scribbler.createElement(HintText, null, this.props.hint);
|
|
3437
|
-
}
|
|
3438
|
-
return (Scribbler.createElement(Line, { indent: 1 },
|
|
3439
|
-
Scribbler.createElement(Text, { color: "34" }, this.props.text),
|
|
3440
|
-
hint));
|
|
3441
|
-
}
|
|
3442
|
-
}
|
|
3443
|
-
class OptionDescriptionText {
|
|
3444
|
-
props;
|
|
3445
|
-
constructor(props) {
|
|
3446
|
-
this.props = props;
|
|
3447
|
-
}
|
|
3448
|
-
render() {
|
|
3449
|
-
return Scribbler.createElement(Line, { indent: 1 }, this.props.text);
|
|
3450
|
-
}
|
|
3451
|
-
}
|
|
3452
|
-
class CliUsageText {
|
|
3453
|
-
render() {
|
|
3454
|
-
const usageText = usageExamples.map(([commandText, descriptionText]) => (Scribbler.createElement(Text, null,
|
|
3455
|
-
Scribbler.createElement(CommandText, { text: commandText }),
|
|
3456
|
-
Scribbler.createElement(OptionDescriptionText, { text: descriptionText }),
|
|
3457
|
-
Scribbler.createElement(Line, null))));
|
|
3458
|
-
return Scribbler.createElement(Text, null, usageText);
|
|
3459
|
-
}
|
|
3460
|
-
}
|
|
3461
|
-
class OptionNameText {
|
|
3462
|
-
props;
|
|
3463
|
-
constructor(props) {
|
|
3464
|
-
this.props = props;
|
|
3465
|
-
}
|
|
3466
|
-
render() {
|
|
3467
|
-
return Scribbler.createElement(Text, null,
|
|
3468
|
-
"--",
|
|
3469
|
-
this.props.text);
|
|
3470
|
-
}
|
|
3471
|
-
}
|
|
3472
|
-
class OptionHintText {
|
|
3473
|
-
props;
|
|
3474
|
-
constructor(props) {
|
|
3475
|
-
this.props = props;
|
|
3476
|
-
}
|
|
3477
|
-
render() {
|
|
3478
|
-
if (this.props.definition.brand === "list") {
|
|
3479
|
-
return (Scribbler.createElement(Text, null,
|
|
3480
|
-
this.props.definition.brand,
|
|
3481
|
-
" of ",
|
|
3482
|
-
this.props.definition.items.brand,
|
|
3483
|
-
"s"));
|
|
3484
|
-
}
|
|
3485
|
-
return Scribbler.createElement(Text, null, this.props.definition.brand);
|
|
3486
|
-
}
|
|
3487
|
-
}
|
|
3488
|
-
class CliOptionsText {
|
|
3489
|
-
props;
|
|
3490
|
-
constructor(props) {
|
|
3491
|
-
this.props = props;
|
|
3492
|
-
}
|
|
3493
|
-
render() {
|
|
3494
|
-
const definitions = Array.from(this.props.optionDefinitions.values());
|
|
3495
|
-
const optionsText = definitions.map((definition) => (Scribbler.createElement(Text, null,
|
|
3496
|
-
Scribbler.createElement(CommandText, { text: Scribbler.createElement(OptionNameText, { text: definition.name }), hint: Scribbler.createElement(OptionHintText, { definition: definition }) }),
|
|
3497
|
-
Scribbler.createElement(OptionDescriptionText, { text: definition.description }),
|
|
3498
|
-
Scribbler.createElement(Line, null))));
|
|
3499
|
-
return (Scribbler.createElement(Text, null,
|
|
3500
|
-
Scribbler.createElement(Line, null, "CLI Options"),
|
|
3501
|
-
Scribbler.createElement(Line, null),
|
|
3502
|
-
optionsText));
|
|
3503
|
-
}
|
|
3504
|
-
}
|
|
3505
|
-
class HelpFooterText {
|
|
3506
|
-
render() {
|
|
3507
|
-
return Scribbler.createElement(Line, null, "To learn more, visit https://tstyche.org");
|
|
3508
|
-
}
|
|
3509
|
-
}
|
|
3510
|
-
function helpText(optionDefinitions, tstycheVersion) {
|
|
3511
|
-
return (Scribbler.createElement(Text, null,
|
|
3512
|
-
Scribbler.createElement(HelpHeaderText, { tstycheVersion: tstycheVersion }),
|
|
3513
|
-
Scribbler.createElement(Line, null),
|
|
3514
|
-
Scribbler.createElement(CliUsageText, null),
|
|
3515
|
-
Scribbler.createElement(Line, null),
|
|
3516
|
-
Scribbler.createElement(CliOptionsText, { optionDefinitions: optionDefinitions }),
|
|
3517
|
-
Scribbler.createElement(Line, null),
|
|
3518
|
-
Scribbler.createElement(HelpFooterText, null),
|
|
3519
|
-
Scribbler.createElement(Line, null)));
|
|
3520
|
-
}
|
|
3521
|
-
|
|
3522
3624
|
class Cli {
|
|
3523
3625
|
#abortController = new AbortController();
|
|
3524
3626
|
#logger;
|
|
@@ -3617,4 +3719,4 @@ class Cli {
|
|
|
3617
3719
|
}
|
|
3618
3720
|
}
|
|
3619
3721
|
|
|
3620
|
-
export { Assertion,
|
|
3722
|
+
export { Assertion, Cli, CollectService, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, Environment, EventEmitter, Expect, ExpectResult, FileResult, Logger, OptionBrand, OptionDefinitionsMap, OptionGroup, ProjectResult, ProjectService, Reporter, Result, ResultCount, ResultManager, ResultStatus, ResultTiming, Scribbler, StoreService, SummaryReporter, TSTyche, TargetResult, TaskRunner, TestMember, TestMemberBrand, TestMemberFlags, TestResult, TestTree, ThoroughReporter, addsPackageStepText, describeNameText, diagnosticText, fileStatusText, fileViewText, formattedText, helpText, summaryText, testNameText, usesCompilerStepText };
|