From d065c8f090fc13205071becc7c87fa0b310e83b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 07:49:12 +0000 Subject: [PATCH 1/2] Initial plan From 59e803de787ec3b851c67ef0897f68720a97f573 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 08:10:02 +0000 Subject: [PATCH 2/2] Add fourslash test reproducing panic on out-of-bounds textDocument/definition Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/fourslash/fourslash.go | 25 ++++++++ .../panicOnOutOfBoundsDefinition_test.go | 57 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 internal/fourslash/tests/panicOnOutOfBoundsDefinition_test.go diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go index 7dcb546e2f..c31678dc30 100644 --- a/internal/fourslash/fourslash.go +++ b/internal/fourslash/fourslash.go @@ -2176,3 +2176,28 @@ func (f *FourslashTest) verifyBaselines(t *testing.T) { type anyTextEdits *[]*lsproto.TextEdit var AnyTextEdits = anyTextEdits(nil) + +// SendDefinitionRequestAtPosition sends a textDocument/definition request +// with the specified line and character position without bounds checking. +// This is useful for testing error handling when the client sends invalid positions. +// Returns the response message, error code, and error message if the request resulted in an error. +func (f *FourslashTest) SendDefinitionRequestAtPosition(t *testing.T, line uint32, character uint32) (*lsproto.Message, int32, string) { + params := &lsproto.DefinitionParams{ + TextDocument: lsproto.TextDocumentIdentifier{ + Uri: lsconv.FileNameToDocumentURI(f.activeFilename), + }, + Position: lsproto.Position{ + Line: line, + Character: character, + }, + } + + resMsg, _, _ := sendRequest(t, f, lsproto.TextDocumentDefinitionInfo, params) + if resMsg != nil && resMsg.Kind == lsproto.MessageKindResponse { + resp := resMsg.AsResponse() + if resp.Error != nil { + return resMsg, resp.Error.Code, resp.Error.Message + } + } + return resMsg, 0, "" +} diff --git a/internal/fourslash/tests/panicOnOutOfBoundsDefinition_test.go b/internal/fourslash/tests/panicOnOutOfBoundsDefinition_test.go new file mode 100644 index 0000000000..feefdc5008 --- /dev/null +++ b/internal/fourslash/tests/panicOnOutOfBoundsDefinition_test.go @@ -0,0 +1,57 @@ +package fourslash_test + +import ( + "strings" + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" + "gotest.tools/v3/assert" +) + +// TestPanicOnOutOfBoundsDefinition reproduces the panic that occurs when +// the client sends a textDocument/definition request with a line number +// that's beyond the file's line count. This can happen due to +// synchronization issues between client and server. +// +// BUG: The server should handle this gracefully by returning an empty result +// or a proper error, but instead it panics with "bad line number". +// This test currently fails because the server panics instead of handling +// the out-of-bounds position gracefully. +func TestPanicOnOutOfBoundsDefinition(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + + const content = `export {}; +interface Point { + x: number; + y: number; +} +declare const p: Point; +p.x; +` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + + // Send a definition request with a line number that's out of bounds. + // The file has 8 lines (0-7), but we're requesting line 65. + // This simulates what happens when the client's view of the file is stale. + msg, errorCode, errorMsg := f.SendDefinitionRequestAtPosition(t, 65, 24) + + // The server should handle this gracefully, not panic. + // We expect either: + // 1. A successful response with an empty result, OR + // 2. A proper error response (not due to panic) + // + // Currently, the server panics and returns InternalError (-32603) + // which is the bug we're testing for. + if errorCode == -32603 && strings.Contains(errorMsg, "panic") { + t.Fatalf("BUG: Server panicked when handling out-of-bounds position.\n"+ + "Error code: %d\n"+ + "Error message: %s\n"+ + "Expected: Server should handle out-of-bounds positions gracefully without panicking.", + errorCode, errorMsg) + } + + // If we get here without panic, verify the response is reasonable + assert.Assert(t, msg != nil, "Expected valid response, got nil") +}