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