tstyche 1.0.0-beta.5 → 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 +32 -6
- package/README.md +7 -6
- package/build/index.d.cts +4 -0
- package/build/index.d.ts +4 -0
- package/build/tstyche.d.ts +108 -71
- package/build/tstyche.js +685 -539
- 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) {
|
|
@@ -1511,6 +1606,7 @@ class CollectService {
|
|
|
1511
1606
|
"toBeUnknown",
|
|
1512
1607
|
"toBeVoid",
|
|
1513
1608
|
"toEqual",
|
|
1609
|
+
"toHaveProperty",
|
|
1514
1610
|
"toMatch",
|
|
1515
1611
|
"toRaiseError",
|
|
1516
1612
|
];
|
|
@@ -1560,8 +1656,8 @@ class CollectService {
|
|
|
1560
1656
|
this.#collectTestMembers(node, identifiers, parent);
|
|
1561
1657
|
});
|
|
1562
1658
|
}
|
|
1563
|
-
createTestTree(sourceFile, semanticDiagnostics = []
|
|
1564
|
-
const testTree = new TestTree(this.compiler, semanticDiagnostics, sourceFile
|
|
1659
|
+
createTestTree(sourceFile, semanticDiagnostics = []) {
|
|
1660
|
+
const testTree = new TestTree(this.compiler, semanticDiagnostics, sourceFile);
|
|
1565
1661
|
this.#collectTestMembers(sourceFile, new IdentifierLookup(this.compiler), testTree);
|
|
1566
1662
|
return testTree;
|
|
1567
1663
|
}
|
|
@@ -1592,365 +1688,551 @@ var TestMemberFlags;
|
|
|
1592
1688
|
TestMemberFlags[TestMemberFlags["Todo"] = 8] = "Todo";
|
|
1593
1689
|
})(TestMemberFlags || (TestMemberFlags = {}));
|
|
1594
1690
|
|
|
1595
|
-
class
|
|
1596
|
-
|
|
1597
|
-
#
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
return false;
|
|
1604
|
-
}
|
|
1605
|
-
function returnUndefined() {
|
|
1606
|
-
return undefined;
|
|
1607
|
-
}
|
|
1608
|
-
const noopWatcher = { close: doNothing };
|
|
1609
|
-
const noopLogger = {
|
|
1610
|
-
close: doNothing,
|
|
1611
|
-
endGroup: doNothing,
|
|
1612
|
-
getLogFileName: returnUndefined,
|
|
1613
|
-
hasLevel: returnFalse,
|
|
1614
|
-
info: doNothing,
|
|
1615
|
-
loggingEnabled: returnFalse,
|
|
1616
|
-
msg: doNothing,
|
|
1617
|
-
perftrc: doNothing,
|
|
1618
|
-
startGroup: doNothing,
|
|
1619
|
-
};
|
|
1620
|
-
const host = {
|
|
1621
|
-
...this.compiler.sys,
|
|
1622
|
-
clearImmediate,
|
|
1623
|
-
clearTimeout,
|
|
1624
|
-
setImmediate,
|
|
1625
|
-
setTimeout,
|
|
1626
|
-
watchDirectory: () => noopWatcher,
|
|
1627
|
-
watchFile: () => noopWatcher,
|
|
1628
|
-
};
|
|
1629
|
-
this.#service = new this.compiler.server.ProjectService({
|
|
1630
|
-
cancellationToken: this.compiler.server.nullCancellationToken,
|
|
1631
|
-
host,
|
|
1632
|
-
logger: noopLogger,
|
|
1633
|
-
session: undefined,
|
|
1634
|
-
useInferredProjectPerProjectRoot: true,
|
|
1635
|
-
useSingleInferredProject: false,
|
|
1636
|
-
});
|
|
1637
|
-
}
|
|
1638
|
-
closeFile(filePath) {
|
|
1639
|
-
this.#service.closeClientFile(filePath);
|
|
1640
|
-
}
|
|
1641
|
-
getDefaultProject(filePath) {
|
|
1642
|
-
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;
|
|
1643
1699
|
}
|
|
1644
|
-
|
|
1645
|
-
const
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
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}'.`)];
|
|
1650
1705
|
}
|
|
1651
|
-
|
|
1652
|
-
const
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
if (configFileErrors && configFileErrors.length > 0) {
|
|
1658
|
-
EventEmitter.dispatch([
|
|
1659
|
-
"project:error",
|
|
1660
|
-
{ diagnostics: Diagnostic.fromDiagnostics(configFileErrors, this.compiler) },
|
|
1661
|
-
]);
|
|
1662
|
-
}
|
|
1706
|
+
match(sourceType, isNot) {
|
|
1707
|
+
const isMatch = Boolean(sourceType.flags & this.#targetTypeFlag);
|
|
1708
|
+
return {
|
|
1709
|
+
explain: () => this.#explain(sourceType, isNot),
|
|
1710
|
+
isMatch,
|
|
1711
|
+
};
|
|
1663
1712
|
}
|
|
1664
1713
|
}
|
|
1665
1714
|
|
|
1666
|
-
class
|
|
1667
|
-
|
|
1668
|
-
constructor(
|
|
1669
|
-
this.
|
|
1670
|
-
}
|
|
1671
|
-
#assertNonNullish(value, message) {
|
|
1672
|
-
if (value == null) {
|
|
1673
|
-
throw Error(message);
|
|
1674
|
-
}
|
|
1715
|
+
class ToBeAssignable {
|
|
1716
|
+
typeChecker;
|
|
1717
|
+
constructor(typeChecker) {
|
|
1718
|
+
this.typeChecker = typeChecker;
|
|
1675
1719
|
}
|
|
1676
|
-
#
|
|
1677
|
-
|
|
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}'.`)];
|
|
1678
1726
|
}
|
|
1679
|
-
|
|
1680
|
-
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
|
+
};
|
|
1681
1733
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
class ToEqual {
|
|
1737
|
+
typeChecker;
|
|
1738
|
+
constructor(typeChecker) {
|
|
1739
|
+
this.typeChecker = typeChecker;
|
|
1684
1740
|
}
|
|
1685
|
-
#
|
|
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
|
-
this
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
end: assertion.matcherName.getEnd(),
|
|
1696
|
-
file: assertion.matcherName.getSourceFile(),
|
|
1697
|
-
start: assertion.matcherName.getStart(),
|
|
1748
|
+
match(sourceType, targetType, isNot) {
|
|
1749
|
+
const isMatch = this.typeChecker.isTypeIdenticalTo(sourceType, targetType);
|
|
1750
|
+
return {
|
|
1751
|
+
explain: () => this.#explain(sourceType, targetType, isNot),
|
|
1752
|
+
isMatch,
|
|
1698
1753
|
};
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1754
|
+
}
|
|
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,
|
|
1794
|
+
};
|
|
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),
|
|
1709
1878
|
];
|
|
1879
|
+
const text = `${sourceText} raised a type error ${expectedText}.`;
|
|
1880
|
+
diagnostics.push(Diagnostic.error(text).add({ related }));
|
|
1710
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);
|
|
1711
1991
|
case "toBeAny":
|
|
1712
|
-
return this.#isType(assertion, "any");
|
|
1713
1992
|
case "toBeBigInt":
|
|
1714
|
-
return this.#isType(assertion, "bigint");
|
|
1715
1993
|
case "toBeBoolean":
|
|
1716
|
-
return this.#isType(assertion, "boolean");
|
|
1717
1994
|
case "toBeNever":
|
|
1718
|
-
return this.#isType(assertion, "never");
|
|
1719
1995
|
case "toBeNull":
|
|
1720
|
-
return this.#isType(assertion, "null");
|
|
1721
1996
|
case "toBeNumber":
|
|
1722
|
-
return this.#isType(assertion, "number");
|
|
1723
1997
|
case "toBeString":
|
|
1724
|
-
return this.#isType(assertion, "string");
|
|
1725
1998
|
case "toBeSymbol":
|
|
1726
|
-
return this.#isType(assertion, "symbol");
|
|
1727
1999
|
case "toBeUndefined":
|
|
1728
|
-
return this.#isType(assertion, "undefined");
|
|
1729
2000
|
case "toBeUniqueSymbol":
|
|
1730
|
-
return this.#isType(assertion, "unique symbol");
|
|
1731
2001
|
case "toBeUnknown":
|
|
1732
|
-
return this.#isType(assertion, "unknown");
|
|
1733
2002
|
case "toBeVoid":
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
];
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
];
|
|
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);
|
|
2008
|
+
case "toHaveProperty": {
|
|
2009
|
+
if (assertion.source[0] == null) {
|
|
2010
|
+
this.#onSourceArgumentMustBeProvided(assertion, expectResult);
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
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;
|
|
2019
|
+
}
|
|
2020
|
+
if (assertion.target[0] == null) {
|
|
2021
|
+
this.#onKeyArgumentMustBeProvided(assertion, expectResult);
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
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);
|
|
1756
2030
|
}
|
|
1757
2031
|
case "toRaiseError": {
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
}
|
|
1762
|
-
const sourceText = assertion.sourceType.source === 0 ? "Expression" : "Type definition";
|
|
1763
|
-
if (assertion.diagnostics.length === 0) {
|
|
1764
|
-
return [Diagnostic.error(`${sourceText} did not raise a type error.`, origin)];
|
|
1765
|
-
}
|
|
1766
|
-
if (assertion.isNot && assertion.targetArguments.length === 0) {
|
|
1767
|
-
const related = [
|
|
1768
|
-
Diagnostic.error(`The raised type error${assertion.diagnostics.length === 1 ? "" : "s"}:`),
|
|
1769
|
-
...Diagnostic.fromDiagnostics(assertion.diagnostics, this.compiler),
|
|
1770
|
-
];
|
|
1771
|
-
return [
|
|
1772
|
-
Diagnostic.error(`${sourceText} raised ${assertion.diagnostics.length === 1 ? "a" : assertion.diagnostics.length} type error${assertion.diagnostics.length === 1 ? "" : "s"}.`, origin).add({ related }),
|
|
1773
|
-
];
|
|
1774
|
-
}
|
|
1775
|
-
if (assertion.diagnostics.length !== assertion.targetArguments.length) {
|
|
1776
|
-
const expectedText = assertion.diagnostics.length > assertion.targetArguments.length
|
|
1777
|
-
? `only ${assertion.targetArguments.length} type error${assertion.targetArguments.length === 1 ? "" : "s"}`
|
|
1778
|
-
: `${assertion.targetArguments.length} type error${assertion.targetArguments.length === 1 ? "" : "s"}`;
|
|
1779
|
-
const foundText = assertion.diagnostics.length > assertion.targetArguments.length
|
|
1780
|
-
? `${assertion.diagnostics.length}`
|
|
1781
|
-
: `only ${assertion.diagnostics.length}`;
|
|
1782
|
-
const related = [
|
|
1783
|
-
Diagnostic.error(`The raised type error${assertion.diagnostics.length === 1 ? "" : "s"}:`),
|
|
1784
|
-
...Diagnostic.fromDiagnostics(assertion.diagnostics, this.compiler),
|
|
1785
|
-
];
|
|
1786
|
-
const diagnostic = Diagnostic.error(`Expected ${expectedText}, but ${foundText} ${assertion.diagnostics.length === 1 ? "was" : "were"} raised.`, origin).add({
|
|
1787
|
-
related,
|
|
1788
|
-
});
|
|
1789
|
-
return [diagnostic];
|
|
2032
|
+
if (assertion.source[0] == null) {
|
|
2033
|
+
this.#onSourceArgumentMustBeProvided(assertion, expectResult);
|
|
2034
|
+
return;
|
|
1790
2035
|
}
|
|
1791
|
-
const
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
const isMatch = this.#matchExpectedError(diagnostic, argument);
|
|
1798
|
-
if (!assertion.isNot && !isMatch) {
|
|
1799
|
-
const expectedText = this.compiler.isStringLiteral(argument)
|
|
1800
|
-
? `matching substring '${argument.text}'`
|
|
1801
|
-
: `with code ${argument.text}`;
|
|
1802
|
-
const related = [
|
|
1803
|
-
Diagnostic.error("The raised type error:"),
|
|
1804
|
-
...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
|
|
1805
|
-
];
|
|
1806
|
-
diagnostics.push(Diagnostic.error(`${sourceText} did not raise a type error ${expectedText}.`, origin).add({ related }));
|
|
1807
|
-
}
|
|
1808
|
-
if (assertion.isNot && isMatch) {
|
|
1809
|
-
const expectedText = this.compiler.isStringLiteral(argument)
|
|
1810
|
-
? `matching substring '${argument.text}'`
|
|
1811
|
-
: `with code ${argument.text}`;
|
|
1812
|
-
const related = [
|
|
1813
|
-
Diagnostic.error("The raised type error:"),
|
|
1814
|
-
...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
|
|
1815
|
-
];
|
|
1816
|
-
diagnostics.push(Diagnostic.error(`${sourceText} raised a type error ${expectedText}.`, origin).add({ related }));
|
|
1817
|
-
}
|
|
1818
|
-
});
|
|
1819
|
-
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);
|
|
1820
2042
|
}
|
|
1821
2043
|
default:
|
|
1822
|
-
|
|
2044
|
+
this.#onNotSupportedMatcherName(assertion, expectResult);
|
|
2045
|
+
return;
|
|
1823
2046
|
}
|
|
1824
2047
|
}
|
|
1825
|
-
#
|
|
1826
|
-
this.#
|
|
1827
|
-
|
|
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 }]);
|
|
1828
2057
|
}
|
|
1829
|
-
#
|
|
1830
|
-
this.#assertNonNullishSourceType(assertion);
|
|
1831
|
-
this.#assertNonNullishTypeChecker(assertion);
|
|
2058
|
+
#onKeyArgumentMustBeProvided(assertion, expectResult) {
|
|
1832
2059
|
const origin = {
|
|
1833
|
-
breadcrumbs: assertion.ancestorNames,
|
|
1834
2060
|
end: assertion.matcherName.getEnd(),
|
|
1835
2061
|
file: assertion.matcherName.getSourceFile(),
|
|
1836
2062
|
start: assertion.matcherName.getStart(),
|
|
1837
2063
|
};
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
:
|
|
1843
|
-
|
|
2064
|
+
EventEmitter.dispatch([
|
|
2065
|
+
"expect:error",
|
|
2066
|
+
{
|
|
2067
|
+
diagnostics: [Diagnostic.error("An argument for 'key' must be provided.", origin)],
|
|
2068
|
+
result: expectResult,
|
|
2069
|
+
},
|
|
2070
|
+
]);
|
|
1844
2071
|
}
|
|
1845
|
-
|
|
1846
|
-
const
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
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
|
-
.includes(expectedArgument.text);
|
|
1916
|
-
}
|
|
1917
|
-
if (this.compiler.isNumericLiteral(expectedArgument)) {
|
|
1918
|
-
return Number(expectedArgument.text) === assertion.diagnostics[index]?.code;
|
|
1919
|
-
}
|
|
1920
|
-
return false;
|
|
1921
|
-
});
|
|
2072
|
+
#onNotSupportedMatcherName(assertion, expectResult) {
|
|
2073
|
+
const matcherNameText = assertion.matcherName.getText();
|
|
2074
|
+
const origin = {
|
|
2075
|
+
end: assertion.matcherName.getEnd(),
|
|
2076
|
+
file: assertion.matcherName.getSourceFile(),
|
|
2077
|
+
start: assertion.matcherName.getStart(),
|
|
2078
|
+
};
|
|
2079
|
+
EventEmitter.dispatch([
|
|
2080
|
+
"expect:error",
|
|
2081
|
+
{
|
|
2082
|
+
diagnostics: [Diagnostic.error(`The '${matcherNameText}()' matcher is not supported.`, origin)],
|
|
2083
|
+
result: expectResult,
|
|
2084
|
+
},
|
|
2085
|
+
]);
|
|
2086
|
+
}
|
|
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));
|
|
1922
2142
|
}
|
|
1923
|
-
default:
|
|
1924
|
-
throw new Error(`The '${matcher}' matcher is not supported.`);
|
|
1925
2143
|
}
|
|
2144
|
+
EventEmitter.dispatch(["expect:error", { diagnostics, result: expectResult }]);
|
|
1926
2145
|
}
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
class ProjectService {
|
|
2149
|
+
compiler;
|
|
2150
|
+
#service;
|
|
2151
|
+
constructor(compiler) {
|
|
2152
|
+
this.compiler = compiler;
|
|
2153
|
+
function doNothing() {
|
|
1930
2154
|
}
|
|
1931
|
-
|
|
1932
|
-
return
|
|
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);
|
|
2194
|
+
}
|
|
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;
|
|
2202
|
+
}
|
|
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
|
+
]);
|
|
1933
2216
|
}
|
|
1934
|
-
throw new Error("An argument for 'target' must be of type 'string | number'.");
|
|
1935
2217
|
}
|
|
1936
2218
|
}
|
|
1937
2219
|
|
|
1938
2220
|
class TestTreeWorker {
|
|
1939
2221
|
resolvedConfig;
|
|
1940
2222
|
compiler;
|
|
1941
|
-
#
|
|
2223
|
+
#expect;
|
|
1942
2224
|
#fileResult;
|
|
1943
2225
|
#hasOnly;
|
|
1944
2226
|
#position;
|
|
1945
2227
|
#signal;
|
|
1946
|
-
constructor(resolvedConfig, compiler, options) {
|
|
2228
|
+
constructor(resolvedConfig, compiler, expect, options) {
|
|
1947
2229
|
this.resolvedConfig = resolvedConfig;
|
|
1948
2230
|
this.compiler = compiler;
|
|
1949
|
-
this.#
|
|
2231
|
+
this.#expect = expect;
|
|
2232
|
+
this.#fileResult = options.fileResult;
|
|
1950
2233
|
this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
|
|
1951
2234
|
this.#position = options.position;
|
|
1952
2235
|
this.#signal = options.signal;
|
|
1953
|
-
this.#fileResult = options.fileResult;
|
|
1954
2236
|
}
|
|
1955
2237
|
#resolveRunMode(mode, member) {
|
|
1956
2238
|
if (member.flags & 1) {
|
|
@@ -2018,8 +2300,11 @@ class TestTreeWorker {
|
|
|
2018
2300
|
]);
|
|
2019
2301
|
return;
|
|
2020
2302
|
}
|
|
2021
|
-
const
|
|
2022
|
-
if (
|
|
2303
|
+
const matchResult = this.#expect.match(assertion, expectResult);
|
|
2304
|
+
if (matchResult == null) {
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
if (assertion.isNot ? !matchResult.isMatch : matchResult.isMatch) {
|
|
2023
2308
|
if (runMode & 1) {
|
|
2024
2309
|
const text = ["The assertion was supposed to fail, but it passed.", "Consider removing the '.fail' flag."];
|
|
2025
2310
|
const origin = {
|
|
@@ -2029,10 +2314,7 @@ class TestTreeWorker {
|
|
|
2029
2314
|
};
|
|
2030
2315
|
EventEmitter.dispatch([
|
|
2031
2316
|
"expect:error",
|
|
2032
|
-
{
|
|
2033
|
-
diagnostics: [Diagnostic.error(text, origin)],
|
|
2034
|
-
result: expectResult,
|
|
2035
|
-
},
|
|
2317
|
+
{ diagnostics: [Diagnostic.error(text, origin)], result: expectResult },
|
|
2036
2318
|
]);
|
|
2037
2319
|
}
|
|
2038
2320
|
else {
|
|
@@ -2044,13 +2326,20 @@ class TestTreeWorker {
|
|
|
2044
2326
|
EventEmitter.dispatch(["expect:pass", { result: expectResult }]);
|
|
2045
2327
|
}
|
|
2046
2328
|
else {
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
]
|
|
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 }]);
|
|
2054
2343
|
}
|
|
2055
2344
|
}
|
|
2056
2345
|
}
|
|
@@ -2154,8 +2443,7 @@ class TestFileRunner {
|
|
|
2154
2443
|
if (!sourceFile) {
|
|
2155
2444
|
return;
|
|
2156
2445
|
}
|
|
2157
|
-
const
|
|
2158
|
-
const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics, typeChecker);
|
|
2446
|
+
const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics);
|
|
2159
2447
|
if (testTree.diagnostics.length > 0) {
|
|
2160
2448
|
EventEmitter.dispatch([
|
|
2161
2449
|
"file:error",
|
|
@@ -2166,7 +2454,14 @@ class TestFileRunner {
|
|
|
2166
2454
|
]);
|
|
2167
2455
|
return;
|
|
2168
2456
|
}
|
|
2169
|
-
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, {
|
|
2170
2465
|
fileResult,
|
|
2171
2466
|
hasOnly: testTree.hasOnly,
|
|
2172
2467
|
position,
|
|
@@ -3066,11 +3361,13 @@ class ManifestWorker {
|
|
|
3066
3361
|
#cachePath;
|
|
3067
3362
|
#manifestFileName = "store-manifest.json";
|
|
3068
3363
|
#manifestFilePath;
|
|
3364
|
+
#onDiagnostic;
|
|
3069
3365
|
#prune;
|
|
3070
3366
|
#registryUrl = new URL("https://registry.npmjs.org");
|
|
3071
3367
|
#timeout = Environment.timeout * 1000;
|
|
3072
|
-
constructor(cachePath, prune) {
|
|
3368
|
+
constructor(cachePath, onDiagnostic, prune) {
|
|
3073
3369
|
this.#cachePath = cachePath;
|
|
3370
|
+
this.#onDiagnostic = onDiagnostic;
|
|
3074
3371
|
this.#manifestFilePath = path.join(cachePath, this.#manifestFileName);
|
|
3075
3372
|
this.#prune = prune;
|
|
3076
3373
|
}
|
|
@@ -3158,9 +3455,6 @@ class ManifestWorker {
|
|
|
3158
3455
|
}
|
|
3159
3456
|
return manifest;
|
|
3160
3457
|
}
|
|
3161
|
-
#onDiagnostic(diagnostic) {
|
|
3162
|
-
EventEmitter.dispatch(["store:error", { diagnostics: [diagnostic] }]);
|
|
3163
|
-
}
|
|
3164
3458
|
async open(signal) {
|
|
3165
3459
|
let manifest;
|
|
3166
3460
|
if (!existsSync(this.#manifestFilePath)) {
|
|
@@ -3224,7 +3518,7 @@ class StoreService {
|
|
|
3224
3518
|
constructor() {
|
|
3225
3519
|
this.#cachePath = Environment.storePath;
|
|
3226
3520
|
this.#compilerModuleWorker = new CompilerModuleWorker(this.#cachePath, this.#onDiagnostic);
|
|
3227
|
-
this.#manifestWorker = new ManifestWorker(this.#cachePath,
|
|
3521
|
+
this.#manifestWorker = new ManifestWorker(this.#cachePath, this.#onDiagnostic, this.prune);
|
|
3228
3522
|
}
|
|
3229
3523
|
get supportedTags() {
|
|
3230
3524
|
if (!this.#manifest) {
|
|
@@ -3272,9 +3566,9 @@ class StoreService {
|
|
|
3272
3566
|
}
|
|
3273
3567
|
this.#manifest = await this.#manifestWorker.open(signal);
|
|
3274
3568
|
}
|
|
3275
|
-
async
|
|
3569
|
+
prune = async () => {
|
|
3276
3570
|
await fs.rm(this.#cachePath, { force: true, recursive: true });
|
|
3277
|
-
}
|
|
3571
|
+
};
|
|
3278
3572
|
resolveTag(tag) {
|
|
3279
3573
|
if (!this.#manifest) {
|
|
3280
3574
|
this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
|
|
@@ -3327,154 +3621,6 @@ class StoreService {
|
|
|
3327
3621
|
}
|
|
3328
3622
|
}
|
|
3329
3623
|
|
|
3330
|
-
class JsonText {
|
|
3331
|
-
props;
|
|
3332
|
-
constructor(props) {
|
|
3333
|
-
this.props = props;
|
|
3334
|
-
}
|
|
3335
|
-
render() {
|
|
3336
|
-
return Scribbler.createElement(Line, null, JSON.stringify(this.#sortObject(this.props.input), null, 2));
|
|
3337
|
-
}
|
|
3338
|
-
#sortObject(target) {
|
|
3339
|
-
if (Array.isArray(target)) {
|
|
3340
|
-
return target;
|
|
3341
|
-
}
|
|
3342
|
-
return Object.keys(target)
|
|
3343
|
-
.sort()
|
|
3344
|
-
.reduce((result, key) => {
|
|
3345
|
-
result[key] = target[key];
|
|
3346
|
-
return result;
|
|
3347
|
-
}, {});
|
|
3348
|
-
}
|
|
3349
|
-
}
|
|
3350
|
-
function formattedText(input) {
|
|
3351
|
-
if (typeof input === "string") {
|
|
3352
|
-
return Scribbler.createElement(Line, null, input);
|
|
3353
|
-
}
|
|
3354
|
-
return Scribbler.createElement(JsonText, { input: input });
|
|
3355
|
-
}
|
|
3356
|
-
|
|
3357
|
-
const usageExamples = [
|
|
3358
|
-
["tstyche", "Run all tests."],
|
|
3359
|
-
["tstyche path/to/first.test.ts second", "Only run the test files with matching path."],
|
|
3360
|
-
["tstyche --target 4.7,4.8,latest", "Test on all specified versions of TypeScript."],
|
|
3361
|
-
];
|
|
3362
|
-
class HintText {
|
|
3363
|
-
props;
|
|
3364
|
-
constructor(props) {
|
|
3365
|
-
this.props = props;
|
|
3366
|
-
}
|
|
3367
|
-
render() {
|
|
3368
|
-
return (Scribbler.createElement(Text, { indent: 1, color: "90" }, this.props.children));
|
|
3369
|
-
}
|
|
3370
|
-
}
|
|
3371
|
-
class HelpHeaderText {
|
|
3372
|
-
props;
|
|
3373
|
-
constructor(props) {
|
|
3374
|
-
this.props = props;
|
|
3375
|
-
}
|
|
3376
|
-
render() {
|
|
3377
|
-
const hint = (Scribbler.createElement(HintText, null,
|
|
3378
|
-
Scribbler.createElement(Text, null, this.props.tstycheVersion)));
|
|
3379
|
-
return (Scribbler.createElement(Line, null,
|
|
3380
|
-
Scribbler.createElement(Text, null, "The TSTyche Type Test Runner"),
|
|
3381
|
-
hint));
|
|
3382
|
-
}
|
|
3383
|
-
}
|
|
3384
|
-
class CommandText {
|
|
3385
|
-
props;
|
|
3386
|
-
constructor(props) {
|
|
3387
|
-
this.props = props;
|
|
3388
|
-
}
|
|
3389
|
-
render() {
|
|
3390
|
-
let hint = undefined;
|
|
3391
|
-
if (this.props.hint != null) {
|
|
3392
|
-
hint = Scribbler.createElement(HintText, null, this.props.hint);
|
|
3393
|
-
}
|
|
3394
|
-
return (Scribbler.createElement(Line, { indent: 1 },
|
|
3395
|
-
Scribbler.createElement(Text, { color: "34" }, this.props.text),
|
|
3396
|
-
hint));
|
|
3397
|
-
}
|
|
3398
|
-
}
|
|
3399
|
-
class OptionDescriptionText {
|
|
3400
|
-
props;
|
|
3401
|
-
constructor(props) {
|
|
3402
|
-
this.props = props;
|
|
3403
|
-
}
|
|
3404
|
-
render() {
|
|
3405
|
-
return Scribbler.createElement(Line, { indent: 1 }, this.props.text);
|
|
3406
|
-
}
|
|
3407
|
-
}
|
|
3408
|
-
class CliUsageText {
|
|
3409
|
-
render() {
|
|
3410
|
-
const usageText = usageExamples.map(([commandText, descriptionText]) => (Scribbler.createElement(Text, null,
|
|
3411
|
-
Scribbler.createElement(CommandText, { text: commandText }),
|
|
3412
|
-
Scribbler.createElement(OptionDescriptionText, { text: descriptionText }),
|
|
3413
|
-
Scribbler.createElement(Line, null))));
|
|
3414
|
-
return Scribbler.createElement(Text, null, usageText);
|
|
3415
|
-
}
|
|
3416
|
-
}
|
|
3417
|
-
class OptionNameText {
|
|
3418
|
-
props;
|
|
3419
|
-
constructor(props) {
|
|
3420
|
-
this.props = props;
|
|
3421
|
-
}
|
|
3422
|
-
render() {
|
|
3423
|
-
return Scribbler.createElement(Text, null,
|
|
3424
|
-
"--",
|
|
3425
|
-
this.props.text);
|
|
3426
|
-
}
|
|
3427
|
-
}
|
|
3428
|
-
class OptionHintText {
|
|
3429
|
-
props;
|
|
3430
|
-
constructor(props) {
|
|
3431
|
-
this.props = props;
|
|
3432
|
-
}
|
|
3433
|
-
render() {
|
|
3434
|
-
if (this.props.definition.brand === "list") {
|
|
3435
|
-
return (Scribbler.createElement(Text, null,
|
|
3436
|
-
this.props.definition.brand,
|
|
3437
|
-
" of ",
|
|
3438
|
-
this.props.definition.items.brand,
|
|
3439
|
-
"s"));
|
|
3440
|
-
}
|
|
3441
|
-
return Scribbler.createElement(Text, null, this.props.definition.brand);
|
|
3442
|
-
}
|
|
3443
|
-
}
|
|
3444
|
-
class CliOptionsText {
|
|
3445
|
-
props;
|
|
3446
|
-
constructor(props) {
|
|
3447
|
-
this.props = props;
|
|
3448
|
-
}
|
|
3449
|
-
render() {
|
|
3450
|
-
const definitions = Array.from(this.props.optionDefinitions.values());
|
|
3451
|
-
const optionsText = definitions.map((definition) => (Scribbler.createElement(Text, null,
|
|
3452
|
-
Scribbler.createElement(CommandText, { text: Scribbler.createElement(OptionNameText, { text: definition.name }), hint: Scribbler.createElement(OptionHintText, { definition: definition }) }),
|
|
3453
|
-
Scribbler.createElement(OptionDescriptionText, { text: definition.description }),
|
|
3454
|
-
Scribbler.createElement(Line, null))));
|
|
3455
|
-
return (Scribbler.createElement(Text, null,
|
|
3456
|
-
Scribbler.createElement(Line, null, "CLI Options"),
|
|
3457
|
-
Scribbler.createElement(Line, null),
|
|
3458
|
-
optionsText));
|
|
3459
|
-
}
|
|
3460
|
-
}
|
|
3461
|
-
class HelpFooterText {
|
|
3462
|
-
render() {
|
|
3463
|
-
return Scribbler.createElement(Line, null, "To learn more, visit https://tstyche.org");
|
|
3464
|
-
}
|
|
3465
|
-
}
|
|
3466
|
-
function helpText(optionDefinitions, tstycheVersion) {
|
|
3467
|
-
return (Scribbler.createElement(Text, null,
|
|
3468
|
-
Scribbler.createElement(HelpHeaderText, { tstycheVersion: tstycheVersion }),
|
|
3469
|
-
Scribbler.createElement(Line, null),
|
|
3470
|
-
Scribbler.createElement(CliUsageText, null),
|
|
3471
|
-
Scribbler.createElement(Line, null),
|
|
3472
|
-
Scribbler.createElement(CliOptionsText, { optionDefinitions: optionDefinitions }),
|
|
3473
|
-
Scribbler.createElement(Line, null),
|
|
3474
|
-
Scribbler.createElement(HelpFooterText, null),
|
|
3475
|
-
Scribbler.createElement(Line, null)));
|
|
3476
|
-
}
|
|
3477
|
-
|
|
3478
3624
|
class Cli {
|
|
3479
3625
|
#abortController = new AbortController();
|
|
3480
3626
|
#logger;
|
|
@@ -3573,4 +3719,4 @@ class Cli {
|
|
|
3573
3719
|
}
|
|
3574
3720
|
}
|
|
3575
3721
|
|
|
3576
|
-
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 };
|