github.com/april1989/origin-go-tools@v0.0.32/cmd/guru/freevars.go (about) 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. 4 5 package main 6 7 import ( 8 "bytes" 9 "go/ast" 10 "go/printer" 11 "go/token" 12 "go/types" 13 "sort" 14 15 "github.com/april1989/origin-go-tools/cmd/guru/serial" 16 "github.com/april1989/origin-go-tools/go/loader" 17 ) 18 19 // freevars displays the lexical (not package-level) free variables of 20 // the selection. 21 // 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. 25 // 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 30 // bands. 31 // 32 func freevars(q *Query) error { 33 lconf := loader.Config{Build: q.Build} 34 allowErrors(&lconf) 35 36 if _, err := importQueryPackage(q.Pos, &lconf); err != nil { 37 return err 38 } 39 40 // Load/parse/type-check the program. 41 lprog, err := lconf.Load() 42 if err != nil { 43 return err 44 } 45 46 qpos, err := parseQueryPos(lprog, q.Pos, false) 47 if err != nil { 48 return err 49 } 50 51 file := qpos.path[len(qpos.path)-1] // the enclosing file 52 fileScope := qpos.info.Scopes[file] 53 pkgScope := fileScope.Parent() 54 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 61 62 sel = func(n *ast.SelectorExpr) types.Object { 63 switch x := unparen(n.X).(type) { 64 case *ast.SelectorExpr: 65 return sel(x) 66 case *ast.Ident: 67 return id(x) 68 } 69 return nil 70 } 71 72 id = func(n *ast.Ident) types.Object { 73 obj := qpos.info.Uses[n] 74 if obj == nil { 75 return nil // not a reference 76 } 77 if _, ok := obj.(*types.PkgName); ok { 78 return nil // imported package 79 } 80 if !(file.Pos() <= obj.Pos() && obj.Pos() <= file.End()) { 81 return nil // not defined in this file 82 } 83 scope := obj.Parent() 84 if scope == nil { 85 return nil // e.g. interface method, struct field 86 } 87 if scope == fileScope || scope == pkgScope { 88 return nil // defined at file or package scope 89 } 90 if qpos.start <= obj.Pos() && obj.Pos() <= qpos.end { 91 return nil // defined within selection => not free 92 } 93 return obj 94 } 95 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) 100 101 // Visit all the identifiers in the selected ASTs. 102 ast.Inspect(qpos.path[0], func(n ast.Node) bool { 103 if n == nil { 104 return true // popping DFS stack 105 } 106 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 { 111 var obj types.Object 112 var prune bool 113 switch n := n.(type) { 114 case *ast.Ident: 115 obj = id(n) 116 117 case *ast.SelectorExpr: 118 obj = sel(n) 119 prune = true 120 } 121 122 if obj != nil { 123 var kind string 124 switch obj.(type) { 125 case *types.Var: 126 kind = "var" 127 case *types.Func: 128 kind = "func" 129 case *types.TypeName: 130 kind = "type" 131 case *types.Const: 132 kind = "const" 133 case *types.Label: 134 kind = "label" 135 default: 136 panic(obj) 137 } 138 139 typ := qpos.info.TypeOf(n.(ast.Expr)) 140 ref := freevarsRef{kind, printNode(lprog.Fset, n), typ, obj} 141 refsMap[ref.ref] = ref 142 143 if prune { 144 return false // don't descend 145 } 146 } 147 } 148 149 return true // descend 150 }) 151 152 refs := make([]freevarsRef, 0, len(refsMap)) 153 for _, ref := range refsMap { 154 refs = append(refs, ref) 155 } 156 sort.Sort(byRef(refs)) 157 158 q.Output(lprog.Fset, &freevarsResult{ 159 qpos: qpos, 160 refs: refs, 161 }) 162 return nil 163 } 164 165 type freevarsResult struct { 166 qpos *queryPos 167 refs []freevarsRef 168 } 169 170 type freevarsRef struct { 171 kind string 172 ref string 173 typ types.Type 174 obj types.Object 175 } 176 177 func (r *freevarsResult) PrintPlain(printf printfFunc) { 178 if len(r.refs) == 0 { 179 printf(r.qpos, "No free identifiers.") 180 } else { 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". 185 var typstr string 186 if ref.kind != "type" && ref.kind != "label" { 187 typstr = " " + types.TypeString(ref.typ, qualifier) 188 } 189 printf(ref.obj, "%s %s%s", ref.kind, ref.ref, typstr) 190 } 191 } 192 } 193 194 func (r *freevarsResult) JSON(fset *token.FileSet) []byte { 195 var buf bytes.Buffer 196 for i, ref := range r.refs { 197 if i > 0 { 198 buf.WriteByte('\n') 199 } 200 buf.Write(toJSON(serial.FreeVar{ 201 Pos: fset.Position(ref.obj.Pos()).String(), 202 Kind: ref.kind, 203 Ref: ref.ref, 204 Type: ref.typ.String(), 205 })) 206 } 207 return buf.Bytes() 208 } 209 210 // -------- utils -------- 211 212 type byRef []freevarsRef 213 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] } 217 218 // printNode returns the pretty-printed syntax of n. 219 func printNode(fset *token.FileSet, n ast.Node) string { 220 var buf bytes.Buffer 221 printer.Fprint(&buf, fset, n) 222 return buf.String() 223 }