github.com/april1989/origin-go-tools@v0.0.32/cmd/guru/definition.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/parser" 12 "go/token" 13 pathpkg "path" 14 "path/filepath" 15 "strconv" 16 17 "github.com/april1989/origin-go-tools/cmd/guru/serial" 18 "github.com/april1989/origin-go-tools/go/buildutil" 19 "github.com/april1989/origin-go-tools/go/loader" 20 ) 21 22 // definition reports the location of the definition of an identifier. 23 func definition(q *Query) error { 24 // First try the simple resolution done by parser. 25 // It only works for intra-file references but it is very fast. 26 // (Extending this approach to all the files of the package, 27 // resolved using ast.NewPackage, was not worth the effort.) 28 { 29 qpos, err := fastQueryPos(q.Build, q.Pos) 30 if err != nil { 31 return err 32 } 33 34 id, _ := qpos.path[0].(*ast.Ident) 35 if id == nil { 36 return fmt.Errorf("no identifier here") 37 } 38 39 // Did the parser resolve it to a local object? 40 if obj := id.Obj; obj != nil && obj.Pos().IsValid() { 41 q.Output(qpos.fset, &definitionResult{ 42 pos: obj.Pos(), 43 descr: fmt.Sprintf("%s %s", obj.Kind, obj.Name), 44 }) 45 return nil // success 46 } 47 48 // Qualified identifier? 49 if pkg := packageForQualIdent(qpos.path, id); pkg != "" { 50 srcdir := filepath.Dir(qpos.fset.File(qpos.start).Name()) 51 tok, pos, err := findPackageMember(q.Build, qpos.fset, srcdir, pkg, id.Name) 52 if err != nil { 53 return err 54 } 55 q.Output(qpos.fset, &definitionResult{ 56 pos: pos, 57 descr: fmt.Sprintf("%s %s.%s", tok, pkg, id.Name), 58 }) 59 return nil // success 60 } 61 62 // Fall back on the type checker. 63 } 64 65 // Run the type checker. 66 lconf := loader.Config{Build: q.Build} 67 allowErrors(&lconf) 68 69 if _, err := importQueryPackage(q.Pos, &lconf); err != nil { 70 return err 71 } 72 73 // Load/parse/type-check the program. 74 lprog, err := lconf.Load() 75 if err != nil { 76 return err 77 } 78 79 qpos, err := parseQueryPos(lprog, q.Pos, false) 80 if err != nil { 81 return err 82 } 83 84 id, _ := qpos.path[0].(*ast.Ident) 85 if id == nil { 86 return fmt.Errorf("no identifier here") 87 } 88 89 // Look up the declaration of this identifier. 90 // If id is an anonymous field declaration, 91 // it is both a use of a type and a def of a field; 92 // prefer the use in that case. 93 obj := qpos.info.Uses[id] 94 if obj == nil { 95 obj = qpos.info.Defs[id] 96 if obj == nil { 97 // Happens for y in "switch y := x.(type)", 98 // and the package declaration, 99 // but I think that's all. 100 return fmt.Errorf("no object for identifier") 101 } 102 } 103 104 if !obj.Pos().IsValid() { 105 return fmt.Errorf("%s is built in", obj.Name()) 106 } 107 108 q.Output(lprog.Fset, &definitionResult{ 109 pos: obj.Pos(), 110 descr: qpos.objectString(obj), 111 }) 112 return nil 113 } 114 115 // packageForQualIdent returns the package p if id is X in a qualified 116 // identifier p.X; it returns "" otherwise. 117 // 118 // Precondition: id is path[0], and the parser did not resolve id to a 119 // local object. For speed, packageForQualIdent assumes that p is a 120 // package iff it is the basename of an import path (and not, say, a 121 // package-level decl in another file or a predeclared identifier). 122 func packageForQualIdent(path []ast.Node, id *ast.Ident) string { 123 if sel, ok := path[1].(*ast.SelectorExpr); ok && sel.Sel == id && ast.IsExported(id.Name) { 124 if pkgid, ok := sel.X.(*ast.Ident); ok && pkgid.Obj == nil { 125 f := path[len(path)-1].(*ast.File) 126 for _, imp := range f.Imports { 127 path, _ := strconv.Unquote(imp.Path.Value) 128 if imp.Name != nil { 129 if imp.Name.Name == pkgid.Name { 130 return path // renaming import 131 } 132 } else if pathpkg.Base(path) == pkgid.Name { 133 return path // ordinary import 134 } 135 } 136 } 137 } 138 return "" 139 } 140 141 // findPackageMember returns the type and position of the declaration of 142 // pkg.member by loading and parsing the files of that package. 143 // srcdir is the directory in which the import appears. 144 func findPackageMember(ctxt *build.Context, fset *token.FileSet, srcdir, pkg, member string) (token.Token, token.Pos, error) { 145 bp, err := ctxt.Import(pkg, srcdir, 0) 146 if err != nil { 147 return 0, token.NoPos, err // no files for package 148 } 149 150 // TODO(adonovan): opt: parallelize. 151 for _, fname := range bp.GoFiles { 152 filename := filepath.Join(bp.Dir, fname) 153 154 // Parse the file, opening it the file via the build.Context 155 // so that we observe the effects of the -modified flag. 156 f, _ := buildutil.ParseFile(fset, ctxt, nil, ".", filename, parser.Mode(0)) 157 if f == nil { 158 continue 159 } 160 161 // Find a package-level decl called 'member'. 162 for _, decl := range f.Decls { 163 switch decl := decl.(type) { 164 case *ast.GenDecl: 165 for _, spec := range decl.Specs { 166 switch spec := spec.(type) { 167 case *ast.ValueSpec: 168 // const or var 169 for _, id := range spec.Names { 170 if id.Name == member { 171 return decl.Tok, id.Pos(), nil 172 } 173 } 174 case *ast.TypeSpec: 175 if spec.Name.Name == member { 176 return token.TYPE, spec.Name.Pos(), nil 177 } 178 } 179 } 180 case *ast.FuncDecl: 181 if decl.Recv == nil && decl.Name.Name == member { 182 return token.FUNC, decl.Name.Pos(), nil 183 } 184 } 185 } 186 } 187 188 return 0, token.NoPos, fmt.Errorf("couldn't find declaration of %s in %q", member, pkg) 189 } 190 191 type definitionResult struct { 192 pos token.Pos // (nonzero) location of definition 193 descr string // description of object it denotes 194 } 195 196 func (r *definitionResult) PrintPlain(printf printfFunc) { 197 printf(r.pos, "defined here as %s", r.descr) 198 } 199 200 func (r *definitionResult) JSON(fset *token.FileSet) []byte { 201 return toJSON(&serial.Definition{ 202 Desc: r.descr, 203 ObjPos: fset.Position(r.pos).String(), 204 }) 205 }