GoPLS Viewer

Home|gopls/cmd/guru/freevars.go
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
5package main
6
7import (
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.
31func freevars(q *Queryerror {
32    lconf := loader.Config{Buildq.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    lprogerr := lconf.Load()
41    if err != nil {
42        return err
43    }
44
45    qposerr := parseQueryPos(lprogq.Posfalse)
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.Identtypes.Object
59    var sel func(n *ast.SelectorExprtypes.Object
60
61    sel = func(n *ast.SelectorExprtypes.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.Identtypes.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.Nodebool {
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{kindprintNode(lprog.Fsetn), typobj}
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([]freevarsRef0len(refsMap))
152    for _ref := range refsMap {
153        refs = append(refsref)
154    }
155    sort.Sort(byRef(refs))
156
157    q.Output(lprog.Fset, &freevarsResult{
158        qposqpos,
159        refsrefs,
160    })
161    return nil
162}
163
164type freevarsResult struct {
165    qpos *queryPos
166    refs []freevarsRef
167}
168
169type freevarsRef struct {
170    kind string
171    ref  string
172    typ  types.Type
173    obj  types.Object
174}
175
176func (r *freevarsResultPrintPlain(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.typqualifier)
187            }
188            printf(ref.obj"%s %s%s"ref.kindref.reftypstr)
189        }
190    }
191}
192
193func (r *freevarsResultJSON(fset *token.FileSet) []byte {
194    var buf bytes.Buffer
195    for iref := 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            Kindref.kind,
202            Ref:  ref.ref,
203            Typeref.typ.String(),
204        }))
205    }
206    return buf.Bytes()
207}
208
209// -------- utils --------
210
211type byRef []freevarsRef
212
213func (p byRefLen() int           { return len(p) }
214func (p byRefLess(ij intbool { return p[i].ref < p[j].ref }
215func (p byRefSwap(ij int)      { p[i], p[j] = p[j], p[i] }
216
217// printNode returns the pretty-printed syntax of n.
218func printNode(fset *token.FileSetn ast.Nodestring {
219    var buf bytes.Buffer
220    printer.Fprint(&buffsetn)
221    return buf.String()
222}
223
MembersX
freevars.qpos
freevars.refsMap
byRef.Less
freevars.err
freevars.BlockStmt.BlockStmt.obj
freevars.BlockStmt.BlockStmt.BlockStmt.kind
freevars.lprog
freevars.BlockStmt.scope
freevarsResult.JSON.buf
freevarsResult.JSON.RangeStmt_4639.ref
byRef
freevars.BlockStmt.BlockStmt.BlockStmt.ref
freevarsResult.PrintPlain.BlockStmt.RangeStmt_4295.ref
printNode
byRef.Less.i
freevarsResult.JSON.RangeStmt_4639.i
freevarsResult.JSON.fset
printer
freevars.BlockStmt.BlockStmt.BlockStmt.typ
freevarsResult.PrintPlain.BlockStmt.qualifier
freevarsResult.PrintPlain
printNode.fset
freevars.q
freevarsResult.PrintPlain.r
byRef.Swap.i
freevarsRef
freevarsRef.ref
freevarsResult.PrintPlain.printf
printNode.n
freevarsRef.typ
byRef.Len
byRef.Less.p
freevarsResult
byRef.Less.j
freevars.BlockStmt.BlockStmt.prune
freevarsRef.kind
freevarsResult.JSON
byRef.Swap
freevars.lconf
freevars.refs
freevars.RangeStmt_3745.ref
freevarsResult.refs
freevarsResult.JSON.r
byRef.Len.p
freevars
freevars._
freevars.pkgScope
freevarsRef.obj
byRef.Swap.p
freevarsResult.qpos
byRef.Swap.j
printNode.buf
freevarsResult.PrintPlain.BlockStmt.RangeStmt_4295.BlockStmt.typstr
Members
X