Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions internal/fourslash/fourslash.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, ""
}
57 changes: 57 additions & 0 deletions internal/fourslash/tests/panicOnOutOfBoundsDefinition_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
Loading