From 3f93d420bfb59335394f3b437ec6cf28cd477451 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 14 Apr 2021 16:56:37 -0700 Subject: [PATCH] Fix @typedef/@callback scope (#43682) JSDoc typedefs don't actually have hosts, because they're not semantically attached to a declaration. However, the parser still attaches them to some declaration (or statement), but that declaration is not related to the typedef. Previously, delayedBindJSDocTypedefTag used getJSDocHost to walk past the unrelated declaration, but #41858 correctly started categorising typedefs as unattached, with no host, so the binder began falling back to file scope. The path to skip the unrelated declaration is always the same, though, so this PR uses `typeAlias.parent.parent` instead of `getJSDocHost(typeAlias)`. --- src/compiler/binder.ts | 6 +-- .../reference/typedefScope1.errors.txt | 21 ++++++++++ tests/baselines/reference/typedefScope1.js | 38 +++++++++++++++++++ .../baselines/reference/typedefScope1.symbols | 23 +++++++++++ tests/baselines/reference/typedefScope1.types | 26 +++++++++++++ .../cases/conformance/jsdoc/typedefScope1.ts | 19 ++++++++++ 6 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/typedefScope1.errors.txt create mode 100644 tests/baselines/reference/typedefScope1.js create mode 100644 tests/baselines/reference/typedefScope1.symbols create mode 100644 tests/baselines/reference/typedefScope1.types create mode 100644 tests/cases/conformance/jsdoc/typedefScope1.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 7038d1121dd..d42035879e4 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2123,9 +2123,9 @@ namespace ts { const saveParent = parent; const saveCurrentFlow = currentFlow; for (const typeAlias of delayedTypeAliases) { - const host = getJSDocHost(typeAlias); - container = (host && findAncestor(host.parent, n => !!(getContainerFlags(n) & ContainerFlags.IsContainer))) || file; - blockScopeContainer = (host && getEnclosingBlockScopeContainer(host)) || file; + const host = typeAlias.parent.parent; + container = findAncestor(host.parent, n => !!(getContainerFlags(n) & ContainerFlags.IsContainer)) || file; + blockScopeContainer = getEnclosingBlockScopeContainer(host) || file; currentFlow = initFlowNode({ flags: FlowFlags.Start }); parent = typeAlias; bind(typeAlias.typeExpression); diff --git a/tests/baselines/reference/typedefScope1.errors.txt b/tests/baselines/reference/typedefScope1.errors.txt new file mode 100644 index 00000000000..640635063bb --- /dev/null +++ b/tests/baselines/reference/typedefScope1.errors.txt @@ -0,0 +1,21 @@ +tests/cases/conformance/jsdoc/typedefScope1.js(13,12): error TS2304: Cannot find name 'B'. + + +==== tests/cases/conformance/jsdoc/typedefScope1.js (1 errors) ==== + function B1() { + /** @typedef {number} B */ + /** @type {B} */ + var ok1 = 0; + } + + function B2() { + /** @typedef {string} B */ + /** @type {B} */ + var ok2 = 'hi'; + } + + /** @type {B} */ + ~ +!!! error TS2304: Cannot find name 'B'. + var notOK = 0; + \ No newline at end of file diff --git a/tests/baselines/reference/typedefScope1.js b/tests/baselines/reference/typedefScope1.js new file mode 100644 index 00000000000..56fa6103c9a --- /dev/null +++ b/tests/baselines/reference/typedefScope1.js @@ -0,0 +1,38 @@ +//// [typedefScope1.js] +function B1() { + /** @typedef {number} B */ + /** @type {B} */ + var ok1 = 0; +} + +function B2() { + /** @typedef {string} B */ + /** @type {B} */ + var ok2 = 'hi'; +} + +/** @type {B} */ +var notOK = 0; + + +//// [typedefScope1.js] +"use strict"; +function B1() { + /** @typedef {number} B */ + /** @type {B} */ + var ok1 = 0; +} +function B2() { + /** @typedef {string} B */ + /** @type {B} */ + var ok2 = 'hi'; +} +/** @type {B} */ +var notOK = 0; + + +//// [typedefScope1.d.ts] +declare function B1(): void; +declare function B2(): void; +/** @type {B} */ +declare var notOK: any; diff --git a/tests/baselines/reference/typedefScope1.symbols b/tests/baselines/reference/typedefScope1.symbols new file mode 100644 index 00000000000..53a9678379c --- /dev/null +++ b/tests/baselines/reference/typedefScope1.symbols @@ -0,0 +1,23 @@ +=== tests/cases/conformance/jsdoc/typedefScope1.js === +function B1() { +>B1 : Symbol(B1, Decl(typedefScope1.js, 0, 0)) + + /** @typedef {number} B */ + /** @type {B} */ + var ok1 = 0; +>ok1 : Symbol(ok1, Decl(typedefScope1.js, 3, 7)) +} + +function B2() { +>B2 : Symbol(B2, Decl(typedefScope1.js, 4, 1)) + + /** @typedef {string} B */ + /** @type {B} */ + var ok2 = 'hi'; +>ok2 : Symbol(ok2, Decl(typedefScope1.js, 9, 7)) +} + +/** @type {B} */ +var notOK = 0; +>notOK : Symbol(notOK, Decl(typedefScope1.js, 13, 3)) + diff --git a/tests/baselines/reference/typedefScope1.types b/tests/baselines/reference/typedefScope1.types new file mode 100644 index 00000000000..c22178321b8 --- /dev/null +++ b/tests/baselines/reference/typedefScope1.types @@ -0,0 +1,26 @@ +=== tests/cases/conformance/jsdoc/typedefScope1.js === +function B1() { +>B1 : () => void + + /** @typedef {number} B */ + /** @type {B} */ + var ok1 = 0; +>ok1 : number +>0 : 0 +} + +function B2() { +>B2 : () => void + + /** @typedef {string} B */ + /** @type {B} */ + var ok2 = 'hi'; +>ok2 : string +>'hi' : "hi" +} + +/** @type {B} */ +var notOK = 0; +>notOK : any +>0 : 0 + diff --git a/tests/cases/conformance/jsdoc/typedefScope1.ts b/tests/cases/conformance/jsdoc/typedefScope1.ts new file mode 100644 index 00000000000..78b237369b0 --- /dev/null +++ b/tests/cases/conformance/jsdoc/typedefScope1.ts @@ -0,0 +1,19 @@ +// @strict: true +// @declaration: true +// @outdir: out/ +// @checkJs: true +// @filename: typedefScope1.js +function B1() { + /** @typedef {number} B */ + /** @type {B} */ + var ok1 = 0; +} + +function B2() { + /** @typedef {string} B */ + /** @type {B} */ + var ok2 = 'hi'; +} + +/** @type {B} */ +var notOK = 0;