From 3b6f9c0030f769fa3ded8a7f0d9420c71ac20caf Mon Sep 17 00:00:00 2001 From: Ian Cottrell Date: Wed, 24 Apr 2019 11:33:45 -0400 Subject: [PATCH] internal/lsp: add document link handling for import paths to godoc Change-Id: Ib2eef50047dfcc64110c264e77d648f959613b88 Reviewed-on: https://go-review.googlesource.com/c/tools/+/173698 Run-TryBot: Ian Cottrell TryBot-Result: Gobot Gobot Reviewed-by: Rebecca Stambler --- internal/lsp/cmd/cmd_test.go | 4 +++ internal/lsp/general.go | 1 + internal/lsp/link.go | 45 ++++++++++++++++++++++++++++ internal/lsp/lsp_test.go | 35 ++++++++++++++++++++++ internal/lsp/server.go | 4 +-- internal/lsp/testdata/links/links.go | 12 ++++++++ internal/lsp/tests/tests.go | 31 +++++++++++++++++++ 7 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 internal/lsp/link.go create mode 100644 internal/lsp/testdata/links/links.go diff --git a/internal/lsp/cmd/cmd_test.go b/internal/lsp/cmd/cmd_test.go index 97eb94f1..f91f3598 100644 --- a/internal/lsp/cmd/cmd_test.go +++ b/internal/lsp/cmd/cmd_test.go @@ -57,6 +57,10 @@ func (r *runner) Signature(t *testing.T, data tests.Signatures) { //TODO: add command line signature tests when it works } +func (r *runner) Link(t *testing.T, data tests.Links) { + //TODO: add command line link tests when it works +} + func captureStdOut(t testing.TB, f func()) string { r, out, err := os.Pipe() if err != nil { diff --git a/internal/lsp/general.go b/internal/lsp/general.go index b3b48408..ade4cf54 100644 --- a/internal/lsp/general.go +++ b/internal/lsp/general.go @@ -84,6 +84,7 @@ func (s *Server) initialize(ctx context.Context, params *protocol.InitializePara DocumentSymbolProvider: true, HoverProvider: true, DocumentHighlightProvider: true, + DocumentLinkProvider: &protocol.DocumentLinkOptions{}, SignatureHelpProvider: &protocol.SignatureHelpOptions{ TriggerCharacters: []string{"(", ","}, }, diff --git a/internal/lsp/link.go b/internal/lsp/link.go new file mode 100644 index 00000000..33e9188b --- /dev/null +++ b/internal/lsp/link.go @@ -0,0 +1,45 @@ +// 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 lsp + +import ( + "context" + "strconv" + + "golang.org/x/tools/internal/lsp/protocol" + "golang.org/x/tools/internal/span" +) + +func (s *Server) documentLink(ctx context.Context, params *protocol.DocumentLinkParams) ([]protocol.DocumentLink, error) { + uri := span.NewURI(params.TextDocument.URI) + view := s.findView(ctx, uri) + f, m, err := newColumnMap(ctx, view, uri) + if err != nil { + return nil, err + } + // find the import block + ast := f.GetAST(ctx) + var result []protocol.DocumentLink + for _, imp := range ast.Imports { + spn, err := span.NewRange(f.GetFileSet(ctx), imp.Pos(), imp.End()).Span() + if err != nil { + return nil, err + } + rng, err := m.Range(spn) + if err != nil { + return nil, err + } + target, err := strconv.Unquote(imp.Path.Value) + if err != nil { + continue + } + target = "https://godoc.org/" + target + result = append(result, protocol.DocumentLink{ + Range: rng, + Target: target, + }) + } + return result, nil +} diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go index f51dcb00..a1498beb 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -536,6 +536,41 @@ func diffSignatures(spn span.Span, want source.SignatureInformation, got *protoc return "" } +func (r *runner) Link(t *testing.T, data tests.Links) { + for uri, wantLinks := range data { + m := r.mapper(uri) + gotLinks, err := r.server.DocumentLink(context.Background(), &protocol.DocumentLinkParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: protocol.NewURI(uri), + }, + }) + if err != nil { + t.Fatal(err) + } + links := make(map[span.Span]string, len(wantLinks)) + for _, link := range wantLinks { + links[link.Src] = link.Target + } + for _, link := range gotLinks { + spn, err := m.RangeSpan(link.Range) + if err != nil { + t.Fatal(err) + } + if target, ok := links[spn]; ok { + delete(links, spn) + if target != link.Target { + t.Errorf("for %v want %v, got %v\n", spn, link.Target, target) + } + } else { + t.Errorf("unexpected link %v:%v\n", spn, link.Target) + } + } + for spn, target := range links { + t.Errorf("missing link %v:%v\n", spn, target) + } + } +} + func (r *runner) mapper(uri span.URI) *protocol.ColumnMapper { fname, err := uri.Filename() if err != nil { diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 63533e80..d94bd934 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -210,8 +210,8 @@ func (s *Server) ResolveCodeLens(context.Context, *protocol.CodeLens) (*protocol return nil, notImplemented("ResolveCodeLens") } -func (s *Server) DocumentLink(context.Context, *protocol.DocumentLinkParams) ([]protocol.DocumentLink, error) { - return nil, nil // ignore +func (s *Server) DocumentLink(ctx context.Context, params *protocol.DocumentLinkParams) ([]protocol.DocumentLink, error) { + return s.documentLink(ctx, params) } func (s *Server) ResolveDocumentLink(context.Context, *protocol.DocumentLink) (*protocol.DocumentLink, error) { diff --git a/internal/lsp/testdata/links/links.go b/internal/lsp/testdata/links/links.go new file mode 100644 index 00000000..b97da745 --- /dev/null +++ b/internal/lsp/testdata/links/links.go @@ -0,0 +1,12 @@ +package links + +import ( + "fmt" //@link(re`".*"`,"https://godoc.org/fmt") + + "golang.org/x/tools/internal/lsp/foo" //@link(re`".*"`,"https://godoc.org/golang.org/x/tools/internal/lsp/foo") +) + +var ( + _ fmt.Formatter + _ foo.StructFoo +) diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go index 4a0363b3..69fafcab 100644 --- a/internal/lsp/tests/tests.go +++ b/internal/lsp/tests/tests.go @@ -36,6 +36,7 @@ const ( ExpectedSymbolsCount = 1 ExpectedSignaturesCount = 19 ExpectedCompletionSnippetCount = 9 + ExpectedLinksCount = 2 ) const ( @@ -57,6 +58,7 @@ type Highlights map[string][]span.Span type Symbols map[span.URI][]source.Symbol type SymbolsChildren map[string][]source.Symbol type Signatures map[span.Span]source.SignatureInformation +type Links map[span.URI][]Link type Data struct { Config packages.Config @@ -71,6 +73,7 @@ type Data struct { Symbols Symbols symbolsChildren SymbolsChildren Signatures Signatures + Links Links t testing.TB fragments map[string]string @@ -85,6 +88,7 @@ type Tests interface { Highlight(*testing.T, Highlights) Symbol(*testing.T, Symbols) Signature(*testing.T, Signatures) + Link(*testing.T, Links) } type Definition struct { @@ -101,6 +105,11 @@ type CompletionSnippet struct { PlaceholderSnippet string } +type Link struct { + Src span.Span + Target string +} + func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data { t.Helper() @@ -114,6 +123,7 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data { Symbols: make(Symbols), symbolsChildren: make(SymbolsChildren), Signatures: make(Signatures), + Links: make(Links), t: t, dir: dir, @@ -180,6 +190,7 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data { "symbol": data.collectSymbols, "signature": data.collectSignatures, "snippet": data.collectCompletionSnippets, + "link": data.collectLinks, }); err != nil { t.Fatal(err) } @@ -264,6 +275,18 @@ func Run(t *testing.T, tests Tests, data *Data) { } tests.Signature(t, data.Signatures) }) + + t.Run("Links", func(t *testing.T) { + t.Helper() + linksCount := 0 + for _, want := range data.Links { + linksCount += len(want) + } + if linksCount != ExpectedLinksCount { + t.Errorf("got %v links expected %v", linksCount, ExpectedLinksCount) + } + tests.Link(t, data.Links) + }) } func (data *Data) Golden(tag string, target string, update func(golden string) error) []byte { @@ -379,3 +402,11 @@ func (data *Data) collectCompletionSnippets(spn span.Span, item token.Pos, plain PlaceholderSnippet: placeholder, } } + +func (data *Data) collectLinks(spn span.Span, link string) { + uri := spn.URI() + data.Links[uri] = append(data.Links[uri], Link{ + Src: spn, + Target: link, + }) +}