From 319ed2d505cdbe66d4631105492bc96bcc35aa5e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 18:28:23 +0000 Subject: [PATCH 1/6] Initial plan From 652cf74a0eb2add357763f2560892b1a102501a5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 18:38:24 +0000 Subject: [PATCH 2/6] Add test to reproduce formatting issue with comments in multi-line arguments Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/format/reproduce_test.go | 67 +++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 internal/format/reproduce_test.go diff --git a/internal/format/reproduce_test.go b/internal/format/reproduce_test.go new file mode 100644 index 0000000000..4705d49fdb --- /dev/null +++ b/internal/format/reproduce_test.go @@ -0,0 +1,67 @@ +package format_test + +import ( +"testing" +"github.com/microsoft/typescript-go/internal/ast" +"github.com/microsoft/typescript-go/internal/core" +"github.com/microsoft/typescript-go/internal/format" +"github.com/microsoft/typescript-go/internal/parser" +"fmt" +) + +func TestReproduceIssue(t *testing.T) { +ctx := format.WithFormatCodeSettings(t.Context(), &format.FormatCodeSettings{ +EditorSettings: format.EditorSettings{ +TabSize: 4, +IndentSize: 4, +BaseIndentSize: 0, +NewLineCharacter: "\n", +ConvertTabsToSpaces: false, +IndentStyle: format.IndentStyleSmart, +TrimTrailingWhitespace: true, +}, +InsertSpaceBeforeTypeAnnotation: core.TSTrue, +}, "\n") + +// Test case 1: console.log with comment +originalText1 := `console.log( +"a", +// the second arg +"b" +);` + +sourceFile1 := parser.ParseSourceFile(ast.SourceFileParseOptions{ +FileName: "/test.ts", +Path: "/test.ts", +}, originalText1, core.ScriptKindTS) + +edits1 := format.FormatDocument(ctx, sourceFile1) +formatted1 := applyBulkEdits(originalText1, edits1) + +fmt.Println("Test case 1: console.log with comment") +fmt.Println("Original:") +fmt.Println(originalText1) +fmt.Println("\nFormatted:") +fmt.Println(formatted1) +fmt.Println() + +// Test case 2: chained method calls with comment +originalText2 := `foo +.bar() +// A second call +.baz();` + +sourceFile2 := parser.ParseSourceFile(ast.SourceFileParseOptions{ +FileName: "/test.ts", +Path: "/test.ts", +}, originalText2, core.ScriptKindTS) + +edits2 := format.FormatDocument(ctx, sourceFile2) +formatted2 := applyBulkEdits(originalText2, edits2) + +fmt.Println("Test case 2: chained method calls with comment") +fmt.Println("Original:") +fmt.Println(originalText2) +fmt.Println("\nFormatted:") +fmt.Println(formatted2) +} From 3e7ab19e6a67a6e68c54f3302b59a4cad8e420eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 18:42:42 +0000 Subject: [PATCH 3/6] Fix incorrect comment indentation in multi-line argument lists and method chains Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/format/comment_test.go | 66 ++++++++++++++++++++++++++++++ internal/format/reproduce_test.go | 67 ------------------------------- internal/format/span.go | 3 ++ 3 files changed, 69 insertions(+), 67 deletions(-) delete mode 100644 internal/format/reproduce_test.go diff --git a/internal/format/comment_test.go b/internal/format/comment_test.go index 50762ab56d..799642c5ba 100644 --- a/internal/format/comment_test.go +++ b/internal/format/comment_test.go @@ -101,6 +101,72 @@ func TestCommentFormatting(t *testing.T) { // Verify console.log is properly indented with tabs assert.Check(t, contains(formatted, "\t\tconsole.log"), "console.log should be indented with two tabs") }) + + t.Run("format comment inside multi-line argument list", func(t *testing.T) { + t.Parallel() + ctx := format.WithFormatCodeSettings(t.Context(), &format.FormatCodeSettings{ + EditorSettings: format.EditorSettings{ + TabSize: 4, + IndentSize: 4, + BaseIndentSize: 0, + NewLineCharacter: "\n", + ConvertTabsToSpaces: false, // Use tabs + IndentStyle: format.IndentStyleSmart, + TrimTrailingWhitespace: true, + }, + InsertSpaceBeforeTypeAnnotation: core.TSTrue, + }, "\n") + + // Original code with proper indentation + originalText := "console.log(\n\t\"a\",\n\t// the second arg\n\t\"b\"\n);" + + sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{ + FileName: "/test.ts", + Path: "/test.ts", + }, originalText, core.ScriptKindTS) + + // Apply formatting + edits := format.FormatDocument(ctx, sourceFile) + formatted := applyBulkEdits(originalText, edits) + + // The comment should remain indented with a tab + assert.Check(t, contains(formatted, "\t// the second arg"), "comment should be indented with tab") + // The comment should not lose its indentation + assert.Check(t, !contains(formatted, "\n// the second arg"), "comment should not lose indentation") + }) + + t.Run("format comment in chained method calls", func(t *testing.T) { + t.Parallel() + ctx := format.WithFormatCodeSettings(t.Context(), &format.FormatCodeSettings{ + EditorSettings: format.EditorSettings{ + TabSize: 4, + IndentSize: 4, + BaseIndentSize: 0, + NewLineCharacter: "\n", + ConvertTabsToSpaces: false, // Use tabs + IndentStyle: format.IndentStyleSmart, + TrimTrailingWhitespace: true, + }, + InsertSpaceBeforeTypeAnnotation: core.TSTrue, + }, "\n") + + // Original code with proper indentation + originalText := "foo\n\t.bar()\n\t// A second call\n\t.baz();" + + sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{ + FileName: "/test.ts", + Path: "/test.ts", + }, originalText, core.ScriptKindTS) + + // Apply formatting + edits := format.FormatDocument(ctx, sourceFile) + formatted := applyBulkEdits(originalText, edits) + + // The comment should remain indented + assert.Check(t, contains(formatted, "\t// A second call") || contains(formatted, " // A second call"), "comment should be indented") + // The comment should not lose its indentation + assert.Check(t, !contains(formatted, "\n// A second call"), "comment should not lose indentation") + }) } func contains(s, substr string) bool { diff --git a/internal/format/reproduce_test.go b/internal/format/reproduce_test.go deleted file mode 100644 index 4705d49fdb..0000000000 --- a/internal/format/reproduce_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package format_test - -import ( -"testing" -"github.com/microsoft/typescript-go/internal/ast" -"github.com/microsoft/typescript-go/internal/core" -"github.com/microsoft/typescript-go/internal/format" -"github.com/microsoft/typescript-go/internal/parser" -"fmt" -) - -func TestReproduceIssue(t *testing.T) { -ctx := format.WithFormatCodeSettings(t.Context(), &format.FormatCodeSettings{ -EditorSettings: format.EditorSettings{ -TabSize: 4, -IndentSize: 4, -BaseIndentSize: 0, -NewLineCharacter: "\n", -ConvertTabsToSpaces: false, -IndentStyle: format.IndentStyleSmart, -TrimTrailingWhitespace: true, -}, -InsertSpaceBeforeTypeAnnotation: core.TSTrue, -}, "\n") - -// Test case 1: console.log with comment -originalText1 := `console.log( -"a", -// the second arg -"b" -);` - -sourceFile1 := parser.ParseSourceFile(ast.SourceFileParseOptions{ -FileName: "/test.ts", -Path: "/test.ts", -}, originalText1, core.ScriptKindTS) - -edits1 := format.FormatDocument(ctx, sourceFile1) -formatted1 := applyBulkEdits(originalText1, edits1) - -fmt.Println("Test case 1: console.log with comment") -fmt.Println("Original:") -fmt.Println(originalText1) -fmt.Println("\nFormatted:") -fmt.Println(formatted1) -fmt.Println() - -// Test case 2: chained method calls with comment -originalText2 := `foo -.bar() -// A second call -.baz();` - -sourceFile2 := parser.ParseSourceFile(ast.SourceFileParseOptions{ -FileName: "/test.ts", -Path: "/test.ts", -}, originalText2, core.ScriptKindTS) - -edits2 := format.FormatDocument(ctx, sourceFile2) -formatted2 := applyBulkEdits(originalText2, edits2) - -fmt.Println("Test case 2: chained method calls with comment") -fmt.Println("Original:") -fmt.Println(originalText2) -fmt.Println("\nFormatted:") -fmt.Println(formatted2) -} diff --git a/internal/format/span.go b/internal/format/span.go index c8d0096bed..3a1ea475cd 100644 --- a/internal/format/span.go +++ b/internal/format/span.go @@ -1093,6 +1093,9 @@ func (i *dynamicIndenter) getIndentationForComment(kind ast.Kind, tokenIndentati case ast.KindCloseBraceToken, ast.KindCloseBracketToken, ast.KindCloseParenToken: return i.indentation + i.getDelta(container) } + if tokenIndentation != -1 { + return tokenIndentation + } return i.indentation } From ccd992277b8db31394f37b921864f001290d9f65 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 18:59:19 +0000 Subject: [PATCH 4/6] Add regression test for issue #1928 (panic formatting chained method call) Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/format/comment_test.go | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/internal/format/comment_test.go b/internal/format/comment_test.go index 799642c5ba..12106070d8 100644 --- a/internal/format/comment_test.go +++ b/internal/format/comment_test.go @@ -167,6 +167,40 @@ func TestCommentFormatting(t *testing.T) { // The comment should not lose its indentation assert.Check(t, !contains(formatted, "\n// A second call"), "comment should not lose indentation") }) + + // Regression test for issue #1928 - panic when formatting chained method call with comment + t.Run("format chained method call with comment (issue #1928)", func(t *testing.T) { + t.Parallel() + ctx := format.WithFormatCodeSettings(t.Context(), &format.FormatCodeSettings{ + EditorSettings: format.EditorSettings{ + TabSize: 4, + IndentSize: 4, + BaseIndentSize: 0, + NewLineCharacter: "\n", + ConvertTabsToSpaces: false, // Use tabs + IndentStyle: format.IndentStyleSmart, + TrimTrailingWhitespace: true, + }, + InsertSpaceBeforeTypeAnnotation: core.TSTrue, + }, "\n") + + // This code previously caused a panic with "strings: negative Repeat count" + // because tokenIndentation was -1 and was being used directly for indentation + originalText := "foo\n\t.bar()\n\t// A second call\n\t.baz();" + + sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{ + FileName: "/test.ts", + Path: "/test.ts", + }, originalText, core.ScriptKindTS) + + // Apply formatting - should not panic + edits := format.FormatDocument(ctx, sourceFile) + formatted := applyBulkEdits(originalText, edits) + + // Verify the comment maintains proper indentation and doesn't lose it + assert.Check(t, contains(formatted, "\t// A second call") || contains(formatted, " // A second call"), "comment should be indented") + assert.Check(t, !contains(formatted, "\n// A second call"), "comment should not be at column 0") + }) } func contains(s, substr string) bool { From 508b55bab04879590c190805371ad1536de1b10b Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 22 Oct 2025 12:10:08 -0700 Subject: [PATCH 5/6] Add test from 1931 --- internal/format/comment_test.go | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/internal/format/comment_test.go b/internal/format/comment_test.go index 12106070d8..b49ef00e64 100644 --- a/internal/format/comment_test.go +++ b/internal/format/comment_test.go @@ -206,3 +206,42 @@ func TestCommentFormatting(t *testing.T) { func contains(s, substr string) bool { return len(substr) > 0 && strings.Contains(s, substr) } + +func TestSliceBoundsPanic(t *testing.T) { + t.Parallel() + + t.Run("format code with trailing semicolon should not panic", func(t *testing.T) { + t.Parallel() + ctx := format.WithFormatCodeSettings(t.Context(), &format.FormatCodeSettings{ + EditorSettings: format.EditorSettings{ + TabSize: 4, + IndentSize: 4, + BaseIndentSize: 4, + NewLineCharacter: "\n", + ConvertTabsToSpaces: true, + IndentStyle: format.IndentStyleSmart, + TrimTrailingWhitespace: true, + }, + InsertSpaceBeforeTypeAnnotation: core.TSTrue, + }, "\n") + + // Code from the issue that causes slice bounds panic + originalText := `const _enableDisposeWithListenerWarning = false + // || Boolean("TRUE") // causes a linter warning so that it cannot be pushed + ; +` + + sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{ + FileName: "/test.ts", + Path: "/test.ts", + }, originalText, core.ScriptKindTS) + + // This should not panic + edits := format.FormatDocument(ctx, sourceFile) + formatted := applyBulkEdits(originalText, edits) + + // Basic sanity checks + assert.Check(t, len(formatted) > 0, "formatted text should not be empty") + assert.Check(t, contains(formatted, "_enableDisposeWithListenerWarning"), "should preserve variable name") + }) +} From 6d563c328e059e33e3617722b9536e2ea84b064f Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 22 Oct 2025 12:10:31 -0700 Subject: [PATCH 6/6] Drop contains helper --- internal/format/comment_test.go | 34 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/internal/format/comment_test.go b/internal/format/comment_test.go index b49ef00e64..f94799d82d 100644 --- a/internal/format/comment_test.go +++ b/internal/format/comment_test.go @@ -47,9 +47,9 @@ func TestCommentFormatting(t *testing.T) { firstFormatted := applyBulkEdits(originalText, edits) // Check that the asterisk is not corrupted - assert.Check(t, !contains(firstFormatted, "*/\n /"), "should not corrupt */ to /") - assert.Check(t, contains(firstFormatted, "*/"), "should preserve */ token") - assert.Check(t, contains(firstFormatted, "async"), "should preserve async keyword") + assert.Check(t, !strings.Contains(firstFormatted, "*/\n /"), "should not corrupt */ to /") + assert.Check(t, strings.Contains(firstFormatted, "*/"), "should preserve */ token") + assert.Check(t, strings.Contains(firstFormatted, "async"), "should preserve async keyword") // Apply formatting a second time to test stability sourceFile2 := parser.ParseSourceFile(ast.SourceFileParseOptions{ @@ -61,8 +61,8 @@ func TestCommentFormatting(t *testing.T) { secondFormatted := applyBulkEdits(firstFormatted, edits2) // Check that second formatting doesn't introduce corruption - assert.Check(t, !contains(secondFormatted, " sync x()"), "should not corrupt async to sync") - assert.Check(t, contains(secondFormatted, "async"), "should preserve async keyword on second pass") + assert.Check(t, !strings.Contains(secondFormatted, " sync x()"), "should not corrupt async to sync") + assert.Check(t, strings.Contains(secondFormatted, "async"), "should preserve async keyword on second pass") }) t.Run("format JSDoc with tab indentation", func(t *testing.T) { @@ -95,11 +95,11 @@ func TestCommentFormatting(t *testing.T) { // Check that tabs come before spaces (not spaces before tabs) // The comment lines should have format: tab followed by space and asterisk // NOT: space followed by tab and asterisk - assert.Check(t, !contains(formatted, " \t*"), "should not have space before tab before asterisk") - assert.Check(t, contains(formatted, "\t *"), "should have tab before space before asterisk") + assert.Check(t, !strings.Contains(formatted, " \t*"), "should not have space before tab before asterisk") + assert.Check(t, strings.Contains(formatted, "\t *"), "should have tab before space before asterisk") // Verify console.log is properly indented with tabs - assert.Check(t, contains(formatted, "\t\tconsole.log"), "console.log should be indented with two tabs") + assert.Check(t, strings.Contains(formatted, "\t\tconsole.log"), "console.log should be indented with two tabs") }) t.Run("format comment inside multi-line argument list", func(t *testing.T) { @@ -130,9 +130,9 @@ func TestCommentFormatting(t *testing.T) { formatted := applyBulkEdits(originalText, edits) // The comment should remain indented with a tab - assert.Check(t, contains(formatted, "\t// the second arg"), "comment should be indented with tab") + assert.Check(t, strings.Contains(formatted, "\t// the second arg"), "comment should be indented with tab") // The comment should not lose its indentation - assert.Check(t, !contains(formatted, "\n// the second arg"), "comment should not lose indentation") + assert.Check(t, !strings.Contains(formatted, "\n// the second arg"), "comment should not lose indentation") }) t.Run("format comment in chained method calls", func(t *testing.T) { @@ -163,9 +163,9 @@ func TestCommentFormatting(t *testing.T) { formatted := applyBulkEdits(originalText, edits) // The comment should remain indented - assert.Check(t, contains(formatted, "\t// A second call") || contains(formatted, " // A second call"), "comment should be indented") + assert.Check(t, strings.Contains(formatted, "\t// A second call") || strings.Contains(formatted, " // A second call"), "comment should be indented") // The comment should not lose its indentation - assert.Check(t, !contains(formatted, "\n// A second call"), "comment should not lose indentation") + assert.Check(t, !strings.Contains(formatted, "\n// A second call"), "comment should not lose indentation") }) // Regression test for issue #1928 - panic when formatting chained method call with comment @@ -198,15 +198,11 @@ func TestCommentFormatting(t *testing.T) { formatted := applyBulkEdits(originalText, edits) // Verify the comment maintains proper indentation and doesn't lose it - assert.Check(t, contains(formatted, "\t// A second call") || contains(formatted, " // A second call"), "comment should be indented") - assert.Check(t, !contains(formatted, "\n// A second call"), "comment should not be at column 0") + assert.Check(t, strings.Contains(formatted, "\t// A second call") || strings.Contains(formatted, " // A second call"), "comment should be indented") + assert.Check(t, !strings.Contains(formatted, "\n// A second call"), "comment should not be at column 0") }) } -func contains(s, substr string) bool { - return len(substr) > 0 && strings.Contains(s, substr) -} - func TestSliceBoundsPanic(t *testing.T) { t.Parallel() @@ -242,6 +238,6 @@ func TestSliceBoundsPanic(t *testing.T) { // Basic sanity checks assert.Check(t, len(formatted) > 0, "formatted text should not be empty") - assert.Check(t, contains(formatted, "_enableDisposeWithListenerWarning"), "should preserve variable name") + assert.Check(t, strings.Contains(formatted, "_enableDisposeWithListenerWarning"), "should preserve variable name") }) }