Skip to content

Commit 6450490

Browse files
authored
Fix jsdoc type resolution [merge to master] (#24204)
* Fix JSDoc type resolution Breaks type parameter resolution that is looked up through prototype methods, though. I need to fix that still. * Check for prototype method assignments first * Undo dedupe changes to getJSDocTags * JS Type aliases can't refer to host type params Previously, js type aliases (@typedef and @callback) could refer to type paremeters defined in @template tags in a *different* jsdoc tag, as long as both tags were hosted on the same signature. * Reduce dedupe changes+update baseline The only reason I had undone them was to merge successfully with an older state of master.
1 parent 2b5ff29 commit 6450490

9 files changed

+253
-16
lines changed

src/compiler/checker.ts

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,6 +1453,12 @@ namespace ts {
14531453
location = location.parent;
14541454
}
14551455
break;
1456+
case SyntaxKind.JSDocTypedefTag:
1457+
case SyntaxKind.JSDocCallbackTag:
1458+
// js type aliases do not resolve names from their host, so skip past it
1459+
lastLocation = location;
1460+
location = getJSDocHost(location).parent;
1461+
continue;
14561462
}
14571463
if (isSelfReferenceLocation(location)) {
14581464
lastSelfReferenceLocation = location;
@@ -2153,25 +2159,31 @@ namespace ts {
21532159
*/
21542160
function resolveEntityNameFromJSSpecialAssignment(name: Identifier, meaning: SymbolFlags) {
21552161
if (isJSDocTypeReference(name.parent)) {
2156-
const host = getJSDocHost(name.parent);
2157-
if (host) {
2158-
const secondaryLocation = getJSSpecialAssignmentSymbol(getJSDocHost(name.parent.parent.parent as JSDocTag));
2159-
return secondaryLocation && resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true);
2162+
const secondaryLocation = getJSSpecialAssignmentLocation(name.parent);
2163+
if (secondaryLocation) {
2164+
return resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true);
21602165
}
21612166
}
21622167
}
21632168

2164-
function getJSSpecialAssignmentSymbol(host: HasJSDoc): Declaration | undefined {
2165-
if (isPropertyAssignment(host) && isFunctionLike(host.initializer)) {
2166-
const symbol = getSymbolOfNode(host.initializer);
2167-
return symbol && symbol.valueDeclaration;
2169+
function getJSSpecialAssignmentLocation(node: TypeReferenceNode): Declaration | undefined {
2170+
const typeAlias = findAncestor(node, node => !(isJSDocNode(node) || node.flags & NodeFlags.JSDoc) ? "quit" : isJSDocTypeAlias(node));
2171+
if (typeAlias) {
2172+
return;
21682173
}
2169-
else if (isExpressionStatement(host) &&
2170-
isBinaryExpression(host.expression) &&
2171-
getSpecialPropertyAssignmentKind(host.expression) === SpecialPropertyAssignmentKind.PrototypeProperty) {
2174+
const host = getJSDocHost(node);
2175+
if (host &&
2176+
isExpressionStatement(host) &&
2177+
isBinaryExpression(host.expression) &&
2178+
getSpecialPropertyAssignmentKind(host.expression) === SpecialPropertyAssignmentKind.PrototypeProperty) {
21722179
const symbol = getSymbolOfNode(host.expression.left);
21732180
return symbol && symbol.parent.valueDeclaration;
21742181
}
2182+
const sig = getHostSignatureFromJSDocHost(host);
2183+
if (sig) {
2184+
const symbol = getSymbolOfNode(sig);
2185+
return symbol && symbol.valueDeclaration;
2186+
}
21752187
}
21762188

21772189
function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression): Symbol {
@@ -9554,7 +9566,16 @@ namespace ts {
95549566
// parameters that are in scope (and therefore potentially referenced). For type literals that
95559567
// aren't the right hand side of a generic type alias declaration we optimize by reducing the
95569568
// set of type parameters to those that are possibly referenced in the literal.
9557-
const declaration = symbol.declarations[0];
9569+
let declaration = symbol.declarations[0];
9570+
if (isInJavaScriptFile(declaration)) {
9571+
const paramTag = findAncestor(declaration, isJSDocParameterTag);
9572+
if (paramTag) {
9573+
const paramSymbol = getParameterSymbolFromJSDoc(paramTag);
9574+
if (paramSymbol) {
9575+
declaration = paramSymbol.valueDeclaration;
9576+
}
9577+
}
9578+
}
95589579
let outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true);
95599580
if (isJavaScriptConstructor(declaration)) {
95609581
const templateTagParameters = getTypeParametersFromDeclaration(declaration as DeclarationWithTypeParameters);

src/compiler/utilities.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1865,8 +1865,7 @@ namespace ts {
18651865
// * @returns {number}
18661866
// */
18671867
// var x = function(name) { return name.length; }
1868-
if (parent.parent &&
1869-
(getSingleVariableOfVariableStatement(parent.parent) === node)) {
1868+
if (parent.parent && (getSingleVariableOfVariableStatement(parent.parent) === node)) {
18701869
getJSDocCommentsAndTagsWorker(parent.parent);
18711870
}
18721871
if (parent.parent && parent.parent.parent &&
@@ -1913,8 +1912,11 @@ namespace ts {
19131912
return parameter && parameter.symbol;
19141913
}
19151914

1916-
export function getHostSignatureFromJSDoc(node: JSDocTag): SignatureDeclaration | undefined {
1917-
const host = getJSDocHost(node);
1915+
export function getHostSignatureFromJSDoc(node: Node): SignatureDeclaration | undefined {
1916+
return getHostSignatureFromJSDocHost(getJSDocHost(node));
1917+
}
1918+
1919+
export function getHostSignatureFromJSDocHost(host: HasJSDoc): SignatureDeclaration | undefined {
19181920
const decl = getSourceOfDefaultedAssignment(host) ||
19191921
getSourceOfAssignment(host) ||
19201922
getSingleInitializerOfVariableStatementOrPropertyDeclaration(host) ||
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
=== tests/cases/conformance/jsdoc/main.js ===
2+
var f = require('./first');
3+
>f : Symbol(f, Decl(main.js, 0, 3))
4+
>require : Symbol(require)
5+
>'./first' : Symbol("tests/cases/conformance/jsdoc/first", Decl(first.js, 0, 0))
6+
7+
f(1, n => { })
8+
>f : Symbol(f, Decl(main.js, 0, 3))
9+
>n : Symbol(n, Decl(main.js, 1, 4))
10+
11+
=== tests/cases/conformance/jsdoc/first.js ===
12+
/** @template T
13+
* @param {T} x
14+
* @param {(t: T) => void} k
15+
*/
16+
module.exports = function (x, k) { return k(x) }
17+
>module : Symbol(export=, Decl(first.js, 0, 0))
18+
>exports : Symbol(export=, Decl(first.js, 0, 0))
19+
>x : Symbol(x, Decl(first.js, 4, 27))
20+
>k : Symbol(k, Decl(first.js, 4, 29))
21+
>k : Symbol(k, Decl(first.js, 4, 29))
22+
>x : Symbol(x, Decl(first.js, 4, 27))
23+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
=== tests/cases/conformance/jsdoc/main.js ===
2+
var f = require('./first');
3+
>f : <T>(x: T, k: (t: T) => void) => void
4+
>require('./first') : <T>(x: T, k: (t: T) => void) => void
5+
>require : any
6+
>'./first' : "./first"
7+
8+
f(1, n => { })
9+
>f(1, n => { }) : void
10+
>f : <T>(x: T, k: (t: T) => void) => void
11+
>1 : 1
12+
>n => { } : (n: number) => void
13+
>n : number
14+
15+
=== tests/cases/conformance/jsdoc/first.js ===
16+
/** @template T
17+
* @param {T} x
18+
* @param {(t: T) => void} k
19+
*/
20+
module.exports = function (x, k) { return k(x) }
21+
>module.exports = function (x, k) { return k(x) } : <T>(x: T, k: (t: T) => void) => void
22+
>module.exports : any
23+
>module : any
24+
>exports : any
25+
>function (x, k) { return k(x) } : <T>(x: T, k: (t: T) => void) => void
26+
>x : T
27+
>k : (t: T) => void
28+
>k(x) : void
29+
>k : (t: T) => void
30+
>x : T
31+
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
tests/cases/conformance/jsdoc/github20832.js(2,15): error TS2304: Cannot find name 'U'.
2+
tests/cases/conformance/jsdoc/github20832.js(17,12): error TS2304: Cannot find name 'V'.
3+
4+
5+
==== tests/cases/conformance/jsdoc/github20832.js (2 errors) ====
6+
// #20832
7+
/** @typedef {U} T - should be "error, can't find type named 'U' */
8+
~
9+
!!! error TS2304: Cannot find name 'U'.
10+
/**
11+
* @template U
12+
* @param {U} x
13+
* @return {T}
14+
*/
15+
function f(x) {
16+
return x;
17+
}
18+
19+
/** @type T - should be fine, since T will be any */
20+
const x = 3;
21+
22+
/**
23+
* @callback Cb
24+
* @param {V} firstParam
25+
~
26+
!!! error TS2304: Cannot find name 'V'.
27+
*/
28+
/**
29+
* @template V
30+
* @param {V} vvvvv
31+
*/
32+
function g(vvvvv) {
33+
}
34+
35+
/** @type {Cb} */
36+
const cb = x => {}
37+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
=== tests/cases/conformance/jsdoc/github20832.js ===
2+
// #20832
3+
/** @typedef {U} T - should be "error, can't find type named 'U' */
4+
/**
5+
* @template U
6+
* @param {U} x
7+
* @return {T}
8+
*/
9+
function f(x) {
10+
>f : Symbol(f, Decl(github20832.js, 0, 0))
11+
>x : Symbol(x, Decl(github20832.js, 7, 11))
12+
13+
return x;
14+
>x : Symbol(x, Decl(github20832.js, 7, 11))
15+
}
16+
17+
/** @type T - should be fine, since T will be any */
18+
const x = 3;
19+
>x : Symbol(x, Decl(github20832.js, 12, 5))
20+
21+
/**
22+
* @callback Cb
23+
* @param {V} firstParam
24+
*/
25+
/**
26+
* @template V
27+
* @param {V} vvvvv
28+
*/
29+
function g(vvvvv) {
30+
>g : Symbol(g, Decl(github20832.js, 12, 12))
31+
>vvvvv : Symbol(vvvvv, Decl(github20832.js, 22, 11))
32+
}
33+
34+
/** @type {Cb} */
35+
const cb = x => {}
36+
>cb : Symbol(cb, Decl(github20832.js, 26, 5))
37+
>x : Symbol(x, Decl(github20832.js, 26, 10))
38+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
=== tests/cases/conformance/jsdoc/github20832.js ===
2+
// #20832
3+
/** @typedef {U} T - should be "error, can't find type named 'U' */
4+
/**
5+
* @template U
6+
* @param {U} x
7+
* @return {T}
8+
*/
9+
function f(x) {
10+
>f : <U>(x: U) => any
11+
>x : U
12+
13+
return x;
14+
>x : U
15+
}
16+
17+
/** @type T - should be fine, since T will be any */
18+
const x = 3;
19+
>x : any
20+
>3 : 3
21+
22+
/**
23+
* @callback Cb
24+
* @param {V} firstParam
25+
*/
26+
/**
27+
* @template V
28+
* @param {V} vvvvv
29+
*/
30+
function g(vvvvv) {
31+
>g : <V>(vvvvv: V) => void
32+
>vvvvv : V
33+
}
34+
35+
/** @type {Cb} */
36+
const cb = x => {}
37+
>cb : Cb
38+
>x => {} : (x: any) => void
39+
>x : any
40+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// @noEmit: true
2+
// @allowJs: true
3+
// @checkJs: true
4+
// @Filename: first.js
5+
/** @template T
6+
* @param {T} x
7+
* @param {(t: T) => void} k
8+
*/
9+
module.exports = function (x, k) { return k(x) }
10+
11+
// @Filename: main.js
12+
var f = require('./first');
13+
f(1, n => { })
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// @noEmit: true
2+
// @allowJs: true
3+
// @checkJs: true
4+
// @Filename: github20832.js
5+
6+
// #20832
7+
/** @typedef {U} T - should be "error, can't find type named 'U' */
8+
/**
9+
* @template U
10+
* @param {U} x
11+
* @return {T}
12+
*/
13+
function f(x) {
14+
return x;
15+
}
16+
17+
/** @type T - should be fine, since T will be any */
18+
const x = 3;
19+
20+
/**
21+
* @callback Cb
22+
* @param {V} firstParam
23+
*/
24+
/**
25+
* @template V
26+
* @param {V} vvvvv
27+
*/
28+
function g(vvvvv) {
29+
}
30+
31+
/** @type {Cb} */
32+
const cb = x => {}

0 commit comments

Comments
 (0)