Skip to content

Commit 097b217

Browse files
madelinekalilgopherbot
authored andcommitted
gopls/internal/golang: allow rename in doc comments
This CL enables the renaming of an identifier from within its doc comment. It modifies the PrepareRename command to check for renames inside doc comments and then changes the rename target to be the identifier instead of the doc comment so that all references to the identifier are updated. Fixes golang/go#42301 Change-Id: I5110d805dbe499c3e4010a5bd313df2a51b9f296 Reviewed-on: https://go-review.googlesource.com/c/tools/+/686015 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Madeline Kalil <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent f30e3d1 commit 097b217

File tree

2 files changed

+153
-24
lines changed

2 files changed

+153
-24
lines changed

gopls/internal/golang/rename.go

Lines changed: 90 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import (
6262

6363
"golang.org/x/mod/modfile"
6464
"golang.org/x/tools/go/ast/astutil"
65+
"golang.org/x/tools/go/ast/inspector"
6566
"golang.org/x/tools/go/types/objectpath"
6667
"golang.org/x/tools/go/types/typeutil"
6768
"golang.org/x/tools/gopls/internal/cache"
@@ -135,17 +136,34 @@ func PrepareRename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle,
135136
return nil, nil, err
136137
}
137138

139+
cur, ok := pgf.Cursor.FindByPos(pos, pos)
140+
if !ok {
141+
return nil, nil, fmt.Errorf("can't find cursor for selection")
142+
}
143+
138144
// Check if we're in a 'func' keyword. If so, we hijack the renaming to
139145
// change the function signature.
140-
if item, err := prepareRenameFuncSignature(pgf, pos); err != nil {
146+
if item, err := prepareRenameFuncSignature(pgf, pos, cur); err != nil {
141147
return nil, nil, err
142148
} else if item != nil {
143149
return item, nil, nil
144150
}
145151

146152
targets, node, err := objectsAt(pkg.TypesInfo(), pgf.File, pos)
147153
if err != nil {
148-
return nil, nil, err
154+
// Check if we are renaming an ident inside its doc comment. The call to
155+
// objectsAt will have returned an error in this case.
156+
id := docCommentPosToIdent(pgf, pos, cur)
157+
if id == nil {
158+
return nil, nil, err
159+
}
160+
obj := pkg.TypesInfo().Defs[id]
161+
if obj == nil {
162+
return nil, nil, fmt.Errorf("error fetching Object for ident %q", id.Name)
163+
}
164+
// Change rename target to the ident.
165+
targets = map[types.Object]ast.Node{obj: id}
166+
node = id
149167
}
150168
var obj types.Object
151169
for obj = range targets {
@@ -209,8 +227,8 @@ func prepareRenamePackageName(ctx context.Context, snapshot *cache.Snapshot, pgf
209227
//
210228
// The resulting text is the signature of the function, which may be edited to
211229
// the new signature.
212-
func prepareRenameFuncSignature(pgf *parsego.File, pos token.Pos) (*PrepareItem, error) {
213-
fdecl := funcKeywordDecl(pgf, pos)
230+
func prepareRenameFuncSignature(pgf *parsego.File, pos token.Pos, cursor inspector.Cursor) (*PrepareItem, error) {
231+
fdecl := funcKeywordDecl(pos, cursor)
214232
if fdecl == nil {
215233
return nil, nil
216234
}
@@ -264,17 +282,8 @@ func nameBlankParams(ftype *ast.FuncType) *ast.FuncType {
264282

265283
// renameFuncSignature computes and applies the effective change signature
266284
// operation resulting from a 'renamed' (=rewritten) signature.
267-
func renameFuncSignature(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position, newName string) (map[protocol.DocumentURI][]protocol.TextEdit, error) {
268-
// Find the renamed signature.
269-
pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, f.URI())
270-
if err != nil {
271-
return nil, err
272-
}
273-
pos, err := pgf.PositionPos(pp)
274-
if err != nil {
275-
return nil, err
276-
}
277-
fdecl := funcKeywordDecl(pgf, pos)
285+
func renameFuncSignature(ctx context.Context, pkg *cache.Package, pgf *parsego.File, pos token.Pos, snapshot *cache.Snapshot, cursor inspector.Cursor, f file.Handle, pp protocol.Position, newName string) (map[protocol.DocumentURI][]protocol.TextEdit, error) {
286+
fdecl := funcKeywordDecl(pos, cursor)
278287
if fdecl == nil {
279288
return nil, nil
280289
}
@@ -350,15 +359,12 @@ func renameFuncSignature(ctx context.Context, snapshot *cache.Snapshot, f file.H
350359

351360
// funcKeywordDecl returns the FuncDecl for which pos is in the 'func' keyword,
352361
// if any.
353-
func funcKeywordDecl(pgf *parsego.File, pos token.Pos) *ast.FuncDecl {
354-
path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos)
355-
if len(path) < 1 {
356-
return nil
357-
}
358-
fdecl, _ := path[0].(*ast.FuncDecl)
359-
if fdecl == nil {
362+
func funcKeywordDecl(pos token.Pos, cursor inspector.Cursor) *ast.FuncDecl {
363+
curDecl, ok := moreiters.First(cursor.Enclosing((*ast.FuncDecl)(nil)))
364+
if !ok {
360365
return nil
361366
}
367+
fdecl := curDecl.Node().(*ast.FuncDecl)
362368
ftyp := fdecl.Type
363369
if pos < ftyp.Func || pos > ftyp.Func+token.Pos(len("func")) { // tolerate renaming immediately after 'func'
364370
return nil
@@ -396,7 +402,21 @@ func Rename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp pro
396402
ctx, done := event.Start(ctx, "golang.Rename")
397403
defer done()
398404

399-
if edits, err := renameFuncSignature(ctx, snapshot, f, pp, newName); err != nil {
405+
pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, f.URI())
406+
if err != nil {
407+
return nil, false, err
408+
}
409+
pos, err := pgf.PositionPos(pp)
410+
if err != nil {
411+
return nil, false, err
412+
}
413+
414+
cur, ok := pgf.Cursor.FindByPos(pos, pos)
415+
if !ok {
416+
return nil, false, fmt.Errorf("can't find cursor for selection")
417+
}
418+
419+
if edits, err := renameFuncSignature(ctx, pkg, pgf, pos, snapshot, cur, f, pp, newName); err != nil {
400420
return nil, false, err
401421
} else if edits != nil {
402422
return edits, false, nil
@@ -503,7 +523,18 @@ func renameOrdinary(ctx context.Context, snapshot *cache.Snapshot, uri protocol.
503523
}
504524
targets, node, err := objectsAt(pkg.TypesInfo(), pgf.File, pos)
505525
if err != nil {
506-
return nil, err
526+
// Check if we are renaming an ident inside its doc comment. The call to
527+
// objectsAt will have returned an error in this case.
528+
id := docCommentPosToIdent(pgf, pos, cur)
529+
if id == nil {
530+
return nil, err
531+
}
532+
obj := pkg.TypesInfo().Defs[id]
533+
if obj == nil {
534+
return nil, fmt.Errorf("error fetching types.Object for ident %q", id.Name)
535+
}
536+
// Change rename target to the ident.
537+
targets = map[types.Object]ast.Node{obj: id}
507538
}
508539

509540
// Pick a representative object arbitrarily.
@@ -1633,6 +1664,41 @@ func docComment(pgf *parsego.File, id *ast.Ident) *ast.CommentGroup {
16331664
return nil
16341665
}
16351666

1667+
// docCommentPosToIdent returns the node whose doc comment contains pos, if any.
1668+
// The pos must be within an occurrence of the identifier's name, otherwise it returns nil.
1669+
func docCommentPosToIdent(pgf *parsego.File, pos token.Pos, cur inspector.Cursor) *ast.Ident {
1670+
for curId := range cur.Preorder((*ast.Ident)(nil)) {
1671+
id := curId.Node().(*ast.Ident)
1672+
if pos > id.Pos() {
1673+
continue // Doc comments are not located after an ident.
1674+
}
1675+
doc := docComment(pgf, id)
1676+
if doc == nil || !(doc.Pos() <= pos && pos < doc.End()) {
1677+
continue
1678+
}
1679+
1680+
docRegexp := regexp.MustCompile(`\b` + id.Name + `\b`)
1681+
for _, comment := range doc.List {
1682+
if isDirective(comment.Text) || !(comment.Pos() <= pos && pos < comment.End()) {
1683+
continue
1684+
}
1685+
start := comment.Pos()
1686+
text, err := pgf.NodeText(comment)
1687+
if err != nil {
1688+
return nil
1689+
}
1690+
for _, locs := range docRegexp.FindAllIndex(text, -1) {
1691+
matchStart := start + token.Pos(locs[0])
1692+
matchEnd := start + token.Pos(locs[1])
1693+
if matchStart <= pos && pos <= matchEnd {
1694+
return id
1695+
}
1696+
}
1697+
}
1698+
}
1699+
return nil
1700+
}
1701+
16361702
// updatePkgName returns the updates to rename a pkgName in the import spec by
16371703
// only modifying the package name portion of the import declaration.
16381704
func (r *renamer) updatePkgName(pgf *parsego.File, pkgName *types.PkgName) (diff.Edit, error) {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
This test verifies the fix for golang/go#42301: renaming an ident inside its doc
2+
comment should also rename the ident.
3+
4+
-- go.mod --
5+
module example.com
6+
7+
go 1.21
8+
-- a/a.go --
9+
package a
10+
11+
// Foo doesn't do anything, Foo is just an empty function. //@rename("Foo", "Bar", fooToBar), renameerr("anything", "Bar", "no identifier found")
12+
func Foo() {}
13+
14+
func _() {
15+
Foo()
16+
}
17+
18+
-- b/b.go --
19+
package b
20+
21+
import "example.com/a"
22+
23+
func _() {
24+
a.Foo()
25+
}
26+
27+
-- c/c.go --
28+
package c
29+
30+
// A is an empty struct. //@rename("A", "B", aToB)
31+
type A struct {}
32+
33+
-- d/d.go --
34+
package d
35+
36+
// Bar doesn't do anything, Bar is just an empty function. //@loc(Bar, re`^.*?\bBar\b.*?\b(Bar)\b.*`), rename(Bar, "Foo", barToFoo)
37+
func Bar() {}
38+
39+
-- @aToB/c/c.go --
40+
@@ -3,2 +3,2 @@
41+
-// A is an empty struct. //@rename("A", "B", aToB)
42+
-type A struct {}
43+
+// B is an empty struct. //@rename("B", "B", aToB)
44+
+type B struct {}
45+
-- @barToFoo/d/d.go --
46+
@@ -3,2 +3,2 @@
47+
-// Bar doesn't do anything, Bar is just an empty function. //@loc(Bar, re`^.*?\bBar\b.*?\b(Bar)\b.*`), rename(Bar, "Foo", barToFoo)
48+
-func Bar() {}
49+
+// Foo doesn't do anything, Foo is just an empty function. //@loc(Foo, re`^.*?\bBar\b.*?\b(Foo)\b.*`), rename(Foo, "Foo", barToFoo)
50+
+func Foo() {}
51+
-- @fooToBar/a/a.go --
52+
@@ -3,2 +3,2 @@
53+
-// Foo doesn't do anything, Foo is just an empty function. //@rename("Foo", "Bar", fooToBar), renameerr("anything", "Bar", "no identifier found")
54+
-func Foo() {}
55+
+// Bar doesn't do anything, Bar is just an empty function. //@rename("Bar", "Bar", fooToBar), renameerr("anything", "Bar", "no identifier found")
56+
+func Bar() {}
57+
@@ -7 +7 @@
58+
- Foo()
59+
+ Bar()
60+
-- @fooToBar/b/b.go --
61+
@@ -6 +6 @@
62+
- a.Foo()
63+
+ a.Bar()

0 commit comments

Comments
 (0)