tstyche 4.0.0-beta.1 → 4.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -5
- package/build/index.d.cts +4 -0
- package/build/index.d.ts +4 -0
- package/build/tstyche.d.ts +3 -1
- package/build/tstyche.js +192 -106
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -18,14 +18,18 @@ If you are used to test JavaScript, a simple type test file should look familiar
|
|
|
18
18
|
```ts
|
|
19
19
|
import { expect, test } from "tstyche";
|
|
20
20
|
|
|
21
|
-
function
|
|
22
|
-
return
|
|
21
|
+
function pickLonger<T extends { length: number }>(a: T, b: T) {
|
|
22
|
+
return a.length >= b.length ? a : b;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
test("
|
|
26
|
-
expect(
|
|
25
|
+
test("pickLonger()", () => {
|
|
26
|
+
expect(pickLonger([1, 2], [1, 2, 3])).type.toBe<Array<number>>();
|
|
27
|
+
expect(pickLonger("two", "three")).type.toBe<"two" | "three">();
|
|
27
28
|
|
|
28
|
-
expect(
|
|
29
|
+
expect(pickLonger).type.not.toBeCallableWith(1, 2);
|
|
30
|
+
|
|
31
|
+
expect(pickLonger).type.not.toBeCallableWith("zero", [123]);
|
|
32
|
+
expect(pickLonger<string | Array<number>>).type.toBeCallableWith("zero", [123]);
|
|
29
33
|
});
|
|
30
34
|
```
|
|
31
35
|
|
|
@@ -59,6 +63,7 @@ Here is the list of all matchers:
|
|
|
59
63
|
- `.toBe()`, `.toBeAssignableTo()`, `.toBeAssignableWith()` compare types or types of expression,
|
|
60
64
|
- `.toAcceptProps()` checks the type of JSX component props,
|
|
61
65
|
- `.toBeApplicable` ensures that the decorator function can be applied,
|
|
66
|
+
- `.toBeCallableWith()` checks whether a function can be called with the given arguments,
|
|
62
67
|
- `.toHaveProperty()` looks up keys on an object type,
|
|
63
68
|
- `.toRaiseError()` captures the message or code of a type error.
|
|
64
69
|
|
package/build/index.d.cts
CHANGED
|
@@ -127,6 +127,10 @@ interface Matchers {
|
|
|
127
127
|
*/
|
|
128
128
|
(target: unknown): void;
|
|
129
129
|
};
|
|
130
|
+
/**
|
|
131
|
+
* Checks if the source type can be called with the target arguments.
|
|
132
|
+
*/
|
|
133
|
+
toBeCallableWith: (...target: Array<unknown>) => void;
|
|
130
134
|
/**
|
|
131
135
|
* Checks if a property key exists on the source type.
|
|
132
136
|
*/
|
package/build/index.d.ts
CHANGED
|
@@ -127,6 +127,10 @@ interface Matchers {
|
|
|
127
127
|
*/
|
|
128
128
|
(target: unknown): void;
|
|
129
129
|
};
|
|
130
|
+
/**
|
|
131
|
+
* Checks if the source type can be called with the target arguments.
|
|
132
|
+
*/
|
|
133
|
+
toBeCallableWith: (...target: Array<unknown>) => void;
|
|
130
134
|
/**
|
|
131
135
|
* Checks if a property key exists on the source type.
|
|
132
136
|
*/
|
package/build/tstyche.d.ts
CHANGED
|
@@ -45,6 +45,7 @@ declare class DiagnosticOrigin {
|
|
|
45
45
|
constructor(start: number, end: number, sourceFile: SourceFile | ts.SourceFile, assertion?: AssertionNode);
|
|
46
46
|
static fromAssertion(assertion: AssertionNode): DiagnosticOrigin;
|
|
47
47
|
static fromNode(node: ts.Node, assertion?: AssertionNode): DiagnosticOrigin;
|
|
48
|
+
static fromNodes(nodes: ts.NodeArray<ts.Node>, assertion?: AssertionNode): DiagnosticOrigin;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
declare class Diagnostic {
|
|
@@ -324,7 +325,7 @@ declare const defaultOptions: Required<ConfigFileOptions>;
|
|
|
324
325
|
|
|
325
326
|
declare class ProjectService {
|
|
326
327
|
#private;
|
|
327
|
-
constructor(
|
|
328
|
+
constructor(compiler: typeof ts, resolvedConfig: ResolvedConfig);
|
|
328
329
|
closeFile(filePath: string): void;
|
|
329
330
|
getDefaultProject(filePath: string): ts.server.Project | undefined;
|
|
330
331
|
getLanguageService(filePath: string): ts.LanguageService | undefined;
|
|
@@ -602,6 +603,7 @@ declare class ExpectService {
|
|
|
602
603
|
private toBeApplicable;
|
|
603
604
|
private toBeAssignableTo;
|
|
604
605
|
private toBeAssignableWith;
|
|
606
|
+
private toBeCallableWith;
|
|
605
607
|
private toHaveProperty;
|
|
606
608
|
private toRaiseError;
|
|
607
609
|
constructor(compiler: typeof ts, typeChecker: TypeChecker, resolvedConfig: ResolvedConfig);
|
package/build/tstyche.js
CHANGED
|
@@ -99,6 +99,9 @@ class DiagnosticOrigin {
|
|
|
99
99
|
static fromNode(node, assertion) {
|
|
100
100
|
return new DiagnosticOrigin(node.getStart(), node.getEnd(), node.getSourceFile(), assertion);
|
|
101
101
|
}
|
|
102
|
+
static fromNodes(nodes, assertion) {
|
|
103
|
+
return new DiagnosticOrigin(nodes.pos, nodes.end, nodes[0].getSourceFile(), assertion);
|
|
104
|
+
}
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
class Diagnostic {
|
|
@@ -2010,11 +2013,12 @@ class AbilityLayer {
|
|
|
2010
2013
|
return " ".repeat(range.end - range.start);
|
|
2011
2014
|
}
|
|
2012
2015
|
const text = [];
|
|
2013
|
-
for (let
|
|
2014
|
-
|
|
2016
|
+
for (let index = range.start; index < range.end; index++) {
|
|
2017
|
+
const character = this.#text.charAt(index);
|
|
2018
|
+
switch (character) {
|
|
2015
2019
|
case "\n":
|
|
2016
2020
|
case "\r":
|
|
2017
|
-
text.push(
|
|
2021
|
+
text.push(character);
|
|
2018
2022
|
break;
|
|
2019
2023
|
default:
|
|
2020
2024
|
text.push(" ");
|
|
@@ -2033,19 +2037,18 @@ class AbilityLayer {
|
|
|
2033
2037
|
if (this.#nodes.length > 0) {
|
|
2034
2038
|
this.#projectService.openFile(this.#filePath, this.#text, this.#resolvedConfig.rootPath);
|
|
2035
2039
|
const languageService = this.#projectService.getLanguageService(this.#filePath);
|
|
2036
|
-
const diagnostics = new Set(languageService?.getSemanticDiagnostics(this.#filePath)
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
}
|
|
2046
|
-
node.abilityDiagnostics.add(diagnostic);
|
|
2047
|
-
diagnostics.delete(diagnostic);
|
|
2040
|
+
const diagnostics = new Set(languageService?.getSemanticDiagnostics(this.#filePath));
|
|
2041
|
+
const nodes = this.#nodes.reverse();
|
|
2042
|
+
for (const diagnostic of diagnostics) {
|
|
2043
|
+
for (const node of nodes) {
|
|
2044
|
+
if (diagnostic.start != null &&
|
|
2045
|
+
diagnostic.start >= node.matcherNode.pos &&
|
|
2046
|
+
diagnostic.start <= node.matcherNode.end) {
|
|
2047
|
+
if (!node.abilityDiagnostics) {
|
|
2048
|
+
node.abilityDiagnostics = new Set();
|
|
2048
2049
|
}
|
|
2050
|
+
node.abilityDiagnostics.add(diagnostic);
|
|
2051
|
+
diagnostics.delete(diagnostic);
|
|
2049
2052
|
}
|
|
2050
2053
|
}
|
|
2051
2054
|
}
|
|
@@ -2067,6 +2070,17 @@ class AbilityLayer {
|
|
|
2067
2070
|
]);
|
|
2068
2071
|
break;
|
|
2069
2072
|
}
|
|
2073
|
+
case "toBeCallableWith": {
|
|
2074
|
+
const expectStart = assertionNode.node.pos;
|
|
2075
|
+
const expectExpressionEnd = assertionNode.node.expression.end;
|
|
2076
|
+
const expectEnd = assertionNode.node.end;
|
|
2077
|
+
const matcherNameEnd = assertionNode.matcherNameNode.end;
|
|
2078
|
+
this.#addRanges(assertionNode, [
|
|
2079
|
+
{ end: expectExpressionEnd + 1, start: expectStart },
|
|
2080
|
+
{ end: matcherNameEnd, start: expectEnd - 1 },
|
|
2081
|
+
]);
|
|
2082
|
+
break;
|
|
2083
|
+
}
|
|
2070
2084
|
}
|
|
2071
2085
|
}
|
|
2072
2086
|
open(sourceFile) {
|
|
@@ -3284,9 +3298,9 @@ class ProjectService {
|
|
|
3284
3298
|
#resolvedConfig;
|
|
3285
3299
|
#seenPrograms = new WeakSet();
|
|
3286
3300
|
#service;
|
|
3287
|
-
constructor(
|
|
3288
|
-
this.#resolvedConfig = resolvedConfig;
|
|
3301
|
+
constructor(compiler, resolvedConfig) {
|
|
3289
3302
|
this.#compiler = compiler;
|
|
3303
|
+
this.#resolvedConfig = resolvedConfig;
|
|
3290
3304
|
const noop = () => undefined;
|
|
3291
3305
|
const noopLogger = {
|
|
3292
3306
|
close: noop,
|
|
@@ -3434,17 +3448,29 @@ class ExpectDiagnosticText {
|
|
|
3434
3448
|
static argumentMustBeProvided(argumentNameText) {
|
|
3435
3449
|
return `An argument for '${argumentNameText}' must be provided.`;
|
|
3436
3450
|
}
|
|
3437
|
-
static
|
|
3451
|
+
static canBeCalled(isTypeNode, targetText) {
|
|
3452
|
+
return `${isTypeNode ? "Type" : "Expression"} can be called ${targetText}.`;
|
|
3453
|
+
}
|
|
3454
|
+
static cannotBeCalled(isTypeNode, targetText) {
|
|
3455
|
+
return `${isTypeNode ? "Type" : "Expression"} cannot be called ${targetText}.`;
|
|
3456
|
+
}
|
|
3457
|
+
static acceptsProps(isTypeNode) {
|
|
3438
3458
|
return `${isTypeNode ? "Component type" : "Component"} accepts props of the given type.`;
|
|
3439
3459
|
}
|
|
3440
|
-
static
|
|
3460
|
+
static doesNotAcceptProps(isTypeNode) {
|
|
3441
3461
|
return `${isTypeNode ? "Component type" : "Component"} does not accept props of the given type.`;
|
|
3442
3462
|
}
|
|
3443
|
-
static
|
|
3463
|
+
static canBeApplied(targetText) {
|
|
3444
3464
|
return `The decorator function can be applied${targetText}.`;
|
|
3445
3465
|
}
|
|
3446
|
-
static
|
|
3447
|
-
return `The decorator function
|
|
3466
|
+
static cannotBeApplied(targetText) {
|
|
3467
|
+
return `The decorator function cannot be applied${targetText}.`;
|
|
3468
|
+
}
|
|
3469
|
+
static doesNotHaveProperty(typeText, propertyNameText) {
|
|
3470
|
+
return `Type '${typeText}' does not have property '${propertyNameText}'.`;
|
|
3471
|
+
}
|
|
3472
|
+
static hasProperty(typeText, propertyNameText) {
|
|
3473
|
+
return `Type '${typeText}' has property '${propertyNameText}'.`;
|
|
3448
3474
|
}
|
|
3449
3475
|
static matcherIsNotSupported(matcherNameText) {
|
|
3450
3476
|
return `The '.${matcherNameText}()' matcher is not supported.`;
|
|
@@ -3461,50 +3487,44 @@ class ExpectDiagnosticText {
|
|
|
3461
3487
|
static typeArgumentMustBe(argumentNameText, expectedText) {
|
|
3462
3488
|
return `A type argument for '${argumentNameText}' must be ${expectedText}.`;
|
|
3463
3489
|
}
|
|
3464
|
-
static
|
|
3465
|
-
|
|
3490
|
+
static raisedError(isTypeNode, count, targetCount) {
|
|
3491
|
+
let countText = "a";
|
|
3492
|
+
if (count > 1 || targetCount > 1) {
|
|
3493
|
+
countText = count > targetCount ? `${count}` : `only ${count}`;
|
|
3494
|
+
}
|
|
3495
|
+
return `${isTypeNode ? "Type" : "Expression type"} raised ${countText} type error${count === 1 ? "" : "s"}.`;
|
|
3466
3496
|
}
|
|
3467
|
-
static
|
|
3468
|
-
return `${isTypeNode ? "Type" : "Expression type"} did not raise a
|
|
3497
|
+
static didNotRaiseError(isTypeNode) {
|
|
3498
|
+
return `${isTypeNode ? "Type" : "Expression type"} did not raise a type error.`;
|
|
3469
3499
|
}
|
|
3470
|
-
static
|
|
3471
|
-
return
|
|
3500
|
+
static raisedMatchingError(isTypeNode) {
|
|
3501
|
+
return `${isTypeNode ? "Type" : "Expression type"} raised a matching type error.`;
|
|
3472
3502
|
}
|
|
3473
|
-
static
|
|
3474
|
-
return
|
|
3503
|
+
static didNotRaiseMatchingError(isTypeNode) {
|
|
3504
|
+
return `${isTypeNode ? "Type" : "Expression type"} did not raise a matching type error.`;
|
|
3475
3505
|
}
|
|
3476
|
-
static
|
|
3506
|
+
static isAssignableTo(sourceTypeText, targetTypeText) {
|
|
3477
3507
|
return `Type '${sourceTypeText}' is assignable to type '${targetTypeText}'.`;
|
|
3478
3508
|
}
|
|
3479
|
-
static
|
|
3480
|
-
return `Type '${sourceTypeText}' is assignable with type '${targetTypeText}'.`;
|
|
3481
|
-
}
|
|
3482
|
-
static typeIsIdenticalTo(sourceTypeText, targetTypeText) {
|
|
3483
|
-
return `Type '${sourceTypeText}' is identical to type '${targetTypeText}'.`;
|
|
3484
|
-
}
|
|
3485
|
-
static typeIsNotAssignableTo(sourceTypeText, targetTypeText) {
|
|
3509
|
+
static isNotAssignableTo(sourceTypeText, targetTypeText) {
|
|
3486
3510
|
return `Type '${sourceTypeText}' is not assignable to type '${targetTypeText}'.`;
|
|
3487
3511
|
}
|
|
3488
|
-
static
|
|
3512
|
+
static isAssignableWith(sourceTypeText, targetTypeText) {
|
|
3513
|
+
return `Type '${sourceTypeText}' is assignable with type '${targetTypeText}'.`;
|
|
3514
|
+
}
|
|
3515
|
+
static isNotAssignableWith(sourceTypeText, targetTypeText) {
|
|
3489
3516
|
return `Type '${sourceTypeText}' is not assignable with type '${targetTypeText}'.`;
|
|
3490
3517
|
}
|
|
3491
|
-
static
|
|
3492
|
-
return `Type '${sourceTypeText}' is
|
|
3518
|
+
static isIdenticalTo(sourceTypeText, targetTypeText) {
|
|
3519
|
+
return `Type '${sourceTypeText}' is identical to type '${targetTypeText}'.`;
|
|
3493
3520
|
}
|
|
3494
|
-
static
|
|
3521
|
+
static isNotIdenticalTo(sourceTypeText, targetTypeText) {
|
|
3495
3522
|
return `Type '${sourceTypeText}' is not identical to type '${targetTypeText}'.`;
|
|
3496
3523
|
}
|
|
3497
|
-
static
|
|
3498
|
-
|
|
3499
|
-
if (count > 1 || targetCount > 1) {
|
|
3500
|
-
countText = count > targetCount ? `${count}` : `only ${count}`;
|
|
3501
|
-
}
|
|
3502
|
-
return `${isTypeNode ? "Type" : "Expression type"} raised ${countText} type error${count === 1 ? "" : "s"}.`;
|
|
3503
|
-
}
|
|
3504
|
-
static typeRaisedMatchingError(isTypeNode) {
|
|
3505
|
-
return `${isTypeNode ? "Type" : "Expression type"} raised a matching type error.`;
|
|
3524
|
+
static isNotCompatibleWith(sourceTypeText, targetTypeText) {
|
|
3525
|
+
return `Type '${sourceTypeText}' is not compatible with type '${targetTypeText}'.`;
|
|
3506
3526
|
}
|
|
3507
|
-
static
|
|
3527
|
+
static requiresProperty(typeText, propertyNameText) {
|
|
3508
3528
|
return `Type '${typeText}' requires property '${propertyNameText}'.`;
|
|
3509
3529
|
}
|
|
3510
3530
|
static typesOfPropertyAreNotCompatible(propertyNameText) {
|
|
@@ -3523,7 +3543,6 @@ class MatchWorker {
|
|
|
3523
3543
|
assertion;
|
|
3524
3544
|
#compiler;
|
|
3525
3545
|
#signatureCache = new Map();
|
|
3526
|
-
#typeCache = new Map();
|
|
3527
3546
|
#typeChecker;
|
|
3528
3547
|
constructor(compiler, typeChecker, assertion) {
|
|
3529
3548
|
this.#compiler = compiler;
|
|
@@ -3575,7 +3594,7 @@ class MatchWorker {
|
|
|
3575
3594
|
if (!parameter) {
|
|
3576
3595
|
return;
|
|
3577
3596
|
}
|
|
3578
|
-
return this
|
|
3597
|
+
return this.getType(parameter);
|
|
3579
3598
|
}
|
|
3580
3599
|
getSignatures(node) {
|
|
3581
3600
|
let signatures = this.#signatureCache.get(node);
|
|
@@ -3593,21 +3612,7 @@ class MatchWorker {
|
|
|
3593
3612
|
return this.#typeChecker.typeToString(type);
|
|
3594
3613
|
}
|
|
3595
3614
|
getType(node) {
|
|
3596
|
-
return this.#
|
|
3597
|
-
}
|
|
3598
|
-
#getTypeOfNode(node) {
|
|
3599
|
-
let type = this.#typeCache.get(node);
|
|
3600
|
-
if (!type) {
|
|
3601
|
-
type = this.#typeChecker.getTypeAtLocation(node);
|
|
3602
|
-
}
|
|
3603
|
-
return type;
|
|
3604
|
-
}
|
|
3605
|
-
#getTypeOfTypeNode(node) {
|
|
3606
|
-
let type = this.#typeCache.get(node);
|
|
3607
|
-
if (!type) {
|
|
3608
|
-
type = this.#typeChecker.getTypeFromTypeNode(node);
|
|
3609
|
-
}
|
|
3610
|
-
return type;
|
|
3615
|
+
return this.#typeChecker.getTypeAtLocation(node);
|
|
3611
3616
|
}
|
|
3612
3617
|
isStringOrNumberLiteralType(type) {
|
|
3613
3618
|
return !!(type.flags & this.#compiler.TypeFlags.StringOrNumberLiteral);
|
|
@@ -3655,8 +3660,8 @@ class ToAcceptProps {
|
|
|
3655
3660
|
return signatures.reduce((accumulator, signature, index) => {
|
|
3656
3661
|
let diagnostic;
|
|
3657
3662
|
const introText = matchWorker.assertion.isNot
|
|
3658
|
-
? ExpectDiagnosticText.
|
|
3659
|
-
: ExpectDiagnosticText.
|
|
3663
|
+
? ExpectDiagnosticText.acceptsProps(this.#compiler.isTypeNode(sourceNode))
|
|
3664
|
+
: ExpectDiagnosticText.doesNotAcceptProps(this.#compiler.isTypeNode(sourceNode));
|
|
3660
3665
|
const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
|
|
3661
3666
|
if (signatures.length > 1) {
|
|
3662
3667
|
const signatureText = this.#typeChecker.signatureToString(signature, sourceNode);
|
|
@@ -3722,8 +3727,8 @@ class ToAcceptProps {
|
|
|
3722
3727
|
const sourceProperty = sourceType?.getProperty(targetPropertyName);
|
|
3723
3728
|
if (!sourceProperty) {
|
|
3724
3729
|
const text = [
|
|
3725
|
-
ExpectDiagnosticText.
|
|
3726
|
-
ExpectDiagnosticText.
|
|
3730
|
+
ExpectDiagnosticText.isNotCompatibleWith(sourceTypeText, targetTypeText),
|
|
3731
|
+
ExpectDiagnosticText.doesNotHaveProperty(sourceTypeText, targetPropertyName),
|
|
3727
3732
|
];
|
|
3728
3733
|
const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
|
|
3729
3734
|
diagnostics.push(diagnostic.extendWith(text, origin));
|
|
@@ -3731,8 +3736,8 @@ class ToAcceptProps {
|
|
|
3731
3736
|
}
|
|
3732
3737
|
if (this.#isOptionalProperty(targetProperty) && !this.#isOptionalProperty(sourceProperty)) {
|
|
3733
3738
|
const text = [
|
|
3734
|
-
ExpectDiagnosticText.
|
|
3735
|
-
ExpectDiagnosticText.
|
|
3739
|
+
ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
|
|
3740
|
+
ExpectDiagnosticText.requiresProperty(sourceTypeText, targetPropertyName),
|
|
3736
3741
|
];
|
|
3737
3742
|
const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
|
|
3738
3743
|
diagnostics.push(diagnostic.extendWith(text, origin));
|
|
@@ -3744,9 +3749,9 @@ class ToAcceptProps {
|
|
|
3744
3749
|
const targetPropertyTypeText = this.#typeChecker.typeToString(targetPropertyType);
|
|
3745
3750
|
const sourcePropertyTypeText = this.#typeChecker.typeToString(sourcePropertyType);
|
|
3746
3751
|
const text = [
|
|
3747
|
-
ExpectDiagnosticText.
|
|
3752
|
+
ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
|
|
3748
3753
|
ExpectDiagnosticText.typesOfPropertyAreNotCompatible(targetPropertyName),
|
|
3749
|
-
ExpectDiagnosticText.
|
|
3754
|
+
ExpectDiagnosticText.isNotAssignableWith(sourcePropertyTypeText, targetPropertyTypeText),
|
|
3750
3755
|
];
|
|
3751
3756
|
const origin = matchWorker.resolveDiagnosticOrigin(targetProperty, targetNode);
|
|
3752
3757
|
diagnostics.push(diagnostic.extendWith(text, origin));
|
|
@@ -3758,15 +3763,15 @@ class ToAcceptProps {
|
|
|
3758
3763
|
const targetProperty = targetType.getProperty(sourcePropertyName);
|
|
3759
3764
|
if (!targetProperty && !this.#isOptionalProperty(sourceProperty)) {
|
|
3760
3765
|
const text = [
|
|
3761
|
-
ExpectDiagnosticText.
|
|
3762
|
-
ExpectDiagnosticText.
|
|
3766
|
+
ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText),
|
|
3767
|
+
ExpectDiagnosticText.requiresProperty(sourceTypeText, sourcePropertyName),
|
|
3763
3768
|
];
|
|
3764
3769
|
diagnostics.push(diagnostic.extendWith(text));
|
|
3765
3770
|
}
|
|
3766
3771
|
}
|
|
3767
3772
|
}
|
|
3768
3773
|
if (diagnostics.length === 0) {
|
|
3769
|
-
const text = ExpectDiagnosticText.
|
|
3774
|
+
const text = ExpectDiagnosticText.isAssignableWith(sourceTypeText, targetTypeText);
|
|
3770
3775
|
diagnostics.push(diagnostic.extendWith(text));
|
|
3771
3776
|
return { diagnostics, isMatch: true };
|
|
3772
3777
|
}
|
|
@@ -3776,8 +3781,8 @@ class ToAcceptProps {
|
|
|
3776
3781
|
let accumulator = [];
|
|
3777
3782
|
const isMatch = sourceType.types.some((sourceType) => {
|
|
3778
3783
|
const text = matchWorker.assertion.isNot
|
|
3779
|
-
? ExpectDiagnosticText.
|
|
3780
|
-
: ExpectDiagnosticText.
|
|
3784
|
+
? ExpectDiagnosticText.isAssignableWith(sourceTypeText, targetTypeText)
|
|
3785
|
+
: ExpectDiagnosticText.isNotAssignableWith(sourceTypeText, targetTypeText);
|
|
3781
3786
|
const { diagnostics, isMatch } = explain(sourceType, targetType, diagnostic.extendWith(text));
|
|
3782
3787
|
if (isMatch) {
|
|
3783
3788
|
accumulator = diagnostics;
|
|
@@ -3839,8 +3844,8 @@ class RelationMatcherBase {
|
|
|
3839
3844
|
}
|
|
3840
3845
|
|
|
3841
3846
|
class ToBe extends RelationMatcherBase {
|
|
3842
|
-
explainText = ExpectDiagnosticText.
|
|
3843
|
-
explainNotText = ExpectDiagnosticText.
|
|
3847
|
+
explainText = ExpectDiagnosticText.isIdenticalTo;
|
|
3848
|
+
explainNotText = ExpectDiagnosticText.isNotIdenticalTo;
|
|
3844
3849
|
match(matchWorker, sourceNode, targetNode) {
|
|
3845
3850
|
return {
|
|
3846
3851
|
explain: () => this.explain(matchWorker, sourceNode, targetNode),
|
|
@@ -3886,7 +3891,7 @@ class ToBeApplicable {
|
|
|
3886
3891
|
if (matchWorker.assertion.abilityDiagnostics) {
|
|
3887
3892
|
for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
|
|
3888
3893
|
const text = [
|
|
3889
|
-
ExpectDiagnosticText.
|
|
3894
|
+
ExpectDiagnosticText.cannotBeApplied(targetText),
|
|
3890
3895
|
typeof diagnostic.messageText === "string"
|
|
3891
3896
|
? diagnostic.messageText
|
|
3892
3897
|
: Diagnostic.toMessageText(diagnostic.messageText),
|
|
@@ -3897,7 +3902,7 @@ class ToBeApplicable {
|
|
|
3897
3902
|
}
|
|
3898
3903
|
else {
|
|
3899
3904
|
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
3900
|
-
diagnostics.push(Diagnostic.error(ExpectDiagnosticText.
|
|
3905
|
+
diagnostics.push(Diagnostic.error(ExpectDiagnosticText.canBeApplied(targetText), origin));
|
|
3901
3906
|
}
|
|
3902
3907
|
return diagnostics;
|
|
3903
3908
|
}
|
|
@@ -3920,8 +3925,8 @@ class ToBeApplicable {
|
|
|
3920
3925
|
}
|
|
3921
3926
|
|
|
3922
3927
|
class ToBeAssignableTo extends RelationMatcherBase {
|
|
3923
|
-
explainText = ExpectDiagnosticText.
|
|
3924
|
-
explainNotText = ExpectDiagnosticText.
|
|
3928
|
+
explainText = ExpectDiagnosticText.isAssignableTo;
|
|
3929
|
+
explainNotText = ExpectDiagnosticText.isNotAssignableTo;
|
|
3925
3930
|
match(matchWorker, sourceNode, targetNode) {
|
|
3926
3931
|
return {
|
|
3927
3932
|
explain: () => this.explain(matchWorker, sourceNode, targetNode),
|
|
@@ -3931,8 +3936,8 @@ class ToBeAssignableTo extends RelationMatcherBase {
|
|
|
3931
3936
|
}
|
|
3932
3937
|
|
|
3933
3938
|
class ToBeAssignableWith extends RelationMatcherBase {
|
|
3934
|
-
explainText = ExpectDiagnosticText.
|
|
3935
|
-
explainNotText = ExpectDiagnosticText.
|
|
3939
|
+
explainText = ExpectDiagnosticText.isAssignableWith;
|
|
3940
|
+
explainNotText = ExpectDiagnosticText.isNotAssignableWith;
|
|
3936
3941
|
match(matchWorker, sourceNode, targetNode) {
|
|
3937
3942
|
return {
|
|
3938
3943
|
explain: () => this.explain(matchWorker, sourceNode, targetNode),
|
|
@@ -3941,6 +3946,84 @@ class ToBeAssignableWith extends RelationMatcherBase {
|
|
|
3941
3946
|
}
|
|
3942
3947
|
}
|
|
3943
3948
|
|
|
3949
|
+
class ToBeCallableWith {
|
|
3950
|
+
#compiler;
|
|
3951
|
+
constructor(compiler) {
|
|
3952
|
+
this.#compiler = compiler;
|
|
3953
|
+
}
|
|
3954
|
+
isDiagnosticWithLocation(diagnostic) {
|
|
3955
|
+
return diagnostic.start != null;
|
|
3956
|
+
}
|
|
3957
|
+
#resolveTargetText(nodes) {
|
|
3958
|
+
if (nodes.length === 0) {
|
|
3959
|
+
return "without arguments";
|
|
3960
|
+
}
|
|
3961
|
+
if (nodes.length === 1 && nodes[0]?.kind === this.#compiler.SyntaxKind.SpreadElement) {
|
|
3962
|
+
return "with the given arguments";
|
|
3963
|
+
}
|
|
3964
|
+
return `with the given argument${nodes.length === 1 ? "" : "s"}`;
|
|
3965
|
+
}
|
|
3966
|
+
#explain(matchWorker, sourceNode, targetNodes) {
|
|
3967
|
+
const isTypeNode = this.#compiler.isTypeNode(sourceNode);
|
|
3968
|
+
const targetText = this.#resolveTargetText(targetNodes);
|
|
3969
|
+
const diagnostics = [];
|
|
3970
|
+
if (matchWorker.assertion.abilityDiagnostics) {
|
|
3971
|
+
for (const diagnostic of matchWorker.assertion.abilityDiagnostics) {
|
|
3972
|
+
const text = [
|
|
3973
|
+
ExpectDiagnosticText.cannotBeCalled(isTypeNode, targetText),
|
|
3974
|
+
typeof diagnostic.messageText === "string"
|
|
3975
|
+
? diagnostic.messageText
|
|
3976
|
+
: Diagnostic.toMessageText(diagnostic.messageText),
|
|
3977
|
+
];
|
|
3978
|
+
let origin;
|
|
3979
|
+
if (this.isDiagnosticWithLocation(diagnostic) &&
|
|
3980
|
+
diagnostic.start >= targetNodes.pos &&
|
|
3981
|
+
diagnostic.start <= targetNodes.end) {
|
|
3982
|
+
origin = new DiagnosticOrigin(diagnostic.start, diagnostic.start + diagnostic.length, sourceNode.getSourceFile());
|
|
3983
|
+
}
|
|
3984
|
+
else {
|
|
3985
|
+
origin =
|
|
3986
|
+
targetNodes.length > 0
|
|
3987
|
+
? DiagnosticOrigin.fromNodes(targetNodes)
|
|
3988
|
+
: DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
3989
|
+
}
|
|
3990
|
+
let related;
|
|
3991
|
+
if (diagnostic.relatedInformation != null) {
|
|
3992
|
+
related = Diagnostic.fromDiagnostics(diagnostic.relatedInformation);
|
|
3993
|
+
}
|
|
3994
|
+
diagnostics.push(Diagnostic.error(text.flat(), origin).add({ related }));
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
else {
|
|
3998
|
+
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
3999
|
+
diagnostics.push(Diagnostic.error(ExpectDiagnosticText.canBeCalled(isTypeNode, targetText), origin));
|
|
4000
|
+
}
|
|
4001
|
+
return diagnostics;
|
|
4002
|
+
}
|
|
4003
|
+
match(matchWorker, sourceNode, targetNodes, onDiagnostics) {
|
|
4004
|
+
let mustBeText;
|
|
4005
|
+
if (!(sourceNode.kind === this.#compiler.SyntaxKind.Identifier ||
|
|
4006
|
+
sourceNode.kind === this.#compiler.SyntaxKind.ExpressionWithTypeArguments)) {
|
|
4007
|
+
mustBeText = "an identifier or instantiation expression";
|
|
4008
|
+
}
|
|
4009
|
+
if (matchWorker.getType(sourceNode).getCallSignatures().length === 0) {
|
|
4010
|
+
mustBeText = "of a function type";
|
|
4011
|
+
}
|
|
4012
|
+
if (mustBeText != null) {
|
|
4013
|
+
const text = this.#compiler.isTypeNode(sourceNode)
|
|
4014
|
+
? ExpectDiagnosticText.typeArgumentMustBe("Source", mustBeText)
|
|
4015
|
+
: ExpectDiagnosticText.argumentMustBe("source", mustBeText);
|
|
4016
|
+
const origin = DiagnosticOrigin.fromNode(sourceNode);
|
|
4017
|
+
onDiagnostics([Diagnostic.error(text, origin)]);
|
|
4018
|
+
return;
|
|
4019
|
+
}
|
|
4020
|
+
return {
|
|
4021
|
+
explain: () => this.#explain(matchWorker, sourceNode, targetNodes),
|
|
4022
|
+
isMatch: !matchWorker.assertion.abilityDiagnostics,
|
|
4023
|
+
};
|
|
4024
|
+
}
|
|
4025
|
+
}
|
|
4026
|
+
|
|
3944
4027
|
class ToHaveProperty {
|
|
3945
4028
|
#compiler;
|
|
3946
4029
|
constructor(compiler) {
|
|
@@ -3958,8 +4041,8 @@ class ToHaveProperty {
|
|
|
3958
4041
|
}
|
|
3959
4042
|
const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
|
|
3960
4043
|
return matchWorker.assertion.isNot
|
|
3961
|
-
? [Diagnostic.error(ExpectDiagnosticText.
|
|
3962
|
-
: [Diagnostic.error(ExpectDiagnosticText.
|
|
4044
|
+
? [Diagnostic.error(ExpectDiagnosticText.hasProperty(sourceTypeText, propertyNameText), origin)]
|
|
4045
|
+
: [Diagnostic.error(ExpectDiagnosticText.doesNotHaveProperty(sourceTypeText, propertyNameText), origin)];
|
|
3963
4046
|
}
|
|
3964
4047
|
match(matchWorker, sourceNode, targetNode, onDiagnostics) {
|
|
3965
4048
|
const diagnostics = [];
|
|
@@ -4009,12 +4092,12 @@ class ToRaiseError {
|
|
|
4009
4092
|
const isTypeNode = this.#compiler.isTypeNode(sourceNode);
|
|
4010
4093
|
const origin = DiagnosticOrigin.fromAssertion(matchWorker.assertion);
|
|
4011
4094
|
if (matchWorker.assertion.diagnostics.size === 0) {
|
|
4012
|
-
const text = ExpectDiagnosticText.
|
|
4095
|
+
const text = ExpectDiagnosticText.didNotRaiseError(isTypeNode);
|
|
4013
4096
|
return [Diagnostic.error(text, origin)];
|
|
4014
4097
|
}
|
|
4015
4098
|
if (matchWorker.assertion.diagnostics.size !== targetNodes.length) {
|
|
4016
4099
|
const count = matchWorker.assertion.diagnostics.size;
|
|
4017
|
-
const text = ExpectDiagnosticText.
|
|
4100
|
+
const text = ExpectDiagnosticText.raisedError(isTypeNode, count, targetNodes.length);
|
|
4018
4101
|
const related = [
|
|
4019
4102
|
Diagnostic.error(ExpectDiagnosticText.raisedTypeError(count)),
|
|
4020
4103
|
...Diagnostic.fromDiagnostics([...matchWorker.assertion.diagnostics]),
|
|
@@ -4026,8 +4109,8 @@ class ToRaiseError {
|
|
|
4026
4109
|
const isMatch = this.#matchExpectedError(diagnostic, targetNode);
|
|
4027
4110
|
if (matchWorker.assertion.isNot ? isMatch : !isMatch) {
|
|
4028
4111
|
const text = matchWorker.assertion.isNot
|
|
4029
|
-
? ExpectDiagnosticText.
|
|
4030
|
-
: ExpectDiagnosticText.
|
|
4112
|
+
? ExpectDiagnosticText.raisedMatchingError(isTypeNode)
|
|
4113
|
+
: ExpectDiagnosticText.didNotRaiseMatchingError(isTypeNode);
|
|
4031
4114
|
const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
|
|
4032
4115
|
const related = [
|
|
4033
4116
|
Diagnostic.error(ExpectDiagnosticText.raisedTypeError()),
|
|
@@ -4092,6 +4175,7 @@ class ExpectService {
|
|
|
4092
4175
|
toBeApplicable;
|
|
4093
4176
|
toBeAssignableTo;
|
|
4094
4177
|
toBeAssignableWith;
|
|
4178
|
+
toBeCallableWith;
|
|
4095
4179
|
toHaveProperty;
|
|
4096
4180
|
toRaiseError;
|
|
4097
4181
|
constructor(compiler, typeChecker, resolvedConfig) {
|
|
@@ -4108,6 +4192,7 @@ class ExpectService {
|
|
|
4108
4192
|
this.toBeApplicable = new ToBeApplicable(compiler);
|
|
4109
4193
|
this.toBeAssignableTo = new ToBeAssignableTo();
|
|
4110
4194
|
this.toBeAssignableWith = new ToBeAssignableWith();
|
|
4195
|
+
this.toBeCallableWith = new ToBeCallableWith(compiler);
|
|
4111
4196
|
this.toHaveProperty = new ToHaveProperty(compiler);
|
|
4112
4197
|
this.toRaiseError = new ToRaiseError(compiler);
|
|
4113
4198
|
}
|
|
@@ -4134,14 +4219,15 @@ class ExpectService {
|
|
|
4134
4219
|
return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
|
|
4135
4220
|
case "toBeApplicable":
|
|
4136
4221
|
return this.toBeApplicable.match(matchWorker, assertion.source[0], onDiagnostics);
|
|
4222
|
+
case "toBeCallableWith":
|
|
4223
|
+
case "toRaiseError":
|
|
4224
|
+
return this[matcherNameText].match(matchWorker, assertion.source[0], assertion.target, onDiagnostics);
|
|
4137
4225
|
case "toHaveProperty":
|
|
4138
4226
|
if (!assertion.target?.[0]) {
|
|
4139
4227
|
this.#onTargetArgumentMustBeProvided("key", assertion, onDiagnostics);
|
|
4140
4228
|
return;
|
|
4141
4229
|
}
|
|
4142
4230
|
return this.toHaveProperty.match(matchWorker, assertion.source[0], assertion.target[0], onDiagnostics);
|
|
4143
|
-
case "toRaiseError":
|
|
4144
|
-
return this.toRaiseError.match(matchWorker, assertion.source[0], [...assertion.target], onDiagnostics);
|
|
4145
4231
|
default:
|
|
4146
4232
|
this.#onMatcherIsNotSupported(matcherNameText, assertion, onDiagnostics);
|
|
4147
4233
|
}
|
|
@@ -4203,7 +4289,7 @@ class TestTreeWalker {
|
|
|
4203
4289
|
#position;
|
|
4204
4290
|
#resolvedConfig;
|
|
4205
4291
|
#taskResult;
|
|
4206
|
-
constructor(
|
|
4292
|
+
constructor(compiler, typeChecker, resolvedConfig, options) {
|
|
4207
4293
|
this.#resolvedConfig = resolvedConfig;
|
|
4208
4294
|
this.#cancellationToken = options.cancellationToken;
|
|
4209
4295
|
this.#hasOnly = options.hasOnly || resolvedConfig.only != null || options.position != null;
|
|
@@ -4349,14 +4435,14 @@ class TestTreeWalker {
|
|
|
4349
4435
|
}
|
|
4350
4436
|
|
|
4351
4437
|
class TaskRunner {
|
|
4352
|
-
#compiler;
|
|
4353
4438
|
#collectService;
|
|
4439
|
+
#compiler;
|
|
4354
4440
|
#resolvedConfig;
|
|
4355
4441
|
#projectService;
|
|
4356
|
-
constructor(
|
|
4357
|
-
this.#resolvedConfig = resolvedConfig;
|
|
4442
|
+
constructor(compiler, resolvedConfig) {
|
|
4358
4443
|
this.#compiler = compiler;
|
|
4359
|
-
this.#
|
|
4444
|
+
this.#resolvedConfig = resolvedConfig;
|
|
4445
|
+
this.#projectService = new ProjectService(compiler, this.#resolvedConfig);
|
|
4360
4446
|
this.#collectService = new CollectService(compiler, this.#projectService, this.#resolvedConfig);
|
|
4361
4447
|
}
|
|
4362
4448
|
run(task, cancellationToken) {
|
|
@@ -4408,7 +4494,7 @@ class TaskRunner {
|
|
|
4408
4494
|
return;
|
|
4409
4495
|
}
|
|
4410
4496
|
const typeChecker = program.getTypeChecker();
|
|
4411
|
-
const testTreeWalker = new TestTreeWalker(this.#
|
|
4497
|
+
const testTreeWalker = new TestTreeWalker(this.#compiler, typeChecker, this.#resolvedConfig, {
|
|
4412
4498
|
cancellationToken,
|
|
4413
4499
|
taskResult,
|
|
4414
4500
|
hasOnly: testTree.hasOnly,
|
|
@@ -4421,7 +4507,7 @@ class TaskRunner {
|
|
|
4421
4507
|
class Runner {
|
|
4422
4508
|
#eventEmitter = new EventEmitter();
|
|
4423
4509
|
#resolvedConfig;
|
|
4424
|
-
static version = "4.0.0-beta.
|
|
4510
|
+
static version = "4.0.0-beta.2";
|
|
4425
4511
|
constructor(resolvedConfig) {
|
|
4426
4512
|
this.#resolvedConfig = resolvedConfig;
|
|
4427
4513
|
}
|
|
@@ -4479,7 +4565,7 @@ class Runner {
|
|
|
4479
4565
|
EventEmitter.dispatch(["target:start", { result: targetResult }]);
|
|
4480
4566
|
const compiler = await Store.load(target);
|
|
4481
4567
|
if (compiler) {
|
|
4482
|
-
const taskRunner = new TaskRunner(this.#resolvedConfig
|
|
4568
|
+
const taskRunner = new TaskRunner(compiler, this.#resolvedConfig);
|
|
4483
4569
|
for (const task of tasks) {
|
|
4484
4570
|
taskRunner.run(task, cancellationToken);
|
|
4485
4571
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tstyche",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.2",
|
|
4
4
|
"description": "The Essential Type Testing Tool.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
"./package.json": "./package.json",
|
|
29
29
|
"./tstyche": "./build/tstyche.js"
|
|
30
30
|
},
|
|
31
|
+
"main": "./build/index.js",
|
|
32
|
+
"types": "./build/index.d.ts",
|
|
31
33
|
"bin": "./build/bin.js",
|
|
32
34
|
"files": [
|
|
33
35
|
"build/*"
|
|
@@ -80,7 +82,7 @@
|
|
|
80
82
|
"optional": true
|
|
81
83
|
}
|
|
82
84
|
},
|
|
83
|
-
"packageManager": "yarn@4.
|
|
85
|
+
"packageManager": "yarn@4.9.0",
|
|
84
86
|
"engines": {
|
|
85
87
|
"node": ">=20.9"
|
|
86
88
|
}
|