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  }