internal/lsp: use protocol.Ranges for source.Identifier

Change-Id: I42cb957e3c1676e2ec7e3f50dd5e3613f3dd9555
Reviewed-on: https://go-review.googlesource.com/c/tools/+/191880
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
Rebecca Stambler 2019-08-27 00:26:45 -04:00
parent aed303cbaa
commit 42f498d34c
18 changed files with 339 additions and 268 deletions

1
go.sum
View File

@ -3,6 +3,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=

View File

@ -162,6 +162,10 @@ func (f *goFile) GetCachedPackage(ctx context.Context) (source.Package, error) {
}
f.mu.Unlock()
if len(cphs) == 0 {
return nil, errors.Errorf("no CheckPackageHandles for %s", f.URI())
}
cph, err := bestCheckPackageHandle(f.URI(), cphs)
if err != nil {
return nil, err
@ -202,6 +206,16 @@ func (f *goFile) wrongParseMode(ctx context.Context, mode source.ParseMode) bool
return true
}
func (f *goFile) Builtin() (*ast.File, bool) {
builtinPkg := f.View().BuiltinPackage()
for filename, file := range builtinPkg.Files {
if filename == f.URI().Filename() {
return file, true
}
}
return nil, false
}
// isDirty is true if the file needs to be type-checked.
// It assumes that the file's view's mutex is held by the caller.
func (f *goFile) isDirty(ctx context.Context) bool {

View File

@ -19,39 +19,20 @@ func (s *Server) definition(ctx context.Context, params *protocol.TextDocumentPo
if err != nil {
return nil, err
}
m, err := getMapper(ctx, f)
ident, err := source.Identifier(ctx, view, f, params.Position)
if err != nil {
return nil, err
}
spn, err := m.PointSpan(params.Position)
decRange, err := ident.Declaration.Range()
if err != nil {
return nil, err
}
rng, err := spn.Range(m.Converter)
if err != nil {
return nil, err
}
ident, err := source.Identifier(ctx, f, rng.Start)
if err != nil {
return nil, err
}
decSpan, err := ident.DeclarationRange().Span()
if err != nil {
return nil, err
}
decFile, err := getGoFile(ctx, view, decSpan.URI())
if err != nil {
return nil, err
}
decM, err := getMapper(ctx, decFile)
if err != nil {
return nil, err
}
loc, err := decM.Location(decSpan)
if err != nil {
return nil, err
}
return []protocol.Location{loc}, nil
return []protocol.Location{
{
URI: protocol.NewURI(ident.Declaration.URI()),
Range: decRange,
},
}, nil
}
func (s *Server) typeDefinition(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.Location, error) {
@ -61,37 +42,18 @@ func (s *Server) typeDefinition(ctx context.Context, params *protocol.TextDocume
if err != nil {
return nil, err
}
m, err := getMapper(ctx, f)
ident, err := source.Identifier(ctx, view, f, params.Position)
if err != nil {
return nil, err
}
spn, err := m.PointSpan(params.Position)
identRange, err := ident.Type.Range()
if err != nil {
return nil, err
}
rng, err := spn.Range(m.Converter)
if err != nil {
return nil, err
}
ident, err := source.Identifier(ctx, f, rng.Start)
if err != nil {
return nil, err
}
identSpan, err := ident.Type.Range.Span()
if err != nil {
return nil, err
}
identFile, err := getGoFile(ctx, view, identSpan.URI())
if err != nil {
return nil, err
}
identM, err := getMapper(ctx, identFile)
if err != nil {
return nil, err
}
loc, err := identM.Location(identSpan)
if err != nil {
return nil, err
}
return []protocol.Location{loc}, nil
return []protocol.Location{
{
URI: protocol.NewURI(ident.Type.URI()),
Range: identRange,
},
}, nil
}

View File

@ -38,19 +38,7 @@ func (s *Server) hover(ctx context.Context, params *protocol.TextDocumentPositio
if err != nil {
return nil, err
}
m, err := getMapper(ctx, f)
if err != nil {
return nil, err
}
spn, err := m.PointSpan(params.Position)
if err != nil {
return nil, err
}
identRange, err := spn.Range(m.Converter)
if err != nil {
return nil, err
}
ident, err := source.Identifier(ctx, f, identRange.Start)
ident, err := source.Identifier(ctx, view, f, params.Position)
if err != nil {
return nil, nil
}
@ -58,11 +46,7 @@ func (s *Server) hover(ctx context.Context, params *protocol.TextDocumentPositio
if err != nil {
return nil, err
}
identSpan, err := ident.Range.Span()
if err != nil {
return nil, err
}
rng, err := m.Range(identSpan)
rng, err := ident.Range()
if err != nil {
return nil, err
}

View File

@ -361,7 +361,7 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) {
} else {
locs, err = r.server.Definition(r.ctx, params)
if err != nil {
t.Fatalf("failed for %v: %v", d.Src, err)
t.Fatalf("failed for %v: %+v", d.Src, err)
}
hover, err = r.server.Hover(r.ctx, params)
}

View File

@ -21,44 +21,21 @@ func (s *Server) references(ctx context.Context, params *protocol.ReferenceParam
if err != nil {
return nil, err
}
m, err := getMapper(ctx, f)
if err != nil {
return nil, err
}
spn, err := m.PointSpan(params.Position)
if err != nil {
return nil, err
}
rng, err := spn.Range(m.Converter)
if err != nil {
return nil, err
}
// Find all references to the identifier at the position.
ident, err := source.Identifier(ctx, f, rng.Start)
ident, err := source.Identifier(ctx, view, f, params.Position)
if err != nil {
return nil, err
}
references, err := ident.References(ctx)
references, err := ident.References(ctx, view)
if err != nil {
log.Error(ctx, "no references", err, tag.Of("Identifier", ident.Name))
}
if params.Context.IncludeDeclaration {
// The declaration of this identifier may not be in the
// scope that we search for references, so make sure
// it is added to the beginning of the list if IncludeDeclaration
// was specified.
references = append([]*source.ReferenceInfo{
&source.ReferenceInfo{
Range: ident.DeclarationRange(),
},
}, references...)
}
// Get the location of each reference to return as the result.
locations := make([]protocol.Location, 0, len(references))
seen := make(map[span.Span]bool)
for _, ref := range references {
refSpan, err := ref.Range.Span()
refSpan, err := ref.Span()
if err != nil {
return nil, err
}
@ -66,20 +43,31 @@ func (s *Server) references(ctx context.Context, params *protocol.ReferenceParam
continue // already added this location
}
seen[refSpan] = true
refRange, err := ref.Range()
if err != nil {
return nil, err
}
locations = append(locations, protocol.Location{
URI: protocol.NewURI(ref.URI()),
Range: refRange,
})
}
// The declaration of this identifier may not be in the
// scope that we search for references, so make sure
// it is added to the beginning of the list if IncludeDeclaration
// was specified.
if params.Context.IncludeDeclaration {
rng, err := ident.Declaration.Range()
if err != nil {
return nil, err
}
locations = append([]protocol.Location{
{
URI: protocol.NewURI(ident.Declaration.URI()),
Range: rng,
},
}, locations...)
refFile, err := getGoFile(ctx, view, refSpan.URI())
if err != nil {
return nil, err
}
refM, err := getMapper(ctx, refFile)
if err != nil {
return nil, err
}
loc, err := refM.Location(refSpan)
if err != nil {
return nil, err
}
locations = append(locations, loc)
}
return locations, nil
}

View File

@ -19,23 +19,11 @@ func (s *Server) rename(ctx context.Context, params *protocol.RenameParams) (*pr
if err != nil {
return nil, err
}
m, err := getMapper(ctx, f)
ident, err := source.Identifier(ctx, view, f, params.Position)
if err != nil {
return nil, err
}
spn, err := m.PointSpan(params.Position)
if err != nil {
return nil, err
}
rng, err := spn.Range(m.Converter)
if err != nil {
return nil, err
}
ident, err := source.Identifier(ctx, f, rng.Start)
if err != nil {
return nil, err
}
edits, err := ident.Rename(ctx, params.NewName)
edits, err := ident.Rename(ctx, view, params.NewName)
if err != nil {
return nil, err
}

View File

@ -33,7 +33,7 @@ func (s *Server) signatureHelp(ctx context.Context, params *protocol.TextDocumen
if err != nil {
return nil, err
}
info, err := source.SignatureHelp(ctx, f, rng.Start)
info, err := source.SignatureHelp(ctx, view, f, params.Position)
if err != nil {
log.Print(ctx, "no signature help", tag.Of("At", rng), tag.Of("Failure", err))
return nil, nil

View File

@ -115,11 +115,11 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
}
// TODO(rstambler): Log errors when this feature is enabled.
if c.opts.WantDocumentaton {
declRange, err := objToRange(c.ctx, c.view.Session().Cache().FileSet(), obj)
declRange, err := objToRange(c.ctx, c.view, obj)
if err != nil {
goto Return
}
pos := declRange.FileSet.Position(declRange.Start)
pos := c.view.Session().Cache().FileSet().Position(declRange.spanRange.Start)
if !pos.IsValid() {
goto Return
}
@ -136,16 +136,20 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
if err != nil {
goto Return
}
var file *ast.File
for _, ph := range pkg.GetHandles() {
if ph.File().Identity().URI == gof.URI() {
file, _ = ph.Cached(c.ctx)
var ph ParseGoHandle
for _, h := range pkg.GetHandles() {
if h.File().Identity().URI == gof.URI() {
ph = h
}
}
if ph == nil {
goto Return
}
file, _ := ph.Cached(c.ctx)
if file == nil {
goto Return
}
ident, err := findIdentifier(c.ctx, gof, pkg, file, declRange.Start)
ident, err := findIdentifier(c.ctx, c.view, gof, pkg, file, declRange.spanRange.Start)
if err != nil {
goto Return
}

View File

@ -38,7 +38,7 @@ func (i *IdentifierInfo) Hover(ctx context.Context) (*HoverInformation, error) {
ctx, done := trace.StartSpan(ctx, "source.Hover")
defer done()
h, err := i.decl.hover(ctx)
h, err := i.Declaration.hover(ctx)
if err != nil {
return nil, err
}
@ -55,8 +55,8 @@ func (i *IdentifierInfo) Hover(ctx context.Context) (*HoverInformation, error) {
}
// Set the documentation.
if i.decl.obj != nil {
h.SingleLine = types.ObjectString(i.decl.obj, i.qf)
if i.Declaration.obj != nil {
h.SingleLine = types.ObjectString(i.Declaration.obj, i.qf)
}
if h.comment != nil {
h.FullDocumentation = h.comment.Text()
@ -65,7 +65,7 @@ func (i *IdentifierInfo) Hover(ctx context.Context) (*HoverInformation, error) {
return h, nil
}
func (d declaration) hover(ctx context.Context) (*HoverInformation, error) {
func (d Declaration) hover(ctx context.Context) (*HoverInformation, error) {
ctx, done := trace.StartSpan(ctx, "source.hover")
defer done()
obj := d.obj

View File

@ -12,6 +12,7 @@ import (
"strconv"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/telemetry/trace"
errors "golang.org/x/xerrors"
@ -19,14 +20,16 @@ import (
// IdentifierInfo holds information about an identifier in Go source.
type IdentifierInfo struct {
Name string
Range span.Range
File GoFile
Type struct {
Range span.Range
Name string
mappedRange
File GoFile
Type struct {
mappedRange
Object types.Object
}
decl declaration
Declaration Declaration
pkg Package
ident *ast.Ident
@ -34,44 +37,39 @@ type IdentifierInfo struct {
qf types.Qualifier
}
type declaration struct {
rng span.Range
type Declaration struct {
mappedRange
node ast.Node
obj types.Object
wasImplicit bool
}
func (i *IdentifierInfo) DeclarationRange() span.Range {
return i.decl.rng
}
// Identifier returns identifier information for a position
// in a file, accounting for a potentially incomplete selector.
func Identifier(ctx context.Context, f GoFile, pos token.Pos) (*IdentifierInfo, error) {
pkg, err := f.GetPackage(ctx)
func Identifier(ctx context.Context, view View, f GoFile, pos protocol.Position) (*IdentifierInfo, error) {
file, pkg, m, err := fileToMapper(ctx, view, f.URI())
if err != nil {
return nil, err
}
var file *ast.File
for _, ph := range pkg.GetHandles() {
if ph.File().Identity().URI == f.URI() {
file, err = ph.Cached(ctx)
}
}
if file == nil {
spn, err := m.PointSpan(pos)
if err != nil {
return nil, err
}
return findIdentifier(ctx, f, pkg, file, pos)
rng, err := spn.Range(m.Converter)
if err != nil {
return nil, err
}
return findIdentifier(ctx, view, f, pkg, file, rng.Start)
}
func findIdentifier(ctx context.Context, f GoFile, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
if result, err := identifier(ctx, f, pkg, file, pos); err != nil || result != nil {
func findIdentifier(ctx context.Context, view View, f GoFile, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
if result, err := identifier(ctx, view, f, pkg, file, 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, f, pkg, file, pos-1)
result, err := identifier(ctx, view, f, pkg, file, pos-1)
if result == nil && err == nil {
err = errors.Errorf("no identifier found for %s", f.FileSet().Position(pos))
}
@ -79,14 +77,14 @@ func findIdentifier(ctx context.Context, f GoFile, pkg Package, file *ast.File,
}
// identifier checks a single position for a potential identifier.
func identifier(ctx context.Context, f GoFile, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
func identifier(ctx context.Context, view View, f GoFile, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
ctx, done := trace.StartSpan(ctx, "source.identifier")
defer done()
var err error
// Handle import specs separately, as there is no formal position for a package declaration.
if result, err := importSpec(ctx, f, file, pkg, pos); result != nil || err != nil {
if result, err := importSpec(ctx, view, f, file, pkg, pos); result != nil || err != nil {
return result, err
}
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
@ -115,9 +113,11 @@ func identifier(ctx context.Context, f GoFile, pkg Package, file *ast.File, pos
}
}
result.Name = result.ident.Name
result.Range = span.NewRange(f.FileSet(), result.ident.Pos(), result.ident.End())
result.decl.obj = pkg.GetTypesInfo().ObjectOf(result.ident)
if result.decl.obj == nil {
if result.mappedRange, err = posToRange(ctx, view, result.ident.Pos(), result.ident.End()); err != nil {
return nil, err
}
result.Declaration.obj = pkg.GetTypesInfo().ObjectOf(result.ident)
if result.Declaration.obj == nil {
// If there was no types.Object for the declaration, there might be an implicit local variable
// declaration in a type switch.
if objs := typeSwitchVar(pkg.GetTypesInfo(), path); len(objs) > 0 {
@ -125,8 +125,8 @@ func identifier(ctx context.Context, f GoFile, pkg Package, file *ast.File, pos
// but all of the types.Objects associated with the usages of this variable can be
// used to connect it back to the declaration.
// Preserve the first of these objects and treat it as if it were the declaring object.
result.decl.obj = objs[0]
result.decl.wasImplicit = true
result.Declaration.obj = objs[0]
result.Declaration.wasImplicit = true
} else {
// Probably a type error.
return nil, errors.Errorf("no object for ident %v", result.Name)
@ -134,13 +134,13 @@ func identifier(ctx context.Context, f GoFile, pkg Package, file *ast.File, pos
}
// Handle builtins separately.
if result.decl.obj.Parent() == types.Universe {
if result.Declaration.obj.Parent() == types.Universe {
decl, ok := lookupBuiltinDecl(f.View(), result.Name).(ast.Node)
if !ok {
return nil, errors.Errorf("no declaration for %s", result.Name)
}
result.decl.node = decl
if result.decl.rng, err = posToRange(ctx, f.FileSet(), result.Name, decl.Pos()); err != nil {
result.Declaration.node = decl
if result.Declaration.mappedRange, err = nameToRange(ctx, view, decl.Pos(), result.Name); err != nil {
return nil, err
}
return result, nil
@ -149,26 +149,26 @@ func identifier(ctx context.Context, f GoFile, pkg Package, file *ast.File, pos
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.decl.obj.(*types.Var); ok {
if v, ok := result.Declaration.obj.(*types.Var); ok {
if typObj := typeToObject(v.Type()); typObj != nil {
result.decl.obj = typObj
result.Declaration.obj = typObj
}
}
}
for _, obj := range pkg.GetTypesInfo().Implicits {
if obj.Pos() == result.decl.obj.Pos() {
if obj.Pos() == result.Declaration.obj.Pos() {
// Mark this declaration as implicit, since it will not
// appear in a (*types.Info).Defs map.
result.decl.wasImplicit = true
result.Declaration.wasImplicit = true
break
}
}
if result.decl.rng, err = objToRange(ctx, f.FileSet(), result.decl.obj); err != nil {
if result.Declaration.mappedRange, err = objToRange(ctx, view, result.Declaration.obj); err != nil {
return nil, err
}
if result.decl.node, err = objToNode(ctx, f.View(), pkg.GetTypes(), result.decl.obj, result.decl.rng); err != nil {
if result.Declaration.node, err = objToNode(ctx, f.View(), pkg.GetTypes(), result.Declaration.obj, result.Declaration.mappedRange.spanRange); err != nil {
return nil, err
}
typ := pkg.GetTypesInfo().TypeOf(result.ident)
@ -182,7 +182,7 @@ func identifier(ctx context.Context, f GoFile, pkg Package, file *ast.File, pos
if hasErrorType(result.Type.Object) {
return result, nil
}
if result.Type.Range, err = objToRange(ctx, f.FileSet(), result.Type.Object); err != nil {
if result.Type.mappedRange, err = objToRange(ctx, view, result.Type.Object); err != nil {
return nil, err
}
}
@ -204,7 +204,7 @@ func hasErrorType(obj types.Object) bool {
return types.IsInterface(obj.Type()) && obj.Pkg() == nil && obj.Name() == "error"
}
func objToRange(ctx context.Context, fset *token.FileSet, obj types.Object) (span.Range, error) {
func objToRange(ctx context.Context, view View, obj types.Object) (mappedRange, error) {
if pkgName, ok := obj.(*types.PkgName); ok {
// An imported Go package has a package-local, unqualified name.
// When the name matches the imported package name, there is no
@ -217,17 +217,32 @@ func objToRange(ctx context.Context, fset *token.FileSet, obj types.Object) (spa
// When the identifier does not appear in the source, have the range
// of the object be the point at the beginning of the declaration.
if pkgName.Imported().Name() == pkgName.Name() {
return posToRange(ctx, fset, "", obj.Pos())
return nameToRange(ctx, view, obj.Pos(), "")
}
}
return posToRange(ctx, fset, obj.Name(), obj.Pos())
return nameToRange(ctx, view, obj.Pos(), obj.Name())
}
func posToRange(ctx context.Context, fset *token.FileSet, name string, pos token.Pos) (span.Range, error) {
func nameToRange(ctx context.Context, view View, pos token.Pos, name string) (mappedRange, error) {
return posToRange(ctx, view, pos, pos+token.Pos(len(name)))
}
func posToRange(ctx context.Context, view View, pos, end token.Pos) (mappedRange, error) {
if !pos.IsValid() {
return span.Range{}, errors.Errorf("invalid position for %v", name)
return mappedRange{}, errors.Errorf("invalid position for %v", pos)
}
return span.NewRange(fset, pos, pos+token.Pos(len(name))), nil
if !end.IsValid() {
return mappedRange{}, errors.Errorf("invalid position for %v", end)
}
posn := view.Session().Cache().FileSet().Position(pos)
_, m, err := cachedFileToMapper(ctx, view, span.FileURI(posn.Filename))
if err != nil {
return mappedRange{}, err
}
return mappedRange{
m: m,
spanRange: span.NewRange(view.Session().Cache().FileSet(), pos, end),
}, nil
}
func objToNode(ctx context.Context, view View, originPkg *types.Package, obj types.Object, rng span.Range) (ast.Decl, error) {
@ -279,7 +294,7 @@ func objToNode(ctx context.Context, view View, originPkg *types.Package, obj typ
}
// importSpec handles positions inside of an *ast.ImportSpec.
func importSpec(ctx context.Context, f GoFile, fAST *ast.File, pkg Package, pos token.Pos) (*IdentifierInfo, error) {
func importSpec(ctx context.Context, view View, f GoFile, fAST *ast.File, pkg Package, pos token.Pos) (*IdentifierInfo, error) {
var imp *ast.ImportSpec
for _, spec := range fAST.Imports {
if spec.Path.Pos() <= pos && pos < spec.Path.End() {
@ -294,10 +309,12 @@ func importSpec(ctx context.Context, f GoFile, fAST *ast.File, pkg Package, pos
return nil, errors.Errorf("import path not quoted: %s (%v)", imp.Path.Value, err)
}
result := &IdentifierInfo{
File: f,
Name: importPath,
Range: span.NewRange(f.FileSet(), imp.Pos(), imp.End()),
pkg: pkg,
File: f,
Name: importPath,
pkg: pkg,
}
if result.mappedRange, err = posToRange(ctx, view, imp.Pos(), imp.End()); err != nil {
return nil, err
}
// Consider the "declaration" of an import spec to be the imported package.
importedPkg, err := pkg.GetImport(ctx, importPath)
@ -317,8 +334,10 @@ func importSpec(ctx context.Context, f GoFile, fAST *ast.File, pkg Package, pos
if dest == nil {
return nil, errors.Errorf("package %q has no files", importPath)
}
result.decl.rng = span.NewRange(f.FileSet(), dest.Name.Pos(), dest.Name.End())
result.decl.node = imp
if result.Declaration.mappedRange, err = posToRange(ctx, view, dest.Pos(), dest.End()); err != nil {
return nil, err
}
result.Declaration.node = imp
return result, nil
}

View File

@ -9,15 +9,14 @@ import (
"go/ast"
"go/types"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/telemetry/trace"
errors "golang.org/x/xerrors"
)
// ReferenceInfo holds information about reference to an identifier in Go source.
type ReferenceInfo struct {
Name string
Range span.Range
Name string
mappedRange
ident *ast.Ident
obj types.Object
pkg Package
@ -26,13 +25,13 @@ type ReferenceInfo struct {
// References returns a list of references for a given identifier within the packages
// containing i.File. Declarations appear first in the result.
func (i *IdentifierInfo) References(ctx context.Context) ([]*ReferenceInfo, error) {
func (i *IdentifierInfo) References(ctx context.Context, view View) ([]*ReferenceInfo, error) {
ctx, done := trace.StartSpan(ctx, "source.References")
defer done()
var references []*ReferenceInfo
// If the object declaration is nil, assume it is an import spec and do not look for references.
if i.decl.obj == nil {
if i.Declaration.obj == nil {
return nil, errors.Errorf("no references for an import spec")
}
@ -46,43 +45,49 @@ func (i *IdentifierInfo) References(ctx context.Context) ([]*ReferenceInfo, erro
return nil, errors.Errorf("package %s has no types info", pkg.PkgPath())
}
if i.decl.wasImplicit {
if i.Declaration.wasImplicit {
// The definition is implicit, so we must add it separately.
// This occurs when the variable is declared in a type switch statement
// or is an implicit package name. Both implicits are local to a file.
references = append(references, &ReferenceInfo{
Name: i.decl.obj.Name(),
Range: i.decl.rng,
obj: i.decl.obj,
Name: i.Declaration.obj.Name(),
mappedRange: i.Declaration.mappedRange,
obj: i.Declaration.obj,
pkg: pkg,
isDeclaration: true,
})
}
for ident, obj := range info.Defs {
if obj == nil || !sameObj(obj, i.decl.obj) {
if obj == nil || !sameObj(obj, i.Declaration.obj) {
continue
}
// Add the declarations at the beginning of the references list.
references = append([]*ReferenceInfo{&ReferenceInfo{
reference := &ReferenceInfo{
Name: ident.Name,
Range: span.NewRange(i.File.FileSet(), ident.Pos(), ident.End()),
ident: ident,
obj: obj,
pkg: pkg,
isDeclaration: true,
}}, references...)
}
if reference.mappedRange, err = posToRange(ctx, view, ident.Pos(), ident.End()); err != nil {
return nil, err
}
// Add the declarations at the beginning of the references list.
references = append([]*ReferenceInfo{reference}, references...)
}
for ident, obj := range info.Uses {
if obj == nil || !sameObj(obj, i.decl.obj) {
if obj == nil || !sameObj(obj, i.Declaration.obj) {
continue
}
references = append(references, &ReferenceInfo{
reference := &ReferenceInfo{
Name: ident.Name,
Range: span.NewRange(i.File.FileSet(), ident.Pos(), ident.End()),
ident: ident,
pkg: pkg,
obj: obj,
})
}
if reference.mappedRange, err = posToRange(ctx, view, ident.Pos(), ident.End()); err != nil {
return nil, err
}
references = append(references, reference)
}
}

View File

@ -36,19 +36,19 @@ type renamer struct {
}
// Rename returns a map of TextEdits for each file modified when renaming a given identifier within a package.
func (i *IdentifierInfo) Rename(ctx context.Context, newName string) (map[span.URI][]diff.TextEdit, error) {
func (i *IdentifierInfo) Rename(ctx context.Context, view View, newName string) (map[span.URI][]diff.TextEdit, error) {
ctx, done := trace.StartSpan(ctx, "source.Rename")
defer done()
// If the object declaration is nil, assume it is an import spec.
if i.decl.obj == nil {
if i.Declaration.obj == nil {
// Find the corresponding package name for this import spec
// and rename that instead.
ident, err := i.getPkgName(ctx)
if err != nil {
return nil, err
}
return ident.Rename(ctx, newName)
return ident.Rename(ctx, view, newName)
}
if i.Name == newName {
return nil, errors.Errorf("old and new names are the same: %s", newName)
@ -57,18 +57,18 @@ func (i *IdentifierInfo) Rename(ctx context.Context, newName string) (map[span.U
return nil, errors.Errorf("invalid identifier to rename: %q", i.Name)
}
// Do not rename builtin identifiers.
if i.decl.obj.Parent() == types.Universe {
if i.Declaration.obj.Parent() == types.Universe {
return nil, errors.Errorf("cannot rename builtin %q", i.Name)
}
if i.pkg == nil || i.pkg.IsIllTyped() {
return nil, errors.Errorf("package for %s is ill typed", i.File.URI())
}
// Do not rename identifiers declared in another package.
if i.pkg.GetTypes() != i.decl.obj.Pkg() {
return nil, errors.Errorf("failed to rename because %q is declared in package %q", i.Name, i.decl.obj.Pkg().Name())
if i.pkg.GetTypes() != i.Declaration.obj.Pkg() {
return nil, errors.Errorf("failed to rename because %q is declared in package %q", i.Name, i.Declaration.obj.Pkg().Name())
}
refs, err := i.References(ctx)
refs, err := i.References(ctx, view)
if err != nil {
return nil, err
}
@ -112,8 +112,8 @@ func (i *IdentifierInfo) Rename(ctx context.Context, newName string) (map[span.U
// getPkgName gets the pkg name associated with an identifer representing
// the import path in an import spec.
func (i *IdentifierInfo) getPkgName(ctx context.Context) (*IdentifierInfo, error) {
file := i.File.FileSet().File(i.Range.Start)
pkgLine := file.Line(i.Range.Start)
file := i.File.FileSet().File(i.mappedRange.spanRange.Start)
pkgLine := file.Line(i.mappedRange.spanRange.Start)
for _, obj := range i.pkg.GetTypesInfo().Defs {
pkgName, ok := obj.(*types.PkgName)
@ -133,22 +133,22 @@ func (i *IdentifierInfo) getPkgName(ctx context.Context) (*IdentifierInfo, error
// getPkgNameIdentifier returns an IdentifierInfo representing pkgName.
// pkgName must be in the same package and file as ident.
func getPkgNameIdentifier(ctx context.Context, ident *IdentifierInfo, pkgName *types.PkgName) (*IdentifierInfo, error) {
decl := declaration{
decl := Declaration{
obj: pkgName,
wasImplicit: true,
}
var err error
if decl.rng, err = objToRange(ctx, ident.File.FileSet(), decl.obj); err != nil {
if decl.mappedRange, err = objToRange(ctx, ident.File.View(), decl.obj); err != nil {
return nil, err
}
if decl.node, err = objToNode(ctx, ident.File.View(), ident.pkg.GetTypes(), decl.obj, decl.rng); err != nil {
if decl.node, err = objToNode(ctx, ident.File.View(), ident.pkg.GetTypes(), decl.obj, decl.mappedRange.spanRange); err != nil {
return nil, err
}
return &IdentifierInfo{
Name: pkgName.Name(),
Range: decl.rng,
mappedRange: decl.mappedRange,
File: ident.File,
decl: decl,
Declaration: decl,
pkg: ident.pkg,
wasEmbeddedField: false,
qf: ident.qf,
@ -165,7 +165,7 @@ func (r *renamer) update() (map[span.URI][]diff.TextEdit, error) {
return nil, err
}
for _, ref := range r.refs {
refSpan, err := ref.Range.Span()
refSpan, err := ref.spanRange.Span()
if err != nil {
return nil, err
}

View File

@ -12,6 +12,7 @@ import (
"go/types"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/telemetry/trace"
errors "golang.org/x/xerrors"
)
@ -26,21 +27,25 @@ type ParameterInformation struct {
Label string
}
func SignatureHelp(ctx context.Context, f GoFile, pos token.Pos) (*SignatureInformation, error) {
func SignatureHelp(ctx context.Context, view View, f GoFile, pos protocol.Position) (*SignatureInformation, error) {
ctx, done := trace.StartSpan(ctx, "source.SignatureHelp")
defer done()
file, err := f.GetAST(ctx, ParseFull)
if file == nil {
file, pkg, m, err := fileToMapper(ctx, view, f.URI())
if err != nil {
return nil, err
}
pkg, err := f.GetPackage(ctx)
spn, err := m.PointSpan(pos)
if err != nil {
return nil, err
}
rng, err := spn.Range(m.Converter)
if err != nil {
return nil, err
}
// Find a call expression surrounding the query position.
var callExpr *ast.CallExpr
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
path, _ := astutil.PathEnclosingInterval(file, rng.Start, rng.Start)
if path == nil {
return nil, errors.Errorf("cannot find node enclosing position")
}
@ -48,7 +53,7 @@ FindCall:
for _, node := range path {
switch node := node.(type) {
case *ast.CallExpr:
if pos >= node.Lparen && pos <= node.Rparen {
if rng.Start >= node.Lparen && rng.Start <= node.Rparen {
callExpr = node
break FindCall
}
@ -76,7 +81,7 @@ FindCall:
// Handle builtin functions separately.
if obj, ok := obj.(*types.Builtin); ok {
return builtinSignature(ctx, f.View(), callExpr, obj.Name(), pos)
return builtinSignature(ctx, f.View(), callExpr, obj.Name(), rng.Start)
}
// Get the type information for the function being called.
@ -93,25 +98,25 @@ FindCall:
qf := qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo())
params := formatParams(sig.Params(), sig.Variadic(), qf)
results, writeResultParens := formatResults(sig.Results(), qf)
activeParam := activeParameter(callExpr, sig.Params().Len(), sig.Variadic(), pos)
activeParam := activeParameter(callExpr, sig.Params().Len(), sig.Variadic(), rng.Start)
var (
name string
comment *ast.CommentGroup
)
if obj != nil {
rng, err := objToRange(ctx, f.FileSet(), obj)
rng, err := objToRange(ctx, view, obj)
if err != nil {
return nil, err
}
node, err := objToNode(ctx, f.View(), pkg.GetTypes(), obj, rng)
node, err := objToNode(ctx, f.View(), pkg.GetTypes(), obj, rng.spanRange)
if err != nil {
return nil, err
}
decl := &declaration{
obj: obj,
rng: rng,
node: node,
decl := &Declaration{
obj: obj,
mappedRange: rng,
node: node,
}
d, err := decl.hover(ctx)
if err != nil {

View File

@ -346,12 +346,11 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) {
if err != nil {
t.Fatalf("failed for %v: %v", d.Src, err)
}
tok, err := f.(source.GoFile).GetToken(ctx)
srcRng, err := spanToRange(r.data, d.Src)
if err != nil {
t.Fatalf("failed to get token for %s: %v", d.Src.URI(), err)
t.Fatal(err)
}
pos := tok.Pos(d.Src.Start().Offset())
ident, err := source.Identifier(ctx, f.(source.GoFile), pos)
ident, err := source.Identifier(ctx, r.view, f.(source.GoFile), srcRng.Start)
if err != nil {
t.Fatalf("failed for %v: %v", d.Src, err)
}
@ -364,9 +363,15 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) {
hover += h.Synopsis + "\n"
}
hover += h.Signature
rng := ident.DeclarationRange()
rng, err := ident.Range()
if err != nil {
t.Fatal(err)
}
if d.IsType {
rng = ident.Type.Range
rng, err = ident.Type.Range()
if err != nil {
t.Fatal(err)
}
hover = ""
}
if hover != "" {
@ -378,10 +383,10 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) {
t.Errorf("for %v got %q want %q", d.Src, hover, expectHover)
}
} else if !d.OnlyHover {
if def, err := rng.Span(); err != nil {
t.Fatalf("failed for %v: %v", rng, err)
} else if def != d.Def {
t.Errorf("for %v got %v want %v", d.Src, def, d.Def)
if defRng, err := spanToRange(r.data, d.Def); err != nil {
t.Fatal(err)
} else if rng != defRng {
t.Errorf("for %v got %v want %v", d.Src, rng, d.Def)
}
} else {
t.Errorf("no tests ran for %s", d.Src.URI())
@ -424,12 +429,11 @@ func (r *runner) Reference(t *testing.T, data tests.References) {
if err != nil {
t.Fatalf("failed for %v: %v", src, err)
}
tok, err := f.(source.GoFile).GetToken(ctx)
srcRng, err := spanToRange(r.data, src)
if err != nil {
t.Fatalf("failed to get token for %s: %v", src.URI(), err)
t.Fatal(err)
}
pos := tok.Pos(src.Start().Offset())
ident, err := source.Identifier(ctx, f.(source.GoFile), pos)
ident, err := source.Identifier(ctx, r.view, f.(source.GoFile), srcRng.Start)
if err != nil {
t.Fatalf("failed for %v: %v", src, err)
}
@ -439,16 +443,16 @@ func (r *runner) Reference(t *testing.T, data tests.References) {
want[pos] = true
}
refs, err := ident.References(ctx)
refs, err := ident.References(ctx, r.view)
if err != nil {
t.Fatalf("failed for %v: %v", src, err)
}
got := make(map[span.Span]bool)
for _, refInfo := range refs {
refSpan, err := refInfo.Range.Span()
refSpan, err := refInfo.Span()
if err != nil {
t.Errorf("failed for %v item %v: %v", src, refInfo.Name, err)
t.Fatal(err)
}
got[refSpan] = true
}
@ -474,17 +478,16 @@ func (r *runner) Rename(t *testing.T, data tests.Renames) {
if err != nil {
t.Fatalf("failed for %v: %v", spn, err)
}
tok, err := f.(source.GoFile).GetToken(ctx)
srcRng, err := spanToRange(r.data, spn)
if err != nil {
t.Fatalf("failed to get token for %s: %v", spn.URI(), err)
t.Fatal(err)
}
pos := tok.Pos(spn.Start().Offset())
ident, err := source.Identifier(r.ctx, f.(source.GoFile), pos)
ident, err := source.Identifier(r.ctx, r.view, f.(source.GoFile), srcRng.Start)
if err != nil {
t.Error(err)
continue
}
changes, err := ident.Rename(r.ctx, newText)
changes, err := ident.Rename(r.ctx, r.view, newText)
if err != nil {
renamed := string(r.data.Golden(tag, spn.URI().Filename(), func() ([]byte, error) {
return []byte(err.Error()), nil
@ -619,12 +622,11 @@ func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) {
if err != nil {
t.Fatalf("failed for %v: %v", spn, err)
}
tok, err := f.(source.GoFile).GetToken(ctx)
rng, err := spanToRange(r.data, spn)
if err != nil {
t.Fatalf("failed to get token for %s: %v", spn.URI(), err)
t.Fatal(err)
}
pos := tok.Pos(spn.Start().Offset())
gotSignature, err := source.SignatureHelp(ctx, f.(source.GoFile), pos)
gotSignature, err := source.SignatureHelp(ctx, r.view, f.(source.GoFile), rng.Start)
if err != nil {
// Only fail if we got an error we did not expect.
if expectedSignature != nil {
@ -667,3 +669,16 @@ func diffSignatures(spn span.Span, want *source.SignatureInformation, got *sourc
func (r *runner) Link(t *testing.T, data tests.Links) {
// This is a pure LSP feature, no source level functionality to be tested.
}
func spanToRange(data *tests.Data, span span.Span) (protocol.Range, error) {
contents, err := data.Exported.FileContents(span.URI().Filename())
if err != nil {
return protocol.Range{}, err
}
m := protocol.NewColumnMapper(span.URI(), span.URI().Filename(), data.Exported.ExpectFileSet, nil, contents)
srcRng, err := m.Range(span)
if err != nil {
return protocol.Range{}, err
}
return srcRng, nil
}

View File

@ -16,6 +16,7 @@ import (
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/span"
errors "golang.org/x/xerrors"
)
type mappedRange struct {
@ -42,10 +43,94 @@ func (s mappedRange) Range() (protocol.Range, error) {
return *s.protocolRange, nil
}
func (s mappedRange) Span() (span.Span, error) {
return s.spanRange.Span()
}
func (s mappedRange) URI() span.URI {
return s.m.URI
}
func fileToMapper(ctx context.Context, view View, uri span.URI) (*ast.File, Package, *protocol.ColumnMapper, error) {
f, err := view.GetFile(ctx, uri)
if err != nil {
return nil, nil, nil, err
}
gof, ok := f.(GoFile)
if !ok {
return nil, nil, nil, errors.Errorf("%s is not a Go file", f.URI())
}
pkg, err := gof.GetPackage(ctx)
if err != nil {
return nil, nil, nil, err
}
file, m, err := pkgToMapper(ctx, view, pkg, uri)
if err != nil {
return nil, nil, nil, err
}
return file, pkg, m, nil
}
func cachedFileToMapper(ctx context.Context, view View, uri span.URI) (*ast.File, *protocol.ColumnMapper, error) {
f, err := view.GetFile(ctx, uri)
if err != nil {
return nil, nil, err
}
gof, ok := f.(GoFile)
if !ok {
return nil, nil, errors.Errorf("%s is not a Go file", f.URI())
}
if file, ok := gof.Builtin(); ok {
return builtinFileToMapper(ctx, view, gof, file)
}
pkg, err := gof.GetCachedPackage(ctx)
if err != nil {
return nil, nil, err
}
file, m, err := pkgToMapper(ctx, view, pkg, uri)
if err != nil {
return nil, nil, err
}
return file, m, nil
}
func pkgToMapper(ctx context.Context, view View, pkg Package, uri span.URI) (*ast.File, *protocol.ColumnMapper, error) {
var ph ParseGoHandle
for _, h := range pkg.GetHandles() {
if h.File().Identity().URI == uri {
ph = h
}
}
file, err := ph.Cached(ctx)
if file == nil {
return nil, nil, err
}
data, _, err := ph.File().Read(ctx)
if err != nil {
return nil, nil, err
}
fset := view.Session().Cache().FileSet()
tok := fset.File(file.Pos())
if tok == nil {
return nil, nil, errors.Errorf("no token.File for %s", uri)
}
return file, protocol.NewColumnMapper(uri, uri.Filename(), fset, tok, data), nil
}
func builtinFileToMapper(ctx context.Context, view View, f GoFile, file *ast.File) (*ast.File, *protocol.ColumnMapper, error) {
fh := f.Handle(ctx)
data, _, err := fh.Read(ctx)
if err != nil {
return nil, nil, err
}
fset := view.Session().Cache().FileSet()
tok := fset.File(file.Pos())
if tok == nil {
return nil, nil, errors.Errorf("no token.File for %s", f.URI())
}
return nil, protocol.NewColumnMapper(f.URI(), f.URI().Filename(), fset, tok, data), nil
}
func IsGenerated(ctx context.Context, view View, uri span.URI) bool {
f, err := view.GetFile(ctx, uri)
if err != nil {

View File

@ -258,6 +258,8 @@ type File interface {
type GoFile interface {
File
Builtin() (*ast.File, bool)
// GetAST returns the AST for the file, at or above the given mode.
GetAST(ctx context.Context, mode ParseMode) (*ast.File, error)

View File

@ -108,10 +108,9 @@ type Tests interface {
type Definition struct {
Name string
Src span.Span
IsType bool
OnlyHover bool
Def span.Span
Src, Def span.Span
}
type CompletionSnippet struct {