blob: a2f2ca004f83d5ab14b10a0fa66339981480e589 [file] [log] [blame]
// 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
})
}