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/protocol"
17 "golang.org/x/tools/internal/span"
20 type LensFunc func(context.Context, Snapshot, FileHandle) ([]protocol.CodeLens, error)
22 // LensFuncs returns the supported lensFuncs for Go files.
23 func LensFuncs() map[string]LensFunc {
24 return map[string]LensFunc{
25 CommandGenerate.Name: goGenerateCodeLens,
26 CommandTest.Name: runTestCodeLens,
27 CommandRegenerateCgo.Name: regenerateCgoLens,
28 CommandToggleDetails.Name: toggleDetailsCodeLens,
33 testRe = regexp.MustCompile("^Test[^a-z]")
34 benchmarkRe = regexp.MustCompile("^Benchmark[^a-z]")
37 func runTestCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) {
38 codeLens := make([]protocol.CodeLens, 0)
40 fns, err := TestsAndBenchmarks(ctx, snapshot, fh)
44 for _, fn := range fns.Tests {
45 jsonArgs, err := MarshalArgs(fh.URI(), []string{fn.Name}, nil)
49 codeLens = append(codeLens, protocol.CodeLens{
50 Range: protocol.Range{Start: fn.Rng.Start, End: fn.Rng.Start},
51 Command: protocol.Command{
53 Command: CommandTest.ID(),
59 for _, fn := range fns.Benchmarks {
60 jsonArgs, err := MarshalArgs(fh.URI(), nil, []string{fn.Name})
64 codeLens = append(codeLens, protocol.CodeLens{
65 Range: protocol.Range{Start: fn.Rng.Start, End: fn.Rng.Start},
66 Command: protocol.Command{
67 Title: "run benchmark",
68 Command: CommandTest.ID(),
74 if len(fns.Benchmarks) > 0 {
75 _, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage)
79 // add a code lens to the top of the file which runs all benchmarks in the file
80 rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, pgf.File.Package, pgf.File.Package).Range()
84 args, err := MarshalArgs(fh.URI(), []string{}, fns.Benchmarks)
88 codeLens = append(codeLens, protocol.CodeLens{
90 Command: protocol.Command{
91 Title: "run file benchmarks",
92 Command: CommandTest.ID(),
105 type testFns struct {
110 func TestsAndBenchmarks(ctx context.Context, snapshot Snapshot, fh FileHandle) (testFns, error) {
113 if !strings.HasSuffix(fh.URI().Filename(), "_test.go") {
116 pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage)
121 for _, d := range pgf.File.Decls {
122 fn, ok := d.(*ast.FuncDecl)
127 rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, d.Pos(), fn.End()).Range()
132 if matchTestFunc(fn, pkg, testRe, "T") {
133 out.Tests = append(out.Tests, testFn{fn.Name.Name, rng})
136 if matchTestFunc(fn, pkg, benchmarkRe, "B") {
137 out.Benchmarks = append(out.Benchmarks, testFn{fn.Name.Name, rng})
144 func matchTestFunc(fn *ast.FuncDecl, pkg Package, nameRe *regexp.Regexp, paramID string) bool {
145 // Make sure that the function name matches a test function.
146 if !nameRe.MatchString(fn.Name.Name) {
149 info := pkg.GetTypesInfo()
153 obj := info.ObjectOf(fn.Name)
157 sig, ok := obj.Type().(*types.Signature)
161 // Test functions should have only one parameter.
162 if sig.Params().Len() != 1 {
166 // Check the type of the only parameter
167 paramTyp, ok := sig.Params().At(0).Type().(*types.Pointer)
171 named, ok := paramTyp.Elem().(*types.Named)
175 namedObj := named.Obj()
176 if namedObj.Pkg().Path() != "testing" {
179 return namedObj.Id() == paramID
182 func goGenerateCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) {
183 pgf, err := snapshot.ParseGo(ctx, fh, ParseFull)
187 const ggDirective = "//go:generate"
188 for _, c := range pgf.File.Comments {
189 for _, l := range c.List {
190 if !strings.HasPrefix(l.Text, ggDirective) {
193 rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, l.Pos(), l.Pos()+token.Pos(len(ggDirective))).Range()
197 dir := span.URIFromPath(filepath.Dir(fh.URI().Filename()))
198 nonRecursiveArgs, err := MarshalArgs(dir, false)
202 recursiveArgs, err := MarshalArgs(dir, true)
206 return []protocol.CodeLens{
209 Command: protocol.Command{
210 Title: "run go generate",
211 Command: CommandGenerate.ID(),
212 Arguments: nonRecursiveArgs,
217 Command: protocol.Command{
218 Title: "run go generate ./...",
219 Command: CommandGenerate.ID(),
220 Arguments: recursiveArgs,
230 func regenerateCgoLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) {
231 pgf, err := snapshot.ParseGo(ctx, fh, ParseFull)
235 var c *ast.ImportSpec
236 for _, imp := range pgf.File.Imports {
237 if imp.Path.Value == `"C"` {
244 rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, c.Pos(), c.EndPos).Range()
248 jsonArgs, err := MarshalArgs(fh.URI())
252 return []protocol.CodeLens{
255 Command: protocol.Command{
256 Title: "regenerate cgo definitions",
257 Command: CommandRegenerateCgo.ID(),
264 func toggleDetailsCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) {
265 _, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage)
269 rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, pgf.File.Package, pgf.File.Package).Range()
273 jsonArgs, err := MarshalArgs(fh.URI())
277 return []protocol.CodeLens{{
279 Command: protocol.Command{
280 Title: "Toggle gc annotation details",
281 Command: CommandToggleDetails.ID(),