From fc2e60c3c3d93bf3ed50cc552213b44c5799b69c Mon Sep 17 00:00:00 2001 From: Ian Cottrell Date: Wed, 14 Nov 2018 20:47:28 -0500 Subject: [PATCH] internal/lsp: make Definition handle embedded fields This change allows it to jump to the type if you are directly on the embedded field when you trigger go to definition. Change-Id: I48825a5a683e69c0714978c76b1d188d40b38c5d Reviewed-on: https://go-review.googlesource.com/c/149615 Run-TryBot: Ian Cottrell TryBot-Result: Gobot Gobot Reviewed-by: Rebecca Stambler --- internal/lsp/source/definition.go | 71 ++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/internal/lsp/source/definition.go b/internal/lsp/source/definition.go index 9886f3d435c..0df2bd139e9 100644 --- a/internal/lsp/source/definition.go +++ b/internal/lsp/source/definition.go @@ -9,6 +9,7 @@ import ( "fmt" "go/ast" "go/token" + "go/types" "golang.org/x/tools/go/ast/astutil" ) @@ -22,45 +23,73 @@ func Definition(ctx context.Context, f *File, pos token.Pos) (Range, error) { if err != nil { return Range{}, err } - ident, err := findIdentifier(fAST, pos) + i, err := findIdentifier(fAST, pos) if err != nil { return Range{}, err } - if ident == nil { + if i.ident == nil { return Range{}, fmt.Errorf("definition was not a valid identifier") } - obj := pkg.TypesInfo.ObjectOf(ident) + obj := pkg.TypesInfo.ObjectOf(i.ident) if obj == nil { return Range{}, fmt.Errorf("no object") } + if i.wasEmbeddedField { + // the original position was on the embedded field declaration + // so we try to dig out the type and jump to that instead + if v, ok := obj.(*types.Var); ok { + if n, ok := v.Type().(*types.Named); ok { + obj = n.Obj() + } + } + } return Range{ Start: obj.Pos(), End: obj.Pos() + token.Pos(len([]byte(obj.Name()))), // TODO: use real range of obj }, nil } +// ident returns the ident plus any extra information needed +type ident struct { + ident *ast.Ident + wasEmbeddedField bool +} + // 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") +func findIdentifier(f *ast.File, pos token.Pos) (ident, error) { + m, err := checkIdentifier(f, pos) + if err != nil { + return ident{}, err + } + if m.ident != nil { + return m, nil } // 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 + return checkIdentifier(f, pos-1) +} + +// checkIdentifier checks a single position for a potential identifier. +func checkIdentifier(f *ast.File, pos token.Pos) (ident, error) { + path, _ := astutil.PathEnclosingInterval(f, pos, pos) + result := ident{} + if path == nil { + return result, fmt.Errorf("can't find node enclosing position") + } + switch node := path[0].(type) { + case *ast.Ident: + result.ident = node + case *ast.SelectorExpr: + result.ident = node.Sel + } + if result.ident != nil { + for _, n := range path[1:] { + if field, ok := n.(*ast.Field); ok { + result.wasEmbeddedField = len(field.Names) == 0 + } + } + } + return result, nil }