Skip to content

Fix format selection deleting comments when selection ends inside comment trivia#3159

Open
Copilot wants to merge 5 commits intomainfrom
copilot/fix-js-ts-format-selection-comments
Open

Fix format selection deleting comments when selection ends inside comment trivia#3159
Copilot wants to merge 5 commits intomainfrom
copilot/fix-js-ts-format-selection-comments

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 19, 2026

  • Write test for format selection deleting comments when selection ends inside a comment
  • Fix the bug in consumeTokenAndAdvanceScanner to cap previousRangeTriviaEnd before unprocessed comment trivia
  • Verify formatSelectionEditAtEndOfRange test still passes
  • Address review comments: strengthen assertions to exact equality, fix misleading comments, rename subtest
Original prompt

This section details on the original issue you should resolve

<issue_title>[bug] JS/TS Format Selection can delete comments</issue_title>
<issue_description>### Extension Version

0.20251214.1

VS Code Version

1.107.0

Operating system Version

Windows 11

Steps to reproduce

  1. open a javascript or typescript file
  2. create a block comment in such a way that a space will be inserted next to it when formatting
const test/* comment */=5;
  1. select any part of the comment (except the ending /)
  2. right click => Format Selection
Image
  1. comment gets deleted ❌
Image
const test =5;

Issue

Format Selection can delete comments

<agent_instructions>Write a test for this FIRST. COMMIT THAT! Then, fix the bug</agent_instructions>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits March 19, 2026 06:43
…nside comment

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

When the selection range ends inside a block comment (e.g., selecting
part of /* comment */ in const test/* comment */=5;), the trailing edit
block's contiguity check would incorrectly pass because
previousRangeTriviaEnd was set to the end of the comment (beyond the
selection range). This caused processPair to generate edits that replaced
the comment text between previousRange and the next token, effectively
deleting the comment.

The fix adds an additional guard: previousRangeTriviaEnd must not exceed
originalRange.End() for the trailing edit to proceed. When trivia extends
beyond the selection range, it was not actually processed, so any edit
spanning that trivia would be bogus.

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Copilot AI changed the title [WIP] [bug] Fix JS/TS format selection deleting comments Fix format selection deleting comments when selection ends inside comment trivia Mar 19, 2026
Copilot AI requested a review from jakebailey March 19, 2026 06:47
@jakebailey
Copy link
Copy Markdown
Member

@copilot you must not have run the tests. you broke formatSelectionEditAtEndOfRange_test

…ectionEditAtEndOfRange

The previous fix added a bounds check in the trailing edit block that was
too broad - it blocked legitimate trailing edits when non-comment trivia
(like newlines) extended past the original range.

The new fix is in consumeTokenAndAdvanceScanner: when setting
previousRangeTriviaEnd, cap it before any comment trivia that extends
past the original range. This way, the contiguity check in execute()
correctly fails for unprocessed comments but still works for legitimate
cases like semicolon removal where only newline trivia extends past.

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 19, 2026

@copilot you must not have run the tests. you broke formatSelectionEditAtEndOfRange_test

Fixed in 82bec94. The previous fix was too broad — it blocked all trailing edits when any trivia extended past the range, including legitimate cases like newline trivia in the semicolon-removal test.

Moved the fix to consumeTokenAndAdvanceScanner instead: when setting previousRangeTriviaEnd, it now caps it before any comment trivia not contained by originalRange. This way newline trivia (like in formatSelectionEditAtEndOfRange) still allows the trailing edit, but unprocessed comment trivia blocks it. Verified all format selection gen tests pass (including formatSelectionEditAtEndOfRange).

@jakebailey jakebailey marked this pull request as ready for review April 6, 2026 23:40
Copilot AI review requested due to automatic review settings April 6, 2026 23:40
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a formatter bug in the Go port’s format selection path where a selection ending inside trailing block-comment trivia could cause the comment to be deleted due to an incorrect “contiguous tokens” check.

Changes:

  • Adjust consumeTokenAndAdvanceScanner to cap previousRangeTriviaEnd before any trailing comment trivia that is not fully contained by the original selection range.
  • Add regression tests covering format-selection ranges that start/end inside a block comment, plus a sanity check for full-document formatting.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
internal/format/span.go Prevents trailing-edit pairing across unprocessed (out-of-range) trailing comment trivia.
internal/format/comment_test.go Adds regression coverage to ensure format selection doesn’t delete block comments.

