From cceead31329ad940aed5d3bb5ecf34d99d0de39b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 19:41:50 +0000 Subject: [PATCH 1/4] Initial plan From 96c99756d9b687b4b5b84a8b96d571831cc01972 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 20:12:23 +0000 Subject: [PATCH 2/4] Fix panic in emitEmbeddedStatement when node is nil When a type-only export (e.g., `export type {}`) is used as the body of an if statement, the transformer elides it during emit, resulting in a nil node being passed to emitEmbeddedStatement. Add a nil check to emit a semicolon (empty statement) instead of panicking. Fixes #1434 Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/e1fda50b-565f-4581-b127-a3a85ead111f Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/printer/printer.go | 7 +++++++ .../compiler/typeOnlyExportAsIfBody.errors.txt | 8 ++++++++ .../reference/compiler/typeOnlyExportAsIfBody.js | 10 ++++++++++ .../reference/compiler/typeOnlyExportAsIfBody.symbols | 6 ++++++ .../reference/compiler/typeOnlyExportAsIfBody.types | 6 ++++++ .../tests/cases/compiler/typeOnlyExportAsIfBody.ts | 4 ++++ 6 files changed, 41 insertions(+) create mode 100644 testdata/baselines/reference/compiler/typeOnlyExportAsIfBody.errors.txt create mode 100644 testdata/baselines/reference/compiler/typeOnlyExportAsIfBody.js create mode 100644 testdata/baselines/reference/compiler/typeOnlyExportAsIfBody.symbols create mode 100644 testdata/baselines/reference/compiler/typeOnlyExportAsIfBody.types create mode 100644 testdata/tests/cases/compiler/typeOnlyExportAsIfBody.ts diff --git a/internal/printer/printer.go b/internal/printer/printer.go index cab32e2589..d2c3bf310a 100644 --- a/internal/printer/printer.go +++ b/internal/printer/printer.go @@ -4092,6 +4092,13 @@ func (p *Printer) emitExportSpecifierNode(node *ast.ExportSpecifierNode) { } func (p *Printer) emitEmbeddedStatement(parentNode *ast.Node, node *ast.Statement) { + if node == nil { + p.writeLine() + p.increaseIndent() + p.writePunctuation(";") + p.decreaseIndent() + return + } if ast.IsBlock(node) || p.shouldEmitOnSingleLine(parentNode) || p.Options.PreserveSourceNewlines && p.getLeadingLineTerminatorCount(parentNode, node, LFNone) == 0 { diff --git a/testdata/baselines/reference/compiler/typeOnlyExportAsIfBody.errors.txt b/testdata/baselines/reference/compiler/typeOnlyExportAsIfBody.errors.txt new file mode 100644 index 0000000000..2dec61a017 --- /dev/null +++ b/testdata/baselines/reference/compiler/typeOnlyExportAsIfBody.errors.txt @@ -0,0 +1,8 @@ +typeOnlyExportAsIfBody.ts(1,11): error TS1233: An export declaration can only be used at the top level of a namespace or module. + + +==== typeOnlyExportAsIfBody.ts (1 errors) ==== + if (true) export type {}; + ~~~~~~ +!!! error TS1233: An export declaration can only be used at the top level of a namespace or module. + \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/typeOnlyExportAsIfBody.js b/testdata/baselines/reference/compiler/typeOnlyExportAsIfBody.js new file mode 100644 index 0000000000..cabf288d52 --- /dev/null +++ b/testdata/baselines/reference/compiler/typeOnlyExportAsIfBody.js @@ -0,0 +1,10 @@ +//// [tests/cases/compiler/typeOnlyExportAsIfBody.ts] //// + +//// [typeOnlyExportAsIfBody.ts] +if (true) export type {}; + + +//// [typeOnlyExportAsIfBody.js] +"use strict"; +if (true) + ; diff --git a/testdata/baselines/reference/compiler/typeOnlyExportAsIfBody.symbols b/testdata/baselines/reference/compiler/typeOnlyExportAsIfBody.symbols new file mode 100644 index 0000000000..0e5778eb1c --- /dev/null +++ b/testdata/baselines/reference/compiler/typeOnlyExportAsIfBody.symbols @@ -0,0 +1,6 @@ +//// [tests/cases/compiler/typeOnlyExportAsIfBody.ts] //// + +=== typeOnlyExportAsIfBody.ts === + +if (true) export type {}; + diff --git a/testdata/baselines/reference/compiler/typeOnlyExportAsIfBody.types b/testdata/baselines/reference/compiler/typeOnlyExportAsIfBody.types new file mode 100644 index 0000000000..65291b44f5 --- /dev/null +++ b/testdata/baselines/reference/compiler/typeOnlyExportAsIfBody.types @@ -0,0 +1,6 @@ +//// [tests/cases/compiler/typeOnlyExportAsIfBody.ts] //// + +=== typeOnlyExportAsIfBody.ts === +if (true) export type {}; +>true : true + diff --git a/testdata/tests/cases/compiler/typeOnlyExportAsIfBody.ts b/testdata/tests/cases/compiler/typeOnlyExportAsIfBody.ts new file mode 100644 index 0000000000..5c025ff045 --- /dev/null +++ b/testdata/tests/cases/compiler/typeOnlyExportAsIfBody.ts @@ -0,0 +1,4 @@ +// @module: esnext +// @moduleResolution: bundler + +if (true) export type {}; From 806c52efc3281fcd674a1371df3829c40598982d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 20:53:21 +0000 Subject: [PATCH 3/4] Move fix from printer to EmitContext.VisitEmbeddedStatement Instead of nil-guarding in the printer's emitEmbeddedStatement, fix the upstream issue in EmitContext.VisitEmbeddedStatement. When a visitor returns nil for an embedded statement (e.g., type-only export elided by the type eraser), create an EmptyStatement instead of propagating nil. This matches the existing handling of NotEmittedStatement nodes. Fixes #1434 Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/d30d9b60-0d66-4539-b25a-e9d521e0c8d4 Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/printer/emitcontext.go | 9 ++++++++- internal/printer/printer.go | 7 ------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/printer/emitcontext.go b/internal/printer/emitcontext.go index ca5d395912..62656a5756 100644 --- a/internal/printer/emitcontext.go +++ b/internal/printer/emitcontext.go @@ -945,7 +945,14 @@ func (c *EmitContext) VisitIterationBody(body *ast.Statement, visitor *ast.NodeV func (c *EmitContext) VisitEmbeddedStatement(node *ast.Statement, visitor *ast.NodeVisitor) *ast.Statement { embeddedStatement := visitor.VisitEmbeddedStatement(node) if embeddedStatement == nil { - return nil + if node == nil { + return nil + } + emptyStatement := visitor.Factory.NewEmptyStatement() + emptyStatement.Loc = node.Loc + c.SetOriginal(emptyStatement, node) + c.AssignCommentRange(emptyStatement, node) + return emptyStatement } if ast.IsNotEmittedStatement(embeddedStatement) { emptyStatement := visitor.Factory.NewEmptyStatement() diff --git a/internal/printer/printer.go b/internal/printer/printer.go index d2c3bf310a..cab32e2589 100644 --- a/internal/printer/printer.go +++ b/internal/printer/printer.go @@ -4092,13 +4092,6 @@ func (p *Printer) emitExportSpecifierNode(node *ast.ExportSpecifierNode) { } func (p *Printer) emitEmbeddedStatement(parentNode *ast.Node, node *ast.Statement) { - if node == nil { - p.writeLine() - p.increaseIndent() - p.writePunctuation(";") - p.decreaseIndent() - return - } if ast.IsBlock(node) || p.shouldEmitOnSingleLine(parentNode) || p.Options.PreserveSourceNewlines && p.getLeadingLineTerminatorCount(parentNode, node, LFNone) == 0 { From 19431b9b4e3481177783a73f5db54c7d8d6ca368 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 20:55:01 +0000 Subject: [PATCH 4/4] Clean up VisitEmbeddedStatement: early nil check and deduplicate branches Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/d30d9b60-0d66-4539-b25a-e9d521e0c8d4 Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/printer/emitcontext.go | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/internal/printer/emitcontext.go b/internal/printer/emitcontext.go index 62656a5756..b81fc97a44 100644 --- a/internal/printer/emitcontext.go +++ b/internal/printer/emitcontext.go @@ -943,18 +943,11 @@ func (c *EmitContext) VisitIterationBody(body *ast.Statement, visitor *ast.NodeV } func (c *EmitContext) VisitEmbeddedStatement(node *ast.Statement, visitor *ast.NodeVisitor) *ast.Statement { - embeddedStatement := visitor.VisitEmbeddedStatement(node) - if embeddedStatement == nil { - if node == nil { - return nil - } - emptyStatement := visitor.Factory.NewEmptyStatement() - emptyStatement.Loc = node.Loc - c.SetOriginal(emptyStatement, node) - c.AssignCommentRange(emptyStatement, node) - return emptyStatement + if node == nil { + return nil } - if ast.IsNotEmittedStatement(embeddedStatement) { + embeddedStatement := visitor.VisitEmbeddedStatement(node) + if embeddedStatement == nil || ast.IsNotEmittedStatement(embeddedStatement) { emptyStatement := visitor.Factory.NewEmptyStatement() emptyStatement.Loc = node.Loc c.SetOriginal(emptyStatement, node)