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:
Alan Donovan 2024-03-08 14:33:08 -05:00 коммит произвёл Gopher Robot
Родитель c6563ca60f
Коммит 1f580da078
52 изменённых файлов: 0 добавлений и 6328 удалений

Просмотреть файл

@ -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=

Просмотреть файл

@ -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

Просмотреть файл

@ -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)
}
}

Просмотреть файл

@ -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"`
}

2
cmd/guru/testdata/src/README.txt поставляемый
Просмотреть файл

@ -1,2 +0,0 @@
This is not a Go source file.
Used by TestIssue14684.

23
cmd/guru/testdata/src/alias/alias.go поставляемый
Просмотреть файл

@ -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"

47
cmd/guru/testdata/src/alias/alias.golden поставляемый
Просмотреть файл

@ -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

68
cmd/guru/testdata/src/definition-json/main.go поставляемый
Просмотреть файл

@ -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

29
cmd/guru/testdata/src/describe-json/main.go поставляемый
Просмотреть файл

@ -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"
}
]
}
}

119
cmd/guru/testdata/src/describe/main.go поставляемый
Просмотреть файл

@ -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{} }

248
cmd/guru/testdata/src/describe/main.golden поставляемый
Просмотреть файл

@ -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()

40
cmd/guru/testdata/src/freevars/main.go поставляемый
Просмотреть файл

@ -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"
}
}

25
cmd/guru/testdata/src/freevars/main.golden поставляемый
Просмотреть файл

@ -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

27
cmd/guru/testdata/src/implements-json/main.go поставляемый
Просмотреть файл

@ -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

44
cmd/guru/testdata/src/implements/main.go поставляемый
Просмотреть файл

@ -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"
}

50
cmd/guru/testdata/src/implements/main.golden поставляемый
Просмотреть файл

@ -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

29
cmd/guru/testdata/src/imports/main.go поставляемый
Просмотреть файл

@ -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
}

52
cmd/guru/testdata/src/imports/main.golden поставляемый
Просмотреть файл

@ -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

37
cmd/guru/testdata/src/lib/lib.go поставляемый
Просмотреть файл

@ -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
}

3
cmd/guru/testdata/src/lib/sublib/sublib.go поставляемый
Просмотреть файл

@ -1,3 +0,0 @@
package sublib
const C = 0

13
cmd/guru/testdata/src/main/multi.go поставляемый
Просмотреть файл

@ -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()
}

24
cmd/guru/testdata/src/referrers-json/main.go поставляемый
Просмотреть файл

@ -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"
}
]
}

12
cmd/guru/testdata/src/referrers/ext_test.go поставляемый
Просмотреть файл

@ -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
}

10
cmd/guru/testdata/src/referrers/int_test.go поставляемый
Просмотреть файл

@ -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
}

36
cmd/guru/testdata/src/referrers/main.go поставляемый
Просмотреть файл

@ -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

64
cmd/guru/testdata/src/referrers/main.golden поставляемый
Просмотреть файл

@ -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)

14
cmd/guru/testdata/src/what-json/main.go поставляемый
Просмотреть файл

@ -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

88
cmd/guru/testdata/src/what-json/main.golden поставляемый
Просмотреть файл

@ -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"
]
}

11
cmd/guru/testdata/src/what/main.go поставляемый
Просмотреть файл

@ -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"
}

41
cmd/guru/testdata/src/what/main.golden поставляемый
Просмотреть файл

@ -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)
}
}
}

Просмотреть файл

@ -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,
})
}