From 8e7ca455f61b36bd914cc9604cf3712295dc5707 Mon Sep 17 00:00:00 2001 From: Maksim Romanov Date: Thu, 1 Oct 2026 22:54:36 +0200 Subject: [PATCH 1/3] Add test for empty named imports next to a default import --- ...mModuleSyntaxDefaultImportTypeOnlyNamed.js | 32 +++++++++++ ...leSyntaxDefaultImportTypeOnlyNamed.symbols | 52 ++++++++++++++++++ ...duleSyntaxDefaultImportTypeOnlyNamed.types | 53 +++++++++++++++++++ ...mModuleSyntaxDefaultImportTypeOnlyNamed.ts | 21 ++++++++ 4 files changed, 158 insertions(+) create mode 100644 tsc/testdata/baselines/reference/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.js create mode 100644 tsc/testdata/baselines/reference/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.types create mode 100644 tsc/testdata/tests/cases/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.ts diff --git a/tsc/testdata/baselines/reference/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.js b/tsc/testdata/baselines/reference/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.js new file mode 100644 index 0000000000000..eea7b581059ae --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.js @@ -0,0 +1,32 @@ +//// [tests/cases/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.ts] //// + +//// [a.ts] +export default class A {} +export type T = number; +export const v = 0; + +//// [b.ts] +import A1, { type T } from "./a"; +import A2, { type T as T2, v } from "./a"; +import { type T as T3 } from "./a"; +import A4, {} from "./a"; +import A5, { /* comment */ type T as T5 } from "./a"; +import A6, { + type T as T6, +} from "./a"; + +export { A1, A2, A4, A5, A6, v }; + + +//// [a.js] +export default class A { +} +export const v = 0; +//// [b.js] +import A1, {} from "./a"; +import A2, { v } from "./a"; +import {} from "./a"; +import A4, {} from "./a"; +import A5, {} from "./a"; +import A6, {} from "./a"; +export { A1, A2, A4, A5, A6, v }; diff --git a/tsc/testdata/baselines/reference/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.symbols b/tsc/testdata/baselines/reference/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.symbols new file mode 100644 index 0000000000000..69ba53c154567 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.symbols @@ -0,0 +1,52 @@ +//// [tests/cases/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.ts] //// + +=== /a.ts === +export default class A {} +>A : Symbol(A, Decl(a.ts, 0, 0)) + +export type T = number; +>T : Symbol(T, Decl(a.ts, 0, 25)) + +export const v = 0; +>v : Symbol(v, Decl(a.ts, 2, 12)) + +=== /b.ts === +import A1, { type T } from "./a"; +>A1 : Symbol(A1, Decl(b.ts, 0, 6)) +>T : Symbol(T, Decl(b.ts, 0, 12)) + +import A2, { type T as T2, v } from "./a"; +>A2 : Symbol(A2, Decl(b.ts, 1, 6)) +>T : Symbol(T, Decl(a.ts, 0, 25)) +>T2 : Symbol(T2, Decl(b.ts, 1, 12)) +>v : Symbol(v, Decl(b.ts, 1, 26)) + +import { type T as T3 } from "./a"; +>T : Symbol(T, Decl(a.ts, 0, 25)) +>T3 : Symbol(T3, Decl(b.ts, 2, 8)) + +import A4, {} from "./a"; +>A4 : Symbol(A4, Decl(b.ts, 3, 6)) + +import A5, { /* comment */ type T as T5 } from "./a"; +>A5 : Symbol(A5, Decl(b.ts, 4, 6)) +>T : Symbol(T, Decl(a.ts, 0, 25)) +>T5 : Symbol(T5, Decl(b.ts, 4, 12)) + +import A6, { +>A6 : Symbol(A6, Decl(b.ts, 5, 6)) + + type T as T6, +>T : Symbol(T, Decl(a.ts, 0, 25)) +>T6 : Symbol(T6, Decl(b.ts, 5, 12)) + +} from "./a"; + +export { A1, A2, A4, A5, A6, v }; +>A1 : Symbol(A1, Decl(b.ts, 9, 8)) +>A2 : Symbol(A2, Decl(b.ts, 9, 12)) +>A4 : Symbol(A4, Decl(b.ts, 9, 16)) +>A5 : Symbol(A5, Decl(b.ts, 9, 20)) +>A6 : Symbol(A6, Decl(b.ts, 9, 24)) +>v : Symbol(v, Decl(b.ts, 9, 28)) + diff --git a/tsc/testdata/baselines/reference/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.types b/tsc/testdata/baselines/reference/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.types new file mode 100644 index 0000000000000..c1a21faad0abb --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.types @@ -0,0 +1,53 @@ +//// [tests/cases/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.ts] //// + +=== /a.ts === +export default class A {} +>A : A + +export type T = number; +>T : number + +export const v = 0; +>v : 0 +>0 : 0 + +=== /b.ts === +import A1, { type T } from "./a"; +>A1 : typeof A1 +>T : any + +import A2, { type T as T2, v } from "./a"; +>A2 : typeof A1 +>T : any +>T2 : any +>v : 0 + +import { type T as T3 } from "./a"; +>T : any +>T3 : any + +import A4, {} from "./a"; +>A4 : typeof A1 + +import A5, { /* comment */ type T as T5 } from "./a"; +>A5 : typeof A1 +>T : any +>T5 : any + +import A6, { +>A6 : typeof A1 + + type T as T6, +>T : any +>T6 : any + +} from "./a"; + +export { A1, A2, A4, A5, A6, v }; +>A1 : typeof A1 +>A2 : typeof A1 +>A4 : typeof A1 +>A5 : typeof A1 +>A6 : typeof A1 +>v : 0 + diff --git a/tsc/testdata/tests/cases/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.ts b/tsc/testdata/tests/cases/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.ts new file mode 100644 index 0000000000000..6cbcb27edb62a --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.ts @@ -0,0 +1,21 @@ +// @target: esnext +// @module: esnext +// @moduleResolution: bundler +// @verbatimModuleSyntax: true + +// @Filename: /a.ts +export default class A {} +export type T = number; +export const v = 0; + +// @Filename: /b.ts +import A1, { type T } from "./a"; +import A2, { type T as T2, v } from "./a"; +import { type T as T3 } from "./a"; +import A4, {} from "./a"; +import A5, { /* comment */ type T as T5 } from "./a"; +import A6, { + type T as T6, +} from "./a"; + +export { A1, A2, A4, A5, A6, v }; From d3cc77f35934af64836b051e667148837d9a2d36 Mon Sep 17 00:00:00 2001 From: Maksim Romanov Date: Thu, 1 Oct 2026 22:54:36 +0200 Subject: [PATCH 2/3] Elide empty named imports next to a default import under verbatimModuleSyntax With verbatimModuleSyntax, erasing every type-only specifier next to a default import used to leave behind an empty {}. The default binding already keeps the import, so the empty braces are dropped now. Braces written in the source and imports without a default binding are unchanged. Fixes #62239 --- tsc/internal/transformers/tstransforms/typeeraser.go | 6 ++++++ .../verbatimModuleSyntaxDefaultImportTypeOnlyNamed.js | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tsc/internal/transformers/tstransforms/typeeraser.go b/tsc/internal/transformers/tstransforms/typeeraser.go index 0951065f645a4..115d8f02e65e6 100644 --- a/tsc/internal/transformers/tstransforms/typeeraser.go +++ b/tsc/internal/transformers/tstransforms/typeeraser.go @@ -316,6 +316,12 @@ func (tx *TypeEraserTransformer) visit(node *ast.Node) *ast.Node { } name := n.Name() namedBindings := tx.Visitor().VisitNode(n.NamedBindings) + if name != nil && namedBindings != nil && ast.IsNamedImports(namedBindings) && + len(namedBindings.AsNamedImports().Elements.Nodes) == 0 && + len(n.NamedBindings.AsNamedImports().Elements.Nodes) != 0 { + // the default binding keeps the import; a source-written {} is left as is + namedBindings = nil + } if name == nil && namedBindings == nil { // all import bindings were elided return nil diff --git a/tsc/testdata/baselines/reference/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.js b/tsc/testdata/baselines/reference/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.js index eea7b581059ae..0c74b3089b094 100644 --- a/tsc/testdata/baselines/reference/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.js +++ b/tsc/testdata/baselines/reference/compiler/verbatimModuleSyntaxDefaultImportTypeOnlyNamed.js @@ -23,10 +23,10 @@ export default class A { } export const v = 0; //// [b.js] -import A1, {} from "./a"; +import A1 from "./a"; import A2, { v } from "./a"; import {} from "./a"; import A4, {} from "./a"; -import A5, {} from "./a"; -import A6, {} from "./a"; +import A5 from "./a"; +import A6 from "./a"; export { A1, A2, A4, A5, A6, v }; From baddb99ac17d63043039833864a4a7ba7a27ef62 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Thu, 1 Oct 2026 16:34:05 -0700 Subject: [PATCH 3/3] Apply suggestion from @RyanCavanaugh --- tsc/internal/transformers/tstransforms/typeeraser.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tsc/internal/transformers/tstransforms/typeeraser.go b/tsc/internal/transformers/tstransforms/typeeraser.go index 115d8f02e65e6..0cf768b9d894a 100644 --- a/tsc/internal/transformers/tstransforms/typeeraser.go +++ b/tsc/internal/transformers/tstransforms/typeeraser.go @@ -316,6 +316,7 @@ func (tx *TypeEraserTransformer) visit(node *ast.Node) *ast.Node { } name := n.Name() namedBindings := tx.Visitor().VisitNode(n.NamedBindings) + // Empty {} due to type-only import erasure can be skipped if there is also a default import if name != nil && namedBindings != nil && ast.IsNamedImports(namedBindings) && len(namedBindings.AsNamedImports().Elements.Nodes) == 0 && len(n.NamedBindings.AsNamedImports().Elements.Nodes) != 0 {