1
0
mirror of https://github.com/golang/go synced 2024-11-19 04:44:41 -07:00
go/internal/lsp/source/definition.go
Ian Cottrell 69a2705782 internal/lsp: remove error return values from interface "field" accessors
we don't really use them, only generate them in cases where the failure is way more fundamental, and then also fail
to remember them for the next call to the same accessor. Better to not have them.

Change-Id: I0e8abeda688f5cc2a932ed95a80d89225c399f93
Reviewed-on: https://go-review.googlesource.com/c/162399
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-02-13 21:58:50 +00:00

173 lines
4.4 KiB
Go

// 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"
"go/types"
"golang.org/x/tools/go/ast/astutil"
)
// IdentifierInfo holds information about an identifier in Go source.
type IdentifierInfo struct {
Name string
Range Range
File File
Type struct {
Range Range
Object types.Object
}
Declaration struct {
Range Range
Object types.Object
}
ident *ast.Ident
wasEmbeddedField bool
}
// Identifier returns identifier information for a position
// in a file, accounting for a potentially incomplete selector.
func Identifier(ctx context.Context, v View, f File, pos token.Pos) (*IdentifierInfo, error) {
if result, err := identifier(ctx, v, f, pos); err != nil || result != nil {
return result, err
}
// 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.
result, err := identifier(ctx, v, f, pos-1)
if result == nil && err == nil {
err = fmt.Errorf("no identifier found")
}
return result, err
}
func (i *IdentifierInfo) Hover(q types.Qualifier) (string, error) {
if q == nil {
fAST := i.File.GetAST()
pkg := i.File.GetPackage()
q = qualifier(fAST, pkg.Types, pkg.TypesInfo)
}
return types.ObjectString(i.Declaration.Object, q), nil
}
// identifier checks a single position for a potential identifier.
func identifier(ctx context.Context, v View, f File, pos token.Pos) (*IdentifierInfo, error) {
fAST := f.GetAST()
pkg := f.GetPackage()
path, _ := astutil.PathEnclosingInterval(fAST, pos, pos)
result := &IdentifierInfo{
File: f,
}
if path == nil {
return nil, 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 {
return nil, nil
}
for _, n := range path[1:] {
if field, ok := n.(*ast.Field); ok {
result.wasEmbeddedField = len(field.Names) == 0
}
}
result.Name = result.ident.Name
result.Range = Range{Start: result.ident.Pos(), End: result.ident.End()}
result.Declaration.Object = pkg.TypesInfo.ObjectOf(result.ident)
if result.Declaration.Object == nil {
return nil, fmt.Errorf("no object for ident %v", result.Name)
}
if result.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 := result.Declaration.Object.(*types.Var); ok {
if n, ok := v.Type().(*types.Named); ok {
result.Declaration.Object = n.Obj()
}
}
}
var err error
if result.Declaration.Range, err = objToRange(ctx, v, result.Declaration.Object); err != nil {
return nil, err
}
typ := pkg.TypesInfo.TypeOf(result.ident)
if typ == nil {
return nil, fmt.Errorf("no type for %s", result.Name)
}
result.Type.Object = typeToObject(typ)
if result.Type.Object != nil {
if result.Type.Range, err = objToRange(ctx, v, result.Type.Object); err != nil {
return nil, err
}
}
return result, nil
}
func typeToObject(typ types.Type) types.Object {
switch typ := typ.(type) {
case *types.Named:
return typ.Obj()
case *types.Pointer:
return typeToObject(typ.Elem())
default:
return nil
}
}
func objToRange(ctx context.Context, v View, obj types.Object) (Range, error) {
p := obj.Pos()
if !p.IsValid() {
return Range{}, fmt.Errorf("invalid position for %v", obj.Name())
}
return Range{
Start: p,
End: p + token.Pos(identifierLen(obj.Name())),
}, nil
}
// TODO: This needs to be fixed to address golang.org/issue/29149.
func identifierLen(ident string) int {
return len([]byte(ident))
}
// this functionality was borrowed from the analysisutil package
func lineStart(f *token.File, line int) token.Pos {
// Use binary search to find the start offset of this line.
//
// TODO(rstambler): eventually replace this function with the
// simpler and more efficient (*go/token.File).LineStart, added
// in go1.12.
min := 0 // inclusive
max := f.Size() // exclusive
for {
offset := (min + max) / 2
pos := f.Pos(offset)
posn := f.Position(pos)
if posn.Line == line {
return pos - (token.Pos(posn.Column) - 1)
}
if min+1 >= max {
return token.NoPos
}
if posn.Line < line {
min = offset
} else {
max = offset
}
}
}