From cbfb669053f449915d8520a0e5c87240ec4d3e13 Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Tue, 22 Nov 2016 14:19:45 -0500 Subject: [PATCH] refactor/rename: perform renaming in doc comments Attempt to update doc comments when renaming an identifier. This reduces the amount of manual steps that need to be taken when using gorename. All occurrences of the old identifier are updated in the doc. The update is done using a regex to ensure that we replace whole word matches only. Fixes golang/go#17994 Change-Id: I4265021b5b34cf7d70bf43ad6ceee74ec132f185 Reviewed-on: https://go-review.googlesource.com/33452 Reviewed-by: Alan Donovan --- refactor/rename/rename.go | 42 ++++++++++- refactor/rename/rename_test.go | 132 +++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 1 deletion(-) diff --git a/refactor/rename/rename.go b/refactor/rename/rename.go index 40ca480f..3e9f797c 100644 --- a/refactor/rename/rename.go +++ b/refactor/rename/rename.go @@ -25,6 +25,7 @@ import ( "os" "os/exec" "path" + "regexp" "sort" "strconv" "strings" @@ -163,7 +164,7 @@ type renamer struct { iprog *loader.Program objsToUpdate map[types.Object]bool hadConflicts bool - to string + from, to string satisfyConstraints map[satisfy.Constraint]bool packages map[*types.Package]*loader.PackageInfo // subset of iprog.AllPackages to inspect msets typeutil.MethodSetCache @@ -314,6 +315,7 @@ func Main(ctxt *build.Context, offsetFlag, fromFlag, to string) error { r := renamer{ iprog: iprog, objsToUpdate: make(map[types.Object]bool), + from: spec.fromName, to: to, packages: make(map[*types.Package]*loader.PackageInfo), } @@ -454,6 +456,8 @@ func (r *renamer) update() error { // token.File captures this distinction; filename does not. var nidents int var filesToUpdate = make(map[*token.File]bool) + + docRegexp := regexp.MustCompile(`\b` + r.from + `\b`) for _, info := range r.packages { // Mutate the ASTs and note the filenames. for id, obj := range info.Defs { @@ -461,8 +465,15 @@ func (r *renamer) update() error { nidents++ id.Name = r.to filesToUpdate[r.iprog.Fset.File(id.Pos())] = true + // Perform the rename in doc comments too. + if doc := r.docComment(id); doc != nil { + for _, comment := range doc.List { + comment.Text = docRegexp.ReplaceAllString(comment.Text, r.to) + } + } } } + for id, obj := range info.Uses { if r.objsToUpdate[obj] { nidents++ @@ -513,6 +524,35 @@ func (r *renamer) update() error { return nil } +// docComment returns the doc for an identifier. +func (r *renamer) docComment(id *ast.Ident) *ast.CommentGroup { + _, nodes, _ := r.iprog.PathEnclosingInterval(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 +} + func plural(n int) string { if n != 1 { return "s" diff --git a/refactor/rename/rename_test.go b/refactor/rename/rename_test.go index 1722f7cd..a02db8f9 100644 --- a/refactor/rename/rename_test.go +++ b/refactor/rename/rename_test.go @@ -521,6 +521,138 @@ var _ foo.U "/go/src/foo/0.go": `package foo type U int +`, + }, + }, + // Rename package-level func plus doc + { + ctxt: main(`package main + +// Foo is a no-op. +// Calling Foo does nothing. +func Foo() { +} +`), + from: "main.Foo", to: "FooBar", + want: map[string]string{ + "/go/src/main/0.go": `package main + +// FooBar is a no-op. +// Calling FooBar does nothing. +func FooBar() { +} +`, + }, + }, + // Rename method plus doc + { + ctxt: main(`package main + +type Foo struct{} + +// Bar does nothing. +func (Foo) Bar() { +} +`), + from: "main.Foo.Bar", to: "Baz", + want: map[string]string{ + "/go/src/main/0.go": `package main + +type Foo struct{} + +// Baz does nothing. +func (Foo) Baz() { +} +`, + }, + }, + // Rename type spec plus doc + { + ctxt: main(`package main + +type ( + // Test but not Testing. + Test struct{} +) +`), + from: "main.Test", to: "Type", + want: map[string]string{ + "/go/src/main/0.go": `package main + +type ( + // Type but not Testing. + Type struct{} +) +`, + }, + }, + // Rename type in gen decl plus doc + { + ctxt: main(`package main + +// T is a test type. +type T struct{} +`), + from: "main.T", to: "Type", + want: map[string]string{ + "/go/src/main/0.go": `package main + +// Type is a test type. +type Type struct{} +`, + }, + }, + // Rename value spec with doc + { + ctxt: main(`package main + +const ( + // C is the speed of light. + C = 2.998e8 +) +`), + from: "main.C", to: "Lightspeed", + want: map[string]string{ + "/go/src/main/0.go": `package main + +const ( + // Lightspeed is the speed of light. + Lightspeed = 2.998e8 +) +`, + }, + }, + // Rename value inside gen decl with doc + { + ctxt: main(`package main + +var out *string +`), + from: "main.out", to: "discard", + want: map[string]string{ + "/go/src/main/0.go": `package main + +var discard *string +`, + }, + }, + // Rename field plus doc + { + ctxt: main(`package main + +type Struct struct { + // Field is a struct field. + Field string +} +`), + from: "main.Struct.Field", to: "Foo", + want: map[string]string{ + "/go/src/main/0.go": `package main + +type Struct struct { + // Foo is a struct field. + Foo string +} `, }, },