// 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/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", obj: nil, } 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 }