From 7d77ecbcff52db885fe7b936891ac793c4b7714a Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 29 Oct 2019 14:56:33 -0700 Subject: [PATCH] JSDoc type reference understands require with entity name (#34804) * resolve require with entity name postfix For example, `require("x").c`. This is the value equivalent of `import("x").a.b.c`, but the syntax tree is not as nicely designed for this purpose. Fixes #34802 * Add bug number to test * Add optional chain test --- src/compiler/checker.ts | 17 ++--- .../jsdocTypeReferenceToImport.symbols | 58 +++++++++++++++++ .../jsdocTypeReferenceToImport.types | 62 +++++++++++++++++++ .../jsdoc/jsdocTypeReferenceToImport.ts | 23 +++++++ 4 files changed, 153 insertions(+), 7 deletions(-) create mode 100644 tests/baselines/reference/jsdocTypeReferenceToImport.symbols create mode 100644 tests/baselines/reference/jsdocTypeReferenceToImport.types create mode 100644 tests/cases/conformance/jsdoc/jsdocTypeReferenceToImport.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2666c0f590f7e..ff688dbf9d657 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10759,14 +10759,17 @@ namespace ts { let typeType = valueType; if (symbol.valueDeclaration) { const decl = getRootDeclaration(symbol.valueDeclaration); - const isRequireAlias = isVariableDeclaration(decl) - && decl.initializer - && isCallExpression(decl.initializer) - && isRequireCall(decl.initializer, /*requireStringLiteralLikeArgument*/ true) - && valueType.symbol; - const isImportType = node.kind === SyntaxKind.ImportType; + let isRequireAlias = false; + if (isVariableDeclaration(decl) && decl.initializer) { + let expr = decl.initializer; + // skip past entity names, eg `require("x").a.b.c` + while (isPropertyAccessExpression(expr)) { + expr = expr.expression; + } + isRequireAlias = isCallExpression(expr) && isRequireCall(expr, /*requireStringLiteralLikeArgument*/ true) && !!valueType.symbol; + } const isDelayedMergeClass = symbol !== valueType.symbol && getMergedSymbol(symbol) === valueType.symbol; - if (isRequireAlias || isImportType || isDelayedMergeClass) { + if (isRequireAlias || node.kind === SyntaxKind.ImportType || isDelayedMergeClass) { typeType = getTypeReferenceType(node, valueType.symbol); } } diff --git a/tests/baselines/reference/jsdocTypeReferenceToImport.symbols b/tests/baselines/reference/jsdocTypeReferenceToImport.symbols new file mode 100644 index 0000000000000..c7d04ff5a1627 --- /dev/null +++ b/tests/baselines/reference/jsdocTypeReferenceToImport.symbols @@ -0,0 +1,58 @@ +=== tests/cases/conformance/jsdoc/jsdocTypeReferenceToImport.js === +// #34802 + +const C = require('./ex').C; +>C : Symbol(C, Decl(jsdocTypeReferenceToImport.js, 2, 5)) +>require('./ex').C : Symbol(C, Decl(ex.d.ts, 0, 0)) +>require : Symbol(require) +>'./ex' : Symbol("tests/cases/conformance/jsdoc/ex", Decl(ex.d.ts, 0, 0)) +>C : Symbol(C, Decl(ex.d.ts, 0, 0)) + +const D = require('./ex')?.C; +>D : Symbol(D, Decl(jsdocTypeReferenceToImport.js, 3, 5)) +>require('./ex')?.C : Symbol(C, Decl(ex.d.ts, 0, 0)) +>require : Symbol(require) +>'./ex' : Symbol("tests/cases/conformance/jsdoc/ex", Decl(ex.d.ts, 0, 0)) +>C : Symbol(C, Decl(ex.d.ts, 0, 0)) + +/** @type {C} c */ +var c = new C() +>c : Symbol(c, Decl(jsdocTypeReferenceToImport.js, 5, 3)) +>C : Symbol(C, Decl(jsdocTypeReferenceToImport.js, 2, 5)) + +c.start +>c.start : Symbol(C.start, Decl(ex.d.ts, 0, 16)) +>c : Symbol(c, Decl(jsdocTypeReferenceToImport.js, 5, 3)) +>start : Symbol(C.start, Decl(ex.d.ts, 0, 16)) + +c.end +>c.end : Symbol(C.end, Decl(ex.d.ts, 1, 17)) +>c : Symbol(c, Decl(jsdocTypeReferenceToImport.js, 5, 3)) +>end : Symbol(C.end, Decl(ex.d.ts, 1, 17)) + +/** @type {D} c */ +var d = new D() +>d : Symbol(d, Decl(jsdocTypeReferenceToImport.js, 10, 3)) +>D : Symbol(D, Decl(jsdocTypeReferenceToImport.js, 3, 5)) + +d.start +>d.start : Symbol(C.start, Decl(ex.d.ts, 0, 16)) +>d : Symbol(d, Decl(jsdocTypeReferenceToImport.js, 10, 3)) +>start : Symbol(C.start, Decl(ex.d.ts, 0, 16)) + +d.end +>d.end : Symbol(C.end, Decl(ex.d.ts, 1, 17)) +>d : Symbol(d, Decl(jsdocTypeReferenceToImport.js, 10, 3)) +>end : Symbol(C.end, Decl(ex.d.ts, 1, 17)) + +=== tests/cases/conformance/jsdoc/ex.d.ts === +export class C { +>C : Symbol(C, Decl(ex.d.ts, 0, 0)) + + start: number +>start : Symbol(C.start, Decl(ex.d.ts, 0, 16)) + + end: number +>end : Symbol(C.end, Decl(ex.d.ts, 1, 17)) +} + diff --git a/tests/baselines/reference/jsdocTypeReferenceToImport.types b/tests/baselines/reference/jsdocTypeReferenceToImport.types new file mode 100644 index 0000000000000..4d4fe89ae02a5 --- /dev/null +++ b/tests/baselines/reference/jsdocTypeReferenceToImport.types @@ -0,0 +1,62 @@ +=== tests/cases/conformance/jsdoc/jsdocTypeReferenceToImport.js === +// #34802 + +const C = require('./ex').C; +>C : typeof import("tests/cases/conformance/jsdoc/ex").C +>require('./ex').C : typeof import("tests/cases/conformance/jsdoc/ex").C +>require('./ex') : typeof import("tests/cases/conformance/jsdoc/ex") +>require : any +>'./ex' : "./ex" +>C : typeof import("tests/cases/conformance/jsdoc/ex").C + +const D = require('./ex')?.C; +>D : typeof import("tests/cases/conformance/jsdoc/ex").C +>require('./ex')?.C : typeof import("tests/cases/conformance/jsdoc/ex").C +>require('./ex') : typeof import("tests/cases/conformance/jsdoc/ex") +>require : any +>'./ex' : "./ex" +>C : typeof import("tests/cases/conformance/jsdoc/ex").C + +/** @type {C} c */ +var c = new C() +>c : import("tests/cases/conformance/jsdoc/ex").C +>new C() : import("tests/cases/conformance/jsdoc/ex").C +>C : typeof import("tests/cases/conformance/jsdoc/ex").C + +c.start +>c.start : number +>c : import("tests/cases/conformance/jsdoc/ex").C +>start : number + +c.end +>c.end : number +>c : import("tests/cases/conformance/jsdoc/ex").C +>end : number + +/** @type {D} c */ +var d = new D() +>d : import("tests/cases/conformance/jsdoc/ex").C +>new D() : import("tests/cases/conformance/jsdoc/ex").C +>D : typeof import("tests/cases/conformance/jsdoc/ex").C + +d.start +>d.start : number +>d : import("tests/cases/conformance/jsdoc/ex").C +>start : number + +d.end +>d.end : number +>d : import("tests/cases/conformance/jsdoc/ex").C +>end : number + +=== tests/cases/conformance/jsdoc/ex.d.ts === +export class C { +>C : C + + start: number +>start : number + + end: number +>end : number +} + diff --git a/tests/cases/conformance/jsdoc/jsdocTypeReferenceToImport.ts b/tests/cases/conformance/jsdoc/jsdocTypeReferenceToImport.ts new file mode 100644 index 0000000000000..8163988fc0c9c --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocTypeReferenceToImport.ts @@ -0,0 +1,23 @@ +// #34802 +// @Filename: jsdocTypeReferenceToImport.js +// @noEmit: true +// @allowJs: true +// @checkJs: true + +const C = require('./ex').C; +const D = require('./ex')?.C; +/** @type {C} c */ +var c = new C() +c.start +c.end + +/** @type {D} c */ +var d = new D() +d.start +d.end + +// @Filename: ex.d.ts +export class C { + start: number + end: number +}