|  | // Copyright 2020 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package source | 
|  |  | 
|  | import ( | 
|  | "context" | 
|  | "go/ast" | 
|  | "go/token" | 
|  | "go/types" | 
|  | "path/filepath" | 
|  | "regexp" | 
|  | "strings" | 
|  |  | 
|  | "golang.org/x/tools/internal/lsp/command" | 
|  | "golang.org/x/tools/internal/lsp/protocol" | 
|  | "golang.org/x/tools/internal/span" | 
|  | ) | 
|  |  | 
|  | type LensFunc func(context.Context, Snapshot, FileHandle) ([]protocol.CodeLens, error) | 
|  |  | 
|  | // LensFuncs returns the supported lensFuncs for Go files. | 
|  | func LensFuncs() map[command.Command]LensFunc { | 
|  | return map[command.Command]LensFunc{ | 
|  | command.Generate:      goGenerateCodeLens, | 
|  | command.Test:          runTestCodeLens, | 
|  | command.RegenerateCgo: regenerateCgoLens, | 
|  | command.GCDetails:     toggleDetailsCodeLens, | 
|  | } | 
|  | } | 
|  |  | 
|  | var ( | 
|  | testRe      = regexp.MustCompile("^Test[^a-z]") | 
|  | benchmarkRe = regexp.MustCompile("^Benchmark[^a-z]") | 
|  | ) | 
|  |  | 
|  | func runTestCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) { | 
|  | codeLens := make([]protocol.CodeLens, 0) | 
|  |  | 
|  | fns, err := TestsAndBenchmarks(ctx, snapshot, fh) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | puri := protocol.URIFromSpanURI(fh.URI()) | 
|  | for _, fn := range fns.Tests { | 
|  | cmd, err := command.NewTestCommand("run test", puri, []string{fn.Name}, nil) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | rng := protocol.Range{Start: fn.Rng.Start, End: fn.Rng.Start} | 
|  | codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: cmd}) | 
|  | } | 
|  |  | 
|  | for _, fn := range fns.Benchmarks { | 
|  | cmd, err := command.NewTestCommand("run benchmark", puri, nil, []string{fn.Name}) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | rng := protocol.Range{Start: fn.Rng.Start, End: fn.Rng.Start} | 
|  | codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: cmd}) | 
|  | } | 
|  |  | 
|  | if len(fns.Benchmarks) > 0 { | 
|  | _, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | // add a code lens to the top of the file which runs all benchmarks in the file | 
|  | rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, pgf.File.Package, pgf.File.Package).Range() | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | var benches []string | 
|  | for _, fn := range fns.Benchmarks { | 
|  | benches = append(benches, fn.Name) | 
|  | } | 
|  | cmd, err := command.NewTestCommand("run file benchmarks", puri, nil, benches) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: cmd}) | 
|  | } | 
|  | return codeLens, nil | 
|  | } | 
|  |  | 
|  | type testFn struct { | 
|  | Name string | 
|  | Rng  protocol.Range | 
|  | } | 
|  |  | 
|  | type testFns struct { | 
|  | Tests      []testFn | 
|  | Benchmarks []testFn | 
|  | } | 
|  |  | 
|  | func TestsAndBenchmarks(ctx context.Context, snapshot Snapshot, fh FileHandle) (testFns, error) { | 
|  | var out testFns | 
|  |  | 
|  | if !strings.HasSuffix(fh.URI().Filename(), "_test.go") { | 
|  | return out, nil | 
|  | } | 
|  | pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage) | 
|  | if err != nil { | 
|  | return out, err | 
|  | } | 
|  |  | 
|  | for _, d := range pgf.File.Decls { | 
|  | fn, ok := d.(*ast.FuncDecl) | 
|  | if !ok { | 
|  | continue | 
|  | } | 
|  |  | 
|  | rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, d.Pos(), fn.End()).Range() | 
|  | if err != nil { | 
|  | return out, err | 
|  | } | 
|  |  | 
|  | if matchTestFunc(fn, pkg, testRe, "T") { | 
|  | out.Tests = append(out.Tests, testFn{fn.Name.Name, rng}) | 
|  | } | 
|  |  | 
|  | if matchTestFunc(fn, pkg, benchmarkRe, "B") { | 
|  | out.Benchmarks = append(out.Benchmarks, testFn{fn.Name.Name, rng}) | 
|  | } | 
|  | } | 
|  |  | 
|  | return out, nil | 
|  | } | 
|  |  | 
|  | func matchTestFunc(fn *ast.FuncDecl, pkg Package, nameRe *regexp.Regexp, paramID string) bool { | 
|  | // Make sure that the function name matches a test function. | 
|  | if !nameRe.MatchString(fn.Name.Name) { | 
|  | return false | 
|  | } | 
|  | info := pkg.GetTypesInfo() | 
|  | if info == nil { | 
|  | return false | 
|  | } | 
|  | obj := info.ObjectOf(fn.Name) | 
|  | if obj == nil { | 
|  | return false | 
|  | } | 
|  | sig, ok := obj.Type().(*types.Signature) | 
|  | if !ok { | 
|  | return false | 
|  | } | 
|  | // Test functions should have only one parameter. | 
|  | if sig.Params().Len() != 1 { | 
|  | return false | 
|  | } | 
|  |  | 
|  | // Check the type of the only parameter | 
|  | paramTyp, ok := sig.Params().At(0).Type().(*types.Pointer) | 
|  | if !ok { | 
|  | return false | 
|  | } | 
|  | named, ok := paramTyp.Elem().(*types.Named) | 
|  | if !ok { | 
|  | return false | 
|  | } | 
|  | namedObj := named.Obj() | 
|  | if namedObj.Pkg().Path() != "testing" { | 
|  | return false | 
|  | } | 
|  | return namedObj.Id() == paramID | 
|  | } | 
|  |  | 
|  | func goGenerateCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) { | 
|  | pgf, err := snapshot.ParseGo(ctx, fh, ParseFull) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | const ggDirective = "//go:generate" | 
|  | for _, c := range pgf.File.Comments { | 
|  | for _, l := range c.List { | 
|  | if !strings.HasPrefix(l.Text, ggDirective) { | 
|  | continue | 
|  | } | 
|  | rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, l.Pos(), l.Pos()+token.Pos(len(ggDirective))).Range() | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | dir := protocol.URIFromSpanURI(span.URIFromPath(filepath.Dir(fh.URI().Filename()))) | 
|  | nonRecursiveCmd, err := command.NewGenerateCommand("run go generate", command.GenerateArgs{Dir: dir, Recursive: false}) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | recursiveCmd, err := command.NewGenerateCommand("run go generate ./...", command.GenerateArgs{Dir: dir, Recursive: true}) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | return []protocol.CodeLens{ | 
|  | {Range: rng, Command: recursiveCmd}, | 
|  | {Range: rng, Command: nonRecursiveCmd}, | 
|  | }, nil | 
|  |  | 
|  | } | 
|  | } | 
|  | return nil, nil | 
|  | } | 
|  |  | 
|  | func regenerateCgoLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) { | 
|  | pgf, err := snapshot.ParseGo(ctx, fh, ParseFull) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | var c *ast.ImportSpec | 
|  | for _, imp := range pgf.File.Imports { | 
|  | if imp.Path.Value == `"C"` { | 
|  | c = imp | 
|  | } | 
|  | } | 
|  | if c == nil { | 
|  | return nil, nil | 
|  | } | 
|  | rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, c.Pos(), c.EndPos).Range() | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | puri := protocol.URIFromSpanURI(fh.URI()) | 
|  | cmd, err := command.NewRegenerateCgoCommand("regenerate cgo definitions", command.URIArg{URI: puri}) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil | 
|  | } | 
|  |  | 
|  | func toggleDetailsCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) { | 
|  | _, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, pgf.File.Package, pgf.File.Package).Range() | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | puri := protocol.URIFromSpanURI(fh.URI()) | 
|  | cmd, err := command.NewGCDetailsCommand("Toggle gc annotation details", puri) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil | 
|  | } |