blob: 32d29bdc11b6129b3cee20e6ecc9a5803352417f [file] [log] [blame]
/*
* Copyright 2017 The Kythe Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package languageserver
import (
"context"
"fmt"
"reflect"
"testing"
"kythe.io/kythe/go/test/testutil"
cpb "kythe.io/kythe/proto/common_go_proto"
gpb "kythe.io/kythe/proto/graph_go_proto"
xpb "kythe.io/kythe/proto/xref_go_proto"
"github.com/sourcegraph/go-langserver/pkg/lsp"
)
type mockDec struct {
ticket string
resp xpb.DecorationsReply
}
type mockRef struct {
ticket string
resp xpb.CrossReferencesReply
}
type mockDoc struct {
ticket string
resp xpb.DocumentationReply
}
type MockClient struct {
decRsp []mockDec
refRsp []mockRef
docRsp []mockDoc
}
func (c MockClient) Decorations(_ context.Context, d *xpb.DecorationsRequest) (*xpb.DecorationsReply, error) {
for _, r := range c.decRsp {
if r.ticket == d.Location.Ticket {
return &r.resp, nil
}
}
return nil, fmt.Errorf("no Decorations Found")
}
func (c MockClient) CrossReferences(_ context.Context, x *xpb.CrossReferencesRequest) (*xpb.CrossReferencesReply, error) {
for _, r := range c.refRsp {
if r.ticket == x.Ticket[0] {
return &r.resp, nil
}
}
return nil, fmt.Errorf("no CrossReferences Found")
}
func (c MockClient) Documentation(_ context.Context, x *xpb.DocumentationRequest) (*xpb.DocumentationReply, error) {
for _, r := range c.docRsp {
if r.ticket == x.Ticket[0] {
return &r.resp, nil
}
}
return nil, fmt.Errorf("no CrossReferences Found")
}
func (c MockClient) Edges(_ context.Context, x *gpb.EdgesRequest) (*gpb.EdgesReply, error) {
return nil, fmt.Errorf("not Implemented")
}
func (c MockClient) Nodes(_ context.Context, x *gpb.NodesRequest) (*gpb.NodesReply, error) {
return nil, fmt.Errorf("not Implemented")
}
func TestReferences(t *testing.T) {
const sourceText = "hi\nthere\nhi\nend"
c := MockClient{
decRsp: []mockDec{
{
ticket: "kythe://corpus?path=file.txt",
resp: xpb.DecorationsReply{
SourceText: []byte(sourceText),
DefinitionLocations: map[string]*xpb.Anchor{
"kythe://corpus?path=file.txt#def": {
Ticket: "kythe://corpus?path=file.txt#def",
Parent: "kythe://corpus?path=file.txt",
Span: &cpb.Span{
Start: &cpb.Point{LineNumber: 3, ColumnOffset: 0},
End: &cpb.Point{LineNumber: 3, ColumnOffset: 2}}},
"kythe://corpus?path=file.txt#alt": {
Ticket: "kythe://corpus?path=file.txt#alt",
Parent: "kythe://corpus?path=file.txt",
Span: &cpb.Span{
Start: &cpb.Point{LineNumber: 4}}},
},
Reference: []*xpb.DecorationsReply_Reference{
{
TargetDefinition: "kythe://corpus?path=file.txt#def",
TargetTicket: "kythe://corpus?path=file.txt#hi",
Span: &cpb.Span{
Start: &cpb.Point{LineNumber: 1, ColumnOffset: 0},
End: &cpb.Point{LineNumber: 1, ColumnOffset: 2}},
},
{
TargetDefinition: "kythe://corpus?path=file.txt#def",
TargetTicket: "kythe://corpus?path=file.txt#hi",
Span: &cpb.Span{
Start: &cpb.Point{LineNumber: 3, ColumnOffset: 0},
End: &cpb.Point{LineNumber: 3, ColumnOffset: 2}},
},
{
TargetDefinition: "kythe://corpus?path=file.txt#end",
TargetTicket: "kythe://corpus?path=file.txt#alt",
Span: &cpb.Span{
Start: &cpb.Point{LineNumber: 4}},
},
}}}},
refRsp: []mockRef{
{
ticket: "kythe://corpus?path=file.txt#hi",
resp: xpb.CrossReferencesReply{
CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{
"kythe://corpus?path=file.txt#hi": {
Ticket: "kythe://corpus?path=file.txt#hi",
Reference: []*xpb.CrossReferencesReply_RelatedAnchor{
{
Anchor: &xpb.Anchor{
Ticket: "kythe://corpus?path=file.txt#hi2",
Parent: "kythe://corpus?path=file.txt",
Span: &cpb.Span{
Start: &cpb.Point{LineNumber: 3, ColumnOffset: 0},
End: &cpb.Point{LineNumber: 3, ColumnOffset: 2},
},
},
},
{
Anchor: &xpb.Anchor{
Ticket: "kythe://corpus?path=file.txt#end",
Parent: "kythe://corpus?path=file.txt",
Span: &cpb.Span{
Start: &cpb.Point{LineNumber: 4},
},
},
},
}}}}}},
docRsp: []mockDoc{{
ticket: "kythe://corpus?path=file.txt#hi",
resp: xpb.DocumentationReply{
Document: []*xpb.DocumentationReply_Document{{
Ticket: "kythe://corpus?path=file.txt#hi",
Text: &xpb.Printable{
RawText: `a[b]c\[d\]e\\f`,
},
MarkedSource: &cpb.MarkedSource{
PreText: "<",
PostText: ">",
Child: []*cpb.MarkedSource{{
PreText: "hi",
}}}}}}}}}
srv := NewServer(c, &Options{
NewWorkspace: func(_ lsp.DocumentURI) (Workspace, error) {
return NewSettingsWorkspace(Settings{
Root: "/root/dir/",
Mappings: []MappingConfig{{
Local: ":path*",
VName: VNameConfig{
Path: ":path*",
Corpus: "corpus",
}},
},
})
},
})
srv.Initialize(lsp.InitializeParams{})
u := "file:///root/dir/file.txt"
err := srv.TextDocumentDidOpen(lsp.DidOpenTextDocumentParams{
TextDocument: lsp.TextDocumentItem{
URI: lsp.DocumentURI(u),
Text: sourceText,
},
})
if err != nil {
t.Errorf("Unexpected error opening document (%s): %v", u, err)
}
// Make some changes to the file to ensure that Kythe LS store changes correctly.
err = srv.TextDocumentDidChange(lsp.DidChangeTextDocumentParams{
TextDocument: lsp.VersionedTextDocumentIdentifier{
TextDocumentIdentifier: lsp.TextDocumentIdentifier{
URI: lsp.DocumentURI(u),
},
},
ContentChanges: []lsp.TextDocumentContentChangeEvent{{
Text: "\n\n\n\n" + sourceText,
}},
})
if err != nil {
t.Errorf("Unexpected error saving changes to document (%s): %v", u, err)
}
locs, err := srv.TextDocumentReferences(lsp.ReferenceParams{
TextDocumentPositionParams: lsp.TextDocumentPositionParams{
TextDocument: lsp.TextDocumentIdentifier{
URI: lsp.DocumentURI(u),
},
Position: lsp.Position{
Line: 4,
Character: 2,
},
},
})
if err != nil {
t.Errorf("Unexpected error finding references: %v", err)
}
expected := []lsp.Location{{
URI: "file:///root/dir/file.txt",
Range: lsp.Range{
Start: lsp.Position{Line: 6, Character: 0},
End: lsp.Position{Line: 6, Character: 2},
},
}, {
URI: "file:///root/dir/file.txt",
Range: lsp.Range{
Start: lsp.Position{Line: 7},
End: lsp.Position{Line: 7},
},
}}
if err := testutil.DeepEqual(locs, expected); err != nil {
t.Errorf("Incorrect references returned\n Expected: %#v\n Found: %#v", expected, locs)
}
defs, err := srv.TextDocumentDefinition(lsp.TextDocumentPositionParams{
TextDocument: lsp.TextDocumentIdentifier{
URI: lsp.DocumentURI(u),
},
Position: lsp.Position{
Line: 4,
Character: 2,
},
})
if err != nil {
t.Error(err)
}
expected = []lsp.Location{{
URI: "file:///root/dir/file.txt",
Range: lsp.Range{
Start: lsp.Position{Line: 6, Character: 0},
End: lsp.Position{Line: 6, Character: 2},
},
}}
if err := testutil.DeepEqual(defs, expected); err != nil {
t.Errorf("Incorrect definitions returned\n Expected: %#v\n Found: %#v", expected, defs)
}
hover, err := srv.TextDocumentHover(lsp.TextDocumentPositionParams{
TextDocument: lsp.TextDocumentIdentifier{
URI: lsp.DocumentURI(u),
},
Position: lsp.Position{
Line: 4,
Character: 2,
},
})
if err != nil {
t.Errorf("Unexpect error fetching hover: %v", err)
}
hovExpected := lsp.Hover{
Contents: []lsp.MarkedString{{
Value: "<hi>",
}, {
Value: "abc[d]e\\f",
}},
Range: &lsp.Range{
Start: lsp.Position{Line: 4, Character: 0},
End: lsp.Position{Line: 4, Character: 2},
},
}
if !reflect.DeepEqual(hovExpected, hover) {
t.Errorf("Hover results:\ngot %+v\nwant %+v", hovExpected, hover)
}
}