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