feat(lsp): document directives on hover and widen completion
Assisted-by: GLM 5.3
This commit is contained in:
@@ -806,6 +806,171 @@ func TestSemanticTokensPipeOperator(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestCodeActionDeclaresArgSize pins the missing-argsize quick fix: the
|
||||
// frame operand $0 grows to $0-16, the area the // func signature implies.
|
||||
func TestCodeActionDeclaresArgSize(t *testing.T) {
|
||||
doc := "// func f(a int) int\n" +
|
||||
"TEXT \u00b7f(SB), $0\n" +
|
||||
"\tMOVQ a+0(FP), AX\n" +
|
||||
"\tMOVQ AX, ret+8(FP)\n" +
|
||||
"\tRET\n"
|
||||
in := session("file:///f_amd64.s", doc) +
|
||||
frame(34, "textDocument/codeAction", map[string]any{
|
||||
"textDocument": map[string]any{"uri": "file:///f_amd64.s"},
|
||||
"range": map[string]any{"start": map[string]any{"line": 1, "character": 0}, "end": map[string]any{"line": 1, "character": 4}},
|
||||
"context": map[string]any{"diagnostics": []map[string]any{
|
||||
{"code": "missing-argsize", "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, 34)
|
||||
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 != "Declare argument area -16" {
|
||||
t.Errorf("action title = %q, want Declare argument area -16", actions[0].Title)
|
||||
}
|
||||
edits := actions[0].Edit.Changes["file:///f_amd64.s"]
|
||||
if len(edits) != 1 || edits[0].NewText != "$0-16" {
|
||||
t.Fatalf("edit = %+v, want the frame operand rewritten to $0-16", edits)
|
||||
}
|
||||
if got := applyEdits(doc, edits); !strings.Contains(got, "TEXT \u00b7f(SB), $0-16") {
|
||||
t.Errorf("edited document = %q, want $0-16 on the TEXT line", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewLintRulesPublished checks that the new rule families reach the
|
||||
// editor through publishDiagnostics: the DATA structure errors and the
|
||||
// addressing edges.
|
||||
func TestNewLintRulesPublished(t *testing.T) {
|
||||
doc := "#include \"textflag.h\"\n" +
|
||||
"DATA ·tab+0(SB)/3, $7\n" +
|
||||
"GLOBL ·tab(SB), RODATA, $8\n" +
|
||||
"TEXT ·f(SB), NOSPLIT, $0-8\n" +
|
||||
"\tMOVQ 0(FP), AX\n" +
|
||||
"\tRET\n"
|
||||
msgs := run(t, session("file:///f_amd64.s", doc)+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 := map[string]bool{}
|
||||
for _, d := range p.Diagnostics {
|
||||
found[d.Code] = true
|
||||
}
|
||||
for _, want := range []string{"data-width", "unnamed-fp-reference"} {
|
||||
if !found[want] {
|
||||
t.Errorf("want a %s diagnostic, got %+v", want, p.Diagnostics)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCompletionDirectives checks the directive completions: the annotation
|
||||
// and padding pseudo-ops appear beside TEXT, DATA and GLOBL.
|
||||
func TestCompletionDirectives(t *testing.T) {
|
||||
in := session("file:///f_amd64.s", cleanDoc) +
|
||||
frame(35, "textDocument/completion", map[string]any{
|
||||
"textDocument": map[string]any{"uri": "file:///f_amd64.s"},
|
||||
"position": map[string]any{"line": 2, "character": 1},
|
||||
}) + frame(nil, "exit", nil)
|
||||
msgs := run(t, in)
|
||||
resp := findByID(msgs, 35)
|
||||
if resp == nil {
|
||||
t.Fatal("no completion response")
|
||||
}
|
||||
var items []CompletionItem
|
||||
if err := json.Unmarshal(mustResult(t, resp), &items); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
labels := map[string]bool{}
|
||||
for _, it := range items {
|
||||
labels[it.Label] = true
|
||||
}
|
||||
for _, want := range []string{"PCALIGN", "FUNCDATA", "PCDATA", "BYTE", "QUAD"} {
|
||||
if !labels[want] {
|
||||
t.Errorf("completion missing %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestHoverDirectives checks hover over the directives the language defines.
|
||||
func TestHoverDirectives(t *testing.T) {
|
||||
doc := "#include \"textflag.h\"\n" +
|
||||
"TEXT ·f(SB), NOSPLIT, $8\n" +
|
||||
"\tPCALIGN $32\n" +
|
||||
"\tMOVQ $1, x-8(SP)\n" +
|
||||
"\tRET\n"
|
||||
in := session("file:///f_amd64.s", doc) +
|
||||
frame(36, "textDocument/hover", map[string]any{
|
||||
"textDocument": map[string]any{"uri": "file:///f_amd64.s"},
|
||||
"position": map[string]any{"line": 2, "character": 2}, // on PCALIGN
|
||||
}) +
|
||||
frame(37, "textDocument/hover", map[string]any{
|
||||
"textDocument": map[string]any{"uri": "file:///f_amd64.s"},
|
||||
"position": map[string]any{"line": 1, "character": 2}, // on TEXT
|
||||
}) + frame(nil, "exit", nil)
|
||||
msgs := run(t, in)
|
||||
for id, want := range map[int]string{36: "boundary", 37: "framesize-argsize"} {
|
||||
resp := findByID(msgs, id)
|
||||
if resp == nil {
|
||||
t.Fatalf("no hover response %d", id)
|
||||
}
|
||||
var h Hover
|
||||
if err := json.Unmarshal(mustResult(t, resp), &h); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(h.Contents.Value, want) {
|
||||
t.Errorf("hover %d = %q, want it to mention %q", id, h.Contents.Value, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCodeActionFallthroughAddsRet pins the quick fix for the second
|
||||
// missing-ret shape: a function that has a RET but can fall off its end
|
||||
// gets the RET appended after its last statement.
|
||||
func TestCodeActionFallthroughAddsRet(t *testing.T) {
|
||||
doc := "#include \"textflag.h\"\n" +
|
||||
"TEXT \u00b7f(SB), NOSPLIT, $0\n" +
|
||||
"\tCMPQ AX, $0\n" +
|
||||
"\tJNE done\n" +
|
||||
"\tRET\n" +
|
||||
"done:\n" +
|
||||
"\tMOVQ $2, BX\n"
|
||||
in := session("file:///f_amd64.s", doc) +
|
||||
frame(38, "textDocument/codeAction", map[string]any{
|
||||
"textDocument": map[string]any{"uri": "file:///f_amd64.s"},
|
||||
"range": map[string]any{"start": map[string]any{"line": 1, "character": 0}, "end": map[string]any{"line": 6, "character": 0}},
|
||||
"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, 38)
|
||||
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))
|
||||
}
|
||||
newText := actions[0].Edit.Changes["file:///f_amd64.s"][0].NewText
|
||||
if !strings.HasSuffix(newText, "MOVQ $2, BX\n\tRET\n") {
|
||||
t.Errorf("edit does not append RET after the last statement:\n%s", newText)
|
||||
}
|
||||
}
|
||||
|
||||
// mustResult re-marshals a response result into raw JSON for typed decoding.
|
||||
func mustResult(t *testing.T, m *rpcMessage) []byte {
|
||||
t.Helper()
|
||||
|
||||
Reference in New Issue
Block a user