| // Copyright 2024 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package misc |
| |
| import ( |
| "fmt" |
| "slices" |
| "strings" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| "golang.org/x/tools/gopls/internal/protocol" |
| "golang.org/x/tools/gopls/internal/settings" |
| . "golang.org/x/tools/gopls/internal/test/integration" |
| ) |
| |
| // This test exercises the filtering of code actions in generated files. |
| // Most code actions, being potential edits, are discarded, but |
| // some (GoTest, GoDoc) are pure queries, and so are allowed. |
| func TestCodeActionsInGeneratedFiles(t *testing.T) { |
| const src = ` |
| -- go.mod -- |
| module example.com |
| go 1.19 |
| |
| -- src/a.go -- |
| package a |
| |
| func f() { g() } |
| func g() {} |
| -- gen/a.go -- |
| // Code generated by hand; DO NOT EDIT. |
| package a |
| |
| func f() { g() } |
| func g() {} |
| |
| -- issue72742/a.go -- |
| package main |
| |
| func main(){ |
| fmt.Println("helloworld") |
| } |
| ` |
| |
| Run(t, src, func(t *testing.T, env *Env) { |
| check := func(filename string, re string, want []protocol.CodeActionKind) { |
| env.OpenFile(filename) |
| loc := env.RegexpSearch(filename, re) |
| actions, err := env.Editor.CodeAction(env.Ctx, loc, nil, protocol.CodeActionUnknownTrigger) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| type kinds = []protocol.CodeActionKind |
| got := make(kinds, 0) |
| for _, act := range actions { |
| got = append(got, act.Kind) |
| } |
| |
| if diff := cmp.Diff(want, got); diff != "" { |
| t.Errorf("%s: unexpected CodeActionKinds: (-want +got):\n%s", |
| filename, diff) |
| t.Log(actions) |
| } |
| } |
| |
| check("src/a.go", `g\(\)`, []protocol.CodeActionKind{ |
| settings.AddTest, |
| settings.GoAssembly, |
| settings.GoDoc, |
| settings.GoFreeSymbols, |
| settings.GoSplitPackage, |
| settings.GoToggleCompilerOptDetails, |
| settings.RefactorInlineCall, |
| settings.GoplsDocFeatures, |
| }) |
| |
| check("gen/a.go", `g\(\)`, []protocol.CodeActionKind{ |
| settings.GoAssembly, |
| settings.GoDoc, |
| settings.GoFreeSymbols, |
| settings.GoSplitPackage, |
| settings.GoToggleCompilerOptDetails, |
| settings.GoplsDocFeatures, |
| }) |
| |
| check("issue72742/a.go", `fmt`, []protocol.CodeActionKind{ |
| settings.OrganizeImports, |
| settings.AddTest, |
| settings.GoAssembly, |
| settings.GoDoc, |
| settings.GoFreeSymbols, |
| settings.GoSplitPackage, |
| settings.GoToggleCompilerOptDetails, |
| settings.GoplsDocFeatures, |
| }) |
| }) |
| } |
| |
| // Test refactor.inline.call is not included in automatically triggered code action |
| // unless users want refactoring. |
| // |
| // (The mechanism behind this behavior has changed. It was added when |
| // we used to interpret CodeAction(Only=[]) as "all kinds", which was |
| // a distracting nuisance (too many lightbulbs); this was fixed by |
| // adding special logic to refactor.inline.call to respect the trigger |
| // kind; but now we do this for all actions (for similar reasons) and |
| // interpret Only=[] as Only=[quickfix] unless triggerKind=invoked; |
| // except that the test client always requests CodeAction(Only=[""]). |
| // So, we should remove the special logic from refactorInlineCall |
| // and vary the Only parameter used by the test client.) |
| func TestVSCodeIssue65167(t *testing.T) { |
| const vim1 = `package main |
| |
| func main() { |
| Func() // range to be selected |
| } |
| |
| func Func() int { return 0 } |
| ` |
| |
| Run(t, "", func(t *testing.T, env *Env) { |
| env.CreateBuffer("main.go", vim1) |
| for _, trigger := range []protocol.CodeActionTriggerKind{ |
| protocol.CodeActionUnknownTrigger, |
| protocol.CodeActionInvoked, |
| protocol.CodeActionAutomatic, |
| } { |
| t.Run(fmt.Sprintf("trigger=%v", trigger), func(t *testing.T) { |
| for _, selectedRange := range []bool{false, true} { |
| t.Run(fmt.Sprintf("range=%t", selectedRange), func(t *testing.T) { |
| loc := env.RegexpSearch("main.go", "Func") |
| if !selectedRange { |
| // assume the cursor is placed at the beginning of `Func`, so end==start. |
| loc.Range.End = loc.Range.Start |
| } |
| actions := env.CodeAction(loc, nil, trigger) |
| want := trigger != protocol.CodeActionAutomatic || selectedRange |
| if got := slices.ContainsFunc(actions, func(act protocol.CodeAction) bool { |
| return act.Kind == settings.RefactorInlineCall |
| }); got != want { |
| t.Errorf("got refactor.inline.call = %t, want %t", got, want) |
| } |
| }) |
| } |
| }) |
| } |
| }) |
| } |
| |
| // TestDescendingRange isn't really a test of CodeAction at all: it |
| // merely tests the response of the server to any (start, end) range |
| // that is descending. See #74394. |
| func TestDescendingRange(t *testing.T) { |
| const src = ` |
| -- go.mod -- |
| module example.com |
| go 1.19 |
| |
| -- a/a.go -- |
| package a |
| ` |
| Run(t, src, func(t *testing.T, env *Env) { |
| env.OpenFile("a/a.go") |
| loc := env.RegexpSearch("a/a.go", "package") |
| rng := &loc.Range |
| rng.Start, rng.End = rng.End, rng.Start |
| _, err := env.Editor.CodeAction(env.Ctx, loc, nil, protocol.CodeActionUnknownTrigger) |
| got, wantSubstr := fmt.Sprint(err), "start (offset 7) > end (offset 0)" |
| if !strings.Contains(got, wantSubstr) { |
| t.Fatalf("CodeAction error: got %q, want substring %q", got, wantSubstr) |
| } |
| }) |
| } |
| |
| // TestIssue76235 is a regression test for a crash in CodeAction |
| // (refactor.extract.variable-all). It can't be a marker test since |
| // the fixed behavior is a no-op: no code actions, no crash. |
| func TestIssue76235(t *testing.T) { |
| const src = ` |
| -- a/a.go -- |
| package a; func f([3]int) |
| ` |
| Run(t, src, func(t *testing.T, env *Env) { |
| env.OpenFile("a/a.go") |
| loc := env.RegexpSearch("a/a.go", "3") |
| env.Editor.CodeAction(env.Ctx, loc, nil, protocol.CodeActionUnknownTrigger) // ignore error |
| }) |
| } |