github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/source/completion/fuzz.go (about)

     1  // Copyright 2022 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 completion
     6  
     7  import (
     8  	"fmt"
     9  	"go/ast"
    10  	"go/token"
    11  	"go/types"
    12  	"strings"
    13  
    14  	"github.com/powerman/golang-tools/internal/lsp/protocol"
    15  )
    16  
    17  // golang/go#51089
    18  // *testing.F deserves special treatment as member use is constrained:
    19  // The arguments to f.Fuzz are determined by the arguments to a previous f.Add
    20  // Inside f.Fuzz only f.Failed and f.Name are allowed.
    21  // PJW: are there other packages where we can deduce usage constraints?
    22  
    23  // if we find fuzz completions, then return true, as those are the only completions to offer
    24  func (c *completer) fuzz(typ types.Type, mset *types.MethodSet, imp *importInfo, cb func(candidate), fset *token.FileSet) bool {
    25  	// 1. inside f.Fuzz? (only f.Failed and f.Name)
    26  	// 2. possible completing f.Fuzz?
    27  	//    [Ident,SelectorExpr,Callexpr,ExprStmt,BlockiStmt,FuncDecl(Fuzz...)]
    28  	// 3. before f.Fuzz, same (for 2., offer choice when looking at an F)
    29  
    30  	// does the path contain FuncLit as arg to f.Fuzz CallExpr?
    31  	inside := false
    32  Loop:
    33  	for i, n := range c.path {
    34  		switch v := n.(type) {
    35  		case *ast.CallExpr:
    36  			if len(v.Args) != 1 {
    37  				continue Loop
    38  			}
    39  			if _, ok := v.Args[0].(*ast.FuncLit); !ok {
    40  				continue
    41  			}
    42  			if s, ok := v.Fun.(*ast.SelectorExpr); !ok || s.Sel.Name != "Fuzz" {
    43  				continue
    44  			}
    45  			if i > 2 { // avoid t.Fuzz itself in tests
    46  				inside = true
    47  				break Loop
    48  			}
    49  		}
    50  	}
    51  	if inside {
    52  		for i := 0; i < mset.Len(); i++ {
    53  			o := mset.At(i).Obj()
    54  			if o.Name() == "Failed" || o.Name() == "Name" {
    55  				cb(candidate{
    56  					obj:         o,
    57  					score:       stdScore,
    58  					imp:         imp,
    59  					addressable: true,
    60  				})
    61  			}
    62  		}
    63  		return true
    64  	}
    65  	// if it could be t.Fuzz, look for the preceding t.Add
    66  	id, ok := c.path[0].(*ast.Ident)
    67  	if ok && strings.HasPrefix("Fuzz", id.Name) {
    68  		var add *ast.CallExpr
    69  		f := func(n ast.Node) bool {
    70  			if n == nil {
    71  				return true
    72  			}
    73  			call, ok := n.(*ast.CallExpr)
    74  			if !ok {
    75  				return true
    76  			}
    77  			s, ok := call.Fun.(*ast.SelectorExpr)
    78  			if !ok {
    79  				return true
    80  			}
    81  			if s.Sel.Name != "Add" {
    82  				return true
    83  			}
    84  			// Sel.X should be of type *testing.F
    85  			got := c.pkg.GetTypesInfo().Types[s.X]
    86  			if got.Type.String() == "*testing.F" {
    87  				add = call
    88  			}
    89  			return false // because we're done...
    90  		}
    91  		// look at the enclosing FuzzFoo functions
    92  		if len(c.path) < 2 {
    93  			return false
    94  		}
    95  		n := c.path[len(c.path)-2]
    96  		if _, ok := n.(*ast.FuncDecl); !ok {
    97  			// the path should start with ast.File, ast.FuncDecl, ...
    98  			// but it didn't, so give up
    99  			return false
   100  		}
   101  		ast.Inspect(n, f)
   102  		if add == nil {
   103  			// looks like f.Fuzz without a preceding f.Add.
   104  			// let the regular completion handle it.
   105  			return false
   106  		}
   107  
   108  		lbl := "Fuzz(func(t *testing.T"
   109  		for i, a := range add.Args {
   110  			info := c.pkg.GetTypesInfo().TypeOf(a)
   111  			if info == nil {
   112  				return false // How could this happen, but better safe than panic.
   113  			}
   114  			lbl += fmt.Sprintf(", %c %s", 'a'+i, info)
   115  		}
   116  		lbl += ")"
   117  		xx := CompletionItem{
   118  			Label:         lbl,
   119  			InsertText:    lbl,
   120  			Kind:          protocol.FunctionCompletion,
   121  			Depth:         0,
   122  			Score:         10, // pretty confident the user should see this
   123  			Documentation: "argument types from f.Add",
   124  			obj:           nil,
   125  		}
   126  		c.items = append(c.items, xx)
   127  		for i := 0; i < mset.Len(); i++ {
   128  			o := mset.At(i).Obj()
   129  			if o.Name() != "Fuzz" {
   130  				cb(candidate{
   131  					obj:         o,
   132  					score:       stdScore,
   133  					imp:         imp,
   134  					addressable: true,
   135  				})
   136  			}
   137  		}
   138  		return true // done
   139  	}
   140  	// let the standard processing take care of it instead
   141  	return false
   142  }