From d5c3015516a1a3423e2b71433a4e9cff9d750bb5 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 9 Feb 2022 11:22:33 -0800 Subject: [PATCH] Constructor function methods:Add two missing tag lookups (#47742) 1. During name resolution, `@param` and `@return` tags should walk up through the jsdoc comment and then jump to the host function. Previously they did not, which would cause them to not resolve type parameters bound in the scope of a host that was not a sibling of the comment. The example from #46618 is a prototype method: ```js /** * @template {T} * @param {T} t */ C.prototype.m = function (t) { } ``` 2. During name resolution, prototype methods are supposed to resolve types both from the host function's location and from the containing class' location. The containing class lookup happens in a separate call to `resolveName`. Previously, the code that finds the containing class only worked for the above style of comment, which is on the outer ExpressionStatement, but not for the below style, which is on the function expression itself: ```js C.prototype.m = /** * @template {T} * @param {T} t */ function (t) { } ``` --- src/compiler/checker.ts | 20 +++-- src/compiler/utilities.ts | 2 +- ...ructorFunctionMethodTypeParameters.symbols | 73 +++++++++++++++ ...structorFunctionMethodTypeParameters.types | 88 +++++++++++++++++++ ...constructorFunctionMethodTypeParameters.ts | 37 ++++++++ 5 files changed, 212 insertions(+), 8 deletions(-) create mode 100644 tests/baselines/reference/constructorFunctionMethodTypeParameters.symbols create mode 100644 tests/baselines/reference/constructorFunctionMethodTypeParameters.types create mode 100644 tests/cases/conformance/salsa/constructorFunctionMethodTypeParameters.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e218766a708..dae973d6346 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1833,6 +1833,8 @@ namespace ts { // type parameters are visible in parameter list, return type and type parameter list ? lastLocation === (location as FunctionLikeDeclaration).type || lastLocation.kind === SyntaxKind.Parameter || + lastLocation.kind === SyntaxKind.JSDocParameterTag || + lastLocation.kind === SyntaxKind.JSDocReturnTag || lastLocation.kind === SyntaxKind.TypeParameter // local types not visible outside the function body : false; @@ -2101,8 +2103,8 @@ namespace ts { lastSelfReferenceLocation = location; } lastLocation = location; - location = isJSDocTemplateTag(location) ? - getEffectiveContainerForJSDocTemplateTag(location) || location.parent : + location = isJSDocTemplateTag(location) ? getEffectiveContainerForJSDocTemplateTag(location) || location.parent : + isJSDocParameterTag(location) || isJSDocReturnTag(location) ? getHostSignatureFromJSDoc(location) || location.parent : location.parent; } @@ -3374,16 +3376,20 @@ namespace ts { return; } const host = getJSDocHost(node); - if (host && - isExpressionStatement(host) && - isBinaryExpression(host.expression) && - getAssignmentDeclarationKind(host.expression) === AssignmentDeclarationKind.PrototypeProperty) { - // X.prototype.m = /** @param {K} p */ function () { } <-- look for K on X's declaration + if (host && isExpressionStatement(host) && isPrototypePropertyAssignment(host.expression)) { + // /** @param {K} p */ X.prototype.m = function () { } <-- look for K on X's declaration const symbol = getSymbolOfNode(host.expression.left); if (symbol) { return getDeclarationOfJSPrototypeContainer(symbol); } } + if (host && isFunctionExpression(host) && isPrototypePropertyAssignment(host.parent) && isExpressionStatement(host.parent.parent)) { + // X.prototype.m = /** @param {K} p */ function () { } <-- look for K on X's declaration + const symbol = getSymbolOfNode(host.parent.left); + if (symbol) { + return getDeclarationOfJSPrototypeContainer(symbol); + } + } if (host && (isObjectLiteralMethod(host) || isPropertyAssignment(host)) && isBinaryExpression(host.parent.parent) && getAssignmentDeclarationKind(host.parent.parent) === AssignmentDeclarationKind.Prototype) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index c723305b84b..cabcb2f9b6c 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2510,7 +2510,7 @@ namespace ts { return expr.right; } - export function isPrototypePropertyAssignment(node: Node): boolean { + export function isPrototypePropertyAssignment(node: Node): node is BinaryExpression { return isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.PrototypeProperty; } diff --git a/tests/baselines/reference/constructorFunctionMethodTypeParameters.symbols b/tests/baselines/reference/constructorFunctionMethodTypeParameters.symbols new file mode 100644 index 00000000000..7af38bcb4cf --- /dev/null +++ b/tests/baselines/reference/constructorFunctionMethodTypeParameters.symbols @@ -0,0 +1,73 @@ +=== tests/cases/conformance/salsa/constructorFunctionMethodTypeParameters.js === +/** + * @template {string} T + * @param {T} t + */ +function Cls(t) { +>Cls : Symbol(Cls, Decl(constructorFunctionMethodTypeParameters.js, 0, 0)) +>t : Symbol(t, Decl(constructorFunctionMethodTypeParameters.js, 4, 13)) + + this.t = t; +>this.t : Symbol(Cls.t, Decl(constructorFunctionMethodTypeParameters.js, 4, 17)) +>this : Symbol(Cls, Decl(constructorFunctionMethodTypeParameters.js, 0, 0)) +>t : Symbol(Cls.t, Decl(constructorFunctionMethodTypeParameters.js, 4, 17)) +>t : Symbol(t, Decl(constructorFunctionMethodTypeParameters.js, 4, 13)) +} + +/** + * @template {string} V + * @param {T} t + * @param {V} v + * @return {V} + */ +Cls.prototype.topLevelComment = function (t, v) { +>Cls.prototype : Symbol(Cls.topLevelComment, Decl(constructorFunctionMethodTypeParameters.js, 6, 1)) +>Cls : Symbol(Cls, Decl(constructorFunctionMethodTypeParameters.js, 0, 0)) +>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) +>topLevelComment : Symbol(Cls.topLevelComment, Decl(constructorFunctionMethodTypeParameters.js, 6, 1)) +>t : Symbol(t, Decl(constructorFunctionMethodTypeParameters.js, 14, 42)) +>v : Symbol(v, Decl(constructorFunctionMethodTypeParameters.js, 14, 44)) + + return v +>v : Symbol(v, Decl(constructorFunctionMethodTypeParameters.js, 14, 44)) + +}; + +Cls.prototype.nestedComment = +>Cls.prototype : Symbol(Cls.nestedComment, Decl(constructorFunctionMethodTypeParameters.js, 16, 2)) +>Cls : Symbol(Cls, Decl(constructorFunctionMethodTypeParameters.js, 0, 0)) +>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) +>nestedComment : Symbol(Cls.nestedComment, Decl(constructorFunctionMethodTypeParameters.js, 16, 2)) + + /** + * @template {string} U + * @param {T} t + * @param {U} u + * @return {T} + */ + function (t, u) { +>t : Symbol(t, Decl(constructorFunctionMethodTypeParameters.js, 25, 14)) +>u : Symbol(u, Decl(constructorFunctionMethodTypeParameters.js, 25, 16)) + + return t +>t : Symbol(t, Decl(constructorFunctionMethodTypeParameters.js, 25, 14)) + + }; + +var c = new Cls('a'); +>c : Symbol(c, Decl(constructorFunctionMethodTypeParameters.js, 29, 3)) +>Cls : Symbol(Cls, Decl(constructorFunctionMethodTypeParameters.js, 0, 0)) + +const s = c.topLevelComment('a', 'b'); +>s : Symbol(s, Decl(constructorFunctionMethodTypeParameters.js, 30, 5)) +>c.topLevelComment : Symbol(Cls.topLevelComment, Decl(constructorFunctionMethodTypeParameters.js, 6, 1)) +>c : Symbol(c, Decl(constructorFunctionMethodTypeParameters.js, 29, 3)) +>topLevelComment : Symbol(Cls.topLevelComment, Decl(constructorFunctionMethodTypeParameters.js, 6, 1)) + +const t = c.nestedComment('a', 'b'); +>t : Symbol(t, Decl(constructorFunctionMethodTypeParameters.js, 31, 5)) +>c.nestedComment : Symbol(Cls.nestedComment, Decl(constructorFunctionMethodTypeParameters.js, 16, 2)) +>c : Symbol(c, Decl(constructorFunctionMethodTypeParameters.js, 29, 3)) +>nestedComment : Symbol(Cls.nestedComment, Decl(constructorFunctionMethodTypeParameters.js, 16, 2)) + + diff --git a/tests/baselines/reference/constructorFunctionMethodTypeParameters.types b/tests/baselines/reference/constructorFunctionMethodTypeParameters.types new file mode 100644 index 00000000000..707a8922c1a --- /dev/null +++ b/tests/baselines/reference/constructorFunctionMethodTypeParameters.types @@ -0,0 +1,88 @@ +=== tests/cases/conformance/salsa/constructorFunctionMethodTypeParameters.js === +/** + * @template {string} T + * @param {T} t + */ +function Cls(t) { +>Cls : typeof Cls +>t : T + + this.t = t; +>this.t = t : T +>this.t : any +>this : this +>t : any +>t : T +} + +/** + * @template {string} V + * @param {T} t + * @param {V} v + * @return {V} + */ +Cls.prototype.topLevelComment = function (t, v) { +>Cls.prototype.topLevelComment = function (t, v) { return v} : (t: T, v: V) => V +>Cls.prototype.topLevelComment : any +>Cls.prototype : any +>Cls : typeof Cls +>prototype : any +>topLevelComment : any +>function (t, v) { return v} : (t: T, v: V) => V +>t : T +>v : V + + return v +>v : V + +}; + +Cls.prototype.nestedComment = +>Cls.prototype.nestedComment = /** * @template {string} U * @param {T} t * @param {U} u * @return {T} */ function (t, u) { return t } : (t: T, u: U) => T +>Cls.prototype.nestedComment : any +>Cls.prototype : any +>Cls : typeof Cls +>prototype : any +>nestedComment : any + + /** + * @template {string} U + * @param {T} t + * @param {U} u + * @return {T} + */ + function (t, u) { +>function (t, u) { return t } : (t: T, u: U) => T +>t : T +>u : U + + return t +>t : T + + }; + +var c = new Cls('a'); +>c : Cls<"a"> +>new Cls('a') : Cls<"a"> +>Cls : typeof Cls +>'a' : "a" + +const s = c.topLevelComment('a', 'b'); +>s : "b" +>c.topLevelComment('a', 'b') : "b" +>c.topLevelComment : (t: "a", v: V) => V +>c : Cls<"a"> +>topLevelComment : (t: "a", v: V) => V +>'a' : "a" +>'b' : "b" + +const t = c.nestedComment('a', 'b'); +>t : "a" +>c.nestedComment('a', 'b') : "a" +>c.nestedComment : (t: "a", u: U) => "a" +>c : Cls<"a"> +>nestedComment : (t: "a", u: U) => "a" +>'a' : "a" +>'b' : "b" + + diff --git a/tests/cases/conformance/salsa/constructorFunctionMethodTypeParameters.ts b/tests/cases/conformance/salsa/constructorFunctionMethodTypeParameters.ts new file mode 100644 index 00000000000..bbe6810a150 --- /dev/null +++ b/tests/cases/conformance/salsa/constructorFunctionMethodTypeParameters.ts @@ -0,0 +1,37 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @filename: constructorFunctionMethodTypeParameters.js +/** + * @template {string} T + * @param {T} t + */ +function Cls(t) { + this.t = t; +} + +/** + * @template {string} V + * @param {T} t + * @param {V} v + * @return {V} + */ +Cls.prototype.topLevelComment = function (t, v) { + return v +}; + +Cls.prototype.nestedComment = + /** + * @template {string} U + * @param {T} t + * @param {U} u + * @return {T} + */ + function (t, u) { + return t + }; + +var c = new Cls('a'); +const s = c.topLevelComment('a', 'b'); +const t = c.nestedComment('a', 'b'); +