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 }