1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
16 "golang.org/x/tools/internal/lsp/command"
17 "golang.org/x/tools/internal/lsp/protocol"
18 "golang.org/x/tools/internal/span"
21 type LensFunc func(context.Context, Snapshot, FileHandle) ([]protocol.CodeLens, error)
23 // LensFuncs returns the supported lensFuncs for Go files.
24 func LensFuncs() map[command.Command]LensFunc {
25 return map[command.Command]LensFunc{
26 command.Generate: goGenerateCodeLens,
27 command.Test: runTestCodeLens,
28 command.RegenerateCgo: regenerateCgoLens,
29 command.GCDetails: toggleDetailsCodeLens,
34 testRe = regexp.MustCompile("^Test[^a-z]")
35 benchmarkRe = regexp.MustCompile("^Benchmark[^a-z]")
38 func runTestCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) {
39 codeLens := make([]protocol.CodeLens, 0)
41 fns, err := TestsAndBenchmarks(ctx, snapshot, fh)
45 puri := protocol.URIFromSpanURI(fh.URI())
46 for _, fn := range fns.Tests {
47 cmd, err := command.NewTestCommand("run test", puri, []string{fn.Name}, nil)
51 rng := protocol.Range{Start: fn.Rng.Start, End: fn.Rng.Start}
52 codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: cmd})
55 for _, fn := range fns.Benchmarks {
56 cmd, err := command.NewTestCommand("run benchmark", puri, nil, []string{fn.Name})
60 rng := protocol.Range{Start: fn.Rng.Start, End: fn.Rng.Start}
61 codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: cmd})
64 if len(fns.Benchmarks) > 0 {
65 _, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage)
69 // add a code lens to the top of the file which runs all benchmarks in the file
70 rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, pgf.File.Package, pgf.File.Package).Range()
75 for _, fn := range fns.Benchmarks {
76 benches = append(benches, fn.Name)
78 cmd, err := command.NewTestCommand("run file benchmarks", puri, nil, benches)
82 codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: cmd})
97 func TestsAndBenchmarks(ctx context.Context, snapshot Snapshot, fh FileHandle) (testFns, error) {
100 if !strings.HasSuffix(fh.URI().Filename(), "_test.go") {
103 pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage)
108 for _, d := range pgf.File.Decls {
109 fn, ok := d.(*ast.FuncDecl)
114 rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, d.Pos(), fn.End()).Range()
119 if matchTestFunc(fn, pkg, testRe, "T") {
120 out.Tests = append(out.Tests, testFn{fn.Name.Name, rng})
123 if matchTestFunc(fn, pkg, benchmarkRe, "B") {
124 out.Benchmarks = append(out.Benchmarks, testFn{fn.Name.Name, rng})
131 func matchTestFunc(fn *ast.FuncDecl, pkg Package, nameRe *regexp.Regexp, paramID string) bool {
132 // Make sure that the function name matches a test function.
133 if !nameRe.MatchString(fn.Name.Name) {
136 info := pkg.GetTypesInfo()
140 obj := info.ObjectOf(fn.Name)
144 sig, ok := obj.Type().(*types.Signature)
148 // Test functions should have only one parameter.
149 if sig.Params().Len() != 1 {
153 // Check the type of the only parameter
154 paramTyp, ok := sig.Params().At(0).Type().(*types.Pointer)
158 named, ok := paramTyp.Elem().(*types.Named)
162 namedObj := named.Obj()
163 if namedObj.Pkg().Path() != "testing" {
166 return namedObj.Id() == paramID
169 func goGenerateCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) {
170 pgf, err := snapshot.ParseGo(ctx, fh, ParseFull)
174 const ggDirective = "//go:generate"
175 for _, c := range pgf.File.Comments {
176 for _, l := range c.List {
177 if !strings.HasPrefix(l.Text, ggDirective) {
180 rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, l.Pos(), l.Pos()+token.Pos(len(ggDirective))).Range()
184 dir := protocol.URIFromSpanURI(span.URIFromPath(filepath.Dir(fh.URI().Filename())))
185 nonRecursiveCmd, err := command.NewGenerateCommand("run go generate", command.GenerateArgs{Dir: dir, Recursive: false})
189 recursiveCmd, err := command.NewGenerateCommand("run go generate ./...", command.GenerateArgs{Dir: dir, Recursive: true})
193 return []protocol.CodeLens{
194 {Range: rng, Command: recursiveCmd},
195 {Range: rng, Command: nonRecursiveCmd},
203 func regenerateCgoLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) {
204 pgf, err := snapshot.ParseGo(ctx, fh, ParseFull)
208 var c *ast.ImportSpec
209 for _, imp := range pgf.File.Imports {
210 if imp.Path.Value == `"C"` {
217 rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, c.Pos(), c.EndPos).Range()
221 puri := protocol.URIFromSpanURI(fh.URI())
222 cmd, err := command.NewRegenerateCgoCommand("regenerate cgo definitions", command.URIArg{URI: puri})
226 return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil
229 func toggleDetailsCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) {
230 _, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage)
234 rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, pgf.File.Package, pgf.File.Package).Range()
238 puri := protocol.URIFromSpanURI(fh.URI())
239 cmd, err := command.NewGCDetailsCommand("Toggle gc annotation details", puri)
243 return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil