1 // Copyright 2013 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
15 "golang.org/x/tools/cmd/guru/serial"
16 "golang.org/x/tools/go/loader"
19 // freevars displays the lexical (not package-level) free variables of
22 // It treats A.B.C as a separate variable from A to reveal the parts
23 // of an aggregate type that are actually needed.
24 // This aids refactoring.
26 // TODO(adonovan): optionally display the free references to
27 // file/package scope objects, and to objects from other packages.
28 // Depending on where the resulting function abstraction will go,
29 // these might be interesting. Perhaps group the results into three
32 func freevars(q *Query) error {
33 lconf := loader.Config{Build: q.Build}
36 if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
40 // Load/parse/type-check the program.
41 lprog, err := lconf.Load()
46 qpos, err := parseQueryPos(lprog, q.Pos, false)
51 file := qpos.path[len(qpos.path)-1] // the enclosing file
52 fileScope := qpos.info.Scopes[file]
53 pkgScope := fileScope.Parent()
55 // The id and sel functions return non-nil if they denote an
56 // object o or selection o.x.y that is referenced by the
57 // selection but defined neither within the selection nor at
58 // file scope, i.e. it is in the lexical environment.
59 var id func(n *ast.Ident) types.Object
60 var sel func(n *ast.SelectorExpr) types.Object
62 sel = func(n *ast.SelectorExpr) types.Object {
63 switch x := unparen(n.X).(type) {
64 case *ast.SelectorExpr:
72 id = func(n *ast.Ident) types.Object {
73 obj := qpos.info.Uses[n]
75 return nil // not a reference
77 if _, ok := obj.(*types.PkgName); ok {
78 return nil // imported package
80 if !(file.Pos() <= obj.Pos() && obj.Pos() <= file.End()) {
81 return nil // not defined in this file
85 return nil // e.g. interface method, struct field
87 if scope == fileScope || scope == pkgScope {
88 return nil // defined at file or package scope
90 if qpos.start <= obj.Pos() && obj.Pos() <= qpos.end {
91 return nil // defined within selection => not free
96 // Maps each reference that is free in the selection
97 // to the object it refers to.
98 // The map de-duplicates repeated references.
99 refsMap := make(map[string]freevarsRef)
101 // Visit all the identifiers in the selected ASTs.
102 ast.Inspect(qpos.path[0], func(n ast.Node) bool {
104 return true // popping DFS stack
107 // Is this node contained within the selection?
108 // (freevars permits inexact selections,
109 // like two stmts in a block.)
110 if qpos.start <= n.Pos() && n.End() <= qpos.end {
113 switch n := n.(type) {
117 case *ast.SelectorExpr:
129 case *types.TypeName:
139 typ := qpos.info.TypeOf(n.(ast.Expr))
140 ref := freevarsRef{kind, printNode(lprog.Fset, n), typ, obj}
141 refsMap[ref.ref] = ref
144 return false // don't descend
149 return true // descend
152 refs := make([]freevarsRef, 0, len(refsMap))
153 for _, ref := range refsMap {
154 refs = append(refs, ref)
156 sort.Sort(byRef(refs))
158 q.Output(lprog.Fset, &freevarsResult{
165 type freevarsResult struct {
170 type freevarsRef struct {
177 func (r *freevarsResult) PrintPlain(printf printfFunc) {
178 if len(r.refs) == 0 {
179 printf(r.qpos, "No free identifiers.")
181 printf(r.qpos, "Free identifiers:")
182 qualifier := types.RelativeTo(r.qpos.info.Pkg)
183 for _, ref := range r.refs {
184 // Avoid printing "type T T".
186 if ref.kind != "type" && ref.kind != "label" {
187 typstr = " " + types.TypeString(ref.typ, qualifier)
189 printf(ref.obj, "%s %s%s", ref.kind, ref.ref, typstr)
194 func (r *freevarsResult) JSON(fset *token.FileSet) []byte {
196 for i, ref := range r.refs {
200 buf.Write(toJSON(serial.FreeVar{
201 Pos: fset.Position(ref.obj.Pos()).String(),
204 Type: ref.typ.String(),
210 // -------- utils --------
212 type byRef []freevarsRef
214 func (p byRef) Len() int { return len(p) }
215 func (p byRef) Less(i, j int) bool { return p[i].ref < p[j].ref }
216 func (p byRef) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
218 // printNode returns the pretty-printed syntax of n.
219 func printNode(fset *token.FileSet, n ast.Node) string {
221 printer.Fprint(&buf, fset, n)