diff --git a/internal/format/indent.go b/internal/format/indent.go index 2565666d2f..170873c2a4 100644 --- a/internal/format/indent.go +++ b/internal/format/indent.go @@ -66,8 +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. - listLine, _ := getStartLineAndCharacterForNode(firstListChild, sourceFile) - listIndentsChild := firstListChild != nil && listLine > containingListOrParentStartLine + var listIndentsChild bool + if firstListChild != nil { + listLine, _ := getStartLineAndCharacterForNode(firstListChild, sourceFile) + listIndentsChild = listLine > containingListOrParentStartLine + } actualIndentation := getActualIndentationForListItem(current, sourceFile, options, listIndentsChild) if actualIndentation != -1 { return actualIndentation + indentationDelta 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 + }) + } +}