github.com/april1989/origin-go-tools@v0.0.32/internal/lsp/source/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 source 6 7 import ( 8 "context" 9 "go/ast" 10 "go/token" 11 "go/types" 12 "path/filepath" 13 "regexp" 14 "strings" 15 16 "github.com/april1989/origin-go-tools/internal/lsp/protocol" 17 "github.com/april1989/origin-go-tools/internal/span" 18 ) 19 20 type LensFunc func(context.Context, Snapshot, FileHandle) ([]protocol.CodeLens, error) 21 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, 29 } 30 } 31 32 var ( 33 testRe = regexp.MustCompile("^Test[^a-z]") 34 benchmarkRe = regexp.MustCompile("^Benchmark[^a-z]") 35 ) 36 37 func runTestCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) { 38 codeLens := make([]protocol.CodeLens, 0) 39 40 if !strings.HasSuffix(fh.URI().Filename(), "_test.go") { 41 return nil, nil 42 } 43 pkg, pgf, err := getParsedFile(ctx, snapshot, fh, WidestPackage) 44 if err != nil { 45 return nil, err 46 } 47 48 var benchFns []string 49 for _, d := range pgf.File.Decls { 50 fn, ok := d.(*ast.FuncDecl) 51 if !ok { 52 continue 53 } 54 if benchmarkRe.MatchString(fn.Name.Name) { 55 benchFns = append(benchFns, fn.Name.Name) 56 } 57 rng, err := newMappedRange(snapshot.FileSet(), pgf.Mapper, d.Pos(), d.Pos()).Range() 58 if err != nil { 59 return nil, err 60 } 61 62 if matchTestFunc(fn, pkg, testRe, "T") { 63 jsonArgs, err := MarshalArgs(fh.URI(), []string{fn.Name.Name}, nil) 64 if err != nil { 65 return nil, err 66 } 67 codeLens = append(codeLens, protocol.CodeLens{ 68 Range: rng, 69 Command: protocol.Command{ 70 Title: "run test", 71 Command: CommandTest.Name, 72 Arguments: jsonArgs, 73 }, 74 }) 75 } 76 77 if matchTestFunc(fn, pkg, benchmarkRe, "B") { 78 jsonArgs, err := MarshalArgs(fh.URI(), nil, []string{fn.Name.Name}) 79 if err != nil { 80 return nil, err 81 } 82 codeLens = append(codeLens, protocol.CodeLens{ 83 Range: rng, 84 Command: protocol.Command{ 85 Title: "run benchmark", 86 Command: CommandTest.Name, 87 Arguments: jsonArgs, 88 }, 89 }) 90 } 91 } 92 // add a code lens to the top of the file which runs all benchmarks in the file 93 rng, err := newMappedRange(snapshot.FileSet(), pgf.Mapper, pgf.File.Package, pgf.File.Package).Range() 94 if err != nil { 95 return nil, err 96 } 97 args, err := MarshalArgs(fh.URI(), []string{}, benchFns) 98 if err != nil { 99 return nil, err 100 } 101 codeLens = append(codeLens, protocol.CodeLens{ 102 Range: rng, 103 Command: protocol.Command{ 104 Title: "run file benchmarks", 105 Command: CommandTest.Name, 106 Arguments: args, 107 }, 108 }) 109 return codeLens, nil 110 } 111 112 func matchTestFunc(fn *ast.FuncDecl, pkg Package, nameRe *regexp.Regexp, paramID string) bool { 113 // Make sure that the function name matches a test function. 114 if !nameRe.MatchString(fn.Name.Name) { 115 return false 116 } 117 info := pkg.GetTypesInfo() 118 if info == nil { 119 return false 120 } 121 obj := info.ObjectOf(fn.Name) 122 if obj == nil { 123 return false 124 } 125 sig, ok := obj.Type().(*types.Signature) 126 if !ok { 127 return false 128 } 129 // Test functions should have only one parameter. 130 if sig.Params().Len() != 1 { 131 return false 132 } 133 134 // Check the type of the only parameter 135 paramTyp, ok := sig.Params().At(0).Type().(*types.Pointer) 136 if !ok { 137 return false 138 } 139 named, ok := paramTyp.Elem().(*types.Named) 140 if !ok { 141 return false 142 } 143 namedObj := named.Obj() 144 if namedObj.Pkg().Path() != "golibexec_testing" { 145 return false 146 } 147 return namedObj.Id() == paramID 148 } 149 150 func goGenerateCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) { 151 pgf, err := snapshot.ParseGo(ctx, fh, ParseFull) 152 if err != nil { 153 return nil, err 154 } 155 const ggDirective = "//go:generate" 156 for _, c := range pgf.File.Comments { 157 for _, l := range c.List { 158 if !strings.HasPrefix(l.Text, ggDirective) { 159 continue 160 } 161 rng, err := newMappedRange(snapshot.FileSet(), pgf.Mapper, l.Pos(), l.Pos()+token.Pos(len(ggDirective))).Range() 162 if err != nil { 163 return nil, err 164 } 165 dir := span.URIFromPath(filepath.Dir(fh.URI().Filename())) 166 nonRecursiveArgs, err := MarshalArgs(dir, false) 167 if err != nil { 168 return nil, err 169 } 170 recursiveArgs, err := MarshalArgs(dir, true) 171 if err != nil { 172 return nil, err 173 } 174 return []protocol.CodeLens{ 175 { 176 Range: rng, 177 Command: protocol.Command{ 178 Title: "run go generate", 179 Command: CommandGenerate.Name, 180 Arguments: nonRecursiveArgs, 181 }, 182 }, 183 { 184 Range: rng, 185 Command: protocol.Command{ 186 Title: "run go generate ./...", 187 Command: CommandGenerate.Name, 188 Arguments: recursiveArgs, 189 }, 190 }, 191 }, nil 192 193 } 194 } 195 return nil, nil 196 } 197 198 func regenerateCgoLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) { 199 pgf, err := snapshot.ParseGo(ctx, fh, ParseFull) 200 if err != nil { 201 return nil, err 202 } 203 var c *ast.ImportSpec 204 for _, imp := range pgf.File.Imports { 205 if imp.Path.Value == `"C"` { 206 c = imp 207 } 208 } 209 if c == nil { 210 return nil, nil 211 } 212 rng, err := newMappedRange(snapshot.FileSet(), pgf.Mapper, c.Pos(), c.EndPos).Range() 213 if err != nil { 214 return nil, err 215 } 216 jsonArgs, err := MarshalArgs(fh.URI()) 217 if err != nil { 218 return nil, err 219 } 220 return []protocol.CodeLens{ 221 { 222 Range: rng, 223 Command: protocol.Command{ 224 Title: "regenerate cgo definitions", 225 Command: CommandRegenerateCgo.Name, 226 Arguments: jsonArgs, 227 }, 228 }, 229 }, nil 230 } 231 232 func toggleDetailsCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) { 233 _, pgf, err := getParsedFile(ctx, snapshot, fh, WidestPackage) 234 if err != nil { 235 return nil, err 236 } 237 rng, err := newMappedRange(snapshot.FileSet(), pgf.Mapper, pgf.File.Package, pgf.File.Package).Range() 238 if err != nil { 239 return nil, err 240 } 241 jsonArgs, err := MarshalArgs(fh.URI()) 242 if err != nil { 243 return nil, err 244 } 245 return []protocol.CodeLens{ 246 { 247 Range: rng, 248 Command: protocol.Command{ 249 Title: "Toggle gc annotation details", 250 Command: CommandToggleDetails.Name, 251 Arguments: jsonArgs, 252 }, 253 }, 254 }, nil 255 }