You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

223 lines
5.2 KiB

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