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.
This commit is contained in:
Nathan Shively-Sanders
2018-05-17 10:46:10 -07:00
committed by GitHub
parent 2b5ff29254
commit 6450490844
9 changed files with 253 additions and 16 deletions

View File

@@ -1453,6 +1453,12 @@ namespace ts {
location = location.parent;
}
break;
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocCallbackTag:
// js type aliases do not resolve names from their host, so skip past it
lastLocation = location;
location = getJSDocHost(location).parent;
continue;
}
if (isSelfReferenceLocation(location)) {
lastSelfReferenceLocation = location;
@@ -2153,25 +2159,31 @@ namespace ts {
*/
function resolveEntityNameFromJSSpecialAssignment(name: Identifier, meaning: SymbolFlags) {
if (isJSDocTypeReference(name.parent)) {
const host = getJSDocHost(name.parent);
if (host) {
const secondaryLocation = getJSSpecialAssignmentSymbol(getJSDocHost(name.parent.parent.parent as JSDocTag));
return secondaryLocation && resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true);
const secondaryLocation = getJSSpecialAssignmentLocation(name.parent);
if (secondaryLocation) {
return resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true);
}
}
}
function getJSSpecialAssignmentSymbol(host: HasJSDoc): Declaration | undefined {
if (isPropertyAssignment(host) && isFunctionLike(host.initializer)) {
const symbol = getSymbolOfNode(host.initializer);
return symbol && symbol.valueDeclaration;
function getJSSpecialAssignmentLocation(node: TypeReferenceNode): Declaration | undefined {
const typeAlias = findAncestor(node, node => !(isJSDocNode(node) || node.flags & NodeFlags.JSDoc) ? "quit" : isJSDocTypeAlias(node));
if (typeAlias) {
return;
}
else if (isExpressionStatement(host) &&
isBinaryExpression(host.expression) &&
getSpecialPropertyAssignmentKind(host.expression) === SpecialPropertyAssignmentKind.PrototypeProperty) {
const host = getJSDocHost(node);
if (host &&
isExpressionStatement(host) &&
isBinaryExpression(host.expression) &&
getSpecialPropertyAssignmentKind(host.expression) === SpecialPropertyAssignmentKind.PrototypeProperty) {
const symbol = getSymbolOfNode(host.expression.left);
return symbol && symbol.parent.valueDeclaration;
}
const sig = getHostSignatureFromJSDocHost(host);
if (sig) {
const symbol = getSymbolOfNode(sig);
return symbol && symbol.valueDeclaration;
}
}
function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression): Symbol {
@@ -9554,7 +9566,16 @@ namespace ts {
// parameters that are in scope (and therefore potentially referenced). For type literals that
// aren't the right hand side of a generic type alias declaration we optimize by reducing the
// set of type parameters to those that are possibly referenced in the literal.
const declaration = symbol.declarations[0];
let declaration = symbol.declarations[0];
if (isInJavaScriptFile(declaration)) {
const paramTag = findAncestor(declaration, isJSDocParameterTag);
if (paramTag) {
const paramSymbol = getParameterSymbolFromJSDoc(paramTag);
if (paramSymbol) {
declaration = paramSymbol.valueDeclaration;
}
}
}
let outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true);
if (isJavaScriptConstructor(declaration)) {
const templateTagParameters = getTypeParametersFromDeclaration(declaration as DeclarationWithTypeParameters);

View File

@@ -1865,8 +1865,7 @@ namespace ts {
// * @returns {number}
// */
// var x = function(name) { return name.length; }
if (parent.parent &&
(getSingleVariableOfVariableStatement(parent.parent) === node)) {
if (parent.parent && (getSingleVariableOfVariableStatement(parent.parent) === node)) {
getJSDocCommentsAndTagsWorker(parent.parent);
}
if (parent.parent && parent.parent.parent &&
@@ -1913,8 +1912,11 @@ namespace ts {
return parameter && parameter.symbol;
}
export function getHostSignatureFromJSDoc(node: JSDocTag): SignatureDeclaration | undefined {
const host = getJSDocHost(node);
export function getHostSignatureFromJSDoc(node: Node): SignatureDeclaration | undefined {
return getHostSignatureFromJSDocHost(getJSDocHost(node));
}
export function getHostSignatureFromJSDocHost(host: HasJSDoc): SignatureDeclaration | undefined {
const decl = getSourceOfDefaultedAssignment(host) ||
getSourceOfAssignment(host) ||
getSingleInitializerOfVariableStatementOrPropertyDeclaration(host) ||

View File

@@ -0,0 +1,23 @@
=== tests/cases/conformance/jsdoc/main.js ===
var f = require('./first');
>f : Symbol(f, Decl(main.js, 0, 3))
>require : Symbol(require)
>'./first' : Symbol("tests/cases/conformance/jsdoc/first", Decl(first.js, 0, 0))
f(1, n => { })
>f : Symbol(f, Decl(main.js, 0, 3))
>n : Symbol(n, Decl(main.js, 1, 4))
=== tests/cases/conformance/jsdoc/first.js ===
/** @template T
* @param {T} x
* @param {(t: T) => void} k
*/
module.exports = function (x, k) { return k(x) }
>module : Symbol(export=, Decl(first.js, 0, 0))
>exports : Symbol(export=, Decl(first.js, 0, 0))
>x : Symbol(x, Decl(first.js, 4, 27))
>k : Symbol(k, Decl(first.js, 4, 29))
>k : Symbol(k, Decl(first.js, 4, 29))
>x : Symbol(x, Decl(first.js, 4, 27))

View File

@@ -0,0 +1,31 @@
=== tests/cases/conformance/jsdoc/main.js ===
var f = require('./first');
>f : <T>(x: T, k: (t: T) => void) => void
>require('./first') : <T>(x: T, k: (t: T) => void) => void
>require : any
>'./first' : "./first"
f(1, n => { })
>f(1, n => { }) : void
>f : <T>(x: T, k: (t: T) => void) => void
>1 : 1
>n => { } : (n: number) => void
>n : number
=== tests/cases/conformance/jsdoc/first.js ===
/** @template T
* @param {T} x
* @param {(t: T) => void} k
*/
module.exports = function (x, k) { return k(x) }
>module.exports = function (x, k) { return k(x) } : <T>(x: T, k: (t: T) => void) => void
>module.exports : any
>module : any
>exports : any
>function (x, k) { return k(x) } : <T>(x: T, k: (t: T) => void) => void
>x : T
>k : (t: T) => void
>k(x) : void
>k : (t: T) => void
>x : T

View File

@@ -0,0 +1,37 @@
tests/cases/conformance/jsdoc/github20832.js(2,15): error TS2304: Cannot find name 'U'.
tests/cases/conformance/jsdoc/github20832.js(17,12): error TS2304: Cannot find name 'V'.
==== tests/cases/conformance/jsdoc/github20832.js (2 errors) ====
// #20832
/** @typedef {U} T - should be "error, can't find type named 'U' */
~
!!! error TS2304: Cannot find name 'U'.
/**
* @template U
* @param {U} x
* @return {T}
*/
function f(x) {
return x;
}
/** @type T - should be fine, since T will be any */
const x = 3;
/**
* @callback Cb
* @param {V} firstParam
~
!!! error TS2304: Cannot find name 'V'.
*/
/**
* @template V
* @param {V} vvvvv
*/
function g(vvvvv) {
}
/** @type {Cb} */
const cb = x => {}

View File

@@ -0,0 +1,38 @@
=== tests/cases/conformance/jsdoc/github20832.js ===
// #20832
/** @typedef {U} T - should be "error, can't find type named 'U' */
/**
* @template U
* @param {U} x
* @return {T}
*/
function f(x) {
>f : Symbol(f, Decl(github20832.js, 0, 0))
>x : Symbol(x, Decl(github20832.js, 7, 11))
return x;
>x : Symbol(x, Decl(github20832.js, 7, 11))
}
/** @type T - should be fine, since T will be any */
const x = 3;
>x : Symbol(x, Decl(github20832.js, 12, 5))
/**
* @callback Cb
* @param {V} firstParam
*/
/**
* @template V
* @param {V} vvvvv
*/
function g(vvvvv) {
>g : Symbol(g, Decl(github20832.js, 12, 12))
>vvvvv : Symbol(vvvvv, Decl(github20832.js, 22, 11))
}
/** @type {Cb} */
const cb = x => {}
>cb : Symbol(cb, Decl(github20832.js, 26, 5))
>x : Symbol(x, Decl(github20832.js, 26, 10))

View File

@@ -0,0 +1,40 @@
=== tests/cases/conformance/jsdoc/github20832.js ===
// #20832
/** @typedef {U} T - should be "error, can't find type named 'U' */
/**
* @template U
* @param {U} x
* @return {T}
*/
function f(x) {
>f : <U>(x: U) => any
>x : U
return x;
>x : U
}
/** @type T - should be fine, since T will be any */
const x = 3;
>x : any
>3 : 3
/**
* @callback Cb
* @param {V} firstParam
*/
/**
* @template V
* @param {V} vvvvv
*/
function g(vvvvv) {
>g : <V>(vvvvv: V) => void
>vvvvv : V
}
/** @type {Cb} */
const cb = x => {}
>cb : Cb
>x => {} : (x: any) => void
>x : any

View File

@@ -0,0 +1,13 @@
// @noEmit: true
// @allowJs: true
// @checkJs: true
// @Filename: first.js
/** @template T
* @param {T} x
* @param {(t: T) => void} k
*/
module.exports = function (x, k) { return k(x) }
// @Filename: main.js
var f = require('./first');
f(1, n => { })

View File

@@ -0,0 +1,32 @@
// @noEmit: true
// @allowJs: true
// @checkJs: true
// @Filename: github20832.js
// #20832
/** @typedef {U} T - should be "error, can't find type named 'U' */
/**
* @template U
* @param {U} x
* @return {T}
*/
function f(x) {
return x;
}
/** @type T - should be fine, since T will be any */
const x = 3;
/**
* @callback Cb
* @param {V} firstParam
*/
/**
* @template V
* @param {V} vvvvv
*/
function g(vvvvv) {
}
/** @type {Cb} */
const cb = x => {}