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.

282 lines
7.3 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. "fmt"
  7. "go/ast"
  8. "go/build"
  9. "go/token"
  10. "os"
  11. "path/filepath"
  12. "sort"
  13. "strings"
  14. "golang.org/x/tools/cmd/guru/serial"
  15. "golang.org/x/tools/go/ast/astutil"
  16. )
  17. // what reports all the information about the query selection that can be
  18. // obtained from parsing only its containing source file.
  19. // It is intended to be a very low-latency query callable from GUI
  20. // tools, e.g. to populate a menu of options of slower queries about
  21. // the selected location.
  22. //
  23. func what(q *Query) error {
  24. qpos, err := fastQueryPos(q.Build, q.Pos)
  25. if err != nil {
  26. return err
  27. }
  28. // (ignore errors)
  29. srcdir, importPath, _ := guessImportPath(qpos.fset.File(qpos.start).Name(), q.Build)
  30. // Determine which query modes are applicable to the selection.
  31. enable := map[string]bool{
  32. "describe": true, // any syntax; always enabled
  33. }
  34. if qpos.end > qpos.start {
  35. enable["freevars"] = true // nonempty selection?
  36. }
  37. for _, n := range qpos.path {
  38. switch n := n.(type) {
  39. case *ast.Ident:
  40. enable["definition"] = true
  41. enable["referrers"] = true
  42. enable["implements"] = true
  43. case *ast.CallExpr:
  44. enable["callees"] = true
  45. case *ast.FuncDecl:
  46. enable["callers"] = true
  47. enable["callstack"] = true
  48. case *ast.SendStmt:
  49. enable["peers"] = true
  50. case *ast.UnaryExpr:
  51. if n.Op == token.ARROW {
  52. enable["peers"] = true
  53. }
  54. }
  55. // For implements, we approximate findInterestingNode.
  56. if _, ok := enable["implements"]; !ok {
  57. switch n.(type) {
  58. case *ast.ArrayType,
  59. *ast.StructType,
  60. *ast.FuncType,
  61. *ast.InterfaceType,
  62. *ast.MapType,
  63. *ast.ChanType:
  64. enable["implements"] = true
  65. }
  66. }
  67. // For pointsto and whicherrs, we approximate findInterestingNode.
  68. if _, ok := enable["pointsto"]; !ok {
  69. switch n.(type) {
  70. case ast.Stmt,
  71. *ast.ArrayType,
  72. *ast.StructType,
  73. *ast.FuncType,
  74. *ast.InterfaceType,
  75. *ast.MapType,
  76. *ast.ChanType:
  77. // not an expression
  78. enable["pointsto"] = false
  79. enable["whicherrs"] = false
  80. case ast.Expr, ast.Decl, *ast.ValueSpec:
  81. // an expression, maybe
  82. enable["pointsto"] = true
  83. enable["whicherrs"] = true
  84. default:
  85. // Comment, Field, KeyValueExpr, etc: ascend.
  86. }
  87. }
  88. }
  89. // If we don't have an exact selection, disable modes that need one.
  90. if !qpos.exact {
  91. enable["callees"] = false
  92. enable["pointsto"] = false
  93. enable["whicherrs"] = false
  94. enable["describe"] = false
  95. }
  96. var modes []string
  97. for mode := range enable {
  98. modes = append(modes, mode)
  99. }
  100. sort.Strings(modes)
  101. // Find the object referred to by the selection (if it's an
  102. // identifier) and report the position of each identifier
  103. // that refers to the same object.
  104. //
  105. // This may return spurious matches (e.g. struct fields) because
  106. // it uses the best-effort name resolution done by go/parser.
  107. var sameids []token.Pos
  108. var object string
  109. if id, ok := qpos.path[0].(*ast.Ident); ok {
  110. if id.Obj == nil {
  111. // An unresolved identifier is potentially a package name.
  112. // Resolve them with a simple importer (adds ~100µs).
  113. importer := func(imports map[string]*ast.Object, path string) (*ast.Object, error) {
  114. pkg, ok := imports[path]
  115. if !ok {
  116. pkg = &ast.Object{
  117. Kind: ast.Pkg,
  118. Name: filepath.Base(path), // a guess
  119. }
  120. imports[path] = pkg
  121. }
  122. return pkg, nil
  123. }
  124. f := qpos.path[len(qpos.path)-1].(*ast.File)
  125. ast.NewPackage(qpos.fset, map[string]*ast.File{"": f}, importer, nil)
  126. }
  127. if id.Obj != nil {
  128. object = id.Obj.Name
  129. decl := qpos.path[len(qpos.path)-1]
  130. ast.Inspect(decl, func(n ast.Node) bool {
  131. if n, ok := n.(*ast.Ident); ok && n.Obj == id.Obj {
  132. sameids = append(sameids, n.Pos())
  133. }
  134. return true
  135. })
  136. }
  137. }
  138. q.Output(qpos.fset, &whatResult{
  139. path: qpos.path,
  140. srcdir: srcdir,
  141. importPath: importPath,
  142. modes: modes,
  143. object: object,
  144. sameids: sameids,
  145. })
  146. return nil
  147. }
  148. // guessImportPath finds the package containing filename, and returns
  149. // its source directory (an element of $GOPATH) and its import path
  150. // relative to it.
  151. //
  152. // TODO(adonovan): what about _test.go files that are not part of the
  153. // package?
  154. //
  155. func guessImportPath(filename string, buildContext *build.Context) (srcdir, importPath string, err error) {
  156. absFile, err := filepath.Abs(filename)
  157. if err != nil {
  158. return "", "", fmt.Errorf("can't form absolute path of %s: %v", filename, err)
  159. }
  160. absFileDir := filepath.Dir(absFile)
  161. resolvedAbsFileDir, err := filepath.EvalSymlinks(absFileDir)
  162. if err != nil {
  163. return "", "", fmt.Errorf("can't evaluate symlinks of %s: %v", absFileDir, err)
  164. }
  165. segmentedAbsFileDir := segments(resolvedAbsFileDir)
  166. // Find the innermost directory in $GOPATH that encloses filename.
  167. minD := 1024
  168. for _, gopathDir := range buildContext.SrcDirs() {
  169. absDir, err := filepath.Abs(gopathDir)
  170. if err != nil {
  171. continue // e.g. non-existent dir on $GOPATH
  172. }
  173. resolvedAbsDir, err := filepath.EvalSymlinks(absDir)
  174. if err != nil {
  175. continue // e.g. non-existent dir on $GOPATH
  176. }
  177. d := prefixLen(segments(resolvedAbsDir), segmentedAbsFileDir)
  178. // If there are multiple matches,
  179. // prefer the innermost enclosing directory
  180. // (smallest d).
  181. if d >= 0 && d < minD {
  182. minD = d
  183. srcdir = gopathDir
  184. importPath = strings.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:], string(os.PathSeparator))
  185. }
  186. }
  187. if srcdir == "" {
  188. return "", "", fmt.Errorf("directory %s is not beneath any of these GOROOT/GOPATH directories: %s",
  189. filepath.Dir(absFile), strings.Join(buildContext.SrcDirs(), ", "))
  190. }
  191. if importPath == "" {
  192. // This happens for e.g. $GOPATH/src/a.go, but
  193. // "" is not a valid path for (*go/build).Import.
  194. return "", "", fmt.Errorf("cannot load package in root of source directory %s", srcdir)
  195. }
  196. return srcdir, importPath, nil
  197. }
  198. func segments(path string) []string {
  199. return strings.Split(path, string(os.PathSeparator))
  200. }
  201. // prefixLen returns the length of the remainder of y if x is a prefix
  202. // of y, a negative number otherwise.
  203. func prefixLen(x, y []string) int {
  204. d := len(y) - len(x)
  205. if d >= 0 {
  206. for i := range x {
  207. if y[i] != x[i] {
  208. return -1 // not a prefix
  209. }
  210. }
  211. }
  212. return d
  213. }
  214. type whatResult struct {
  215. path []ast.Node
  216. modes []string
  217. srcdir string
  218. importPath string
  219. object string
  220. sameids []token.Pos
  221. }
  222. func (r *whatResult) PrintPlain(printf printfFunc) {
  223. for _, n := range r.path {
  224. printf(n, "%s", astutil.NodeDescription(n))
  225. }
  226. printf(nil, "modes: %s", r.modes)
  227. printf(nil, "srcdir: %s", r.srcdir)
  228. printf(nil, "import path: %s", r.importPath)
  229. for _, pos := range r.sameids {
  230. printf(pos, "%s", r.object)
  231. }
  232. }
  233. func (r *whatResult) JSON(fset *token.FileSet) []byte {
  234. var enclosing []serial.SyntaxNode
  235. for _, n := range r.path {
  236. enclosing = append(enclosing, serial.SyntaxNode{
  237. Description: astutil.NodeDescription(n),
  238. Start: fset.Position(n.Pos()).Offset,
  239. End: fset.Position(n.End()).Offset,
  240. })
  241. }
  242. var sameids []string
  243. for _, pos := range r.sameids {
  244. sameids = append(sameids, fset.Position(pos).String())
  245. }
  246. return toJSON(&serial.What{
  247. Modes: r.modes,
  248. SrcDir: r.srcdir,
  249. ImportPath: r.importPath,
  250. Enclosing: enclosing,
  251. Object: r.object,
  252. SameIDs: sameids,
  253. })
  254. }