internal/lsp: add definition support for packages

Now the "type" of a *ast.PkgName is the package it points to. Of
course, a package is not a real types.Type, but we can still jump you
there. We have to pick one of the package's files, so we choose the
longest one, hoping it is the most interesting.

Similarly, the "definition" of an *ast.ImportSpec is the package being
imported.

I also added a nil check for the package in SignatureHelp. This panics
for me occasionally.

Change-Id: Ide4640530a28bcec9da6de36723eb7f0e4cc941c
GitHub-Last-Rev: 8190baa0b908065db5b53f236de03d2f3bff39b5
GitHub-Pull-Request: golang/tools#92
Reviewed-on: https://go-review.googlesource.com/c/tools/+/174081
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Muir Manders 2019-05-10 14:32:25 +00:00 committed by Rebecca Stambler
parent 35884eef20
commit 63859f3815
9 changed files with 95 additions and 16 deletions

View File

@ -155,3 +155,13 @@ func (pkg *Package) GetTypesSizes() types.Sizes {
func (pkg *Package) IsIllTyped() bool { func (pkg *Package) IsIllTyped() bool {
return pkg.types == nil && pkg.typesInfo == nil return pkg.types == nil && pkg.typesInfo == nil
} }
func (pkg *Package) GetImport(pkgPath string) source.Package {
imported := pkg.imports[pkgPath]
// Be careful not to return a nil pointer because that still satisfies the
// interface.
if imported != nil {
return imported
}
return nil
}

View File

@ -5,13 +5,13 @@
package source package source
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"go/ast" "go/ast"
"go/format" "go/format"
"go/token" "go/token"
"go/types" "go/types"
"strings"
) )
// formatter returns the a hover value formatted with its documentation. // formatter returns the a hover value formatted with its documentation.
@ -23,9 +23,9 @@ func (i *IdentifierInfo) Hover(ctx context.Context, qf types.Qualifier, enhanced
pkg := i.File.GetPackage(ctx) pkg := i.File.GetPackage(ctx)
qf = qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo()) qf = qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo())
} }
b := bytes.NewBuffer(nil) var b strings.Builder
f := func(x interface{}, c *ast.CommentGroup) (string, error) { f := func(x interface{}, c *ast.CommentGroup) (string, error) {
return writeHover(x, i.File.GetFileSet(ctx), b, c, markdownSupported, qf) return writeHover(x, i.File.GetFileSet(ctx), &b, c, markdownSupported, qf)
} }
obj := i.Declaration.Object obj := i.Declaration.Object
// TODO(rstambler): Remove this configuration when hover behavior is stable. // TODO(rstambler): Remove this configuration when hover behavior is stable.
@ -113,7 +113,7 @@ func formatVar(node ast.Spec, obj types.Object, f formatter) (string, error) {
} }
// writeHover writes the hover for a given node and its documentation. // writeHover writes the hover for a given node and its documentation.
func writeHover(x interface{}, fset *token.FileSet, b *bytes.Buffer, c *ast.CommentGroup, markdownSupported bool, qf types.Qualifier) (string, error) { func writeHover(x interface{}, fset *token.FileSet, b *strings.Builder, c *ast.CommentGroup, markdownSupported bool, qf types.Qualifier) (string, error) {
if c != nil { if c != nil {
b.WriteString(c.Text()) b.WriteString(c.Text())
b.WriteRune('\n') b.WriteRune('\n')

View File

@ -10,6 +10,7 @@ import (
"go/ast" "go/ast"
"go/token" "go/token"
"go/types" "go/types"
"strconv"
"golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/internal/span" "golang.org/x/tools/internal/span"
@ -54,19 +55,25 @@ func Identifier(ctx context.Context, v View, f File, pos token.Pos) (*Identifier
func identifier(ctx context.Context, v View, f File, pos token.Pos) (*IdentifierInfo, error) { func identifier(ctx context.Context, v View, f File, pos token.Pos) (*IdentifierInfo, error) {
fAST := f.GetAST(ctx) fAST := f.GetAST(ctx)
pkg := f.GetPackage(ctx) pkg := f.GetPackage(ctx)
if pkg == nil { if pkg == nil || pkg.IsIllTyped() {
return nil, fmt.Errorf("no package for %s", f.URI())
}
if pkg.IsIllTyped() {
return nil, fmt.Errorf("package for %s is ill typed", f.URI()) return nil, fmt.Errorf("package for %s is ill typed", f.URI())
} }
path, _ := astutil.PathEnclosingInterval(fAST, pos, pos) path, _ := astutil.PathEnclosingInterval(fAST, pos, pos)
result := &IdentifierInfo{
File: f,
}
if path == nil { if path == nil {
return nil, fmt.Errorf("can't find node enclosing position") return nil, fmt.Errorf("can't find node enclosing position")
} }
// Handle import specs first because they can contain *ast.Idents, and
// we don't want the default *ast.Ident behavior below.
if result, err := checkImportSpec(f, fAST, pkg, pos); result != nil || err != nil {
return result, err
}
result := &IdentifierInfo{
File: f,
}
switch node := path[0].(type) { switch node := path[0].(type) {
case *ast.Ident: case *ast.Ident:
result.ident = node result.ident = node
@ -121,6 +128,58 @@ func identifier(ctx context.Context, v View, f File, pos token.Pos) (*Identifier
return result, nil return result, nil
} }
func checkImportSpec(f File, fAST *ast.File, pkg Package, pos token.Pos) (*IdentifierInfo, error) {
// Check if pos is in an *ast.ImportSpec.
for _, imp := range fAST.Imports {
if imp.Pos() <= pos && pos < imp.End() {
pkgPath, err := strconv.Unquote(imp.Path.Value)
if err != nil {
return nil, fmt.Errorf("import path not quoted: %s (%v)", imp.Path.Value, err)
}
result := &IdentifierInfo{
File: f,
Name: pkgPath,
Range: span.NewRange(f.View().FileSet(), imp.Pos(), imp.End()),
}
// Consider the definition of an import spec to be the imported package.
result.Declaration.Range, err = importedPkg(f.View(), pkg, pkgPath)
if err != nil {
return nil, err
}
return result, nil
}
}
return nil, nil
}
func importedPkg(v View, pkg Package, importPath string) (span.Range, error) {
otherPkg := pkg.GetImport(importPath)
if otherPkg == nil {
return span.Range{}, fmt.Errorf("no import for %q", importPath)
}
if otherPkg.GetSyntax() == nil {
return span.Range{}, fmt.Errorf("no syntax for for %q", importPath)
}
// Heuristic: Jump to the longest file of the package, assuming it's the most "interesting."
// TODO: Consider alternative approaches, if necessary.
var longest *ast.File
for _, astFile := range otherPkg.GetSyntax() {
if longest == nil || astFile.End()-astFile.Pos() > longest.End()-longest.Pos() {
longest = astFile
}
}
if longest == nil {
return span.Range{}, fmt.Errorf("package %q has no files", importPath)
}
return span.NewRange(v.FileSet(), longest.Name.Pos(), longest.Name.End()), nil
}
func typeToObject(typ types.Type) types.Object { func typeToObject(typ types.Type) types.Object {
switch typ := typ.(type) { switch typ := typ.(type) {
case *types.Named: case *types.Named:

View File

@ -28,7 +28,7 @@ type ParameterInformation struct {
func SignatureHelp(ctx context.Context, f File, pos token.Pos) (*SignatureInformation, error) { func SignatureHelp(ctx context.Context, f File, pos token.Pos) (*SignatureInformation, error) {
fAST := f.GetAST(ctx) fAST := f.GetAST(ctx)
pkg := f.GetPackage(ctx) pkg := f.GetPackage(ctx)
if pkg.IsIllTyped() { if pkg == nil || pkg.IsIllTyped() {
return nil, fmt.Errorf("package for %s is ill typed", f.URI()) return nil, fmt.Errorf("package for %s is ill typed", f.URI())
} }

View File

@ -59,6 +59,7 @@ type Package interface {
GetTypesSizes() types.Sizes GetTypesSizes() types.Sizes
IsIllTyped() bool IsIllTyped() bool
GetActionGraph(ctx context.Context, a *analysis.Analyzer) (*Action, error) GetActionGraph(ctx context.Context, a *analysis.Analyzer) (*Action, error)
GetImport(pkgPath string) Package
} }
// TextEdit represents a change to a section of a document. // TextEdit represents a change to a section of a document.

View File

@ -1,4 +1,4 @@
package foo package foo //@mark(PackageFoo, "foo")
type StructFoo struct { //@item(StructFoo, "StructFoo", "struct{...}", "struct") type StructFoo struct { //@item(StructFoo, "StructFoo", "struct{...}", "struct")
Value int //@item(Value, "Value", "int", "field") Value int //@item(Value, "Value", "int", "field")

View File

@ -1,6 +1,9 @@
package b package b
import "golang.org/x/tools/internal/lsp/godef/a" import (
myFoo "golang.org/x/tools/internal/lsp/foo" //@godef("foo", PackageFoo),godef("myFoo", PackageFoo)
"golang.org/x/tools/internal/lsp/godef/a" //@mark(AImport, "\"")
)
type S1 struct { //@S1 type S1 struct { //@S1
F1 int //@mark(S1F1, "F1") F1 int //@mark(S1F1, "F1")
@ -11,7 +14,7 @@ type S1 struct { //@S1
type S2 struct { //@S2 type S2 struct { //@S2
F1 string //@mark(S2F1, "F1") F1 string //@mark(S2F1, "F1")
F2 int //@mark(S2F2, "F2") F2 int //@mark(S2F2, "F2")
*a.A //@godef("A", A) *a.A //@godef("A", A),godef("a",AImport)
} }
type S3 struct { type S3 struct {
@ -27,4 +30,6 @@ func Bar() {
_ = x.F1 //@godef("F1", S1F1) _ = x.F1 //@godef("F1", S1F1)
_ = x.F2 //@godef("F2", S2F2) _ = x.F2 //@godef("F2", S2F2)
_ = x.S2.F1 //@godef("F1", S2F1) _ = x.S2.F1 //@godef("F1", S2F1)
var _ *myFoo.StructFoo
} }

View File

@ -1,5 +1,9 @@
-- A-hover -- -- A-hover --
type a.A string type a.A string
-- AImport-hover --
package a ("golang.org/x/tools/internal/lsp/godef/a")
-- PackageFoo-hover --
-- S1-hover -- -- S1-hover --
type S1 struct{F1 int; S2; a.A} type S1 struct{F1 int; S2; a.A}
-- S1F1-hover -- -- S1F1-hover --

View File

@ -31,7 +31,7 @@ const (
ExpectedCompletionsCount = 85 ExpectedCompletionsCount = 85
ExpectedDiagnosticsCount = 17 ExpectedDiagnosticsCount = 17
ExpectedFormatCount = 5 ExpectedFormatCount = 5
ExpectedDefinitionsCount = 21 ExpectedDefinitionsCount = 24
ExpectedTypeDefinitionsCount = 2 ExpectedTypeDefinitionsCount = 2
ExpectedHighlightsCount = 2 ExpectedHighlightsCount = 2
ExpectedSymbolsCount = 1 ExpectedSymbolsCount = 1