fix(lsp): recover from handler panics and decode client uris
This commit is contained in:
+123
-3
@@ -381,7 +381,7 @@ func TestCodeActions(t *testing.T) {
|
||||
frame(10, "textDocument/codeAction", map[string]any{
|
||||
"textDocument": map[string]any{"uri": "file:///f_amd64.s"},
|
||||
"range": map[string]any{"start": map[string]any{"line": 0, "character": 0}, "end": map[string]any{"line": 2, "character": 0}},
|
||||
"context": map[string]any{"diagnostics": []map[string]any{{"code": "missing-ret", "range": map[string]any{"start": map[string]any{"line": 0, "character": 0}, "end": map[string]any{"line": 0, "character": 4}}}}},
|
||||
"context": map[string]any{"diagnostics": []map[string]any{{"code": "missing-ret", "range": map[string]any{"start": map[string]any{"line": 1, "character": 0}, "end": map[string]any{"line": 1, "character": 4}}}}},
|
||||
}) + frame(nil, "exit", nil)
|
||||
msgs := run(t, in)
|
||||
resp := findByID(msgs, 10)
|
||||
@@ -395,8 +395,8 @@ func TestCodeActions(t *testing.T) {
|
||||
if len(actions) == 0 {
|
||||
t.Fatal("want at least 1 code action for missing-ret")
|
||||
}
|
||||
if actions[0].Title != "Add RET" {
|
||||
t.Errorf("action title = %q, want Add RET", actions[0].Title)
|
||||
if actions[0].Title != "Add RET to foo" {
|
||||
t.Errorf("action title = %q, want Add RET to foo", actions[0].Title)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,3 +571,123 @@ func TestFoldingRanges(t *testing.T) {
|
||||
t.Errorf("folding kind = %q, want region", ranges[0].Kind)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInitializeVersion checks the version reported in the initialize
|
||||
// result when the caller stamps one.
|
||||
func TestInitializeVersion(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
srv := New(strings.NewReader(frame(1, "initialize", map[string]any{})+frame(nil, "exit", nil)), &out)
|
||||
srv.SetVersion("9.9.9")
|
||||
if err := srv.Run(); err != nil {
|
||||
t.Fatalf("server run: %v", err)
|
||||
}
|
||||
msgs := readFrames(t, &out)
|
||||
resp := findByID(msgs, 1)
|
||||
if resp == nil {
|
||||
t.Fatal("no initialize response")
|
||||
}
|
||||
var res initializeResult
|
||||
if err := json.Unmarshal(mustResult(t, resp), &res); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if res.ServerInfo["version"] != "9.9.9" {
|
||||
t.Errorf("serverInfo = %v, want version 9.9.9", res.ServerInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSyntaxDiagnosticsPublished checks that parse errors reach the editor
|
||||
// as error-severity diagnostics with the syntax code.
|
||||
func TestSyntaxDiagnosticsPublished(t *testing.T) {
|
||||
msgs := run(t, session("file:///f_amd64.s", "TEXT $\n")+frame(nil, "exit", nil))
|
||||
pub := findMethod(msgs, "textDocument/publishDiagnostics")
|
||||
if pub == nil {
|
||||
t.Fatal("no publishDiagnostics notification")
|
||||
}
|
||||
var p publishDiagnosticsParams
|
||||
json.Unmarshal(pub.Params, &p)
|
||||
found := false
|
||||
for _, d := range p.Diagnostics {
|
||||
if d.Code == "syntax" && d.Severity == sevError {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("expected a syntax error diagnostic, got %+v", p.Diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDispatchRecoversFromPanic exercises the per-message recover: a panic
|
||||
// inside a handler is answered as an internal error instead of taking the
|
||||
// server down.
|
||||
func TestDispatchRecoversFromPanic(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
srv := New(strings.NewReader(""), &out)
|
||||
srv.docs = nil // force a nil-map write inside didOpen
|
||||
raw := json.RawMessage(`{"textDocument":{"uri":"file:///x.s","text":"RET"}}`)
|
||||
srv.dispatch(&rpcMessage{ID: rawID(t, 7), Method: "textDocument/didOpen", Params: raw})
|
||||
msgs := readFrames(t, &out)
|
||||
resp := findByID(msgs, 7)
|
||||
if resp == nil {
|
||||
t.Fatal("no error response after panic")
|
||||
}
|
||||
if resp.Error == nil || resp.Error.Code != errInternalError {
|
||||
t.Fatalf("error = %+v, want internal error", resp.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func rawID(t *testing.T, n int) *json.RawMessage {
|
||||
t.Helper()
|
||||
b, err := json.Marshal(n)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw := json.RawMessage(b)
|
||||
return &raw
|
||||
}
|
||||
|
||||
// TestURIDecoding pins the percent-decoding of file URIs: clients encode
|
||||
// non-ASCII paths, and the decoded form is what resolves on disk.
|
||||
func TestURIDecoding(t *testing.T) {
|
||||
got := uriPath("file:///home/petrbalvin/Repozit%C3%A1%C5%99e/k.s")
|
||||
if got != "/home/petrbalvin/Repozitáře/k.s" {
|
||||
t.Errorf("uriPath = %q", got)
|
||||
}
|
||||
if got := uriPath("/plain/path.s"); got != "/plain/path.s" {
|
||||
t.Errorf("uriPath plain = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCodeActionsTargetsFlaggedFunctionOnly checks that a missing-ret
|
||||
// diagnostic offers an edit for the flagged function only, even when the
|
||||
// file defines several functions.
|
||||
func TestCodeActionsTargetsFlaggedFunctionOnly(t *testing.T) {
|
||||
doc := "TEXT \u00b7first(SB), NOSPLIT, $0\n" +
|
||||
"\tMOVQ AX, CX\n" +
|
||||
"\tRET\n" +
|
||||
"TEXT \u00b7second(SB), NOSPLIT, $0\n" +
|
||||
"\tMOVQ AX, CX\n"
|
||||
in := session("file:///f_amd64.s", doc) +
|
||||
frame(11, "textDocument/codeAction", map[string]any{
|
||||
"textDocument": map[string]any{"uri": "file:///f_amd64.s"},
|
||||
"range": map[string]any{"start": map[string]any{"line": 0, "character": 0}, "end": map[string]any{"line": 4, "character": 0}},
|
||||
"context": map[string]any{"diagnostics": []map[string]any{{"code": "missing-ret", "range": map[string]any{"start": map[string]any{"line": 3, "character": 0}, "end": map[string]any{"line": 3, "character": 4}}}}},
|
||||
}) + frame(nil, "exit", nil)
|
||||
msgs := run(t, in)
|
||||
resp := findByID(msgs, 11)
|
||||
if resp == nil {
|
||||
t.Fatal("no codeAction response")
|
||||
}
|
||||
var actions []CodeAction
|
||||
if err := json.Unmarshal(mustResult(t, resp), &actions); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(actions) != 1 {
|
||||
t.Fatalf("actions = %d, want 1", len(actions))
|
||||
}
|
||||
if actions[0].Title != "Add RET to second" {
|
||||
t.Errorf("action title = %q, want Add RET to second", actions[0].Title)
|
||||
}
|
||||
if !strings.Contains(actions[0].Edit.Changes["file:///f_amd64.s"][0].NewText, "\tRET\nTEXT \u00b7second") {
|
||||
t.Errorf("edit does not insert RET at the end of second:\n%s", actions[0].Edit.Changes["file:///f_amd64.s"][0].NewText)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user