Skip to content

Commit 35c6fbf

Browse files
JSDoc @type tag optional parameters (#48132)
* JSDoc @type tag optional parameters * Don't repeat isInJSFile() condition * Exclude variable initializers * Add tests for class methods * Don't contextually type JS function declarations * Update Baselines and/or Applied Lint Fixes * Reword comment Co-authored-by: TypeScript Bot <[email protected]>
1 parent 2513a2d commit 35c6fbf

12 files changed

+208
-27
lines changed

src/compiler/checker.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9065,10 +9065,8 @@ namespace ts {
90659065
return getReturnTypeOfSignature(getterSignature);
90669066
}
90679067
}
9068-
if (isInJSFile(declaration)) {
9069-
const type = getParameterTypeOfTypeTag(func, declaration);
9070-
if (type) return type;
9071-
}
9068+
const parameterTypeOfTypeTag = getParameterTypeOfTypeTag(func, declaration);
9069+
if (parameterTypeOfTypeTag) return parameterTypeOfTypeTag;
90729070
// Use contextual parameter type if one is available
90739071
const type = declaration.symbol.escapedName === InternalSymbolName.This ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration);
90749072
if (type) {
@@ -13117,7 +13115,14 @@ namespace ts {
1311713115
continue;
1311813116
}
1311913117
}
13120-
result.push(getSignatureFromDeclaration(decl));
13118+
// If this is a function or method declaration, get the signature from the @type tag for the sake of optional parameters.
13119+
// Exclude contextually-typed kinds because we already apply the @type tag to the context, plus applying it here to the initializer would supress checks that the two are compatible.
13120+
result.push(
13121+
(!isFunctionExpressionOrArrowFunction(decl) &&
13122+
!isObjectLiteralMethod(decl) &&
13123+
getSignatureOfTypeTag(decl)) ||
13124+
getSignatureFromDeclaration(decl)
13125+
);
1312113126
}
1312213127
return result;
1312313128
}
@@ -13152,7 +13157,7 @@ namespace ts {
1315213157
else {
1315313158
const type = signature.declaration && getEffectiveReturnTypeNode(signature.declaration);
1315413159
let jsdocPredicate: TypePredicate | undefined;
13155-
if (!type && isInJSFile(signature.declaration)) {
13160+
if (!type) {
1315613161
const jsdocSignature = getSignatureOfTypeTag(signature.declaration!);
1315713162
if (jsdocSignature && signature !== jsdocSignature) {
1315813163
jsdocPredicate = getTypePredicateOfSignature(jsdocSignature);
@@ -17470,8 +17475,7 @@ namespace ts {
1747017475
}
1747117476

1747217477
function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean {
17473-
return (!isFunctionDeclaration(node) || isInJSFile(node) && !!getTypeForDeclarationFromJSDocComment(node)) &&
17474-
(hasContextSensitiveParameters(node) || hasContextSensitiveReturnExpression(node));
17478+
return hasContextSensitiveParameters(node) || hasContextSensitiveReturnExpression(node);
1747517479
}
1747617480

1747717481
function hasContextSensitiveReturnExpression(node: FunctionLikeDeclaration) {
@@ -17480,7 +17484,7 @@ namespace ts {
1748017484
}
1748117485

1748217486
function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration {
17483-
return (isInJSFile(func) && isFunctionDeclaration(func) || isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) &&
17487+
return (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) &&
1748417488
isContextSensitiveFunctionLikeDeclaration(func);
1748517489
}
1748617490

tests/baselines/reference/checkJsdocTypeTag5.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ var k = function (x) { return x }
4444
/** @typedef {(x: 'hi' | 'bye') => 0 | 1 | 2} Argle */
4545
/** @type {Argle} */
4646
function blargle(s) {
47-
>blargle : (s: "hi" | "bye") => 0 | 1 | 2
47+
>blargle : (x: 'hi' | 'bye') => 0 | 1 | 2
4848
>s : "hi" | "bye"
4949

5050
return 0;
@@ -55,7 +55,7 @@ function blargle(s) {
5555
var zeroonetwo = blargle('hi')
5656
>zeroonetwo : 0 | 1 | 2
5757
>blargle('hi') : 0 | 1 | 2
58-
>blargle : (s: "hi" | "bye") => 0 | 1 | 2
58+
>blargle : (x: "hi" | "bye") => 0 | 1 | 2
5959
>'hi' : "hi"
6060

6161
/** @typedef {{(s: string): 0 | 1; (b: boolean): 2 | 3 }} Gioconda */

tests/baselines/reference/checkJsdocTypeTag6.errors.txt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
tests/cases/conformance/jsdoc/test.js(1,12): error TS8030: The type of a function declaration must match the function's signature.
22
tests/cases/conformance/jsdoc/test.js(7,5): error TS2322: Type '(prop: any) => void' is not assignable to type '{ prop: string; }'.
33
tests/cases/conformance/jsdoc/test.js(10,12): error TS8030: The type of a function declaration must match the function's signature.
4+
tests/cases/conformance/jsdoc/test.js(23,12): error TS8030: The type of a function declaration must match the function's signature.
5+
tests/cases/conformance/jsdoc/test.js(27,7): error TS2322: Type '(more: any) => void' is not assignable to type '() => void'.
6+
tests/cases/conformance/jsdoc/test.js(30,7): error TS2322: Type '(more: any) => void' is not assignable to type '() => void'.
7+
tests/cases/conformance/jsdoc/test.js(34,3): error TS2322: Type '(more: any) => void' is not assignable to type '() => void'.
48

59

6-
==== tests/cases/conformance/jsdoc/test.js (3 errors) ====
10+
==== tests/cases/conformance/jsdoc/test.js (7 errors) ====
711
/** @type {number} */
812
~~~~~~
913
!!! error TS8030: The type of a function declaration must match the function's signature.
@@ -28,4 +32,29 @@ tests/cases/conformance/jsdoc/test.js(10,12): error TS8030: The type of a functi
2832
// TODO: Should be an error since signature doesn't match.
2933
/** @type {(a: number, b: number, c: number) => number} */
3034
function add3(a, b) { return a + b; }
35+
36+
// Confirm initializers are compatible.
37+
// They can't have more parameters than the type/context.
38+
39+
/** @type {() => void} */
40+
~~~~~~~~~~
41+
!!! error TS8030: The type of a function declaration must match the function's signature.
42+
function funcWithMoreParameters(more) {} // error
43+
44+
/** @type {() => void} */
45+
const variableWithMoreParameters = function (more) {}; // error
46+
~~~~~~~~~~~~~~~~~~~~~~~~~~
47+
!!! error TS2322: Type '(more: any) => void' is not assignable to type '() => void'.
48+
49+
/** @type {() => void} */
50+
const arrowWithMoreParameters = (more) => {}; // error
51+
~~~~~~~~~~~~~~~~~~~~~~~
52+
!!! error TS2322: Type '(more: any) => void' is not assignable to type '() => void'.
53+
54+
({
55+
/** @type {() => void} */
56+
methodWithMoreParameters(more) {}, // error
57+
~~~~~~~~~~~~~~~~~~~~~~~~
58+
!!! error TS2322: Type '(more: any) => void' is not assignable to type '() => void'.
59+
});
3160

tests/baselines/reference/checkJsdocTypeTag6.symbols

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,29 @@ function add3(a, b) { return a + b; }
3737
>a : Symbol(a, Decl(test.js, 17, 14))
3838
>b : Symbol(b, Decl(test.js, 17, 16))
3939

40+
// Confirm initializers are compatible.
41+
// They can't have more parameters than the type/context.
42+
43+
/** @type {() => void} */
44+
function funcWithMoreParameters(more) {} // error
45+
>funcWithMoreParameters : Symbol(funcWithMoreParameters, Decl(test.js, 17, 37))
46+
>more : Symbol(more, Decl(test.js, 23, 32))
47+
48+
/** @type {() => void} */
49+
const variableWithMoreParameters = function (more) {}; // error
50+
>variableWithMoreParameters : Symbol(variableWithMoreParameters, Decl(test.js, 26, 5))
51+
>more : Symbol(more, Decl(test.js, 26, 45))
52+
53+
/** @type {() => void} */
54+
const arrowWithMoreParameters = (more) => {}; // error
55+
>arrowWithMoreParameters : Symbol(arrowWithMoreParameters, Decl(test.js, 29, 5))
56+
>more : Symbol(more, Decl(test.js, 29, 33))
57+
58+
({
59+
/** @type {() => void} */
60+
methodWithMoreParameters(more) {}, // error
61+
>methodWithMoreParameters : Symbol(methodWithMoreParameters, Decl(test.js, 31, 2))
62+
>more : Symbol(more, Decl(test.js, 33, 27))
63+
64+
});
65+

tests/baselines/reference/checkJsdocTypeTag6.types

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ var g = function (prop) {
1616

1717
/** @type {(a: number) => number} */
1818
function add1(a, b) { return a + b; }
19-
>add1 : (a: number, b: any) => number
19+
>add1 : (a: number) => number
2020
>a : number
2121
>b : any
2222
>a + b : any
@@ -35,10 +35,41 @@ function add2(a, b) { return a + b; }
3535
// TODO: Should be an error since signature doesn't match.
3636
/** @type {(a: number, b: number, c: number) => number} */
3737
function add3(a, b) { return a + b; }
38-
>add3 : (a: number, b: number) => number
38+
>add3 : (a: number, b: number, c: number) => number
3939
>a : number
4040
>b : number
4141
>a + b : number
4242
>a : number
4343
>b : number
4444

45+
// Confirm initializers are compatible.
46+
// They can't have more parameters than the type/context.
47+
48+
/** @type {() => void} */
49+
function funcWithMoreParameters(more) {} // error
50+
>funcWithMoreParameters : () => void
51+
>more : any
52+
53+
/** @type {() => void} */
54+
const variableWithMoreParameters = function (more) {}; // error
55+
>variableWithMoreParameters : () => void
56+
>function (more) {} : (more: any) => void
57+
>more : any
58+
59+
/** @type {() => void} */
60+
const arrowWithMoreParameters = (more) => {}; // error
61+
>arrowWithMoreParameters : () => void
62+
>(more) => {} : (more: any) => void
63+
>more : any
64+
65+
({
66+
>({ /** @type {() => void} */ methodWithMoreParameters(more) {}, // error}) : { methodWithMoreParameters(): void; }
67+
>{ /** @type {() => void} */ methodWithMoreParameters(more) {}, // error} : { methodWithMoreParameters(): void; }
68+
69+
/** @type {() => void} */
70+
methodWithMoreParameters(more) {}, // error
71+
>methodWithMoreParameters : (more: any) => void
72+
>more : any
73+
74+
});
75+

tests/baselines/reference/checkJsdocTypeTag7.symbols

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,9 @@ class C {
1111
>foo : Symbol(C.foo, Decl(test.js, 4, 9))
1212
>a : Symbol(a, Decl(test.js, 6, 8))
1313
>b : Symbol(b, Decl(test.js, 6, 10))
14+
15+
/** @type {(optional?) => void} */
16+
methodWithOptionalParameters() {}
17+
>methodWithOptionalParameters : Symbol(C.methodWithOptionalParameters, Decl(test.js, 6, 16))
1418
}
1519

tests/baselines/reference/checkJsdocTypeTag7.types

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,9 @@ class C {
1111
>foo : (a: string, b: number) => void
1212
>a : string
1313
>b : number
14+
15+
/** @type {(optional?) => void} */
16+
methodWithOptionalParameters() {}
17+
>methodWithOptionalParameters : (optional?: any) => void
1418
}
1519

tests/baselines/reference/jsDocDontBreakWithNamespaces.baseline

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -181,19 +181,82 @@
181181
"kind": "space"
182182
}
183183
],
184-
"parameters": [],
185-
"documentation": [],
186-
"tags": [
184+
"parameters": [
187185
{
188-
"name": "type",
189-
"text": [
186+
"name": "arg0",
187+
"documentation": [],
188+
"displayParts": [
190189
{
191-
"text": "{function(module:xxxx, module:xxxx): module:xxxxx}",
192-
"kind": "text"
190+
"text": "arg0",
191+
"kind": "parameterName"
192+
},
193+
{
194+
"text": ":",
195+
"kind": "punctuation"
196+
},
197+
{
198+
"text": " ",
199+
"kind": "space"
200+
},
201+
{
202+
"text": "any",
203+
"kind": "keyword"
193204
}
194-
]
205+
],
206+
"isOptional": false,
207+
"isRest": false
208+
},
209+
{
210+
"name": "arg1",
211+
"documentation": [],
212+
"displayParts": [
213+
{
214+
"text": "arg1",
215+
"kind": "parameterName"
216+
},
217+
{
218+
"text": ":",
219+
"kind": "punctuation"
220+
},
221+
{
222+
"text": " ",
223+
"kind": "space"
224+
},
225+
{
226+
"text": "any",
227+
"kind": "keyword"
228+
}
229+
],
230+
"isOptional": false,
231+
"isRest": false
232+
},
233+
{
234+
"name": "arg2",
235+
"documentation": [],
236+
"displayParts": [
237+
{
238+
"text": "arg2",
239+
"kind": "parameterName"
240+
},
241+
{
242+
"text": ":",
243+
"kind": "punctuation"
244+
},
245+
{
246+
"text": " ",
247+
"kind": "space"
248+
},
249+
{
250+
"text": "any",
251+
"kind": "keyword"
252+
}
253+
],
254+
"isOptional": false,
255+
"isRest": false
195256
}
196-
]
257+
],
258+
"documentation": [],
259+
"tags": []
197260
}
198261
],
199262
"applicableSpan": {

tests/baselines/reference/jsdocTypeTagRequiredParameters.errors.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ tests/cases/conformance/jsdoc/a.js(13,1): error TS2554: Expected 1 arguments, bu
2121
g() // should error
2222
~~~
2323
!!! error TS2554: Expected 1 arguments, but got 0.
24-
!!! related TS6210 tests/cases/conformance/jsdoc/a.js:5:12: An argument for 's' was not provided.
24+
!!! related TS6210 tests/cases/conformance/jsdoc/a.js:4:13: An argument for 's' was not provided.
2525
h()
2626
~~~
2727
!!! error TS2554: Expected 1 arguments, but got 0.
28-
!!! related TS6210 tests/cases/conformance/jsdoc/a.js:8:12: An argument for 's' was not provided.
28+
!!! related TS6210 tests/cases/conformance/jsdoc/a.js:7:14: An argument for 's' was not provided.
2929

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
=== tests/cases/conformance/jsdoc/bug25618.js ===
22
/** @type {<T>(param?: T) => T | undefined} */
33
function typed(param) {
4-
>typed : <T>(param: T | undefined) => T | undefined
4+
>typed : <T>(param?: T | undefined) => T | undefined
55
>param : T | undefined
66

77
return param;
@@ -11,7 +11,7 @@ function typed(param) {
1111
var n = typed(1);
1212
>n : number | undefined
1313
>typed(1) : 1 | undefined
14-
>typed : <T>(param: T | undefined) => T | undefined
14+
>typed : <T>(param?: T | undefined) => T | undefined
1515
>1 : 1
1616

1717

tests/cases/conformance/jsdoc/checkJsdocTypeTag6.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,20 @@ function add2(a, b) { return a + b; }
2121
// TODO: Should be an error since signature doesn't match.
2222
/** @type {(a: number, b: number, c: number) => number} */
2323
function add3(a, b) { return a + b; }
24+
25+
// Confirm initializers are compatible.
26+
// They can't have more parameters than the type/context.
27+
28+
/** @type {() => void} */
29+
function funcWithMoreParameters(more) {} // error
30+
31+
/** @type {() => void} */
32+
const variableWithMoreParameters = function (more) {}; // error
33+
34+
/** @type {() => void} */
35+
const arrowWithMoreParameters = (more) => {}; // error
36+
37+
({
38+
/** @type {() => void} */
39+
methodWithMoreParameters(more) {}, // error
40+
});

tests/cases/conformance/jsdoc/checkJsdocTypeTag7.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,7 @@
1010
class C {
1111
/** @type {Foo} */
1212
foo(a, b) {}
13+
14+
/** @type {(optional?) => void} */
15+
methodWithOptionalParameters() {}
1316
}

0 commit comments

Comments
 (0)