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