Skip to content

Commit f500289

Browse files
author
Andy
authored
Stricter test that JSDoc @type tag matches function signature (microsoft#25615)
1 parent 6a2ffec commit f500289

File tree

7 files changed

+94
-20
lines changed

7 files changed

+94
-20
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4682,13 +4682,7 @@ namespace ts {
46824682
}
46834683
}
46844684
// Use contextual parameter type if one is available
4685-
let type: Type | undefined;
4686-
if (declaration.symbol.escapedName === "this") {
4687-
type = getContextualThisParameterType(func);
4688-
}
4689-
else {
4690-
type = getContextuallyTypedParameterType(declaration);
4691-
}
4685+
const type = declaration.symbol.escapedName === "this" ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration);
46924686
if (type) {
46934687
return addOptionality(type, isOptional);
46944688
}
@@ -16209,7 +16203,7 @@ namespace ts {
1620916203

1621016204
// If the given type is an object or union type with a single signature, and if that signature has at
1621116205
// least as many parameters as the given function, return the signature. Otherwise return undefined.
16212-
function getContextualCallSignature(type: Type, node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature | undefined {
16206+
function getContextualCallSignature(type: Type, node: SignatureDeclaration): Signature | undefined {
1621316207
const signatures = getSignaturesOfType(type, SignatureKind.Call);
1621416208
if (signatures.length === 1) {
1621516209
const signature = signatures[0];
@@ -16220,7 +16214,7 @@ namespace ts {
1622016214
}
1622116215

1622216216
/** If the contextual signature has fewer parameters than the function expression, do not use it */
16223-
function isAritySmaller(signature: Signature, target: FunctionExpression | ArrowFunction | MethodDeclaration) {
16217+
function isAritySmaller(signature: Signature, target: SignatureDeclaration) {
1622416218
let targetParameterCount = 0;
1622516219
for (; targetParameterCount < target.parameters.length; targetParameterCount++) {
1622616220
const param = target.parameters[targetParameterCount];
@@ -23538,12 +23532,13 @@ namespace ts {
2353823532
// yielded values. The only way to trigger these errors is to try checking its return type.
2353923533
getReturnTypeOfSignature(getSignatureFromDeclaration(node));
2354023534
}
23541-
// A js function declaration can have a @type tag instead of a return type node, but that type must have a call signature
23542-
if (isInJavaScriptFile(node)) {
23543-
const typeTag = getJSDocTypeTag(node);
23544-
if (typeTag && typeTag.typeExpression && !getSignaturesOfType(getTypeFromTypeNode(typeTag.typeExpression), SignatureKind.Call).length) {
23545-
error(typeTag, Diagnostics.The_type_of_a_function_declaration_must_be_callable);
23546-
}
23535+
}
23536+
23537+
// A js function declaration can have a @type tag instead of a return type node, but that type must have a call signature
23538+
if (isInJavaScriptFile(node)) {
23539+
const typeTag = getJSDocTypeTag(node);
23540+
if (typeTag && typeTag.typeExpression && !getContextualCallSignature(getTypeFromTypeNode(typeTag.typeExpression), node)) {
23541+
error(typeTag, Diagnostics.The_type_of_a_function_declaration_must_match_the_function_s_signature);
2354723542
}
2354823543
}
2354923544
}

src/compiler/diagnosticMessages.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4021,7 +4021,7 @@
40214021
"category": "Error",
40224022
"code": 8029
40234023
},
4024-
"The type of a function declaration must be callable.": {
4024+
"The type of a function declaration must match the function's signature.": {
40254025
"category": "Error",
40264026
"code": 8030
40274027
},

tests/baselines/reference/checkJsdocTypeTag5.errors.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ tests/cases/conformance/jsdoc/test.js(7,24): error TS2322: Type 'number' is not
44
tests/cases/conformance/jsdoc/test.js(10,17): error TS2322: Type 'number' is not assignable to type 'string'.
55
tests/cases/conformance/jsdoc/test.js(12,14): error TS2322: Type 'number' is not assignable to type 'string'.
66
tests/cases/conformance/jsdoc/test.js(14,24): error TS2322: Type 'number' is not assignable to type 'string'.
7+
tests/cases/conformance/jsdoc/test.js(28,5): error TS8030: The type of a function declaration must match the function's signature.
78
tests/cases/conformance/jsdoc/test.js(34,5): error TS2322: Type '1 | 2' is not assignable to type '2 | 3'.
89
Type '1' is not assignable to type '2 | 3'.
910

1011

11-
==== tests/cases/conformance/jsdoc/test.js (7 errors) ====
12+
==== tests/cases/conformance/jsdoc/test.js (8 errors) ====
1213
// all 6 should error on return statement/expression
1314
/** @type {(x: number) => string} */
1415
function h(x) { return x }
@@ -49,6 +50,8 @@ tests/cases/conformance/jsdoc/test.js(34,5): error TS2322: Type '1 | 2' is not a
4950
/** @typedef {{(s: string): 0 | 1; (b: boolean): 2 | 3 }} Gioconda */
5051

5152
/** @type {Gioconda} */
53+
~~~~~~~~~~~~~~~~
54+
!!! error TS8030: The type of a function declaration must match the function's signature.
5255
function monaLisa(sb) {
5356
return typeof sb === 'string' ? 1 : 2;
5457
}
Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
tests/cases/conformance/jsdoc/test.js(1,5): error TS8030: The type of a function declaration must be callable.
1+
tests/cases/conformance/jsdoc/test.js(1,5): 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
Property 'prop' is missing in type '(prop: any) => void'.
4+
tests/cases/conformance/jsdoc/test.js(10,5): error TS8030: The type of a function declaration must match the function's signature.
45

56

6-
==== tests/cases/conformance/jsdoc/test.js (2 errors) ====
7+
==== tests/cases/conformance/jsdoc/test.js (3 errors) ====
78
/** @type {number} */
89
~~~~~~~~~~~~~~
9-
!!! error TS8030: The type of a function declaration must be callable.
10+
!!! error TS8030: The type of a function declaration must match the function's signature.
1011
function f() {
1112
return 1
1213
}
@@ -17,4 +18,16 @@ tests/cases/conformance/jsdoc/test.js(7,5): error TS2322: Type '(prop: any) => v
1718
!!! error TS2322: Type '(prop: any) => void' is not assignable to type '{ prop: string; }'.
1819
!!! error TS2322: Property 'prop' is missing in type '(prop: any) => void'.
1920
}
21+
22+
/** @type {(a: number) => number} */
23+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
24+
!!! error TS8030: The type of a function declaration must match the function's signature.
25+
function add1(a, b) { return a + b; }
26+
27+
/** @type {(a: number, b: number) => number} */
28+
function add2(a, b) { return a + b; }
29+
30+
// TODO: Should be an error since signature doesn't match.
31+
/** @type {(a: number, b: number, c: number) => number} */
32+
function add3(a, b) { return a + b; }
2033

tests/baselines/reference/checkJsdocTypeTag6.symbols

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,28 @@ var g = function (prop) {
1212
>prop : Symbol(prop, Decl(test.js, 6, 18))
1313
}
1414

15+
/** @type {(a: number) => number} */
16+
function add1(a, b) { return a + b; }
17+
>add1 : Symbol(add1, Decl(test.js, 7, 1))
18+
>a : Symbol(a, Decl(test.js, 10, 14))
19+
>b : Symbol(b, Decl(test.js, 10, 16))
20+
>a : Symbol(a, Decl(test.js, 10, 14))
21+
>b : Symbol(b, Decl(test.js, 10, 16))
22+
23+
/** @type {(a: number, b: number) => number} */
24+
function add2(a, b) { return a + b; }
25+
>add2 : Symbol(add2, Decl(test.js, 10, 37))
26+
>a : Symbol(a, Decl(test.js, 13, 14))
27+
>b : Symbol(b, Decl(test.js, 13, 16))
28+
>a : Symbol(a, Decl(test.js, 13, 14))
29+
>b : Symbol(b, Decl(test.js, 13, 16))
30+
31+
// TODO: Should be an error since signature doesn't match.
32+
/** @type {(a: number, b: number, c: number) => number} */
33+
function add3(a, b) { return a + b; }
34+
>add3 : Symbol(add3, Decl(test.js, 13, 37))
35+
>a : Symbol(a, Decl(test.js, 17, 14))
36+
>b : Symbol(b, Decl(test.js, 17, 16))
37+
>a : Symbol(a, Decl(test.js, 17, 14))
38+
>b : Symbol(b, Decl(test.js, 17, 16))
39+

tests/baselines/reference/checkJsdocTypeTag6.types

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,31 @@ var g = function (prop) {
1414
>prop : any
1515
}
1616

17+
/** @type {(a: number) => number} */
18+
function add1(a, b) { return a + b; }
19+
>add1 : (a: any, b: any) => number
20+
>a : any
21+
>b : any
22+
>a + b : any
23+
>a : any
24+
>b : any
25+
26+
/** @type {(a: number, b: number) => number} */
27+
function add2(a, b) { return a + b; }
28+
>add2 : (a: number, b: number) => number
29+
>a : number
30+
>b : number
31+
>a + b : number
32+
>a : number
33+
>b : number
34+
35+
// TODO: Should be an error since signature doesn't match.
36+
/** @type {(a: number, b: number, c: number) => number} */
37+
function add3(a, b) { return a + b; }
38+
>add3 : (a: number, b: number) => number
39+
>a : number
40+
>b : number
41+
>a + b : number
42+
>a : number
43+
>b : number
44+

tests/cases/conformance/jsdoc/checkJsdocTypeTag6.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,13 @@ function f() {
1111
/** @type {{ prop: string }} */
1212
var g = function (prop) {
1313
}
14+
15+
/** @type {(a: number) => number} */
16+
function add1(a, b) { return a + b; }
17+
18+
/** @type {(a: number, b: number) => number} */
19+
function add2(a, b) { return a + b; }
20+
21+
// TODO: Should be an error since signature doesn't match.
22+
/** @type {(a: number, b: number, c: number) => number} */
23+
function add3(a, b) { return a + b; }

0 commit comments

Comments
 (0)