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