|  | // Copyright 2013 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package main | 
|  |  | 
|  | import ( | 
|  | "fmt" | 
|  | "go/ast" | 
|  | "go/build" | 
|  | "go/token" | 
|  | "os" | 
|  | "path" | 
|  | "path/filepath" | 
|  | "sort" | 
|  | "strings" | 
|  |  | 
|  | "golang.org/x/tools/cmd/guru/serial" | 
|  | "golang.org/x/tools/go/ast/astutil" | 
|  | ) | 
|  |  | 
|  | // what reports all the information about the query selection that can be | 
|  | // obtained from parsing only its containing source file. | 
|  | // It is intended to be a very low-latency query callable from GUI | 
|  | // tools, e.g. to populate a menu of options of slower queries about | 
|  | // the selected location. | 
|  | func what(q *Query) error { | 
|  | qpos, err := fastQueryPos(q.Build, q.Pos) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | // (ignore errors) | 
|  | srcdir, importPath, _ := guessImportPath(qpos.fset.File(qpos.start).Name(), q.Build) | 
|  |  | 
|  | // Determine which query modes are applicable to the selection. | 
|  | enable := map[string]bool{ | 
|  | "describe": true, // any syntax; always enabled | 
|  | } | 
|  |  | 
|  | if qpos.end > qpos.start { | 
|  | enable["freevars"] = true // nonempty selection? | 
|  | } | 
|  |  | 
|  | for _, n := range qpos.path { | 
|  | switch n.(type) { | 
|  | case *ast.Ident: | 
|  | enable["definition"] = true | 
|  | enable["referrers"] = true | 
|  | enable["implements"] = true | 
|  | } | 
|  |  | 
|  | // For implements, we approximate findInterestingNode. | 
|  | if _, ok := enable["implements"]; !ok { | 
|  | switch n.(type) { | 
|  | case *ast.ArrayType, | 
|  | *ast.StructType, | 
|  | *ast.FuncType, | 
|  | *ast.InterfaceType, | 
|  | *ast.MapType, | 
|  | *ast.ChanType: | 
|  | enable["implements"] = true | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // If we don't have an exact selection, disable modes that need one. | 
|  | if !qpos.exact { | 
|  | enable["describe"] = false | 
|  | } | 
|  |  | 
|  | var modes []string | 
|  | for mode := range enable { | 
|  | modes = append(modes, mode) | 
|  | } | 
|  | sort.Strings(modes) | 
|  |  | 
|  | // Find the object referred to by the selection (if it's an | 
|  | // identifier) and report the position of each identifier | 
|  | // that refers to the same object. | 
|  | // | 
|  | // This may return spurious matches (e.g. struct fields) because | 
|  | // it uses the best-effort name resolution done by go/parser. | 
|  | var sameids []token.Pos | 
|  | var object string | 
|  | if id, ok := qpos.path[0].(*ast.Ident); ok { | 
|  | if id.Obj == nil { | 
|  | // An unresolved identifier is potentially a package name. | 
|  | // Resolve them with a simple importer (adds ~100µs). | 
|  | importer := func(imports map[string]*ast.Object, path string) (*ast.Object, error) { | 
|  | pkg, ok := imports[path] | 
|  | if !ok { | 
|  | pkg = &ast.Object{ | 
|  | Kind: ast.Pkg, | 
|  | Name: filepath.Base(path), // a guess | 
|  | } | 
|  | imports[path] = pkg | 
|  | } | 
|  | return pkg, nil | 
|  | } | 
|  | f := qpos.path[len(qpos.path)-1].(*ast.File) | 
|  | ast.NewPackage(qpos.fset, map[string]*ast.File{"": f}, importer, nil) | 
|  | } | 
|  |  | 
|  | if id.Obj != nil { | 
|  | object = id.Obj.Name | 
|  | decl := qpos.path[len(qpos.path)-1] | 
|  | ast.Inspect(decl, func(n ast.Node) bool { | 
|  | if n, ok := n.(*ast.Ident); ok && n.Obj == id.Obj { | 
|  | sameids = append(sameids, n.Pos()) | 
|  | } | 
|  | return true | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | q.Output(qpos.fset, &whatResult{ | 
|  | path:       qpos.path, | 
|  | srcdir:     srcdir, | 
|  | importPath: importPath, | 
|  | modes:      modes, | 
|  | object:     object, | 
|  | sameids:    sameids, | 
|  | }) | 
|  | return nil | 
|  | } | 
|  |  | 
|  | // guessImportPath finds the package containing filename, and returns | 
|  | // its source directory (an element of $GOPATH) and its import path | 
|  | // relative to it. | 
|  | // | 
|  | // TODO(adonovan): what about _test.go files that are not part of the | 
|  | // package? | 
|  | func guessImportPath(filename string, buildContext *build.Context) (srcdir, importPath string, err error) { | 
|  | absFile, err := filepath.Abs(filename) | 
|  | if err != nil { | 
|  | return "", "", fmt.Errorf("can't form absolute path of %s: %v", filename, err) | 
|  | } | 
|  |  | 
|  | absFileDir := filepath.Dir(absFile) | 
|  | resolvedAbsFileDir, err := filepath.EvalSymlinks(absFileDir) | 
|  | if err != nil { | 
|  | return "", "", fmt.Errorf("can't evaluate symlinks of %s: %v", absFileDir, err) | 
|  | } | 
|  |  | 
|  | segmentedAbsFileDir := segments(resolvedAbsFileDir) | 
|  | // Find the innermost directory in $GOPATH that encloses filename. | 
|  | minD := 1024 | 
|  | for _, gopathDir := range buildContext.SrcDirs() { | 
|  | absDir, err := filepath.Abs(gopathDir) | 
|  | if err != nil { | 
|  | continue // e.g. non-existent dir on $GOPATH | 
|  | } | 
|  | resolvedAbsDir, err := filepath.EvalSymlinks(absDir) | 
|  | if err != nil { | 
|  | continue // e.g. non-existent dir on $GOPATH | 
|  | } | 
|  |  | 
|  | d := prefixLen(segments(resolvedAbsDir), segmentedAbsFileDir) | 
|  | // If there are multiple matches, | 
|  | // prefer the innermost enclosing directory | 
|  | // (smallest d). | 
|  | if d >= 0 && d < minD { | 
|  | minD = d | 
|  | srcdir = gopathDir | 
|  | importPath = path.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:]...) | 
|  | } | 
|  | } | 
|  | if srcdir == "" { | 
|  | return "", "", fmt.Errorf("directory %s is not beneath any of these GOROOT/GOPATH directories: %s", | 
|  | filepath.Dir(absFile), strings.Join(buildContext.SrcDirs(), ", ")) | 
|  | } | 
|  | if importPath == "" { | 
|  | // This happens for e.g. $GOPATH/src/a.go, but | 
|  | // "" is not a valid path for (*go/build).Import. | 
|  | return "", "", fmt.Errorf("cannot load package in root of source directory %s", srcdir) | 
|  | } | 
|  | return srcdir, importPath, nil | 
|  | } | 
|  |  | 
|  | func segments(path string) []string { | 
|  | return strings.Split(path, string(os.PathSeparator)) | 
|  | } | 
|  |  | 
|  | // prefixLen returns the length of the remainder of y if x is a prefix | 
|  | // of y, a negative number otherwise. | 
|  | func prefixLen(x, y []string) int { | 
|  | d := len(y) - len(x) | 
|  | if d >= 0 { | 
|  | for i := range x { | 
|  | if y[i] != x[i] { | 
|  | return -1 // not a prefix | 
|  | } | 
|  | } | 
|  | } | 
|  | return d | 
|  | } | 
|  |  | 
|  | type whatResult struct { | 
|  | path       []ast.Node | 
|  | modes      []string | 
|  | srcdir     string | 
|  | importPath string | 
|  | object     string | 
|  | sameids    []token.Pos | 
|  | } | 
|  |  | 
|  | func (r *whatResult) PrintPlain(printf printfFunc) { | 
|  | for _, n := range r.path { | 
|  | printf(n, "%s", astutil.NodeDescription(n)) | 
|  | } | 
|  | printf(nil, "modes: %s", r.modes) | 
|  | printf(nil, "srcdir: %s", r.srcdir) | 
|  | printf(nil, "import path: %s", r.importPath) | 
|  | for _, pos := range r.sameids { | 
|  | printf(pos, "%s", r.object) | 
|  | } | 
|  | } | 
|  |  | 
|  | func (r *whatResult) JSON(fset *token.FileSet) []byte { | 
|  | var enclosing []serial.SyntaxNode | 
|  | for _, n := range r.path { | 
|  | enclosing = append(enclosing, serial.SyntaxNode{ | 
|  | Description: astutil.NodeDescription(n), | 
|  | Start:       fset.Position(n.Pos()).Offset, | 
|  | End:         fset.Position(n.End()).Offset, | 
|  | }) | 
|  | } | 
|  |  | 
|  | var sameids []string | 
|  | for _, pos := range r.sameids { | 
|  | sameids = append(sameids, fset.Position(pos).String()) | 
|  | } | 
|  |  | 
|  | return toJSON(&serial.What{ | 
|  | Modes:      r.modes, | 
|  | SrcDir:     r.srcdir, | 
|  | ImportPath: r.importPath, | 
|  | Enclosing:  enclosing, | 
|  | Object:     r.object, | 
|  | SameIDs:    sameids, | 
|  | }) | 
|  | } |