@@ -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.
16381704func (r * renamer ) updatePkgName (pgf * parsego.File , pkgName * types.PkgName ) (diff.Edit , error ) {
0 commit comments