From 7d1ed12792bfd8cb13f922da6e6253aad18a6936 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Mon, 6 Jan 2025 15:35:52 +0200 Subject: [PATCH 1/6] fix(60908): avoid binding jsdocimport children to the jsdoc host container --- src/compiler/binder.ts | 4 + .../reference/importTag24.errors.txt | 38 ++++++++++ tests/baselines/reference/importTag24.symbols | 40 ++++++++++ tests/baselines/reference/importTag24.types | 74 +++++++++++++++++++ tests/cases/conformance/jsdoc/importTag24.ts | 33 +++++++++ 5 files changed, 189 insertions(+) create mode 100644 tests/baselines/reference/importTag24.errors.txt create mode 100644 tests/baselines/reference/importTag24.symbols create mode 100644 tests/baselines/reference/importTag24.types create mode 100644 tests/cases/conformance/jsdoc/importTag24.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 41966df6672e9..27e9fe8b7df39 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -176,6 +176,7 @@ import { isInTopLevelContext, isJSDocConstructSignature, isJSDocEnumTag, + isJSDocImportTag, isJSDocTemplateTag, isJSDocTypeAlias, isJSDocTypeAssertion, @@ -1108,6 +1109,9 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { if (canHaveFlowNode(node) && node.flowNode) { node.flowNode = undefined; } + if (isJSDocImportTag(node)) { + return; + } bindEachChild(node); bindJSDoc(node); inAssignmentPattern = saveInAssignmentPattern; diff --git a/tests/baselines/reference/importTag24.errors.txt b/tests/baselines/reference/importTag24.errors.txt new file mode 100644 index 0000000000000..25144a37a61f7 --- /dev/null +++ b/tests/baselines/reference/importTag24.errors.txt @@ -0,0 +1,38 @@ +a.js(3,10): error TS6133: 'f1' is declared but its value is never read. +a.js(11,10): error TS6133: 'f3' is declared but its value is never read. +a.js(19,10): error TS6133: 'f4' is declared but its value is never read. +a.js(19,17): error TS2322: Type 'number' is not assignable to type 'string'. + + +==== types.d.ts (0 errors) ==== + export type Foo = string; + +==== a.js (4 errors) ==== + /** @import { Foo } from './types.d.ts') */ + + function f1() { return undefined; } + ~~ +!!! error TS6133: 'f1' is declared but its value is never read. + + export function f2() { + /** @type {Set} */ + const foo = new Set([ 'a', 'b' ]); + return foo; + } + + function f3() { return undefined; } + ~~ +!!! error TS6133: 'f3' is declared but its value is never read. + + /** @type {Set} */ + export const foo = new Set([ 'a', 'b' ]); + + /** + * @returns {Foo} + */ + function f4() { return 1; } + ~~ +!!! error TS6133: 'f4' is declared but its value is never read. + ~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'string'. + \ No newline at end of file diff --git a/tests/baselines/reference/importTag24.symbols b/tests/baselines/reference/importTag24.symbols new file mode 100644 index 0000000000000..0842c99a14993 --- /dev/null +++ b/tests/baselines/reference/importTag24.symbols @@ -0,0 +1,40 @@ +//// [tests/cases/conformance/jsdoc/importTag24.ts] //// + +=== types.d.ts === +export type Foo = string; +>Foo : Symbol(Foo, Decl(types.d.ts, 0, 0)) + +=== a.js === +/** @import { Foo } from './types.d.ts') */ + +function f1() { return undefined; } +>f1 : Symbol(f1, Decl(a.js, 0, 0)) +>undefined : Symbol(undefined) + +export function f2() { +>f2 : Symbol(f2, Decl(a.js, 2, 35)) + + /** @type {Set} */ + const foo = new Set([ 'a', 'b' ]); +>foo : Symbol(foo, Decl(a.js, 6, 9)) +>Set : Symbol(Set, Decl(lib.es2015.collection.d.ts, --, --), Decl(lib.es2015.collection.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.esnext.collection.d.ts, --, --)) + + return foo; +>foo : Symbol(foo, Decl(a.js, 6, 9)) +} + +function f3() { return undefined; } +>f3 : Symbol(f3, Decl(a.js, 8, 1)) +>undefined : Symbol(undefined) + +/** @type {Set} */ +export const foo = new Set([ 'a', 'b' ]); +>foo : Symbol(foo, Decl(a.js, 13, 12)) +>Set : Symbol(Set, Decl(lib.es2015.collection.d.ts, --, --), Decl(lib.es2015.collection.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.esnext.collection.d.ts, --, --)) + +/** + * @returns {Foo} + */ +function f4() { return 1; } +>f4 : Symbol(f4, Decl(a.js, 13, 41)) + diff --git a/tests/baselines/reference/importTag24.types b/tests/baselines/reference/importTag24.types new file mode 100644 index 0000000000000..5164c9413d2e1 --- /dev/null +++ b/tests/baselines/reference/importTag24.types @@ -0,0 +1,74 @@ +//// [tests/cases/conformance/jsdoc/importTag24.ts] //// + +=== Performance Stats === +Type Count: 1,000 +Instantiation count: 2,500 + +=== types.d.ts === +export type Foo = string; +>Foo : string +> : ^^^^^^ + +=== a.js === +/** @import { Foo } from './types.d.ts') */ + +function f1() { return undefined; } +>f1 : () => any +> : ^^^^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ + +export function f2() { +>f2 : () => Set +> : ^^^^^^^^^^^^^^^^^ + + /** @type {Set} */ + const foo = new Set([ 'a', 'b' ]); +>foo : Set +> : ^^^^^^^^^^^ +>new Set([ 'a', 'b' ]) : Set +> : ^^^^^^^^^^^ +>Set : SetConstructor +> : ^^^^^^^^^^^^^^ +>[ 'a', 'b' ] : string[] +> : ^^^^^^^^ +>'a' : "a" +> : ^^^ +>'b' : "b" +> : ^^^ + + return foo; +>foo : Set +> : ^^^^^^^^^^^ +} + +function f3() { return undefined; } +>f3 : () => any +> : ^^^^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ + +/** @type {Set} */ +export const foo = new Set([ 'a', 'b' ]); +>foo : Set +> : ^^^^^^^^^^^ +>new Set([ 'a', 'b' ]) : Set +> : ^^^^^^^^^^^ +>Set : SetConstructor +> : ^^^^^^^^^^^^^^ +>[ 'a', 'b' ] : string[] +> : ^^^^^^^^ +>'a' : "a" +> : ^^^ +>'b' : "b" +> : ^^^ + +/** + * @returns {Foo} + */ +function f4() { return 1; } +>f4 : () => Foo +> : ^^^^^^^^^ +>1 : 1 +> : ^ + diff --git a/tests/cases/conformance/jsdoc/importTag24.ts b/tests/cases/conformance/jsdoc/importTag24.ts new file mode 100644 index 0000000000000..35e743b5ea648 --- /dev/null +++ b/tests/cases/conformance/jsdoc/importTag24.ts @@ -0,0 +1,33 @@ +// @checkJs: true +// @allowJs: true +// @noEmit: true +// @lib: esnext +// @moduleResolution: bundler +// @module: preserve +// @noUnusedLocals: true +// @noUnusedParameters: true +// @allowImportingTsExtensions: true + +// @filename: types.d.ts +export type Foo = string; + +// @filename: a.js +/** @import { Foo } from './types.d.ts') */ + +function f1() { return undefined; } + +export function f2() { + /** @type {Set} */ + const foo = new Set([ 'a', 'b' ]); + return foo; +} + +function f3() { return undefined; } + +/** @type {Set} */ +export const foo = new Set([ 'a', 'b' ]); + +/** + * @returns {Foo} + */ +function f4() { return 1; } From 49cdbda58adb34f722543006f6936a4cfd87c7d0 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Sun, 12 Jan 2025 02:19:32 +0200 Subject: [PATCH 2/6] add additional tests --- tests/baselines/reference/importTag23.symbols | 25 +++++++++++++++ tests/baselines/reference/importTag23.types | 32 +++++++++++++++++++ tests/cases/conformance/jsdoc/importTag23.ts | 18 +++++++++++ 3 files changed, 75 insertions(+) create mode 100644 tests/baselines/reference/importTag23.symbols create mode 100644 tests/baselines/reference/importTag23.types create mode 100644 tests/cases/conformance/jsdoc/importTag23.ts diff --git a/tests/baselines/reference/importTag23.symbols b/tests/baselines/reference/importTag23.symbols new file mode 100644 index 0000000000000..5f58d84ace59b --- /dev/null +++ b/tests/baselines/reference/importTag23.symbols @@ -0,0 +1,25 @@ +//// [tests/cases/conformance/jsdoc/importTag23.ts] //// + +=== types.d.ts === +export type T = { +>T : Symbol(T, Decl(types.d.ts, 0, 0)) + + a: number; +>a : Symbol(a, Decl(types.d.ts, 0, 17)) + +}; + +=== foo.js === +/** @import { T } from "./types.d.ts" */ + +export default async function f() { +>f : Symbol(f, Decl(foo.js, 0, 0)) + + /** @type {T[]} */ + const types = []; +>types : Symbol(types, Decl(foo.js, 4, 6)) + + return types; +>types : Symbol(types, Decl(foo.js, 4, 6)) +} + diff --git a/tests/baselines/reference/importTag23.types b/tests/baselines/reference/importTag23.types new file mode 100644 index 0000000000000..1e0b7c8a2a03f --- /dev/null +++ b/tests/baselines/reference/importTag23.types @@ -0,0 +1,32 @@ +//// [tests/cases/conformance/jsdoc/importTag23.ts] //// + +=== types.d.ts === +export type T = { +>T : T +> : ^ + + a: number; +>a : number +> : ^^^^^^ + +}; + +=== foo.js === +/** @import { T } from "./types.d.ts" */ + +export default async function f() { +>f : () => Promise +> : ^^^^^^^^^^^^^^^^^^ + + /** @type {T[]} */ + const types = []; +>types : T[] +> : ^^^ +>[] : undefined[] +> : ^^^^^^^^^^^ + + return types; +>types : T[] +> : ^^^ +} + diff --git a/tests/cases/conformance/jsdoc/importTag23.ts b/tests/cases/conformance/jsdoc/importTag23.ts new file mode 100644 index 0000000000000..423ea12b50518 --- /dev/null +++ b/tests/cases/conformance/jsdoc/importTag23.ts @@ -0,0 +1,18 @@ +// @noUnusedLocals: true +// @allowJs: true +// @checkJs: true +// @noEmit: true + +// @filename: types.d.ts +export type T = { + a: number; +}; + +// @filename: foo.js +/** @import { T } from "./types.d.ts" */ + +export default async function f() { + /** @type {T[]} */ + const types = []; + return types; +} From 7c18a9cc292b4bb76ddb2cc433d35ff5435cd5d7 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Mon, 20 Jan 2025 01:48:13 +0200 Subject: [PATCH 3/6] prevent early binding of importClause in jsdoc imports --- src/compiler/binder.ts | 8 +++++--- tests/baselines/reference/importTag24.errors.txt | 2 +- tests/baselines/reference/importTag24.symbols | 2 +- tests/baselines/reference/importTag24.types | 2 +- tests/cases/conformance/jsdoc/importTag24.ts | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 27e9fe8b7df39..23bdab6bff910 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1110,10 +1110,12 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { node.flowNode = undefined; } if (isJSDocImportTag(node)) { - return; + bindJSDocImportTag(node); + } + else { + bindEachChild(node); + bindJSDoc(node); } - bindEachChild(node); - bindJSDoc(node); inAssignmentPattern = saveInAssignmentPattern; return; } diff --git a/tests/baselines/reference/importTag24.errors.txt b/tests/baselines/reference/importTag24.errors.txt index 25144a37a61f7..5cd7499550029 100644 --- a/tests/baselines/reference/importTag24.errors.txt +++ b/tests/baselines/reference/importTag24.errors.txt @@ -8,7 +8,7 @@ a.js(19,17): error TS2322: Type 'number' is not assignable to type 'string'. export type Foo = string; ==== a.js (4 errors) ==== - /** @import { Foo } from './types.d.ts') */ + /** @import { Foo } from './types.d.ts' */ function f1() { return undefined; } ~~ diff --git a/tests/baselines/reference/importTag24.symbols b/tests/baselines/reference/importTag24.symbols index 0842c99a14993..1cfa53ff72210 100644 --- a/tests/baselines/reference/importTag24.symbols +++ b/tests/baselines/reference/importTag24.symbols @@ -5,7 +5,7 @@ export type Foo = string; >Foo : Symbol(Foo, Decl(types.d.ts, 0, 0)) === a.js === -/** @import { Foo } from './types.d.ts') */ +/** @import { Foo } from './types.d.ts' */ function f1() { return undefined; } >f1 : Symbol(f1, Decl(a.js, 0, 0)) diff --git a/tests/baselines/reference/importTag24.types b/tests/baselines/reference/importTag24.types index 5164c9413d2e1..1a4387b3d0ca8 100644 --- a/tests/baselines/reference/importTag24.types +++ b/tests/baselines/reference/importTag24.types @@ -10,7 +10,7 @@ export type Foo = string; > : ^^^^^^ === a.js === -/** @import { Foo } from './types.d.ts') */ +/** @import { Foo } from './types.d.ts' */ function f1() { return undefined; } >f1 : () => any diff --git a/tests/cases/conformance/jsdoc/importTag24.ts b/tests/cases/conformance/jsdoc/importTag24.ts index 35e743b5ea648..1db1fe46b9e26 100644 --- a/tests/cases/conformance/jsdoc/importTag24.ts +++ b/tests/cases/conformance/jsdoc/importTag24.ts @@ -12,7 +12,7 @@ export type Foo = string; // @filename: a.js -/** @import { Foo } from './types.d.ts') */ +/** @import { Foo } from './types.d.ts' */ function f1() { return undefined; } From 58f9d2f24e2aa2d72c122a069f7d69ff00369702 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Mon, 20 Jan 2025 01:55:13 +0200 Subject: [PATCH 4/6] add tests --- tests/baselines/reference/importTag25.symbols | 25 +++++++++++++++ tests/baselines/reference/importTag25.types | 32 +++++++++++++++++++ tests/cases/conformance/jsdoc/importTag25.ts | 18 +++++++++++ 3 files changed, 75 insertions(+) create mode 100644 tests/baselines/reference/importTag25.symbols create mode 100644 tests/baselines/reference/importTag25.types create mode 100644 tests/cases/conformance/jsdoc/importTag25.ts diff --git a/tests/baselines/reference/importTag25.symbols b/tests/baselines/reference/importTag25.symbols new file mode 100644 index 0000000000000..2df3cd255937d --- /dev/null +++ b/tests/baselines/reference/importTag25.symbols @@ -0,0 +1,25 @@ +//// [tests/cases/conformance/jsdoc/importTag25.ts] //// + +=== types.d.ts === +export type T = { +>T : Symbol(T, Decl(types.d.ts, 0, 0)) + + a: number; +>a : Symbol(a, Decl(types.d.ts, 0, 17)) + +}; + +=== foo.js === +/** @import { T } from "./types.d.ts" */ + +export default async function f() { +>f : Symbol(f, Decl(foo.js, 0, 0)) + + /** @type {T[]} */ + const types = []; +>types : Symbol(types, Decl(foo.js, 4, 6)) + + return types; +>types : Symbol(types, Decl(foo.js, 4, 6)) +} + diff --git a/tests/baselines/reference/importTag25.types b/tests/baselines/reference/importTag25.types new file mode 100644 index 0000000000000..9e72920ba9677 --- /dev/null +++ b/tests/baselines/reference/importTag25.types @@ -0,0 +1,32 @@ +//// [tests/cases/conformance/jsdoc/importTag25.ts] //// + +=== types.d.ts === +export type T = { +>T : T +> : ^ + + a: number; +>a : number +> : ^^^^^^ + +}; + +=== foo.js === +/** @import { T } from "./types.d.ts" */ + +export default async function f() { +>f : () => Promise +> : ^^^^^^^^^^^^^^^^^^ + + /** @type {T[]} */ + const types = []; +>types : T[] +> : ^^^ +>[] : undefined[] +> : ^^^^^^^^^^^ + + return types; +>types : T[] +> : ^^^ +} + diff --git a/tests/cases/conformance/jsdoc/importTag25.ts b/tests/cases/conformance/jsdoc/importTag25.ts new file mode 100644 index 0000000000000..423ea12b50518 --- /dev/null +++ b/tests/cases/conformance/jsdoc/importTag25.ts @@ -0,0 +1,18 @@ +// @noUnusedLocals: true +// @allowJs: true +// @checkJs: true +// @noEmit: true + +// @filename: types.d.ts +export type T = { + a: number; +}; + +// @filename: foo.js +/** @import { T } from "./types.d.ts" */ + +export default async function f() { + /** @type {T[]} */ + const types = []; + return types; +} From 9649ed067f25081296ac3cf95d05080ba1af0076 Mon Sep 17 00:00:00 2001 From: Oleksandr Tarasiuk Date: Fri, 14 Mar 2025 23:36:46 +0200 Subject: [PATCH 5/6] experiment: treat import tag as a container --- src/compiler/binder.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 147089d51b648..ec44da518f770 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -176,7 +176,6 @@ import { isInTopLevelContext, isJSDocConstructSignature, isJSDocEnumTag, - isJSDocImportTag, isJSDocTemplateTag, isJSDocTypeAlias, isJSDocTypeAssertion, @@ -1109,13 +1108,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { if (canHaveFlowNode(node) && node.flowNode) { node.flowNode = undefined; } - if (isJSDocImportTag(node)) { - bindJSDocImportTag(node); - } - else { - bindEachChild(node); - bindJSDoc(node); - } + bindEachChild(node); + bindJSDoc(node); inAssignmentPattern = saveInAssignmentPattern; return; } @@ -3954,6 +3948,9 @@ export function getContainerFlags(node: Node): ContainerFlags { case SyntaxKind.ClassStaticBlockDeclaration: return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike; + case SyntaxKind.JSDocImportTag: + return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals; + case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike | ContainerFlags.IsFunctionExpression; From d7a3e0c044da51666d7fbce02746d9679ad70b5e Mon Sep 17 00:00:00 2001 From: Oleksandr Tarasiuk Date: Mon, 17 Mar 2025 19:11:40 +0200 Subject: [PATCH 6/6] add clarifying comment --- src/compiler/binder.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index ec44da518f770..4adea95f1f69e 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3949,6 +3949,7 @@ export function getContainerFlags(node: Node): ContainerFlags { return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike; case SyntaxKind.JSDocImportTag: + // treat as a container to prevent using an enclosing effective host, ensuring import bindings are scoped correctly return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals; case SyntaxKind.FunctionExpression: