Skip to content

Commit cfbb8b4

Browse files
Copilotjakebailey
andauthored
Fix incorrect formatting for comments inside multi-line argument lists and method chains (#1929)
Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: jakebailey <[email protected]>
1 parent 48b739c commit cfbb8b4

File tree

2 files changed

+148
-10
lines changed

2 files changed

+148
-10
lines changed

internal/format/comment_test.go

Lines changed: 145 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ func TestCommentFormatting(t *testing.T) {
4747
firstFormatted := applyBulkEdits(originalText, edits)
4848

4949
// Check that the asterisk is not corrupted
50-
assert.Check(t, !contains(firstFormatted, "*/\n /"), "should not corrupt */ to /")
51-
assert.Check(t, contains(firstFormatted, "*/"), "should preserve */ token")
52-
assert.Check(t, contains(firstFormatted, "async"), "should preserve async keyword")
50+
assert.Check(t, !strings.Contains(firstFormatted, "*/\n /"), "should not corrupt */ to /")
51+
assert.Check(t, strings.Contains(firstFormatted, "*/"), "should preserve */ token")
52+
assert.Check(t, strings.Contains(firstFormatted, "async"), "should preserve async keyword")
5353

5454
// Apply formatting a second time to test stability
5555
sourceFile2 := parser.ParseSourceFile(ast.SourceFileParseOptions{
@@ -61,8 +61,8 @@ func TestCommentFormatting(t *testing.T) {
6161
secondFormatted := applyBulkEdits(firstFormatted, edits2)
6262

6363
// Check that second formatting doesn't introduce corruption
64-
assert.Check(t, !contains(secondFormatted, " sync x()"), "should not corrupt async to sync")
65-
assert.Check(t, contains(secondFormatted, "async"), "should preserve async keyword on second pass")
64+
assert.Check(t, !strings.Contains(secondFormatted, " sync x()"), "should not corrupt async to sync")
65+
assert.Check(t, strings.Contains(secondFormatted, "async"), "should preserve async keyword on second pass")
6666
})
6767

6868
t.Run("format JSDoc with tab indentation", func(t *testing.T) {
@@ -95,14 +95,149 @@ func TestCommentFormatting(t *testing.T) {
9595
// Check that tabs come before spaces (not spaces before tabs)
9696
// The comment lines should have format: tab followed by space and asterisk
9797
// NOT: space followed by tab and asterisk
98-
assert.Check(t, !contains(formatted, " \t*"), "should not have space before tab before asterisk")
99-
assert.Check(t, contains(formatted, "\t *"), "should have tab before space before asterisk")
98+
assert.Check(t, !strings.Contains(formatted, " \t*"), "should not have space before tab before asterisk")
99+
assert.Check(t, strings.Contains(formatted, "\t *"), "should have tab before space before asterisk")
100100

101101
// Verify console.log is properly indented with tabs
102-
assert.Check(t, contains(formatted, "\t\tconsole.log"), "console.log should be indented with two tabs")
102+
assert.Check(t, strings.Contains(formatted, "\t\tconsole.log"), "console.log should be indented with two tabs")
103+
})
104+
105+
t.Run("format comment inside multi-line argument list", func(t *testing.T) {
106+
t.Parallel()
107+
ctx := format.WithFormatCodeSettings(t.Context(), &format.FormatCodeSettings{
108+
EditorSettings: format.EditorSettings{
109+
TabSize: 4,
110+
IndentSize: 4,
111+
BaseIndentSize: 0,
112+
NewLineCharacter: "\n",
113+
ConvertTabsToSpaces: false, // Use tabs
114+
IndentStyle: format.IndentStyleSmart,
115+
TrimTrailingWhitespace: true,
116+
},
117+
InsertSpaceBeforeTypeAnnotation: core.TSTrue,
118+
}, "\n")
119+
120+
// Original code with proper indentation
121+
originalText := "console.log(\n\t\"a\",\n\t// the second arg\n\t\"b\"\n);"
122+
123+
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
124+
FileName: "/test.ts",
125+
Path: "/test.ts",
126+
}, originalText, core.ScriptKindTS)
127+
128+
// Apply formatting
129+
edits := format.FormatDocument(ctx, sourceFile)
130+
formatted := applyBulkEdits(originalText, edits)
131+
132+
// The comment should remain indented with a tab
133+
assert.Check(t, strings.Contains(formatted, "\t// the second arg"), "comment should be indented with tab")
134+
// The comment should not lose its indentation
135+
assert.Check(t, !strings.Contains(formatted, "\n// the second arg"), "comment should not lose indentation")
136+
})
137+
138+
t.Run("format comment in chained method calls", func(t *testing.T) {
139+
t.Parallel()
140+
ctx := format.WithFormatCodeSettings(t.Context(), &format.FormatCodeSettings{
141+
EditorSettings: format.EditorSettings{
142+
TabSize: 4,
143+
IndentSize: 4,
144+
BaseIndentSize: 0,
145+
NewLineCharacter: "\n",
146+
ConvertTabsToSpaces: false, // Use tabs
147+
IndentStyle: format.IndentStyleSmart,
148+
TrimTrailingWhitespace: true,
149+
},
150+
InsertSpaceBeforeTypeAnnotation: core.TSTrue,
151+
}, "\n")
152+
153+
// Original code with proper indentation
154+
originalText := "foo\n\t.bar()\n\t// A second call\n\t.baz();"
155+
156+
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
157+
FileName: "/test.ts",
158+
Path: "/test.ts",
159+
}, originalText, core.ScriptKindTS)
160+
161+
// Apply formatting
162+
edits := format.FormatDocument(ctx, sourceFile)
163+
formatted := applyBulkEdits(originalText, edits)
164+
165+
// The comment should remain indented
166+
assert.Check(t, strings.Contains(formatted, "\t// A second call") || strings.Contains(formatted, " // A second call"), "comment should be indented")
167+
// The comment should not lose its indentation
168+
assert.Check(t, !strings.Contains(formatted, "\n// A second call"), "comment should not lose indentation")
169+
})
170+
171+
// Regression test for issue #1928 - panic when formatting chained method call with comment
172+
t.Run("format chained method call with comment (issue #1928)", func(t *testing.T) {
173+
t.Parallel()
174+
ctx := format.WithFormatCodeSettings(t.Context(), &format.FormatCodeSettings{
175+
EditorSettings: format.EditorSettings{
176+
TabSize: 4,
177+
IndentSize: 4,
178+
BaseIndentSize: 0,
179+
NewLineCharacter: "\n",
180+
ConvertTabsToSpaces: false, // Use tabs
181+
IndentStyle: format.IndentStyleSmart,
182+
TrimTrailingWhitespace: true,
183+
},
184+
InsertSpaceBeforeTypeAnnotation: core.TSTrue,
185+
}, "\n")
186+
187+
// This code previously caused a panic with "strings: negative Repeat count"
188+
// because tokenIndentation was -1 and was being used directly for indentation
189+
originalText := "foo\n\t.bar()\n\t// A second call\n\t.baz();"
190+
191+
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
192+
FileName: "/test.ts",
193+
Path: "/test.ts",
194+
}, originalText, core.ScriptKindTS)
195+
196+
// Apply formatting - should not panic
197+
edits := format.FormatDocument(ctx, sourceFile)
198+
formatted := applyBulkEdits(originalText, edits)
199+
200+
// Verify the comment maintains proper indentation and doesn't lose it
201+
assert.Check(t, strings.Contains(formatted, "\t// A second call") || strings.Contains(formatted, " // A second call"), "comment should be indented")
202+
assert.Check(t, !strings.Contains(formatted, "\n// A second call"), "comment should not be at column 0")
103203
})
104204
}
105205

106-
func contains(s, substr string) bool {
107-
return len(substr) > 0 && strings.Contains(s, substr)
206+
func TestSliceBoundsPanic(t *testing.T) {
207+
t.Parallel()
208+
209+
t.Run("format code with trailing semicolon should not panic", func(t *testing.T) {
210+
t.Parallel()
211+
ctx := format.WithFormatCodeSettings(t.Context(), &format.FormatCodeSettings{
212+
EditorSettings: format.EditorSettings{
213+
TabSize: 4,
214+
IndentSize: 4,
215+
BaseIndentSize: 4,
216+
NewLineCharacter: "\n",
217+
ConvertTabsToSpaces: true,
218+
IndentStyle: format.IndentStyleSmart,
219+
TrimTrailingWhitespace: true,
220+
},
221+
InsertSpaceBeforeTypeAnnotation: core.TSTrue,
222+
}, "\n")
223+
224+
// Code from the issue that causes slice bounds panic
225+
originalText := `const _enableDisposeWithListenerWarning = false
226+
// || Boolean("TRUE") // causes a linter warning so that it cannot be pushed
227+
;
228+
`
229+
230+
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
231+
FileName: "/test.ts",
232+
Path: "/test.ts",
233+
}, originalText, core.ScriptKindTS)
234+
235+
// This should not panic
236+
edits := format.FormatDocument(ctx, sourceFile)
237+
formatted := applyBulkEdits(originalText, edits)
238+
239+
// Basic sanity checks
240+
assert.Check(t, len(formatted) > 0, "formatted text should not be empty")
241+
assert.Check(t, strings.Contains(formatted, "_enableDisposeWithListenerWarning"), "should preserve variable name")
242+
})
108243
}

internal/format/span.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,9 @@ func (i *dynamicIndenter) getIndentationForComment(kind ast.Kind, tokenIndentati
10931093
case ast.KindCloseBraceToken, ast.KindCloseBracketToken, ast.KindCloseParenToken:
10941094
return i.indentation + i.getDelta(container)
10951095
}
1096+
if tokenIndentation != -1 {
1097+
return tokenIndentation
1098+
}
10961099
return i.indentation
10971100
}
10981101

0 commit comments

Comments
 (0)