зеркало из https://github.com/golang/tools.git
cmd/guru: delete it
RIP guru, Go's LSP server before LSP was invented. Fixes golang/go#65880 Change-Id: I75adef368e6e940c3b6dfc0ca06ad38ba0cf3656 Reviewed-on: https://go-review.googlesource.com/c/tools/+/569882 Reviewed-by: Robert Findley <rfindley@google.com> Auto-Submit: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Родитель
c6563ca60f
Коммит
1f580da078
|
@ -1,11 +0,0 @@
|
|||
-*- text -*-
|
||||
|
||||
Guru to-do list
|
||||
===========================
|
||||
|
||||
Generics:
|
||||
- decide on whether to support generics in guru
|
||||
- decide on whether to instantiate generics in ssa (go.dev/issue/52503)
|
||||
|
||||
MISC:
|
||||
- test support for *ssa.SliceToArrayPointer instructions (go.dev/issue/47326)
|
|
@ -1,205 +0,0 @@
|
|||
// Copyright 2013 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
pathpkg "path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/tools/cmd/guru/serial"
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
)
|
||||
|
||||
// definition reports the location of the definition of an identifier.
|
||||
func definition(q *Query) error {
|
||||
// First try the simple resolution done by parser.
|
||||
// It only works for intra-file references but it is very fast.
|
||||
// (Extending this approach to all the files of the package,
|
||||
// resolved using ast.NewPackage, was not worth the effort.)
|
||||
{
|
||||
qpos, err := fastQueryPos(q.Build, q.Pos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, _ := qpos.path[0].(*ast.Ident)
|
||||
if id == nil {
|
||||
return fmt.Errorf("no identifier here")
|
||||
}
|
||||
|
||||
// Did the parser resolve it to a local object?
|
||||
if obj := id.Obj; obj != nil && obj.Pos().IsValid() {
|
||||
q.Output(qpos.fset, &definitionResult{
|
||||
pos: obj.Pos(),
|
||||
descr: fmt.Sprintf("%s %s", obj.Kind, obj.Name),
|
||||
})
|
||||
return nil // success
|
||||
}
|
||||
|
||||
// Qualified identifier?
|
||||
if pkg := packageForQualIdent(qpos.path, id); pkg != "" {
|
||||
srcdir := filepath.Dir(qpos.fset.File(qpos.start).Name())
|
||||
tok, pos, err := findPackageMember(q.Build, qpos.fset, srcdir, pkg, id.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Output(qpos.fset, &definitionResult{
|
||||
pos: pos,
|
||||
descr: fmt.Sprintf("%s %s.%s", tok, pkg, id.Name),
|
||||
})
|
||||
return nil // success
|
||||
}
|
||||
|
||||
// Fall back on the type checker.
|
||||
}
|
||||
|
||||
// Run the type checker.
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
allowErrors(&lconf)
|
||||
|
||||
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, _ := qpos.path[0].(*ast.Ident)
|
||||
if id == nil {
|
||||
return fmt.Errorf("no identifier here")
|
||||
}
|
||||
|
||||
// Look up the declaration of this identifier.
|
||||
// If id is an anonymous field declaration,
|
||||
// it is both a use of a type and a def of a field;
|
||||
// prefer the use in that case.
|
||||
obj := qpos.info.Uses[id]
|
||||
if obj == nil {
|
||||
obj = qpos.info.Defs[id]
|
||||
if obj == nil {
|
||||
// Happens for y in "switch y := x.(type)",
|
||||
// and the package declaration,
|
||||
// but I think that's all.
|
||||
return fmt.Errorf("no object for identifier")
|
||||
}
|
||||
}
|
||||
|
||||
if !obj.Pos().IsValid() {
|
||||
return fmt.Errorf("%s is built in", obj.Name())
|
||||
}
|
||||
|
||||
q.Output(lprog.Fset, &definitionResult{
|
||||
pos: obj.Pos(),
|
||||
descr: qpos.objectString(obj),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// packageForQualIdent returns the package p if id is X in a qualified
|
||||
// identifier p.X; it returns "" otherwise.
|
||||
//
|
||||
// Precondition: id is path[0], and the parser did not resolve id to a
|
||||
// local object. For speed, packageForQualIdent assumes that p is a
|
||||
// package iff it is the basename of an import path (and not, say, a
|
||||
// package-level decl in another file or a predeclared identifier).
|
||||
func packageForQualIdent(path []ast.Node, id *ast.Ident) string {
|
||||
if sel, ok := path[1].(*ast.SelectorExpr); ok && sel.Sel == id && ast.IsExported(id.Name) {
|
||||
if pkgid, ok := sel.X.(*ast.Ident); ok && pkgid.Obj == nil {
|
||||
f := path[len(path)-1].(*ast.File)
|
||||
for _, imp := range f.Imports {
|
||||
path, _ := strconv.Unquote(imp.Path.Value)
|
||||
if imp.Name != nil {
|
||||
if imp.Name.Name == pkgid.Name {
|
||||
return path // renaming import
|
||||
}
|
||||
} else if pathpkg.Base(path) == pkgid.Name {
|
||||
return path // ordinary import
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// findPackageMember returns the type and position of the declaration of
|
||||
// pkg.member by loading and parsing the files of that package.
|
||||
// srcdir is the directory in which the import appears.
|
||||
func findPackageMember(ctxt *build.Context, fset *token.FileSet, srcdir, pkg, member string) (token.Token, token.Pos, error) {
|
||||
bp, err := ctxt.Import(pkg, srcdir, 0)
|
||||
if err != nil {
|
||||
return 0, token.NoPos, err // no files for package
|
||||
}
|
||||
|
||||
// TODO(adonovan): opt: parallelize.
|
||||
for _, fname := range bp.GoFiles {
|
||||
filename := filepath.Join(bp.Dir, fname)
|
||||
|
||||
// Parse the file, opening it the file via the build.Context
|
||||
// so that we observe the effects of the -modified flag.
|
||||
f, _ := buildutil.ParseFile(fset, ctxt, nil, ".", filename, parser.Mode(0))
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find a package-level decl called 'member'.
|
||||
for _, decl := range f.Decls {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.GenDecl:
|
||||
for _, spec := range decl.Specs {
|
||||
switch spec := spec.(type) {
|
||||
case *ast.ValueSpec:
|
||||
// const or var
|
||||
for _, id := range spec.Names {
|
||||
if id.Name == member {
|
||||
return decl.Tok, id.Pos(), nil
|
||||
}
|
||||
}
|
||||
case *ast.TypeSpec:
|
||||
if spec.Name.Name == member {
|
||||
return token.TYPE, spec.Name.Pos(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
if decl.Recv == nil && decl.Name.Name == member {
|
||||
return token.FUNC, decl.Name.Pos(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, token.NoPos, fmt.Errorf("couldn't find declaration of %s in %q", member, pkg)
|
||||
}
|
||||
|
||||
type definitionResult struct {
|
||||
pos token.Pos // (nonzero) location of definition
|
||||
descr string // description of object it denotes
|
||||
}
|
||||
|
||||
func (r *definitionResult) PrintPlain(printf printfFunc) {
|
||||
printf(r.pos, "defined here as %s", r.descr)
|
||||
}
|
||||
|
||||
func (r *definitionResult) JSON(fset *token.FileSet) []byte {
|
||||
return toJSON(&serial.Definition{
|
||||
Desc: r.descr,
|
||||
ObjPos: fset.Position(r.pos).String(),
|
||||
})
|
||||
}
|
|
@ -1,960 +0,0 @@
|
|||
// Copyright 2013 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/tools/cmd/guru/serial"
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
"golang.org/x/tools/internal/aliases"
|
||||
"golang.org/x/tools/internal/typesinternal"
|
||||
)
|
||||
|
||||
// describe describes the syntax node denoted by the query position,
|
||||
// including:
|
||||
// - its syntactic category
|
||||
// - the definition of its referent (for identifiers) [now redundant]
|
||||
// - its type, fields, and methods (for an expression or type expression)
|
||||
func describe(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
allowErrors(&lconf)
|
||||
|
||||
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, true) // (need exact pos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if false { // debugging
|
||||
fprintf(os.Stderr, lprog.Fset, qpos.path[0], "you selected: %s %s",
|
||||
astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path))
|
||||
}
|
||||
|
||||
var qr QueryResult
|
||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
||||
switch action {
|
||||
case actionExpr:
|
||||
qr, err = describeValue(qpos, path)
|
||||
|
||||
case actionType:
|
||||
qr, err = describeType(qpos, path)
|
||||
|
||||
case actionPackage:
|
||||
qr, err = describePackage(qpos, path)
|
||||
|
||||
case actionStmt:
|
||||
qr, err = describeStmt(qpos, path)
|
||||
|
||||
case actionUnknown:
|
||||
qr = &describeUnknownResult{path[0]}
|
||||
|
||||
default:
|
||||
panic(action) // unreachable
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Output(lprog.Fset, qr)
|
||||
return nil
|
||||
}
|
||||
|
||||
type describeUnknownResult struct {
|
||||
node ast.Node
|
||||
}
|
||||
|
||||
func (r *describeUnknownResult) PrintPlain(printf printfFunc) {
|
||||
// Nothing much to say about misc syntax.
|
||||
printf(r.node, "%s", astutil.NodeDescription(r.node))
|
||||
}
|
||||
|
||||
func (r *describeUnknownResult) JSON(fset *token.FileSet) []byte {
|
||||
return toJSON(&serial.Describe{
|
||||
Desc: astutil.NodeDescription(r.node),
|
||||
Pos: fset.Position(r.node.Pos()).String(),
|
||||
})
|
||||
}
|
||||
|
||||
type action int
|
||||
|
||||
const (
|
||||
actionUnknown action = iota // None of the below
|
||||
actionExpr // FuncDecl, true Expr or Ident(types.{Const,Var})
|
||||
actionType // type Expr or Ident(types.TypeName).
|
||||
actionStmt // Stmt or Ident(types.Label)
|
||||
actionPackage // Ident(types.Package) or ImportSpec
|
||||
)
|
||||
|
||||
// findInterestingNode classifies the syntax node denoted by path as one of:
|
||||
// - an expression, part of an expression or a reference to a constant
|
||||
// or variable;
|
||||
// - a type, part of a type, or a reference to a named type;
|
||||
// - a statement, part of a statement, or a label referring to a statement;
|
||||
// - part of a package declaration or import spec.
|
||||
// - none of the above.
|
||||
//
|
||||
// and returns the most "interesting" associated node, which may be
|
||||
// the same node, an ancestor or a descendent.
|
||||
func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.Node, action) {
|
||||
// TODO(adonovan): integrate with go/types/stdlib_test.go and
|
||||
// apply this to every AST node we can find to make sure it
|
||||
// doesn't crash.
|
||||
|
||||
// TODO(adonovan): audit for ParenExpr safety, esp. since we
|
||||
// traverse up and down.
|
||||
|
||||
// TODO(adonovan): if the users selects the "." in
|
||||
// "fmt.Fprintf()", they'll get an ambiguous selection error;
|
||||
// we won't even reach here. Can we do better?
|
||||
|
||||
// TODO(adonovan): describing a field within 'type T struct {...}'
|
||||
// describes the (anonymous) struct type and concludes "no methods".
|
||||
// We should ascend to the enclosing type decl, if any.
|
||||
|
||||
for len(path) > 0 {
|
||||
switch n := path[0].(type) {
|
||||
case *ast.GenDecl:
|
||||
if len(n.Specs) == 1 {
|
||||
// Descend to sole {Import,Type,Value}Spec child.
|
||||
path = append([]ast.Node{n.Specs[0]}, path...)
|
||||
continue
|
||||
}
|
||||
return path, actionUnknown // uninteresting
|
||||
|
||||
case *ast.FuncDecl:
|
||||
// Descend to function name.
|
||||
path = append([]ast.Node{n.Name}, path...)
|
||||
continue
|
||||
|
||||
case *ast.ImportSpec:
|
||||
return path, actionPackage
|
||||
|
||||
case *ast.ValueSpec:
|
||||
if len(n.Names) == 1 {
|
||||
// Descend to sole Ident child.
|
||||
path = append([]ast.Node{n.Names[0]}, path...)
|
||||
continue
|
||||
}
|
||||
return path, actionUnknown // uninteresting
|
||||
|
||||
case *ast.TypeSpec:
|
||||
// Descend to type name.
|
||||
path = append([]ast.Node{n.Name}, path...)
|
||||
continue
|
||||
|
||||
case *ast.Comment, *ast.CommentGroup, *ast.File, *ast.KeyValueExpr, *ast.CommClause:
|
||||
return path, actionUnknown // uninteresting
|
||||
|
||||
case ast.Stmt:
|
||||
return path, actionStmt
|
||||
|
||||
case *ast.ArrayType,
|
||||
*ast.StructType,
|
||||
*ast.FuncType,
|
||||
*ast.InterfaceType,
|
||||
*ast.MapType,
|
||||
*ast.ChanType:
|
||||
return path, actionType
|
||||
|
||||
case *ast.Ellipsis:
|
||||
// Continue to enclosing node.
|
||||
// e.g. [...]T in ArrayType
|
||||
// f(x...) in CallExpr
|
||||
// f(x...T) in FuncType
|
||||
|
||||
case *ast.Field:
|
||||
// TODO(adonovan): this needs more thought,
|
||||
// since fields can be so many things.
|
||||
if len(n.Names) == 1 {
|
||||
// Descend to sole Ident child.
|
||||
path = append([]ast.Node{n.Names[0]}, path...)
|
||||
continue
|
||||
}
|
||||
// Zero names (e.g. anon field in struct)
|
||||
// or multiple field or param names:
|
||||
// continue to enclosing field list.
|
||||
|
||||
case *ast.FieldList:
|
||||
// Continue to enclosing node:
|
||||
// {Struct,Func,Interface}Type or FuncDecl.
|
||||
|
||||
case *ast.BasicLit:
|
||||
if _, ok := path[1].(*ast.ImportSpec); ok {
|
||||
return path[1:], actionPackage
|
||||
}
|
||||
return path, actionExpr
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
// TODO(adonovan): use Selections info directly.
|
||||
if pkginfo.Uses[n.Sel] == nil {
|
||||
// TODO(adonovan): is this reachable?
|
||||
return path, actionUnknown
|
||||
}
|
||||
// Descend to .Sel child.
|
||||
path = append([]ast.Node{n.Sel}, path...)
|
||||
continue
|
||||
|
||||
case *ast.Ident:
|
||||
switch pkginfo.ObjectOf(n).(type) {
|
||||
case *types.PkgName:
|
||||
return path, actionPackage
|
||||
|
||||
case *types.Const:
|
||||
return path, actionExpr
|
||||
|
||||
case *types.Label:
|
||||
return path, actionStmt
|
||||
|
||||
case *types.TypeName:
|
||||
return path, actionType
|
||||
|
||||
case *types.Var:
|
||||
// For x in 'struct {x T}', return struct type, for now.
|
||||
if _, ok := path[1].(*ast.Field); ok {
|
||||
_ = path[2].(*ast.FieldList) // assertion
|
||||
if _, ok := path[3].(*ast.StructType); ok {
|
||||
return path[3:], actionType
|
||||
}
|
||||
}
|
||||
return path, actionExpr
|
||||
|
||||
case *types.Func:
|
||||
return path, actionExpr
|
||||
|
||||
case *types.Builtin:
|
||||
// For reference to built-in function, return enclosing call.
|
||||
path = path[1:] // ascend to enclosing function call
|
||||
continue
|
||||
|
||||
case *types.Nil:
|
||||
return path, actionExpr
|
||||
}
|
||||
|
||||
// No object.
|
||||
switch path[1].(type) {
|
||||
case *ast.SelectorExpr:
|
||||
// Return enclosing selector expression.
|
||||
return path[1:], actionExpr
|
||||
|
||||
case *ast.Field:
|
||||
// TODO(adonovan): test this.
|
||||
// e.g. all f in:
|
||||
// struct { f, g int }
|
||||
// interface { f() }
|
||||
// func (f T) method(f, g int) (f, g bool)
|
||||
//
|
||||
// switch path[3].(type) {
|
||||
// case *ast.FuncDecl:
|
||||
// case *ast.StructType:
|
||||
// case *ast.InterfaceType:
|
||||
// }
|
||||
//
|
||||
// return path[1:], actionExpr
|
||||
//
|
||||
// Unclear what to do with these.
|
||||
// Struct.Fields -- field
|
||||
// Interface.Methods -- field
|
||||
// FuncType.{Params.Results} -- actionExpr
|
||||
// FuncDecl.Recv -- actionExpr
|
||||
|
||||
case *ast.File:
|
||||
// 'package foo'
|
||||
return path, actionPackage
|
||||
|
||||
case *ast.ImportSpec:
|
||||
return path[1:], actionPackage
|
||||
|
||||
default:
|
||||
// e.g. blank identifier
|
||||
// or y in "switch y := x.(type)"
|
||||
// or code in a _test.go file that's not part of the package.
|
||||
return path, actionUnknown
|
||||
}
|
||||
|
||||
case *ast.StarExpr:
|
||||
if pkginfo.Types[n].IsType() {
|
||||
return path, actionType
|
||||
}
|
||||
return path, actionExpr
|
||||
|
||||
case ast.Expr:
|
||||
// All Expr but {BasicLit,Ident,StarExpr} are
|
||||
// "true" expressions that evaluate to a value.
|
||||
return path, actionExpr
|
||||
}
|
||||
|
||||
// Ascend to parent.
|
||||
path = path[1:]
|
||||
}
|
||||
|
||||
return nil, actionUnknown // unreachable
|
||||
}
|
||||
|
||||
func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error) {
|
||||
var expr ast.Expr
|
||||
var obj types.Object
|
||||
switch n := path[0].(type) {
|
||||
case *ast.ValueSpec:
|
||||
// ambiguous ValueSpec containing multiple names
|
||||
return nil, fmt.Errorf("multiple value specification")
|
||||
case *ast.Ident:
|
||||
obj = qpos.info.ObjectOf(n)
|
||||
expr = n
|
||||
case ast.Expr:
|
||||
expr = n
|
||||
default:
|
||||
// TODO(adonovan): is this reachable?
|
||||
return nil, fmt.Errorf("unexpected AST for expr: %T", n)
|
||||
}
|
||||
|
||||
typ := qpos.info.TypeOf(expr)
|
||||
if typ == nil {
|
||||
typ = types.Typ[types.Invalid]
|
||||
}
|
||||
constVal := qpos.info.Types[expr].Value
|
||||
if c, ok := obj.(*types.Const); ok {
|
||||
constVal = c.Val()
|
||||
}
|
||||
|
||||
return &describeValueResult{
|
||||
qpos: qpos,
|
||||
expr: expr,
|
||||
typ: typ,
|
||||
names: appendNames(nil, typ),
|
||||
constVal: constVal,
|
||||
obj: obj,
|
||||
methods: accessibleMethods(typ, qpos.info.Pkg),
|
||||
fields: accessibleFields(typ, qpos.info.Pkg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// appendNames returns named types found within the Type by
|
||||
// removing map, pointer, channel, slice, and array constructors.
|
||||
// It does not descend into structs or interfaces.
|
||||
func appendNames(names []*types.Named, typ types.Type) []*types.Named {
|
||||
// elemType specifies type that has some element in it
|
||||
// such as array, slice, chan, pointer
|
||||
type elemType interface {
|
||||
Elem() types.Type
|
||||
}
|
||||
|
||||
switch t := aliases.Unalias(typ).(type) {
|
||||
case *types.Named:
|
||||
names = append(names, t)
|
||||
case *types.Map:
|
||||
names = appendNames(names, t.Key())
|
||||
names = appendNames(names, t.Elem())
|
||||
case elemType:
|
||||
names = appendNames(names, t.Elem())
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
type describeValueResult struct {
|
||||
qpos *queryPos
|
||||
expr ast.Expr // query node
|
||||
typ types.Type // type of expression
|
||||
names []*types.Named // named types within typ
|
||||
constVal constant.Value // value of expression, if constant
|
||||
obj types.Object // var/func/const object, if expr was Ident
|
||||
methods []*types.Selection
|
||||
fields []describeField
|
||||
}
|
||||
|
||||
func (r *describeValueResult) PrintPlain(printf printfFunc) {
|
||||
var prefix, suffix string
|
||||
if r.constVal != nil {
|
||||
suffix = fmt.Sprintf(" of value %s", r.constVal)
|
||||
}
|
||||
switch obj := r.obj.(type) {
|
||||
case *types.Func:
|
||||
if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
|
||||
if _, ok := recv.Type().Underlying().(*types.Interface); ok {
|
||||
prefix = "interface method "
|
||||
} else {
|
||||
prefix = "method "
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Describe the expression.
|
||||
if r.obj != nil {
|
||||
if r.obj.Pos() == r.expr.Pos() {
|
||||
// defining ident
|
||||
printf(r.expr, "definition of %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
|
||||
} else {
|
||||
// referring ident
|
||||
printf(r.expr, "reference to %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
|
||||
if def := r.obj.Pos(); def != token.NoPos {
|
||||
printf(def, "defined here")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
desc := astutil.NodeDescription(r.expr)
|
||||
if suffix != "" {
|
||||
// constant expression
|
||||
printf(r.expr, "%s%s", desc, suffix)
|
||||
} else {
|
||||
// non-constant expression
|
||||
printf(r.expr, "%s of type %s", desc, r.qpos.typeString(r.typ))
|
||||
}
|
||||
}
|
||||
|
||||
printMethods(printf, r.expr, r.methods)
|
||||
printFields(printf, r.expr, r.fields)
|
||||
printNamedTypes(printf, r.expr, r.names)
|
||||
}
|
||||
|
||||
func (r *describeValueResult) JSON(fset *token.FileSet) []byte {
|
||||
var value, objpos string
|
||||
if r.constVal != nil {
|
||||
value = r.constVal.String()
|
||||
}
|
||||
if r.obj != nil {
|
||||
objpos = fset.Position(r.obj.Pos()).String()
|
||||
}
|
||||
|
||||
typesPos := make([]serial.Definition, len(r.names))
|
||||
for i, t := range r.names {
|
||||
typesPos[i] = serial.Definition{
|
||||
ObjPos: fset.Position(t.Obj().Pos()).String(),
|
||||
Desc: r.qpos.typeString(t),
|
||||
}
|
||||
}
|
||||
|
||||
return toJSON(&serial.Describe{
|
||||
Desc: astutil.NodeDescription(r.expr),
|
||||
Pos: fset.Position(r.expr.Pos()).String(),
|
||||
Detail: "value",
|
||||
Value: &serial.DescribeValue{
|
||||
Type: r.qpos.typeString(r.typ),
|
||||
TypesPos: typesPos,
|
||||
Value: value,
|
||||
ObjPos: objpos,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ---- TYPE ------------------------------------------------------------
|
||||
|
||||
func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) {
|
||||
var description string
|
||||
var typ types.Type
|
||||
switch n := path[0].(type) {
|
||||
case *ast.Ident:
|
||||
obj := qpos.info.ObjectOf(n).(*types.TypeName)
|
||||
typ = obj.Type()
|
||||
if isAlias(obj) {
|
||||
description = "alias of "
|
||||
} else if obj.Pos() == n.Pos() {
|
||||
description = "definition of " // (Named type)
|
||||
} else if _, ok := aliases.Unalias(typ).(*types.Basic); ok {
|
||||
description = "reference to built-in "
|
||||
} else {
|
||||
description = "reference to " // (Named type)
|
||||
}
|
||||
|
||||
case ast.Expr:
|
||||
typ = qpos.info.TypeOf(n)
|
||||
|
||||
default:
|
||||
// Unreachable?
|
||||
return nil, fmt.Errorf("unexpected AST for type: %T", n)
|
||||
}
|
||||
|
||||
description = description + "type " + qpos.typeString(typ)
|
||||
|
||||
// Show sizes for structs and named types (it's fairly obvious for others).
|
||||
switch aliases.Unalias(typ).(type) {
|
||||
case *types.Named, *types.Struct:
|
||||
szs := types.StdSizes{WordSize: 8, MaxAlign: 8} // assume amd64
|
||||
description = fmt.Sprintf("%s (size %d, align %d)", description,
|
||||
szs.Sizeof(typ), szs.Alignof(typ))
|
||||
}
|
||||
|
||||
return &describeTypeResult{
|
||||
qpos: qpos,
|
||||
node: path[0],
|
||||
description: description,
|
||||
typ: typ,
|
||||
methods: accessibleMethods(typ, qpos.info.Pkg),
|
||||
fields: accessibleFields(typ, qpos.info.Pkg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type describeTypeResult struct {
|
||||
qpos *queryPos
|
||||
node ast.Node
|
||||
description string
|
||||
typ types.Type
|
||||
methods []*types.Selection
|
||||
fields []describeField
|
||||
}
|
||||
|
||||
type describeField struct {
|
||||
implicits []*types.Named
|
||||
field *types.Var
|
||||
}
|
||||
|
||||
func printMethods(printf printfFunc, node ast.Node, methods []*types.Selection) {
|
||||
if len(methods) > 0 {
|
||||
printf(node, "Methods:")
|
||||
}
|
||||
for _, meth := range methods {
|
||||
// Print the method type relative to the package
|
||||
// in which it was defined, not the query package,
|
||||
printf(meth.Obj(), "\t%s",
|
||||
types.SelectionString(meth, types.RelativeTo(meth.Obj().Pkg())))
|
||||
}
|
||||
}
|
||||
|
||||
func printFields(printf printfFunc, node ast.Node, fields []describeField) {
|
||||
if len(fields) > 0 {
|
||||
printf(node, "Fields:")
|
||||
}
|
||||
|
||||
// Align the names and the types (requires two passes).
|
||||
var width int
|
||||
var names []string
|
||||
for _, f := range fields {
|
||||
var buf bytes.Buffer
|
||||
for _, fld := range f.implicits {
|
||||
buf.WriteString(fld.Obj().Name())
|
||||
buf.WriteByte('.')
|
||||
}
|
||||
buf.WriteString(f.field.Name())
|
||||
name := buf.String()
|
||||
if n := utf8.RuneCountInString(name); n > width {
|
||||
width = n
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
for i, f := range fields {
|
||||
// Print the field type relative to the package
|
||||
// in which it was defined, not the query package,
|
||||
printf(f.field, "\t%*s %s", -width, names[i],
|
||||
types.TypeString(f.field.Type(), types.RelativeTo(f.field.Pkg())))
|
||||
}
|
||||
}
|
||||
|
||||
func printNamedTypes(printf printfFunc, node ast.Node, names []*types.Named) {
|
||||
if len(names) > 0 {
|
||||
printf(node, "Named types:")
|
||||
}
|
||||
|
||||
for _, t := range names {
|
||||
// Print the type relative to the package
|
||||
// in which it was defined, not the query package,
|
||||
printf(t.Obj(), "\ttype %s defined here",
|
||||
types.TypeString(t.Obj().Type(), types.RelativeTo(t.Obj().Pkg())))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *describeTypeResult) PrintPlain(printf printfFunc) {
|
||||
printf(r.node, "%s", r.description)
|
||||
|
||||
// Show the underlying type for a reference to a named type.
|
||||
if nt, ok := aliases.Unalias(r.typ).(*types.Named); ok && r.node.Pos() != nt.Obj().Pos() {
|
||||
// TODO(adonovan): improve display of complex struct/interface types.
|
||||
printf(nt.Obj(), "defined as %s", r.qpos.typeString(nt.Underlying()))
|
||||
}
|
||||
|
||||
printMethods(printf, r.node, r.methods)
|
||||
if len(r.methods) == 0 {
|
||||
// Only report null result for type kinds
|
||||
// capable of bearing methods.
|
||||
switch aliases.Unalias(r.typ).(type) {
|
||||
case *types.Interface, *types.Struct, *types.Named:
|
||||
printf(r.node, "No methods.")
|
||||
}
|
||||
}
|
||||
|
||||
printFields(printf, r.node, r.fields)
|
||||
}
|
||||
|
||||
func (r *describeTypeResult) JSON(fset *token.FileSet) []byte {
|
||||
var namePos, nameDef string
|
||||
if nt, ok := aliases.Unalias(r.typ).(*types.Named); ok {
|
||||
namePos = fset.Position(nt.Obj().Pos()).String()
|
||||
nameDef = nt.Underlying().String()
|
||||
}
|
||||
return toJSON(&serial.Describe{
|
||||
Desc: r.description,
|
||||
Pos: fset.Position(r.node.Pos()).String(),
|
||||
Detail: "type",
|
||||
Type: &serial.DescribeType{
|
||||
Type: r.qpos.typeString(r.typ),
|
||||
NamePos: namePos,
|
||||
NameDef: nameDef,
|
||||
Methods: methodsToSerial(r.qpos.info.Pkg, r.methods, fset),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ---- PACKAGE ------------------------------------------------------------
|
||||
|
||||
func describePackage(qpos *queryPos, path []ast.Node) (*describePackageResult, error) {
|
||||
var description string
|
||||
var pkg *types.Package
|
||||
switch n := path[0].(type) {
|
||||
case *ast.ImportSpec:
|
||||
var obj types.Object
|
||||
if n.Name != nil {
|
||||
obj = qpos.info.Defs[n.Name]
|
||||
} else {
|
||||
obj = qpos.info.Implicits[n]
|
||||
}
|
||||
pkgname, _ := obj.(*types.PkgName)
|
||||
if pkgname == nil {
|
||||
return nil, fmt.Errorf("can't import package %s", n.Path.Value)
|
||||
}
|
||||
pkg = pkgname.Imported()
|
||||
description = fmt.Sprintf("import of package %q", pkg.Path())
|
||||
|
||||
case *ast.Ident:
|
||||
if _, isDef := path[1].(*ast.File); isDef {
|
||||
// e.g. package id
|
||||
pkg = qpos.info.Pkg
|
||||
description = fmt.Sprintf("definition of package %q", pkg.Path())
|
||||
} else {
|
||||
// e.g. import id "..."
|
||||
// or id.F()
|
||||
pkg = qpos.info.ObjectOf(n).(*types.PkgName).Imported()
|
||||
description = fmt.Sprintf("reference to package %q", pkg.Path())
|
||||
}
|
||||
|
||||
default:
|
||||
// Unreachable?
|
||||
return nil, fmt.Errorf("unexpected AST for package: %T", n)
|
||||
}
|
||||
|
||||
var members []*describeMember
|
||||
// NB: "unsafe" has no types.Package
|
||||
if pkg != nil {
|
||||
// Enumerate the accessible package members
|
||||
// in lexicographic order.
|
||||
for _, name := range pkg.Scope().Names() {
|
||||
if pkg == qpos.info.Pkg || ast.IsExported(name) {
|
||||
mem := pkg.Scope().Lookup(name)
|
||||
var methods []*types.Selection
|
||||
if mem, ok := mem.(*types.TypeName); ok {
|
||||
methods = accessibleMethods(mem.Type(), qpos.info.Pkg)
|
||||
}
|
||||
members = append(members, &describeMember{
|
||||
mem,
|
||||
methods,
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &describePackageResult{qpos.fset, path[0], description, pkg, members}, nil
|
||||
}
|
||||
|
||||
type describePackageResult struct {
|
||||
fset *token.FileSet
|
||||
node ast.Node
|
||||
description string
|
||||
pkg *types.Package
|
||||
members []*describeMember // in lexicographic name order
|
||||
}
|
||||
|
||||
type describeMember struct {
|
||||
obj types.Object
|
||||
methods []*types.Selection // in types.MethodSet order
|
||||
}
|
||||
|
||||
func (r *describePackageResult) PrintPlain(printf printfFunc) {
|
||||
printf(r.node, "%s", r.description)
|
||||
|
||||
// Compute max width of name "column".
|
||||
maxname := 0
|
||||
for _, mem := range r.members {
|
||||
if l := len(mem.obj.Name()); l > maxname {
|
||||
maxname = l
|
||||
}
|
||||
}
|
||||
|
||||
for _, mem := range r.members {
|
||||
printf(mem.obj, "\t%s", formatMember(mem.obj, maxname))
|
||||
for _, meth := range mem.methods {
|
||||
printf(meth.Obj(), "\t\t%s", types.SelectionString(meth, types.RelativeTo(r.pkg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func formatMember(obj types.Object, maxname int) string {
|
||||
qualifier := types.RelativeTo(obj.Pkg())
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%-5s %-*s", tokenOf(obj), maxname, obj.Name())
|
||||
switch obj := obj.(type) {
|
||||
case *types.Const:
|
||||
fmt.Fprintf(&buf, " %s = %s", types.TypeString(obj.Type(), qualifier), obj.Val())
|
||||
|
||||
case *types.Func:
|
||||
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
|
||||
|
||||
case *types.TypeName:
|
||||
typ := obj.Type()
|
||||
if isAlias(obj) {
|
||||
buf.WriteString(" = ")
|
||||
} else {
|
||||
buf.WriteByte(' ')
|
||||
typ = typ.Underlying()
|
||||
}
|
||||
var typestr string
|
||||
// Abbreviate long aggregate type names.
|
||||
switch typ := aliases.Unalias(typ).(type) {
|
||||
case *types.Interface:
|
||||
if typ.NumMethods() > 1 {
|
||||
typestr = "interface{...}"
|
||||
}
|
||||
case *types.Struct:
|
||||
if typ.NumFields() > 1 {
|
||||
typestr = "struct{...}"
|
||||
}
|
||||
}
|
||||
if typestr == "" {
|
||||
// The fix for #44515 changed the printing of unsafe.Pointer
|
||||
// such that it uses a qualifier if one is provided. Using
|
||||
// the types.RelativeTo qualifier provided here, the output
|
||||
// is just "Pointer" rather than "unsafe.Pointer". This is
|
||||
// consistent with the printing of non-type objects but it
|
||||
// breaks an existing test which needs to work with older
|
||||
// versions of Go. Re-establish the original output by not
|
||||
// using a qualifier at all if we're printing a type from
|
||||
// package unsafe - there's only unsafe.Pointer (#44596).
|
||||
// NOTE: This correction can be removed (and the test's
|
||||
// golden file adjusted) once we only run against go1.17
|
||||
// or bigger.
|
||||
qualifier := qualifier
|
||||
if obj.Pkg() == types.Unsafe {
|
||||
qualifier = nil
|
||||
}
|
||||
typestr = types.TypeString(typ, qualifier)
|
||||
}
|
||||
buf.WriteString(typestr)
|
||||
|
||||
case *types.Var:
|
||||
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (r *describePackageResult) JSON(fset *token.FileSet) []byte {
|
||||
var members []*serial.DescribeMember
|
||||
for _, mem := range r.members {
|
||||
obj := mem.obj
|
||||
typ := obj.Type()
|
||||
var val string
|
||||
var alias string
|
||||
switch obj := obj.(type) {
|
||||
case *types.Const:
|
||||
val = obj.Val().String()
|
||||
case *types.TypeName:
|
||||
if isAlias(obj) {
|
||||
alias = "= " // kludgy
|
||||
} else {
|
||||
typ = typ.Underlying()
|
||||
}
|
||||
}
|
||||
members = append(members, &serial.DescribeMember{
|
||||
Name: obj.Name(),
|
||||
Type: alias + typ.String(),
|
||||
Value: val,
|
||||
Pos: fset.Position(obj.Pos()).String(),
|
||||
Kind: tokenOf(obj),
|
||||
Methods: methodsToSerial(r.pkg, mem.methods, fset),
|
||||
})
|
||||
}
|
||||
return toJSON(&serial.Describe{
|
||||
Desc: r.description,
|
||||
Pos: fset.Position(r.node.Pos()).String(),
|
||||
Detail: "package",
|
||||
Package: &serial.DescribePackage{
|
||||
Path: r.pkg.Path(),
|
||||
Members: members,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func tokenOf(o types.Object) string {
|
||||
switch o.(type) {
|
||||
case *types.Func:
|
||||
return "func"
|
||||
case *types.Var:
|
||||
return "var"
|
||||
case *types.TypeName:
|
||||
return "type"
|
||||
case *types.Const:
|
||||
return "const"
|
||||
case *types.PkgName:
|
||||
return "package"
|
||||
case *types.Builtin:
|
||||
return "builtin" // e.g. when describing package "unsafe"
|
||||
case *types.Nil:
|
||||
return "nil"
|
||||
case *types.Label:
|
||||
return "label"
|
||||
}
|
||||
panic(o)
|
||||
}
|
||||
|
||||
// ---- STATEMENT ------------------------------------------------------------
|
||||
|
||||
func describeStmt(qpos *queryPos, path []ast.Node) (*describeStmtResult, error) {
|
||||
var description string
|
||||
switch n := path[0].(type) {
|
||||
case *ast.Ident:
|
||||
if qpos.info.Defs[n] != nil {
|
||||
description = "labelled statement"
|
||||
} else {
|
||||
description = "reference to labelled statement"
|
||||
}
|
||||
|
||||
default:
|
||||
// Nothing much to say about statements.
|
||||
description = astutil.NodeDescription(n)
|
||||
}
|
||||
return &describeStmtResult{qpos.fset, path[0], description}, nil
|
||||
}
|
||||
|
||||
type describeStmtResult struct {
|
||||
fset *token.FileSet
|
||||
node ast.Node
|
||||
description string
|
||||
}
|
||||
|
||||
func (r *describeStmtResult) PrintPlain(printf printfFunc) {
|
||||
printf(r.node, "%s", r.description)
|
||||
}
|
||||
|
||||
func (r *describeStmtResult) JSON(fset *token.FileSet) []byte {
|
||||
return toJSON(&serial.Describe{
|
||||
Desc: r.description,
|
||||
Pos: fset.Position(r.node.Pos()).String(),
|
||||
Detail: "unknown",
|
||||
})
|
||||
}
|
||||
|
||||
// ------------------- Utilities -------------------
|
||||
|
||||
// pathToString returns a string containing the concrete types of the
|
||||
// nodes in path.
|
||||
func pathToString(path []ast.Node) string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, "[")
|
||||
for i, n := range path {
|
||||
if i > 0 {
|
||||
fmt.Fprint(&buf, " ")
|
||||
}
|
||||
fmt.Fprint(&buf, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast."))
|
||||
}
|
||||
fmt.Fprint(&buf, "]")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func accessibleMethods(t types.Type, from *types.Package) []*types.Selection {
|
||||
var methods []*types.Selection
|
||||
for _, meth := range typeutil.IntuitiveMethodSet(t, nil) {
|
||||
if isAccessibleFrom(meth.Obj(), from) {
|
||||
methods = append(methods, meth)
|
||||
}
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
// accessibleFields returns the set of accessible
|
||||
// field selections on a value of type recv.
|
||||
func accessibleFields(recv types.Type, from *types.Package) []describeField {
|
||||
wantField := func(f *types.Var) bool {
|
||||
if !isAccessibleFrom(f, from) {
|
||||
return false
|
||||
}
|
||||
// Check that the field is not shadowed.
|
||||
obj, _, _ := types.LookupFieldOrMethod(recv, true, f.Pkg(), f.Name())
|
||||
return obj == f
|
||||
}
|
||||
|
||||
var fields []describeField
|
||||
var visit func(t types.Type, stack []*types.Named)
|
||||
visit = func(t types.Type, stack []*types.Named) {
|
||||
tStruct, ok := deref(t).Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
fieldloop:
|
||||
for i := 0; i < tStruct.NumFields(); i++ {
|
||||
f := tStruct.Field(i)
|
||||
|
||||
// Handle recursion through anonymous fields.
|
||||
if f.Anonymous() {
|
||||
if _, named := typesinternal.ReceiverNamed(f); named != nil {
|
||||
// If we've already visited this named type
|
||||
// on this path, break the cycle.
|
||||
for _, x := range stack {
|
||||
if x == named {
|
||||
continue fieldloop
|
||||
}
|
||||
}
|
||||
visit(f.Type(), append(stack, named))
|
||||
}
|
||||
}
|
||||
|
||||
// Save accessible fields.
|
||||
if wantField(f) {
|
||||
fields = append(fields, describeField{
|
||||
implicits: append([]*types.Named(nil), stack...),
|
||||
field: f,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
visit(recv, nil)
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
func isAccessibleFrom(obj types.Object, pkg *types.Package) bool {
|
||||
return ast.IsExported(obj.Name()) || obj.Pkg() == pkg
|
||||
}
|
||||
|
||||
func methodsToSerial(this *types.Package, methods []*types.Selection, fset *token.FileSet) []serial.DescribeMethod {
|
||||
qualifier := types.RelativeTo(this)
|
||||
var jmethods []serial.DescribeMethod
|
||||
for _, meth := range methods {
|
||||
var ser serial.DescribeMethod
|
||||
if meth != nil { // may contain nils when called by implements (on a method)
|
||||
ser = serial.DescribeMethod{
|
||||
Name: types.SelectionString(meth, qualifier),
|
||||
Pos: fset.Position(meth.Obj().Pos()).String(),
|
||||
}
|
||||
}
|
||||
jmethods = append(jmethods, ser)
|
||||
}
|
||||
return jmethods
|
||||
}
|
|
@ -1,222 +0,0 @@
|
|||
// Copyright 2013 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/cmd/guru/serial"
|
||||
"golang.org/x/tools/go/loader"
|
||||
)
|
||||
|
||||
// freevars displays the lexical (not package-level) free variables of
|
||||
// the selection.
|
||||
//
|
||||
// It treats A.B.C as a separate variable from A to reveal the parts
|
||||
// of an aggregate type that are actually needed.
|
||||
// This aids refactoring.
|
||||
//
|
||||
// TODO(adonovan): optionally display the free references to
|
||||
// file/package scope objects, and to objects from other packages.
|
||||
// Depending on where the resulting function abstraction will go,
|
||||
// these might be interesting. Perhaps group the results into three
|
||||
// bands.
|
||||
func freevars(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
allowErrors(&lconf)
|
||||
|
||||
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file := qpos.path[len(qpos.path)-1] // the enclosing file
|
||||
fileScope := qpos.info.Scopes[file]
|
||||
pkgScope := fileScope.Parent()
|
||||
|
||||
// The id and sel functions return non-nil if they denote an
|
||||
// object o or selection o.x.y that is referenced by the
|
||||
// selection but defined neither within the selection nor at
|
||||
// file scope, i.e. it is in the lexical environment.
|
||||
var id func(n *ast.Ident) types.Object
|
||||
var sel func(n *ast.SelectorExpr) types.Object
|
||||
|
||||
sel = func(n *ast.SelectorExpr) types.Object {
|
||||
switch x := unparen(n.X).(type) {
|
||||
case *ast.SelectorExpr:
|
||||
return sel(x)
|
||||
case *ast.Ident:
|
||||
return id(x)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
id = func(n *ast.Ident) types.Object {
|
||||
obj := qpos.info.Uses[n]
|
||||
if obj == nil {
|
||||
return nil // not a reference
|
||||
}
|
||||
if _, ok := obj.(*types.PkgName); ok {
|
||||
return nil // imported package
|
||||
}
|
||||
if !(file.Pos() <= obj.Pos() && obj.Pos() <= file.End()) {
|
||||
return nil // not defined in this file
|
||||
}
|
||||
scope := obj.Parent()
|
||||
if scope == nil {
|
||||
return nil // e.g. interface method, struct field
|
||||
}
|
||||
if scope == fileScope || scope == pkgScope {
|
||||
return nil // defined at file or package scope
|
||||
}
|
||||
if qpos.start <= obj.Pos() && obj.Pos() <= qpos.end {
|
||||
return nil // defined within selection => not free
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
// Maps each reference that is free in the selection
|
||||
// to the object it refers to.
|
||||
// The map de-duplicates repeated references.
|
||||
refsMap := make(map[string]freevarsRef)
|
||||
|
||||
// Visit all the identifiers in the selected ASTs.
|
||||
ast.Inspect(qpos.path[0], func(n ast.Node) bool {
|
||||
if n == nil {
|
||||
return true // popping DFS stack
|
||||
}
|
||||
|
||||
// Is this node contained within the selection?
|
||||
// (freevars permits inexact selections,
|
||||
// like two stmts in a block.)
|
||||
if qpos.start <= n.Pos() && n.End() <= qpos.end {
|
||||
var obj types.Object
|
||||
var prune bool
|
||||
switch n := n.(type) {
|
||||
case *ast.Ident:
|
||||
obj = id(n)
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
obj = sel(n)
|
||||
prune = true
|
||||
}
|
||||
|
||||
if obj != nil {
|
||||
var kind string
|
||||
switch obj.(type) {
|
||||
case *types.Var:
|
||||
kind = "var"
|
||||
case *types.Func:
|
||||
kind = "func"
|
||||
case *types.TypeName:
|
||||
kind = "type"
|
||||
case *types.Const:
|
||||
kind = "const"
|
||||
case *types.Label:
|
||||
kind = "label"
|
||||
default:
|
||||
panic(obj)
|
||||
}
|
||||
|
||||
typ := qpos.info.TypeOf(n.(ast.Expr))
|
||||
ref := freevarsRef{kind, printNode(lprog.Fset, n), typ, obj}
|
||||
refsMap[ref.ref] = ref
|
||||
|
||||
if prune {
|
||||
return false // don't descend
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true // descend
|
||||
})
|
||||
|
||||
refs := make([]freevarsRef, 0, len(refsMap))
|
||||
for _, ref := range refsMap {
|
||||
refs = append(refs, ref)
|
||||
}
|
||||
sort.Sort(byRef(refs))
|
||||
|
||||
q.Output(lprog.Fset, &freevarsResult{
|
||||
qpos: qpos,
|
||||
refs: refs,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
type freevarsResult struct {
|
||||
qpos *queryPos
|
||||
refs []freevarsRef
|
||||
}
|
||||
|
||||
type freevarsRef struct {
|
||||
kind string
|
||||
ref string
|
||||
typ types.Type
|
||||
obj types.Object
|
||||
}
|
||||
|
||||
func (r *freevarsResult) PrintPlain(printf printfFunc) {
|
||||
if len(r.refs) == 0 {
|
||||
printf(r.qpos, "No free identifiers.")
|
||||
} else {
|
||||
printf(r.qpos, "Free identifiers:")
|
||||
qualifier := types.RelativeTo(r.qpos.info.Pkg)
|
||||
for _, ref := range r.refs {
|
||||
// Avoid printing "type T T".
|
||||
var typstr string
|
||||
if ref.kind != "type" && ref.kind != "label" {
|
||||
typstr = " " + types.TypeString(ref.typ, qualifier)
|
||||
}
|
||||
printf(ref.obj, "%s %s%s", ref.kind, ref.ref, typstr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *freevarsResult) JSON(fset *token.FileSet) []byte {
|
||||
var buf bytes.Buffer
|
||||
for i, ref := range r.refs {
|
||||
if i > 0 {
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
buf.Write(toJSON(serial.FreeVar{
|
||||
Pos: fset.Position(ref.obj.Pos()).String(),
|
||||
Kind: ref.kind,
|
||||
Ref: ref.ref,
|
||||
Type: ref.typ.String(),
|
||||
}))
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// -------- utils --------
|
||||
|
||||
type byRef []freevarsRef
|
||||
|
||||
func (p byRef) Len() int { return len(p) }
|
||||
func (p byRef) Less(i, j int) bool { return p[i].ref < p[j].ref }
|
||||
func (p byRef) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
// printNode returns the pretty-printed syntax of n.
|
||||
func printNode(fset *token.FileSet, n ast.Node) string {
|
||||
var buf bytes.Buffer
|
||||
printer.Fprint(&buf, fset, n)
|
||||
return buf.String()
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
module golang.org/x/tools/cmd/guru
|
||||
|
||||
go 1.19
|
||||
|
||||
require golang.org/x/tools v0.19.1-0.20240308170711-c1789339c5a6
|
||||
|
||||
require golang.org/x/mod v0.16.0 // indirect
|
|
@ -1,6 +0,0 @@
|
|||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/tools v0.19.1-0.20240308170711-c1789339c5a6 h1:bC3/XX2akBpqlWaYaX0R0Zlhouw71LSWY79WXuER1C8=
|
||||
golang.org/x/tools v0.19.1-0.20240308170711-c1789339c5a6/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
334
cmd/guru/guru.go
334
cmd/guru/guru.go
|
@ -1,334 +0,0 @@
|
|||
// Copyright 2014 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 main
|
||||
|
||||
// TODO(adonovan): new queries
|
||||
// - show all statements that may update the selected lvalue
|
||||
// (local, global, field, etc).
|
||||
// - show all places where an object of type T is created
|
||||
// (&T{}, var t T, new(T), new(struct{array [3]T}), etc.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
)
|
||||
|
||||
type printfFunc func(pos interface{}, format string, args ...interface{})
|
||||
|
||||
// A QueryResult is an item of output. Each query produces a stream of
|
||||
// query results, calling Query.Output for each one.
|
||||
type QueryResult interface {
|
||||
// JSON returns the QueryResult in JSON form.
|
||||
JSON(fset *token.FileSet) []byte
|
||||
|
||||
// PrintPlain prints the QueryResult in plain text form.
|
||||
// The implementation calls printfFunc to print each line of output.
|
||||
PrintPlain(printf printfFunc)
|
||||
}
|
||||
|
||||
// A QueryPos represents the position provided as input to a query:
|
||||
// a textual extent in the program's source code, the AST node it
|
||||
// corresponds to, and the package to which it belongs.
|
||||
// Instances are created by parseQueryPos.
|
||||
type queryPos struct {
|
||||
fset *token.FileSet
|
||||
start, end token.Pos // source extent of query
|
||||
path []ast.Node // AST path from query node to root of ast.File
|
||||
exact bool // 2nd result of PathEnclosingInterval
|
||||
info *loader.PackageInfo // type info for the queried package (nil for fastQueryPos)
|
||||
}
|
||||
|
||||
// typeString prints type T relative to the query position.
|
||||
func (qpos *queryPos) typeString(T types.Type) string {
|
||||
return types.TypeString(T, types.RelativeTo(qpos.info.Pkg))
|
||||
}
|
||||
|
||||
// objectString prints object obj relative to the query position.
|
||||
func (qpos *queryPos) objectString(obj types.Object) string {
|
||||
return types.ObjectString(obj, types.RelativeTo(qpos.info.Pkg))
|
||||
}
|
||||
|
||||
// A Query specifies a single guru query.
|
||||
type Query struct {
|
||||
Pos string // query position
|
||||
Build *build.Context // package loading configuration
|
||||
|
||||
// result-printing function, safe for concurrent use
|
||||
Output func(*token.FileSet, QueryResult)
|
||||
}
|
||||
|
||||
// Run runs an guru query and populates its Fset and Result.
|
||||
func Run(mode string, q *Query) error {
|
||||
switch mode {
|
||||
case "definition":
|
||||
return definition(q)
|
||||
case "describe":
|
||||
return describe(q)
|
||||
case "freevars":
|
||||
return freevars(q)
|
||||
case "implements":
|
||||
return implements(q)
|
||||
case "referrers":
|
||||
return referrers(q)
|
||||
case "what":
|
||||
return what(q)
|
||||
case "callees", "callers", "pointsto", "whicherrs", "callstack", "peers":
|
||||
return fmt.Errorf("mode %q is no longer supported (see Go issue #59676)", mode)
|
||||
default:
|
||||
return fmt.Errorf("invalid mode: %q", mode)
|
||||
}
|
||||
}
|
||||
|
||||
// importQueryPackage finds the package P containing the
|
||||
// query position and tells conf to import it.
|
||||
// It returns the package's path.
|
||||
func importQueryPackage(pos string, conf *loader.Config) (string, error) {
|
||||
fqpos, err := fastQueryPos(conf.Build, pos)
|
||||
if err != nil {
|
||||
return "", err // bad query
|
||||
}
|
||||
filename := fqpos.fset.File(fqpos.start).Name()
|
||||
|
||||
_, importPath, err := guessImportPath(filename, conf.Build)
|
||||
if err != nil {
|
||||
// Can't find GOPATH dir.
|
||||
// Treat the query file as its own package.
|
||||
importPath = "command-line-arguments"
|
||||
conf.CreateFromFilenames(importPath, filename)
|
||||
} else {
|
||||
// Check that it's possible to load the queried package.
|
||||
// (e.g. guru tests contain different 'package' decls in same dir.)
|
||||
// Keep consistent with logic in loader/util.go!
|
||||
cfg2 := *conf.Build
|
||||
cfg2.CgoEnabled = false
|
||||
bp, err := cfg2.Import(importPath, "", 0)
|
||||
if err != nil {
|
||||
return "", err // no files for package
|
||||
}
|
||||
|
||||
switch pkgContainsFile(bp, filename) {
|
||||
case 'T':
|
||||
conf.ImportWithTests(importPath)
|
||||
case 'X':
|
||||
conf.ImportWithTests(importPath)
|
||||
importPath += "_test" // for TypeCheckFuncBodies
|
||||
case 'G':
|
||||
conf.Import(importPath)
|
||||
default:
|
||||
// This happens for ad-hoc packages like
|
||||
// $GOROOT/src/net/http/triv.go.
|
||||
return "", fmt.Errorf("package %q doesn't contain file %s",
|
||||
importPath, filename)
|
||||
}
|
||||
}
|
||||
|
||||
conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath }
|
||||
|
||||
return importPath, nil
|
||||
}
|
||||
|
||||
// pkgContainsFile reports whether file was among the packages Go
|
||||
// files, Test files, eXternal test files, or not found.
|
||||
func pkgContainsFile(bp *build.Package, filename string) byte {
|
||||
for i, files := range [][]string{bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles} {
|
||||
for _, file := range files {
|
||||
if sameFile(filepath.Join(bp.Dir, file), filename) {
|
||||
return "GTX"[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0 // not found
|
||||
}
|
||||
|
||||
// parseQueryPos parses the source query position pos and returns the
|
||||
// AST node of the loaded program lprog that it identifies.
|
||||
// If needExact, it must identify a single AST subtree;
|
||||
// this is appropriate for queries that allow fairly arbitrary syntax,
|
||||
// e.g. "describe".
|
||||
func parseQueryPos(lprog *loader.Program, pos string, needExact bool) (*queryPos, error) {
|
||||
filename, startOffset, endOffset, err := parsePos(pos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Find the named file among those in the loaded program.
|
||||
var file *token.File
|
||||
lprog.Fset.Iterate(func(f *token.File) bool {
|
||||
if sameFile(filename, f.Name()) {
|
||||
file = f
|
||||
return false // done
|
||||
}
|
||||
return true // continue
|
||||
})
|
||||
if file == nil {
|
||||
return nil, fmt.Errorf("file %s not found in loaded program", filename)
|
||||
}
|
||||
|
||||
start, end, err := fileOffsetToPos(file, startOffset, endOffset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, path, exact := lprog.PathEnclosingInterval(start, end)
|
||||
if path == nil {
|
||||
return nil, fmt.Errorf("no syntax here")
|
||||
}
|
||||
if needExact && !exact {
|
||||
return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
|
||||
}
|
||||
return &queryPos{lprog.Fset, start, end, path, exact, info}, nil
|
||||
}
|
||||
|
||||
// ---------- Utilities ----------
|
||||
|
||||
// loadWithSoftErrors calls lconf.Load, suppressing "soft" errors. (See Go issue 16530.)
|
||||
// TODO(adonovan): Once the loader has an option to allow soft errors,
|
||||
// replace calls to loadWithSoftErrors with loader calls with that parameter.
|
||||
func loadWithSoftErrors(lconf *loader.Config) (*loader.Program, error) {
|
||||
lconf.AllowErrors = true
|
||||
|
||||
// Ideally we would just return conf.Load() here, but go/types
|
||||
// reports certain "soft" errors that gc does not (Go issue 14596).
|
||||
// As a workaround, we set AllowErrors=true and then duplicate
|
||||
// the loader's error checking but allow soft errors.
|
||||
// It would be nice if the loader API permitted "AllowErrors: soft".
|
||||
prog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var errpkgs []string
|
||||
// Report hard errors in indirectly imported packages.
|
||||
for _, info := range prog.AllPackages {
|
||||
if containsHardErrors(info.Errors) {
|
||||
errpkgs = append(errpkgs, info.Pkg.Path())
|
||||
} else {
|
||||
// Enable SSA construction for packages containing only soft errors.
|
||||
info.TransitivelyErrorFree = true
|
||||
}
|
||||
}
|
||||
if errpkgs != nil {
|
||||
var more string
|
||||
if len(errpkgs) > 3 {
|
||||
more = fmt.Sprintf(" and %d more", len(errpkgs)-3)
|
||||
errpkgs = errpkgs[:3]
|
||||
}
|
||||
return nil, fmt.Errorf("couldn't load packages due to errors: %s%s",
|
||||
strings.Join(errpkgs, ", "), more)
|
||||
}
|
||||
return prog, err
|
||||
}
|
||||
|
||||
func containsHardErrors(errors []error) bool {
|
||||
for _, err := range errors {
|
||||
if err, ok := err.(types.Error); ok && err.Soft {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// allowErrors causes type errors to be silently ignored.
|
||||
// (Not suitable if SSA construction follows.)
|
||||
func allowErrors(lconf *loader.Config) {
|
||||
ctxt := *lconf.Build // copy
|
||||
ctxt.CgoEnabled = false
|
||||
lconf.Build = &ctxt
|
||||
lconf.AllowErrors = true
|
||||
// AllErrors makes the parser always return an AST instead of
|
||||
// bailing out after 10 errors and returning an empty ast.File.
|
||||
lconf.ParserMode = parser.AllErrors
|
||||
lconf.TypeChecker.Error = func(err error) {}
|
||||
}
|
||||
|
||||
func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) }
|
||||
|
||||
// deref returns a pointer's element type; otherwise it returns typ.
|
||||
func deref(typ types.Type) types.Type {
|
||||
if p, ok := typ.Underlying().(*types.Pointer); ok {
|
||||
return p.Elem()
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
// fprintf prints to w a message of the form "location: message\n"
|
||||
// where location is derived from pos.
|
||||
//
|
||||
// pos must be one of:
|
||||
// - a token.Pos, denoting a position
|
||||
// - an ast.Node, denoting an interval
|
||||
// - anything with a Pos() method:
|
||||
// ssa.Member, ssa.Value, ssa.Instruction, types.Object, etc.
|
||||
// - a QueryPos, denoting the extent of the user's query.
|
||||
// - nil, meaning no position at all.
|
||||
//
|
||||
// The output format is compatible with the 'gnu'
|
||||
// compilation-error-regexp in Emacs' compilation mode.
|
||||
func fprintf(w io.Writer, fset *token.FileSet, pos interface{}, format string, args ...interface{}) {
|
||||
var start, end token.Pos
|
||||
switch pos := pos.(type) {
|
||||
case ast.Node:
|
||||
start = pos.Pos()
|
||||
end = pos.End()
|
||||
case token.Pos:
|
||||
start = pos
|
||||
end = start
|
||||
case *types.PkgName:
|
||||
// The Pos of most PkgName objects does not coincide with an identifier,
|
||||
// so we suppress the usual start+len(name) heuristic for types.Objects.
|
||||
start = pos.Pos()
|
||||
end = start
|
||||
case types.Object:
|
||||
start = pos.Pos()
|
||||
end = start + token.Pos(len(pos.Name())) // heuristic
|
||||
case interface {
|
||||
Pos() token.Pos
|
||||
}:
|
||||
start = pos.Pos()
|
||||
end = start
|
||||
case *queryPos:
|
||||
start = pos.start
|
||||
end = pos.end
|
||||
case nil:
|
||||
// no-op
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid pos: %T", pos))
|
||||
}
|
||||
|
||||
if sp := fset.Position(start); start == end {
|
||||
// (prints "-: " for token.NoPos)
|
||||
fmt.Fprintf(w, "%s: ", sp)
|
||||
} else {
|
||||
ep := fset.Position(end)
|
||||
// The -1 below is a concession to Emacs's broken use of
|
||||
// inclusive (not half-open) intervals.
|
||||
// Other editors may not want it.
|
||||
// TODO(adonovan): add an -editor=vim|emacs|acme|auto
|
||||
// flag; auto uses EMACS=t / VIM=... / etc env vars.
|
||||
fmt.Fprintf(w, "%s:%d.%d-%d.%d: ",
|
||||
sp.Filename, sp.Line, sp.Column, ep.Line, ep.Column-1)
|
||||
}
|
||||
fmt.Fprintf(w, format, args...)
|
||||
io.WriteString(w, "\n")
|
||||
}
|
||||
|
||||
func toJSON(x interface{}) []byte {
|
||||
b, err := json.MarshalIndent(x, "", "\t")
|
||||
if err != nil {
|
||||
log.Fatalf("JSON error: %v", err)
|
||||
}
|
||||
return b
|
||||
}
|
|
@ -1,325 +0,0 @@
|
|||
// Copyright 2013 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 main_test
|
||||
|
||||
// This file defines a test framework for guru queries.
|
||||
//
|
||||
// The files beneath testdata/src contain Go programs containing
|
||||
// query annotations of the form:
|
||||
//
|
||||
// @verb id "select"
|
||||
//
|
||||
// where verb is the query mode (e.g. "callers"), id is a unique name
|
||||
// for this query, and "select" is a regular expression matching the
|
||||
// substring of the current line that is the query's input selection.
|
||||
//
|
||||
// The expected output for each query is provided in the accompanying
|
||||
// .golden file.
|
||||
//
|
||||
// (Location information is not included because it's too fragile to
|
||||
// display as text. TODO(adonovan): think about how we can test its
|
||||
// correctness, since it is critical information.)
|
||||
//
|
||||
// Run this test with:
|
||||
// % go test golang.org/x/tools/cmd/guru -update
|
||||
// to update the golden files.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
guru "golang.org/x/tools/cmd/guru"
|
||||
"golang.org/x/tools/internal/testenv"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// This test currently requires GOPATH mode.
|
||||
// Explicitly disabling module mode should suffix, but
|
||||
// we'll also turn off GOPROXY just for good measure.
|
||||
if err := os.Setenv("GO111MODULE", "off"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := os.Setenv("GOPROXY", "off"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
var updateFlag = flag.Bool("update", false, "Update the golden files.")
|
||||
|
||||
type query struct {
|
||||
id string // unique id
|
||||
verb string // query mode, e.g. "callees"
|
||||
posn token.Position // query position
|
||||
filename string
|
||||
queryPos string // query position in command-line syntax
|
||||
}
|
||||
|
||||
func parseRegexp(text string) (*regexp.Regexp, error) {
|
||||
pattern, err := strconv.Unquote(text)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't unquote %s", text)
|
||||
}
|
||||
return regexp.Compile(pattern)
|
||||
}
|
||||
|
||||
// parseQueries parses and returns the queries in the named file.
|
||||
func parseQueries(t *testing.T, filename string) []*query {
|
||||
filedata, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Parse the file once to discover the test queries.
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, filename, filedata, parser.ParseComments)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
lines := bytes.Split(filedata, []byte("\n"))
|
||||
|
||||
var queries []*query
|
||||
queriesById := make(map[string]*query)
|
||||
|
||||
// Find all annotations of these forms:
|
||||
expectRe := regexp.MustCompile(`@([a-z]+)\s+(\S+)\s+(\".*)$`) // @verb id "regexp"
|
||||
for _, c := range f.Comments {
|
||||
text := strings.TrimSpace(c.Text())
|
||||
if text == "" || text[0] != '@' {
|
||||
continue
|
||||
}
|
||||
posn := fset.Position(c.Pos())
|
||||
|
||||
// @verb id "regexp"
|
||||
match := expectRe.FindStringSubmatch(text)
|
||||
if match == nil {
|
||||
t.Errorf("%s: ill-formed query: %s", posn, text)
|
||||
continue
|
||||
}
|
||||
|
||||
id := match[2]
|
||||
if prev, ok := queriesById[id]; ok {
|
||||
t.Errorf("%s: duplicate id %s", posn, id)
|
||||
t.Errorf("%s: previously used here", prev.posn)
|
||||
continue
|
||||
}
|
||||
|
||||
q := &query{
|
||||
id: id,
|
||||
verb: match[1],
|
||||
filename: filename,
|
||||
posn: posn,
|
||||
}
|
||||
|
||||
if match[3] != `"nopos"` {
|
||||
selectRe, err := parseRegexp(match[3])
|
||||
if err != nil {
|
||||
t.Errorf("%s: %s", posn, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Find text of the current line, sans query.
|
||||
// (Queries must be // not /**/ comments.)
|
||||
line := lines[posn.Line-1][:posn.Column-1]
|
||||
|
||||
// Apply regexp to current line to find input selection.
|
||||
loc := selectRe.FindIndex(line)
|
||||
if loc == nil {
|
||||
t.Errorf("%s: selection pattern %s doesn't match line %q",
|
||||
posn, match[3], string(line))
|
||||
continue
|
||||
}
|
||||
|
||||
// Assumes ASCII. TODO(adonovan): test on UTF-8.
|
||||
linestart := posn.Offset - (posn.Column - 1)
|
||||
|
||||
// Compute the file offsets.
|
||||
q.queryPos = fmt.Sprintf("%s:#%d,#%d",
|
||||
filename, linestart+loc[0], linestart+loc[1])
|
||||
}
|
||||
|
||||
queries = append(queries, q)
|
||||
queriesById[id] = q
|
||||
}
|
||||
|
||||
// Return the slice, not map, for deterministic iteration.
|
||||
return queries
|
||||
}
|
||||
|
||||
// doQuery poses query q to the guru and writes its response and
|
||||
// error (if any) to out.
|
||||
func doQuery(out io.Writer, q *query, json bool) {
|
||||
fmt.Fprintf(out, "-------- @%s %s --------\n", q.verb, q.id)
|
||||
|
||||
var buildContext = build.Default
|
||||
buildContext.GOPATH = "testdata"
|
||||
|
||||
gopathAbs, _ := filepath.Abs(buildContext.GOPATH)
|
||||
|
||||
var outputMu sync.Mutex // guards outputs
|
||||
var outputs []string // JSON objects or lines of text
|
||||
outputFn := func(fset *token.FileSet, qr guru.QueryResult) {
|
||||
outputMu.Lock()
|
||||
defer outputMu.Unlock()
|
||||
if json {
|
||||
jsonstr := string(qr.JSON(fset))
|
||||
// Sanitize any absolute filenames that creep in.
|
||||
jsonstr = strings.Replace(jsonstr, gopathAbs, "$GOPATH", -1)
|
||||
outputs = append(outputs, jsonstr)
|
||||
} else {
|
||||
// suppress position information
|
||||
qr.PrintPlain(func(_ interface{}, format string, args ...interface{}) {
|
||||
outputs = append(outputs, fmt.Sprintf(format, args...))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
query := guru.Query{
|
||||
Pos: q.queryPos,
|
||||
Build: &buildContext,
|
||||
Output: outputFn,
|
||||
}
|
||||
|
||||
if err := guru.Run(q.verb, &query); err != nil {
|
||||
fmt.Fprintf(out, "\nError: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// In a "referrers" query, references are sorted within each
|
||||
// package but packages are visited in arbitrary order,
|
||||
// so for determinism we sort them. Line 0 is a caption.
|
||||
if q.verb == "referrers" {
|
||||
sort.Strings(outputs[1:])
|
||||
}
|
||||
|
||||
for _, output := range outputs {
|
||||
// Replace occurrences of interface{} with any, for consistent output
|
||||
// across go 1.18 and earlier.
|
||||
output = strings.ReplaceAll(output, "interface{}", "any")
|
||||
fmt.Fprintf(out, "%s\n", output)
|
||||
}
|
||||
|
||||
if !json {
|
||||
io.WriteString(out, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuru(t *testing.T) {
|
||||
if testing.Short() {
|
||||
// These tests are super slow.
|
||||
// TODO: make a lighter version of the tests for short mode?
|
||||
t.Skipf("skipping in short mode")
|
||||
}
|
||||
|
||||
diffCmd := "/usr/bin/diff"
|
||||
if runtime.GOOS == "plan9" {
|
||||
diffCmd = "/bin/diff"
|
||||
}
|
||||
if _, err := exec.LookPath(diffCmd); err != nil {
|
||||
t.Skipf("skipping test: %v", err)
|
||||
}
|
||||
|
||||
for _, filename := range []string{
|
||||
"testdata/src/alias/alias.go",
|
||||
"testdata/src/describe/main.go",
|
||||
"testdata/src/freevars/main.go",
|
||||
"testdata/src/implements/main.go",
|
||||
"testdata/src/implements-methods/main.go",
|
||||
"testdata/src/imports/main.go",
|
||||
"testdata/src/referrers/main.go",
|
||||
"testdata/src/what/main.go",
|
||||
"testdata/src/definition-json/main.go",
|
||||
"testdata/src/describe-json/main.go",
|
||||
"testdata/src/implements-json/main.go",
|
||||
"testdata/src/implements-methods-json/main.go",
|
||||
"testdata/src/referrers-json/main.go",
|
||||
"testdata/src/what-json/main.go",
|
||||
} {
|
||||
filename := filename
|
||||
name := strings.Split(filename, "/")[2]
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if filename == "testdata/src/referrers/main.go" && runtime.GOOS == "plan9" {
|
||||
// Disable this test on plan9 since it expects a particular
|
||||
// wording for a "no such file or directory" error.
|
||||
t.Skip()
|
||||
}
|
||||
json := strings.Contains(filename, "-json/")
|
||||
queries := parseQueries(t, filename)
|
||||
golden := filename + "lden"
|
||||
gotfh, err := os.CreateTemp("", filepath.Base(filename)+"t")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := gotfh.Name()
|
||||
defer func() {
|
||||
gotfh.Close()
|
||||
os.Remove(got)
|
||||
}()
|
||||
|
||||
// Run the guru on each query, redirecting its output
|
||||
// and error (if any) to the foo.got file.
|
||||
for _, q := range queries {
|
||||
doQuery(gotfh, q, json)
|
||||
}
|
||||
|
||||
// Compare foo.got with foo.golden.
|
||||
var cmd *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "plan9":
|
||||
cmd = exec.Command(diffCmd, "-c", golden, got)
|
||||
default:
|
||||
cmd = exec.Command(diffCmd, "-u", golden, got)
|
||||
}
|
||||
testenv.NeedsTool(t, cmd.Path)
|
||||
buf := new(bytes.Buffer)
|
||||
cmd.Stdout = buf
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Errorf("Guru tests for %s failed: %s.\n%s\n",
|
||||
filename, err, buf)
|
||||
|
||||
if *updateFlag {
|
||||
t.Logf("Updating %s...", golden)
|
||||
if err := exec.Command("/bin/cp", got, golden).Run(); err != nil {
|
||||
t.Errorf("Update failed: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue14684(t *testing.T) {
|
||||
var buildContext = build.Default
|
||||
buildContext.GOPATH = "testdata"
|
||||
query := guru.Query{
|
||||
Pos: "testdata/src/README.txt:#1",
|
||||
Build: &buildContext,
|
||||
}
|
||||
err := guru.Run("freevars", &query)
|
||||
if err == nil {
|
||||
t.Fatal("guru query succeeded unexpectedly")
|
||||
}
|
||||
if got, want := err.Error(), "testdata/src/README.txt is not a Go source file"; got != want {
|
||||
t.Errorf("query error was %q, want %q", got, want)
|
||||
}
|
||||
}
|
|
@ -1,359 +0,0 @@
|
|||
// Copyright 2013 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/cmd/guru/serial"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
"golang.org/x/tools/internal/aliases"
|
||||
"golang.org/x/tools/refactor/importgraph"
|
||||
)
|
||||
|
||||
// The implements function displays the "implements" relation as it pertains to the
|
||||
// selected type.
|
||||
// If the selection is a method, 'implements' displays
|
||||
// the corresponding methods of the types that would have been reported
|
||||
// by an implements query on the receiver type.
|
||||
func implements(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
allowErrors(&lconf)
|
||||
|
||||
qpkg, err := importQueryPackage(q.Pos, &lconf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the packages to search.
|
||||
{
|
||||
// Otherwise inspect the forward and reverse
|
||||
// transitive closure of the selected package.
|
||||
// (In theory even this is incomplete.)
|
||||
_, rev, _ := importgraph.Build(q.Build)
|
||||
for path := range rev.Search(qpkg) {
|
||||
lconf.ImportWithTests(path)
|
||||
}
|
||||
|
||||
// TODO(adonovan): for completeness, we should also
|
||||
// type-check and inspect function bodies in all
|
||||
// imported packages. This would be expensive, but we
|
||||
// could optimize by skipping functions that do not
|
||||
// contain type declarations. This would require
|
||||
// changing the loader's TypeCheckFuncBodies hook to
|
||||
// provide the []*ast.File.
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find the selected type.
|
||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
||||
|
||||
var method *types.Func
|
||||
var T types.Type // selected type (receiver if method != nil)
|
||||
|
||||
switch action {
|
||||
case actionExpr:
|
||||
// method?
|
||||
if id, ok := path[0].(*ast.Ident); ok {
|
||||
if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok {
|
||||
recv := obj.Type().(*types.Signature).Recv()
|
||||
if recv == nil {
|
||||
return fmt.Errorf("this function is not a method")
|
||||
}
|
||||
method = obj
|
||||
T = recv.Type()
|
||||
}
|
||||
}
|
||||
|
||||
// If not a method, use the expression's type.
|
||||
if T == nil {
|
||||
T = qpos.info.TypeOf(path[0].(ast.Expr))
|
||||
}
|
||||
|
||||
case actionType:
|
||||
T = qpos.info.TypeOf(path[0].(ast.Expr))
|
||||
}
|
||||
if T == nil {
|
||||
return fmt.Errorf("not a type, method, or value")
|
||||
}
|
||||
|
||||
// Find all named types, even local types (which can have
|
||||
// methods due to promotion) and the built-in "error".
|
||||
// We ignore aliases 'type M = N' to avoid duplicate
|
||||
// reporting of the Named type N.
|
||||
var allNamed []*types.Named
|
||||
for _, info := range lprog.AllPackages {
|
||||
for _, obj := range info.Defs {
|
||||
if obj, ok := obj.(*types.TypeName); ok && !isAlias(obj) {
|
||||
if named, ok := obj.Type().(*types.Named); ok {
|
||||
allNamed = append(allNamed, named)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
allNamed = append(allNamed, types.Universe.Lookup("error").Type().(*types.Named))
|
||||
|
||||
var msets typeutil.MethodSetCache
|
||||
|
||||
// Test each named type.
|
||||
var to, from, fromPtr []types.Type
|
||||
for _, U := range allNamed {
|
||||
if isInterface(T) {
|
||||
if msets.MethodSet(T).Len() == 0 {
|
||||
continue // empty interface
|
||||
}
|
||||
if isInterface(U) {
|
||||
if msets.MethodSet(U).Len() == 0 {
|
||||
continue // empty interface
|
||||
}
|
||||
|
||||
// T interface, U interface
|
||||
if !types.Identical(T, U) {
|
||||
if types.AssignableTo(U, T) {
|
||||
to = append(to, U)
|
||||
}
|
||||
if types.AssignableTo(T, U) {
|
||||
from = append(from, U)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// T interface, U concrete
|
||||
if types.AssignableTo(U, T) {
|
||||
to = append(to, U)
|
||||
} else if pU := types.NewPointer(U); types.AssignableTo(pU, T) {
|
||||
to = append(to, pU)
|
||||
}
|
||||
}
|
||||
} else if isInterface(U) {
|
||||
if msets.MethodSet(U).Len() == 0 {
|
||||
continue // empty interface
|
||||
}
|
||||
|
||||
// T concrete, U interface
|
||||
if types.AssignableTo(T, U) {
|
||||
from = append(from, U)
|
||||
} else if pT := types.NewPointer(T); types.AssignableTo(pT, U) {
|
||||
fromPtr = append(fromPtr, U)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pos interface{} = qpos
|
||||
if nt, ok := aliases.Unalias(deref(T)).(*types.Named); ok {
|
||||
pos = nt.Obj()
|
||||
}
|
||||
|
||||
// Sort types (arbitrarily) to ensure test determinism.
|
||||
sort.Sort(typesByString(to))
|
||||
sort.Sort(typesByString(from))
|
||||
sort.Sort(typesByString(fromPtr))
|
||||
|
||||
var toMethod, fromMethod, fromPtrMethod []*types.Selection // contain nils
|
||||
if method != nil {
|
||||
for _, t := range to {
|
||||
toMethod = append(toMethod,
|
||||
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
|
||||
}
|
||||
for _, t := range from {
|
||||
fromMethod = append(fromMethod,
|
||||
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
|
||||
}
|
||||
for _, t := range fromPtr {
|
||||
fromPtrMethod = append(fromPtrMethod,
|
||||
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
q.Output(lprog.Fset, &implementsResult{
|
||||
qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
type implementsResult struct {
|
||||
qpos *queryPos
|
||||
|
||||
t types.Type // queried type (not necessarily named)
|
||||
pos interface{} // pos of t (*types.Name or *QueryPos)
|
||||
to []types.Type // named or ptr-to-named types assignable to interface T
|
||||
from []types.Type // named interfaces assignable from T
|
||||
fromPtr []types.Type // named interfaces assignable only from *T
|
||||
|
||||
// if a method was queried:
|
||||
method *types.Func // queried method
|
||||
toMethod []*types.Selection // method of type to[i], if any
|
||||
fromMethod []*types.Selection // method of type from[i], if any
|
||||
fromPtrMethod []*types.Selection // method of type fromPtrMethod[i], if any
|
||||
}
|
||||
|
||||
func (r *implementsResult) PrintPlain(printf printfFunc) {
|
||||
relation := "is implemented by"
|
||||
|
||||
meth := func(sel *types.Selection) {
|
||||
if sel != nil {
|
||||
printf(sel.Obj(), "\t%s method (%s).%s",
|
||||
relation, r.qpos.typeString(sel.Recv()), sel.Obj().Name())
|
||||
}
|
||||
}
|
||||
|
||||
if isInterface(r.t) {
|
||||
if types.NewMethodSet(r.t).Len() == 0 { // TODO(adonovan): cache mset
|
||||
printf(r.pos, "empty interface type %s", r.qpos.typeString(r.t))
|
||||
return
|
||||
}
|
||||
|
||||
if r.method == nil {
|
||||
printf(r.pos, "interface type %s", r.qpos.typeString(r.t))
|
||||
} else {
|
||||
printf(r.method, "abstract method %s", r.qpos.objectString(r.method))
|
||||
}
|
||||
|
||||
// Show concrete types (or methods) first; use two passes.
|
||||
for i, sub := range r.to {
|
||||
if !isInterface(sub) {
|
||||
if r.method == nil {
|
||||
printf(aliases.Unalias(deref(sub)).(*types.Named).Obj(), "\t%s %s type %s",
|
||||
relation, typeKind(sub), r.qpos.typeString(sub))
|
||||
} else {
|
||||
meth(r.toMethod[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, sub := range r.to {
|
||||
if isInterface(sub) {
|
||||
if r.method == nil {
|
||||
printf(aliases.Unalias(sub).(*types.Named).Obj(), "\t%s %s type %s",
|
||||
relation, typeKind(sub), r.qpos.typeString(sub))
|
||||
} else {
|
||||
meth(r.toMethod[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
relation = "implements"
|
||||
for i, super := range r.from {
|
||||
if r.method == nil {
|
||||
printf(aliases.Unalias(super).(*types.Named).Obj(), "\t%s %s",
|
||||
relation, r.qpos.typeString(super))
|
||||
} else {
|
||||
meth(r.fromMethod[i])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
relation = "implements"
|
||||
|
||||
if r.from != nil {
|
||||
if r.method == nil {
|
||||
printf(r.pos, "%s type %s",
|
||||
typeKind(r.t), r.qpos.typeString(r.t))
|
||||
} else {
|
||||
printf(r.method, "concrete method %s",
|
||||
r.qpos.objectString(r.method))
|
||||
}
|
||||
for i, super := range r.from {
|
||||
if r.method == nil {
|
||||
printf(aliases.Unalias(super).(*types.Named).Obj(), "\t%s %s",
|
||||
relation, r.qpos.typeString(super))
|
||||
} else {
|
||||
meth(r.fromMethod[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
if r.fromPtr != nil {
|
||||
if r.method == nil {
|
||||
printf(r.pos, "pointer type *%s", r.qpos.typeString(r.t))
|
||||
} else {
|
||||
// TODO(adonovan): de-dup (C).f and (*C).f implementing (I).f.
|
||||
printf(r.method, "concrete method %s",
|
||||
r.qpos.objectString(r.method))
|
||||
}
|
||||
|
||||
for i, psuper := range r.fromPtr {
|
||||
if r.method == nil {
|
||||
printf(aliases.Unalias(psuper).(*types.Named).Obj(), "\t%s %s",
|
||||
relation, r.qpos.typeString(psuper))
|
||||
} else {
|
||||
meth(r.fromPtrMethod[i])
|
||||
}
|
||||
}
|
||||
} else if r.from == nil {
|
||||
printf(r.pos, "%s type %s implements only interface{}",
|
||||
typeKind(r.t), r.qpos.typeString(r.t))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *implementsResult) JSON(fset *token.FileSet) []byte {
|
||||
var method *serial.DescribeMethod
|
||||
if r.method != nil {
|
||||
method = &serial.DescribeMethod{
|
||||
Name: r.qpos.objectString(r.method),
|
||||
Pos: fset.Position(r.method.Pos()).String(),
|
||||
}
|
||||
}
|
||||
return toJSON(&serial.Implements{
|
||||
T: makeImplementsType(r.t, fset),
|
||||
AssignableTo: makeImplementsTypes(r.to, fset),
|
||||
AssignableFrom: makeImplementsTypes(r.from, fset),
|
||||
AssignableFromPtr: makeImplementsTypes(r.fromPtr, fset),
|
||||
AssignableToMethod: methodsToSerial(r.qpos.info.Pkg, r.toMethod, fset),
|
||||
AssignableFromMethod: methodsToSerial(r.qpos.info.Pkg, r.fromMethod, fset),
|
||||
AssignableFromPtrMethod: methodsToSerial(r.qpos.info.Pkg, r.fromPtrMethod, fset),
|
||||
Method: method,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func makeImplementsTypes(tt []types.Type, fset *token.FileSet) []serial.ImplementsType {
|
||||
var r []serial.ImplementsType
|
||||
for _, t := range tt {
|
||||
r = append(r, makeImplementsType(t, fset))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func makeImplementsType(T types.Type, fset *token.FileSet) serial.ImplementsType {
|
||||
var pos token.Pos
|
||||
if nt, ok := aliases.Unalias(deref(T)).(*types.Named); ok { // implementsResult.t may be non-named
|
||||
pos = nt.Obj().Pos()
|
||||
}
|
||||
return serial.ImplementsType{
|
||||
Name: T.String(),
|
||||
Pos: fset.Position(pos).String(),
|
||||
Kind: typeKind(T),
|
||||
}
|
||||
}
|
||||
|
||||
// typeKind returns a string describing the underlying kind of type,
|
||||
// e.g. "slice", "array", "struct".
|
||||
func typeKind(T types.Type) string {
|
||||
s := reflect.TypeOf(T.Underlying()).String()
|
||||
return strings.ToLower(strings.TrimPrefix(s, "*types."))
|
||||
}
|
||||
|
||||
func isInterface(T types.Type) bool { return types.IsInterface(T) }
|
||||
|
||||
type typesByString []types.Type
|
||||
|
||||
func (p typesByString) Len() int { return len(p) }
|
||||
func (p typesByString) Less(i, j int) bool { return p[i].String() < p[j].String() }
|
||||
func (p typesByString) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright 2017 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.
|
||||
|
||||
//go:build !go1.9
|
||||
// +build !go1.9
|
||||
|
||||
package main
|
||||
|
||||
import "go/types"
|
||||
|
||||
func isAlias(obj *types.TypeName) bool {
|
||||
return false // there are no type aliases before Go 1.9
|
||||
}
|
||||
|
||||
const HasAlias = false
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright 2017 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.
|
||||
|
||||
//go:build go1.9
|
||||
// +build go1.9
|
||||
|
||||
package main
|
||||
|
||||
import "go/types"
|
||||
|
||||
func isAlias(obj *types.TypeName) bool {
|
||||
return obj.IsAlias()
|
||||
}
|
||||
|
||||
const HasAlias = true
|
191
cmd/guru/main.go
191
cmd/guru/main.go
|
@ -1,191 +0,0 @@
|
|||
// Copyright 2013 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.
|
||||
|
||||
// guru: a tool for answering questions about Go source code.
|
||||
//
|
||||
// http://golang.org/s/using-guru
|
||||
//
|
||||
// Run with -help flag or help subcommand for usage information.
|
||||
package main // import "golang.org/x/tools/cmd/guru"
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
)
|
||||
|
||||
// flags
|
||||
var (
|
||||
modifiedFlag = flag.Bool("modified", false, "read archive of modified files from standard input")
|
||||
scopeFlag = flag.String("scope", "", "comma-separated list of `packages` the analysis should be limited to")
|
||||
ptalogFlag = flag.String("ptalog", "", "write points-to analysis log to `file`")
|
||||
jsonFlag = flag.Bool("json", false, "emit output in JSON format")
|
||||
reflectFlag = flag.Bool("reflect", false, "analyze reflection soundly (slow)")
|
||||
cpuprofileFlag = flag.String("cpuprofile", "", "write CPU profile to `file`")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
|
||||
|
||||
// gccgo does not provide a GOROOT with standard library sources.
|
||||
// If we have one in the environment, force gc mode.
|
||||
if build.Default.Compiler == "gccgo" {
|
||||
if _, err := os.Stat(filepath.Join(runtime.GOROOT(), "src", "runtime", "runtime.go")); err == nil {
|
||||
build.Default.Compiler = "gc"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const useHelp = "Run 'guru -help' for more information.\n"
|
||||
|
||||
const helpMessage = `Go source code guru.
|
||||
Usage: guru [flags] <mode> <position>
|
||||
|
||||
The mode argument determines the query to perform:
|
||||
|
||||
callees show possible targets of selected function call
|
||||
callers show possible callers of selected function
|
||||
callstack show path from callgraph root to selected function
|
||||
definition show declaration of selected identifier
|
||||
describe describe selected syntax: definition, methods, etc
|
||||
freevars show free variables of selection
|
||||
implements show 'implements' relation for selected type or method
|
||||
peers show send/receive corresponding to selected channel op
|
||||
referrers show all refs to entity denoted by selected identifier
|
||||
what show basic information about the selected syntax node
|
||||
|
||||
The position argument specifies the filename and byte offset (or range)
|
||||
of the syntax element to query. For example:
|
||||
|
||||
foo.go:#123,#128
|
||||
bar.go:#123
|
||||
|
||||
The -json flag causes guru to emit output in JSON format;
|
||||
golang.org/x/tools/cmd/guru/serial defines its schema.
|
||||
Otherwise, the output is in an editor-friendly format in which
|
||||
every line has the form "pos: text", where pos is "-" if unknown.
|
||||
|
||||
The -modified flag causes guru to read an archive from standard input.
|
||||
Files in this archive will be used in preference to those in
|
||||
the file system. In this way, a text editor may supply guru
|
||||
with the contents of its unsaved buffers. Each archive entry
|
||||
consists of the file name, a newline, the decimal file size,
|
||||
another newline, and the contents of the file.
|
||||
|
||||
The -scope flag restricts analysis to the specified packages.
|
||||
Its value is a comma-separated list of patterns of these forms:
|
||||
golang.org/x/tools/cmd/guru # a single package
|
||||
golang.org/x/tools/... # all packages beneath dir
|
||||
... # the entire workspace.
|
||||
A pattern preceded by '-' is negative, so the scope
|
||||
encoding/...,-encoding/xml
|
||||
matches all encoding packages except encoding/xml.
|
||||
|
||||
User manual: http://golang.org/s/using-guru
|
||||
|
||||
Example: describe syntax at offset 530 in this file (an import spec):
|
||||
|
||||
$ guru describe src/golang.org/x/tools/cmd/guru/main.go:#530
|
||||
`
|
||||
|
||||
func printHelp() {
|
||||
fmt.Fprint(os.Stderr, helpMessage)
|
||||
fmt.Fprintln(os.Stderr, "\nFlags:")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetPrefix("guru: ")
|
||||
log.SetFlags(0)
|
||||
|
||||
// Don't print full help unless -help was requested.
|
||||
// Just gently remind users that it's there.
|
||||
flag.Usage = func() { fmt.Fprint(os.Stderr, useHelp) }
|
||||
flag.CommandLine.Init(os.Args[0], flag.ContinueOnError) // hack
|
||||
if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
|
||||
// (err has already been printed)
|
||||
if err == flag.ErrHelp {
|
||||
printHelp()
|
||||
}
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
args := flag.Args()
|
||||
if len(args) != 2 {
|
||||
flag.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
mode, posn := args[0], args[1]
|
||||
|
||||
if mode == "help" {
|
||||
printHelp()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// Profiling support.
|
||||
if *cpuprofileFlag != "" {
|
||||
f, err := os.Create(*cpuprofileFlag)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
pprof.StartCPUProfile(f)
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
ctxt := &build.Default
|
||||
|
||||
// If there were modified files,
|
||||
// read them from the standard input and
|
||||
// overlay them on the build context.
|
||||
if *modifiedFlag {
|
||||
modified, err := buildutil.ParseOverlayArchive(os.Stdin)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// All I/O done by guru needs to consult the modified map.
|
||||
// The ReadFile done by referrers does,
|
||||
// but the loader's cgo preprocessing currently does not.
|
||||
|
||||
if len(modified) > 0 {
|
||||
ctxt = buildutil.OverlayContext(ctxt, modified)
|
||||
}
|
||||
}
|
||||
|
||||
var outputMu sync.Mutex
|
||||
output := func(fset *token.FileSet, qr QueryResult) {
|
||||
outputMu.Lock()
|
||||
defer outputMu.Unlock()
|
||||
if *jsonFlag {
|
||||
// JSON output
|
||||
fmt.Printf("%s\n", qr.JSON(fset))
|
||||
} else {
|
||||
// plain output
|
||||
printf := func(pos interface{}, format string, args ...interface{}) {
|
||||
fprintf(os.Stdout, fset, pos, format, args...)
|
||||
}
|
||||
qr.PrintPlain(printf)
|
||||
}
|
||||
}
|
||||
|
||||
// Ask the guru.
|
||||
query := Query{
|
||||
Pos: posn,
|
||||
Build: ctxt,
|
||||
Output: output,
|
||||
}
|
||||
|
||||
if err := Run(mode, &query); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
139
cmd/guru/pos.go
139
cmd/guru/pos.go
|
@ -1,139 +0,0 @@
|
|||
// Copyright 2013 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 main
|
||||
|
||||
// This file defines utilities for working with file positions.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
)
|
||||
|
||||
// parseOctothorpDecimal returns the numeric value if s matches "#%d",
|
||||
// otherwise -1.
|
||||
func parseOctothorpDecimal(s string) int {
|
||||
if s != "" && s[0] == '#' {
|
||||
if s, err := strconv.ParseInt(s[1:], 10, 32); err == nil {
|
||||
return int(s)
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// parsePos parses a string of the form "file:pos" or
|
||||
// file:start,end" where pos, start, end match #%d and represent byte
|
||||
// offsets, and returns its components.
|
||||
//
|
||||
// (Numbers without a '#' prefix are reserved for future use,
|
||||
// e.g. to indicate line/column positions.)
|
||||
func parsePos(pos string) (filename string, startOffset, endOffset int, err error) {
|
||||
if pos == "" {
|
||||
err = fmt.Errorf("no source position specified")
|
||||
return
|
||||
}
|
||||
|
||||
colon := strings.LastIndex(pos, ":")
|
||||
if colon < 0 {
|
||||
err = fmt.Errorf("bad position syntax %q", pos)
|
||||
return
|
||||
}
|
||||
filename, offset := pos[:colon], pos[colon+1:]
|
||||
startOffset = -1
|
||||
endOffset = -1
|
||||
if comma := strings.Index(offset, ","); comma < 0 {
|
||||
// e.g. "foo.go:#123"
|
||||
startOffset = parseOctothorpDecimal(offset)
|
||||
endOffset = startOffset
|
||||
} else {
|
||||
// e.g. "foo.go:#123,#456"
|
||||
startOffset = parseOctothorpDecimal(offset[:comma])
|
||||
endOffset = parseOctothorpDecimal(offset[comma+1:])
|
||||
}
|
||||
if startOffset < 0 || endOffset < 0 {
|
||||
err = fmt.Errorf("invalid offset %q in query position", offset)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// fileOffsetToPos translates the specified file-relative byte offsets
|
||||
// into token.Pos form. It returns an error if the file was not found
|
||||
// or the offsets were out of bounds.
|
||||
func fileOffsetToPos(file *token.File, startOffset, endOffset int) (start, end token.Pos, err error) {
|
||||
// Range check [start..end], inclusive of both end-points.
|
||||
|
||||
if 0 <= startOffset && startOffset <= file.Size() {
|
||||
start = file.Pos(int(startOffset))
|
||||
} else {
|
||||
err = fmt.Errorf("start position is beyond end of file")
|
||||
return
|
||||
}
|
||||
|
||||
if 0 <= endOffset && endOffset <= file.Size() {
|
||||
end = file.Pos(int(endOffset))
|
||||
} else {
|
||||
err = fmt.Errorf("end position is beyond end of file")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// sameFile returns true if x and y have the same basename and denote
|
||||
// the same file.
|
||||
func sameFile(x, y string) bool {
|
||||
if filepath.Base(x) == filepath.Base(y) { // (optimisation)
|
||||
if xi, err := os.Stat(x); err == nil {
|
||||
if yi, err := os.Stat(y); err == nil {
|
||||
return os.SameFile(xi, yi)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// fastQueryPos parses the position string and returns a queryPos.
|
||||
// It parses only a single file and does not run the type checker.
|
||||
func fastQueryPos(ctxt *build.Context, pos string) (*queryPos, error) {
|
||||
filename, startOffset, endOffset, err := parsePos(pos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse the file, opening it the file via the build.Context
|
||||
// so that we observe the effects of the -modified flag.
|
||||
fset := token.NewFileSet()
|
||||
cwd, _ := os.Getwd()
|
||||
f, err := buildutil.ParseFile(fset, ctxt, nil, cwd, filename, parser.Mode(0))
|
||||
// ParseFile usually returns a partial file along with an error.
|
||||
// Only fail if there is no file.
|
||||
if f == nil {
|
||||
return nil, err
|
||||
}
|
||||
if !f.Pos().IsValid() {
|
||||
return nil, fmt.Errorf("%s is not a Go source file", filename)
|
||||
}
|
||||
|
||||
start, end, err := fileOffsetToPos(fset.File(f.Pos()), startOffset, endOffset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
path, exact := astutil.PathEnclosingInterval(f, start, end)
|
||||
if path == nil {
|
||||
return nil, fmt.Errorf("no syntax here")
|
||||
}
|
||||
|
||||
return &queryPos{fset, start, end, path, exact, nil}, nil
|
||||
}
|
|
@ -1,801 +0,0 @@
|
|||
// Copyright 2013 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/cmd/guru/serial"
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/imports"
|
||||
"golang.org/x/tools/refactor/importgraph"
|
||||
)
|
||||
|
||||
// The referrers function reports all identifiers that resolve to the same object
|
||||
// as the queried identifier, within any package in the workspace.
|
||||
func referrers(q *Query) error {
|
||||
fset := token.NewFileSet()
|
||||
lconf := loader.Config{Fset: fset, Build: q.Build}
|
||||
allowErrors(&lconf)
|
||||
|
||||
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load tests of the query package
|
||||
// even if the query location is not in the tests.
|
||||
for path := range lconf.ImportPkgs {
|
||||
lconf.ImportPkgs[path] = true
|
||||
}
|
||||
|
||||
// Load/parse/type-check the query package.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, _ := qpos.path[0].(*ast.Ident)
|
||||
if id == nil {
|
||||
return fmt.Errorf("no identifier here")
|
||||
}
|
||||
|
||||
obj := qpos.info.ObjectOf(id)
|
||||
if obj == nil {
|
||||
// Happens for y in "switch y := x.(type)",
|
||||
// the package declaration,
|
||||
// and unresolved identifiers.
|
||||
if _, ok := qpos.path[1].(*ast.File); ok { // package decl?
|
||||
return packageReferrers(q, qpos.info.Pkg.Path())
|
||||
}
|
||||
return fmt.Errorf("no object for identifier: %T", qpos.path[1])
|
||||
}
|
||||
|
||||
// Imported package name?
|
||||
if pkgname, ok := obj.(*types.PkgName); ok {
|
||||
return packageReferrers(q, pkgname.Imported().Path())
|
||||
}
|
||||
|
||||
if obj.Pkg() == nil {
|
||||
return fmt.Errorf("references to predeclared %q are everywhere!", obj.Name())
|
||||
}
|
||||
|
||||
q.Output(fset, &referrersInitialResult{
|
||||
qinfo: qpos.info,
|
||||
obj: obj,
|
||||
})
|
||||
|
||||
// For a globally accessible object defined in package P, we
|
||||
// must load packages that depend on P. Specifically, for a
|
||||
// package-level object, we need load only direct importers
|
||||
// of P, but for a field or method, we must load
|
||||
// any package that transitively imports P.
|
||||
|
||||
if global, pkglevel := classify(obj); global {
|
||||
if pkglevel {
|
||||
return globalReferrersPkgLevel(q, obj, fset)
|
||||
}
|
||||
// We'll use the object's position to identify it in the larger program.
|
||||
objposn := fset.Position(obj.Pos())
|
||||
defpkg := obj.Pkg().Path() // defining package
|
||||
return globalReferrers(q, qpos.info.Pkg.Path(), defpkg, objposn)
|
||||
}
|
||||
|
||||
outputUses(q, fset, usesOf(obj, qpos.info), obj.Pkg())
|
||||
|
||||
return nil // success
|
||||
}
|
||||
|
||||
// classify classifies objects by how far
|
||||
// we have to look to find references to them.
|
||||
func classify(obj types.Object) (global, pkglevel bool) {
|
||||
if obj.Exported() {
|
||||
if obj.Parent() == nil {
|
||||
// selectable object (field or method)
|
||||
return true, false
|
||||
}
|
||||
if obj.Parent() == obj.Pkg().Scope() {
|
||||
// lexical object (package-level var/const/func/type)
|
||||
return true, true
|
||||
}
|
||||
}
|
||||
// object with unexported named or defined in local scope
|
||||
return false, false
|
||||
}
|
||||
|
||||
// packageReferrers reports all references to the specified package
|
||||
// throughout the workspace.
|
||||
func packageReferrers(q *Query, path string) error {
|
||||
// Scan the workspace and build the import graph.
|
||||
// Ignore broken packages.
|
||||
_, rev, _ := importgraph.Build(q.Build)
|
||||
|
||||
// Find the set of packages that directly import the query package.
|
||||
// Only those packages need typechecking of function bodies.
|
||||
users := rev[path]
|
||||
|
||||
// Load the larger program.
|
||||
fset := token.NewFileSet()
|
||||
lconf := loader.Config{
|
||||
Fset: fset,
|
||||
Build: q.Build,
|
||||
TypeCheckFuncBodies: func(p string) bool {
|
||||
return users[strings.TrimSuffix(p, "_test")]
|
||||
},
|
||||
}
|
||||
allowErrors(&lconf)
|
||||
|
||||
// The importgraph doesn't treat external test packages
|
||||
// as separate nodes, so we must use ImportWithTests.
|
||||
for path := range users {
|
||||
lconf.ImportWithTests(path)
|
||||
}
|
||||
|
||||
// Subtle! AfterTypeCheck needs no mutex for qpkg because the
|
||||
// topological import order gives us the necessary happens-before edges.
|
||||
// TODO(adonovan): what about import cycles?
|
||||
var qpkg *types.Package
|
||||
|
||||
// For efficiency, we scan each package for references
|
||||
// just after it has been type-checked. The loader calls
|
||||
// AfterTypeCheck (concurrently), providing us with a stream of
|
||||
// packages.
|
||||
lconf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) {
|
||||
// AfterTypeCheck may be called twice for the same package due to augmentation.
|
||||
|
||||
if info.Pkg.Path() == path && qpkg == nil {
|
||||
// Found the package of interest.
|
||||
qpkg = info.Pkg
|
||||
fakepkgname := types.NewPkgName(token.NoPos, qpkg, qpkg.Name(), qpkg)
|
||||
q.Output(fset, &referrersInitialResult{
|
||||
qinfo: info,
|
||||
obj: fakepkgname, // bogus
|
||||
})
|
||||
}
|
||||
|
||||
// Only inspect packages that directly import the
|
||||
// declaring package (and thus were type-checked).
|
||||
if lconf.TypeCheckFuncBodies(info.Pkg.Path()) {
|
||||
// Find PkgNames that refer to qpkg.
|
||||
// TODO(adonovan): perhaps more useful would be to show imports
|
||||
// of the package instead of qualified identifiers.
|
||||
var refs []*ast.Ident
|
||||
for id, obj := range info.Uses {
|
||||
if obj, ok := obj.(*types.PkgName); ok && obj.Imported() == qpkg {
|
||||
refs = append(refs, id)
|
||||
}
|
||||
}
|
||||
outputUses(q, fset, refs, info.Pkg)
|
||||
}
|
||||
|
||||
clearInfoFields(info) // save memory
|
||||
}
|
||||
|
||||
lconf.Load() // ignore error
|
||||
|
||||
if qpkg == nil {
|
||||
log.Fatalf("query package %q not found during reloading", path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func usesOf(queryObj types.Object, info *loader.PackageInfo) []*ast.Ident {
|
||||
var refs []*ast.Ident
|
||||
for id, obj := range info.Uses {
|
||||
if sameObj(queryObj, obj) {
|
||||
refs = append(refs, id)
|
||||
}
|
||||
}
|
||||
return refs
|
||||
}
|
||||
|
||||
// outputUses outputs a result describing refs, which appear in the package denoted by info.
|
||||
func outputUses(q *Query, fset *token.FileSet, refs []*ast.Ident, pkg *types.Package) {
|
||||
if len(refs) > 0 {
|
||||
sort.Sort(byNamePos{fset, refs})
|
||||
q.Output(fset, &referrersPackageResult{
|
||||
pkg: pkg,
|
||||
build: q.Build,
|
||||
fset: fset,
|
||||
refs: refs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// globalReferrers reports references throughout the entire workspace to the
|
||||
// object (a field or method) at the specified source position.
|
||||
// Its defining package is defpkg, and the query package is qpkg.
|
||||
func globalReferrers(q *Query, qpkg, defpkg string, objposn token.Position) error {
|
||||
// Scan the workspace and build the import graph.
|
||||
// Ignore broken packages.
|
||||
_, rev, _ := importgraph.Build(q.Build)
|
||||
|
||||
// Find the set of packages that depend on defpkg.
|
||||
// Only function bodies in those packages need type-checking.
|
||||
users := rev.Search(defpkg) // transitive importers
|
||||
|
||||
// Prepare to load the larger program.
|
||||
fset := token.NewFileSet()
|
||||
lconf := loader.Config{
|
||||
Fset: fset,
|
||||
Build: q.Build,
|
||||
TypeCheckFuncBodies: func(p string) bool {
|
||||
return users[strings.TrimSuffix(p, "_test")]
|
||||
},
|
||||
}
|
||||
allowErrors(&lconf)
|
||||
|
||||
// The importgraph doesn't treat external test packages
|
||||
// as separate nodes, so we must use ImportWithTests.
|
||||
for path := range users {
|
||||
lconf.ImportWithTests(path)
|
||||
}
|
||||
|
||||
// The remainder of this function is somewhat tricky because it
|
||||
// operates on the concurrent stream of packages observed by the
|
||||
// loader's AfterTypeCheck hook. Most of guru's helper
|
||||
// functions assume the entire program has already been loaded,
|
||||
// so we can't use them here.
|
||||
// TODO(adonovan): smooth things out once the other changes have landed.
|
||||
|
||||
// Results are reported concurrently from within the
|
||||
// AfterTypeCheck hook. The program may provide a useful stream
|
||||
// of information even if the user doesn't let the program run
|
||||
// to completion.
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
qobj types.Object
|
||||
)
|
||||
|
||||
// For efficiency, we scan each package for references
|
||||
// just after it has been type-checked. The loader calls
|
||||
// AfterTypeCheck (concurrently), providing us with a stream of
|
||||
// packages.
|
||||
lconf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) {
|
||||
// AfterTypeCheck may be called twice for the same package due to augmentation.
|
||||
|
||||
// Only inspect packages that depend on the declaring package
|
||||
// (and thus were type-checked).
|
||||
if lconf.TypeCheckFuncBodies(info.Pkg.Path()) {
|
||||
// Record the query object and its package when we see it.
|
||||
mu.Lock()
|
||||
if qobj == nil && info.Pkg.Path() == defpkg {
|
||||
// Find the object by its position (slightly ugly).
|
||||
qobj = findObject(fset, &info.Info, objposn)
|
||||
if qobj == nil {
|
||||
// It really ought to be there;
|
||||
// we found it once already.
|
||||
log.Fatalf("object at %s not found in package %s",
|
||||
objposn, defpkg)
|
||||
}
|
||||
}
|
||||
obj := qobj
|
||||
mu.Unlock()
|
||||
|
||||
// Look for references to the query object.
|
||||
if obj != nil {
|
||||
outputUses(q, fset, usesOf(obj, info), info.Pkg)
|
||||
}
|
||||
}
|
||||
|
||||
clearInfoFields(info) // save memory
|
||||
}
|
||||
|
||||
lconf.Load() // ignore error
|
||||
|
||||
if qobj == nil {
|
||||
log.Fatal("query object not found during reloading")
|
||||
}
|
||||
|
||||
return nil // success
|
||||
}
|
||||
|
||||
// globalReferrersPkgLevel reports references throughout the entire workspace to the package-level object obj.
|
||||
// It assumes that the query object itself has already been reported.
|
||||
func globalReferrersPkgLevel(q *Query, obj types.Object, fset *token.FileSet) error {
|
||||
// globalReferrersPkgLevel uses go/ast and friends instead of go/types.
|
||||
// This affords a considerable performance benefit.
|
||||
// It comes at the cost of some code complexity.
|
||||
//
|
||||
// Here's a high level summary.
|
||||
//
|
||||
// The goal is to find references to the query object p.Q.
|
||||
// There are several possible scenarios, each handled differently.
|
||||
//
|
||||
// 1. We are looking in a package other than p, and p is not dot-imported.
|
||||
// This is the simplest case. Q must be referred to as n.Q,
|
||||
// where n is the name under which p is imported.
|
||||
// We look at all imports of p to gather all names under which it is imported.
|
||||
// (In the typical case, it is imported only once, under its default name.)
|
||||
// Then we look at all selector expressions and report any matches.
|
||||
//
|
||||
// 2. We are looking in a package other than p, and p is dot-imported.
|
||||
// In this case, Q will be referred to just as Q.
|
||||
// Furthermore, go/ast's object resolution will not be able to resolve
|
||||
// Q to any other object, unlike any local (file- or function- or block-scoped) object.
|
||||
// So we look at all matching identifiers and report all unresolvable ones.
|
||||
//
|
||||
// 3. We are looking in package p.
|
||||
// (Care must be taken to separate p and p_test (an xtest package),
|
||||
// and make sure that they are treated as separate packages.)
|
||||
// In this case, we give go/ast the entire package for object resolution,
|
||||
// instead of going file by file.
|
||||
// We then iterate over all identifiers that resolve to the query object.
|
||||
// (The query object itself has already been reported, so we don't re-report it.)
|
||||
//
|
||||
// We always skip all files that don't contain the string Q, as they cannot be
|
||||
// relevant to finding references to Q.
|
||||
//
|
||||
// We parse all files leniently. In the presence of parsing errors, results are best-effort.
|
||||
|
||||
// Scan the workspace and build the import graph.
|
||||
// Ignore broken packages.
|
||||
_, rev, _ := importgraph.Build(q.Build)
|
||||
|
||||
// Find the set of packages that directly import defpkg.
|
||||
defpkg := obj.Pkg().Path()
|
||||
defpkg = strings.TrimSuffix(defpkg, "_test") // package x_test actually has package name x
|
||||
defpkg = imports.VendorlessPath(defpkg) // remove vendor goop
|
||||
|
||||
users := rev[defpkg]
|
||||
if len(users) == 0 {
|
||||
users = make(map[string]bool)
|
||||
}
|
||||
// We also need to check defpkg itself, and its xtests.
|
||||
// For the reverse graph packages, we process xtests with the main package.
|
||||
// defpkg gets special handling; we must distinguish between in-package vs out-of-package.
|
||||
// To make the control flow below simpler, add defpkg and defpkg xtest placeholders.
|
||||
// Use "!test" instead of "_test" because "!" is not a valid character in an import path.
|
||||
// (More precisely, it is not guaranteed to be a valid character in an import path,
|
||||
// so it is unlikely that it will be in use. See https://golang.org/ref/spec#Import_declarations.)
|
||||
users[defpkg] = true
|
||||
users[defpkg+"!test"] = true
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defname := obj.Pkg().Name() // name of defining package, used for imports using import path only
|
||||
isxtest := strings.HasSuffix(defname, "_test") // indicates whether the query object is defined in an xtest package
|
||||
|
||||
name := obj.Name()
|
||||
namebytes := []byte(name) // byte slice version of query object name, for early filtering
|
||||
objpos := fset.Position(obj.Pos()) // position of query object, used to prevent re-emitting original decl
|
||||
|
||||
sema := make(chan struct{}, 20) // counting semaphore to limit I/O concurrency
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for u := range users {
|
||||
u := u
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
uIsXTest := strings.HasSuffix(u, "!test") // indicates whether this package is the special defpkg xtest package
|
||||
u = strings.TrimSuffix(u, "!test")
|
||||
|
||||
// Resolve package.
|
||||
sema <- struct{}{} // acquire token
|
||||
pkg, err := q.Build.Import(u, cwd, build.IgnoreVendor)
|
||||
<-sema // release token
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// If we're not in the query package,
|
||||
// the object is in another package regardless,
|
||||
// so we want to process all files.
|
||||
// If we are in the query package,
|
||||
// we want to only process the files that are
|
||||
// part of that query package;
|
||||
// that set depends on whether the query package itself is an xtest.
|
||||
inQueryPkg := u == defpkg && isxtest == uIsXTest
|
||||
var files []string
|
||||
if !inQueryPkg || !isxtest {
|
||||
files = append(files, pkg.GoFiles...)
|
||||
files = append(files, pkg.TestGoFiles...)
|
||||
files = append(files, pkg.CgoFiles...) // use raw cgo files, as we're only parsing
|
||||
}
|
||||
if !inQueryPkg || isxtest {
|
||||
files = append(files, pkg.XTestGoFiles...)
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var deffiles map[string]*ast.File
|
||||
if inQueryPkg {
|
||||
deffiles = make(map[string]*ast.File)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer) // reusable buffer for reading files
|
||||
|
||||
for _, file := range files {
|
||||
if !buildutil.IsAbsPath(q.Build, file) {
|
||||
file = buildutil.JoinPath(q.Build, pkg.Dir, file)
|
||||
}
|
||||
buf.Reset()
|
||||
sema <- struct{}{} // acquire token
|
||||
src, err := readFile(q.Build, file, buf)
|
||||
<-sema // release token
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Fast path: If the object's name isn't present anywhere in the source, ignore the file.
|
||||
if !bytes.Contains(src, namebytes) {
|
||||
continue
|
||||
}
|
||||
|
||||
if inQueryPkg {
|
||||
// If we're in the query package, we defer final processing until we have
|
||||
// parsed all of the candidate files in the package.
|
||||
// Best effort; allow errors and use what we can from what remains.
|
||||
f, _ := parser.ParseFile(fset, file, src, parser.AllErrors)
|
||||
if f != nil {
|
||||
deffiles[file] = f
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// We aren't in the query package. Go file by file.
|
||||
|
||||
// Parse out only the imports, to check whether the defining package
|
||||
// was imported, and if so, under what names.
|
||||
// Best effort; allow errors and use what we can from what remains.
|
||||
f, _ := parser.ParseFile(fset, file, src, parser.ImportsOnly|parser.AllErrors)
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// pkgnames is the set of names by which defpkg is imported in this file.
|
||||
// (Multiple imports in the same file are legal but vanishingly rare.)
|
||||
pkgnames := make([]string, 0, 1)
|
||||
var isdotimport bool
|
||||
for _, imp := range f.Imports {
|
||||
path, err := strconv.Unquote(imp.Path.Value)
|
||||
if err != nil || path != defpkg {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case imp.Name == nil:
|
||||
pkgnames = append(pkgnames, defname)
|
||||
case imp.Name.Name == ".":
|
||||
isdotimport = true
|
||||
default:
|
||||
pkgnames = append(pkgnames, imp.Name.Name)
|
||||
}
|
||||
}
|
||||
if len(pkgnames) == 0 && !isdotimport {
|
||||
// Defining package not imported, bail.
|
||||
continue
|
||||
}
|
||||
|
||||
// Re-parse the entire file.
|
||||
// Parse errors are ok; we'll do the best we can with a partial AST, if we have one.
|
||||
f, _ = parser.ParseFile(fset, file, src, parser.AllErrors)
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Walk the AST looking for references.
|
||||
var refs []*ast.Ident
|
||||
ast.Inspect(f, func(n ast.Node) bool {
|
||||
// Check selector expressions.
|
||||
// If the selector matches the target name,
|
||||
// and the expression is one of the names
|
||||
// that the defining package was imported under,
|
||||
// then we have a match.
|
||||
if sel, ok := n.(*ast.SelectorExpr); ok && sel.Sel.Name == name {
|
||||
if id, ok := sel.X.(*ast.Ident); ok {
|
||||
for _, n := range pkgnames {
|
||||
if n == id.Name {
|
||||
refs = append(refs, sel.Sel)
|
||||
// Don't recurse further, to avoid duplicate entries
|
||||
// from the dot import check below.
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Dot imports are special.
|
||||
// Objects imported from the defining package are placed in the package scope.
|
||||
// go/ast does not resolve them to an object.
|
||||
// At all other scopes (file, local), go/ast can do the resolution.
|
||||
// So we're looking for object-free idents with the right name.
|
||||
// The only other way to get something with the right name at the package scope
|
||||
// is to *be* the defining package. We handle that case separately (inQueryPkg).
|
||||
if isdotimport {
|
||||
if id, ok := n.(*ast.Ident); ok && id.Obj == nil && id.Name == name {
|
||||
refs = append(refs, id)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// Emit any references we found.
|
||||
if len(refs) > 0 {
|
||||
q.Output(fset, &referrersPackageResult{
|
||||
pkg: types.NewPackage(pkg.ImportPath, pkg.Name),
|
||||
build: q.Build,
|
||||
fset: fset,
|
||||
refs: refs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// If we're in the query package, we've now collected all the files in the package.
|
||||
// (Or at least the ones that might contain references to the object.)
|
||||
// Find and emit refs.
|
||||
if inQueryPkg {
|
||||
// Bundle the files together into a package.
|
||||
// This does package-level object resolution.
|
||||
qpkg, _ := ast.NewPackage(fset, deffiles, nil, nil)
|
||||
// Look up the query object; we know that it is defined in the package scope.
|
||||
pkgobj := qpkg.Scope.Objects[name]
|
||||
if pkgobj == nil {
|
||||
panic("missing defpkg object for " + defpkg + "." + name)
|
||||
}
|
||||
// Find all references to the query object.
|
||||
var refs []*ast.Ident
|
||||
ast.Inspect(qpkg, func(n ast.Node) bool {
|
||||
if id, ok := n.(*ast.Ident); ok {
|
||||
// Check both that this is a reference to the query object
|
||||
// and that it is not the query object itself;
|
||||
// the query object itself was already emitted.
|
||||
if id.Obj == pkgobj && objpos != fset.Position(id.Pos()) {
|
||||
refs = append(refs, id)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if len(refs) > 0 {
|
||||
q.Output(fset, &referrersPackageResult{
|
||||
pkg: types.NewPackage(pkg.ImportPath, pkg.Name),
|
||||
build: q.Build,
|
||||
fset: fset,
|
||||
refs: refs,
|
||||
})
|
||||
}
|
||||
deffiles = nil // allow GC
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findObject returns the object defined at the specified position.
|
||||
func findObject(fset *token.FileSet, info *types.Info, objposn token.Position) types.Object {
|
||||
good := func(obj types.Object) bool {
|
||||
if obj == nil {
|
||||
return false
|
||||
}
|
||||
posn := fset.Position(obj.Pos())
|
||||
return posn.Filename == objposn.Filename && posn.Offset == objposn.Offset
|
||||
}
|
||||
for _, obj := range info.Defs {
|
||||
if good(obj) {
|
||||
return obj
|
||||
}
|
||||
}
|
||||
for _, obj := range info.Implicits {
|
||||
if good(obj) {
|
||||
return obj
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// same reports whether x and y are identical, or both are PkgNames
|
||||
// that import the same Package.
|
||||
func sameObj(x, y types.Object) bool {
|
||||
if x == y {
|
||||
return true
|
||||
}
|
||||
if x, ok := x.(*types.PkgName); ok {
|
||||
if y, ok := y.(*types.PkgName); ok {
|
||||
return x.Imported() == y.Imported()
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func clearInfoFields(info *loader.PackageInfo) {
|
||||
// TODO(adonovan): opt: save memory by eliminating unneeded scopes/objects.
|
||||
// (Requires go/types change for Go 1.7.)
|
||||
// info.Pkg.Scope().ClearChildren()
|
||||
|
||||
// Discard the file ASTs and their accumulated type
|
||||
// information to save memory.
|
||||
info.Files = nil
|
||||
info.Defs = make(map[*ast.Ident]types.Object)
|
||||
info.Uses = make(map[*ast.Ident]types.Object)
|
||||
info.Implicits = make(map[ast.Node]types.Object)
|
||||
|
||||
// Also, disable future collection of wholly unneeded
|
||||
// type information for the package in case there is
|
||||
// more type-checking to do (augmentation).
|
||||
info.Types = nil
|
||||
info.Scopes = nil
|
||||
info.Selections = nil
|
||||
}
|
||||
|
||||
// -------- utils --------
|
||||
|
||||
// An deterministic ordering for token.Pos that doesn't
|
||||
// depend on the order in which packages were loaded.
|
||||
func lessPos(fset *token.FileSet, x, y token.Pos) bool {
|
||||
fx := fset.File(x)
|
||||
fy := fset.File(y)
|
||||
if fx != fy {
|
||||
return fx.Name() < fy.Name()
|
||||
}
|
||||
return x < y
|
||||
}
|
||||
|
||||
type byNamePos struct {
|
||||
fset *token.FileSet
|
||||
ids []*ast.Ident
|
||||
}
|
||||
|
||||
func (p byNamePos) Len() int { return len(p.ids) }
|
||||
func (p byNamePos) Swap(i, j int) { p.ids[i], p.ids[j] = p.ids[j], p.ids[i] }
|
||||
func (p byNamePos) Less(i, j int) bool {
|
||||
return lessPos(p.fset, p.ids[i].NamePos, p.ids[j].NamePos)
|
||||
}
|
||||
|
||||
// referrersInitialResult is the initial result of a "referrers" query.
|
||||
type referrersInitialResult struct {
|
||||
qinfo *loader.PackageInfo
|
||||
obj types.Object // object it denotes
|
||||
}
|
||||
|
||||
func (r *referrersInitialResult) PrintPlain(printf printfFunc) {
|
||||
printf(r.obj, "references to %s",
|
||||
types.ObjectString(r.obj, types.RelativeTo(r.qinfo.Pkg)))
|
||||
}
|
||||
|
||||
func (r *referrersInitialResult) JSON(fset *token.FileSet) []byte {
|
||||
var objpos string
|
||||
if pos := r.obj.Pos(); pos.IsValid() {
|
||||
objpos = fset.Position(pos).String()
|
||||
}
|
||||
return toJSON(&serial.ReferrersInitial{
|
||||
Desc: r.obj.String(),
|
||||
ObjPos: objpos,
|
||||
})
|
||||
}
|
||||
|
||||
// referrersPackageResult is the streaming result for one package of a "referrers" query.
|
||||
type referrersPackageResult struct {
|
||||
pkg *types.Package
|
||||
build *build.Context
|
||||
fset *token.FileSet
|
||||
refs []*ast.Ident // set of all other references to it
|
||||
}
|
||||
|
||||
// foreachRef calls f(id, text) for id in r.refs, in order.
|
||||
// Text is the text of the line on which id appears.
|
||||
func (r *referrersPackageResult) foreachRef(f func(id *ast.Ident, text string)) {
|
||||
// Show referring lines, like grep.
|
||||
type fileinfo struct {
|
||||
refs []*ast.Ident
|
||||
linenums []int // line number of refs[i]
|
||||
data chan interface{} // file contents or error
|
||||
}
|
||||
var fileinfos []*fileinfo
|
||||
fileinfosByName := make(map[string]*fileinfo)
|
||||
|
||||
// First pass: start the file reads concurrently.
|
||||
sema := make(chan struct{}, 20) // counting semaphore to limit I/O concurrency
|
||||
for _, ref := range r.refs {
|
||||
posn := r.fset.Position(ref.Pos())
|
||||
fi := fileinfosByName[posn.Filename]
|
||||
if fi == nil {
|
||||
fi = &fileinfo{data: make(chan interface{})}
|
||||
fileinfosByName[posn.Filename] = fi
|
||||
fileinfos = append(fileinfos, fi)
|
||||
|
||||
// First request for this file:
|
||||
// start asynchronous read.
|
||||
go func() {
|
||||
sema <- struct{}{} // acquire token
|
||||
content, err := readFile(r.build, posn.Filename, nil)
|
||||
<-sema // release token
|
||||
if err != nil {
|
||||
fi.data <- err
|
||||
} else {
|
||||
fi.data <- content
|
||||
}
|
||||
}()
|
||||
}
|
||||
fi.refs = append(fi.refs, ref)
|
||||
fi.linenums = append(fi.linenums, posn.Line)
|
||||
}
|
||||
|
||||
// Second pass: print refs in original order.
|
||||
// One line may have several refs at different columns.
|
||||
for _, fi := range fileinfos {
|
||||
v := <-fi.data // wait for I/O completion
|
||||
|
||||
// Print one item for all refs in a file that could not
|
||||
// be loaded (perhaps due to //line directives).
|
||||
if err, ok := v.(error); ok {
|
||||
var suffix string
|
||||
if more := len(fi.refs) - 1; more > 0 {
|
||||
suffix = fmt.Sprintf(" (+ %d more refs in this file)", more)
|
||||
}
|
||||
f(fi.refs[0], err.Error()+suffix)
|
||||
continue
|
||||
}
|
||||
|
||||
lines := bytes.Split(v.([]byte), []byte("\n"))
|
||||
for i, ref := range fi.refs {
|
||||
f(ref, string(lines[fi.linenums[i]-1]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readFile is like os.ReadFile, but
|
||||
// it goes through the virtualized build.Context.
|
||||
// If non-nil, buf must have been reset.
|
||||
func readFile(ctxt *build.Context, filename string, buf *bytes.Buffer) ([]byte, error) {
|
||||
rc, err := buildutil.OpenFile(ctxt, filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rc.Close()
|
||||
if buf == nil {
|
||||
buf = new(bytes.Buffer)
|
||||
}
|
||||
if _, err := io.Copy(buf, rc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (r *referrersPackageResult) PrintPlain(printf printfFunc) {
|
||||
r.foreachRef(func(id *ast.Ident, text string) {
|
||||
printf(id, "%s", text)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *referrersPackageResult) JSON(fset *token.FileSet) []byte {
|
||||
refs := serial.ReferrersPackage{Package: r.pkg.Path()}
|
||||
r.foreachRef(func(id *ast.Ident, text string) {
|
||||
refs.Refs = append(refs.Refs, serial.Ref{
|
||||
Pos: fset.Position(id.NamePos).String(),
|
||||
Text: text,
|
||||
})
|
||||
})
|
||||
return toJSON(refs)
|
||||
}
|
|
@ -1,251 +0,0 @@
|
|||
// Copyright 2013 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 serial defines the guru's schema for -json output.
|
||||
//
|
||||
// The output of a guru query is a stream of one or more JSON objects.
|
||||
// This table shows the types of objects in the result stream for each
|
||||
// query type.
|
||||
//
|
||||
// Query Result stream
|
||||
// ----- -------------
|
||||
// definition Definition
|
||||
// describe Describe
|
||||
// freevars FreeVar ...
|
||||
// implements Implements
|
||||
// referrers ReferrersInitial ReferrersPackage ...
|
||||
// what What
|
||||
//
|
||||
// All 'pos' strings in the output are of the form "file:line:col",
|
||||
// where line is the 1-based line number and col is the 1-based byte index.
|
||||
package serial
|
||||
|
||||
// A Peers is the result of a 'peers' query.
|
||||
// If Allocs is empty, the selected channel can't point to anything.
|
||||
type Peers struct {
|
||||
Pos string `json:"pos"` // location of the selected channel op (<-)
|
||||
Type string `json:"type"` // type of the selected channel
|
||||
Allocs []string `json:"allocs,omitempty"` // locations of aliased make(chan) ops
|
||||
Sends []string `json:"sends,omitempty"` // locations of aliased ch<-x ops
|
||||
Receives []string `json:"receives,omitempty"` // locations of aliased <-ch ops
|
||||
Closes []string `json:"closes,omitempty"` // locations of aliased close(ch) ops
|
||||
}
|
||||
|
||||
// A "referrers" query emits a ReferrersInitial object followed by zero or
|
||||
// more ReferrersPackage objects, one per package that contains a reference.
|
||||
type (
|
||||
ReferrersInitial struct {
|
||||
ObjPos string `json:"objpos,omitempty"` // location of the definition
|
||||
Desc string `json:"desc"` // description of the denoted object
|
||||
}
|
||||
ReferrersPackage struct {
|
||||
Package string `json:"package"`
|
||||
Refs []Ref `json:"refs"` // non-empty list of references within this package
|
||||
}
|
||||
Ref struct {
|
||||
Pos string `json:"pos"` // location of all references
|
||||
Text string `json:"text"` // text of the referring line
|
||||
}
|
||||
)
|
||||
|
||||
// A Definition is the result of a 'definition' query.
|
||||
type Definition struct {
|
||||
ObjPos string `json:"objpos,omitempty"` // location of the definition
|
||||
Desc string `json:"desc"` // description of the denoted object
|
||||
}
|
||||
|
||||
// A Callees is the result of a 'callees' query.
|
||||
//
|
||||
// Callees is nonempty unless the call was a dynamic call on a
|
||||
// provably nil func or interface value.
|
||||
type (
|
||||
Callees struct {
|
||||
Pos string `json:"pos"` // location of selected call site
|
||||
Desc string `json:"desc"` // description of call site
|
||||
Callees []*Callee `json:"callees"`
|
||||
}
|
||||
Callee struct {
|
||||
Name string `json:"name"` // full name of called function
|
||||
Pos string `json:"pos"` // location of called function
|
||||
}
|
||||
)
|
||||
|
||||
// A Caller is one element of the slice returned by a 'callers' query.
|
||||
// (Callstack also contains a similar slice.)
|
||||
//
|
||||
// The root of the callgraph has an unspecified "Caller" string.
|
||||
type Caller struct {
|
||||
Pos string `json:"pos,omitempty"` // location of the calling function
|
||||
Desc string `json:"desc"` // description of call site
|
||||
Caller string `json:"caller"` // full name of calling function
|
||||
}
|
||||
|
||||
// A CallStack is the result of a 'callstack' query.
|
||||
// It indicates an arbitrary path from the root of the callgraph to
|
||||
// the query function.
|
||||
//
|
||||
// If the Callers slice is empty, the function was unreachable in this
|
||||
// analysis scope.
|
||||
type CallStack struct {
|
||||
Pos string `json:"pos"` // location of the selected function
|
||||
Target string `json:"target"` // the selected function
|
||||
Callers []Caller `json:"callers"` // enclosing calls, innermost first.
|
||||
}
|
||||
|
||||
// A FreeVar is one element of the slice returned by a 'freevars'
|
||||
// query. Each one identifies an expression referencing a local
|
||||
// identifier defined outside the selected region.
|
||||
type FreeVar struct {
|
||||
Pos string `json:"pos"` // location of the identifier's definition
|
||||
Kind string `json:"kind"` // one of {var,func,type,const,label}
|
||||
Ref string `json:"ref"` // referring expression (e.g. "x" or "x.y.z")
|
||||
Type string `json:"type"` // type of the expression
|
||||
}
|
||||
|
||||
// An Implements contains the result of an 'implements' query.
|
||||
// It describes the queried type, the set of named non-empty interface
|
||||
// types to which it is assignable, and the set of named/*named types
|
||||
// (concrete or non-empty interface) which may be assigned to it.
|
||||
type Implements struct {
|
||||
T ImplementsType `json:"type,omitempty"` // the queried type
|
||||
AssignableTo []ImplementsType `json:"to,omitempty"` // types assignable to T
|
||||
AssignableFrom []ImplementsType `json:"from,omitempty"` // interface types assignable from T
|
||||
AssignableFromPtr []ImplementsType `json:"fromptr,omitempty"` // interface types assignable only from *T
|
||||
|
||||
// The following fields are set only if the query was a method.
|
||||
// Assignable{To,From,FromPtr}Method[i] is the corresponding
|
||||
// method of type Assignable{To,From,FromPtr}[i], or blank
|
||||
// {"",""} if that type lacks the method.
|
||||
Method *DescribeMethod `json:"method,omitempty"` // the queried method
|
||||
AssignableToMethod []DescribeMethod `json:"to_method,omitempty"`
|
||||
AssignableFromMethod []DescribeMethod `json:"from_method,omitempty"`
|
||||
AssignableFromPtrMethod []DescribeMethod `json:"fromptr_method,omitempty"`
|
||||
}
|
||||
|
||||
// An ImplementsType describes a single type as part of an 'implements' query.
|
||||
type ImplementsType struct {
|
||||
Name string `json:"name"` // full name of the type
|
||||
Pos string `json:"pos"` // location of its definition
|
||||
Kind string `json:"kind"` // "basic", "array", etc
|
||||
}
|
||||
|
||||
// A SyntaxNode is one element of a stack of enclosing syntax nodes in
|
||||
// a "what" query.
|
||||
type SyntaxNode struct {
|
||||
Description string `json:"desc"` // description of syntax tree
|
||||
Start int `json:"start"` // start byte offset, 0-based
|
||||
End int `json:"end"` // end byte offset
|
||||
}
|
||||
|
||||
// A What is the result of the "what" query, which quickly identifies
|
||||
// the selection, parsing only a single file. It is intended for use
|
||||
// in low-latency GUIs.
|
||||
type What struct {
|
||||
Enclosing []SyntaxNode `json:"enclosing"` // enclosing nodes of syntax tree
|
||||
Modes []string `json:"modes"` // query modes enabled for this selection.
|
||||
SrcDir string `json:"srcdir,omitempty"` // $GOROOT src directory containing queried package
|
||||
ImportPath string `json:"importpath,omitempty"` // import path of queried package
|
||||
Object string `json:"object,omitempty"` // name of identified object, if any
|
||||
SameIDs []string `json:"sameids,omitempty"` // locations of references to same object
|
||||
}
|
||||
|
||||
// A PointsToLabel describes a pointer analysis label.
|
||||
//
|
||||
// A "label" is an object that may be pointed to by a pointer, map,
|
||||
// channel, 'func', slice or interface. Labels include:
|
||||
// - functions
|
||||
// - globals
|
||||
// - arrays created by literals (e.g. []byte("foo")) and conversions ([]byte(s))
|
||||
// - stack- and heap-allocated variables (including composite literals)
|
||||
// - arrays allocated by append()
|
||||
// - channels, maps and arrays created by make()
|
||||
// - and their subelements, e.g. "alloc.y[*].z"
|
||||
type PointsToLabel struct {
|
||||
Pos string `json:"pos"` // location of syntax that allocated the object
|
||||
Desc string `json:"desc"` // description of the label
|
||||
}
|
||||
|
||||
// A PointsTo is one element of the result of a 'pointsto' query on an
|
||||
// expression. It describes a single pointer: its type and the set of
|
||||
// "labels" it points to.
|
||||
//
|
||||
// If the pointer is of interface type, it will have one PTS entry
|
||||
// describing each concrete type that it may contain. For each
|
||||
// concrete type that is a pointer, the PTS entry describes the labels
|
||||
// it may point to. The same is true for reflect.Values, except the
|
||||
// dynamic types needn't be concrete.
|
||||
type PointsTo struct {
|
||||
Type string `json:"type"` // (concrete) type of the pointer
|
||||
NamePos string `json:"namepos,omitempty"` // location of type defn, if Named
|
||||
Labels []PointsToLabel `json:"labels,omitempty"` // pointed-to objects
|
||||
}
|
||||
|
||||
// A DescribeValue is the additional result of a 'describe' query
|
||||
// if the selection indicates a value or expression.
|
||||
type DescribeValue struct {
|
||||
Type string `json:"type"` // type of the expression
|
||||
Value string `json:"value,omitempty"` // value of the expression, if constant
|
||||
ObjPos string `json:"objpos,omitempty"` // location of the definition, if an Ident
|
||||
TypesPos []Definition `json:"typespos,omitempty"` // location of the named types, that type consist of
|
||||
}
|
||||
|
||||
type DescribeMethod struct {
|
||||
Name string `json:"name"` // method name, as defined by types.Selection.String()
|
||||
Pos string `json:"pos"` // location of the method's definition
|
||||
}
|
||||
|
||||
// A DescribeType is the additional result of a 'describe' query
|
||||
// if the selection indicates a type.
|
||||
type DescribeType struct {
|
||||
Type string `json:"type"` // the string form of the type
|
||||
NamePos string `json:"namepos,omitempty"` // location of definition of type, if named
|
||||
NameDef string `json:"namedef,omitempty"` // underlying definition of type, if named
|
||||
Methods []DescribeMethod `json:"methods,omitempty"` // methods of the type
|
||||
}
|
||||
|
||||
type DescribeMember struct {
|
||||
Name string `json:"name"` // name of member
|
||||
Type string `json:"type,omitempty"` // type of member (underlying, if 'type')
|
||||
Value string `json:"value,omitempty"` // value of member (if 'const')
|
||||
Pos string `json:"pos"` // location of definition of member
|
||||
Kind string `json:"kind"` // one of {var,const,func,type}
|
||||
Methods []DescribeMethod `json:"methods,omitempty"` // methods (if member is a type)
|
||||
}
|
||||
|
||||
// A DescribePackage is the additional result of a 'describe' if
|
||||
// the selection indicates a package.
|
||||
type DescribePackage struct {
|
||||
Path string `json:"path"` // import path of the package
|
||||
Members []*DescribeMember `json:"members,omitempty"` // accessible members of the package
|
||||
}
|
||||
|
||||
// A Describe is the result of a 'describe' query.
|
||||
// It may contain an element describing the selected semantic entity
|
||||
// in detail.
|
||||
type Describe struct {
|
||||
Desc string `json:"desc"` // description of the selected syntax node
|
||||
Pos string `json:"pos"` // location of the selected syntax node
|
||||
Detail string `json:"detail,omitempty"` // one of {package, type, value}, or "".
|
||||
|
||||
// At most one of the following fields is populated:
|
||||
// the one specified by 'detail'.
|
||||
Package *DescribePackage `json:"package,omitempty"`
|
||||
Type *DescribeType `json:"type,omitempty"`
|
||||
Value *DescribeValue `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// A WhichErrs is the result of a 'whicherrs' query.
|
||||
// It contains the position of the queried error and the possible globals,
|
||||
// constants, and types it may point to.
|
||||
type WhichErrs struct {
|
||||
ErrPos string `json:"errpos,omitempty"` // location of queried error
|
||||
Globals []string `json:"globals,omitempty"` // locations of globals
|
||||
Constants []string `json:"constants,omitempty"` // locations of constants
|
||||
Types []WhichErrsType `json:"types,omitempty"` // Types
|
||||
}
|
||||
|
||||
type WhichErrsType struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Position string `json:"position,omitempty"`
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
This is not a Go source file.
|
||||
Used by TestIssue14684.
|
|
@ -1,23 +0,0 @@
|
|||
// Tests of Go 1.9 type aliases.
|
||||
// See go.tools/guru/guru_test.go for explanation.
|
||||
// See alias.golden for expected query results.
|
||||
|
||||
package alias // @describe describe-pkg "alias"
|
||||
|
||||
type I interface { // @implements implements-I "I"
|
||||
f()
|
||||
}
|
||||
|
||||
type N int
|
||||
|
||||
func (N) f() {}
|
||||
|
||||
type M = N // @describe describe-def-M "M"
|
||||
var m M // @describe describe-ref-M "M"
|
||||
|
||||
type O N // @describe describe-O "O"
|
||||
|
||||
type P = struct{ N } // @describe describe-P "N"
|
||||
|
||||
type U = undefined // @describe describe-U "U"
|
||||
type _ = undefined // @describe describe-undefined "undefined"
|
|
@ -1,47 +0,0 @@
|
|||
-------- @describe describe-pkg --------
|
||||
definition of package "alias"
|
||||
type I interface{f()}
|
||||
method (I) f()
|
||||
type M = N
|
||||
method (N) f()
|
||||
type N int
|
||||
method (N) f()
|
||||
type O int
|
||||
type P = struct{N}
|
||||
method (struct{N}) f()
|
||||
type U = invalid type
|
||||
var m N
|
||||
|
||||
-------- @implements implements-I --------
|
||||
interface type I
|
||||
is implemented by basic type N
|
||||
|
||||
-------- @describe describe-def-M --------
|
||||
alias of type N (size 8, align 8)
|
||||
defined as int
|
||||
Methods:
|
||||
method (N) f()
|
||||
|
||||
-------- @describe describe-ref-M --------
|
||||
alias of type N (size 8, align 8)
|
||||
defined as int
|
||||
Methods:
|
||||
method (N) f()
|
||||
|
||||
-------- @describe describe-O --------
|
||||
definition of type O (size 8, align 8)
|
||||
No methods.
|
||||
|
||||
-------- @describe describe-P --------
|
||||
type struct{N} (size 8, align 8)
|
||||
Methods:
|
||||
method (struct{N}) f()
|
||||
Fields:
|
||||
N N
|
||||
|
||||
-------- @describe describe-U --------
|
||||
alias of type invalid type
|
||||
|
||||
-------- @describe describe-undefined --------
|
||||
identifier
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
package definition
|
||||
|
||||
// Tests of 'definition' query, -json output.
|
||||
// See golang.org/x/tools/cmd/guru/guru_test.go for explanation.
|
||||
// See main.golden for expected query results.
|
||||
|
||||
// TODO(adonovan): test: selection of member of same package defined in another file.
|
||||
|
||||
import (
|
||||
"lib"
|
||||
lib2 "lib"
|
||||
"nosuchpkg"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var _ int // @definition builtin "int"
|
||||
|
||||
var _ undef // @definition lexical-undef "undef"
|
||||
var x lib.T // @definition lexical-pkgname "lib"
|
||||
f() // @definition lexical-func "f"
|
||||
print(x) // @definition lexical-var "x"
|
||||
if x := ""; x == "" { // @definition lexical-shadowing "x"
|
||||
}
|
||||
|
||||
var _ lib.Type // @definition qualified-type "Type"
|
||||
var _ lib.Func // @definition qualified-func "Func"
|
||||
var _ lib.Var // @definition qualified-var "Var"
|
||||
var _ lib.Const // @definition qualified-const "Const"
|
||||
var _ lib2.Type // @definition qualified-type-renaming "Type"
|
||||
var _ lib.Nonesuch // @definition qualified-nomember "Nonesuch"
|
||||
var _ nosuchpkg.T // @definition qualified-nopkg "nosuchpkg"
|
||||
|
||||
var u U
|
||||
print(u.field) // @definition select-field "field"
|
||||
u.method() // @definition select-method "method"
|
||||
}
|
||||
|
||||
func f()
|
||||
|
||||
type T struct{ field int }
|
||||
|
||||
func (T) method()
|
||||
|
||||
type U struct{ T }
|
||||
|
||||
type V1 struct {
|
||||
W // @definition embedded-other-file "W"
|
||||
}
|
||||
|
||||
type V2 struct {
|
||||
*W // @definition embedded-other-file-pointer "W"
|
||||
}
|
||||
|
||||
type V3 struct {
|
||||
int // @definition embedded-basic "int"
|
||||
}
|
||||
|
||||
type V4 struct {
|
||||
*int // @definition embedded-basic-pointer "int"
|
||||
}
|
||||
|
||||
type V5 struct {
|
||||
lib.Type // @definition embedded-other-pkg "Type"
|
||||
}
|
||||
|
||||
type V6 struct {
|
||||
T // @definition embedded-same-file "T"
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
-------- @definition builtin --------
|
||||
|
||||
Error: int is built in
|
||||
-------- @definition lexical-undef --------
|
||||
|
||||
Error: no object for identifier
|
||||
-------- @definition lexical-pkgname --------
|
||||
{
|
||||
"objpos": "testdata/src/definition-json/main.go:10:2",
|
||||
"desc": "package lib"
|
||||
}
|
||||
-------- @definition lexical-func --------
|
||||
{
|
||||
"objpos": "$GOPATH/src/definition-json/main.go:38:6",
|
||||
"desc": "func f"
|
||||
}
|
||||
-------- @definition lexical-var --------
|
||||
{
|
||||
"objpos": "$GOPATH/src/definition-json/main.go:19:6",
|
||||
"desc": "var x"
|
||||
}
|
||||
-------- @definition lexical-shadowing --------
|
||||
{
|
||||
"objpos": "$GOPATH/src/definition-json/main.go:22:5",
|
||||
"desc": "var x"
|
||||
}
|
||||
-------- @definition qualified-type --------
|
||||
{
|
||||
"objpos": "testdata/src/lib/lib.go:3:6",
|
||||
"desc": "type lib.Type"
|
||||
}
|
||||
-------- @definition qualified-func --------
|
||||
{
|
||||
"objpos": "testdata/src/lib/lib.go:9:6",
|
||||
"desc": "func lib.Func"
|
||||
}
|
||||
-------- @definition qualified-var --------
|
||||
{
|
||||
"objpos": "testdata/src/lib/lib.go:14:5",
|
||||
"desc": "var lib.Var"
|
||||
}
|
||||
-------- @definition qualified-const --------
|
||||
{
|
||||
"objpos": "testdata/src/lib/lib.go:12:7",
|
||||
"desc": "const lib.Const"
|
||||
}
|
||||
-------- @definition qualified-type-renaming --------
|
||||
{
|
||||
"objpos": "testdata/src/lib/lib.go:3:6",
|
||||
"desc": "type lib.Type"
|
||||
}
|
||||
-------- @definition qualified-nomember --------
|
||||
|
||||
Error: couldn't find declaration of Nonesuch in "lib"
|
||||
-------- @definition qualified-nopkg --------
|
||||
{
|
||||
"objpos": "testdata/src/definition-json/main.go:12:2",
|
||||
"desc": "package nosuchpkg"
|
||||
}
|
||||
-------- @definition select-field --------
|
||||
{
|
||||
"objpos": "testdata/src/definition-json/main.go:40:16",
|
||||
"desc": "field field int"
|
||||
}
|
||||
-------- @definition select-method --------
|
||||
{
|
||||
"objpos": "testdata/src/definition-json/main.go:42:10",
|
||||
"desc": "func (T).method()"
|
||||
}
|
||||
-------- @definition embedded-other-file --------
|
||||
{
|
||||
"objpos": "testdata/src/definition-json/type.go:3:6",
|
||||
"desc": "type W int"
|
||||
}
|
||||
-------- @definition embedded-other-file-pointer --------
|
||||
{
|
||||
"objpos": "testdata/src/definition-json/type.go:3:6",
|
||||
"desc": "type W int"
|
||||
}
|
||||
-------- @definition embedded-basic --------
|
||||
|
||||
Error: int is built in
|
||||
-------- @definition embedded-basic-pointer --------
|
||||
|
||||
Error: int is built in
|
||||
-------- @definition embedded-other-pkg --------
|
||||
{
|
||||
"objpos": "testdata/src/lib/lib.go:3:6",
|
||||
"desc": "type lib.Type"
|
||||
}
|
||||
-------- @definition embedded-same-file --------
|
||||
{
|
||||
"objpos": "$GOPATH/src/definition-json/main.go:40:6",
|
||||
"desc": "type T"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
package definition
|
||||
|
||||
type W int
|
|
@ -1,29 +0,0 @@
|
|||
package describe // @describe pkgdecl "describe"
|
||||
|
||||
// Tests of 'describe' query, -format=json.
|
||||
// See go.tools/guru/guru_test.go for explanation.
|
||||
// See describe-json.golden for expected query results.
|
||||
|
||||
func main() {
|
||||
var s struct{ x [3]int }
|
||||
p := &s.x[0] // @describe desc-val-p "p"
|
||||
_ = p
|
||||
|
||||
var i I = C(0)
|
||||
if i == nil {
|
||||
i = new(D)
|
||||
}
|
||||
print(i) // @describe desc-val-i "\\bi\\b"
|
||||
|
||||
go main() // @describe desc-stmt "go"
|
||||
}
|
||||
|
||||
type I interface {
|
||||
f()
|
||||
}
|
||||
|
||||
type C int // @describe desc-type-C "C"
|
||||
type D struct{}
|
||||
|
||||
func (c C) f() {} // @describe desc-param-c "\\bc\\b"
|
||||
func (d *D) f() {} // @describe desc-param-d "\\bd\\b"
|
|
@ -1,134 +0,0 @@
|
|||
-------- @describe pkgdecl --------
|
||||
{
|
||||
"desc": "definition of package \"describe-json\"",
|
||||
"pos": "testdata/src/describe-json/main.go:1:9",
|
||||
"detail": "package",
|
||||
"package": {
|
||||
"path": "describe-json",
|
||||
"members": [
|
||||
{
|
||||
"name": "C",
|
||||
"type": "int",
|
||||
"pos": "testdata/src/describe-json/main.go:25:6",
|
||||
"kind": "type",
|
||||
"methods": [
|
||||
{
|
||||
"name": "method (C) f()",
|
||||
"pos": "testdata/src/describe-json/main.go:28:12"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "D",
|
||||
"type": "struct{}",
|
||||
"pos": "testdata/src/describe-json/main.go:26:6",
|
||||
"kind": "type",
|
||||
"methods": [
|
||||
{
|
||||
"name": "method (*D) f()",
|
||||
"pos": "testdata/src/describe-json/main.go:29:13"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "I",
|
||||
"type": "interface{f()}",
|
||||
"pos": "testdata/src/describe-json/main.go:21:6",
|
||||
"kind": "type",
|
||||
"methods": [
|
||||
{
|
||||
"name": "method (I) f()",
|
||||
"pos": "testdata/src/describe-json/main.go:22:2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "main",
|
||||
"type": "func()",
|
||||
"pos": "testdata/src/describe-json/main.go:7:6",
|
||||
"kind": "func"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @describe desc-val-p --------
|
||||
{
|
||||
"desc": "identifier",
|
||||
"pos": "testdata/src/describe-json/main.go:9:2",
|
||||
"detail": "value",
|
||||
"value": {
|
||||
"type": "*int",
|
||||
"objpos": "testdata/src/describe-json/main.go:9:2"
|
||||
}
|
||||
}
|
||||
-------- @describe desc-val-i --------
|
||||
{
|
||||
"desc": "identifier",
|
||||
"pos": "testdata/src/describe-json/main.go:16:8",
|
||||
"detail": "value",
|
||||
"value": {
|
||||
"type": "I",
|
||||
"objpos": "testdata/src/describe-json/main.go:12:6",
|
||||
"typespos": [
|
||||
{
|
||||
"objpos": "testdata/src/describe-json/main.go:21:6",
|
||||
"desc": "I"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @describe desc-stmt --------
|
||||
{
|
||||
"desc": "go statement",
|
||||
"pos": "testdata/src/describe-json/main.go:18:2",
|
||||
"detail": "unknown"
|
||||
}
|
||||
-------- @describe desc-type-C --------
|
||||
{
|
||||
"desc": "definition of type C (size 8, align 8)",
|
||||
"pos": "testdata/src/describe-json/main.go:25:6",
|
||||
"detail": "type",
|
||||
"type": {
|
||||
"type": "C",
|
||||
"namepos": "testdata/src/describe-json/main.go:25:6",
|
||||
"namedef": "int",
|
||||
"methods": [
|
||||
{
|
||||
"name": "method (C) f()",
|
||||
"pos": "testdata/src/describe-json/main.go:28:12"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @describe desc-param-c --------
|
||||
{
|
||||
"desc": "identifier",
|
||||
"pos": "testdata/src/describe-json/main.go:28:7",
|
||||
"detail": "value",
|
||||
"value": {
|
||||
"type": "C",
|
||||
"objpos": "testdata/src/describe-json/main.go:28:7",
|
||||
"typespos": [
|
||||
{
|
||||
"objpos": "testdata/src/describe-json/main.go:25:6",
|
||||
"desc": "C"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-------- @describe desc-param-d --------
|
||||
{
|
||||
"desc": "identifier",
|
||||
"pos": "testdata/src/describe-json/main.go:29:7",
|
||||
"detail": "value",
|
||||
"value": {
|
||||
"type": "*D",
|
||||
"objpos": "testdata/src/describe-json/main.go:29:7",
|
||||
"typespos": [
|
||||
{
|
||||
"objpos": "testdata/src/describe-json/main.go:26:6",
|
||||
"desc": "D"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
package describe // @describe pkgdecl "describe"
|
||||
|
||||
// Tests of 'describe' query.
|
||||
// See go.tools/guru/guru_test.go for explanation.
|
||||
// See describe.golden for expected query results.
|
||||
|
||||
// TODO(adonovan): more coverage of the (extensive) logic.
|
||||
|
||||
import (
|
||||
"lib"
|
||||
"nosuchpkg" // @describe badimport1 "nosuchpkg"
|
||||
nosuchpkg2 "nosuchpkg" // @describe badimport2 "nosuchpkg2"
|
||||
// The unsafe package changed in Go 1.17 with the addition of
|
||||
// unsafe.Add and unsafe.Slice. While we still support older versions
|
||||
// of Go, the test case below cannot be enabled.
|
||||
// _ "unsafe" // @describe unsafe "unsafe"
|
||||
)
|
||||
|
||||
var _ nosuchpkg.T
|
||||
var _ nosuchpkg2.T
|
||||
|
||||
type cake float64 // @describe type-ref-builtin "float64"
|
||||
|
||||
const c = iota // @describe const-ref-iota "iota"
|
||||
|
||||
const pi = 3.141 // @describe const-def-pi "pi"
|
||||
const pie = cake(pi) // @describe const-def-pie "pie"
|
||||
const _ = pi // @describe const-ref-pi "pi"
|
||||
|
||||
var global = new(string) // NB: ssa.Global is indirect, i.e. **string
|
||||
|
||||
func main() { // @describe func-def-main "main"
|
||||
// func objects
|
||||
_ = main // @describe func-ref-main "main"
|
||||
_ = (*C).f // @describe func-ref-*C.f "..C..f"
|
||||
_ = D.f // @describe func-ref-D.f "D.f"
|
||||
_ = I.f // @describe func-ref-I.f "I.f"
|
||||
var d D // @describe type-D "D"
|
||||
var i I // @describe type-I "I"
|
||||
_ = d.f // @describe func-ref-d.f "d.f"
|
||||
_ = i.f // @describe func-ref-i.f "i.f"
|
||||
var slice []D // @describe slice-of-D "slice"
|
||||
|
||||
var dptr *D // @describe ptr-with-nonptr-methods "dptr"
|
||||
_ = dptr
|
||||
|
||||
// var objects
|
||||
anon := func() {
|
||||
_ = d // @describe ref-lexical-d "d"
|
||||
}
|
||||
_ = anon // @describe ref-anon "anon"
|
||||
_ = global // @describe ref-global "global"
|
||||
|
||||
// SSA affords some local flow sensitivity.
|
||||
var a, b int
|
||||
var x = &a // @describe var-def-x-1 "x"
|
||||
_ = x // @describe var-ref-x-1 "x"
|
||||
x = &b // @describe var-def-x-2 "x"
|
||||
_ = x // @describe var-ref-x-2 "x"
|
||||
|
||||
i = new(C) // @describe var-ref-i-C "i"
|
||||
if i != nil {
|
||||
i = D{} // @describe var-ref-i-D "i"
|
||||
}
|
||||
print(i) // @describe var-ref-i "\\bi\\b"
|
||||
|
||||
// const objects
|
||||
const localpi = 3.141 // @describe const-local-pi "localpi"
|
||||
const localpie = cake(pi) // @describe const-local-pie "localpie"
|
||||
const _ = localpi // @describe const-ref-localpi "localpi"
|
||||
|
||||
// type objects
|
||||
type T int // @describe type-def-T "T"
|
||||
var three T = 3 // @describe type-ref-T "T"
|
||||
_ = three
|
||||
|
||||
print(1 + 2*3) // @describe const-expr " 2.3"
|
||||
print(real(1+2i) - 3) // @describe const-expr2 "real.*3"
|
||||
|
||||
m := map[string]*int{"a": &a}
|
||||
mapval, _ := m["a"] // @describe map-lookup,ok "m..a.."
|
||||
_ = mapval // @describe mapval "mapval"
|
||||
_ = m // @describe m "m"
|
||||
|
||||
defer main() // @describe defer-stmt "defer"
|
||||
go main() // @describe go-stmt "go"
|
||||
|
||||
panic(3) // @describe builtin-ref-panic "panic"
|
||||
|
||||
var a2 int // @describe var-decl-stmt "var a2 int"
|
||||
_ = a2
|
||||
var _ int // @describe var-decl-stmt2 "var _ int"
|
||||
var _ int // @describe var-def-blank "_"
|
||||
|
||||
var _ lib.Outer // @describe lib-outer "Outer"
|
||||
|
||||
var mmm map[C]D // @describe var-map-of-C-D "mmm"
|
||||
|
||||
d := newD().ThirdField // @describe field-access "ThirdField"
|
||||
|
||||
astCopy := ast
|
||||
unknown() // @describe call-unknown "\\("
|
||||
}
|
||||
|
||||
type I interface { // @describe def-iface-I "I"
|
||||
f() // @describe def-imethod-I.f "f"
|
||||
}
|
||||
|
||||
type C int
|
||||
type D struct {
|
||||
Field int
|
||||
AnotherField string
|
||||
ThirdField C
|
||||
}
|
||||
|
||||
func (c *C) f() {}
|
||||
func (d D) f() {}
|
||||
|
||||
func newD() D { return D{} }
|
|
@ -1,248 +0,0 @@
|
|||
-------- @describe pkgdecl --------
|
||||
definition of package "describe"
|
||||
type C int
|
||||
method (*C) f()
|
||||
type D struct{...}
|
||||
method (D) f()
|
||||
type I interface{f()}
|
||||
method (I) f()
|
||||
const c untyped int = 0
|
||||
type cake float64
|
||||
var global *string
|
||||
func main func()
|
||||
func newD func() D
|
||||
const pi untyped float = 3.141
|
||||
const pie cake = 3.141
|
||||
|
||||
-------- @describe badimport1 --------
|
||||
import of package "nosuchpkg"
|
||||
|
||||
-------- @describe badimport2 --------
|
||||
reference to package "nosuchpkg"
|
||||
|
||||
-------- @describe type-ref-builtin --------
|
||||
reference to built-in type float64
|
||||
|
||||
-------- @describe const-ref-iota --------
|
||||
reference to const iota untyped int of value 0
|
||||
|
||||
-------- @describe const-def-pi --------
|
||||
definition of const pi untyped float of value 3.141
|
||||
|
||||
-------- @describe const-def-pie --------
|
||||
definition of const pie cake of value 3.141
|
||||
Named types:
|
||||
type cake defined here
|
||||
|
||||
-------- @describe const-ref-pi --------
|
||||
reference to const pi untyped float of value 3.141
|
||||
defined here
|
||||
|
||||
-------- @describe func-def-main --------
|
||||
definition of func main()
|
||||
|
||||
-------- @describe func-ref-main --------
|
||||
reference to func main()
|
||||
defined here
|
||||
|
||||
-------- @describe func-ref-*C.f --------
|
||||
reference to method func (*C).f()
|
||||
defined here
|
||||
|
||||
-------- @describe func-ref-D.f --------
|
||||
reference to method func (D).f()
|
||||
defined here
|
||||
|
||||
-------- @describe func-ref-I.f --------
|
||||
reference to interface method func (I).f()
|
||||
defined here
|
||||
|
||||
-------- @describe type-D --------
|
||||
reference to type D (size 32, align 8)
|
||||
defined as struct{Field int; AnotherField string; ThirdField C}
|
||||
Methods:
|
||||
method (D) f()
|
||||
Fields:
|
||||
Field int
|
||||
AnotherField string
|
||||
ThirdField C
|
||||
|
||||
-------- @describe type-I --------
|
||||
reference to type I (size 16, align 8)
|
||||
defined as interface{f()}
|
||||
Methods:
|
||||
method (I) f()
|
||||
|
||||
-------- @describe func-ref-d.f --------
|
||||
reference to method func (D).f()
|
||||
defined here
|
||||
|
||||
-------- @describe func-ref-i.f --------
|
||||
reference to interface method func (I).f()
|
||||
defined here
|
||||
|
||||
-------- @describe slice-of-D --------
|
||||
definition of var slice []D
|
||||
Named types:
|
||||
type D defined here
|
||||
|
||||
-------- @describe ptr-with-nonptr-methods --------
|
||||
definition of var dptr *D
|
||||
Methods:
|
||||
method (*D) f()
|
||||
Fields:
|
||||
Field int
|
||||
AnotherField string
|
||||
ThirdField C
|
||||
Named types:
|
||||
type D defined here
|
||||
|
||||
-------- @describe ref-lexical-d --------
|
||||
reference to var d D
|
||||
defined here
|
||||
Methods:
|
||||
method (D) f()
|
||||
Fields:
|
||||
Field int
|
||||
AnotherField string
|
||||
ThirdField C
|
||||
Named types:
|
||||
type D defined here
|
||||
|
||||
-------- @describe ref-anon --------
|
||||
reference to var anon func()
|
||||
defined here
|
||||
|
||||
-------- @describe ref-global --------
|
||||
reference to var global *string
|
||||
defined here
|
||||
|
||||
-------- @describe var-def-x-1 --------
|
||||
definition of var x *int
|
||||
|
||||
-------- @describe var-ref-x-1 --------
|
||||
reference to var x *int
|
||||
defined here
|
||||
|
||||
-------- @describe var-def-x-2 --------
|
||||
reference to var x *int
|
||||
defined here
|
||||
|
||||
-------- @describe var-ref-x-2 --------
|
||||
reference to var x *int
|
||||
defined here
|
||||
|
||||
-------- @describe var-ref-i-C --------
|
||||
reference to var i I
|
||||
defined here
|
||||
Methods:
|
||||
method (I) f()
|
||||
Named types:
|
||||
type I defined here
|
||||
|
||||
-------- @describe var-ref-i-D --------
|
||||
reference to var i I
|
||||
defined here
|
||||
Methods:
|
||||
method (I) f()
|
||||
Named types:
|
||||
type I defined here
|
||||
|
||||
-------- @describe var-ref-i --------
|
||||
reference to var i I
|
||||
defined here
|
||||
Methods:
|
||||
method (I) f()
|
||||
Named types:
|
||||
type I defined here
|
||||
|
||||
-------- @describe const-local-pi --------
|
||||
definition of const localpi untyped float of value 3.141
|
||||
|
||||
-------- @describe const-local-pie --------
|
||||
definition of const localpie cake of value 3.141
|
||||
Named types:
|
||||
type cake defined here
|
||||
|
||||
-------- @describe const-ref-localpi --------
|
||||
reference to const localpi untyped float of value 3.141
|
||||
defined here
|
||||
|
||||
-------- @describe type-def-T --------
|
||||
definition of type T (size 8, align 8)
|
||||
No methods.
|
||||
|
||||
-------- @describe type-ref-T --------
|
||||
reference to type T (size 8, align 8)
|
||||
defined as int
|
||||
No methods.
|
||||
|
||||
-------- @describe const-expr --------
|
||||
binary * operation of value 6
|
||||
|
||||
-------- @describe const-expr2 --------
|
||||
binary - operation of value -2
|
||||
|
||||
-------- @describe map-lookup,ok --------
|
||||
index expression of type (*int, bool)
|
||||
|
||||
-------- @describe mapval --------
|
||||
reference to var mapval *int
|
||||
defined here
|
||||
|
||||
-------- @describe m --------
|
||||
reference to var m map[string]*int
|
||||
defined here
|
||||
|
||||
-------- @describe defer-stmt --------
|
||||
defer statement
|
||||
|
||||
-------- @describe go-stmt --------
|
||||
go statement
|
||||
|
||||
-------- @describe builtin-ref-panic --------
|
||||
function call (or conversion) of type ()
|
||||
|
||||
-------- @describe var-decl-stmt --------
|
||||
definition of var a2 int
|
||||
|
||||
-------- @describe var-decl-stmt2 --------
|
||||
definition of var _ int
|
||||
|
||||
-------- @describe var-def-blank --------
|
||||
definition of var _ int
|
||||
|
||||
-------- @describe lib-outer --------
|
||||
reference to type lib.Outer (size 56, align 8)
|
||||
defined as struct{A int; b int; lib.inner}
|
||||
No methods.
|
||||
Fields:
|
||||
A int
|
||||
inner.C bool
|
||||
inner.recursive.E bool
|
||||
|
||||
-------- @describe var-map-of-C-D --------
|
||||
definition of var mmm map[C]D
|
||||
Named types:
|
||||
type C defined here
|
||||
type D defined here
|
||||
|
||||
-------- @describe field-access --------
|
||||
reference to field ThirdField C
|
||||
defined here
|
||||
Methods:
|
||||
method (*C) f()
|
||||
Named types:
|
||||
type C defined here
|
||||
|
||||
-------- @describe call-unknown --------
|
||||
function call of type invalid type
|
||||
|
||||
-------- @describe def-iface-I --------
|
||||
definition of type I (size 16, align 8)
|
||||
Methods:
|
||||
method (I) f()
|
||||
|
||||
-------- @describe def-imethod-I.f --------
|
||||
definition of interface method func (I).f()
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
package main
|
||||
|
||||
// Tests of 'freevars' query.
|
||||
// See go.tools/guru/guru_test.go for explanation.
|
||||
// See freevars.golden for expected query results.
|
||||
|
||||
// TODO(adonovan): it's hard to test this query in a single line of gofmt'd code.
|
||||
|
||||
type T struct {
|
||||
a, b int
|
||||
}
|
||||
|
||||
type S struct {
|
||||
x int
|
||||
t T
|
||||
}
|
||||
|
||||
func f(int) {}
|
||||
|
||||
func main() {
|
||||
type C int
|
||||
x := 1
|
||||
const exp = 6
|
||||
if y := 2; x+y+int(C(3)) != exp { // @freevars fv1 "if.*{"
|
||||
panic("expected 6")
|
||||
}
|
||||
|
||||
var s S
|
||||
|
||||
for x, y := range "foo" {
|
||||
println(s.x + s.t.a + s.t.b + x + int(y)) // @freevars fv2 "print.*y."
|
||||
}
|
||||
|
||||
f(x) // @freevars fv3 "f.x."
|
||||
|
||||
loop: // @freevars fv-def-label "loop:"
|
||||
for {
|
||||
break loop // @freevars fv-ref-label "break loop"
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
-------- @freevars fv1 --------
|
||||
Free identifiers:
|
||||
type C
|
||||
const exp int
|
||||
var x int
|
||||
|
||||
-------- @freevars fv2 --------
|
||||
Free identifiers:
|
||||
var s.t.a int
|
||||
var s.t.b int
|
||||
var s.x int
|
||||
var x int
|
||||
var y rune
|
||||
|
||||
-------- @freevars fv3 --------
|
||||
Free identifiers:
|
||||
var x int
|
||||
|
||||
-------- @freevars fv-def-label --------
|
||||
No free identifiers.
|
||||
|
||||
-------- @freevars fv-ref-label --------
|
||||
Free identifiers:
|
||||
label loop
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package main
|
||||
|
||||
// Tests of 'implements' query, -output=json.
|
||||
// See go.tools/guru/guru_test.go for explanation.
|
||||
// See implements.golden for expected query results.
|
||||
|
||||
func main() {
|
||||
}
|
||||
|
||||
type E interface{} // @implements E "E"
|
||||
|
||||
type F interface { // @implements F "F"
|
||||
f()
|
||||
}
|
||||
|
||||
type FG interface { // @implements FG "FG"
|
||||
f()
|
||||
g() []int // @implements slice "..int"
|
||||
}
|
||||
|
||||
type C int // @implements C "C"
|
||||
type D struct{}
|
||||
|
||||
func (c *C) f() {} // @implements starC ".C"
|
||||
func (d D) f() {} // @implements D "D"
|
||||
|
||||
func (d *D) g() []int { return nil } // @implements starD ".D"
|
|
@ -1,135 +0,0 @@
|
|||
-------- @implements E --------
|
||||
{
|
||||
"type": {
|
||||
"name": "implements-json.E",
|
||||
"pos": "testdata/src/implements-json/main.go:10:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
}
|
||||
-------- @implements F --------
|
||||
{
|
||||
"type": {
|
||||
"name": "implements-json.F",
|
||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*implements-json.C",
|
||||
"pos": "testdata/src/implements-json/main.go:21:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
{
|
||||
"name": "implements-json.D",
|
||||
"pos": "testdata/src/implements-json/main.go:22:6",
|
||||
"kind": "struct"
|
||||
},
|
||||
{
|
||||
"name": "implements-json.FG",
|
||||
"pos": "testdata/src/implements-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
-------- @implements FG --------
|
||||
{
|
||||
"type": {
|
||||
"name": "implements-json.FG",
|
||||
"pos": "testdata/src/implements-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*implements-json.D",
|
||||
"pos": "testdata/src/implements-json/main.go:22:6",
|
||||
"kind": "pointer"
|
||||
}
|
||||
],
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-json.F",
|
||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
-------- @implements slice --------
|
||||
{
|
||||
"type": {
|
||||
"name": "[]int",
|
||||
"pos": "-",
|
||||
"kind": "slice"
|
||||
}
|
||||
}
|
||||
-------- @implements C --------
|
||||
{
|
||||
"type": {
|
||||
"name": "implements-json.C",
|
||||
"pos": "testdata/src/implements-json/main.go:21:6",
|
||||
"kind": "basic"
|
||||
},
|
||||
"fromptr": [
|
||||
{
|
||||
"name": "implements-json.F",
|
||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
-------- @implements starC --------
|
||||
{
|
||||
"type": {
|
||||
"name": "*implements-json.C",
|
||||
"pos": "testdata/src/implements-json/main.go:21:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-json.F",
|
||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
-------- @implements D --------
|
||||
{
|
||||
"type": {
|
||||
"name": "implements-json.D",
|
||||
"pos": "testdata/src/implements-json/main.go:22:6",
|
||||
"kind": "struct"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-json.F",
|
||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"fromptr": [
|
||||
{
|
||||
"name": "implements-json.FG",
|
||||
"pos": "testdata/src/implements-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
-------- @implements starD --------
|
||||
{
|
||||
"type": {
|
||||
"name": "*implements-json.D",
|
||||
"pos": "testdata/src/implements-json/main.go:22:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-json.F",
|
||||
"pos": "testdata/src/implements-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
{
|
||||
"name": "implements-json.FG",
|
||||
"pos": "testdata/src/implements-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package main
|
||||
|
||||
// Tests of 'implements' query applied to methods, -output=json.
|
||||
// See go.tools/guru/guru_test.go for explanation.
|
||||
// See implements-methods.golden for expected query results.
|
||||
|
||||
import _ "lib"
|
||||
|
||||
func main() {
|
||||
}
|
||||
|
||||
type F interface {
|
||||
f() // @implements F.f "f"
|
||||
}
|
||||
|
||||
type FG interface {
|
||||
f() // @implements FG.f "f"
|
||||
g() []int // @implements FG.g "g"
|
||||
}
|
||||
|
||||
type C int
|
||||
type D struct{}
|
||||
|
||||
func (c *C) f() {} // @implements *C.f "f"
|
||||
func (d D) f() {} // @implements D.f "f"
|
||||
|
||||
func (d *D) g() []int { return nil } // @implements *D.g "g"
|
||||
|
||||
type sorter []int
|
||||
|
||||
func (sorter) Len() int { return 0 } // @implements Len "Len"
|
||||
func (sorter) Less(i, j int) bool { return false }
|
||||
func (sorter) Swap(i, j int) {}
|
||||
|
||||
type I interface {
|
||||
Method(*int) *int // @implements I.Method "Method"
|
||||
}
|
|
@ -1,266 +0,0 @@
|
|||
-------- @implements F.f --------
|
||||
{
|
||||
"type": {
|
||||
"name": "implements-methods-json.F",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*implements-methods-json.C",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:21:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
{
|
||||
"name": "implements-methods-json.D",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
||||
"kind": "struct"
|
||||
},
|
||||
{
|
||||
"name": "implements-methods-json.FG",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (F).f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:13:2"
|
||||
},
|
||||
"to_method": [
|
||||
{
|
||||
"name": "method (*C) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:24:13"
|
||||
},
|
||||
{
|
||||
"name": "method (D) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:25:12"
|
||||
},
|
||||
{
|
||||
"name": "method (FG) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:17:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
-------- @implements FG.f --------
|
||||
{
|
||||
"type": {
|
||||
"name": "implements-methods-json.FG",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*implements-methods-json.D",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
||||
"kind": "pointer"
|
||||
}
|
||||
],
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-methods-json.F",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (FG).f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:17:2"
|
||||
},
|
||||
"to_method": [
|
||||
{
|
||||
"name": "method (*D) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:25:12"
|
||||
}
|
||||
],
|
||||
"from_method": [
|
||||
{
|
||||
"name": "method (F) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:13:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
-------- @implements FG.g --------
|
||||
{
|
||||
"type": {
|
||||
"name": "implements-methods-json.FG",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "*implements-methods-json.D",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
||||
"kind": "pointer"
|
||||
}
|
||||
],
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-methods-json.F",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (FG).g() []int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:18:2"
|
||||
},
|
||||
"to_method": [
|
||||
{
|
||||
"name": "method (*D) g() []int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:27:13"
|
||||
}
|
||||
],
|
||||
"from_method": [
|
||||
{
|
||||
"name": "",
|
||||
"pos": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
-------- @implements *C.f --------
|
||||
{
|
||||
"type": {
|
||||
"name": "*implements-methods-json.C",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:21:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-methods-json.F",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (*C).f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:24:13"
|
||||
},
|
||||
"from_method": [
|
||||
{
|
||||
"name": "method (F) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:13:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
-------- @implements D.f --------
|
||||
{
|
||||
"type": {
|
||||
"name": "implements-methods-json.D",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
||||
"kind": "struct"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-methods-json.F",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"fromptr": [
|
||||
{
|
||||
"name": "implements-methods-json.FG",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (D).f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:25:12"
|
||||
},
|
||||
"from_method": [
|
||||
{
|
||||
"name": "method (F) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:13:2"
|
||||
}
|
||||
],
|
||||
"fromptr_method": [
|
||||
{
|
||||
"name": "method (FG) f()",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:17:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
-------- @implements *D.g --------
|
||||
{
|
||||
"type": {
|
||||
"name": "*implements-methods-json.D",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:22:6",
|
||||
"kind": "pointer"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "implements-methods-json.F",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:12:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
{
|
||||
"name": "implements-methods-json.FG",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (*D).g() []int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:27:13"
|
||||
},
|
||||
"from_method": [
|
||||
{
|
||||
"name": "",
|
||||
"pos": ""
|
||||
},
|
||||
{
|
||||
"name": "method (FG) g() []int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:18:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
-------- @implements Len --------
|
||||
{
|
||||
"type": {
|
||||
"name": "implements-methods-json.sorter",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:29:6",
|
||||
"kind": "slice"
|
||||
},
|
||||
"from": [
|
||||
{
|
||||
"name": "lib.Sorter",
|
||||
"pos": "testdata/src/lib/lib.go:16:6",
|
||||
"kind": "interface"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (sorter).Len() int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:31:15"
|
||||
},
|
||||
"from_method": [
|
||||
{
|
||||
"name": "method (lib.Sorter) Len() int",
|
||||
"pos": "testdata/src/lib/lib.go:17:2"
|
||||
}
|
||||
]
|
||||
}
|
||||
-------- @implements I.Method --------
|
||||
{
|
||||
"type": {
|
||||
"name": "implements-methods-json.I",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:35:6",
|
||||
"kind": "interface"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "lib.Type",
|
||||
"pos": "testdata/src/lib/lib.go:3:6",
|
||||
"kind": "basic"
|
||||
}
|
||||
],
|
||||
"method": {
|
||||
"name": "func (I).Method(*int) *int",
|
||||
"pos": "testdata/src/implements-methods-json/main.go:36:2"
|
||||
},
|
||||
"to_method": [
|
||||
{
|
||||
"name": "method (lib.Type) Method(x *int) *int",
|
||||
"pos": "testdata/src/lib/lib.go:5:13"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package main
|
||||
|
||||
// Tests of 'implements' query applied to methods.
|
||||
// See go.tools/guru/guru_test.go for explanation.
|
||||
// See implements-methods.golden for expected query results.
|
||||
|
||||
import _ "lib"
|
||||
|
||||
func main() {
|
||||
}
|
||||
|
||||
type F interface {
|
||||
f() // @implements F.f "f"
|
||||
}
|
||||
|
||||
type FG interface {
|
||||
f() // @implements FG.f "f"
|
||||
g() []int // @implements FG.g "g"
|
||||
}
|
||||
|
||||
type C int
|
||||
type D struct{}
|
||||
|
||||
func (c *C) f() {} // @implements *C.f "f"
|
||||
func (d D) f() {} // @implements D.f "f"
|
||||
|
||||
func (d *D) g() []int { return nil } // @implements *D.g "g"
|
||||
|
||||
type sorter []int
|
||||
|
||||
func (sorter) Len() int { return 0 } // @implements Len "Len"
|
||||
func (sorter) Less(i, j int) bool { return false }
|
||||
func (sorter) Swap(i, j int) {}
|
||||
|
||||
type I interface {
|
||||
Method(*int) *int // @implements I.Method "Method"
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
-------- @implements F.f --------
|
||||
abstract method func (F).f()
|
||||
is implemented by method (*C).f
|
||||
is implemented by method (D).f
|
||||
is implemented by method (FG).f
|
||||
|
||||
-------- @implements FG.f --------
|
||||
abstract method func (FG).f()
|
||||
is implemented by method (*D).f
|
||||
implements method (F).f
|
||||
|
||||
-------- @implements FG.g --------
|
||||
abstract method func (FG).g() []int
|
||||
is implemented by method (*D).g
|
||||
|
||||
-------- @implements *C.f --------
|
||||
concrete method func (*C).f()
|
||||
implements method (F).f
|
||||
|
||||
-------- @implements D.f --------
|
||||
concrete method func (D).f()
|
||||
implements method (F).f
|
||||
concrete method func (D).f()
|
||||
implements method (FG).f
|
||||
|
||||
-------- @implements *D.g --------
|
||||
concrete method func (*D).g() []int
|
||||
implements method (FG).g
|
||||
|
||||
-------- @implements Len --------
|
||||
concrete method func (sorter).Len() int
|
||||
implements method (lib.Sorter).Len
|
||||
|
||||
-------- @implements I.Method --------
|
||||
abstract method func (I).Method(*int) *int
|
||||
is implemented by method (lib.Type).Method
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
package main
|
||||
|
||||
// Tests of 'implements' query.
|
||||
// See go.tools/guru/guru_test.go for explanation.
|
||||
// See implements.golden for expected query results.
|
||||
|
||||
import _ "lib"
|
||||
|
||||
func main() {
|
||||
}
|
||||
|
||||
type E interface{} // @implements E "E"
|
||||
|
||||
type F interface { // @implements F "F"
|
||||
f()
|
||||
}
|
||||
|
||||
type FG interface { // @implements FG "FG"
|
||||
f()
|
||||
g() []int // @implements slice "..int"
|
||||
}
|
||||
|
||||
type C int // @implements C "C"
|
||||
type D struct{}
|
||||
|
||||
func (c *C) f() {} // @implements starC ".C"
|
||||
func (d D) f() {} // @implements D "D"
|
||||
|
||||
func (d *D) g() []int { return nil } // @implements starD ".D"
|
||||
|
||||
type sorter []int // @implements sorter "sorter"
|
||||
|
||||
func (sorter) Len() int { return 0 }
|
||||
func (sorter) Less(i, j int) bool { return false }
|
||||
func (sorter) Swap(i, j int) {}
|
||||
|
||||
type I interface { // @implements I "I"
|
||||
Method(*int) *int
|
||||
}
|
||||
|
||||
func _() {
|
||||
var d D
|
||||
_ = d // @implements var_d "d"
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
-------- @implements E --------
|
||||
empty interface type E
|
||||
|
||||
-------- @implements F --------
|
||||
interface type F
|
||||
is implemented by pointer type *C
|
||||
is implemented by struct type D
|
||||
is implemented by interface type FG
|
||||
|
||||
-------- @implements FG --------
|
||||
interface type FG
|
||||
is implemented by pointer type *D
|
||||
implements F
|
||||
|
||||
-------- @implements slice --------
|
||||
slice type []int implements only any
|
||||
|
||||
-------- @implements C --------
|
||||
pointer type *C
|
||||
implements F
|
||||
|
||||
-------- @implements starC --------
|
||||
pointer type *C
|
||||
implements F
|
||||
|
||||
-------- @implements D --------
|
||||
struct type D
|
||||
implements F
|
||||
pointer type *D
|
||||
implements FG
|
||||
|
||||
-------- @implements starD --------
|
||||
pointer type *D
|
||||
implements F
|
||||
implements FG
|
||||
|
||||
-------- @implements sorter --------
|
||||
slice type sorter
|
||||
implements lib.Sorter
|
||||
|
||||
-------- @implements I --------
|
||||
interface type I
|
||||
is implemented by basic type lib.Type
|
||||
|
||||
-------- @implements var_d --------
|
||||
struct type D
|
||||
implements F
|
||||
pointer type *D
|
||||
implements FG
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"lib" // @describe ref-pkg-import "lib"
|
||||
"lib/sublib" // @describe ref-pkg-import2 "sublib"
|
||||
)
|
||||
|
||||
// Tests that import another package. (To make the tests run quickly,
|
||||
// we avoid using imports in all the other tests. Remember, each
|
||||
// query causes parsing and typechecking of the whole program.)
|
||||
//
|
||||
// See go.tools/guru/guru_test.go for explanation.
|
||||
// See imports.golden for expected query results.
|
||||
|
||||
var a int
|
||||
|
||||
func main() {
|
||||
const c = lib.Const // @describe ref-const "Const"
|
||||
lib.Func() // @describe ref-func "Func"
|
||||
lib.Var++ // @describe ref-var "Var"
|
||||
var t lib.Type // @describe ref-type "Type"
|
||||
p := t.Method(&a) // @describe ref-method "Method"
|
||||
|
||||
print(*p + 1)
|
||||
|
||||
var _ lib.Type // @describe ref-pkg "lib"
|
||||
|
||||
_ = sublib.C
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
-------- @describe ref-pkg-import --------
|
||||
import of package "lib"
|
||||
const Const untyped int = 3
|
||||
func Func func()
|
||||
type Outer struct{...}
|
||||
type Sorter interface{...}
|
||||
method (Sorter) Len() int
|
||||
method (Sorter) Less(i int, j int) bool
|
||||
method (Sorter) Swap(i int, j int)
|
||||
type Type int
|
||||
method (Type) Method(x *int) *int
|
||||
var Var int
|
||||
|
||||
-------- @describe ref-pkg-import2 --------
|
||||
import of package "lib/sublib"
|
||||
const C untyped int = 0
|
||||
|
||||
-------- @describe ref-const --------
|
||||
reference to const lib.Const untyped int of value 3
|
||||
defined here
|
||||
|
||||
-------- @describe ref-func --------
|
||||
reference to func lib.Func()
|
||||
defined here
|
||||
|
||||
-------- @describe ref-var --------
|
||||
reference to var lib.Var int
|
||||
defined here
|
||||
|
||||
-------- @describe ref-type --------
|
||||
reference to type lib.Type (size 8, align 8)
|
||||
defined as int
|
||||
Methods:
|
||||
method (Type) Method(x *int) *int
|
||||
|
||||
-------- @describe ref-method --------
|
||||
reference to method func (lib.Type).Method(x *int) *int
|
||||
defined here
|
||||
|
||||
-------- @describe ref-pkg --------
|
||||
reference to package "lib"
|
||||
const Const untyped int = 3
|
||||
func Func func()
|
||||
type Outer struct{...}
|
||||
type Sorter interface{...}
|
||||
method (Sorter) Len() int
|
||||
method (Sorter) Less(i int, j int) bool
|
||||
method (Sorter) Swap(i int, j int)
|
||||
type Type int
|
||||
method (Type) Method(x *int) *int
|
||||
var Var int
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
package lib
|
||||
|
||||
type Type int
|
||||
|
||||
func (Type) Method(x *int) *int {
|
||||
return x
|
||||
}
|
||||
|
||||
func Func() {
|
||||
}
|
||||
|
||||
const Const = 3
|
||||
|
||||
var Var = 0
|
||||
|
||||
type Sorter interface {
|
||||
Len() int
|
||||
Less(i, j int) bool
|
||||
Swap(i, j int)
|
||||
}
|
||||
|
||||
type Outer struct {
|
||||
A int
|
||||
b int
|
||||
inner
|
||||
}
|
||||
|
||||
type inner struct {
|
||||
C bool
|
||||
d string
|
||||
recursive
|
||||
}
|
||||
|
||||
type recursive struct {
|
||||
E bool
|
||||
*inner
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
package sublib
|
||||
|
||||
const C = 0
|
|
@ -1,13 +0,0 @@
|
|||
package main
|
||||
|
||||
func g(x int) {
|
||||
}
|
||||
|
||||
func f() {
|
||||
x := 1
|
||||
g(x) // "g(x)" is the selection for multiple queries
|
||||
}
|
||||
|
||||
func main() {
|
||||
f()
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package main
|
||||
|
||||
// Tests of 'referrers' query.
|
||||
// See go.tools/guru/guru_test.go for explanation.
|
||||
// See referrers.golden for expected query results.
|
||||
|
||||
import "lib"
|
||||
|
||||
type s struct {
|
||||
f int
|
||||
}
|
||||
|
||||
func main() {
|
||||
var v lib.Type = lib.Const // @referrers ref-package "lib"
|
||||
_ = v.Method // @referrers ref-method "Method"
|
||||
_ = v.Method
|
||||
v++ //@referrers ref-local "v"
|
||||
v++
|
||||
|
||||
_ = s{}.f // @referrers ref-field "f"
|
||||
|
||||
var s2 s
|
||||
s2.f = 1
|
||||
}
|
|
@ -1,234 +0,0 @@
|
|||
-------- @referrers ref-package --------
|
||||
{
|
||||
"desc": "package lib"
|
||||
}
|
||||
{
|
||||
"package": "definition-json",
|
||||
"refs": [
|
||||
{
|
||||
"pos": "testdata/src/definition-json/main.go:19:8",
|
||||
"text": "\tvar x lib.T // @definition lexical-pkgname \"lib\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/definition-json/main.go:25:8",
|
||||
"text": "\tvar _ lib.Type // @definition qualified-type \"Type\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/definition-json/main.go:26:8",
|
||||
"text": "\tvar _ lib.Func // @definition qualified-func \"Func\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/definition-json/main.go:27:8",
|
||||
"text": "\tvar _ lib.Var // @definition qualified-var \"Var\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/definition-json/main.go:28:8",
|
||||
"text": "\tvar _ lib.Const // @definition qualified-const \"Const\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/definition-json/main.go:29:8",
|
||||
"text": "\tvar _ lib2.Type // @definition qualified-type-renaming \"Type\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/definition-json/main.go:30:8",
|
||||
"text": "\tvar _ lib.Nonesuch // @definition qualified-nomember \"Nonesuch\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/definition-json/main.go:63:2",
|
||||
"text": "\tlib.Type // @definition embedded-other-pkg \"Type\""
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"package": "describe",
|
||||
"refs": [
|
||||
{
|
||||
"pos": "testdata/src/describe/main.go:95:8",
|
||||
"text": "\tvar _ lib.Outer // @describe lib-outer \"Outer\""
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"package": "imports",
|
||||
"refs": [
|
||||
{
|
||||
"pos": "testdata/src/imports/main.go:18:12",
|
||||
"text": "\tconst c = lib.Const // @describe ref-const \"Const\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/imports/main.go:19:2",
|
||||
"text": "\tlib.Func() // @describe ref-func \"Func\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/imports/main.go:20:2",
|
||||
"text": "\tlib.Var++ // @describe ref-var \"Var\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/imports/main.go:21:8",
|
||||
"text": "\tvar t lib.Type // @describe ref-type \"Type\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/imports/main.go:26:8",
|
||||
"text": "\tvar _ lib.Type // @describe ref-pkg \"lib\""
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"package": "referrers",
|
||||
"refs": [
|
||||
{
|
||||
"pos": "testdata/src/referrers/int_test.go:7:7",
|
||||
"text": "\t_ = (lib.Type).Method // ref from internal test package"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"package": "referrers",
|
||||
"refs": [
|
||||
{
|
||||
"pos": "testdata/src/referrers/main.go:16:8",
|
||||
"text": "\tvar v lib.Type = lib.Const // @referrers ref-package \"lib\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/referrers/main.go:16:19",
|
||||
"text": "\tvar v lib.Type = lib.Const // @referrers ref-package \"lib\""
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"package": "referrers-json",
|
||||
"refs": [
|
||||
{
|
||||
"pos": "testdata/src/referrers-json/main.go:14:8",
|
||||
"text": "\tvar v lib.Type = lib.Const // @referrers ref-package \"lib\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/referrers-json/main.go:14:19",
|
||||
"text": "\tvar v lib.Type = lib.Const // @referrers ref-package \"lib\""
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"package": "referrers_test",
|
||||
"refs": [
|
||||
{
|
||||
"pos": "testdata/src/referrers/ext_test.go:10:7",
|
||||
"text": "\t_ = (lib.Type).Method // ref from external test package"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"package": "what-json",
|
||||
"refs": [
|
||||
{
|
||||
"pos": "testdata/src/what-json/main.go:13:7",
|
||||
"text": "var _ lib.Var // @what pkg \"lib\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/what-json/main.go:14:8",
|
||||
"text": "type _ lib.T"
|
||||
}
|
||||
]
|
||||
}
|
||||
-------- @referrers ref-method --------
|
||||
{
|
||||
"objpos": "testdata/src/lib/lib.go:5:13",
|
||||
"desc": "func (lib.Type).Method(x *int) *int"
|
||||
}
|
||||
{
|
||||
"package": "imports",
|
||||
"refs": [
|
||||
{
|
||||
"pos": "testdata/src/imports/main.go:22:9",
|
||||
"text": "\tp := t.Method(\u0026a) // @describe ref-method \"Method\""
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"package": "referrers",
|
||||
"refs": [
|
||||
{
|
||||
"pos": "testdata/src/referrers/int_test.go:7:17",
|
||||
"text": "\t_ = (lib.Type).Method // ref from internal test package"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"package": "referrers",
|
||||
"refs": [
|
||||
{
|
||||
"pos": "testdata/src/referrers/main.go:17:8",
|
||||
"text": "\t_ = v.Method // @referrers ref-method \"Method\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/referrers/main.go:18:8",
|
||||
"text": "\t_ = v.Method"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"package": "referrers-json",
|
||||
"refs": [
|
||||
{
|
||||
"pos": "testdata/src/referrers-json/main.go:15:8",
|
||||
"text": "\t_ = v.Method // @referrers ref-method \"Method\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/referrers-json/main.go:16:8",
|
||||
"text": "\t_ = v.Method"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"package": "referrers_test",
|
||||
"refs": [
|
||||
{
|
||||
"pos": "testdata/src/referrers/ext_test.go:10:17",
|
||||
"text": "\t_ = (lib.Type).Method // ref from external test package"
|
||||
}
|
||||
]
|
||||
}
|
||||
-------- @referrers ref-local --------
|
||||
{
|
||||
"objpos": "testdata/src/referrers-json/main.go:14:6",
|
||||
"desc": "var v lib.Type"
|
||||
}
|
||||
{
|
||||
"package": "referrers-json",
|
||||
"refs": [
|
||||
{
|
||||
"pos": "testdata/src/referrers-json/main.go:15:6",
|
||||
"text": "\t_ = v.Method // @referrers ref-method \"Method\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/referrers-json/main.go:16:6",
|
||||
"text": "\t_ = v.Method"
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/referrers-json/main.go:17:2",
|
||||
"text": "\tv++ //@referrers ref-local \"v\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/referrers-json/main.go:18:2",
|
||||
"text": "\tv++"
|
||||
}
|
||||
]
|
||||
}
|
||||
-------- @referrers ref-field --------
|
||||
{
|
||||
"objpos": "testdata/src/referrers-json/main.go:10:2",
|
||||
"desc": "field f int"
|
||||
}
|
||||
{
|
||||
"package": "referrers-json",
|
||||
"refs": [
|
||||
{
|
||||
"pos": "testdata/src/referrers-json/main.go:20:10",
|
||||
"text": "\t_ = s{}.f // @referrers ref-field \"f\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/referrers-json/main.go:23:5",
|
||||
"text": "\ts2.f = 1"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package main_test
|
||||
|
||||
import (
|
||||
"lib"
|
||||
renamed "referrers" // package has name "main", path "referrers", local name "renamed"
|
||||
)
|
||||
|
||||
func _() {
|
||||
// This reference should be found by the ref-method query.
|
||||
_ = (lib.Type).Method // ref from external test package
|
||||
var _ renamed.T
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package main
|
||||
|
||||
import "lib"
|
||||
|
||||
func _() {
|
||||
// This reference should be found by the ref-method query.
|
||||
_ = (lib.Type).Method // ref from internal test package
|
||||
|
||||
_ = notexported
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package main // @referrers package-decl "main"
|
||||
|
||||
// Tests of 'referrers' query.
|
||||
// See go.tools/guru/guru_test.go for explanation.
|
||||
// See referrers.golden for expected query results.
|
||||
|
||||
import "lib"
|
||||
|
||||
type s struct { // @referrers type " s "
|
||||
f int
|
||||
}
|
||||
|
||||
type T int
|
||||
|
||||
func main() {
|
||||
var v lib.Type = lib.Const // @referrers ref-package "lib"
|
||||
_ = v.Method // @referrers ref-method "Method"
|
||||
_ = v.Method
|
||||
v++ //@referrers ref-local "v"
|
||||
v++
|
||||
|
||||
_ = s{}.f // @referrers ref-field "f"
|
||||
|
||||
var s2 s
|
||||
s2.f = 1
|
||||
}
|
||||
|
||||
var notexported int // @referrers unexported-from-test "notexported"
|
||||
|
||||
// Test //line directives:
|
||||
|
||||
type U int // @referrers ref-type-U "U"
|
||||
|
||||
//line nosuchfile.y:123
|
||||
var u1 U
|
||||
var u2 U
|
|
@ -1,64 +0,0 @@
|
|||
-------- @referrers package-decl --------
|
||||
references to package main ("referrers")
|
||||
var _ renamed.T
|
||||
|
||||
-------- @referrers type --------
|
||||
references to type s struct{f int}
|
||||
_ = s{}.f // @referrers ref-field "f"
|
||||
var s2 s
|
||||
|
||||
-------- @referrers ref-package --------
|
||||
references to package lib
|
||||
_ = (lib.Type).Method // ref from external test package
|
||||
_ = (lib.Type).Method // ref from internal test package
|
||||
const c = lib.Const // @describe ref-const "Const"
|
||||
lib.Func() // @describe ref-func "Func"
|
||||
lib.Type // @definition embedded-other-pkg "Type"
|
||||
lib.Var++ // @describe ref-var "Var"
|
||||
var _ lib.Const // @definition qualified-const "Const"
|
||||
var _ lib.Func // @definition qualified-func "Func"
|
||||
var _ lib.Nonesuch // @definition qualified-nomember "Nonesuch"
|
||||
var _ lib.Outer // @describe lib-outer "Outer"
|
||||
var _ lib.Type // @definition qualified-type "Type"
|
||||
var _ lib.Type // @describe ref-pkg "lib"
|
||||
var _ lib.Var // @definition qualified-var "Var"
|
||||
var _ lib2.Type // @definition qualified-type-renaming "Type"
|
||||
var t lib.Type // @describe ref-type "Type"
|
||||
var v lib.Type = lib.Const // @referrers ref-package "lib"
|
||||
var v lib.Type = lib.Const // @referrers ref-package "lib"
|
||||
var v lib.Type = lib.Const // @referrers ref-package "lib"
|
||||
var v lib.Type = lib.Const // @referrers ref-package "lib"
|
||||
var x lib.T // @definition lexical-pkgname "lib"
|
||||
type _ lib.T
|
||||
var _ lib.Var // @what pkg "lib"
|
||||
|
||||
-------- @referrers ref-method --------
|
||||
references to func (lib.Type).Method(x *int) *int
|
||||
_ = (lib.Type).Method // ref from external test package
|
||||
_ = (lib.Type).Method // ref from internal test package
|
||||
_ = v.Method
|
||||
_ = v.Method
|
||||
_ = v.Method // @referrers ref-method "Method"
|
||||
_ = v.Method // @referrers ref-method "Method"
|
||||
p := t.Method(&a) // @describe ref-method "Method"
|
||||
|
||||
-------- @referrers ref-local --------
|
||||
references to var v lib.Type
|
||||
_ = v.Method
|
||||
_ = v.Method // @referrers ref-method "Method"
|
||||
v++
|
||||
v++ //@referrers ref-local "v"
|
||||
|
||||
-------- @referrers ref-field --------
|
||||
references to field f int
|
||||
_ = s{}.f // @referrers ref-field "f"
|
||||
s2.f = 1
|
||||
|
||||
-------- @referrers unexported-from-test --------
|
||||
references to var notexported int
|
||||
_ = notexported
|
||||
|
||||
-------- @referrers ref-type-U --------
|
||||
references to type U int
|
||||
open testdata/src/referrers/nosuchfile.y: no such file or directory (+ 1 more refs in this file)
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package main
|
||||
|
||||
import "lib"
|
||||
|
||||
// Tests of 'what' queries, -format=json.
|
||||
// See go.tools/guru/guru_test.go for explanation.
|
||||
// See what-json.golden for expected query results.
|
||||
|
||||
func main() {
|
||||
f() // @what call "f"
|
||||
}
|
||||
|
||||
var _ lib.Var // @what pkg "lib"
|
||||
type _ lib.T
|
|
@ -1,88 +0,0 @@
|
|||
-------- @what call --------
|
||||
{
|
||||
"enclosing": [
|
||||
{
|
||||
"desc": "identifier",
|
||||
"start": 189,
|
||||
"end": 190
|
||||
},
|
||||
{
|
||||
"desc": "function call",
|
||||
"start": 189,
|
||||
"end": 192
|
||||
},
|
||||
{
|
||||
"desc": "expression statement",
|
||||
"start": 189,
|
||||
"end": 192
|
||||
},
|
||||
{
|
||||
"desc": "block",
|
||||
"start": 186,
|
||||
"end": 212
|
||||
},
|
||||
{
|
||||
"desc": "function declaration",
|
||||
"start": 174,
|
||||
"end": 212
|
||||
},
|
||||
{
|
||||
"desc": "source file",
|
||||
"start": 0,
|
||||
"end": 259
|
||||
}
|
||||
],
|
||||
"modes": [
|
||||
"definition",
|
||||
"describe",
|
||||
"freevars",
|
||||
"implements",
|
||||
"referrers"
|
||||
],
|
||||
"srcdir": "testdata/src",
|
||||
"importpath": "what-json"
|
||||
}
|
||||
-------- @what pkg --------
|
||||
{
|
||||
"enclosing": [
|
||||
{
|
||||
"desc": "identifier",
|
||||
"start": 220,
|
||||
"end": 223
|
||||
},
|
||||
{
|
||||
"desc": "selector",
|
||||
"start": 220,
|
||||
"end": 227
|
||||
},
|
||||
{
|
||||
"desc": "value specification",
|
||||
"start": 218,
|
||||
"end": 227
|
||||
},
|
||||
{
|
||||
"desc": "variable declaration",
|
||||
"start": 214,
|
||||
"end": 227
|
||||
},
|
||||
{
|
||||
"desc": "source file",
|
||||
"start": 0,
|
||||
"end": 259
|
||||
}
|
||||
],
|
||||
"modes": [
|
||||
"definition",
|
||||
"describe",
|
||||
"freevars",
|
||||
"implements",
|
||||
"referrers"
|
||||
],
|
||||
"srcdir": "testdata/src",
|
||||
"importpath": "what-json",
|
||||
"object": "lib",
|
||||
"sameids": [
|
||||
"$GOPATH/src/what-json/main.go:13:7",
|
||||
"$GOPATH/src/what-json/main.go:14:8"
|
||||
]
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package main // @what pkgdecl "main"
|
||||
|
||||
// Tests of 'what' queries.
|
||||
// See go.tools/guru/guru_test.go for explanation.
|
||||
// See what.golden for expected query results.
|
||||
|
||||
func main() {
|
||||
f() // @what call "f"
|
||||
var ch chan int // @what var "var"
|
||||
<-ch // @what recv "ch"
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
-------- @what pkgdecl --------
|
||||
identifier
|
||||
source file
|
||||
modes: [definition describe freevars implements referrers]
|
||||
srcdir: testdata/src
|
||||
import path: what
|
||||
|
||||
-------- @what call --------
|
||||
identifier
|
||||
function call
|
||||
expression statement
|
||||
block
|
||||
function declaration
|
||||
source file
|
||||
modes: [definition describe freevars implements referrers]
|
||||
srcdir: testdata/src
|
||||
import path: what
|
||||
|
||||
-------- @what var --------
|
||||
variable declaration
|
||||
variable declaration statement
|
||||
block
|
||||
function declaration
|
||||
source file
|
||||
modes: [describe freevars]
|
||||
srcdir: testdata/src
|
||||
import path: what
|
||||
|
||||
-------- @what recv --------
|
||||
identifier
|
||||
unary <- operation
|
||||
expression statement
|
||||
block
|
||||
function declaration
|
||||
source file
|
||||
modes: [definition describe freevars implements referrers]
|
||||
srcdir: testdata/src
|
||||
import path: what
|
||||
ch
|
||||
ch
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
// Copyright 2013 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/build"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Unit tests for internal guru functions
|
||||
|
||||
func TestIssue17515(t *testing.T) {
|
||||
// Tests handling of symlinks in function guessImportPath
|
||||
// If we have Go code inside $HOME/go/src and create a symlink $HOME/src to it
|
||||
// there are 4 possible cases that need to be tested:
|
||||
// (1) absolute & absolute: GOPATH=$HOME/go/src file=$HOME/go/src/test/test.go
|
||||
// (2) absolute & symlink: GOPATH=$HOME/go/src file=$HOME/src/test/test.go
|
||||
// (3) symlink & symlink: GOPATH=$HOME/src file=$HOME/src/test/test.go
|
||||
// (4) symlink & absolute: GOPATH=$HOME/src file= $HOME/go/src/test/test.go
|
||||
|
||||
// Create a temporary home directory under /tmp
|
||||
home, err := os.MkdirTemp(os.TempDir(), "home")
|
||||
if err != nil {
|
||||
t.Errorf("Unable to create a temporary directory in %s", os.TempDir())
|
||||
}
|
||||
|
||||
defer os.RemoveAll(home)
|
||||
|
||||
// create filepath /tmp/home/go/src/test/test.go
|
||||
if err = os.MkdirAll(home+"/go/src/test", 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var buildContext = build.Default
|
||||
|
||||
// Success test cases
|
||||
type SuccessTest struct {
|
||||
gopath, filename, wantSrcdir string
|
||||
}
|
||||
|
||||
successTests := []SuccessTest{
|
||||
{home + "/go", home + "/go/src/test/test.go", filepath.FromSlash(home + "/go/src")},
|
||||
}
|
||||
|
||||
// symlink between /tmp/home/go/src and /tmp/home/src
|
||||
symlinkErr := os.Symlink(filepath.Join("go", "src"), home+"/src")
|
||||
if symlinkErr == nil {
|
||||
successTests = append(successTests, []SuccessTest{
|
||||
{home + "/go", home + "/src/test/test.go", filepath.FromSlash(home + "/go/src")},
|
||||
{home, home + "/go/src/test/test.go", filepath.FromSlash(home + "/src")},
|
||||
{home, home + "/src/test/test.go", filepath.FromSlash(home + "/src")},
|
||||
}...)
|
||||
} else {
|
||||
switch runtime.GOOS {
|
||||
case "aix", "darwin", "dragonfly", "freebsd", "illumos", "linux", "netbsd", "openbsd", "solaris":
|
||||
// Non-mobile OS known to always support symlinks.
|
||||
t.Fatal(err)
|
||||
default:
|
||||
t.Logf("omitting symlink cases: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, test := range successTests {
|
||||
buildContext.GOPATH = test.gopath
|
||||
srcdir, importPath, err := guessImportPath(test.filename, &buildContext)
|
||||
if srcdir != test.wantSrcdir || importPath != "test" || err != nil {
|
||||
t.Errorf("guessImportPath(%q, %q) = %q, %q, %q; want %q, %q, %q",
|
||||
test.filename, test.gopath, srcdir, importPath, err, test.wantSrcdir, "test", "nil")
|
||||
}
|
||||
}
|
||||
// Function to format expected error message
|
||||
errFormat := func(fpath string) string {
|
||||
return fmt.Sprintf("can't evaluate symlinks of %s", fpath)
|
||||
}
|
||||
|
||||
// Failure test cases
|
||||
type FailTest struct {
|
||||
gopath, filename, wantErr string
|
||||
}
|
||||
|
||||
failTests := []FailTest{
|
||||
{home + "/go", home + "/go/src/fake/test.go", errFormat(filepath.FromSlash(home + "/go/src/fake"))},
|
||||
}
|
||||
|
||||
if symlinkErr == nil {
|
||||
failTests = append(failTests, []FailTest{
|
||||
{home + "/go", home + "/src/fake/test.go", errFormat(filepath.FromSlash(home + "/src/fake"))},
|
||||
{home, home + "/src/fake/test.go", errFormat(filepath.FromSlash(home + "/src/fake"))},
|
||||
{home, home + "/go/src/fake/test.go", errFormat(filepath.FromSlash(home + "/go/src/fake"))},
|
||||
}...)
|
||||
}
|
||||
|
||||
for _, test := range failTests {
|
||||
buildContext.GOPATH = test.gopath
|
||||
srcdir, importPath, err := guessImportPath(test.filename, &buildContext)
|
||||
if !strings.HasPrefix(fmt.Sprint(err), test.wantErr) {
|
||||
t.Errorf("guessImportPath(%q, %q) = %q, %q, %q; want %q, %q, %q",
|
||||
test.filename, test.gopath, srcdir, importPath, err, "", "", test.wantErr)
|
||||
}
|
||||
}
|
||||
}
|
243
cmd/guru/what.go
243
cmd/guru/what.go
|
@ -1,243 +0,0 @@
|
|||
// Copyright 2013 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/cmd/guru/serial"
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
// what reports all the information about the query selection that can be
|
||||
// obtained from parsing only its containing source file.
|
||||
// It is intended to be a very low-latency query callable from GUI
|
||||
// tools, e.g. to populate a menu of options of slower queries about
|
||||
// the selected location.
|
||||
func what(q *Query) error {
|
||||
qpos, err := fastQueryPos(q.Build, q.Pos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// (ignore errors)
|
||||
srcdir, importPath, _ := guessImportPath(qpos.fset.File(qpos.start).Name(), q.Build)
|
||||
|
||||
// Determine which query modes are applicable to the selection.
|
||||
enable := map[string]bool{
|
||||
"describe": true, // any syntax; always enabled
|
||||
}
|
||||
|
||||
if qpos.end > qpos.start {
|
||||
enable["freevars"] = true // nonempty selection?
|
||||
}
|
||||
|
||||
for _, n := range qpos.path {
|
||||
switch n.(type) {
|
||||
case *ast.Ident:
|
||||
enable["definition"] = true
|
||||
enable["referrers"] = true
|
||||
enable["implements"] = true
|
||||
}
|
||||
|
||||
// For implements, we approximate findInterestingNode.
|
||||
if _, ok := enable["implements"]; !ok {
|
||||
switch n.(type) {
|
||||
case *ast.ArrayType,
|
||||
*ast.StructType,
|
||||
*ast.FuncType,
|
||||
*ast.InterfaceType,
|
||||
*ast.MapType,
|
||||
*ast.ChanType:
|
||||
enable["implements"] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have an exact selection, disable modes that need one.
|
||||
if !qpos.exact {
|
||||
enable["describe"] = false
|
||||
}
|
||||
|
||||
var modes []string
|
||||
for mode := range enable {
|
||||
modes = append(modes, mode)
|
||||
}
|
||||
sort.Strings(modes)
|
||||
|
||||
// Find the object referred to by the selection (if it's an
|
||||
// identifier) and report the position of each identifier
|
||||
// that refers to the same object.
|
||||
//
|
||||
// This may return spurious matches (e.g. struct fields) because
|
||||
// it uses the best-effort name resolution done by go/parser.
|
||||
var sameids []token.Pos
|
||||
var object string
|
||||
if id, ok := qpos.path[0].(*ast.Ident); ok {
|
||||
if id.Obj == nil {
|
||||
// An unresolved identifier is potentially a package name.
|
||||
// Resolve them with a simple importer (adds ~100µs).
|
||||
importer := func(imports map[string]*ast.Object, path string) (*ast.Object, error) {
|
||||
pkg, ok := imports[path]
|
||||
if !ok {
|
||||
pkg = &ast.Object{
|
||||
Kind: ast.Pkg,
|
||||
Name: filepath.Base(path), // a guess
|
||||
}
|
||||
imports[path] = pkg
|
||||
}
|
||||
return pkg, nil
|
||||
}
|
||||
f := qpos.path[len(qpos.path)-1].(*ast.File)
|
||||
ast.NewPackage(qpos.fset, map[string]*ast.File{"": f}, importer, nil)
|
||||
}
|
||||
|
||||
if id.Obj != nil {
|
||||
object = id.Obj.Name
|
||||
decl := qpos.path[len(qpos.path)-1]
|
||||
ast.Inspect(decl, func(n ast.Node) bool {
|
||||
if n, ok := n.(*ast.Ident); ok && n.Obj == id.Obj {
|
||||
sameids = append(sameids, n.Pos())
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
q.Output(qpos.fset, &whatResult{
|
||||
path: qpos.path,
|
||||
srcdir: srcdir,
|
||||
importPath: importPath,
|
||||
modes: modes,
|
||||
object: object,
|
||||
sameids: sameids,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// guessImportPath finds the package containing filename, and returns
|
||||
// its source directory (an element of $GOPATH) and its import path
|
||||
// relative to it.
|
||||
//
|
||||
// TODO(adonovan): what about _test.go files that are not part of the
|
||||
// package?
|
||||
func guessImportPath(filename string, buildContext *build.Context) (srcdir, importPath string, err error) {
|
||||
absFile, err := filepath.Abs(filename)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("can't form absolute path of %s: %v", filename, err)
|
||||
}
|
||||
|
||||
absFileDir := filepath.Dir(absFile)
|
||||
resolvedAbsFileDir, err := filepath.EvalSymlinks(absFileDir)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("can't evaluate symlinks of %s: %v", absFileDir, err)
|
||||
}
|
||||
|
||||
segmentedAbsFileDir := segments(resolvedAbsFileDir)
|
||||
// Find the innermost directory in $GOPATH that encloses filename.
|
||||
minD := 1024
|
||||
for _, gopathDir := range buildContext.SrcDirs() {
|
||||
absDir, err := filepath.Abs(gopathDir)
|
||||
if err != nil {
|
||||
continue // e.g. non-existent dir on $GOPATH
|
||||
}
|
||||
resolvedAbsDir, err := filepath.EvalSymlinks(absDir)
|
||||
if err != nil {
|
||||
continue // e.g. non-existent dir on $GOPATH
|
||||
}
|
||||
|
||||
d := prefixLen(segments(resolvedAbsDir), segmentedAbsFileDir)
|
||||
// If there are multiple matches,
|
||||
// prefer the innermost enclosing directory
|
||||
// (smallest d).
|
||||
if d >= 0 && d < minD {
|
||||
minD = d
|
||||
srcdir = gopathDir
|
||||
importPath = path.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:]...)
|
||||
}
|
||||
}
|
||||
if srcdir == "" {
|
||||
return "", "", fmt.Errorf("directory %s is not beneath any of these GOROOT/GOPATH directories: %s",
|
||||
filepath.Dir(absFile), strings.Join(buildContext.SrcDirs(), ", "))
|
||||
}
|
||||
if importPath == "" {
|
||||
// This happens for e.g. $GOPATH/src/a.go, but
|
||||
// "" is not a valid path for (*go/build).Import.
|
||||
return "", "", fmt.Errorf("cannot load package in root of source directory %s", srcdir)
|
||||
}
|
||||
return srcdir, importPath, nil
|
||||
}
|
||||
|
||||
func segments(path string) []string {
|
||||
return strings.Split(path, string(os.PathSeparator))
|
||||
}
|
||||
|
||||
// prefixLen returns the length of the remainder of y if x is a prefix
|
||||
// of y, a negative number otherwise.
|
||||
func prefixLen(x, y []string) int {
|
||||
d := len(y) - len(x)
|
||||
if d >= 0 {
|
||||
for i := range x {
|
||||
if y[i] != x[i] {
|
||||
return -1 // not a prefix
|
||||
}
|
||||
}
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
type whatResult struct {
|
||||
path []ast.Node
|
||||
modes []string
|
||||
srcdir string
|
||||
importPath string
|
||||
object string
|
||||
sameids []token.Pos
|
||||
}
|
||||
|
||||
func (r *whatResult) PrintPlain(printf printfFunc) {
|
||||
for _, n := range r.path {
|
||||
printf(n, "%s", astutil.NodeDescription(n))
|
||||
}
|
||||
printf(nil, "modes: %s", r.modes)
|
||||
printf(nil, "srcdir: %s", r.srcdir)
|
||||
printf(nil, "import path: %s", r.importPath)
|
||||
for _, pos := range r.sameids {
|
||||
printf(pos, "%s", r.object)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *whatResult) JSON(fset *token.FileSet) []byte {
|
||||
var enclosing []serial.SyntaxNode
|
||||
for _, n := range r.path {
|
||||
enclosing = append(enclosing, serial.SyntaxNode{
|
||||
Description: astutil.NodeDescription(n),
|
||||
Start: fset.Position(n.Pos()).Offset,
|
||||
End: fset.Position(n.End()).Offset,
|
||||
})
|
||||
}
|
||||
|
||||
var sameids []string
|
||||
for _, pos := range r.sameids {
|
||||
sameids = append(sameids, fset.Position(pos).String())
|
||||
}
|
||||
|
||||
return toJSON(&serial.What{
|
||||
Modes: r.modes,
|
||||
SrcDir: r.srcdir,
|
||||
ImportPath: r.importPath,
|
||||
Enclosing: enclosing,
|
||||
Object: r.object,
|
||||
SameIDs: sameids,
|
||||
})
|
||||
}
|
Загрузка…
Ссылка в новой задаче