GoPLS Viewer

Home|gopls/cmd/guru/definition.go
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 main
6
7import (
8    "fmt"
9    "go/ast"
10    "go/build"
11    "go/parser"
12    "go/token"
13    pathpkg "path"
14    "path/filepath"
15    "strconv"
16
17    "golang.org/x/tools/cmd/guru/serial"
18    "golang.org/x/tools/go/buildutil"
19    "golang.org/x/tools/go/loader"
20)
21
22// definition reports the location of the definition of an identifier.
23func definition(q *Queryerror {
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        qposerr := fastQueryPos(q.Buildq.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.Objobj != nil && obj.Pos().IsValid() {
41            q.Output(qpos.fset, &definitionResult{
42                pos:   obj.Pos(),
43                descrfmt.Sprintf("%s %s"obj.Kindobj.Name),
44            })
45            return nil // success
46        }
47
48        // Qualified identifier?
49        if pkg := packageForQualIdent(qpos.pathid); pkg != "" {
50            srcdir := filepath.Dir(qpos.fset.File(qpos.start).Name())
51            tokposerr := findPackageMember(q.Buildqpos.fsetsrcdirpkgid.Name)
52            if err != nil {
53                return err
54            }
55            q.Output(qpos.fset, &definitionResult{
56                pos:   pos,
57                descrfmt.Sprintf("%s %s.%s"tokpkgid.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{Buildq.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    lprogerr := lconf.Load()
75    if err != nil {
76        return err
77    }
78
79    qposerr := parseQueryPos(lprogq.Posfalse)
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        descrqpos.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).
122func packageForQualIdent(path []ast.Nodeid *ast.Identstring {
123    if selok := path[1].(*ast.SelectorExpr); ok && sel.Sel == id && ast.IsExported(id.Name) {
124        if pkgidok := 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.
144func findPackageMember(ctxt *build.Contextfset *token.FileSetsrcdirpkgmember string) (token.Tokentoken.Poserror) {
145    bperr := ctxt.Import(pkgsrcdir0)
146    if err != nil {
147        return 0token.NoPoserr // no files for package
148    }
149
150    // TODO(adonovan): opt: parallelize.
151    for _fname := range bp.GoFiles {
152        filename := filepath.Join(bp.Dirfname)
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(fsetctxtnil"."filenameparser.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.Tokid.Pos(), nil
172                            }
173                        }
174                    case *ast.TypeSpec:
175                        if spec.Name.Name == member {
176                            return token.TYPEspec.Name.Pos(), nil
177                        }
178                    }
179                }
180            case *ast.FuncDecl:
181                if decl.Recv == nil && decl.Name.Name == member {
182                    return token.FUNCdecl.Name.Pos(), nil
183                }
184            }
185        }
186    }
187
188    return 0token.NoPosfmt.Errorf("couldn't find declaration of %s in %q"memberpkg)
189}
190
191type definitionResult struct {
192    pos   token.Pos // (nonzero) location of definition
193    descr string    // description of object it denotes
194}
195
196func (r *definitionResultPrintPlain(printf printfFunc) {
197    printf(r.pos"defined here as %s"r.descr)
198}
199
200func (r *definitionResultJSON(fset *token.FileSet) []byte {
201    return toJSON(&serial.Definition{
202        Desc:   r.descr,
203        ObjPosfset.Position(r.pos).String(),
204    })
205}
206
MembersX
buildutil
definition.BlockStmt.obj
definition.BlockStmt.BlockStmt.srcdir
definition.err
findPackageMember.srcdir
findPackageMember.err
findPackageMember.RangeStmt_4152.BlockStmt.filename
definition.qpos
packageForQualIdent
findPackageMember.RangeStmt_4152.BlockStmt.f
findPackageMember.RangeStmt_4152.BlockStmt.RangeStmt_4514.decl
findPackageMember.RangeStmt_4152.BlockStmt.RangeStmt_4514.BlockStmt.BlockStmt.RangeStmt_4603.BlockStmt.BlockStmt.RangeStmt_4725.id
definitionResult.pos
build
pathpkg
definition.BlockStmt.err
definition.BlockStmt.BlockStmt.tok
definition._
packageForQualIdent.id
findPackageMember
findPackageMember.RangeStmt_4152.fname
definition
definition.BlockStmt.pkg
packageForQualIdent.BlockStmt.BlockStmt.RangeStmt_3376.imp
findPackageMember.pkg
findPackageMember.member
findPackageMember.RangeStmt_4152.BlockStmt.RangeStmt_4514.BlockStmt.BlockStmt.RangeStmt_4603.spec
strconv
definition.BlockStmt.BlockStmt.err
packageForQualIdent.BlockStmt.BlockStmt.RangeStmt_3376.BlockStmt.path
findPackageMember.fset
findPackageMember.RangeStmt_4152.BlockStmt._
definitionResult.descr
definitionResult.PrintPlain.r
parser
filepath
definition.BlockStmt.BlockStmt.pos
findPackageMember.ctxt
definitionResult.PrintPlain
definitionResult.JSON.r
definition.q
definition.lprog
packageForQualIdent.path
findPackageMember.bp
definitionResult
definitionResult.PrintPlain.printf
definitionResult.JSON.fset
definition.BlockStmt.qpos
definition.lconf
packageForQualIdent.BlockStmt.BlockStmt.RangeStmt_3376.BlockStmt._
definitionResult.JSON
Members
X