Comment thread internal/format/comment_test.go Outdated
Comment on lines +291 to +296
// Select a range that starts at the beginning of the line and ends inside the comment
// "const test/* comment" - selecting up to but not including the closing "*/"
// The comment "/* comment */" starts at position 10 and ends at position 24
// We select up to position 22 (inside the comment, before "*/)
commentStart := strings.Index(originalText, "/*")
selectionEnd := commentStart + len("/* comment") // ends inside the comment
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The comment describing comment start/end positions appears incorrect for const test/* comment */=5; (and the quoted "*/) is missing a closing quote). This makes the test harder to trust/maintain; consider either removing the hard-coded positions or correcting them based on the actual string indices / ranges used in the test.

Suggested change
// Select a range that starts at the beginning of the line and ends inside the comment
// "const test/* comment" - selecting up to but not including the closing "*/"
// The comment "/* comment */" starts at position 10 and ends at position 24
// We select up to position 22 (inside the comment, before "*/)
commentStart := strings.Index(originalText, "/*")
selectionEnd := commentStart + len("/* comment") // ends inside the comment
// Select a range that starts at the beginning of the line and ends inside the block comment.
// This covers `const test/* comment`, stopping before the closing `*/`.
// Compute the comment start from the actual source text rather than relying on hard-coded positions.
commentStart := strings.Index(originalText, "/*")
selectionEnd := commentStart + len("/* comment") // ends inside the comment, before the closing `*/`

Copilot uses AI. Check for mistakes.
Comment thread internal/format/comment_test.go Outdated
Comment on lines +338 to +365
t.Run("full document format should preserve block comment and add spaces", func(t *testing.T) {
t.Parallel()
ctx := format.WithFormatCodeSettings(t.Context(), &lsutil.FormatCodeSettings{
EditorSettings: lsutil.EditorSettings{
TabSize: 4,
IndentSize: 4,
BaseIndentSize: 0,
NewLineCharacter: "\n",
ConvertTabsToSpaces: true,
IndentStyle: lsutil.IndentStyleSmart,
TrimTrailingWhitespace: true,
},
}, "\n")

originalText := `const test/* comment */=5;`

sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
FileName: "/test.ts",
Path: "/test.ts",
}, originalText, core.ScriptKindTS)

edits := format.FormatDocument(ctx, sourceFile)
formatted := applyBulkEdits(originalText, edits)

// Full document format should preserve the comment and add proper spacing
assert.Check(t, strings.Contains(formatted, "/* comment */"), "full format should preserve the block comment")
assert.Check(t, strings.Contains(formatted, "test"), "should preserve identifier")
})
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

This subtest name says it verifies that formatting "adds spaces", but the assertions only check that the comment/identifier are preserved. Either assert the expected spacing (or exact output), or rename the subtest to reflect what it actually checks.

Copilot uses AI. Check for mistakes.
Comment on lines +298 to +304
edits := format.FormatSelection(ctx, sourceFile, 0, selectionEnd)
formatted := applyBulkEdits(originalText, edits)

// The comment should be preserved
assert.Check(t, strings.Contains(formatted, "/* comment */"), "format selection should not delete the block comment")
assert.Check(t, strings.Contains(formatted, "test"), "should preserve identifier")
})
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The format-selection regression assertions are fairly loose (e.g., they only check that /* comment */ and sometimes test are present). This could still pass if other important parts of the statement (like =5;) are accidentally deleted or reordered. Consider strengthening the assertions to include the rest of the statement (or assert an exact expected result).

Copilot uses AI. Check for mistakes.
@jakebailey
Copy link
Copy Markdown
Member

@copilot address review comments

- Replace loose Contains checks with exact assert.Equal for all three
  format-selection subtests, ensuring the entire statement is verified
- Fix misleading comment about hard-coded positions; use computed
  positions from strings.Index (already done in code, fixed in comments)
- Rename "full document format should preserve block comment and add
  spaces" to just "...preserve block comment" since no spaces are added

Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/9945b15e-7056-43e7-aacb-d12f4c1285e3

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 6, 2026

@copilot address review comments

Addressed all three review comments in 11fd998:

  1. Fixed misleading hard-coded position comments — now describes what the computed strings.Index positions represent, with a clear note about stopping before */.
  2. Renamed "full document format should preserve block comment and add spaces" → "...preserve block comment" since no spaces are actually added.
  3. Replaced all loose strings.Contains assertions with exact assert.Equal checks against originalText, so any accidental deletion or reordering of statement parts (like =5;) will be caught.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@RyanCavanaugh RyanCavanaugh added this to the TypeScript 7.0 RC milestone Apr 17, 2026
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.

[bug] JS/TS Format Selection can delete comments

4 participants