github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/oracle/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 // +build go1.5 6 7 package oracle 8 9 import ( 10 "bytes" 11 "go/ast" 12 "go/printer" 13 "go/token" 14 "go/types" 15 "sort" 16 17 "golang.org/x/tools/go/loader" 18 "golang.org/x/tools/oracle/serial" 19 ) 20 21 // freevars displays the lexical (not package-level) free variables of 22 // the selection. 23 // 24 // It treats A.B.C as a separate variable from A to reveal the parts 25 // of an aggregate type that are actually needed. 26 // This aids refactoring. 27 // 28 // TODO(adonovan): optionally display the free references to 29 // file/package scope objects, and to objects from other packages. 30 // Depending on where the resulting function abstraction will go, 31 // these might be interesting. Perhaps group the results into three 32 // bands. 33 // 34 func freevars(q *Query) error { 35 lconf := loader.Config{Build: q.Build} 36 allowErrors(&lconf) 37 38 if _, err := importQueryPackage(q.Pos, &lconf); err != nil { 39 return err 40 } 41 42 // Load/parse/type-check the program. 43 lprog, err := lconf.Load() 44 if err != nil { 45 return err 46 } 47 q.Fset = lprog.Fset 48 49 qpos, err := parseQueryPos(lprog, q.Pos, false) 50 if err != nil { 51 return err 52 } 53 54 file := qpos.path[len(qpos.path)-1] // the enclosing file 55 fileScope := qpos.info.Scopes[file] 56 pkgScope := fileScope.Parent() 57 58 // The id and sel functions return non-nil if they denote an 59 // object o or selection o.x.y that is referenced by the 60 // selection but defined neither within the selection nor at 61 // file scope, i.e. it is in the lexical environment. 62 var id func(n *ast.Ident) types.Object 63 var sel func(n *ast.SelectorExpr) types.Object 64 65 sel = func(n *ast.SelectorExpr) types.Object { 66 switch x := unparen(n.X).(type) { 67 case *ast.SelectorExpr: 68 return sel(x) 69 case *ast.Ident: 70 return id(x) 71 } 72 return nil 73 } 74 75 id = func(n *ast.Ident) types.Object { 76 obj := qpos.info.Uses[n] 77 if obj == nil { 78 return nil // not a reference 79 } 80 if _, ok := obj.(*types.PkgName); ok { 81 return nil // imported package 82 } 83 if !(file.Pos() <= obj.Pos() && obj.Pos() <= file.End()) { 84 return nil // not defined in this file 85 } 86 scope := obj.Parent() 87 if scope == nil { 88 return nil // e.g. interface method, struct field 89 } 90 if scope == fileScope || scope == pkgScope { 91 return nil // defined at file or package scope 92 } 93 if qpos.start <= obj.Pos() && obj.Pos() <= qpos.end { 94 return nil // defined within selection => not free 95 } 96 return obj 97 } 98 99 // Maps each reference that is free in the selection 100 // to the object it refers to. 101 // The map de-duplicates repeated references. 102 refsMap := make(map[string]freevarsRef) 103 104 // Visit all the identifiers in the selected ASTs. 105 ast.Inspect(qpos.path[0], func(n ast.Node) bool { 106 if n == nil { 107 return true // popping DFS stack 108 } 109 110 // Is this node contained within the selection? 111 // (freevars permits inexact selections, 112 // like two stmts in a block.) 113 if qpos.start <= n.Pos() && n.End() <= qpos.end { 114 var obj types.Object 115 var prune bool 116 switch n := n.(type) { 117 case *ast.Ident: 118 obj = id(n) 119 120 case *ast.SelectorExpr: 121 obj = sel(n) 122 prune = true 123 } 124 125 if obj != nil { 126 var kind string 127 switch obj.(type) { 128 case *types.Var: 129 kind = "var" 130 case *types.Func: 131 kind = "func" 132 case *types.TypeName: 133 kind = "type" 134 case *types.Const: 135 kind = "const" 136 case *types.Label: 137 kind = "label" 138 default: 139 panic(obj) 140 } 141 142 typ := qpos.info.TypeOf(n.(ast.Expr)) 143 ref := freevarsRef{kind, printNode(lprog.Fset, n), typ, obj} 144 refsMap[ref.ref] = ref 145 146 if prune { 147 return false // don't descend 148 } 149 } 150 } 151 152 return true // descend 153 }) 154 155 refs := make([]freevarsRef, 0, len(refsMap)) 156 for _, ref := range refsMap { 157 refs = append(refs, ref) 158 } 159 sort.Sort(byRef(refs)) 160 161 q.result = &freevarsResult{ 162 qpos: qpos, 163 refs: refs, 164 } 165 return nil 166 } 167 168 type freevarsResult struct { 169 qpos *queryPos 170 refs []freevarsRef 171 } 172 173 type freevarsRef struct { 174 kind string 175 ref string 176 typ types.Type 177 obj types.Object 178 } 179 180 func (r *freevarsResult) display(printf printfFunc) { 181 if len(r.refs) == 0 { 182 printf(r.qpos, "No free identifiers.") 183 } else { 184 printf(r.qpos, "Free identifiers:") 185 qualifier := types.RelativeTo(r.qpos.info.Pkg) 186 for _, ref := range r.refs { 187 // Avoid printing "type T T". 188 var typstr string 189 if ref.kind != "type" { 190 typstr = " " + types.TypeString(ref.typ, qualifier) 191 } 192 printf(ref.obj, "%s %s%s", ref.kind, ref.ref, typstr) 193 } 194 } 195 } 196 197 func (r *freevarsResult) toSerial(res *serial.Result, fset *token.FileSet) { 198 var refs []*serial.FreeVar 199 for _, ref := range r.refs { 200 refs = append(refs, 201 &serial.FreeVar{ 202 Pos: fset.Position(ref.obj.Pos()).String(), 203 Kind: ref.kind, 204 Ref: ref.ref, 205 Type: ref.typ.String(), 206 }) 207 } 208 res.Freevars = refs 209 } 210 211 // -------- utils -------- 212 213 type byRef []freevarsRef 214 215 func (p byRef) Len() int { return len(p) } 216 func (p byRef) Less(i, j int) bool { return p[i].ref < p[j].ref } 217 func (p byRef) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 218 219 // printNode returns the pretty-printed syntax of n. 220 func printNode(fset *token.FileSet, n ast.Node) string { 221 var buf bytes.Buffer 222 printer.Fprint(&buf, fset, n) 223 return buf.String() 224 }