From 948def2e16ae3375c6837b5197e5ca42452d66f1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 18:18:57 +0000 Subject: [PATCH 1/3] Initial plan From 9f74d75db5eb88207d84f7954cb7012bb73683fa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 18:36:09 +0000 Subject: [PATCH 2/3] Fix nil pointer dereference in range formatting - Added nil check before calling getStartLineAndCharacterForNode in indent.go - Added comprehensive test cases to verify the fix - All tests pass successfully Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- internal/format/indent.go | 5 ++- internal/ls/format_test.go | 64 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/internal/format/indent.go b/internal/format/indent.go index 2565666d2f..af508586b5 100644 --- a/internal/format/indent.go +++ b/internal/format/indent.go @@ -66,7 +66,10 @@ func getIndentationForNodeWorker( // }, { itself contributes nothing. // prop: 1 L3 - The indentation of the second object literal is best understood by // }) looking at the relationship between the list and *first* list item. - listLine, _ := getStartLineAndCharacterForNode(firstListChild, sourceFile) + var listLine int + if firstListChild != nil { + listLine, _ = getStartLineAndCharacterForNode(firstListChild, sourceFile) + } listIndentsChild := firstListChild != nil && listLine > containingListOrParentStartLine actualIndentation := getActualIndentationForListItem(current, sourceFile, options, listIndentsChild) if actualIndentation != -1 { diff --git a/internal/ls/format_test.go b/internal/ls/format_test.go index b1f21d8fff..ae9403e0e0 100644 --- a/internal/ls/format_test.go +++ b/internal/ls/format_test.go @@ -8,6 +8,7 @@ import ( "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/format" "github.com/microsoft/typescript-go/internal/parser" + "gotest.tools/v3/assert" ) // Test for issue: Panic Handling textDocument/onTypeFormatting @@ -70,3 +71,66 @@ func TestGetFormattingEditsAfterKeystroke_SimpleStatement(t *testing.T) { // Should return nil or empty edits, not panic _ = edits } + +// Test for issue: Crash in range formatting when requested on a line that is different from the containing function +// This reproduces the panic when formatting a range inside a function body +func TestGetFormattingEditsForRange_FunctionBody(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + text string + startPos int + endPos int + }{ + { + name: "return statement in function", + text: "function foo() {\n return (1 + 2);\n}", + startPos: 21, // Start of "return" + endPos: 38, // End of ");" + }, + { + name: "function with newline after keyword", + text: "function\nf() {\n}", + startPos: 9, // After "function\n" + endPos: 13, // Inside or after function + }, + { + name: "empty function body", + text: "function f() {\n \n}", + startPos: 15, // Inside body + endPos: 17, // Inside body + }, + { + name: "after function closing brace", + text: "function f() {\n}", + startPos: 15, // After closing brace + endPos: 15, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{ + FileName: "/test.ts", + Path: "/test.ts", + }, tc.text, core.ScriptKindTS) + + langService := &LanguageService{} + ctx := context.Background() + options := format.GetDefaultFormatCodeSettings("\n") + + // This should not panic + edits := langService.getFormattingEditsForRange( + ctx, + sourceFile, + options, + core.NewTextRange(tc.startPos, tc.endPos), + ) + + // Should not panic and should return some edits or empty array + assert.Assert(t, edits != nil || true) // Just ensuring no panic + }) + } +} From 76969b2623a305a42b1d9a23ec58949689bbef3a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 18:49:26 +0000 Subject: [PATCH 3/3] Refactor nil check logic per code review feedback Simplified the listIndentsChild computation by moving it inside the nil check, avoiding redundant nil check and limiting listLine scope. Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/format/indent.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/format/indent.go b/internal/format/indent.go index af508586b5..170873c2a4 100644 --- a/internal/format/indent.go +++ b/internal/format/indent.go @@ -66,11 +66,11 @@ func getIndentationForNodeWorker( // }, { itself contributes nothing. // prop: 1 L3 - The indentation of the second object literal is best understood by // }) looking at the relationship between the list and *first* list item. - var listLine int + var listIndentsChild bool if firstListChild != nil { - listLine, _ = getStartLineAndCharacterForNode(firstListChild, sourceFile) + listLine, _ := getStartLineAndCharacterForNode(firstListChild, sourceFile) + listIndentsChild = listLine > containingListOrParentStartLine } - listIndentsChild := firstListChild != nil && listLine > containingListOrParentStartLine actualIndentation := getActualIndentationForListItem(current, sourceFile, options, listIndentsChild) if actualIndentation != -1 { return actualIndentation + indentationDelta