diff --git a/internal/lsp/source/references.go b/internal/lsp/source/references.go index 1ca3ea9ffb..a9f0e1b2b3 100644 --- a/internal/lsp/source/references.go +++ b/internal/lsp/source/references.go @@ -15,10 +15,11 @@ import ( // ReferenceInfo holds information about reference to an identifier in Go source. type ReferenceInfo struct { - Name string - Range span.Range - ident *ast.Ident - obj types.Object + Name string + Range span.Range + ident *ast.Ident + obj types.Object + isDeclaration bool } // References returns a list of references for a given identifier within a package. @@ -44,9 +45,10 @@ func (i *IdentifierInfo) References(ctx context.Context) ([]*ReferenceInfo, erro // This occurs when the variable is declared in a type switch statement // or is an implicit package name. references = append(references, &ReferenceInfo{ - Name: i.decl.obj.Name(), - Range: i.decl.rng, - obj: i.decl.obj, + Name: i.decl.obj.Name(), + Range: i.decl.rng, + obj: i.decl.obj, + isDeclaration: true, }) } @@ -55,10 +57,11 @@ func (i *IdentifierInfo) References(ctx context.Context) ([]*ReferenceInfo, erro continue } references = append(references, &ReferenceInfo{ - Name: ident.Name, - Range: span.NewRange(i.File.FileSet(), ident.Pos(), ident.End()), - ident: ident, - obj: obj, + Name: ident.Name, + Range: span.NewRange(i.File.FileSet(), ident.Pos(), ident.End()), + ident: ident, + obj: obj, + isDeclaration: true, }) } diff --git a/internal/lsp/source/rename.go b/internal/lsp/source/rename.go index cdb40e15c0..1d10da6245 100644 --- a/internal/lsp/source/rename.go +++ b/internal/lsp/source/rename.go @@ -7,8 +7,10 @@ package source import ( "context" "fmt" + "go/ast" "go/token" "go/types" + "regexp" "golang.org/x/tools/go/types/typeutil" "golang.org/x/tools/internal/span" @@ -89,6 +91,7 @@ func Rename(ctx context.Context, view View, f GoFile, pos token.Pos, newName str func (r *renamer) update(ctx context.Context, view View) (map[span.URI][]TextEdit, error) { result := make(map[span.URI][]TextEdit) + docRegexp := regexp.MustCompile(`\b` + r.from + `\b`) for _, ref := range r.refs { refSpan, err := ref.Range.Span() if err != nil { @@ -100,7 +103,57 @@ func (r *renamer) update(ctx context.Context, view View) (map[span.URI][]TextEdi NewText: r.to, } result[refSpan.URI()] = append(result[refSpan.URI()], edit) + + if ref.isDeclaration { + // Perform the rename in doc comments too (declared in the original package) + if doc := r.docComment(r.pkg, ref.ident); doc != nil { + for _, comment := range doc.List { + for _, locs := range docRegexp.FindAllStringIndex(comment.Text, -1) { + rng := span.NewRange(r.fset, comment.Pos()+token.Pos(locs[0]), comment.Pos()+token.Pos(locs[1])) + spn, err := rng.Span() + if err != nil { + return nil, err + } + result[refSpan.URI()] = append(result[refSpan.URI()], TextEdit{ + Span: spn, + NewText: r.to, + }) + } + comment.Text = docRegexp.ReplaceAllString(comment.Text, r.to) + } + } + } + } return result, nil } + +// docComment returns the doc for an identifier. +func (r *renamer) docComment(pkg Package, id *ast.Ident) *ast.CommentGroup { + _, nodes, _ := pathEnclosingInterval(r.fset, pkg, id.Pos(), id.End()) + for _, node := range nodes { + switch decl := node.(type) { + case *ast.FuncDecl: + return decl.Doc + case *ast.Field: + return decl.Doc + case *ast.GenDecl: + return decl.Doc + // For {Type,Value}Spec, if the doc on the spec is absent, + // search for the enclosing GenDecl + case *ast.TypeSpec: + if decl.Doc != nil { + return decl.Doc + } + case *ast.ValueSpec: + if decl.Doc != nil { + return decl.Doc + } + case *ast.Ident: + default: + return nil + } + } + return nil +}