From a284c814545854deaf78bd2eb5157931c0546795 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 2 Nov 2025 11:45:25 -0800 Subject: [PATCH 1/6] Hoist @typedef in classes to containing scope --- internal/parser/parser.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/internal/parser/parser.go b/internal/parser/parser.go index a51566f02d..5f8a473d2c 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -498,12 +498,21 @@ func (p *Parser) reparseTopLevelAwait(sourceFile *ast.SourceFile) *ast.Node { func (p *Parser) parseListIndex(kind ParsingContext, parseElement func(p *Parser, index int) *ast.Node) []*ast.Node { saveParsingContexts := p.parsingContexts p.parsingContexts |= 1 << kind + outerReparseList := p.reparseList + p.reparseList = nil list := make([]*ast.Node, 0, 16) for i := 0; !p.isListTerminator(kind); i++ { if p.isListElement(kind, false /*inErrorRecovery*/) { elt := parseElement(p, i) if len(p.reparseList) > 0 { - list = append(list, p.reparseList...) + for _, e := range p.reparseList { + // Propagate @typedef type alias declarations outwards to a context that permits them. + if ast.IsJSTypeAliasDeclaration(e) && kind != PCSourceElements && kind != PCBlockStatements { + outerReparseList = append(outerReparseList, e) + } else { + list = append(list, e) + } + } p.reparseList = nil } list = append(list, elt) @@ -513,6 +522,7 @@ func (p *Parser) parseListIndex(kind ParsingContext, parseElement func(p *Parser break } } + p.reparseList = outerReparseList p.parsingContexts = saveParsingContexts return p.nodeSlicePool.Clone(list) } From f29e9f70c86bfe1b7ad7fe69c60913b5820be3a6 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 2 Nov 2025 11:46:10 -0800 Subject: [PATCH 2/6] Accept new baselines --- .../callbackOnConstructor.errors.txt | 20 -------------- .../conformance/callbackOnConstructor.js | 2 +- .../conformance/callbackOnConstructor.js.diff | 2 +- .../conformance/callbackOnConstructor.symbols | 2 ++ .../callbackOnConstructor.symbols.diff | 9 ------- .../conformance/callbackOnConstructor.types | 10 +++---- .../conformance/jsdocTemplateClass.errors.txt | 8 +++++- .../conformance/jsdocTemplateClass.types | 6 ++--- .../typedefInnerNamepaths.errors.txt | 8 +++++- .../typedefOnSemicolonClassElement.js | 25 +---------------- .../typedefOnSemicolonClassElement.js.diff | 27 ++----------------- .../callbackOnConstructor.errors.txt.diff | 24 ----------------- .../callbackOnConstructor.types.diff | 13 +++------ .../jsdocTemplateClass.errors.txt.diff | 24 +++++++++++++++++ .../conformance/jsdocTemplateClass.types.diff | 20 ++++++++++++++ .../typedefInnerNamepaths.errors.txt.diff | 19 +++++-------- 16 files changed, 83 insertions(+), 136 deletions(-) delete mode 100644 testdata/baselines/reference/submodule/conformance/callbackOnConstructor.errors.txt delete mode 100644 testdata/baselines/reference/submodule/conformance/callbackOnConstructor.symbols.diff delete mode 100644 testdata/baselines/reference/submoduleAccepted/conformance/callbackOnConstructor.errors.txt.diff create mode 100644 testdata/baselines/reference/submoduleAccepted/conformance/jsdocTemplateClass.errors.txt.diff create mode 100644 testdata/baselines/reference/submoduleAccepted/conformance/jsdocTemplateClass.types.diff diff --git a/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.errors.txt b/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.errors.txt deleted file mode 100644 index 9b0c649c61..0000000000 --- a/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.errors.txt +++ /dev/null @@ -1,20 +0,0 @@ -callbackOnConstructor.js(12,12): error TS2304: Cannot find name 'ValueGetter_2'. - - -==== callbackOnConstructor.js (1 errors) ==== - export class Preferences { - assignability = "no" - /** - * @callback ValueGetter_2 - * @param {string} name - * @returns {boolean|number|string|undefined} - */ - constructor() {} - } - - - /** @type {ValueGetter_2} */ - ~~~~~~~~~~~~~ -!!! error TS2304: Cannot find name 'ValueGetter_2'. - var ooscope2 = s => s.length > 0 - \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.js b/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.js index 431cb909b3..e9feaf36f4 100644 --- a/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.js +++ b/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.js @@ -35,9 +35,9 @@ var ooscope2 = s => s.length > 0; //// [callbackOnConstructor.d.ts] +export type ValueGetter_2 = (name: string) => boolean | number | string | undefined; export declare class Preferences { assignability: string; - export type ValueGetter_2 = (name: string) => boolean | number | string | undefined; /** * @callback ValueGetter_2 * @param {string} name diff --git a/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.js.diff b/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.js.diff index 45a701f8e7..7650b96ec3 100644 --- a/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.js.diff +++ b/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.js.diff @@ -22,9 +22,9 @@ //// [callbackOnConstructor.d.ts] -export class Preferences { ++export type ValueGetter_2 = (name: string) => boolean | number | string | undefined; +export declare class Preferences { assignability: string; -+ export type ValueGetter_2 = (name: string) => boolean | number | string | undefined; + /** + * @callback ValueGetter_2 + * @param {string} name diff --git a/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.symbols b/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.symbols index 675c7c5e85..d5559f79e0 100644 --- a/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.symbols +++ b/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.symbols @@ -20,5 +20,7 @@ export class Preferences { var ooscope2 = s => s.length > 0 >ooscope2 : Symbol(ooscope2, Decl(callbackOnConstructor.js, 12, 3)) >s : Symbol(s, Decl(callbackOnConstructor.js, 12, 14)) +>s.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) >s : Symbol(s, Decl(callbackOnConstructor.js, 12, 14)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) diff --git a/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.symbols.diff b/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.symbols.diff deleted file mode 100644 index eeddc1fb04..0000000000 --- a/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.symbols.diff +++ /dev/null @@ -1,9 +0,0 @@ ---- old.callbackOnConstructor.symbols -+++ new.callbackOnConstructor.symbols -@@= skipped -19, +19 lines =@@ - var ooscope2 = s => s.length > 0 - >ooscope2 : Symbol(ooscope2, Decl(callbackOnConstructor.js, 12, 3)) - >s : Symbol(s, Decl(callbackOnConstructor.js, 12, 14)) -->s.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) - >s : Symbol(s, Decl(callbackOnConstructor.js, 12, 14)) -->length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) diff --git a/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.types b/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.types index 448a21aa29..ea3a0f2df3 100644 --- a/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.types +++ b/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.types @@ -20,11 +20,11 @@ export class Preferences { /** @type {ValueGetter_2} */ var ooscope2 = s => s.length > 0 >ooscope2 : ValueGetter_2 ->s => s.length > 0 : (s: any) => boolean ->s : any +>s => s.length > 0 : (s: string) => boolean +>s : string >s.length > 0 : boolean ->s.length : any ->s : any ->length : any +>s.length : number +>s : string +>length : number >0 : 0 diff --git a/testdata/baselines/reference/submodule/conformance/jsdocTemplateClass.errors.txt b/testdata/baselines/reference/submodule/conformance/jsdocTemplateClass.errors.txt index 75303c2689..02cf3bbbec 100644 --- a/testdata/baselines/reference/submodule/conformance/jsdocTemplateClass.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/jsdocTemplateClass.errors.txt @@ -1,7 +1,9 @@ +templateTagOnClasses.js(7,23): error TS2304: Cannot find name 'T'. +templateTagOnClasses.js(7,29): error TS2304: Cannot find name 'T'. templateTagOnClasses.js(25,1): error TS2322: Type 'boolean' is not assignable to type 'number'. -==== templateTagOnClasses.js (1 errors) ==== +==== templateTagOnClasses.js (3 errors) ==== /** * @template T * @typedef {(t: T) => T} Id @@ -9,6 +11,10 @@ templateTagOnClasses.js(25,1): error TS2322: Type 'boolean' is not assignable to /** @template T */ class Foo { /** @typedef {(t: T) => T} Id2 */ + ~ +!!! error TS2304: Cannot find name 'T'. + ~ +!!! error TS2304: Cannot find name 'T'. /** @param {T} x */ constructor (x) { this.a = x diff --git a/testdata/baselines/reference/submodule/conformance/jsdocTemplateClass.types b/testdata/baselines/reference/submodule/conformance/jsdocTemplateClass.types index 550c90bd8a..4ba0f951be 100644 --- a/testdata/baselines/reference/submodule/conformance/jsdocTemplateClass.types +++ b/testdata/baselines/reference/submodule/conformance/jsdocTemplateClass.types @@ -29,14 +29,14 @@ class Foo { * @return {T} */ foo(x, y, alpha) { ->foo : (x: T, y: Id, alpha: (t: T) => T) => T +>foo : (x: T, y: Id, alpha: Id2) => T >x : T >y : Id ->alpha : (t: T) => T +>alpha : Id2 return alpha(y(x)) >alpha(y(x)) : T ->alpha : (t: T) => T +>alpha : Id2 >y(x) : T >y : Id >x : T diff --git a/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt b/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt index 2aa319ce1c..5a5e1fedf5 100644 --- a/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt @@ -1,14 +1,20 @@ +bug25104.js(1,7): error TS2300: Duplicate identifier 'C'. bug25104.js(3,19): error TS1005: '}' expected. +bug25104.js(4,26): error TS2300: Duplicate identifier 'C'. bug25104.js(6,18): error TS1005: '}' expected. -==== bug25104.js (2 errors) ==== +==== bug25104.js (4 errors) ==== class C { + ~ +!!! error TS2300: Duplicate identifier 'C'. /** * @typedef {C~A} C~B ~ !!! error TS1005: '}' expected. * @typedef {object} C~A + ~ +!!! error TS2300: Duplicate identifier 'C'. */ /** @param {C~A} o */ ~ diff --git a/testdata/baselines/reference/submodule/conformance/typedefOnSemicolonClassElement.js b/testdata/baselines/reference/submodule/conformance/typedefOnSemicolonClassElement.js index 230abbb8ef..bae46ccae5 100644 --- a/testdata/baselines/reference/submodule/conformance/typedefOnSemicolonClassElement.js +++ b/testdata/baselines/reference/submodule/conformance/typedefOnSemicolonClassElement.js @@ -23,31 +23,8 @@ exports.Preferences = Preferences; //// [typedefOnSemicolonClassElement.d.ts] +export type A = string; export declare class Preferences { - type A = string; /** @type {A} */ a: A; } - - -//// [DtsFileErrors] - - -dist/typedefOnSemicolonClassElement.d.ts(2,5): error TS1068: Unexpected token. A constructor, method, accessor, or property was expected. -dist/typedefOnSemicolonClassElement.d.ts(4,8): error TS2693: 'A' only refers to a type, but is being used as a value here. -dist/typedefOnSemicolonClassElement.d.ts(5,1): error TS1128: Declaration or statement expected. - - -==== dist/typedefOnSemicolonClassElement.d.ts (3 errors) ==== - export declare class Preferences { - type A = string; - ~~~~ -!!! error TS1068: Unexpected token. A constructor, method, accessor, or property was expected. - /** @type {A} */ - a: A; - ~ -!!! error TS2693: 'A' only refers to a type, but is being used as a value here. - } - ~ -!!! error TS1128: Declaration or statement expected. - \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/typedefOnSemicolonClassElement.js.diff b/testdata/baselines/reference/submodule/conformance/typedefOnSemicolonClassElement.js.diff index d4a2b25707..d4bf30386b 100644 --- a/testdata/baselines/reference/submodule/conformance/typedefOnSemicolonClassElement.js.diff +++ b/testdata/baselines/reference/submodule/conformance/typedefOnSemicolonClassElement.js.diff @@ -18,32 +18,9 @@ //// [typedefOnSemicolonClassElement.d.ts] -export class Preferences { ++export type A = string; +export declare class Preferences { -+ type A = string; /** @type {A} */ - a: string; + a: A; - } -+ -+ -+//// [DtsFileErrors] -+ -+ -+dist/typedefOnSemicolonClassElement.d.ts(2,5): error TS1068: Unexpected token. A constructor, method, accessor, or property was expected. -+dist/typedefOnSemicolonClassElement.d.ts(4,8): error TS2693: 'A' only refers to a type, but is being used as a value here. -+dist/typedefOnSemicolonClassElement.d.ts(5,1): error TS1128: Declaration or statement expected. -+ -+ -+==== dist/typedefOnSemicolonClassElement.d.ts (3 errors) ==== -+ export declare class Preferences { -+ type A = string; -+ ~~~~ -+!!! error TS1068: Unexpected token. A constructor, method, accessor, or property was expected. -+ /** @type {A} */ -+ a: A; -+ ~ -+!!! error TS2693: 'A' only refers to a type, but is being used as a value here. -+ } -+ ~ -+!!! error TS1128: Declaration or statement expected. -+ \ No newline at end of file + } \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/callbackOnConstructor.errors.txt.diff b/testdata/baselines/reference/submoduleAccepted/conformance/callbackOnConstructor.errors.txt.diff deleted file mode 100644 index e019fdce7f..0000000000 --- a/testdata/baselines/reference/submoduleAccepted/conformance/callbackOnConstructor.errors.txt.diff +++ /dev/null @@ -1,24 +0,0 @@ ---- old.callbackOnConstructor.errors.txt -+++ new.callbackOnConstructor.errors.txt -@@= skipped -0, +0 lines =@@ -- -+callbackOnConstructor.js(12,12): error TS2304: Cannot find name 'ValueGetter_2'. -+ -+ -+==== callbackOnConstructor.js (1 errors) ==== -+ export class Preferences { -+ assignability = "no" -+ /** -+ * @callback ValueGetter_2 -+ * @param {string} name -+ * @returns {boolean|number|string|undefined} -+ */ -+ constructor() {} -+ } -+ -+ -+ /** @type {ValueGetter_2} */ -+ ~~~~~~~~~~~~~ -+!!! error TS2304: Cannot find name 'ValueGetter_2'. -+ var ooscope2 = s => s.length > 0 -+ \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/callbackOnConstructor.types.diff b/testdata/baselines/reference/submoduleAccepted/conformance/callbackOnConstructor.types.diff index 1f736624c7..2869da9615 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/callbackOnConstructor.types.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/callbackOnConstructor.types.diff @@ -6,15 +6,8 @@ var ooscope2 = s => s.length > 0 ->ooscope2 : (name: string) => boolean | number | string | undefined ->s => s.length > 0 : (s: string) => string | number | boolean -->s : string +>ooscope2 : ValueGetter_2 -+>s => s.length > 0 : (s: any) => boolean -+>s : any ++>s => s.length > 0 : (s: string) => boolean + >s : string >s.length > 0 : boolean -->s.length : number -->s : string -->length : number -+>s.length : any -+>s : any -+>length : any - >0 : 0 + >s.length : number \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsdocTemplateClass.errors.txt.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsdocTemplateClass.errors.txt.diff new file mode 100644 index 0000000000..7aafc7f02d --- /dev/null +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsdocTemplateClass.errors.txt.diff @@ -0,0 +1,24 @@ +--- old.jsdocTemplateClass.errors.txt ++++ new.jsdocTemplateClass.errors.txt +@@= skipped -0, +0 lines =@@ ++templateTagOnClasses.js(7,23): error TS2304: Cannot find name 'T'. ++templateTagOnClasses.js(7,29): error TS2304: Cannot find name 'T'. + templateTagOnClasses.js(25,1): error TS2322: Type 'boolean' is not assignable to type 'number'. + + +-==== templateTagOnClasses.js (1 errors) ==== ++==== templateTagOnClasses.js (3 errors) ==== + /** + * @template T + * @typedef {(t: T) => T} Id +@@= skipped -8, +10 lines =@@ + /** @template T */ + class Foo { + /** @typedef {(t: T) => T} Id2 */ ++ ~ ++!!! error TS2304: Cannot find name 'T'. ++ ~ ++!!! error TS2304: Cannot find name 'T'. + /** @param {T} x */ + constructor (x) { + this.a = x \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsdocTemplateClass.types.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsdocTemplateClass.types.diff new file mode 100644 index 0000000000..46539f9422 --- /dev/null +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsdocTemplateClass.types.diff @@ -0,0 +1,20 @@ +--- old.jsdocTemplateClass.types ++++ new.jsdocTemplateClass.types +@@= skipped -28, +28 lines =@@ + * @return {T} + */ + foo(x, y, alpha) { +->foo : (x: T, y: Id, alpha: (t: T) => T) => T ++>foo : (x: T, y: Id, alpha: Id2) => T + >x : T + >y : Id +->alpha : (t: T) => T ++>alpha : Id2 + + return alpha(y(x)) + >alpha(y(x)) : T +->alpha : (t: T) => T ++>alpha : Id2 + >y(x) : T + >y : Id + >x : T \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/typedefInnerNamepaths.errors.txt.diff b/testdata/baselines/reference/submoduleAccepted/conformance/typedefInnerNamepaths.errors.txt.diff index 668a3e4dd1..db8d8129a0 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/typedefInnerNamepaths.errors.txt.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/typedefInnerNamepaths.errors.txt.diff @@ -1,25 +1,20 @@ --- old.typedefInnerNamepaths.errors.txt +++ new.typedefInnerNamepaths.errors.txt @@= skipped -0, +0 lines =@@ --bug25104.js(1,7): error TS2300: Duplicate identifier 'C'. + bug25104.js(1,7): error TS2300: Duplicate identifier 'C'. bug25104.js(3,19): error TS1005: '}' expected. --bug25104.js(4,26): error TS2300: Duplicate identifier 'C'. + bug25104.js(4,26): error TS2300: Duplicate identifier 'C'. -bug25104.js(6,18): error TS8024: JSDoc '@param' tag has name '', but there is no parameter with that name. bug25104.js(6,18): error TS1005: '}' expected. -==== bug25104.js (5 errors) ==== -+==== bug25104.js (2 errors) ==== ++==== bug25104.js (4 errors) ==== class C { -- ~ --!!! error TS2300: Duplicate identifier 'C'. - /** - * @typedef {C~A} C~B - ~ - !!! error TS1005: '}' expected. - * @typedef {object} C~A -- ~ --!!! error TS2300: Duplicate identifier 'C'. + ~ + !!! error TS2300: Duplicate identifier 'C'. +@@= skipped -17, +16 lines =@@ + !!! error TS2300: Duplicate identifier 'C'. */ /** @param {C~A} o */ - From 4e98df1a6a8b9057958bfade0963ef2f04a3174a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 2 Nov 2025 11:46:20 -0800 Subject: [PATCH 3/6] Add regression test --- .../compiler/typedefHoisting.errors.txt | 13 +++++++++++++ .../reference/compiler/typedefHoisting.js | 19 +++++++++++++++++++ .../compiler/typedefHoisting.symbols | 12 ++++++++++++ .../reference/compiler/typedefHoisting.types | 13 +++++++++++++ .../tests/cases/compiler/typedefHoisting.ts | 9 +++++++++ 5 files changed, 66 insertions(+) create mode 100644 testdata/baselines/reference/compiler/typedefHoisting.errors.txt create mode 100644 testdata/baselines/reference/compiler/typedefHoisting.js create mode 100644 testdata/baselines/reference/compiler/typedefHoisting.symbols create mode 100644 testdata/baselines/reference/compiler/typedefHoisting.types create mode 100644 testdata/tests/cases/compiler/typedefHoisting.ts diff --git a/testdata/baselines/reference/compiler/typedefHoisting.errors.txt b/testdata/baselines/reference/compiler/typedefHoisting.errors.txt new file mode 100644 index 0000000000..596e5b3b4e --- /dev/null +++ b/testdata/baselines/reference/compiler/typedefHoisting.errors.txt @@ -0,0 +1,13 @@ +error TS5055: Cannot write file 'x.js' because it would overwrite input file. + Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig. + + +!!! error TS5055: Cannot write file 'x.js' because it would overwrite input file. +!!! error TS5055: Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig. +==== x.js (0 errors) ==== + class C { + /** @typedef {string} Foo */ + /** @type {Foo} */ + foo = "abc" + } + \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/typedefHoisting.js b/testdata/baselines/reference/compiler/typedefHoisting.js new file mode 100644 index 0000000000..9c0f38d810 --- /dev/null +++ b/testdata/baselines/reference/compiler/typedefHoisting.js @@ -0,0 +1,19 @@ +//// [tests/cases/compiler/typedefHoisting.ts] //// + +//// [x.js] +class C { + /** @typedef {string} Foo */ + /** @type {Foo} */ + foo = "abc" +} + + + + +//// [x.d.ts] +type Foo = string; +declare class C { + /** @typedef {string} Foo */ + /** @type {Foo} */ + foo: Foo; +} diff --git a/testdata/baselines/reference/compiler/typedefHoisting.symbols b/testdata/baselines/reference/compiler/typedefHoisting.symbols new file mode 100644 index 0000000000..bf0c9f43a4 --- /dev/null +++ b/testdata/baselines/reference/compiler/typedefHoisting.symbols @@ -0,0 +1,12 @@ +//// [tests/cases/compiler/typedefHoisting.ts] //// + +=== x.js === +class C { +>C : Symbol(C, Decl(x.js, 0, 0)) + + /** @typedef {string} Foo */ + /** @type {Foo} */ + foo = "abc" +>foo : Symbol(C.foo, Decl(x.js, 0, 9)) +} + diff --git a/testdata/baselines/reference/compiler/typedefHoisting.types b/testdata/baselines/reference/compiler/typedefHoisting.types new file mode 100644 index 0000000000..3c2fc590bc --- /dev/null +++ b/testdata/baselines/reference/compiler/typedefHoisting.types @@ -0,0 +1,13 @@ +//// [tests/cases/compiler/typedefHoisting.ts] //// + +=== x.js === +class C { +>C : C + + /** @typedef {string} Foo */ + /** @type {Foo} */ + foo = "abc" +>foo : string +>"abc" : "abc" +} + diff --git a/testdata/tests/cases/compiler/typedefHoisting.ts b/testdata/tests/cases/compiler/typedefHoisting.ts new file mode 100644 index 0000000000..d64a34edad --- /dev/null +++ b/testdata/tests/cases/compiler/typedefHoisting.ts @@ -0,0 +1,9 @@ +// @allowJs: true +// @checkJs: true +// @declaration: true +// @Filename: x.js +class C { + /** @typedef {string} Foo */ + /** @type {Foo} */ + foo = "abc" +} From 9a747f91676062a1ab4023badcb5e46b7ddcb694 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 2 Nov 2025 11:56:06 -0800 Subject: [PATCH 4/6] Address CR feedback --- testdata/tests/cases/compiler/typedefHoisting.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testdata/tests/cases/compiler/typedefHoisting.ts b/testdata/tests/cases/compiler/typedefHoisting.ts index d64a34edad..7920086a95 100644 --- a/testdata/tests/cases/compiler/typedefHoisting.ts +++ b/testdata/tests/cases/compiler/typedefHoisting.ts @@ -1,7 +1,7 @@ // @allowJs: true // @checkJs: true // @declaration: true -// @Filename: x.js +// @filename: x.js class C { /** @typedef {string} Foo */ /** @type {Foo} */ From 73c6546b7f069c98b5b95afd2dd9044ead91b815 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 3 Nov 2025 08:02:04 -0800 Subject: [PATCH 5/6] Also hoist @import tags --- internal/ast/ast.go | 4 ++++ internal/parser/parser.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/ast/ast.go b/internal/ast/ast.go index 57f92fbbfa..82d4f26e37 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -4845,6 +4845,10 @@ func IsImportDeclaration(node *Node) bool { return node.Kind == KindImportDeclaration } +func IsJSImportDeclaration(node *Node) bool { + return node.Kind == KindJSImportDeclaration +} + func IsImportDeclarationOrJSImportDeclaration(node *Node) bool { return node.Kind == KindImportDeclaration || node.Kind == KindJSImportDeclaration } diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 5f8a473d2c..7ad377c237 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -507,7 +507,7 @@ func (p *Parser) parseListIndex(kind ParsingContext, parseElement func(p *Parser if len(p.reparseList) > 0 { for _, e := range p.reparseList { // Propagate @typedef type alias declarations outwards to a context that permits them. - if ast.IsJSTypeAliasDeclaration(e) && kind != PCSourceElements && kind != PCBlockStatements { + if (ast.IsJSTypeAliasDeclaration(e) || ast.IsJSImportDeclaration(e)) && kind != PCSourceElements && kind != PCBlockStatements { outerReparseList = append(outerReparseList, e) } else { list = append(list, e) From 97c3a6e69ff4433556534509d7de1b3efaed56a6 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 3 Nov 2025 08:02:11 -0800 Subject: [PATCH 6/6] Update test --- .../compiler/typedefHoisting.errors.txt | 18 ++++++++++-- .../reference/compiler/typedefHoisting.js | 29 ++++++++++++++----- .../compiler/typedefHoisting.symbols | 20 +++++++++++-- .../reference/compiler/typedefHoisting.types | 24 ++++++++++++--- .../tests/cases/compiler/typedefHoisting.ts | 15 ++++++++-- 5 files changed, 86 insertions(+), 20 deletions(-) diff --git a/testdata/baselines/reference/compiler/typedefHoisting.errors.txt b/testdata/baselines/reference/compiler/typedefHoisting.errors.txt index 596e5b3b4e..d4264baa84 100644 --- a/testdata/baselines/reference/compiler/typedefHoisting.errors.txt +++ b/testdata/baselines/reference/compiler/typedefHoisting.errors.txt @@ -1,13 +1,25 @@ error TS5055: Cannot write file 'x.js' because it would overwrite input file. Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig. +error TS5055: Cannot write file 'y.js' because it would overwrite input file. + Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig. !!! error TS5055: Cannot write file 'x.js' because it would overwrite input file. !!! error TS5055: Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig. +!!! error TS5055: Cannot write file 'y.js' because it would overwrite input file. +!!! error TS5055: Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig. ==== x.js (0 errors) ==== class C { - /** @typedef {string} Foo */ - /** @type {Foo} */ - foo = "abc" + /** @import {Bar} from "./y" */ + /** @typedef {Bar[]} Bars */ + /** @type {Bars} */ + foo = ["abc", "def"] + bar(/** @type {Bar} */ x) { + return x + } } + +==== y.js (0 errors) ==== + /** @typedef {string} Bar */ + export {} \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/typedefHoisting.js b/testdata/baselines/reference/compiler/typedefHoisting.js index 9c0f38d810..6803d6b512 100644 --- a/testdata/baselines/reference/compiler/typedefHoisting.js +++ b/testdata/baselines/reference/compiler/typedefHoisting.js @@ -2,18 +2,33 @@ //// [x.js] class C { - /** @typedef {string} Foo */ - /** @type {Foo} */ - foo = "abc" + /** @import {Bar} from "./y" */ + /** @typedef {Bar[]} Bars */ + /** @type {Bars} */ + foo = ["abc", "def"] + bar(/** @type {Bar} */ x) { + return x + } } +//// [y.js] +/** @typedef {string} Bar */ +export {} + +//// [y.d.ts] +export type Bar = string; +/** @typedef {string} Bar */ +export {}; //// [x.d.ts] -type Foo = string; +import type { Bar } from "./y"; +type Bars = Bar[]; declare class C { - /** @typedef {string} Foo */ - /** @type {Foo} */ - foo: Foo; + /** @import {Bar} from "./y" */ + /** @typedef {Bar[]} Bars */ + /** @type {Bars} */ + foo: Bars; + bar(/** @type {Bar} */ x: Bar): string; } diff --git a/testdata/baselines/reference/compiler/typedefHoisting.symbols b/testdata/baselines/reference/compiler/typedefHoisting.symbols index bf0c9f43a4..7202639df6 100644 --- a/testdata/baselines/reference/compiler/typedefHoisting.symbols +++ b/testdata/baselines/reference/compiler/typedefHoisting.symbols @@ -4,9 +4,23 @@ class C { >C : Symbol(C, Decl(x.js, 0, 0)) - /** @typedef {string} Foo */ - /** @type {Foo} */ - foo = "abc" + /** @import {Bar} from "./y" */ + /** @typedef {Bar[]} Bars */ + /** @type {Bars} */ + foo = ["abc", "def"] >foo : Symbol(C.foo, Decl(x.js, 0, 9)) + + bar(/** @type {Bar} */ x) { +>bar : Symbol(C.bar, Decl(x.js, 4, 24)) +>x : Symbol(x, Decl(x.js, 5, 8)) + + return x +>x : Symbol(x, Decl(x.js, 5, 8)) + } } +=== y.js === + +/** @typedef {string} Bar */ +export {} + diff --git a/testdata/baselines/reference/compiler/typedefHoisting.types b/testdata/baselines/reference/compiler/typedefHoisting.types index 3c2fc590bc..5b60cac604 100644 --- a/testdata/baselines/reference/compiler/typedefHoisting.types +++ b/testdata/baselines/reference/compiler/typedefHoisting.types @@ -4,10 +4,26 @@ class C { >C : C - /** @typedef {string} Foo */ - /** @type {Foo} */ - foo = "abc" ->foo : string + /** @import {Bar} from "./y" */ + /** @typedef {Bar[]} Bars */ + /** @type {Bars} */ + foo = ["abc", "def"] +>foo : Bars +>["abc", "def"] : string[] >"abc" : "abc" +>"def" : "def" + + bar(/** @type {Bar} */ x) { +>bar : (x: string) => string +>x : string + + return x +>x : string + } } +=== y.js === + +/** @typedef {string} Bar */ +export {} + diff --git a/testdata/tests/cases/compiler/typedefHoisting.ts b/testdata/tests/cases/compiler/typedefHoisting.ts index 7920086a95..80683421ee 100644 --- a/testdata/tests/cases/compiler/typedefHoisting.ts +++ b/testdata/tests/cases/compiler/typedefHoisting.ts @@ -1,9 +1,18 @@ // @allowJs: true // @checkJs: true // @declaration: true + // @filename: x.js class C { - /** @typedef {string} Foo */ - /** @type {Foo} */ - foo = "abc" + /** @import {Bar} from "./y" */ + /** @typedef {Bar[]} Bars */ + /** @type {Bars} */ + foo = ["abc", "def"] + bar(/** @type {Bar} */ x) { + return x + } } + +// @filename: y.js +/** @typedef {string} Bar */ +export {}