internal/lsp: implement type definitions

Extend definition tests to add typdef test.

Change-Id: Ibad988ae68f91d18f2c6b4739d758a536172fb35
Reviewed-on: https://go-review.googlesource.com/c/152239
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
Rebecca Stambler 2018-12-03 17:14:30 -05:00
parent 65c3061cc9
commit 3832e276fb
5 changed files with 76 additions and 8 deletions

View File

@ -38,6 +38,7 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
const expectedDiagnosticsCount = 14 const expectedDiagnosticsCount = 14
const expectedFormatCount = 3 const expectedFormatCount = 3
const expectedDefinitionsCount = 16 const expectedDefinitionsCount = 16
const expectedTypeDefinitionsCount = 2
files := packagestest.MustCopyFileTree(dir) files := packagestest.MustCopyFileTree(dir)
for fragment, operation := range files { for fragment, operation := range files {
@ -78,6 +79,7 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
expectedCompletions := make(completions) expectedCompletions := make(completions)
expectedFormat := make(formats) expectedFormat := make(formats)
expectedDefinitions := make(definitions) expectedDefinitions := make(definitions)
expectedTypeDefinitions := make(definitions)
// Collect any data that needs to be used by subsequent tests. // Collect any data that needs to be used by subsequent tests.
if err := exported.Expect(map[string]interface{}{ if err := exported.Expect(map[string]interface{}{
@ -86,6 +88,7 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
"complete": expectedCompletions.collect, "complete": expectedCompletions.collect,
"format": expectedFormat.collect, "format": expectedFormat.collect,
"godef": expectedDefinitions.collect, "godef": expectedDefinitions.collect,
"typdef": expectedTypeDefinitions.collect,
}); err != nil { }); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -127,7 +130,17 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
t.Errorf("got %v definitions expected %v", len(expectedDefinitions), expectedDefinitionsCount) t.Errorf("got %v definitions expected %v", len(expectedDefinitions), expectedDefinitionsCount)
} }
} }
expectedDefinitions.test(t, s) expectedDefinitions.test(t, s, false)
})
t.Run("TypeDefinitions", func(t *testing.T) {
t.Helper()
if goVersion111 { // TODO(rstambler): Remove this when we no longer support Go 1.10.
if len(expectedTypeDefinitions) != expectedTypeDefinitionsCount {
t.Errorf("got %v type definitions expected %v", len(expectedTypeDefinitions), expectedTypeDefinitionsCount)
}
}
expectedTypeDefinitions.test(t, s, true)
}) })
} }
@ -290,14 +303,21 @@ func (f formats) collect(pos token.Position) {
f[pos.Filename] = stdout.String() f[pos.Filename] = stdout.String()
} }
func (d definitions) test(t *testing.T, s *server) { func (d definitions) test(t *testing.T, s *server, typ bool) {
for src, target := range d { for src, target := range d {
locs, err := s.Definition(context.Background(), &protocol.TextDocumentPositionParams{ params := &protocol.TextDocumentPositionParams{
TextDocument: protocol.TextDocumentIdentifier{ TextDocument: protocol.TextDocumentIdentifier{
URI: src.URI, URI: src.URI,
}, },
Position: src.Range.Start, Position: src.Range.Start,
}) }
var locs []protocol.Location
var err error
if typ {
locs, err = s.TypeDefinition(context.Background(), params)
} else {
locs, err = s.Definition(context.Background(), params)
}
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -64,6 +64,7 @@ func (s *server) Initialize(ctx context.Context, params *protocol.InitializePara
Change: float64(protocol.Full), // full contents of file sent on each update Change: float64(protocol.Full), // full contents of file sent on each update
OpenClose: true, OpenClose: true,
}, },
TypeDefinitionProvider: true,
}, },
}, nil }, nil
} }
@ -215,8 +216,18 @@ func (s *server) Definition(ctx context.Context, params *protocol.TextDocumentPo
return []protocol.Location{toProtocolLocation(s.view.Config.Fset, r)}, nil return []protocol.Location{toProtocolLocation(s.view.Config.Fset, r)}, nil
} }
func (s *server) TypeDefinition(context.Context, *protocol.TextDocumentPositionParams) ([]protocol.Location, error) { func (s *server) TypeDefinition(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.Location, error) {
return nil, notImplemented("TypeDefinition") f := s.view.GetFile(source.URI(params.TextDocument.URI))
tok, err := f.GetToken()
if err != nil {
return nil, err
}
pos := fromProtocolPosition(tok, params.Position)
r, err := source.TypeDefinition(ctx, f, pos)
if err != nil {
return nil, err
}
return []protocol.Location{toProtocolLocation(s.view.Config.Fset, r)}, nil
} }
func (s *server) Implementation(context.Context, *protocol.TextDocumentPositionParams) ([]protocol.Location, error) { func (s *server) Implementation(context.Context, *protocol.TextDocumentPositionParams) ([]protocol.Location, error) {

View File

@ -48,6 +48,43 @@ func Definition(ctx context.Context, f *File, pos token.Pos) (Range, error) {
return objToRange(f.view.Config.Fset, obj), nil return objToRange(f.view.Config.Fset, obj), nil
} }
func TypeDefinition(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
}
i, err := findIdentifier(fAST, pos)
if err != nil {
return Range{}, err
}
if i.ident == nil {
return Range{}, fmt.Errorf("not a valid identifier")
}
typ := pkg.TypesInfo.TypeOf(i.ident)
if typ == nil {
return Range{}, fmt.Errorf("no type for %s", i.ident.Name)
}
obj := typeToObject(typ)
if obj == nil {
return Range{}, fmt.Errorf("no object for type %s", typ.String())
}
return objToRange(f.view.Config.Fset, obj), nil
}
func typeToObject(typ types.Type) (obj types.Object) {
switch typ := typ.(type) {
case *types.Named:
obj = typ.Obj()
case *types.Pointer:
obj = typeToObject(typ.Elem())
}
return obj
}
// ident returns the ident plus any extra information needed // ident returns the ident plus any extra information needed
type ident struct { type ident struct {
ident *ast.Ident ident *ast.Ident

View File

@ -12,7 +12,7 @@ func Baz() {
defer bar.Bar() //@complete("B", Bar) defer bar.Bar() //@complete("B", Bar)
// TODO(rstambler): Test completion here. // TODO(rstambler): Test completion here.
defer bar.B defer bar.B
var _ f.IntFoo //@complete("n", IntFoo) var x f.IntFoo //@complete("n", IntFoo),typdef("x", IntFoo)
bar.Bar() //@complete("B", Bar) bar.Bar() //@complete("B", Bar)
} }

View File

@ -14,7 +14,7 @@ func Foo() { //@item(Foo, "Foo()", "", "func")
func _() { func _() {
var sFoo StructFoo //@complete("t", StructFoo) var sFoo StructFoo //@complete("t", StructFoo)
if x := sFoo; x.Value == 1 { //@complete("V", Value) if x := sFoo; x.Value == 1 { //@complete("V", Value),typdef("sFoo", StructFoo)
return return
} }
} }