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