Skip to content

Commit b56093f

Browse files
authored
Fix type when annotated with a JSDoc function type (microsoft#22692)
* Fix type when annotated with a JSDoc function type Previously, 1. A variable annotated with a JSDoc function type would not require all its parameters to be provided. This should only apply to functions without a type annotation. 2. A parameter in a function with a JSDoc function type annotation would still have the type 'any'. 3. Two `var` declarations in a Typescript and Javascript file, respectively, would error even when they had identical function types. * Update baselines and add constructor test * Handle ConstructorType too * Add test:method sig inside literal type * Contextually type parameters by parent sig's JSDoc Instead of a syntactic check in getJSDocTag * Remove redundant check:isUntypedSignatureInJSFile * Positive check for value signatures Instead of excluding type signatures piecemeal.
1 parent d88041f commit b56093f

18 files changed

+327
-68
lines changed

src/compiler/checker.ts

+55-41
Original file line numberDiff line numberDiff line change
@@ -6691,7 +6691,11 @@ namespace ts {
66916691
let hasThisParameter: boolean;
66926692
const iife = getImmediatelyInvokedFunctionExpression(declaration);
66936693
const isJSConstructSignature = isJSDocConstructSignature(declaration);
6694-
const isUntypedSignatureInJSFile = !iife && !isJSConstructSignature && isInJavaScriptFile(declaration) && !hasJSDocParameterTags(declaration);
6694+
const isUntypedSignatureInJSFile = !iife &&
6695+
isInJavaScriptFile(declaration) &&
6696+
isValueSignatureDeclaration(declaration) &&
6697+
!hasJSDocParameterTags(declaration) &&
6698+
!getJSDocType(declaration);
66956699

66966700
// If this is a JSDoc construct signature, then skip the first parameter in the
66976701
// parameter list. The first parameter represents the return type of the construct
@@ -9100,7 +9104,8 @@ namespace ts {
91009104
}
91019105

91029106
function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration {
9103-
return (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && isContextSensitiveFunctionLikeDeclaration(func);
9107+
return (isInJavaScriptFile(func) && isFunctionDeclaration(func) || isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) &&
9108+
isContextSensitiveFunctionLikeDeclaration(func);
91049109
}
91059110

91069111
function getTypeWithoutSignatures(type: Type): Type {
@@ -14168,49 +14173,49 @@ namespace ts {
1416814173
// Return contextual type of parameter or undefined if no contextual type is available
1416914174
function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type | undefined {
1417014175
const func = parameter.parent;
14171-
if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) {
14172-
const iife = getImmediatelyInvokedFunctionExpression(func);
14173-
if (iife && iife.arguments) {
14174-
const indexOfParameter = func.parameters.indexOf(parameter);
14175-
if (parameter.dotDotDotToken) {
14176-
const restTypes: Type[] = [];
14177-
for (let i = indexOfParameter; i < iife.arguments.length; i++) {
14178-
restTypes.push(getWidenedLiteralType(checkExpression(iife.arguments[i])));
14179-
}
14180-
return restTypes.length ? createArrayType(getUnionType(restTypes)) : undefined;
14181-
}
14182-
const links = getNodeLinks(iife);
14183-
const cached = links.resolvedSignature;
14184-
links.resolvedSignature = anySignature;
14185-
const type = indexOfParameter < iife.arguments.length ?
14186-
getWidenedLiteralType(checkExpression(iife.arguments[indexOfParameter])) :
14187-
parameter.initializer ? undefined : undefinedWideningType;
14188-
links.resolvedSignature = cached;
14189-
return type;
14176+
if (!isContextSensitiveFunctionOrObjectLiteralMethod(func)) {
14177+
return undefined;
14178+
}
14179+
const iife = getImmediatelyInvokedFunctionExpression(func);
14180+
if (iife && iife.arguments) {
14181+
const indexOfParameter = func.parameters.indexOf(parameter);
14182+
if (parameter.dotDotDotToken) {
14183+
const restTypes: Type[] = [];
14184+
for (let i = indexOfParameter; i < iife.arguments.length; i++) {
14185+
restTypes.push(getWidenedLiteralType(checkExpression(iife.arguments[i])));
14186+
}
14187+
return restTypes.length ? createArrayType(getUnionType(restTypes)) : undefined;
14188+
}
14189+
const links = getNodeLinks(iife);
14190+
const cached = links.resolvedSignature;
14191+
links.resolvedSignature = anySignature;
14192+
const type = indexOfParameter < iife.arguments.length ?
14193+
getWidenedLiteralType(checkExpression(iife.arguments[indexOfParameter])) :
14194+
parameter.initializer ? undefined : undefinedWideningType;
14195+
links.resolvedSignature = cached;
14196+
return type;
14197+
}
14198+
const contextualSignature = getContextualSignature(func);
14199+
if (contextualSignature) {
14200+
const funcHasRestParameters = hasRestParameter(func);
14201+
const len = func.parameters.length - (funcHasRestParameters ? 1 : 0);
14202+
let indexOfParameter = func.parameters.indexOf(parameter);
14203+
if (getThisParameter(func) !== undefined && !contextualSignature.thisParameter) {
14204+
Debug.assert(indexOfParameter !== 0); // Otherwise we should not have called `getContextuallyTypedParameterType`.
14205+
indexOfParameter -= 1;
1419014206
}
14191-
const contextualSignature = getContextualSignature(func);
14192-
if (contextualSignature) {
14193-
const funcHasRestParameters = hasRestParameter(func);
14194-
const len = func.parameters.length - (funcHasRestParameters ? 1 : 0);
14195-
let indexOfParameter = func.parameters.indexOf(parameter);
14196-
if (getThisParameter(func) !== undefined && !contextualSignature.thisParameter) {
14197-
Debug.assert(indexOfParameter !== 0); // Otherwise we should not have called `getContextuallyTypedParameterType`.
14198-
indexOfParameter -= 1;
14199-
}
1420014207

14201-
if (indexOfParameter < len) {
14202-
return getTypeAtPosition(contextualSignature, indexOfParameter);
14203-
}
14208+
if (indexOfParameter < len) {
14209+
return getTypeAtPosition(contextualSignature, indexOfParameter);
14210+
}
1420414211

14205-
// If last parameter is contextually rest parameter get its type
14206-
if (funcHasRestParameters &&
14207-
indexOfParameter === (func.parameters.length - 1) &&
14208-
isRestParameterIndex(contextualSignature, func.parameters.length - 1)) {
14209-
return getTypeOfSymbol(lastOrUndefined(contextualSignature.parameters));
14210-
}
14212+
// If last parameter is contextually rest parameter get its type
14213+
if (funcHasRestParameters &&
14214+
indexOfParameter === (func.parameters.length - 1) &&
14215+
isRestParameterIndex(contextualSignature, func.parameters.length - 1)) {
14216+
return getTypeOfSymbol(lastOrUndefined(contextualSignature.parameters));
1421114217
}
1421214218
}
14213-
return undefined;
1421414219
}
1421514220

1421614221
// In a variable, parameter or property declaration with a type annotation,
@@ -14773,7 +14778,16 @@ namespace ts {
1477314778
// union type of return types from these signatures
1477414779
function getContextualSignature(node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature {
1477514780
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node));
14776-
const type = getContextualTypeForFunctionLikeDeclaration(node);
14781+
let type: Type;
14782+
if (isInJavaScriptFile(node)) {
14783+
const jsdoc = getJSDocType(node);
14784+
if (jsdoc) {
14785+
type = getTypeFromTypeNode(jsdoc);
14786+
}
14787+
}
14788+
if (!type) {
14789+
type = getContextualTypeForFunctionLikeDeclaration(node);
14790+
}
1477714791
if (!type) {
1477814792
return undefined;
1477914793
}

src/compiler/utilities.ts

+12
Original file line numberDiff line numberDiff line change
@@ -1958,6 +1958,18 @@ namespace ts {
19581958
return false;
19591959
}
19601960

1961+
export type ValueSignatureDeclaration =
1962+
| FunctionDeclaration
1963+
| MethodDeclaration
1964+
| ConstructorDeclaration
1965+
| AccessorDeclaration
1966+
| FunctionExpression
1967+
| ArrowFunction;
1968+
1969+
export function isValueSignatureDeclaration(node: Node): node is ValueSignatureDeclaration {
1970+
return isFunctionExpression(node) || isArrowFunction(node) || isMethodOrAccessor(node) || isFunctionDeclaration(node) || isConstructorDeclaration(node);
1971+
}
1972+
19611973
function walkUp(node: Node, kind: SyntaxKind) {
19621974
while (node && node.kind === kind) {
19631975
node = node.parent;

tests/baselines/reference/checkJsdocTypeTagOnObjectProperty1.types

+8-8
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ const obj = {
2020

2121
/** @type {function(number): number} */
2222
method1(n1) {
23-
>method1 : (n1: any) => any
24-
>n1 : any
23+
>method1 : (n1: number) => number
24+
>n1 : number
2525

2626
return n1 + 42;
27-
>n1 + 42 : any
28-
>n1 : any
27+
>n1 + 42 : number
28+
>n1 : number
2929
>42 : 42
3030

3131
},
@@ -44,10 +44,10 @@ const obj = {
4444
/** @type {function(number): number} */
4545
arrowFunc: (num) => num + 42
4646
>arrowFunc : (arg0: number) => number
47-
>(num) => num + 42 : (num: any) => any
48-
>num : any
49-
>num + 42 : any
50-
>num : any
47+
>(num) => num + 42 : (num: number) => number
48+
>num : number
49+
>num + 42 : number
50+
>num : number
5151
>42 : 42
5252
}
5353
obj.foo = 'string'

tests/baselines/reference/checkJsdocTypeTagOnObjectProperty2.errors.txt

+7-11
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
tests/cases/conformance/jsdoc/0.js(5,3): error TS2322: Type 'number' is not assignable to type 'string | undefined'.
2-
tests/cases/conformance/jsdoc/0.js(7,3): error TS2322: Type '(n1: any) => string' is not assignable to type '(arg0: number) => number'.
2+
tests/cases/conformance/jsdoc/0.js(7,3): error TS2322: Type '(n1: number) => string' is not assignable to type '(arg0: number) => number'.
33
Type 'string' is not assignable to type 'number'.
4-
tests/cases/conformance/jsdoc/0.js(11,3): error TS2322: Type '(n1: any) => string' is not assignable to type '(arg0: number) => number'.
4+
tests/cases/conformance/jsdoc/0.js(11,3): error TS2322: Type '(n1: number) => string' is not assignable to type '(arg0: number) => number'.
55
Type 'string' is not assignable to type 'number'.
6-
tests/cases/conformance/jsdoc/0.js(13,3): error TS2322: Type '(num?: string) => string' is not assignable to type '(arg0: number) => number'.
7-
Types of parameters 'num' and 'arg0' are incompatible.
8-
Type 'number' is not assignable to type 'string | undefined'.
6+
tests/cases/conformance/jsdoc/0.js(13,15): error TS2322: Type '"0"' is not assignable to type 'number'.
97
tests/cases/conformance/jsdoc/0.js(15,3): error TS2322: Type 'undefined' is not assignable to type 'string'.
108
tests/cases/conformance/jsdoc/0.js(19,5): error TS2322: Type 'number' is not assignable to type 'string'.
119
tests/cases/conformance/jsdoc/0.js(22,22): error TS2345: Argument of type '"0"' is not assignable to parameter of type 'number'.
@@ -22,21 +20,19 @@ tests/cases/conformance/jsdoc/0.js(22,22): error TS2345: Argument of type '"0"'
2220
/** @type {function(number): number} */
2321
method1(n1) {
2422
~~~~~~~
25-
!!! error TS2322: Type '(n1: any) => string' is not assignable to type '(arg0: number) => number'.
23+
!!! error TS2322: Type '(n1: number) => string' is not assignable to type '(arg0: number) => number'.
2624
!!! error TS2322: Type 'string' is not assignable to type 'number'.
2725
return "42";
2826
},
2927
/** @type {function(number): number} */
3028
method2: (n1) => "lol",
3129
~~~~~~~~~~~~~~~~~~~~~~
32-
!!! error TS2322: Type '(n1: any) => string' is not assignable to type '(arg0: number) => number'.
30+
!!! error TS2322: Type '(n1: number) => string' is not assignable to type '(arg0: number) => number'.
3331
!!! error TS2322: Type 'string' is not assignable to type 'number'.
3432
/** @type {function(number): number} */
3533
arrowFunc: (num="0") => num + 42,
36-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37-
!!! error TS2322: Type '(num?: string) => string' is not assignable to type '(arg0: number) => number'.
38-
!!! error TS2322: Types of parameters 'num' and 'arg0' are incompatible.
39-
!!! error TS2322: Type 'number' is not assignable to type 'string | undefined'.
34+
~~~~~~~
35+
!!! error TS2322: Type '"0"' is not assignable to type 'number'.
4036
/** @type {string} */
4137
lol
4238
~~~

tests/baselines/reference/checkJsdocTypeTagOnObjectProperty2.types

+8-8
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ const obj = {
1414

1515
/** @type {function(number): number} */
1616
method1(n1) {
17-
>method1 : (n1: any) => string
18-
>n1 : any
17+
>method1 : (n1: number) => string
18+
>n1 : number
1919

2020
return "42";
2121
>"42" : "42"
@@ -24,18 +24,18 @@ const obj = {
2424
/** @type {function(number): number} */
2525
method2: (n1) => "lol",
2626
>method2 : (arg0: number) => number
27-
>(n1) => "lol" : (n1: any) => string
28-
>n1 : any
27+
>(n1) => "lol" : (n1: number) => string
28+
>n1 : number
2929
>"lol" : "lol"
3030

3131
/** @type {function(number): number} */
3232
arrowFunc: (num="0") => num + 42,
3333
>arrowFunc : (arg0: number) => number
34-
>(num="0") => num + 42 : (num?: string) => string
35-
>num : string
34+
>(num="0") => num + 42 : (num?: number) => number
35+
>num : number
3636
>"0" : "0"
37-
>num + 42 : string
38-
>num : string
37+
>num + 42 : number
38+
>num : number
3939
>42 : 42
4040

4141
/** @type {string} */

tests/baselines/reference/jsdocTypeTag.js

+14
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ var obj;
6060

6161
/** @type {Function} */
6262
var Func;
63+
64+
/** @type {(s: string) => number} */
65+
var f;
66+
67+
/** @type {new (s: string) => { s: string }} */
68+
var ctor;
6369

6470
//// [b.ts]
6571
var S: string;
@@ -82,6 +88,8 @@ var nullable: number | null;
8288
var Obj: any;
8389
var obj: any;
8490
var Func: Function;
91+
var f: (s: string) => number;
92+
var ctor: new (s: string) => { s: string };
8593

8694

8795
//// [a.js]
@@ -125,6 +133,10 @@ var Obj;
125133
var obj;
126134
/** @type {Function} */
127135
var Func;
136+
/** @type {(s: string) => number} */
137+
var f;
138+
/** @type {new (s: string) => { s: string }} */
139+
var ctor;
128140
//// [b.js]
129141
var S;
130142
var s;
@@ -146,3 +158,5 @@ var nullable;
146158
var Obj;
147159
var obj;
148160
var Func;
161+
var f;
162+
var ctor;

tests/baselines/reference/jsdocTypeTag.symbols

+17
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,14 @@ var obj;
7979
var Func;
8080
>Func : Symbol(Func, Decl(a.js, 58, 3), Decl(b.ts, 19, 3))
8181

82+
/** @type {(s: string) => number} */
83+
var f;
84+
>f : Symbol(f, Decl(a.js, 61, 3), Decl(b.ts, 20, 3))
85+
86+
/** @type {new (s: string) => { s: string }} */
87+
var ctor;
88+
>ctor : Symbol(ctor, Decl(a.js, 64, 3), Decl(b.ts, 21, 3))
89+
8290
=== tests/cases/conformance/jsdoc/b.ts ===
8391
var S: string;
8492
>S : Symbol(S, Decl(a.js, 1, 3), Decl(b.ts, 0, 3))
@@ -143,3 +151,12 @@ var Func: Function;
143151
>Func : Symbol(Func, Decl(a.js, 58, 3), Decl(b.ts, 19, 3))
144152
>Function : Symbol(Function, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
145153

154+
var f: (s: string) => number;
155+
>f : Symbol(f, Decl(a.js, 61, 3), Decl(b.ts, 20, 3))
156+
>s : Symbol(s, Decl(b.ts, 20, 8))
157+
158+
var ctor: new (s: string) => { s: string };
159+
>ctor : Symbol(ctor, Decl(a.js, 64, 3), Decl(b.ts, 21, 3))
160+
>s : Symbol(s, Decl(b.ts, 21, 15))
161+
>s : Symbol(s, Decl(b.ts, 21, 30))
162+

tests/baselines/reference/jsdocTypeTag.types

+17
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,14 @@ var obj;
7979
var Func;
8080
>Func : Function
8181

82+
/** @type {(s: string) => number} */
83+
var f;
84+
>f : (s: string) => number
85+
86+
/** @type {new (s: string) => { s: string }} */
87+
var ctor;
88+
>ctor : new (s: string) => { s: string; }
89+
8290
=== tests/cases/conformance/jsdoc/b.ts ===
8391
var S: string;
8492
>S : string
@@ -146,3 +154,12 @@ var Func: Function;
146154
>Func : Function
147155
>Function : Function
148156

157+
var f: (s: string) => number;
158+
>f : (s: string) => number
159+
>s : string
160+
161+
var ctor: new (s: string) => { s: string };
162+
>ctor : new (s: string) => { s: string; }
163+
>s : string
164+
>s : string
165+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
tests/cases/conformance/jsdoc/a.js(3,5): error TS2322: Type '1' is not assignable to type 'string'.
2+
tests/cases/conformance/jsdoc/a.js(7,5): error TS2322: Type '1' is not assignable to type 'string'.
3+
4+
5+
==== tests/cases/conformance/jsdoc/a.js (2 errors) ====
6+
/** @type {function(string): void} */
7+
const f = (value) => {
8+
value = 1 // should error
9+
~~~~~
10+
!!! error TS2322: Type '1' is not assignable to type 'string'.
11+
};
12+
/** @type {(s: string) => void} */
13+
function g(s) {
14+
s = 1 // Should error
15+
~
16+
!!! error TS2322: Type '1' is not assignable to type 'string'.
17+
}
18+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
=== tests/cases/conformance/jsdoc/a.js ===
2+
/** @type {function(string): void} */
3+
const f = (value) => {
4+
>f : Symbol(f, Decl(a.js, 1, 5))
5+
>value : Symbol(value, Decl(a.js, 1, 11))
6+
7+
value = 1 // should error
8+
>value : Symbol(value, Decl(a.js, 1, 11))
9+
10+
};
11+
/** @type {(s: string) => void} */
12+
function g(s) {
13+
>g : Symbol(g, Decl(a.js, 3, 2))
14+
>s : Symbol(s, Decl(a.js, 5, 11))
15+
16+
s = 1 // Should error
17+
>s : Symbol(s, Decl(a.js, 5, 11))
18+
}
19+

0 commit comments

Comments
 (0)