Stricter test that JSDoc @type tag matches function signature (#25615)

This commit is contained in:
Andy
2018-07-12 14:02:02 -07:00
committed by GitHub
parent 6a2ffec53d
commit f500289a44
7 changed files with 94 additions and 20 deletions

View File

@@ -4682,13 +4682,7 @@ namespace ts {
}
}
// Use contextual parameter type if one is available
let type: Type | undefined;
if (declaration.symbol.escapedName === "this") {
type = getContextualThisParameterType(func);
}
else {
type = getContextuallyTypedParameterType(declaration);
}
const type = declaration.symbol.escapedName === "this" ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration);
if (type) {
return addOptionality(type, isOptional);
}
@@ -16209,7 +16203,7 @@ namespace ts {
// If the given type is an object or union type with a single signature, and if that signature has at
// least as many parameters as the given function, return the signature. Otherwise return undefined.
function getContextualCallSignature(type: Type, node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature | undefined {
function getContextualCallSignature(type: Type, node: SignatureDeclaration): Signature | undefined {
const signatures = getSignaturesOfType(type, SignatureKind.Call);
if (signatures.length === 1) {
const signature = signatures[0];
@@ -16220,7 +16214,7 @@ namespace ts {
}
/** If the contextual signature has fewer parameters than the function expression, do not use it */
function isAritySmaller(signature: Signature, target: FunctionExpression | ArrowFunction | MethodDeclaration) {
function isAritySmaller(signature: Signature, target: SignatureDeclaration) {
let targetParameterCount = 0;
for (; targetParameterCount < target.parameters.length; targetParameterCount++) {
const param = target.parameters[targetParameterCount];
@@ -23538,12 +23532,13 @@ namespace ts {
// yielded values. The only way to trigger these errors is to try checking its return type.
getReturnTypeOfSignature(getSignatureFromDeclaration(node));
}
// A js function declaration can have a @type tag instead of a return type node, but that type must have a call signature
if (isInJavaScriptFile(node)) {
const typeTag = getJSDocTypeTag(node);
if (typeTag && typeTag.typeExpression && !getSignaturesOfType(getTypeFromTypeNode(typeTag.typeExpression), SignatureKind.Call).length) {
error(typeTag, Diagnostics.The_type_of_a_function_declaration_must_be_callable);
}
}
// A js function declaration can have a @type tag instead of a return type node, but that type must have a call signature
if (isInJavaScriptFile(node)) {
const typeTag = getJSDocTypeTag(node);
if (typeTag && typeTag.typeExpression && !getContextualCallSignature(getTypeFromTypeNode(typeTag.typeExpression), node)) {
error(typeTag, Diagnostics.The_type_of_a_function_declaration_must_match_the_function_s_signature);
}
}
}

View File

@@ -4021,7 +4021,7 @@
"category": "Error",
"code": 8029
},
"The type of a function declaration must be callable.": {
"The type of a function declaration must match the function's signature.": {
"category": "Error",
"code": 8030
},

View File

@@ -4,11 +4,12 @@ tests/cases/conformance/jsdoc/test.js(7,24): error TS2322: Type 'number' is not
tests/cases/conformance/jsdoc/test.js(10,17): error TS2322: Type 'number' is not assignable to type 'string'.
tests/cases/conformance/jsdoc/test.js(12,14): error TS2322: Type 'number' is not assignable to type 'string'.
tests/cases/conformance/jsdoc/test.js(14,24): error TS2322: Type 'number' is not assignable to type 'string'.
tests/cases/conformance/jsdoc/test.js(28,5): error TS8030: The type of a function declaration must match the function's signature.
tests/cases/conformance/jsdoc/test.js(34,5): error TS2322: Type '1 | 2' is not assignable to type '2 | 3'.
Type '1' is not assignable to type '2 | 3'.
==== tests/cases/conformance/jsdoc/test.js (7 errors) ====
==== tests/cases/conformance/jsdoc/test.js (8 errors) ====
// all 6 should error on return statement/expression
/** @type {(x: number) => string} */
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
/** @typedef {{(s: string): 0 | 1; (b: boolean): 2 | 3 }} Gioconda */
/** @type {Gioconda} */
~~~~~~~~~~~~~~~~
!!! error TS8030: The type of a function declaration must match the function's signature.
function monaLisa(sb) {
return typeof sb === 'string' ? 1 : 2;
}

View File

@@ -1,12 +1,13 @@
tests/cases/conformance/jsdoc/test.js(1,5): error TS8030: The type of a function declaration must be callable.
tests/cases/conformance/jsdoc/test.js(1,5): error TS8030: The type of a function declaration must match the function's signature.
tests/cases/conformance/jsdoc/test.js(7,5): error TS2322: Type '(prop: any) => void' is not assignable to type '{ prop: string; }'.
Property 'prop' is missing in type '(prop: any) => void'.
tests/cases/conformance/jsdoc/test.js(10,5): error TS8030: The type of a function declaration must match the function's signature.
==== tests/cases/conformance/jsdoc/test.js (2 errors) ====
==== tests/cases/conformance/jsdoc/test.js (3 errors) ====
/** @type {number} */
~~~~~~~~~~~~~~
!!! error TS8030: The type of a function declaration must be callable.
!!! error TS8030: The type of a function declaration must match the function's signature.
function f() {
return 1
}
@@ -17,4 +18,16 @@ tests/cases/conformance/jsdoc/test.js(7,5): error TS2322: Type '(prop: any) => v
!!! error TS2322: Type '(prop: any) => void' is not assignable to type '{ prop: string; }'.
!!! error TS2322: Property 'prop' is missing in type '(prop: any) => void'.
}
/** @type {(a: number) => number} */
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS8030: The type of a function declaration must match the function's signature.
function add1(a, b) { return a + b; }
/** @type {(a: number, b: number) => number} */
function add2(a, b) { return a + b; }
// TODO: Should be an error since signature doesn't match.
/** @type {(a: number, b: number, c: number) => number} */
function add3(a, b) { return a + b; }

View File

@@ -12,3 +12,28 @@ var g = function (prop) {
>prop : Symbol(prop, Decl(test.js, 6, 18))
}
/** @type {(a: number) => number} */
function add1(a, b) { return a + b; }
>add1 : Symbol(add1, Decl(test.js, 7, 1))
>a : Symbol(a, Decl(test.js, 10, 14))
>b : Symbol(b, Decl(test.js, 10, 16))
>a : Symbol(a, Decl(test.js, 10, 14))
>b : Symbol(b, Decl(test.js, 10, 16))
/** @type {(a: number, b: number) => number} */
function add2(a, b) { return a + b; }
>add2 : Symbol(add2, Decl(test.js, 10, 37))
>a : Symbol(a, Decl(test.js, 13, 14))
>b : Symbol(b, Decl(test.js, 13, 16))
>a : Symbol(a, Decl(test.js, 13, 14))
>b : Symbol(b, Decl(test.js, 13, 16))
// TODO: Should be an error since signature doesn't match.
/** @type {(a: number, b: number, c: number) => number} */
function add3(a, b) { return a + b; }
>add3 : Symbol(add3, Decl(test.js, 13, 37))
>a : Symbol(a, Decl(test.js, 17, 14))
>b : Symbol(b, Decl(test.js, 17, 16))
>a : Symbol(a, Decl(test.js, 17, 14))
>b : Symbol(b, Decl(test.js, 17, 16))

View File

@@ -14,3 +14,31 @@ var g = function (prop) {
>prop : any
}
/** @type {(a: number) => number} */
function add1(a, b) { return a + b; }
>add1 : (a: any, b: any) => number
>a : any
>b : any
>a + b : any
>a : any
>b : any
/** @type {(a: number, b: number) => number} */
function add2(a, b) { return a + b; }
>add2 : (a: number, b: number) => number
>a : number
>b : number
>a + b : number
>a : number
>b : number
// TODO: Should be an error since signature doesn't match.
/** @type {(a: number, b: number, c: number) => number} */
function add3(a, b) { return a + b; }
>add3 : (a: number, b: number) => number
>a : number
>b : number
>a + b : number
>a : number
>b : number

View File

@@ -11,3 +11,13 @@ function f() {
/** @type {{ prop: string }} */
var g = function (prop) {
}
/** @type {(a: number) => number} */
function add1(a, b) { return a + b; }
/** @type {(a: number, b: number) => number} */
function add2(a, b) { return a + b; }
// TODO: Should be an error since signature doesn't match.
/** @type {(a: number, b: number, c: number) => number} */
function add3(a, b) { return a + b; }