From 9358f12a8862a60cce68d6d256c223e3aadd72fa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:20:04 +0000 Subject: [PATCH 1/5] Initial plan From 26fdb1708737bf5198c7cd18b71b891aa496867f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:31:31 +0000 Subject: [PATCH 2/5] Add test to reproduce panic in onTypeFormatting with empty file Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/ls/format_test.go | 70 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 internal/ls/format_test.go diff --git a/internal/ls/format_test.go b/internal/ls/format_test.go new file mode 100644 index 0000000000..06e7348f7a --- /dev/null +++ b/internal/ls/format_test.go @@ -0,0 +1,70 @@ +package ls + +import ( + "context" + "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" +) + +// Test for issue: Panic Handling textDocument/onTypeFormatting +// This reproduces the panic when pressing enter in an empty file +func TestGetFormattingEditsAfterKeystroke_EmptyFile(t *testing.T) { + // Create an empty file + text := "" + sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{ + FileName: "/index.ts", + Path: "/index.ts", + }, text, core.ScriptKindTS) + + // Create language service with nil program (we're only testing the formatting function) + langService := &LanguageService{} + + // Test formatting after keystroke with newline character at position 0 + ctx := context.Background() + options := format.GetDefaultFormatCodeSettings("\n") + + // This should not panic + edits := langService.getFormattingEditsAfterKeystroke( + ctx, + sourceFile, + options, + 0, // position + "\n", + ) + + // Should return nil or empty edits, not panic + _ = edits +} + +// Test with a simple statement +func TestGetFormattingEditsAfterKeystroke_SimpleStatement(t *testing.T) { + // Create a file with a simple statement + text := "const x = 1" + sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{ + FileName: "/index.ts", + Path: "/index.ts", + }, text, core.ScriptKindTS) + + // Create language service with nil program + langService := &LanguageService{} + + // Test formatting after keystroke with newline character at end of statement + ctx := context.Background() + options := format.GetDefaultFormatCodeSettings("\n") + + // This should not panic + edits := langService.getFormattingEditsAfterKeystroke( + ctx, + sourceFile, + options, + len(text), // position at end of file + "\n", + ) + + // Should return nil or empty edits, not panic + _ = edits +} From 531ffef098dcc110eb42e95d61525242615c84c5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:34:18 +0000 Subject: [PATCH 3/5] Fix panic when tokenAtPosition is nil in onTypeFormatting Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/ls/format.go | 3 +++ internal/ls/utilities.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/internal/ls/format.go b/internal/ls/format.go index 548c766741..5705345838 100644 --- a/internal/ls/format.go +++ b/internal/ls/format.go @@ -140,6 +140,9 @@ func getRangeOfEnclosingComment( precedingToken *ast.Node, tokenAtPosition *ast.Node, ) *ast.CommentRange { + if tokenAtPosition == nil { + return nil + } jsdoc := ast.FindAncestor(tokenAtPosition, (*ast.Node).IsJSDoc) if jsdoc != nil { tokenAtPosition = jsdoc.Parent diff --git a/internal/ls/utilities.go b/internal/ls/utilities.go index 86e27a9fb7..b6d8a70684 100644 --- a/internal/ls/utilities.go +++ b/internal/ls/utilities.go @@ -182,6 +182,9 @@ func isExportSpecifierAlias(referenceLocation *ast.Identifier, exportSpecifier * } func isInComment(file *ast.SourceFile, position int, tokenAtPosition *ast.Node) *ast.CommentRange { + if tokenAtPosition == nil { + tokenAtPosition = astnav.GetTokenAtPosition(file, position) + } return getRangeOfEnclosingComment(file, position, astnav.FindPrecedingToken(file, position), tokenAtPosition) } From 646b07d81cd027b5836a5f238fedd5fb476fa369 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:37:17 +0000 Subject: [PATCH 4/5] Add t.Parallel() calls to format tests Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/ls/format_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/ls/format_test.go b/internal/ls/format_test.go index 06e7348f7a..b1f21d8fff 100644 --- a/internal/ls/format_test.go +++ b/internal/ls/format_test.go @@ -13,6 +13,7 @@ import ( // Test for issue: Panic Handling textDocument/onTypeFormatting // This reproduces the panic when pressing enter in an empty file func TestGetFormattingEditsAfterKeystroke_EmptyFile(t *testing.T) { + t.Parallel() // Create an empty file text := "" sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{ @@ -42,6 +43,7 @@ func TestGetFormattingEditsAfterKeystroke_EmptyFile(t *testing.T) { // Test with a simple statement func TestGetFormattingEditsAfterKeystroke_SimpleStatement(t *testing.T) { + t.Parallel() // Create a file with a simple statement text := "const x = 1" sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{ From dee6e4a419da40061fbb0e6fe881e6b70da450fa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Oct 2025 12:03:38 +0000 Subject: [PATCH 5/5] Fix the caller instead of adapting helper functions to nil Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/ls/format.go | 6 ++---- internal/ls/utilities.go | 3 --- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/internal/ls/format.go b/internal/ls/format.go index 5705345838..9e0f55cf19 100644 --- a/internal/ls/format.go +++ b/internal/ls/format.go @@ -113,7 +113,8 @@ func (l *LanguageService) getFormattingEditsAfterKeystroke( ) []core.TextChange { ctx = format.WithFormatCodeSettings(ctx, options, options.NewLineCharacter) - if isInComment(file, position, nil) == nil { + tokenAtPosition := astnav.GetTokenAtPosition(file, position) + if isInComment(file, position, tokenAtPosition) == nil { switch key { case "{": return format.FormatOnOpeningCurly(ctx, file, position) @@ -140,9 +141,6 @@ func getRangeOfEnclosingComment( precedingToken *ast.Node, tokenAtPosition *ast.Node, ) *ast.CommentRange { - if tokenAtPosition == nil { - return nil - } jsdoc := ast.FindAncestor(tokenAtPosition, (*ast.Node).IsJSDoc) if jsdoc != nil { tokenAtPosition = jsdoc.Parent diff --git a/internal/ls/utilities.go b/internal/ls/utilities.go index b6d8a70684..86e27a9fb7 100644 --- a/internal/ls/utilities.go +++ b/internal/ls/utilities.go @@ -182,9 +182,6 @@ func isExportSpecifierAlias(referenceLocation *ast.Identifier, exportSpecifier * } func isInComment(file *ast.SourceFile, position int, tokenAtPosition *ast.Node) *ast.CommentRange { - if tokenAtPosition == nil { - tokenAtPosition = astnav.GetTokenAtPosition(file, position) - } return getRangeOfEnclosingComment(file, position, astnav.FindPrecedingToken(file, position), tokenAtPosition) }