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 oracle 6 7import ( 8 "fmt" 9 "go/ast" 10 "go/build" 11 "go/token" 12 "os" 13 "path/filepath" 14 "sort" 15 "strings" 16 17 "github.com/visualfc/gotools/oracle/oracle/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// 27func what(q *Query) error { 28 qpos, err := fastQueryPos(q.Pos) 29 if err != nil { 30 return err 31 } 32 q.Fset = qpos.fset 33 34 // (ignore errors) 35 srcdir, importPath, _ := guessImportPath(q.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, 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 enable["pointsto"] = false // not an expr 89 90 case ast.Expr, ast.Decl, *ast.ValueSpec: 91 enable["pointsto"] = true // an expr, maybe 92 93 default: 94 // Comment, Field, KeyValueExpr, etc: ascend. 95 } 96 } 97 } 98 99 // If we don't have an exact selection, disable modes that need one. 100 if !qpos.exact { 101 enable["callees"] = false 102 enable["pointsto"] = false 103 enable["whicherrs"] = false 104 enable["describe"] = false 105 } 106 107 var modes []string 108 for mode := range enable { 109 modes = append(modes, mode) 110 } 111 sort.Strings(modes) 112 113 q.result = &whatResult{ 114 path: qpos.path, 115 srcdir: srcdir, 116 importPath: importPath, 117 modes: modes, 118 } 119 return nil 120} 121 122// guessImportPath finds the package containing filename, and returns 123// its source directory (an element of $GOPATH) and its import path 124// relative to it. 125// 126// TODO(adonovan): what about _test.go files that are not part of the 127// package? 128// 129func guessImportPath(filename string, buildContext *build.Context) (srcdir, importPath string, err error) { 130 absFile, err := filepath.Abs(filename) 131 if err != nil { 132 err = fmt.Errorf("can't form absolute path of %s", filename) 133 return 134 } 135 absFileDir := segments(filepath.Dir(absFile)) 136 137 // Find the innermost directory in $GOPATH that encloses filename. 138 minD := 1024 139 for _, gopathDir := range buildContext.SrcDirs() { 140 absDir, err := filepath.Abs(gopathDir) 141 if err != nil { 142 continue // e.g. non-existent dir on $GOPATH 143 } 144 d := prefixLen(segments(absDir), absFileDir) 145 // If there are multiple matches, 146 // prefer the innermost enclosing directory 147 // (smallest d). 148 if d >= 0 && d < minD { 149 minD = d 150 srcdir = gopathDir 151 importPath = strings.Join(absFileDir[len(absFileDir)-minD:], string(os.PathSeparator)) 152 } 153 } 154 if srcdir == "" { 155 err = fmt.Errorf("directory %s is not beneath any of these GOROOT/GOPATH directories: %s", 156 filepath.Dir(absFile), strings.Join(buildContext.SrcDirs(), ", ")) 157 } 158 return 159} 160 161func segments(path string) []string { 162 return strings.Split(path, string(os.PathSeparator)) 163} 164 165// prefixLen returns the length of the remainder of y if x is a prefix 166// of y, a negative number otherwise. 167func prefixLen(x, y []string) int { 168 d := len(y) - len(x) 169 if d >= 0 { 170 for i := range x { 171 if y[i] != x[i] { 172 return -1 // not a prefix 173 } 174 } 175 } 176 return d 177} 178 179type whatResult struct { 180 path []ast.Node 181 modes []string 182 srcdir string 183 importPath string 184} 185 186func (r *whatResult) display(printf printfFunc) { 187 for _, n := range r.path { 188 printf(n, "%s", astutil.NodeDescription(n)) 189 } 190 printf(nil, "modes: %s", r.modes) 191 printf(nil, "srcdir: %s", r.srcdir) 192 printf(nil, "import path: %s", r.importPath) 193} 194 195func (r *whatResult) toSerial(res *serial.Result, fset *token.FileSet) { 196 var enclosing []serial.SyntaxNode 197 for _, n := range r.path { 198 enclosing = append(enclosing, serial.SyntaxNode{ 199 Description: astutil.NodeDescription(n), 200 Start: fset.Position(n.Pos()).Offset, 201 End: fset.Position(n.End()).Offset, 202 }) 203 } 204 res.What = &serial.What{ 205 Modes: r.modes, 206 SrcDir: r.srcdir, 207 ImportPath: r.importPath, 208 Enclosing: enclosing, 209 } 210} 211