.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.1-0.20210319172145-bda8f5cee399 / internal / lsp / source / code_lens.go
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 source
6
7 import (
8         "context"
9         "go/ast"
10         "go/token"
11         "go/types"
12         "path/filepath"
13         "regexp"
14         "strings"
15
16         "golang.org/x/tools/internal/lsp/command"
17         "golang.org/x/tools/internal/lsp/protocol"
18         "golang.org/x/tools/internal/span"
19 )
20
21 type LensFunc func(context.Context, Snapshot, FileHandle) ([]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]")
35         benchmarkRe = regexp.MustCompile("^Benchmark[^a-z]")
36 )
37
38 func runTestCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) {
39         codeLens := make([]protocol.CodeLens, 0)
40
41         fns, err := TestsAndBenchmarks(ctx, snapshot, fh)
42         if err != nil {
43                 return nil, err
44         }
45         puri := protocol.URIFromSpanURI(fh.URI())
46         for _, fn := range fns.Tests {
47                 cmd, err := command.NewTestCommand("run test", puri, []string{fn.Name}, nil)
48                 if err != nil {
49                         return nil, err
50                 }
51                 rng := protocol.Range{Start: fn.Rng.Start, End: fn.Rng.Start}
52                 codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: cmd})
53         }
54
55         for _, fn := range fns.Benchmarks {
56                 cmd, err := command.NewTestCommand("run benchmark", puri, nil, []string{fn.Name})
57                 if err != nil {
58                         return nil, err
59                 }
60                 rng := protocol.Range{Start: fn.Rng.Start, End: fn.Rng.Start}
61                 codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: cmd})
62         }
63
64         if len(fns.Benchmarks) > 0 {
65                 _, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage)
66                 if err != nil {
67                         return nil, err
68                 }
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()
71                 if err != nil {
72                         return nil, err
73                 }
74                 var benches []string
75                 for _, fn := range fns.Benchmarks {
76                         benches = append(benches, fn.Name)
77                 }
78                 cmd, err := command.NewTestCommand("run file benchmarks", puri, nil, benches)
79                 if err != nil {
80                         return nil, err
81                 }
82                 codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: cmd})
83         }
84         return codeLens, nil
85 }
86
87 type testFn struct {
88         Name string
89         Rng  protocol.Range
90 }
91
92 type testFns struct {
93         Tests      []testFn
94         Benchmarks []testFn
95 }
96
97 func TestsAndBenchmarks(ctx context.Context, snapshot Snapshot, fh FileHandle) (testFns, error) {
98         var out testFns
99
100         if !strings.HasSuffix(fh.URI().Filename(), "_test.go") {
101                 return out, nil
102         }
103         pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage)
104         if err != nil {
105                 return out, err
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 := NewMappedRange(snapshot.FileSet(), pgf.Mapper, d.Pos(), fn.End()).Range()
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 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 Snapshot, fh FileHandle) ([]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 := NewMappedRange(snapshot.FileSet(), pgf.Mapper, l.Pos(), l.Pos()+token.Pos(len(ggDirective))).Range()
181                         if err != nil {
182                                 return nil, err
183                         }
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})
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 Snapshot, fh FileHandle) ([]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 := NewMappedRange(snapshot.FileSet(), pgf.Mapper, c.Pos(), c.EndPos).Range()
218         if err != nil {
219                 return nil, err
220         }
221         puri := protocol.URIFromSpanURI(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 Snapshot, fh FileHandle) ([]protocol.CodeLens, error) {
230         _, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage)
231         if err != nil {
232                 return nil, err
233         }
234         rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, pgf.File.Package, pgf.File.Package).Range()
235         if err != nil {
236                 return nil, err
237         }
238         puri := protocol.URIFromSpanURI(fh.URI())
239         cmd, err := command.NewGCDetailsCommand("Toggle gc annotation details", puri)
240         if err != nil {
241                 return nil, err
242         }
243         return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil
244 }