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