Skip to content

Preserve comments when downleveling arrow expression bodies - #4723

Open
Jake Bailey (jakebailey) with Copilot wants to merge 7 commits into
mainfrom
copilot/fix-esi2018-nested-coalescing
Open

Preserve comments when downleveling arrow expression bodies#4723
Jake Bailey (jakebailey) with Copilot wants to merge 7 commits into
mainfrom
copilot/fix-esi2018-nested-coalescing

Conversation

Copilot AI commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Downlevel optional chaining can hoist temporaries from an arrow expression body, forcing the body to become a block with a synthesized return. When the expression had a leading comment, that comment emitted after return, triggering ASI and skipping the expression.

  • Emit fix

    • Propagate the original expression range to the synthesized return statement.
    • This keeps leading comments attached before return instead of between return and the returned expression.
  • Regression coverage

    • Added an ES2018 compiler test covering an arrow expression body with a leading comment and optional chaining.
const wat = () =>
    // explanatory comment
    thing?.nested?.condition ? "pass" : "fail";

Now emits with the comment before return, avoiding return\n... ASI behavior.

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
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
if !ast.IsBlock(updated) {
statements := c.MergeEnvironment([]*ast.Statement{c.Factory.NewReturnStatement(updated)}, declarations)
returnStatement := c.Factory.NewReturnStatement(updated)
returnStatement.Loc = updated.Loc

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.

This clones the expression's pos to the return statement - which is fine IF you also suppress comments on updated. As it is now, there may be duplicated comments in some situations (inline comments, likely).

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.

Copilot think harder, ground yourself in Strada's code, etc etc

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 38e7d06. I now suppress comments on the rewritten expression when synthesizing the return in VisitFunctionBody, so we keep the ASI-safe return positioning without re-emitting comments from updated.

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.

Copilot did you actually have a test for this that showed the difference?

definitely consider redoing the stack such that the first commit is all tests, the second commit is all fixes indicating that things changed

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
@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;

Copilot AI and others added 2 commits August 10, 2026 17:07
Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Comment on lines +913 to +919
if sourceFile != nil {
tokenPos := scanner.GetTokenPosOfNode(parseNode, sourceFile, false /*includeJSDoc*/)
if tokenPos > parseNode.Pos() &&
scanner.GetECMALineOfPosition(sourceFile, parseNode.Pos()) == scanner.GetECMALineOfPosition(sourceFile, tokenPos) {
c.AddEmitFlags(returnStatement, EFNoLeadingComments)
}
}

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.

Copilot this is some interesting code; what did Strada do?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Strada's convertToFunctionBlock simply applies the expression's text range to both the synthesized return and block (factory/nodeConverters.ts:54-60). It has no scanner check or comment suppression. That currently duplicates same-line comments, while suppressing return comments unconditionally loses preceding-line comments in tsgo; this conditional preserves the preceding-line case while avoiding the inline duplicate.

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.

Copilot Is there precedence for this in the repo? Existing helpers? Existing cases in the old compiler, even?

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>

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.

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