Skip to content

Commit c940306

Browse files
committed
internal/lsp: add prepare rename support
Prepare rename gets the range of the identifier to rename. Returns an error when there is no identifier to rename. Change-Id: I5e5865bc9ff97e6a95ac4f0c48edddcfd0f9ed67 Reviewed-on: https://go-review.googlesource.com/c/tools/+/191170 Run-TryBot: Suzy Mueller <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent 114c575 commit c940306

File tree

11 files changed

+228
-15
lines changed

11 files changed

+228
-15
lines changed

internal/lsp/cmd/cmd_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ func (r *runner) Rename(t *testing.T, data tests.Renames) {
6060
//TODO: add command line rename tests when it works
6161
}
6262

63+
func (r *runner) PrepareRename(t *testing.T, data tests.PrepareRenames) {
64+
//TODO: add command line prepare rename tests when it works
65+
}
66+
6367
func (r *runner) Symbol(t *testing.T, data tests.Symbols) {
6468
//TODO: add command line symbol tests when it works
6569
}

internal/lsp/general.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ func (s *Server) initialize(ctx context.Context, params *protocol.InitializePara
9090
} else {
9191
codeActionProvider = true
9292
}
93+
var renameOpts interface{}
94+
if params.Capabilities.TextDocument.Rename.PrepareSupport {
95+
renameOpts = &protocol.RenameOptions{
96+
PrepareProvider: true,
97+
}
98+
} else {
99+
renameOpts = true
100+
}
93101
return &protocol.InitializeResult{
94102
Capabilities: protocol.ServerCapabilities{
95103
CodeActionProvider: codeActionProvider,
@@ -104,7 +112,7 @@ func (s *Server) initialize(ctx context.Context, params *protocol.InitializePara
104112
DocumentHighlightProvider: true,
105113
DocumentLinkProvider: &protocol.DocumentLinkOptions{},
106114
ReferencesProvider: true,
107-
RenameProvider: true,
115+
RenameProvider: renameOpts,
108116
SignatureHelpProvider: &protocol.SignatureHelpOptions{
109117
TriggerCharacters: []string{"(", ","},
110118
},

internal/lsp/lsp_test.go

+44
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,50 @@ func (r *runner) Rename(t *testing.T, data tests.Renames) {
644644
}
645645
}
646646

647+
func (r *runner) PrepareRename(t *testing.T, data tests.PrepareRenames) {
648+
for src, want := range data {
649+
650+
sm, err := r.mapper(src.URI())
651+
if err != nil {
652+
t.Fatal(err)
653+
}
654+
loc, err := sm.Location(src)
655+
if err != nil {
656+
t.Fatalf("failed for %v: %v", src, err)
657+
}
658+
659+
params := &protocol.TextDocumentPositionParams{
660+
TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
661+
Position: loc.Range.Start,
662+
}
663+
664+
rng, err := r.server.PrepareRename(context.Background(), params)
665+
if err != nil {
666+
t.Errorf("prepare rename failed for %v: got error: %v", src, err)
667+
continue
668+
}
669+
if rng == nil {
670+
if want.Text != "" { // expected an ident.
671+
t.Errorf("prepare rename failed for %v: got nil", src)
672+
}
673+
continue
674+
}
675+
676+
wantSpn, err := want.Range.Span()
677+
if err != nil {
678+
t.Fatalf("failed for %v: %v", src, err)
679+
}
680+
got, err := sm.RangeSpan(*rng)
681+
if err != nil {
682+
t.Fatalf("failed for %v: %v", src, err)
683+
}
684+
685+
if got != wantSpn {
686+
t.Errorf("prepare rename failed: incorrect range got %v want %v", got, wantSpn)
687+
}
688+
}
689+
}
690+
647691
func applyEdits(contents string, edits []diff.TextEdit) string {
648692
res := contents
649693

internal/lsp/rename.go

+32
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,35 @@ func (s *Server) rename(ctx context.Context, params *protocol.RenameParams) (*pr
4646

4747
return &protocol.WorkspaceEdit{Changes: &changes}, nil
4848
}
49+
50+
func (s *Server) prepareRename(ctx context.Context, params *protocol.TextDocumentPositionParams) (*protocol.Range, error) {
51+
uri := span.NewURI(params.TextDocument.URI)
52+
view := s.session.ViewOf(uri)
53+
f, err := getGoFile(ctx, view, uri)
54+
if err != nil {
55+
return nil, err
56+
}
57+
m, err := getMapper(ctx, f)
58+
if err != nil {
59+
return nil, err
60+
}
61+
62+
// Find the identifier at the position.
63+
ident, err := source.PrepareRename(ctx, view, f, params.Position)
64+
if err != nil {
65+
// Do not return the errors here, as it adds clutter.
66+
// Returning a nil result means there is not a valid rename.
67+
return nil, nil
68+
}
69+
identSpn, err := ident.Range.Span()
70+
if err != nil {
71+
return nil, err
72+
}
73+
74+
identRng, err := m.Range(identSpn)
75+
if err != nil {
76+
return nil, err
77+
}
78+
// TODO(suzmue): return ident.Name as the placeholder text.
79+
return &identRng, nil
80+
}

internal/lsp/server.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,9 @@ func (s *Server) LogTraceNotification(context.Context, *protocol.LogTraceParams)
268268
return notImplemented("LogtraceNotification")
269269
}
270270

271-
func (s *Server) PrepareRename(context.Context, *protocol.TextDocumentPositionParams) (*protocol.Range, error) {
272-
return nil, notImplemented("PrepareRename")
271+
func (s *Server) PrepareRename(ctx context.Context, params *protocol.TextDocumentPositionParams) (*protocol.Range, error) {
272+
// TODO(suzmue): support sending placeholder text.
273+
return s.prepareRename(ctx, params)
273274
}
274275

275276
func (s *Server) SetTraceNotification(context.Context, *protocol.SetTraceParams) error {

internal/lsp/source/identifier.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ func importSpec(ctx context.Context, view View, f GoFile, fAST *ast.File, pkg Pa
313313
Name: importPath,
314314
pkg: pkg,
315315
}
316-
if result.mappedRange, err = posToRange(ctx, view, imp.Pos(), imp.End()); err != nil {
316+
if result.mappedRange, err = posToRange(ctx, view, imp.Path.Pos(), imp.Path.End()); err != nil {
317317
return nil, err
318318
}
319319
// Consider the "declaration" of an import spec to be the imported package.

internal/lsp/source/rename.go

+52-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
"golang.org/x/tools/go/types/typeutil"
1717
"golang.org/x/tools/internal/lsp/diff"
18+
"golang.org/x/tools/internal/lsp/protocol"
1819
"golang.org/x/tools/internal/span"
1920
"golang.org/x/tools/internal/telemetry/trace"
2021
"golang.org/x/tools/refactor/satisfy"
@@ -35,6 +36,39 @@ type renamer struct {
3536
changeMethods bool
3637
}
3738

39+
type PrepareItem struct {
40+
Range span.Range
41+
Text string
42+
}
43+
44+
func PrepareRename(ctx context.Context, view View, f GoFile, pos protocol.Position) (*PrepareItem, error) {
45+
ctx, done := trace.StartSpan(ctx, "source.PrepareRename")
46+
defer done()
47+
48+
i, err := Identifier(ctx, view, f, pos)
49+
if err != nil {
50+
return nil, err
51+
}
52+
// If the object declaration is nil, assume it is an import spec.
53+
if i.Declaration.obj == nil {
54+
// Find the corresponding package name for this import spec
55+
// and rename that instead.
56+
i, err = i.getPkgName(ctx)
57+
if err != nil {
58+
return nil, err
59+
}
60+
}
61+
62+
// Do not rename builtin identifiers.
63+
if i.Declaration.obj.Parent() == types.Universe {
64+
return nil, errors.Errorf("cannot rename builtin %q", i.Name)
65+
}
66+
return &PrepareItem{
67+
Range: i.spanRange,
68+
Text: i.Name,
69+
}, nil
70+
}
71+
3872
// Rename returns a map of TextEdits for each file modified when renaming a given identifier within a package.
3973
func (i *IdentifierInfo) Rename(ctx context.Context, view View, newName string) (map[span.URI][]diff.TextEdit, error) {
4074
ctx, done := trace.StartSpan(ctx, "source.Rename")
@@ -53,7 +87,7 @@ func (i *IdentifierInfo) Rename(ctx context.Context, view View, newName string)
5387
if i.Name == newName {
5488
return nil, errors.Errorf("old and new names are the same: %s", newName)
5589
}
56-
if !isValidIdentifier(i.Name) {
90+
if !isValidIdentifier(newName) {
5791
return nil, errors.Errorf("invalid identifier to rename: %q", i.Name)
5892
}
5993
// Do not rename builtin identifiers.
@@ -112,18 +146,31 @@ func (i *IdentifierInfo) Rename(ctx context.Context, view View, newName string)
112146
// getPkgName gets the pkg name associated with an identifer representing
113147
// the import path in an import spec.
114148
func (i *IdentifierInfo) getPkgName(ctx context.Context) (*IdentifierInfo, error) {
115-
file := i.File.FileSet().File(i.mappedRange.spanRange.Start)
116-
pkgLine := file.Line(i.mappedRange.spanRange.Start)
149+
file, err := i.File.GetAST(ctx, ParseHeader)
150+
if err != nil {
151+
return nil, err
152+
}
153+
var namePos token.Pos
154+
for _, spec := range file.Imports {
155+
if spec.Path.Pos() == i.spanRange.Start {
156+
namePos = spec.Pos()
157+
break
158+
}
159+
}
160+
if !namePos.IsValid() {
161+
return nil, errors.Errorf("import spec not found for %q", i.Name)
162+
}
117163

164+
// Look for the object defined at NamePos.
118165
for _, obj := range i.pkg.GetTypesInfo().Defs {
119166
pkgName, ok := obj.(*types.PkgName)
120-
if ok && file.Line(pkgName.Pos()) == pkgLine {
167+
if ok && pkgName.Pos() == namePos {
121168
return getPkgNameIdentifier(ctx, i, pkgName)
122169
}
123170
}
124171
for _, obj := range i.pkg.GetTypesInfo().Implicits {
125172
pkgName, ok := obj.(*types.PkgName)
126-
if ok && file.Line(pkgName.Pos()) == pkgLine {
173+
if ok && pkgName.Pos() == namePos {
127174
return getPkgNameIdentifier(ctx, i, pkgName)
128175
}
129176
}

internal/lsp/source/source_test.go

+47
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,53 @@ func applyEdits(contents string, edits []diff.TextEdit) string {
634634
return res
635635
}
636636

637+
func (r *runner) PrepareRename(t *testing.T, data tests.PrepareRenames) {
638+
ctx := context.Background()
639+
for src, want := range data {
640+
f, err := r.view.GetFile(ctx, src.URI())
641+
if err != nil {
642+
t.Fatal(err)
643+
}
644+
srcRng, err := spanToRange(r.data, src)
645+
if err != nil {
646+
t.Fatal(err)
647+
}
648+
649+
// Find the identifier at the position.
650+
item, err := source.PrepareRename(ctx, r.view, f.(source.GoFile), srcRng.Start)
651+
if err != nil {
652+
if want.Text != "" { // expected an ident.
653+
t.Errorf("prepare rename failed for %v: got error: %v", src, err)
654+
}
655+
continue
656+
}
657+
if item == nil {
658+
if want.Text != "" {
659+
t.Errorf("prepare rename failed for %v: got nil", src)
660+
}
661+
continue
662+
}
663+
664+
if want.Text == "" && item != nil {
665+
t.Errorf("prepare rename failed for %v: expected nil, got %v", src, item)
666+
continue
667+
}
668+
gotSpn, err := item.Range.Span()
669+
if err != nil {
670+
t.Fatal(err)
671+
}
672+
673+
wantSpn, err := want.Range.Span()
674+
if err != nil {
675+
t.Fatal(err)
676+
}
677+
678+
if gotSpn != wantSpn {
679+
t.Errorf("prepare rename failed: incorrect range got %v want %v", item.Range, want.Range)
680+
}
681+
}
682+
}
683+
637684
func (r *runner) Symbol(t *testing.T, data tests.Symbols) {
638685
ctx := r.ctx
639686
for uri, expectedSymbols := range data {

internal/lsp/testdata/good/good0.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package good //@diag("package", "no_diagnostics", "")
22

3-
func stuff() { //@item(good_stuff, "stuff", "func()", "func")
3+
func stuff() { //@item(good_stuff, "stuff", "func()", "func"),prepare("stu", "stuff", "stuff")
44
x := 5
5-
random2(x)
5+
random2(x) //@prepare("dom", "random2", "random2")
66
}

internal/lsp/testdata/good/good1.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
package good //@diag("package", "no_diagnostics", "")
22

33
import (
4-
"golang.org/x/tools/internal/lsp/types" //@item(types_import, "types", "\"golang.org/x/tools/internal/lsp/types\"", "package")
4+
_ "go/ast" //@prepare("go/ast", "_", "_")
5+
"golang.org/x/tools/internal/lsp/types" //@item(types_import, "types", "\"golang.org/x/tools/internal/lsp/types\"", "package"),prepare("types","\"", "types")
56
)
67

78
func random() int { //@item(good_random, "random", "func() int", "func")
8-
y := 6 + 7
9-
return y
9+
_ = "random() int" //@prepare("random", "", "")
10+
y := 6 + 7 //@prepare("7", "", "")
11+
return y //@prepare("return", "","")
1012
}
1113

1214
func random2(y int) int { //@item(good_random2, "random2", "func(y int) int", "func"),item(good_y_param, "y", "int", "parameter")
1315
//@complete("", good_y_param, types_import, good_random, good_random2, good_stuff)
14-
var b types.Bob = &types.X{}
16+
var b types.Bob = &types.X{} //@prepare("ypes","types", "types")
1517
if _, ok := b.(*types.X); ok { //@complete("X", X_struct, Y_struct, Bob_interface)
1618
}
1719

0 commit comments

Comments
 (0)