mirror of https://github.com/golang/go.git
143 lines
3.6 KiB
Go
143 lines
3.6 KiB
Go
// 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
|
|
}
|