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