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