Skip to content
Open
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
11 changes: 11 additions & 0 deletions gopls/doc/codelenses.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ Default: on

File type: Go

## `go_to_test`: Go to the functions's Test, Example, Benchmark, or Fuzz declarations


This codelens source annotates function and method declarations
with their corresponding Test, Example, Benchmark, and Fuzz functions.


Default: off

File type: Go

## `regenerate_cgo`: Re-generate cgo declarations


Expand Down
2 changes: 1 addition & 1 deletion gopls/internal/cache/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -976,7 +976,7 @@ func (s *Snapshot) WorkspaceMetadata(ctx context.Context) ([]*metadata.Package,
defer s.mu.Unlock()

meta := make([]*metadata.Package, 0, s.workspacePackages.Len())
for id, _ := range s.workspacePackages.All() {
for id := range s.workspacePackages.All() {
meta = append(meta, s.meta.Packages[id])
}
return meta, nil
Expand Down
73 changes: 50 additions & 23 deletions gopls/internal/cache/testfuncs/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"go/ast"
"go/constant"
"go/types"
"iter"
"strings"
"unicode"
"unicode/utf8"
Expand Down Expand Up @@ -35,22 +36,36 @@ func (index *Index) Encode() []byte {
return packageCodec.Encode(index.pkg)
}

func (index *Index) All() []Result {
var results []Result
for _, file := range index.pkg.Files {
for _, test := range file.Tests {
results = append(results, test.result())
func (index *Index) All() iter.Seq[Result] {
return func(yield func(Result) bool) {
for _, file := range index.pkg.Files {
for _, test := range file.Tests {
if !yield(test.result()) {
return
}
}
}
}
return results
}

// A Result reports a test function
type Result struct {
Location protocol.Location // location of the test
Name string // name of the test
Type TestType // type of the test
Subtest bool
}

type TestType int

const (
TypeInvalid TestType = iota
TypeTest
TypeBenchmark
TypeFuzz
TypeExample
)

// NewIndex returns a new index of method-set information for all
// package-level types in the specified package.
func NewIndex(files []*parsego.File, info *types.Info) *Index {
Expand Down Expand Up @@ -84,15 +99,16 @@ func (b *indexBuilder) build(files []*parsego.File, info *types.Info) *Index {
continue
}

isTest, isExample := isTestOrExample(obj)
if !isTest && !isExample {
testType := getTestType(obj)
if testType == TypeInvalid {
continue
}

var t gobTest
t.Name = decl.Name.Name
t.Location.URI = file.URI
t.Location.Range, _ = file.NodeRange(decl)
t.Type = testType

i, ok := b.fileIndex[t.Location.URI]
if !ok {
Expand All @@ -105,7 +121,8 @@ func (b *indexBuilder) build(files []*parsego.File, info *types.Info) *Index {
b.visited[obj] = true

// Check for subtests
if isTest {
switch testType {
case TypeTest, TypeBenchmark, TypeFuzz:
b.Files[i].Tests = append(b.Files[i].Tests, b.findSubtests(t, decl.Type, decl.Body, file, files, info)...)
}
}
Expand Down Expand Up @@ -168,6 +185,8 @@ func (b *indexBuilder) findSubtests(parent gobTest, typ *ast.FuncType, body *ast
t.Name = b.uniqueName(parent.Name, rewrite(constant.StringVal(val)))
t.Location.URI = file.URI
t.Location.Range, _ = file.NodeRange(call)
t.Type = parent.Type
t.Subtest = true
tests = append(tests, t)

fn, typ, body := findFunc(files, info, body, call.Args[1])
Expand All @@ -182,7 +201,8 @@ func (b *indexBuilder) findSubtests(parent gobTest, typ *ast.FuncType, body *ast
}

// Never recurse if the second argument is a top-level test function
if isTest, _ := isTestOrExample(fn); isTest {
switch getTestType(fn) {
case TypeTest, TypeBenchmark, TypeFuzz:
continue
}

Expand Down Expand Up @@ -258,30 +278,35 @@ func findFunc(files []*parsego.File, info *types.Info, body *ast.BlockStmt, expr
return nil, nil, nil
}

// isTestOrExample reports whether the given func is a testing func or an
// example func (or neither). isTestOrExample returns (true, false) for testing
// funcs, (false, true) for example funcs, and (false, false) otherwise.
func isTestOrExample(fn *types.Func) (isTest, isExample bool) {
// getTestType reports the test type of the given function.
func getTestType(fn *types.Func) TestType {
sig := fn.Type().(*types.Signature)
if sig.Params().Len() == 0 &&
sig.Results().Len() == 0 {
return false, isTestName(fn.Name(), "Example")
if sig.Params().Len() == 0 && sig.Results().Len() == 0 {
if isTestName(fn.Name(), "Example") {
return TypeExample
}
return TypeInvalid
}

kind, ok := testKind(sig)
if !ok {
return false, false
return TypeInvalid
}
switch kind.Name() {
case "T":
return isTestName(fn.Name(), "Test"), false
if isTestName(fn.Name(), "Test") {
return TypeTest
}
case "B":
return isTestName(fn.Name(), "Benchmark"), false
if isTestName(fn.Name(), "Benchmark") {
return TypeBenchmark
}
case "F":
return isTestName(fn.Name(), "Fuzz"), false
default:
return false, false // "can't happen" (see testKind)
if isTestName(fn.Name(), "Fuzz") {
return TypeFuzz
}
}
return TypeInvalid
}

// isTestName reports whether name is a valid test name for the test kind
Expand Down Expand Up @@ -352,6 +377,8 @@ type gobFile struct {
type gobTest struct {
Location protocol.Location // location of the test
Name string // name of the test
Type TestType // type of the test
Subtest bool
}

func (t *gobTest) result() Result {
Expand Down
14 changes: 14 additions & 0 deletions gopls/internal/doc/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -1917,6 +1917,12 @@
"Default": "true",
"Status": ""
},
{
"Name": "\"go_to_test\"",
"Doc": "`\"go_to_test\"`: Go to the functions's Test, Example, Benchmark, or Fuzz declarations\n\nThis codelens source annotates function and method declarations\nwith their corresponding Test, Example, Benchmark, and Fuzz functions.\n",
"Default": "false",
"Status": ""
},
{
"Name": "\"regenerate_cgo\"",
"Doc": "`\"regenerate_cgo\"`: Re-generate cgo declarations\n\nThis codelens source annotates an `import \"C\"` declaration\nwith a command to re-run the [cgo\ncommand](https://pkg.go.dev/cmd/cgo) to regenerate the\ncorresponding Go declarations.\n\nUse this after editing the C code in comments attached to\nthe import, or in C header files included by it.\n",
Expand Down Expand Up @@ -2104,6 +2110,14 @@
"Default": true,
"Status": ""
},
{
"FileType": "Go",
"Lens": "go_to_test",
"Title": "Go to the functions's Test, Example, Benchmark, or Fuzz declarations",
"Doc": "\nThis codelens source annotates function and method declarations\nwith their corresponding Test, Example, Benchmark, and Fuzz functions.\n",
"Default": false,
"Status": ""
},
{
"FileType": "Go",
"Lens": "regenerate_cgo",
Expand Down
Loading