mirror of
https://github.com/golang/go.git
synced 2025-05-05 07:33:00 +00:00
tools/gopls: add command line support for links
This adds support for calling links from the gopls command line, e.g. $ gopls links ~/tmp/foo/main.go Optional arguments are: -json, which emits range and uri in JSON With no arguments, a unique list of links are emitted. Updates golang/go#32875 Change-Id: I1e7cbf00a636c05ccf21bd544d9a5b7742d5d70b GitHub-Last-Rev: 7ed1e4612186bce4077d3c73f2407cf6def211d9 GitHub-Pull-Request: golang/tools#181 Reviewed-on: https://go-review.googlesource.com/c/tools/+/203297 Reviewed-by: Rebecca Stambler <rstambler@golang.org> Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
229318561b
commit
b8f202ca5e
@ -143,6 +143,7 @@ func (app *Application) commands() []tool.Application {
|
||||
&bug{},
|
||||
&check{app: app},
|
||||
&format{app: app},
|
||||
&links{app: app},
|
||||
&imports{app: app},
|
||||
&query{app: app},
|
||||
&references{app: app},
|
||||
|
77
internal/lsp/cmd/links.go
Normal file
77
internal/lsp/cmd/links.go
Normal file
@ -0,0 +1,77 @@
|
||||
// Copyright 2019 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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/span"
|
||||
"golang.org/x/tools/internal/tool"
|
||||
errors "golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// links implements the links verb for gopls.
|
||||
type links struct {
|
||||
JSON bool `flag:"json" help:"emit document links in JSON format"`
|
||||
|
||||
app *Application
|
||||
}
|
||||
|
||||
func (l *links) Name() string { return "links" }
|
||||
func (l *links) Usage() string { return "<filename>" }
|
||||
func (l *links) ShortHelp() string { return "list links in a file" }
|
||||
func (l *links) DetailedHelp(f *flag.FlagSet) {
|
||||
fmt.Fprintf(f.Output(), `
|
||||
Example: list links contained within a file:
|
||||
|
||||
$ gopls links internal/lsp/cmd/check.go
|
||||
|
||||
gopls links flags are:
|
||||
`)
|
||||
f.PrintDefaults()
|
||||
}
|
||||
|
||||
// Run finds all the links within a document
|
||||
// - if -json is specified, outputs location range and uri
|
||||
// - otherwise, prints the a list of unique links
|
||||
func (l *links) Run(ctx context.Context, args ...string) error {
|
||||
if len(args) != 1 {
|
||||
return tool.CommandLineErrorf("links expects 1 argument")
|
||||
}
|
||||
conn, err := l.app.connect(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.terminate(ctx)
|
||||
|
||||
from := span.Parse(args[0])
|
||||
uri := from.URI()
|
||||
file := conn.AddFile(ctx, uri)
|
||||
if file.err != nil {
|
||||
return file.err
|
||||
}
|
||||
results, err := conn.DocumentLink(ctx, &protocol.DocumentLinkParams{
|
||||
TextDocument: protocol.TextDocumentIdentifier{
|
||||
URI: protocol.NewURI(uri),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Errorf("%v: %v", from, err)
|
||||
}
|
||||
if l.JSON {
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", "\t")
|
||||
return enc.Encode(results)
|
||||
}
|
||||
for _, v := range results {
|
||||
fmt.Println(v.Target)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -82,10 +82,6 @@ func (r *runner) Symbol(t *testing.T, uri span.URI, expectedSymbols []protocol.D
|
||||
//TODO: add command line symbol tests when it works
|
||||
}
|
||||
|
||||
func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {
|
||||
//TODO: add command line link tests when it works
|
||||
}
|
||||
|
||||
func (r *runner) Implementation(t *testing.T, spn span.Span, imp tests.Implementations) {
|
||||
//TODO: add implements tests when it works
|
||||
}
|
||||
|
36
internal/lsp/cmd/test/links.go
Normal file
36
internal/lsp/cmd/test/links.go
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2019 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 cmdtest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/cmd"
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/lsp/tests"
|
||||
"golang.org/x/tools/internal/span"
|
||||
"golang.org/x/tools/internal/tool"
|
||||
)
|
||||
|
||||
func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {
|
||||
m, err := r.data.Mapper(uri)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
args := []string{"links", "-json", uri.Filename()}
|
||||
app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Exported.Config.Env, r.options)
|
||||
out := CaptureStdOut(t, func() {
|
||||
_ = tool.Run(r.ctx, app, args)
|
||||
})
|
||||
var got []protocol.DocumentLink
|
||||
err = json.Unmarshal([]byte(out), &got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := tests.DiffLinks(m, wantLinks, got); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
}
|
@ -797,7 +797,7 @@ func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gotLinks, err := r.server.DocumentLink(r.ctx, &protocol.DocumentLinkParams{
|
||||
got, err := r.server.DocumentLink(r.ctx, &protocol.DocumentLinkParams{
|
||||
TextDocument: protocol.TextDocumentIdentifier{
|
||||
URI: protocol.NewURI(uri),
|
||||
},
|
||||
@ -805,41 +805,8 @@ func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var notePositions []token.Position
|
||||
links := make(map[span.Span]string, len(wantLinks))
|
||||
for _, link := range wantLinks {
|
||||
links[link.Src] = link.Target
|
||||
notePositions = append(notePositions, link.NotePosition)
|
||||
}
|
||||
|
||||
for _, link := range gotLinks {
|
||||
spn, err := m.RangeSpan(link.Range)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
linkInNote := false
|
||||
for _, notePosition := range notePositions {
|
||||
// Drop the links found inside expectation notes arguments as this links are not collected by expect package
|
||||
if notePosition.Line == spn.Start().Line() &&
|
||||
notePosition.Column <= spn.Start().Column() {
|
||||
delete(links, spn)
|
||||
linkInNote = true
|
||||
}
|
||||
}
|
||||
if linkInNote {
|
||||
continue
|
||||
}
|
||||
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)
|
||||
if diff := tests.DiffLinks(m, wantLinks, got); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
}
|
||||
|
||||
|
55
internal/lsp/tests/links.go
Normal file
55
internal/lsp/tests/links.go
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright 2019 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 tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/span"
|
||||
)
|
||||
|
||||
// DiffLinks takes the links we got and checks if they are located within the source or a Note.
|
||||
// If the link is within a Note, the link is removed.
|
||||
// Returns an diff comment if there are differences and empty string if no diffs
|
||||
func DiffLinks(mapper *protocol.ColumnMapper, wantLinks []Link, gotLinks []protocol.DocumentLink) string {
|
||||
var notePositions []token.Position
|
||||
links := make(map[span.Span]string, len(wantLinks))
|
||||
for _, link := range wantLinks {
|
||||
links[link.Src] = link.Target
|
||||
notePositions = append(notePositions, link.NotePosition)
|
||||
}
|
||||
for _, link := range gotLinks {
|
||||
spn, err := mapper.RangeSpan(link.Range)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%v", err)
|
||||
}
|
||||
linkInNote := false
|
||||
for _, notePosition := range notePositions {
|
||||
// Drop the links found inside expectation notes arguments as this links are not collected by expect package
|
||||
if notePosition.Line == spn.Start().Line() &&
|
||||
notePosition.Column <= spn.Start().Column() {
|
||||
delete(links, spn)
|
||||
linkInNote = true
|
||||
}
|
||||
}
|
||||
if linkInNote {
|
||||
continue
|
||||
}
|
||||
if target, ok := links[spn]; ok {
|
||||
delete(links, spn)
|
||||
if target != link.Target {
|
||||
return fmt.Sprintf("for %v want %v, got %v\n", spn, link.Target, target)
|
||||
}
|
||||
} else {
|
||||
return fmt.Sprintf("unexpected link %v:%v\n", spn, link.Target)
|
||||
}
|
||||
}
|
||||
for spn, target := range links {
|
||||
return fmt.Sprintf("missing link %v:%v\n", spn, target)
|
||||
}
|
||||
return ""
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user