mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +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{},
|
&bug{},
|
||||||
&check{app: app},
|
&check{app: app},
|
||||||
&format{app: app},
|
&format{app: app},
|
||||||
|
&links{app: app},
|
||||||
&imports{app: app},
|
&imports{app: app},
|
||||||
&query{app: app},
|
&query{app: app},
|
||||||
&references{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
|
//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) {
|
func (r *runner) Implementation(t *testing.T, spn span.Span, imp tests.Implementations) {
|
||||||
//TODO: add implements tests when it works
|
//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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
gotLinks, err := r.server.DocumentLink(r.ctx, &protocol.DocumentLinkParams{
|
got, err := r.server.DocumentLink(r.ctx, &protocol.DocumentLinkParams{
|
||||||
TextDocument: protocol.TextDocumentIdentifier{
|
TextDocument: protocol.TextDocumentIdentifier{
|
||||||
URI: protocol.NewURI(uri),
|
URI: protocol.NewURI(uri),
|
||||||
},
|
},
|
||||||
@ -805,41 +805,8 @@ func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
var notePositions []token.Position
|
if diff := tests.DiffLinks(m, wantLinks, got); diff != "" {
|
||||||
links := make(map[span.Span]string, len(wantLinks))
|
t.Error(diff)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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