Skip to content

Commit eadafd2

Browse files
authored
Merge pull request #16346 from Microsoft/fix15618
Improve contextual types using jsdoc tags
2 parents 025fa87 + ff1f337 commit eadafd2

10 files changed

+350
-108
lines changed

src/compiler/checker.ts

+84-84
Large diffs are not rendered by default.

src/compiler/utilities.ts

+48-3
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,14 +2620,19 @@ namespace ts {
26152620
});
26162621
}
26172622

2618-
/** Get the type annotaion for the value parameter. */
2619-
export function getSetAccessorTypeAnnotationNode(accessor: SetAccessorDeclaration): TypeNode {
2623+
function getSetAccessorValueParameter(accessor: SetAccessorDeclaration): ParameterDeclaration | undefined {
26202624
if (accessor && accessor.parameters.length > 0) {
26212625
const hasThis = accessor.parameters.length === 2 && parameterIsThisKeyword(accessor.parameters[0]);
2622-
return accessor.parameters[hasThis ? 1 : 0].type;
2626+
return accessor.parameters[hasThis ? 1 : 0];
26232627
}
26242628
}
26252629

2630+
/** Get the type annotation for the value parameter. */
2631+
export function getSetAccessorTypeAnnotationNode(accessor: SetAccessorDeclaration): TypeNode {
2632+
const parameter = getSetAccessorValueParameter(accessor);
2633+
return parameter && parameter.type;
2634+
}
2635+
26262636
export function getThisParameter(signature: SignatureDeclaration): ParameterDeclaration | undefined {
26272637
if (signature.parameters.length) {
26282638
const thisParameter = signature.parameters[0];
@@ -2701,6 +2711,41 @@ namespace ts {
27012711
};
27022712
}
27032713

2714+
/**
2715+
* Gets the effective type annotation of a variable, parameter, or property. If the node was
2716+
* parsed in a JavaScript file, gets the type annotation from JSDoc.
2717+
*/
2718+
export function getEffectiveTypeAnnotationNode(node: VariableLikeDeclaration): TypeNode {
2719+
if (node.type) {
2720+
return node.type;
2721+
}
2722+
if (node.flags & NodeFlags.JavaScriptFile) {
2723+
return getJSDocType(node);
2724+
}
2725+
}
2726+
2727+
/**
2728+
* Gets the effective return type annotation of a signature. If the node was parsed in a
2729+
* JavaScript file, gets the return type annotation from JSDoc.
2730+
*/
2731+
export function getEffectiveReturnTypeNode(node: SignatureDeclaration): TypeNode {
2732+
if (node.type) {
2733+
return node.type;
2734+
}
2735+
if (node.flags & NodeFlags.JavaScriptFile) {
2736+
return getJSDocReturnType(node);
2737+
}
2738+
}
2739+
2740+
/**
2741+
* Gets the effective type annotation of the value parameter of a set accessor. If the node
2742+
* was parsed in a JavaScript file, gets the type annotation from JSDoc.
2743+
*/
2744+
export function getEffectiveSetAccessorTypeAnnotationNode(node: SetAccessorDeclaration): TypeNode {
2745+
const parameter = getSetAccessorValueParameter(node);
2746+
return parameter && getEffectiveTypeAnnotationNode(parameter);
2747+
}
2748+
27042749
export function emitNewLineBeforeLeadingComments(lineMap: number[], writer: EmitTextWriter, node: TextRange, leadingComments: CommentRange[]) {
27052750
emitNewLineBeforeLeadingCommentsOfPosition(lineMap, writer, node.pos, leadingComments);
27062751
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
tests/cases/conformance/jsdoc/returns.js(6,5): error TS2322: Type '5' is not assignable to type 'string'.
2+
tests/cases/conformance/jsdoc/returns.js(13,5): error TS2322: Type 'true | 5' is not assignable to type 'string | number'.
3+
Type 'true' is not assignable to type 'string | number'.
4+
5+
6+
==== tests/cases/conformance/jsdoc/returns.js (2 errors) ====
7+
// @ts-check
8+
/**
9+
* @returns {string} This comment is not currently exposed
10+
*/
11+
function f() {
12+
return 5;
13+
~~~~~~~~~
14+
!!! error TS2322: Type '5' is not assignable to type 'string'.
15+
}
16+
17+
/**
18+
* @returns {string | number} This comment is not currently exposed
19+
*/
20+
function f1() {
21+
return 5 || true;
22+
~~~~~~~~~~~~~~~~~
23+
!!! error TS2322: Type 'true | 5' is not assignable to type 'string | number'.
24+
!!! error TS2322: Type 'true' is not assignable to type 'string | number'.
25+
}

tests/baselines/reference/checkJsdocTypeTag1.types

+8-8
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

+13-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
tests/cases/conformance/jsdoc/0.js(3,5): error TS2322: Type 'true' is not assignable to type 'string'.
22
tests/cases/conformance/jsdoc/0.js(6,5): error TS2322: Type '"hello"' is not assignable to type 'number'.
33
tests/cases/conformance/jsdoc/0.js(10,4): error TS2345: Argument of type '"string"' is not assignable to parameter of type 'number'.
4-
tests/cases/conformance/jsdoc/0.js(13,7): error TS2451: Cannot redeclare block-scoped variable 'x2'.
54
tests/cases/conformance/jsdoc/0.js(17,1): error TS2322: Type 'number' is not assignable to type 'string'.
6-
tests/cases/conformance/jsdoc/0.js(20,7): error TS2451: Cannot redeclare block-scoped variable 'x2'.
5+
tests/cases/conformance/jsdoc/0.js(20,21): error TS2339: Property 'concat' does not exist on type 'number'.
6+
tests/cases/conformance/jsdoc/0.js(24,7): error TS2322: Type '(a: number) => number' is not assignable to type '(arg0: number) => string'.
7+
Type 'number' is not assignable to type 'string'.
78

89

910
==== tests/cases/conformance/jsdoc/0.js (6 errors) ====
@@ -26,8 +27,6 @@ tests/cases/conformance/jsdoc/0.js(20,7): error TS2451: Cannot redeclare block-s
2627

2728
/** @type {function (number): number} */
2829
const x2 = (a) => a + 1;
29-
~~
30-
!!! error TS2451: Cannot redeclare block-scoped variable 'x2'.
3130

3231
/** @type {string} */
3332
var a;
@@ -36,7 +35,14 @@ tests/cases/conformance/jsdoc/0.js(20,7): error TS2451: Cannot redeclare block-s
3635
!!! error TS2322: Type 'number' is not assignable to type 'string'.
3736

3837
/** @type {function (number): number} */
39-
const x2 = (a) => a.concat("hi");
38+
const x3 = (a) => a.concat("hi");
39+
~~~~~~
40+
!!! error TS2339: Property 'concat' does not exist on type 'number'.
41+
x3(0);
42+
43+
/** @type {function (number): string} */
44+
const x4 = (a) => a + 1;
4045
~~
41-
!!! error TS2451: Cannot redeclare block-scoped variable 'x2'.
42-
x2(0);
46+
!!! error TS2322: Type '(a: number) => number' is not assignable to type '(arg0: number) => string'.
47+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
48+
x4(0);

tests/baselines/reference/checkJsdocTypeTag2.js

+11-4
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@ var a;
1818
a = x2(0);
1919

2020
/** @type {function (number): number} */
21-
const x2 = (a) => a.concat("hi");
22-
x2(0);
21+
const x3 = (a) => a.concat("hi");
22+
x3(0);
23+
24+
/** @type {function (number): string} */
25+
const x4 = (a) => a + 1;
26+
x4(0);
2327

2428
//// [0.js]
2529
// @ts-check
@@ -36,5 +40,8 @@ var x2 = function (a) { return a + 1; };
3640
var a;
3741
a = x2(0);
3842
/** @type {function (number): number} */
39-
var x2 = function (a) { return a.concat("hi"); };
40-
x2(0);
43+
var x3 = function (a) { return a.concat("hi"); };
44+
x3(0);
45+
/** @type {function (number): string} */
46+
var x4 = function (a) { return a + 1; };
47+
x4(0);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
];
13+
14+
/** @return {Array<[string, {x?:number, y?:number}]>} */
15+
function f() {
16+
>f : Symbol(f, Decl(index.js, 4, 2))
17+
18+
return [
19+
['a', { x: 1 }],
20+
>x : Symbol(x, Decl(index.js, 9, 15))
21+
22+
['b', { y: 2 }]
23+
>y : Symbol(y, Decl(index.js, 10, 15))
24+
25+
];
26+
}
27+
28+
class C {
29+
>C : Symbol(C, Decl(index.js, 12, 1))
30+
31+
/** @param {Array<[string, {x?:number, y?:number}]>} value */
32+
set x(value) { }
33+
>x : Symbol(C.x, Decl(index.js, 14, 9), Decl(index.js, 16, 20))
34+
>value : Symbol(value, Decl(index.js, 16, 10))
35+
36+
get x() {
37+
>x : Symbol(C.x, Decl(index.js, 14, 9), Decl(index.js, 16, 20))
38+
39+
return [
40+
['a', { x: 1 }],
41+
>x : Symbol(x, Decl(index.js, 19, 19))
42+
43+
['b', { y: 2 }]
44+
>y : Symbol(y, Decl(index.js, 20, 19))
45+
46+
];
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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+
];
22+
23+
/** @return {Array<[string, {x?:number, y?:number}]>} */
24+
function f() {
25+
>f : () => [string, { x?: number; y?: number; }][]
26+
27+
return [
28+
>[ ['a', { x: 1 }], ['b', { y: 2 }] ] : ([string, { x: number; }] | [string, { y: number; }])[]
29+
30+
['a', { x: 1 }],
31+
>['a', { x: 1 }] : [string, { x: number; }]
32+
>'a' : "a"
33+
>{ x: 1 } : { x: number; }
34+
>x : number
35+
>1 : 1
36+
37+
['b', { y: 2 }]
38+
>['b', { y: 2 }] : [string, { y: number; }]
39+
>'b' : "b"
40+
>{ y: 2 } : { y: number; }
41+
>y : number
42+
>2 : 2
43+
44+
];
45+
}
46+
47+
class C {
48+
>C : C
49+
50+
/** @param {Array<[string, {x?:number, y?:number}]>} value */
51+
set x(value) { }
52+
>x : [string, { x?: number; y?: number; }][]
53+
>value : [string, { x?: number; y?: number; }][]
54+
55+
get x() {
56+
>x : [string, { x?: number; y?: number; }][]
57+
58+
return [
59+
>[ ['a', { x: 1 }], ['b', { y: 2 }] ] : ([string, { x: number; }] | [string, { y: number; }])[]
60+
61+
['a', { x: 1 }],
62+
>['a', { x: 1 }] : [string, { x: number; }]
63+
>'a' : "a"
64+
>{ x: 1 } : { x: number; }
65+
>x : number
66+
>1 : 1
67+
68+
['b', { y: 2 }]
69+
>['b', { y: 2 }] : [string, { y: number; }]
70+
>'b' : "b"
71+
>{ y: 2 } : { y: number; }
72+
>y : number
73+
>2 : 2
74+
75+
];
76+
}
77+
}

tests/cases/conformance/jsdoc/checkJsdocTypeTag2.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,9 @@ var a;
2121
a = x2(0);
2222

2323
/** @type {function (number): number} */
24-
const x2 = (a) => a.concat("hi");
25-
x2(0);
24+
const x3 = (a) => a.concat("hi");
25+
x3(0);
26+
27+
/** @type {function (number): string} */
28+
const x4 = (a) => a + 1;
29+
x4(0);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// @allowJs: true
2+
// @checkJs: true
3+
// @noEmit: true
4+
// @filename: index.js
5+
// @target: esnext
6+
7+
/** @type {Array<[string, {x?:number, y?:number}]>} */
8+
const arr = [
9+
['a', { x: 1 }],
10+
['b', { y: 2 }]
11+
];
12+
13+
/** @return {Array<[string, {x?:number, y?:number}]>} */
14+
function f() {
15+
return [
16+
['a', { x: 1 }],
17+
['b', { y: 2 }]
18+
];
19+
}
20+
21+
class C {
22+
/** @param {Array<[string, {x?:number, y?:number}]>} value */
23+
set x(value) { }
24+
get x() {
25+
return [
26+
['a', { x: 1 }],
27+
['b', { y: 2 }]
28+
];
29+
}
30+
}

0 commit comments

Comments
 (0)