golang.org/x/tools/gopls@v0.15.3/internal/golang/code_lens.go (about) 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. 4 5 package golang 6 7 import ( 8 "context" 9 "go/ast" 10 "go/token" 11 "go/types" 12 "regexp" 13 "strings" 14 15 "golang.org/x/tools/gopls/internal/cache" 16 "golang.org/x/tools/gopls/internal/file" 17 "golang.org/x/tools/gopls/internal/protocol" 18 "golang.org/x/tools/gopls/internal/protocol/command" 19 ) 20 21 type LensFunc func(context.Context, *cache.Snapshot, file.Handle) ([]protocol.CodeLens, error) 22 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, 30 } 31 } 32 33 var ( 34 testRe = regexp.MustCompile(`^Test([^a-z]|$)`) // TestFoo or Test but not Testable 35 benchmarkRe = regexp.MustCompile(`^Benchmark([^a-z]|$)`) 36 ) 37 38 func runTestCodeLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.CodeLens, error) { 39 var codeLens []protocol.CodeLens 40 41 pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI()) 42 if err != nil { 43 return nil, err 44 } 45 fns, err := TestsAndBenchmarks(pkg, pgf) 46 if err != nil { 47 return nil, err 48 } 49 puri := fh.URI() 50 for _, fn := range fns.Tests { 51 cmd, err := command.NewTestCommand("run test", puri, []string{fn.Name}, nil) 52 if err != nil { 53 return nil, err 54 } 55 rng := protocol.Range{Start: fn.Rng.Start, End: fn.Rng.Start} 56 codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: &cmd}) 57 } 58 59 for _, fn := range fns.Benchmarks { 60 cmd, err := command.NewTestCommand("run benchmark", puri, nil, []string{fn.Name}) 61 if err != nil { 62 return nil, err 63 } 64 rng := protocol.Range{Start: fn.Rng.Start, End: fn.Rng.Start} 65 codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: &cmd}) 66 } 67 68 if len(fns.Benchmarks) > 0 { 69 pgf, err := snapshot.ParseGo(ctx, fh, ParseFull) 70 if err != nil { 71 return nil, err 72 } 73 // add a code lens to the top of the file which runs all benchmarks in the file 74 rng, err := pgf.PosRange(pgf.File.Package, pgf.File.Package) 75 if err != nil { 76 return nil, err 77 } 78 var benches []string 79 for _, fn := range fns.Benchmarks { 80 benches = append(benches, fn.Name) 81 } 82 cmd, err := command.NewTestCommand("run file benchmarks", puri, nil, benches) 83 if err != nil { 84 return nil, err 85 } 86 codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: &cmd}) 87 } 88 return codeLens, nil 89 } 90 91 type TestFn struct { 92 Name string 93 Rng protocol.Range 94 } 95 96 type TestFns struct { 97 Tests []TestFn 98 Benchmarks []TestFn 99 } 100 101 func TestsAndBenchmarks(pkg *cache.Package, pgf *ParsedGoFile) (TestFns, error) { 102 var out TestFns 103 104 if !strings.HasSuffix(pgf.URI.Path(), "_test.go") { 105 return out, nil 106 } 107 108 for _, d := range pgf.File.Decls { 109 fn, ok := d.(*ast.FuncDecl) 110 if !ok { 111 continue 112 } 113 114 rng, err := pgf.NodeRange(fn) 115 if err != nil { 116 return out, err 117 } 118 119 if matchTestFunc(fn, pkg, testRe, "T") { 120 out.Tests = append(out.Tests, TestFn{fn.Name.Name, rng}) 121 } 122 123 if matchTestFunc(fn, pkg, benchmarkRe, "B") { 124 out.Benchmarks = append(out.Benchmarks, TestFn{fn.Name.Name, rng}) 125 } 126 } 127 128 return out, nil 129 } 130 131 func matchTestFunc(fn *ast.FuncDecl, pkg *cache.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) { 134 return false 135 } 136 info := pkg.GetTypesInfo() 137 if info == nil { 138 return false 139 } 140 obj := info.ObjectOf(fn.Name) 141 if obj == nil { 142 return false 143 } 144 sig, ok := obj.Type().(*types.Signature) 145 if !ok { 146 return false 147 } 148 // Test functions should have only one parameter. 149 if sig.Params().Len() != 1 { 150 return false 151 } 152 153 // Check the type of the only parameter 154 paramTyp, ok := sig.Params().At(0).Type().(*types.Pointer) 155 if !ok { 156 return false 157 } 158 named, ok := paramTyp.Elem().(*types.Named) 159 if !ok { 160 return false 161 } 162 namedObj := named.Obj() 163 if namedObj.Pkg().Path() != "testing" { 164 return false 165 } 166 return namedObj.Id() == paramID 167 } 168 169 func goGenerateCodeLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.CodeLens, error) { 170 pgf, err := snapshot.ParseGo(ctx, fh, ParseFull) 171 if err != nil { 172 return nil, err 173 } 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) { 178 continue 179 } 180 rng, err := pgf.PosRange(l.Pos(), l.Pos()+token.Pos(len(ggDirective))) 181 if err != nil { 182 return nil, err 183 } 184 dir := fh.URI().Dir() 185 nonRecursiveCmd, err := command.NewGenerateCommand("run go generate", command.GenerateArgs{Dir: dir, Recursive: false}) 186 if err != nil { 187 return nil, err 188 } 189 recursiveCmd, err := command.NewGenerateCommand("run go generate ./...", command.GenerateArgs{Dir: dir, Recursive: true}) 190 if err != nil { 191 return nil, err 192 } 193 return []protocol.CodeLens{ 194 {Range: rng, Command: &recursiveCmd}, 195 {Range: rng, Command: &nonRecursiveCmd}, 196 }, nil 197 198 } 199 } 200 return nil, nil 201 } 202 203 func regenerateCgoLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.CodeLens, error) { 204 pgf, err := snapshot.ParseGo(ctx, fh, ParseFull) 205 if err != nil { 206 return nil, err 207 } 208 var c *ast.ImportSpec 209 for _, imp := range pgf.File.Imports { 210 if imp.Path.Value == `"C"` { 211 c = imp 212 } 213 } 214 if c == nil { 215 return nil, nil 216 } 217 rng, err := pgf.NodeRange(c) 218 if err != nil { 219 return nil, err 220 } 221 puri := fh.URI() 222 cmd, err := command.NewRegenerateCgoCommand("regenerate cgo definitions", command.URIArg{URI: puri}) 223 if err != nil { 224 return nil, err 225 } 226 return []protocol.CodeLens{{Range: rng, Command: &cmd}}, nil 227 } 228 229 func toggleDetailsCodeLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.CodeLens, error) { 230 pgf, err := snapshot.ParseGo(ctx, fh, ParseFull) 231 if err != nil { 232 return nil, err 233 } 234 if !pgf.File.Package.IsValid() { 235 // Without a package name we have nowhere to put the codelens, so give up. 236 return nil, nil 237 } 238 rng, err := pgf.PosRange(pgf.File.Package, pgf.File.Package) 239 if err != nil { 240 return nil, err 241 } 242 puri := fh.URI() 243 cmd, err := command.NewGCDetailsCommand("Toggle gc annotation details", puri) 244 if err != nil { 245 return nil, err 246 } 247 return []protocol.CodeLens{{Range: rng, Command: &cmd}}, nil 248 }