Skip to content

Commit 6bbacb6

Browse files
committed
Improve contextual types using jsdoc tags
1 parent 43e3d60 commit 6bbacb6

File tree

7 files changed

+80
-17
lines changed

7 files changed

+80
-17
lines changed

src/compiler/checker.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12744,6 +12744,10 @@ namespace ts {
1274412744
if (declaration.type) {
1274512745
return getTypeFromTypeNode(declaration.type);
1274612746
}
12747+
const jsDocType = isInJavaScriptFile(declaration) && getTypeForDeclarationFromJSDocComment(declaration);
12748+
if (jsDocType) {
12749+
return jsDocType;
12750+
}
1274712751
if (declaration.kind === SyntaxKind.Parameter) {
1274812752
const type = getContextuallyTypedParameterType(<ParameterDeclaration>declaration);
1274912753
if (type) {
@@ -12816,8 +12820,9 @@ namespace ts {
1281612820
// If the containing function has a return type annotation, is a constructor, or is a get accessor whose
1281712821
// corresponding set accessor has a type annotation, return statements in the function are contextually typed
1281812822
if (functionDecl.type ||
12823+
(isInJavaScriptFile(functionDecl) && getJSDocReturnType(functionDecl)) ||
1281912824
functionDecl.kind === SyntaxKind.Constructor ||
12820-
functionDecl.kind === SyntaxKind.GetAccessor && getSetAccessorTypeAnnotationNode(getDeclarationOfKind<SetAccessorDeclaration>(functionDecl.symbol, SyntaxKind.SetAccessor))) {
12825+
functionDecl.kind === SyntaxKind.GetAccessor && getSetAccessorTypeAnnotationNode(getDeclarationOfKind<SetAccessorDeclaration>(functionDecl.symbol, SyntaxKind.SetAccessor), /*includeJSDocType*/ true)) {
1282112826
return getReturnTypeOfSignature(getSignatureFromDeclaration(functionDecl));
1282212827
}
1282312828

@@ -16437,11 +16442,10 @@ namespace ts {
1643716442
}
1643816443

1643916444
function getReturnTypeFromJSDocComment(func: SignatureDeclaration | FunctionDeclaration): Type {
16440-
const returnTag = getJSDocReturnTag(func);
16441-
if (returnTag && returnTag.typeExpression) {
16442-
return getTypeFromTypeNode(returnTag.typeExpression.type);
16445+
const jsdocType = getJSDocReturnType(func);
16446+
if (jsdocType) {
16447+
return getTypeFromTypeNode(jsdocType);
1644316448
}
16444-
1644516449
return undefined;
1644616450
}
1644716451

src/compiler/utilities.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,6 +1570,11 @@ namespace ts {
15701570
return getFirstJSDocTag(node, SyntaxKind.JSDocReturnTag) as JSDocReturnTag;
15711571
}
15721572

1573+
export function getJSDocReturnType(node: Node): JSDocType {
1574+
const returnTag = getJSDocReturnTag(node);
1575+
return returnTag && returnTag.typeExpression && returnTag.typeExpression.type;
1576+
}
1577+
15731578
export function getJSDocTemplateTag(node: Node): JSDocTemplateTag {
15741579
return getFirstJSDocTag(node, SyntaxKind.JSDocTemplateTag) as JSDocTemplateTag;
15751580
}
@@ -2615,11 +2620,19 @@ namespace ts {
26152620
});
26162621
}
26172622

2618-
/** Get the type annotaion for the value parameter. */
2619-
export function getSetAccessorTypeAnnotationNode(accessor: SetAccessorDeclaration): TypeNode {
2623+
/** Get the type annotation for the value parameter. */
2624+
export function getSetAccessorTypeAnnotationNode(accessor: SetAccessorDeclaration, includeJSDocType?: boolean): TypeNode {
26202625
if (accessor && accessor.parameters.length > 0) {
26212626
const hasThis = accessor.parameters.length === 2 && parameterIsThisKeyword(accessor.parameters[0]);
2622-
return accessor.parameters[hasThis ? 1 : 0].type;
2627+
const parameter = accessor.parameters[hasThis ? 1 : 0];
2628+
if (parameter) {
2629+
if (parameter.type) {
2630+
return parameter.type;
2631+
}
2632+
if (includeJSDocType && parameter.flags & NodeFlags.JavaScriptFile) {
2633+
return getJSDocType(parameter);
2634+
}
2635+
}
26232636
}
26242637
}
26252638

tests/baselines/reference/checkJsdocTypeTag1.types

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ x(1);
6161
/** @type {function (number)} */
6262
const x1 = (a) => a + 1;
6363
>x1 : (arg0: number) => any
64-
>(a) => a + 1 : (a: any) => any
65-
>a : any
66-
>a + 1 : any
67-
>a : any
64+
>(a) => a + 1 : (a: number) => number
65+
>a : number
66+
>a + 1 : number
67+
>a : number
6868
>1 : 1
6969

7070
x1(0);
@@ -75,10 +75,10 @@ x1(0);
7575
/** @type {function (number): number} */
7676
const x2 = (a) => a + 1;
7777
>x2 : (arg0: number) => number
78-
>(a) => a + 1 : (a: any) => any
79-
>a : any
80-
>a + 1 : any
81-
>a : any
78+
>(a) => a + 1 : (a: number) => number
79+
>a : number
80+
>a + 1 : number
81+
>a : number
8282
>1 : 1
8383

8484
x2(0);

tests/baselines/reference/checkJsdocTypeTag2.errors.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ tests/cases/conformance/jsdoc/0.js(10,4): error TS2345: Argument of type '"strin
44
tests/cases/conformance/jsdoc/0.js(13,7): error TS2451: Cannot redeclare block-scoped variable 'x2'.
55
tests/cases/conformance/jsdoc/0.js(17,1): error TS2322: Type 'number' is not assignable to type 'string'.
66
tests/cases/conformance/jsdoc/0.js(20,7): error TS2451: Cannot redeclare block-scoped variable 'x2'.
7+
tests/cases/conformance/jsdoc/0.js(20,21): error TS2339: Property 'concat' does not exist on type 'number'.
78

89

9-
==== tests/cases/conformance/jsdoc/0.js (6 errors) ====
10+
==== tests/cases/conformance/jsdoc/0.js (7 errors) ====
1011
// @ts-check
1112
/** @type {String} */
1213
var S = true;
@@ -39,4 +40,6 @@ tests/cases/conformance/jsdoc/0.js(20,7): error TS2451: Cannot redeclare block-s
3940
const x2 = (a) => a.concat("hi");
4041
~~
4142
!!! error TS2451: Cannot redeclare block-scoped variable 'x2'.
43+
~~~~~~
44+
!!! error TS2339: Property 'concat' does not exist on type 'number'.
4245
x2(0);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
=== tests/cases/conformance/types/contextualTypes/jsdoc/index.js ===
2+
/** @type {Array<[string, {x?:number, y?:number}]>} */
3+
const arr = [
4+
>arr : Symbol(arr, Decl(index.js, 1, 5))
5+
6+
['a', { x: 1 }],
7+
>x : Symbol(x, Decl(index.js, 2, 11))
8+
9+
['b', { y: 2 }]
10+
>y : Symbol(y, Decl(index.js, 3, 11))
11+
12+
];
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
=== tests/cases/conformance/types/contextualTypes/jsdoc/index.js ===
2+
/** @type {Array<[string, {x?:number, y?:number}]>} */
3+
const arr = [
4+
>arr : [string, { x?: number; y?: number; }][]
5+
>[ ['a', { x: 1 }], ['b', { y: 2 }]] : ([string, { x: number; }] | [string, { y: number; }])[]
6+
7+
['a', { x: 1 }],
8+
>['a', { x: 1 }] : [string, { x: number; }]
9+
>'a' : "a"
10+
>{ x: 1 } : { x: number; }
11+
>x : number
12+
>1 : 1
13+
14+
['b', { y: 2 }]
15+
>['b', { y: 2 }] : [string, { y: number; }]
16+
>'b' : "b"
17+
>{ y: 2 } : { y: number; }
18+
>y : number
19+
>2 : 2
20+
21+
];
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// @allowJs: true
2+
// @checkJs: true
3+
// @noEmit: true
4+
// @filename: index.js
5+
6+
/** @type {Array<[string, {x?:number, y?:number}]>} */
7+
const arr = [
8+
['a', { x: 1 }],
9+
['b', { y: 2 }]
10+
];

0 commit comments

Comments
 (0)