Fix format selection deleting comments when selection ends inside comment trivia#3159
Fix format selection deleting comments when selection ends inside comment trivia#3159
Conversation
…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 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>
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 |
There was a problem hiding this comment.
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
consumeTokenAndAdvanceScannerto cappreviousRangeTriviaEndbefore 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. |
| // 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 |
There was a problem hiding this comment.
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.
| // 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 `*/` |
| 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") | ||
| }) |
There was a problem hiding this comment.
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.
| 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") | ||
| }) |
There was a problem hiding this comment.
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 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>
Addressed all three review comments in 11fd998:
|
consumeTokenAndAdvanceScannerto cappreviousRangeTriviaEndbefore unprocessed comment triviaformatSelectionEditAtEndOfRangetest still passesOriginal prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.