| // Copyright 2018 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 source |
| |
| import ( |
| "context" |
| "fmt" |
| "go/ast" |
| "go/token" |
| |
| "golang.org/x/tools/go/ast/astutil" |
| ) |
| |
| func Definition(ctx context.Context, f *File, pos token.Pos) (Range, error) { |
| fAST, err := f.GetAST() |
| if err != nil { |
| return Range{}, err |
| } |
| pkg, err := f.GetPackage() |
| if err != nil { |
| return Range{}, err |
| } |
| ident, err := findIdentifier(fAST, pos) |
| if err != nil { |
| return Range{}, err |
| } |
| if ident == nil { |
| return Range{}, fmt.Errorf("definition was not a valid identifier") |
| } |
| obj := pkg.TypesInfo.ObjectOf(ident) |
| if obj == nil { |
| return Range{}, fmt.Errorf("no object") |
| } |
| return Range{ |
| Start: obj.Pos(), |
| End: obj.Pos() + token.Pos(len([]byte(obj.Name()))), // TODO: use real range of obj |
| }, nil |
| } |
| |
| // findIdentifier returns the ast.Ident for a position |
| // in a file, accounting for a potentially incomplete selector. |
| func findIdentifier(f *ast.File, pos token.Pos) (*ast.Ident, error) { |
| path, _ := astutil.PathEnclosingInterval(f, pos, pos) |
| if path == nil { |
| return nil, fmt.Errorf("can't find node enclosing position") |
| } |
| // If the position is not an identifier but immediately follows |
| // an identifier or selector period (as is common when |
| // requesting a completion), use the path to the preceding node. |
| if ident, ok := path[0].(*ast.Ident); ok { |
| return ident, nil |
| } |
| path, _ = astutil.PathEnclosingInterval(f, pos-1, pos-1) |
| if path == nil { |
| return nil, nil |
| } |
| switch prev := path[0].(type) { |
| case *ast.Ident: |
| return prev, nil |
| case *ast.SelectorExpr: |
| return prev.Sel, nil |
| } |
| return nil, nil |
| } |