Skip to content

Commit b8f202c

Browse files
kalmanbstamblerre
authored andcommitted
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: 7ed1e46 GitHub-Pull-Request: #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>
1 parent 2293185 commit b8f202c

File tree

6 files changed

+172
-40
lines changed

6 files changed

+172
-40
lines changed

internal/lsp/cmd/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ func (app *Application) commands() []tool.Application {
143143
&bug{},
144144
&check{app: app},
145145
&format{app: app},
146+
&links{app: app},
146147
&imports{app: app},
147148
&query{app: app},
148149
&references{app: app},

internal/lsp/cmd/links.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2019 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package cmd
6+
7+
import (
8+
"context"
9+
"encoding/json"
10+
"flag"
11+
"fmt"
12+
"os"
13+
14+
"golang.org/x/tools/internal/lsp/protocol"
15+
"golang.org/x/tools/internal/span"
16+
"golang.org/x/tools/internal/tool"
17+
errors "golang.org/x/xerrors"
18+
)
19+
20+
// links implements the links verb for gopls.
21+
type links struct {
22+
JSON bool `flag:"json" help:"emit document links in JSON format"`
23+
24+
app *Application
25+
}
26+
27+
func (l *links) Name() string { return "links" }
28+
func (l *links) Usage() string { return "<filename>" }
29+
func (l *links) ShortHelp() string { return "list links in a file" }
30+
func (l *links) DetailedHelp(f *flag.FlagSet) {
31+
fmt.Fprintf(f.Output(), `
32+
Example: list links contained within a file:
33+
34+
  $ gopls links internal/lsp/cmd/check.go
35+
36+
gopls links flags are:
37+
`)
38+
f.PrintDefaults()
39+
}
40+
41+
// Run finds all the links within a document
42+
// - if -json is specified, outputs location range and uri
43+
// - otherwise, prints the a list of unique links
44+
func (l *links) Run(ctx context.Context, args ...string) error {
45+
if len(args) != 1 {
46+
return tool.CommandLineErrorf("links expects 1 argument")
47+
}
48+
conn, err := l.app.connect(ctx)
49+
if err != nil {
50+
return err
51+
}
52+
defer conn.terminate(ctx)
53+
54+
from := span.Parse(args[0])
55+
uri := from.URI()
56+
file := conn.AddFile(ctx, uri)
57+
if file.err != nil {
58+
return file.err
59+
}
60+
results, err := conn.DocumentLink(ctx, &protocol.DocumentLinkParams{
61+
TextDocument: protocol.TextDocumentIdentifier{
62+
URI: protocol.NewURI(uri),
63+
},
64+
})
65+
if err != nil {
66+
return errors.Errorf("%v: %v", from, err)
67+
}
68+
if l.JSON {
69+
enc := json.NewEncoder(os.Stdout)
70+
enc.SetIndent("", "\t")
71+
return enc.Encode(results)
72+
}
73+
for _, v := range results {
74+
fmt.Println(v.Target)
75+
}
76+
return nil
77+
}

internal/lsp/cmd/test/cmdtest.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,6 @@ func (r *runner) Symbol(t *testing.T, uri span.URI, expectedSymbols []protocol.D
8282
//TODO: add command line symbol tests when it works
8383
}
8484

85-
func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {
86-
//TODO: add command line link tests when it works
87-
}
88-
8985
func (r *runner) Implementation(t *testing.T, spn span.Span, imp tests.Implementations) {
9086
//TODO: add implements tests when it works
9187
}

internal/lsp/cmd/test/links.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2019 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package cmdtest
6+
7+
import (
8+
"encoding/json"
9+
"testing"
10+
11+
"golang.org/x/tools/internal/lsp/cmd"
12+
"golang.org/x/tools/internal/lsp/protocol"
13+
"golang.org/x/tools/internal/lsp/tests"
14+
"golang.org/x/tools/internal/span"
15+
"golang.org/x/tools/internal/tool"
16+
)
17+
18+
func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {
19+
m, err := r.data.Mapper(uri)
20+
if err != nil {
21+
t.Fatal(err)
22+
}
23+
args := []string{"links", "-json", uri.Filename()}
24+
app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Exported.Config.Env, r.options)
25+
out := CaptureStdOut(t, func() {
26+
_ = tool.Run(r.ctx, app, args)
27+
})
28+
var got []protocol.DocumentLink
29+
err = json.Unmarshal([]byte(out), &got)
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
if diff := tests.DiffLinks(m, wantLinks, got); diff != "" {
34+
t.Error(diff)
35+
}
36+
}

internal/lsp/lsp_test.go

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -797,49 +797,16 @@ func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {
797797
if err != nil {
798798
t.Fatal(err)
799799
}
800-
gotLinks, err := r.server.DocumentLink(r.ctx, &protocol.DocumentLinkParams{
800+
got, err := r.server.DocumentLink(r.ctx, &protocol.DocumentLinkParams{
801801
TextDocument: protocol.TextDocumentIdentifier{
802802
URI: protocol.NewURI(uri),
803803
},
804804
})
805805
if err != nil {
806806
t.Fatal(err)
807807
}
808-
var notePositions []token.Position
809-
links := make(map[span.Span]string, len(wantLinks))
810-
for _, link := range wantLinks {
811-
links[link.Src] = link.Target
812-
notePositions = append(notePositions, link.NotePosition)
813-
}
814-
815-
for _, link := range gotLinks {
816-
spn, err := m.RangeSpan(link.Range)
817-
if err != nil {
818-
t.Fatal(err)
819-
}
820-
linkInNote := false
821-
for _, notePosition := range notePositions {
822-
// Drop the links found inside expectation notes arguments as this links are not collected by expect package
823-
if notePosition.Line == spn.Start().Line() &&
824-
notePosition.Column <= spn.Start().Column() {
825-
delete(links, spn)
826-
linkInNote = true
827-
}
828-
}
829-
if linkInNote {
830-
continue
831-
}
832-
if target, ok := links[spn]; ok {
833-
delete(links, spn)
834-
if target != link.Target {
835-
t.Errorf("for %v want %v, got %v\n", spn, link.Target, target)
836-
}
837-
} else {
838-
t.Errorf("unexpected link %v:%v\n", spn, link.Target)
839-
}
840-
}
841-
for spn, target := range links {
842-
t.Errorf("missing link %v:%v\n", spn, target)
808+
if diff := tests.DiffLinks(m, wantLinks, got); diff != "" {
809+
t.Error(diff)
843810
}
844811
}
845812

internal/lsp/tests/links.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2019 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package tests
6+
7+
import (
8+
"fmt"
9+
"go/token"
10+
11+
"golang.org/x/tools/internal/lsp/protocol"
12+
"golang.org/x/tools/internal/span"
13+
)
14+
15+
// DiffLinks takes the links we got and checks if they are located within the source or a Note.
16+
// If the link is within a Note, the link is removed.
17+
// Returns an diff comment if there are differences and empty string if no diffs
18+
func DiffLinks(mapper *protocol.ColumnMapper, wantLinks []Link, gotLinks []protocol.DocumentLink) string {
19+
var notePositions []token.Position
20+
links := make(map[span.Span]string, len(wantLinks))
21+
for _, link := range wantLinks {
22+
links[link.Src] = link.Target
23+
notePositions = append(notePositions, link.NotePosition)
24+
}
25+
for _, link := range gotLinks {
26+
spn, err := mapper.RangeSpan(link.Range)
27+
if err != nil {
28+
return fmt.Sprintf("%v", err)
29+
}
30+
linkInNote := false
31+
for _, notePosition := range notePositions {
32+
// Drop the links found inside expectation notes arguments as this links are not collected by expect package
33+
if notePosition.Line == spn.Start().Line() &&
34+
notePosition.Column <= spn.Start().Column() {
35+
delete(links, spn)
36+
linkInNote = true
37+
}
38+
}
39+
if linkInNote {
40+
continue
41+
}
42+
if target, ok := links[spn]; ok {
43+
delete(links, spn)
44+
if target != link.Target {
45+
return fmt.Sprintf("for %v want %v, got %v\n", spn, link.Target, target)
46+
}
47+
} else {
48+
return fmt.Sprintf("unexpected link %v:%v\n", spn, link.Target)
49+
}
50+
}
51+
for spn, target := range links {
52+
return fmt.Sprintf("missing link %v:%v\n", spn, target)
53+
}
54+
return ""
55+
}

0 commit comments

Comments
 (0)