diff options
Diffstat (limited to 'gopls/internal/lsp/source/completion/fuzz.go')
-rw-r--r-- | gopls/internal/lsp/source/completion/fuzz.go | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/gopls/internal/lsp/source/completion/fuzz.go b/gopls/internal/lsp/source/completion/fuzz.go new file mode 100644 index 000000000..08e7654c7 --- /dev/null +++ b/gopls/internal/lsp/source/completion/fuzz.go @@ -0,0 +1,142 @@ +// Copyright 2022 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 completion + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "strings" + + "golang.org/x/tools/gopls/internal/lsp/protocol" +) + +// golang/go#51089 +// *testing.F deserves special treatment as member use is constrained: +// The arguments to f.Fuzz are determined by the arguments to a previous f.Add +// Inside f.Fuzz only f.Failed and f.Name are allowed. +// PJW: are there other packages where we can deduce usage constraints? + +// if we find fuzz completions, then return true, as those are the only completions to offer +func (c *completer) fuzz(typ types.Type, mset *types.MethodSet, imp *importInfo, cb func(candidate), fset *token.FileSet) bool { + // 1. inside f.Fuzz? (only f.Failed and f.Name) + // 2. possible completing f.Fuzz? + // [Ident,SelectorExpr,Callexpr,ExprStmt,BlockiStmt,FuncDecl(Fuzz...)] + // 3. before f.Fuzz, same (for 2., offer choice when looking at an F) + + // does the path contain FuncLit as arg to f.Fuzz CallExpr? + inside := false +Loop: + for i, n := range c.path { + switch v := n.(type) { + case *ast.CallExpr: + if len(v.Args) != 1 { + continue Loop + } + if _, ok := v.Args[0].(*ast.FuncLit); !ok { + continue + } + if s, ok := v.Fun.(*ast.SelectorExpr); !ok || s.Sel.Name != "Fuzz" { + continue + } + if i > 2 { // avoid t.Fuzz itself in tests + inside = true + break Loop + } + } + } + if inside { + for i := 0; i < mset.Len(); i++ { + o := mset.At(i).Obj() + if o.Name() == "Failed" || o.Name() == "Name" { + cb(candidate{ + obj: o, + score: stdScore, + imp: imp, + addressable: true, + }) + } + } + return true + } + // if it could be t.Fuzz, look for the preceding t.Add + id, ok := c.path[0].(*ast.Ident) + if ok && strings.HasPrefix("Fuzz", id.Name) { + var add *ast.CallExpr + f := func(n ast.Node) bool { + if n == nil { + return true + } + call, ok := n.(*ast.CallExpr) + if !ok { + return true + } + s, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + if s.Sel.Name != "Add" { + return true + } + // Sel.X should be of type *testing.F + got := c.pkg.GetTypesInfo().Types[s.X] + if got.Type.String() == "*testing.F" { + add = call + } + return false // because we're done... + } + // look at the enclosing FuzzFoo functions + if len(c.path) < 2 { + return false + } + n := c.path[len(c.path)-2] + if _, ok := n.(*ast.FuncDecl); !ok { + // the path should start with ast.File, ast.FuncDecl, ... + // but it didn't, so give up + return false + } + ast.Inspect(n, f) + if add == nil { + // looks like f.Fuzz without a preceding f.Add. + // let the regular completion handle it. + return false + } + + lbl := "Fuzz(func(t *testing.T" + for i, a := range add.Args { + info := c.pkg.GetTypesInfo().TypeOf(a) + if info == nil { + return false // How could this happen, but better safe than panic. + } + lbl += fmt.Sprintf(", %c %s", 'a'+i, info) + } + lbl += ")" + xx := CompletionItem{ + Label: lbl, + InsertText: lbl, + Kind: protocol.FunctionCompletion, + Depth: 0, + Score: 10, // pretty confident the user should see this + Documentation: "argument types from f.Add", + isSlice: false, + } + c.items = append(c.items, xx) + for i := 0; i < mset.Len(); i++ { + o := mset.At(i).Obj() + if o.Name() != "Fuzz" { + cb(candidate{ + obj: o, + score: stdScore, + imp: imp, + addressable: true, + }) + } + } + return true // done + } + // let the standard processing take care of it instead + return false +} |