diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bbd38ddbc5ded..b7b9b6c38040b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6178,24 +6178,16 @@ namespace ts { return undefined; } - function getTypeParametersFromJSDocTemplate(declaration: SignatureDeclaration): TypeParameter[] { - if (declaration.flags & NodeFlags.JavaScriptFile) { - const templateTag = getJSDocTemplateTag(declaration); - if (templateTag) { - return getTypeParametersFromDeclaration(templateTag.typeParameters); - } - } - - return undefined; - } - // Return list of type parameters with duplicates removed (duplicate identifier errors are generated in the actual // type checking functions). - function getTypeParametersFromDeclaration(typeParameterDeclarations: TypeParameterDeclaration[]): TypeParameter[] { - const result: TypeParameter[] = []; - forEach(typeParameterDeclarations, node => { + function getTypeParametersFromDeclaration(declaration: DeclarationWithTypeParameters): TypeParameter[] { + let result: TypeParameter[]; + forEach(getEffectiveTypeParameterDeclarations(declaration), node => { const tp = getDeclaredTypeOfTypeParameter(node.symbol); if (!contains(result, tp)) { + if (!result) { + result = []; + } result.push(tp); } }); @@ -6391,9 +6383,7 @@ namespace ts { const classType = declaration.kind === SyntaxKind.Constructor ? getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent).symbol)) : undefined; - const typeParameters = classType ? classType.localTypeParameters : - declaration.typeParameters ? getTypeParametersFromDeclaration(declaration.typeParameters) : - getTypeParametersFromJSDocTemplate(declaration); + const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration); const returnType = getSignatureReturnTypeFromDeclaration(declaration, isJSConstructSignature, classType); const typePredicate = declaration.type && declaration.type.kind === SyntaxKind.TypePredicate ? createTypePredicateFromTypePredicateNode(declaration.type as TypePredicateNode) : @@ -8167,9 +8157,9 @@ namespace ts { case SyntaxKind.ClassExpression: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.TypeAliasDeclaration: - const declaration = node as DeclarationWithTypeParameters; - if (declaration.typeParameters) { - for (const d of declaration.typeParameters) { + const typeParameters = getEffectiveTypeParameterDeclarations(node as DeclarationWithTypeParameters); + if (typeParameters) { + for (const d of typeParameters) { if (contains(mappedTypes, getDeclaredTypeOfTypeParameter(getSymbolOfNode(d)))) { return true; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index cee2e3626adb3..7b7fb92ccc5b4 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1448,7 +1448,7 @@ namespace ts { return node && firstOrUndefined(getJSDocTags(node, kind)); } - export function getJSDocs(node: Node): (JSDoc | JSDocTag)[] { + export function getJSDocs(node: Node): (JSDoc | JSDocTag)[] { if (isJSDocTypedefTag(node)) { return [node.parent]; } @@ -2743,6 +2743,20 @@ namespace ts { } } + /** + * Gets the effective type parameters. If the node was parsed in a + * JavaScript file, gets the type parameters from the `@template` tag from JSDoc. + */ + export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): TypeParameterDeclaration[] { + if (node.typeParameters) { + return node.typeParameters; + } + if (node.flags & NodeFlags.JavaScriptFile) { + const templateTag = getJSDocTemplateTag(node); + return templateTag && templateTag.typeParameters; + } + } + /** * Gets the effective type annotation of the value parameter of a set accessor. If the node * was parsed in a JavaScript file, gets the type annotation from JSDoc. diff --git a/tests/baselines/reference/jsdocTemplateTag.symbols b/tests/baselines/reference/jsdocTemplateTag.symbols new file mode 100644 index 0000000000000..699e93339edbc --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTag.symbols @@ -0,0 +1,36 @@ +=== tests/cases/conformance/jsdoc/jsdocTemplateTag.ts === +/** + * @param {T} a + * @template T + */ +function f(a: T) { +>f : Symbol(f, Decl(jsdocTemplateTag.ts, 0, 0)) +>T : Symbol(T, Decl(jsdocTemplateTag.ts, 4, 11)) +>a : Symbol(a, Decl(jsdocTemplateTag.ts, 4, 14)) +>T : Symbol(T, Decl(jsdocTemplateTag.ts, 4, 11)) + + return () => a +>a : Symbol(a, Decl(jsdocTemplateTag.ts, 4, 14)) +} +let n = f(1)() +>n : Symbol(n, Decl(jsdocTemplateTag.ts, 7, 3)) +>f : Symbol(f, Decl(jsdocTemplateTag.ts, 0, 0)) + +/** + * @param {T} a + * @template T + * @returns {function(): T} + */ +function g(a: T) { +>g : Symbol(g, Decl(jsdocTemplateTag.ts, 7, 14)) +>T : Symbol(T, Decl(jsdocTemplateTag.ts, 14, 11)) +>a : Symbol(a, Decl(jsdocTemplateTag.ts, 14, 14)) +>T : Symbol(T, Decl(jsdocTemplateTag.ts, 14, 11)) + + return () => a +>a : Symbol(a, Decl(jsdocTemplateTag.ts, 14, 14)) +} +let s = g('hi')() +>s : Symbol(s, Decl(jsdocTemplateTag.ts, 17, 3)) +>g : Symbol(g, Decl(jsdocTemplateTag.ts, 7, 14)) + diff --git a/tests/baselines/reference/jsdocTemplateTag.types b/tests/baselines/reference/jsdocTemplateTag.types new file mode 100644 index 0000000000000..72b0529d97efe --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTag.types @@ -0,0 +1,44 @@ +=== tests/cases/conformance/jsdoc/jsdocTemplateTag.ts === +/** + * @param {T} a + * @template T + */ +function f(a: T) { +>f : (a: T) => () => T +>T : T +>a : T +>T : T + + return () => a +>() => a : () => T +>a : T +} +let n = f(1)() +>n : number +>f(1)() : number +>f(1) : () => number +>f : (a: T) => () => T +>1 : 1 + +/** + * @param {T} a + * @template T + * @returns {function(): T} + */ +function g(a: T) { +>g : (a: T) => () => T +>T : T +>a : T +>T : T + + return () => a +>() => a : () => T +>a : T +} +let s = g('hi')() +>s : string +>g('hi')() : string +>g('hi') : () => string +>g : (a: T) => () => T +>'hi' : "hi" + diff --git a/tests/cases/conformance/jsdoc/jsdocTemplateTag.ts b/tests/cases/conformance/jsdoc/jsdocTemplateTag.ts new file mode 100644 index 0000000000000..42b21cf935bf7 --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocTemplateTag.ts @@ -0,0 +1,21 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +/** + * @param {T} a + * @template T + */ +function f(a: T) { + return () => a +} +let n = f(1)() + +/** + * @param {T} a + * @template T + * @returns {function(): T} + */ +function g(a: T) { + return () => a +} +let s = g('hi')()