Skip to content

Commit d5c3015

Browse files
authored
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) { } ```
1 parent 2cf5afd commit d5c3015

File tree

5 files changed

+212
-8
lines changed

5 files changed

+212
-8
lines changed

src/compiler/checker.ts

+13-7
Original file line numberDiff line numberDiff line change
@@ -1833,6 +1833,8 @@ namespace ts {
18331833
// type parameters are visible in parameter list, return type and type parameter list
18341834
? lastLocation === (location as FunctionLikeDeclaration).type ||
18351835
lastLocation.kind === SyntaxKind.Parameter ||
1836+
lastLocation.kind === SyntaxKind.JSDocParameterTag ||
1837+
lastLocation.kind === SyntaxKind.JSDocReturnTag ||
18361838
lastLocation.kind === SyntaxKind.TypeParameter
18371839
// local types not visible outside the function body
18381840
: false;
@@ -2101,8 +2103,8 @@ namespace ts {
21012103
lastSelfReferenceLocation = location;
21022104
}
21032105
lastLocation = location;
2104-
location = isJSDocTemplateTag(location) ?
2105-
getEffectiveContainerForJSDocTemplateTag(location) || location.parent :
2106+
location = isJSDocTemplateTag(location) ? getEffectiveContainerForJSDocTemplateTag(location) || location.parent :
2107+
isJSDocParameterTag(location) || isJSDocReturnTag(location) ? getHostSignatureFromJSDoc(location) || location.parent :
21062108
location.parent;
21072109
}
21082110

@@ -3374,16 +3376,20 @@ namespace ts {
33743376
return;
33753377
}
33763378
const host = getJSDocHost(node);
3377-
if (host &&
3378-
isExpressionStatement(host) &&
3379-
isBinaryExpression(host.expression) &&
3380-
getAssignmentDeclarationKind(host.expression) === AssignmentDeclarationKind.PrototypeProperty) {
3381-
// X.prototype.m = /** @param {K} p */ function () { } <-- look for K on X's declaration
3379+
if (host && isExpressionStatement(host) && isPrototypePropertyAssignment(host.expression)) {
3380+
// /** @param {K} p */ X.prototype.m = function () { } <-- look for K on X's declaration
33823381
const symbol = getSymbolOfNode(host.expression.left);
33833382
if (symbol) {
33843383
return getDeclarationOfJSPrototypeContainer(symbol);
33853384
}
33863385
}
3386+
if (host && isFunctionExpression(host) && isPrototypePropertyAssignment(host.parent) && isExpressionStatement(host.parent.parent)) {
3387+
// X.prototype.m = /** @param {K} p */ function () { } <-- look for K on X's declaration
3388+
const symbol = getSymbolOfNode(host.parent.left);
3389+
if (symbol) {
3390+
return getDeclarationOfJSPrototypeContainer(symbol);
3391+
}
3392+
}
33873393
if (host && (isObjectLiteralMethod(host) || isPropertyAssignment(host)) &&
33883394
isBinaryExpression(host.parent.parent) &&
33893395
getAssignmentDeclarationKind(host.parent.parent) === AssignmentDeclarationKind.Prototype) {

src/compiler/utilities.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2510,7 +2510,7 @@ namespace ts {
25102510
return expr.right;
25112511
}
25122512

2513-
export function isPrototypePropertyAssignment(node: Node): boolean {
2513+
export function isPrototypePropertyAssignment(node: Node): node is BinaryExpression {
25142514
return isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.PrototypeProperty;
25152515
}
25162516

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
=== tests/cases/conformance/salsa/constructorFunctionMethodTypeParameters.js ===
2+
/**
3+
* @template {string} T
4+
* @param {T} t
5+
*/
6+
function Cls(t) {
7+
>Cls : Symbol(Cls, Decl(constructorFunctionMethodTypeParameters.js, 0, 0))
8+
>t : Symbol(t, Decl(constructorFunctionMethodTypeParameters.js, 4, 13))
9+
10+
this.t = t;
11+
>this.t : Symbol(Cls.t, Decl(constructorFunctionMethodTypeParameters.js, 4, 17))
12+
>this : Symbol(Cls, Decl(constructorFunctionMethodTypeParameters.js, 0, 0))
13+
>t : Symbol(Cls.t, Decl(constructorFunctionMethodTypeParameters.js, 4, 17))
14+
>t : Symbol(t, Decl(constructorFunctionMethodTypeParameters.js, 4, 13))
15+
}
16+
17+
/**
18+
* @template {string} V
19+
* @param {T} t
20+
* @param {V} v
21+
* @return {V}
22+
*/
23+
Cls.prototype.topLevelComment = function (t, v) {
24+
>Cls.prototype : Symbol(Cls.topLevelComment, Decl(constructorFunctionMethodTypeParameters.js, 6, 1))
25+
>Cls : Symbol(Cls, Decl(constructorFunctionMethodTypeParameters.js, 0, 0))
26+
>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
27+
>topLevelComment : Symbol(Cls.topLevelComment, Decl(constructorFunctionMethodTypeParameters.js, 6, 1))
28+
>t : Symbol(t, Decl(constructorFunctionMethodTypeParameters.js, 14, 42))
29+
>v : Symbol(v, Decl(constructorFunctionMethodTypeParameters.js, 14, 44))
30+
31+
return v
32+
>v : Symbol(v, Decl(constructorFunctionMethodTypeParameters.js, 14, 44))
33+
34+
};
35+
36+
Cls.prototype.nestedComment =
37+
>Cls.prototype : Symbol(Cls.nestedComment, Decl(constructorFunctionMethodTypeParameters.js, 16, 2))
38+
>Cls : Symbol(Cls, Decl(constructorFunctionMethodTypeParameters.js, 0, 0))
39+
>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
40+
>nestedComment : Symbol(Cls.nestedComment, Decl(constructorFunctionMethodTypeParameters.js, 16, 2))
41+
42+
/**
43+
* @template {string} U
44+
* @param {T} t
45+
* @param {U} u
46+
* @return {T}
47+
*/
48+
function (t, u) {
49+
>t : Symbol(t, Decl(constructorFunctionMethodTypeParameters.js, 25, 14))
50+
>u : Symbol(u, Decl(constructorFunctionMethodTypeParameters.js, 25, 16))
51+
52+
return t
53+
>t : Symbol(t, Decl(constructorFunctionMethodTypeParameters.js, 25, 14))
54+
55+
};
56+
57+
var c = new Cls('a');
58+
>c : Symbol(c, Decl(constructorFunctionMethodTypeParameters.js, 29, 3))
59+
>Cls : Symbol(Cls, Decl(constructorFunctionMethodTypeParameters.js, 0, 0))
60+
61+
const s = c.topLevelComment('a', 'b');
62+
>s : Symbol(s, Decl(constructorFunctionMethodTypeParameters.js, 30, 5))
63+
>c.topLevelComment : Symbol(Cls.topLevelComment, Decl(constructorFunctionMethodTypeParameters.js, 6, 1))
64+
>c : Symbol(c, Decl(constructorFunctionMethodTypeParameters.js, 29, 3))
65+
>topLevelComment : Symbol(Cls.topLevelComment, Decl(constructorFunctionMethodTypeParameters.js, 6, 1))
66+
67+
const t = c.nestedComment('a', 'b');
68+
>t : Symbol(t, Decl(constructorFunctionMethodTypeParameters.js, 31, 5))
69+
>c.nestedComment : Symbol(Cls.nestedComment, Decl(constructorFunctionMethodTypeParameters.js, 16, 2))
70+
>c : Symbol(c, Decl(constructorFunctionMethodTypeParameters.js, 29, 3))
71+
>nestedComment : Symbol(Cls.nestedComment, Decl(constructorFunctionMethodTypeParameters.js, 16, 2))
72+
73+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
=== tests/cases/conformance/salsa/constructorFunctionMethodTypeParameters.js ===
2+
/**
3+
* @template {string} T
4+
* @param {T} t
5+
*/
6+
function Cls(t) {
7+
>Cls : typeof Cls
8+
>t : T
9+
10+
this.t = t;
11+
>this.t = t : T
12+
>this.t : any
13+
>this : this
14+
>t : any
15+
>t : T
16+
}
17+
18+
/**
19+
* @template {string} V
20+
* @param {T} t
21+
* @param {V} v
22+
* @return {V}
23+
*/
24+
Cls.prototype.topLevelComment = function (t, v) {
25+
>Cls.prototype.topLevelComment = function (t, v) { return v} : <V extends string>(t: T, v: V) => V
26+
>Cls.prototype.topLevelComment : any
27+
>Cls.prototype : any
28+
>Cls : typeof Cls
29+
>prototype : any
30+
>topLevelComment : any
31+
>function (t, v) { return v} : <V extends string>(t: T, v: V) => V
32+
>t : T
33+
>v : V
34+
35+
return v
36+
>v : V
37+
38+
};
39+
40+
Cls.prototype.nestedComment =
41+
>Cls.prototype.nestedComment = /** * @template {string} U * @param {T} t * @param {U} u * @return {T} */ function (t, u) { return t } : <U extends string>(t: T, u: U) => T
42+
>Cls.prototype.nestedComment : any
43+
>Cls.prototype : any
44+
>Cls : typeof Cls
45+
>prototype : any
46+
>nestedComment : any
47+
48+
/**
49+
* @template {string} U
50+
* @param {T} t
51+
* @param {U} u
52+
* @return {T}
53+
*/
54+
function (t, u) {
55+
>function (t, u) { return t } : <U extends string>(t: T, u: U) => T
56+
>t : T
57+
>u : U
58+
59+
return t
60+
>t : T
61+
62+
};
63+
64+
var c = new Cls('a');
65+
>c : Cls<"a">
66+
>new Cls('a') : Cls<"a">
67+
>Cls : typeof Cls
68+
>'a' : "a"
69+
70+
const s = c.topLevelComment('a', 'b');
71+
>s : "b"
72+
>c.topLevelComment('a', 'b') : "b"
73+
>c.topLevelComment : <V extends string>(t: "a", v: V) => V
74+
>c : Cls<"a">
75+
>topLevelComment : <V extends string>(t: "a", v: V) => V
76+
>'a' : "a"
77+
>'b' : "b"
78+
79+
const t = c.nestedComment('a', 'b');
80+
>t : "a"
81+
>c.nestedComment('a', 'b') : "a"
82+
>c.nestedComment : <U extends string>(t: "a", u: U) => "a"
83+
>c : Cls<"a">
84+
>nestedComment : <U extends string>(t: "a", u: U) => "a"
85+
>'a' : "a"
86+
>'b' : "b"
87+
88+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// @noEmit: true
2+
// @allowJs: true
3+
// @checkJs: true
4+
// @filename: constructorFunctionMethodTypeParameters.js
5+
/**
6+
* @template {string} T
7+
* @param {T} t
8+
*/
9+
function Cls(t) {
10+
this.t = t;
11+
}
12+
13+
/**
14+
* @template {string} V
15+
* @param {T} t
16+
* @param {V} v
17+
* @return {V}
18+
*/
19+
Cls.prototype.topLevelComment = function (t, v) {
20+
return v
21+
};
22+
23+
Cls.prototype.nestedComment =
24+
/**
25+
* @template {string} U
26+
* @param {T} t
27+
* @param {U} u
28+
* @return {T}
29+
*/
30+
function (t, u) {
31+
return t
32+
};
33+
34+
var c = new Cls('a');
35+
const s = c.topLevelComment('a', 'b');
36+
const t = c.nestedComment('a', 'b');
37+

0 commit comments

Comments
 (0)