Skip to content

Preserve comments when downleveling arrow expression bodies - #4723

Merged
Wesley Wigham (weswigham) merged 4 commits into
mainfrom
copilot/fix-esi2018-nested-coalescing
Aug 19, 2026
Merged

Preserve comments when downleveling arrow expression bodies#4723
Wesley Wigham (weswigham) merged 4 commits into
mainfrom
copilot/fix-esi2018-nested-coalescing

Conversation

Copilot AI commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Fixes #4722

Analysis

Downleveling optional chains can introduce temporary variable declarations into an arrow function with an expression body. VisitFunctionBody then rewrites the expression body into a block containing those declarations and a synthesized return.

The synthesized return previously had no source range. Leading comments were therefore emitted with the returned expression, after the return keyword:

return
// comment
expression;

The newline triggers ASI, so the function returns undefined without evaluating the expression.

Fix

Apply the original expression range to both the synthesized return and block, matching Strada's convertToFunctionBlock. Suppress comment emission on the rewritten expression so comments are owned by the enclosing synthesized statement instead of being emitted after return.

The compiler regression covers:

  • the original nested optional-chain report
  • the reported single optional-chain variant
  • same-line and multiline block comments
  • an inline block comment followed by a leading line comment

The first commit contains the test and known-bad baselines. The second contains the implementation and corrected JavaScript baseline. Inline comments retain Strada's existing duplicated emit behavior.

Copilot Checklist

I successfully ran these commands at the end of my session, and they completed without error:

  • npx hereby build
  • npx hereby test
  • npx hereby lint
  • npx hereby format

Copilot AI changed the title [WIP] Fix nested nullish coalescing with comments in ES2018 transpilation Preserve comments when downleveling arrow expression bodies Jul 23, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Preserves leading comments when arrow expression bodies are downleveled into blocks, preventing ASI from skipping returned expressions.

Changes:

  • Copies the expression source range to synthesized return statements.
  • Adds compiler regression coverage and baselines.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
internal/printer/emitcontext.go Preserves the expression range on synthesized returns.
testdata/tests/cases/compiler/optionalChainingArrowFunctionCommentEs2018.ts Adds the regression case.
testdata/baselines/reference/compiler/optionalChainingArrowFunctionCommentEs2018.js Verifies correct comment placement in emitted JavaScript.
testdata/baselines/reference/compiler/optionalChainingArrowFunctionCommentEs2018.types Records type output.
testdata/baselines/reference/compiler/optionalChainingArrowFunctionCommentEs2018.symbols Records symbol output.

@jakebailey
Jake Bailey (jakebailey) marked this pull request as ready for review July 23, 2026 21:01
Comment thread internal/printer/emitcontext.go Outdated
@jakebailey

Copy link
Copy Markdown
Member

Copilot

also consider this test case from #4722 (comment)

// m.ts
declare const o: { a?: number[] };

export const f = () =>
  // comment
  o.a?.length;

Comment thread internal/printer/emitcontext.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

@weswigham Wesley Wigham (weswigham) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think it's a bit suspect that both the return statement and the containing block are getting the same text range set (at least without disabling some subset of comment emit on each) - to me, that seems like a recipe for getting some kinda function() /*comment*/ { /*comment*/ return expr; } duplicate output.

@jakebailey

Copy link
Copy Markdown
Member

@weswigham Wesley Wigham (weswigham) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right I suppose. Though I'm a little weirded out by the duplicate location-set in strada, too.

But! Another good point from that link brings up - there's already a convertToFunctionBlock in async.go (which actually has a triple identical .Loc set) that we should extract to a helper function and reuse in both places, so when we go back to fix that duplicate location down the line when it tracks back to the root cause of some weird comment emit issue, we have only one place to fix and not two.

@jakebailey

Copy link
Copy Markdown
Member

Copilot Please address the above :)

Copilot AI and others added 2 commits August 18, 2026 22:22
Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>

Copilot AI commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

Copilot Please address the above :)

Addressed in aca5f3b and 70c3165. ConvertToFunctionBlock is now shared by VisitFunctionBody and the async transform, with the async-only original-node attribution preserved at its call site.

Copilot

also consider this test case from #4722 (comment)
...

Covered by the regression in de6d68f; the supplied single optional-chain case is included verbatim and its emitted comment placement is validated.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@weswigham
Wesley Wigham (weswigham) added this pull request to the merge queue Aug 19, 2026
Merged via the queue into main with commit aeff766 Aug 19, 2026
22 checks passed
@weswigham
Wesley Wigham (weswigham) deleted the copilot/fix-esi2018-nested-coalescing branch August 19, 2026 21:11
Jake Bailey (jakebailey) added a commit to jakebailey/TypeScript that referenced this pull request Aug 19, 2026
…t/typescript-go#4723)

Co-authored-by: Jake Bailey <5341706+jakebailey@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nested nullish coalescing + comment + ES2018 causes function body to be ignored

4 participants