From 5b0f899b8a6c5883f5e2bb2bc47d30f462dfe92f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 14 May 2018 10:38:00 -0700 Subject: [PATCH 1/5] Fix JSDoc type resolution Breaks type parameter resolution that is looked up through prototype methods, though. I need to fix that still. --- src/compiler/checker.ts | 33 ++++++++++++------- src/compiler/utilities.ts | 2 +- .../reference/paramTagTypeResolution.symbols | 23 +++++++++++++ .../reference/paramTagTypeResolution.types | 31 +++++++++++++++++ .../jsdoc/paramTagTypeResolution.ts | 13 ++++++++ 5 files changed, 90 insertions(+), 12 deletions(-) create mode 100644 tests/baselines/reference/paramTagTypeResolution.symbols create mode 100644 tests/baselines/reference/paramTagTypeResolution.types create mode 100644 tests/cases/conformance/jsdoc/paramTagTypeResolution.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4c9a98951fae8..7d8438b829323 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2153,22 +2153,24 @@ 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 = getJSSpecialAssignmentSymbol(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); + function getJSSpecialAssignmentSymbol(node: TypeReferenceNode): Declaration | undefined { + const sig = getHostSignatureFromJSDoc(node); + if (sig) { + const symbol = getSymbolOfNode(sig); return symbol && symbol.valueDeclaration; } - 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; } @@ -9554,7 +9556,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); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 7339907547a57..66447ab520345 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1913,7 +1913,7 @@ namespace ts { return parameter && parameter.symbol; } - export function getHostSignatureFromJSDoc(node: JSDocTag): SignatureDeclaration | undefined { + export function getHostSignatureFromJSDoc(node: Node): SignatureDeclaration | undefined { const host = getJSDocHost(node); const decl = getSourceOfDefaultedAssignment(host) || getSourceOfAssignment(host) || diff --git a/tests/baselines/reference/paramTagTypeResolution.symbols b/tests/baselines/reference/paramTagTypeResolution.symbols new file mode 100644 index 0000000000000..1584e2fcb95c6 --- /dev/null +++ b/tests/baselines/reference/paramTagTypeResolution.symbols @@ -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)) + diff --git a/tests/baselines/reference/paramTagTypeResolution.types b/tests/baselines/reference/paramTagTypeResolution.types new file mode 100644 index 0000000000000..daf7749c6bb84 --- /dev/null +++ b/tests/baselines/reference/paramTagTypeResolution.types @@ -0,0 +1,31 @@ +=== tests/cases/conformance/jsdoc/main.js === +var f = require('./first'); +>f : (x: T, k: (t: T) => void) => void +>require('./first') : (x: T, k: (t: T) => void) => void +>require : any +>'./first' : "./first" + +f(1, n => { }) +>f(1, n => { }) : void +>f : (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) } : (x: T, k: (t: T) => void) => void +>module.exports : any +>module : any +>exports : any +>function (x, k) { return k(x) } : (x: T, k: (t: T) => void) => void +>x : T +>k : (t: T) => void +>k(x) : void +>k : (t: T) => void +>x : T + diff --git a/tests/cases/conformance/jsdoc/paramTagTypeResolution.ts b/tests/cases/conformance/jsdoc/paramTagTypeResolution.ts new file mode 100644 index 0000000000000..0256651234d25 --- /dev/null +++ b/tests/cases/conformance/jsdoc/paramTagTypeResolution.ts @@ -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 => { }) From 4726878f10618f3f0782e8ad84347cb50dce574a Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 14 May 2018 13:52:38 -0700 Subject: [PATCH 2/5] Check for prototype method assignments first --- src/compiler/checker.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7d8438b829323..60b0831e19224 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2153,19 +2153,14 @@ namespace ts { */ function resolveEntityNameFromJSSpecialAssignment(name: Identifier, meaning: SymbolFlags) { if (isJSDocTypeReference(name.parent)) { - const secondaryLocation = getJSSpecialAssignmentSymbol(name.parent); + const secondaryLocation = getJSSpecialAssignmentLocation(name.parent); if (secondaryLocation) { return resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true); } } } - function getJSSpecialAssignmentSymbol(node: TypeReferenceNode): Declaration | undefined { - const sig = getHostSignatureFromJSDoc(node); - if (sig) { - const symbol = getSymbolOfNode(sig); - return symbol && symbol.valueDeclaration; - } + function getJSSpecialAssignmentLocation(node: TypeReferenceNode): Declaration | undefined { const host = getJSDocHost(node); if (host && isExpressionStatement(host) && @@ -2174,6 +2169,11 @@ namespace ts { const symbol = getSymbolOfNode(host.expression.left); return symbol && symbol.parent.valueDeclaration; } + const sig = getHostSignatureFromJSDoc(node); + if (sig) { + const symbol = getSymbolOfNode(sig); + return symbol && symbol.valueDeclaration; + } } function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression): Symbol { From fd6628ccc3bb029923f8fb82030df43a24061d82 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 14 May 2018 13:59:41 -0700 Subject: [PATCH 3/5] Undo dedupe changes to getJSDocTags --- src/compiler/utilities.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 66447ab520345..3aaa03b5bda18 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1866,7 +1866,7 @@ namespace ts { // */ // var x = function(name) { return name.length; } if (parent.parent && - (getSingleVariableOfVariableStatement(parent.parent) === node)) { + (getSingleVariableOfVariableStatement(parent.parent) === node || getSourceOfAssignment(parent.parent))) { getJSDocCommentsAndTagsWorker(parent.parent); } if (parent.parent && parent.parent.parent && @@ -1875,8 +1875,8 @@ namespace ts { getSourceOfDefaultedAssignment(parent.parent.parent))) { getJSDocCommentsAndTagsWorker(parent.parent.parent); } - if (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.EqualsToken || - isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken || + if (isBinaryExpression(node) && getSpecialPropertyAssignmentKind(node) !== SpecialPropertyAssignmentKind.None || + isBinaryExpression(parent) && getSpecialPropertyAssignmentKind(parent) !== SpecialPropertyAssignmentKind.None || node.kind === SyntaxKind.PropertyAccessExpression && node.parent && node.parent.kind === SyntaxKind.ExpressionStatement) { getJSDocCommentsAndTagsWorker(parent); } From 22cd06b65e411ca6477f1fa868c95c8e085efcb5 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 15 May 2018 09:01:44 -0700 Subject: [PATCH 4/5] 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. --- src/compiler/checker.ts | 12 +++++- src/compiler/utilities.ts | 5 ++- .../typedefTagTypeResolution.errors.txt | 37 +++++++++++++++++ .../typedefTagTypeResolution.symbols | 38 ++++++++++++++++++ .../reference/typedefTagTypeResolution.types | 40 +++++++++++++++++++ .../jsdoc/typedefTagTypeResolution.ts | 32 +++++++++++++++ 6 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/typedefTagTypeResolution.errors.txt create mode 100644 tests/baselines/reference/typedefTagTypeResolution.symbols create mode 100644 tests/baselines/reference/typedefTagTypeResolution.types create mode 100644 tests/cases/conformance/jsdoc/typedefTagTypeResolution.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 60b0831e19224..59ce935546bf5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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; @@ -2161,6 +2167,10 @@ namespace ts { } function getJSSpecialAssignmentLocation(node: TypeReferenceNode): Declaration | undefined { + const typeAlias = findAncestor(node, node => !(isJSDocNode(node) || node.flags & NodeFlags.JSDoc) ? "quit" : isJSDocTypeAlias(node)); + if (typeAlias) { + return; + } const host = getJSDocHost(node); if (host && isExpressionStatement(host) && @@ -2169,7 +2179,7 @@ namespace ts { const symbol = getSymbolOfNode(host.expression.left); return symbol && symbol.parent.valueDeclaration; } - const sig = getHostSignatureFromJSDoc(node); + const sig = getHostSignatureFromJSDocHost(host); if (sig) { const symbol = getSymbolOfNode(sig); return symbol && symbol.valueDeclaration; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 3aaa03b5bda18..f40bee24b6f02 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1914,7 +1914,10 @@ namespace ts { } export function getHostSignatureFromJSDoc(node: Node): SignatureDeclaration | undefined { - const host = getJSDocHost(node); + return getHostSignatureFromJSDocHost(getJSDocHost(node)); + } + + export function getHostSignatureFromJSDocHost(host: HasJSDoc): SignatureDeclaration | undefined { const decl = getSourceOfDefaultedAssignment(host) || getSourceOfAssignment(host) || getSingleInitializerOfVariableStatementOrPropertyDeclaration(host) || diff --git a/tests/baselines/reference/typedefTagTypeResolution.errors.txt b/tests/baselines/reference/typedefTagTypeResolution.errors.txt new file mode 100644 index 0000000000000..8d8c324beb32f --- /dev/null +++ b/tests/baselines/reference/typedefTagTypeResolution.errors.txt @@ -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 => {} + \ No newline at end of file diff --git a/tests/baselines/reference/typedefTagTypeResolution.symbols b/tests/baselines/reference/typedefTagTypeResolution.symbols new file mode 100644 index 0000000000000..9201ecb2cd855 --- /dev/null +++ b/tests/baselines/reference/typedefTagTypeResolution.symbols @@ -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)) + diff --git a/tests/baselines/reference/typedefTagTypeResolution.types b/tests/baselines/reference/typedefTagTypeResolution.types new file mode 100644 index 0000000000000..5c225d166d21c --- /dev/null +++ b/tests/baselines/reference/typedefTagTypeResolution.types @@ -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 : (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 : (vvvvv: V) => void +>vvvvv : V +} + +/** @type {Cb} */ +const cb = x => {} +>cb : (firstParam: any) => any +>x => {} : (x: any) => void +>x : any + diff --git a/tests/cases/conformance/jsdoc/typedefTagTypeResolution.ts b/tests/cases/conformance/jsdoc/typedefTagTypeResolution.ts new file mode 100644 index 0000000000000..c4c993d9c78e0 --- /dev/null +++ b/tests/cases/conformance/jsdoc/typedefTagTypeResolution.ts @@ -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 => {} From a643ff74269ba81aa27fae8cff154cb354432398 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 17 May 2018 10:25:09 -0700 Subject: [PATCH 5/5] Reduce dedupe changes+update baseline The only reason I had undone them was to merge successfully with an older state of master. --- src/compiler/utilities.ts | 7 +++---- tests/baselines/reference/typedefTagTypeResolution.types | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index f40bee24b6f02..6e440e01ecfbc 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1865,8 +1865,7 @@ namespace ts { // * @returns {number} // */ // var x = function(name) { return name.length; } - if (parent.parent && - (getSingleVariableOfVariableStatement(parent.parent) === node || getSourceOfAssignment(parent.parent))) { + if (parent.parent && (getSingleVariableOfVariableStatement(parent.parent) === node)) { getJSDocCommentsAndTagsWorker(parent.parent); } if (parent.parent && parent.parent.parent && @@ -1875,8 +1874,8 @@ namespace ts { getSourceOfDefaultedAssignment(parent.parent.parent))) { getJSDocCommentsAndTagsWorker(parent.parent.parent); } - if (isBinaryExpression(node) && getSpecialPropertyAssignmentKind(node) !== SpecialPropertyAssignmentKind.None || - isBinaryExpression(parent) && getSpecialPropertyAssignmentKind(parent) !== SpecialPropertyAssignmentKind.None || + if (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.EqualsToken || + isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken || node.kind === SyntaxKind.PropertyAccessExpression && node.parent && node.parent.kind === SyntaxKind.ExpressionStatement) { getJSDocCommentsAndTagsWorker(parent); } diff --git a/tests/baselines/reference/typedefTagTypeResolution.types b/tests/baselines/reference/typedefTagTypeResolution.types index 5c225d166d21c..20c9412832667 100644 --- a/tests/baselines/reference/typedefTagTypeResolution.types +++ b/tests/baselines/reference/typedefTagTypeResolution.types @@ -34,7 +34,7 @@ function g(vvvvv) { /** @type {Cb} */ const cb = x => {} ->cb : (firstParam: any) => any +>cb : Cb >x => {} : (x: any) => void >x : any