github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/refactor/rename/rename.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // +build go1.5 6 7 // Package rename contains the implementation of the 'gorename' command 8 // whose main function is in golang.org/x/tools/cmd/gorename. 9 // See the Usage constant for the command documentation. 10 package rename // import "golang.org/x/tools/refactor/rename" 11 12 import ( 13 "bytes" 14 "errors" 15 "fmt" 16 "go/ast" 17 "go/build" 18 "go/format" 19 "go/parser" 20 "go/token" 21 "go/types" 22 "io" 23 "io/ioutil" 24 "log" 25 "os" 26 "os/exec" 27 "path" 28 "sort" 29 "strconv" 30 "strings" 31 32 "golang.org/x/tools/go/loader" 33 "golang.org/x/tools/go/types/typeutil" 34 "golang.org/x/tools/refactor/importgraph" 35 "golang.org/x/tools/refactor/satisfy" 36 ) 37 38 const Usage = `gorename: precise type-safe renaming of identifiers in Go source code. 39 40 Usage: 41 42 gorename (-from <spec> | -offset <file>:#<byte-offset>) -to <name> [-force] 43 44 You must specify the object (named entity) to rename using the -offset 45 or -from flag. Exactly one must be specified. 46 47 Flags: 48 49 -offset specifies the filename and byte offset of an identifier to rename. 50 This form is intended for use by text editors. 51 52 -from specifies the object to rename using a query notation; 53 This form is intended for interactive use at the command line. 54 A legal -from query has one of the following forms: 55 56 "encoding/json".Decoder.Decode method of package-level named type 57 (*"encoding/json".Decoder).Decode ditto, alternative syntax 58 "encoding/json".Decoder.buf field of package-level named struct type 59 "encoding/json".HTMLEscape package member (const, func, var, type) 60 "encoding/json".Decoder.Decode::x local object x within a method 61 "encoding/json".HTMLEscape::x local object x within a function 62 "encoding/json"::x object x anywhere within a package 63 json.go::x object x within file json.go 64 65 Double-quotes must be escaped when writing a shell command. 66 Quotes may be omitted for single-segment import paths such as "fmt". 67 68 For methods, the parens and '*' on the receiver type are both 69 optional. 70 71 It is an error if one of the ::x queries matches multiple 72 objects. 73 74 -to the new name. 75 76 -force causes the renaming to proceed even if conflicts were reported. 77 The resulting program may be ill-formed, or experience a change 78 in behaviour. 79 80 WARNING: this flag may even cause the renaming tool to crash. 81 (In due course this bug will be fixed by moving certain 82 analyses into the type-checker.) 83 84 -d display diffs instead of rewriting files 85 86 -v enables verbose logging. 87 88 gorename automatically computes the set of packages that might be 89 affected. For a local renaming, this is just the package specified by 90 -from or -offset, but for a potentially exported name, gorename scans 91 the workspace ($GOROOT and $GOPATH). 92 93 gorename rejects renamings of concrete methods that would change the 94 assignability relation between types and interfaces. If the interface 95 change was intentional, initiate the renaming at the interface method. 96 97 gorename rejects any renaming that would create a conflict at the point 98 of declaration, or a reference conflict (ambiguity or shadowing), or 99 anything else that could cause the resulting program not to compile. 100 101 102 Examples: 103 104 $ gorename -offset file.go:#123 -to foo 105 106 Rename the object whose identifier is at byte offset 123 within file file.go. 107 108 $ gorename -from '"bytes".Buffer.Len' -to Size 109 110 Rename the "Len" method of the *bytes.Buffer type to "Size". 111 112 ---- TODO ---- 113 114 Correctness: 115 - handle dot imports correctly 116 - document limitations (reflection, 'implements' algorithm). 117 - sketch a proof of exhaustiveness. 118 119 Features: 120 - support running on packages specified as *.go files on the command line 121 - support running on programs containing errors (loader.Config.AllowErrors) 122 - allow users to specify a scope other than "global" (to avoid being 123 stuck by neglected packages in $GOPATH that don't build). 124 - support renaming the package clause (no object) 125 - support renaming an import path (no ident or object) 126 (requires filesystem + SCM updates). 127 - detect and reject edits to autogenerated files (cgo, protobufs) 128 and optionally $GOROOT packages. 129 - report all conflicts, or at least all qualitatively distinct ones. 130 Sometimes we stop to avoid redundancy, but 131 it may give a disproportionate sense of safety in -force mode. 132 - support renaming all instances of a pattern, e.g. 133 all receiver vars of a given type, 134 all local variables of a given type, 135 all PkgNames for a given package. 136 - emit JSON output for other editors and tools. 137 ` 138 139 var ( 140 // Force enables patching of the source files even if conflicts were reported. 141 // The resulting program may be ill-formed. 142 // It may even cause gorename to crash. TODO(adonovan): fix that. 143 Force bool 144 145 // Diff causes the tool to display diffs instead of rewriting files. 146 Diff bool 147 148 // DiffCmd specifies the diff command used by the -d feature. 149 // (The command must accept a -u flag and two filename arguments.) 150 DiffCmd = "diff" 151 152 // ConflictError is returned by Main when it aborts the renaming due to conflicts. 153 // (It is distinguished because the interesting errors are the conflicts themselves.) 154 ConflictError = errors.New("renaming aborted due to conflicts") 155 156 // Verbose enables extra logging. 157 Verbose bool 158 ) 159 160 var stdout io.Writer 161 162 type renamer struct { 163 iprog *loader.Program 164 objsToUpdate map[types.Object]bool 165 hadConflicts bool 166 to string 167 satisfyConstraints map[satisfy.Constraint]bool 168 packages map[*types.Package]*loader.PackageInfo // subset of iprog.AllPackages to inspect 169 msets typeutil.MethodSetCache 170 changeMethods bool 171 } 172 173 var reportError = func(posn token.Position, message string) { 174 fmt.Fprintf(os.Stderr, "%s: %s\n", posn, message) 175 } 176 177 // importName renames imports of the package with the given path in 178 // the given package. If fromName is not empty, only imports as 179 // fromName will be renamed. If the renaming would lead to a conflict, 180 // the file is left unchanged. 181 func importName(iprog *loader.Program, info *loader.PackageInfo, fromPath, fromName, to string) error { 182 for _, f := range info.Files { 183 var from types.Object 184 for _, imp := range f.Imports { 185 importPath, _ := strconv.Unquote(imp.Path.Value) 186 importName := path.Base(importPath) 187 if imp.Name != nil { 188 importName = imp.Name.Name 189 } 190 if importPath == fromPath && (fromName == "" || importName == fromName) { 191 from = info.Implicits[imp] 192 break 193 } 194 } 195 if from == nil { 196 continue 197 } 198 r := renamer{ 199 iprog: iprog, 200 objsToUpdate: make(map[types.Object]bool), 201 to: to, 202 packages: map[*types.Package]*loader.PackageInfo{info.Pkg: info}, 203 } 204 r.check(from) 205 if r.hadConflicts { 206 continue // ignore errors; leave the existing name 207 } 208 if err := r.update(); err != nil { 209 return err 210 } 211 } 212 return nil 213 } 214 215 func Main(ctxt *build.Context, offsetFlag, fromFlag, to string) error { 216 // -- Parse the -from or -offset specifier ---------------------------- 217 218 if (offsetFlag == "") == (fromFlag == "") { 219 return fmt.Errorf("exactly one of the -from and -offset flags must be specified") 220 } 221 222 if !isValidIdentifier(to) { 223 return fmt.Errorf("-to %q: not a valid identifier", to) 224 } 225 226 if Diff { 227 defer func(saved func(string, []byte) error) { writeFile = saved }(writeFile) 228 writeFile = diff 229 } 230 231 var spec *spec 232 var err error 233 if fromFlag != "" { 234 spec, err = parseFromFlag(ctxt, fromFlag) 235 } else { 236 spec, err = parseOffsetFlag(ctxt, offsetFlag) 237 } 238 if err != nil { 239 return err 240 } 241 242 if spec.fromName == to { 243 return fmt.Errorf("the old and new names are the same: %s", to) 244 } 245 246 // -- Load the program consisting of the initial package ------------- 247 248 iprog, err := loadProgram(ctxt, map[string]bool{spec.pkg: true}) 249 if err != nil { 250 return err 251 } 252 253 fromObjects, err := findFromObjects(iprog, spec) 254 if err != nil { 255 return err 256 } 257 258 // -- Load a larger program, for global renamings --------------------- 259 260 if requiresGlobalRename(fromObjects, to) { 261 // For a local refactoring, we needn't load more 262 // packages, but if the renaming affects the package's 263 // API, we we must load all packages that depend on the 264 // package defining the object, plus their tests. 265 266 if Verbose { 267 log.Print("Potentially global renaming; scanning workspace...") 268 } 269 270 // Scan the workspace and build the import graph. 271 _, rev, errors := importgraph.Build(ctxt) 272 if len(errors) > 0 { 273 // With a large GOPATH tree, errors are inevitable. 274 // Report them but proceed. 275 fmt.Fprintf(os.Stderr, "While scanning Go workspace:\n") 276 for path, err := range errors { 277 fmt.Fprintf(os.Stderr, "Package %q: %s.\n", path, err) 278 } 279 } 280 281 // Enumerate the set of potentially affected packages. 282 affectedPackages := make(map[string]bool) 283 for _, obj := range fromObjects { 284 // External test packages are never imported, 285 // so they will never appear in the graph. 286 for path := range rev.Search(obj.Pkg().Path()) { 287 affectedPackages[path] = true 288 } 289 } 290 291 // TODO(adonovan): allow the user to specify the scope, 292 // or -ignore patterns? Computing the scope when we 293 // don't (yet) support inputs containing errors can make 294 // the tool rather brittle. 295 296 // Re-load the larger program. 297 iprog, err = loadProgram(ctxt, affectedPackages) 298 if err != nil { 299 return err 300 } 301 302 fromObjects, err = findFromObjects(iprog, spec) 303 if err != nil { 304 return err 305 } 306 } 307 308 // -- Do the renaming ------------------------------------------------- 309 310 r := renamer{ 311 iprog: iprog, 312 objsToUpdate: make(map[types.Object]bool), 313 to: to, 314 packages: make(map[*types.Package]*loader.PackageInfo), 315 } 316 317 // A renaming initiated at an interface method indicates the 318 // intention to rename abstract and concrete methods as needed 319 // to preserve assignability. 320 for _, obj := range fromObjects { 321 if obj, ok := obj.(*types.Func); ok { 322 recv := obj.Type().(*types.Signature).Recv() 323 if recv != nil && isInterface(recv.Type().Underlying()) { 324 r.changeMethods = true 325 break 326 } 327 } 328 } 329 330 // Only the initially imported packages (iprog.Imported) and 331 // their external tests (iprog.Created) should be inspected or 332 // modified, as only they have type-checked functions bodies. 333 // The rest are just dependencies, needed only for package-level 334 // type information. 335 for _, info := range iprog.Imported { 336 r.packages[info.Pkg] = info 337 } 338 for _, info := range iprog.Created { // (tests) 339 r.packages[info.Pkg] = info 340 } 341 342 for _, from := range fromObjects { 343 r.check(from) 344 } 345 if r.hadConflicts && !Force { 346 return ConflictError 347 } 348 return r.update() 349 } 350 351 // loadProgram loads the specified set of packages (plus their tests) 352 // and all their dependencies, from source, through the specified build 353 // context. Only packages in pkgs will have their functions bodies typechecked. 354 func loadProgram(ctxt *build.Context, pkgs map[string]bool) (*loader.Program, error) { 355 conf := loader.Config{ 356 Build: ctxt, 357 ParserMode: parser.ParseComments, 358 359 // TODO(adonovan): enable this. Requires making a lot of code more robust! 360 AllowErrors: false, 361 } 362 363 // Optimization: don't type-check the bodies of functions in our 364 // dependencies, since we only need exported package members. 365 conf.TypeCheckFuncBodies = func(p string) bool { 366 return pkgs[p] || pkgs[strings.TrimSuffix(p, "_test")] 367 } 368 369 if Verbose { 370 var list []string 371 for pkg := range pkgs { 372 list = append(list, pkg) 373 } 374 sort.Strings(list) 375 for _, pkg := range list { 376 log.Printf("Loading package: %s", pkg) 377 } 378 } 379 380 for pkg := range pkgs { 381 conf.ImportWithTests(pkg) 382 } 383 return conf.Load() 384 } 385 386 // requiresGlobalRename reports whether this renaming could potentially 387 // affect other packages in the Go workspace. 388 func requiresGlobalRename(fromObjects []types.Object, to string) bool { 389 var tfm bool 390 for _, from := range fromObjects { 391 if from.Exported() { 392 return true 393 } 394 switch objectKind(from) { 395 case "type", "field", "method": 396 tfm = true 397 } 398 } 399 if ast.IsExported(to) && tfm { 400 // A global renaming may be necessary even if we're 401 // exporting a previous unexported name, since if it's 402 // the name of a type, field or method, this could 403 // change selections in other packages. 404 // (We include "type" in this list because a type 405 // used as an embedded struct field entails a field 406 // renaming.) 407 return true 408 } 409 return false 410 } 411 412 // update updates the input files. 413 func (r *renamer) update() error { 414 // We use token.File, not filename, since a file may appear to 415 // belong to multiple packages and be parsed more than once. 416 // token.File captures this distinction; filename does not. 417 var nidents int 418 var filesToUpdate = make(map[*token.File]bool) 419 for _, info := range r.packages { 420 // Mutate the ASTs and note the filenames. 421 for id, obj := range info.Defs { 422 if r.objsToUpdate[obj] { 423 nidents++ 424 id.Name = r.to 425 filesToUpdate[r.iprog.Fset.File(id.Pos())] = true 426 } 427 } 428 for id, obj := range info.Uses { 429 if r.objsToUpdate[obj] { 430 nidents++ 431 id.Name = r.to 432 filesToUpdate[r.iprog.Fset.File(id.Pos())] = true 433 } 434 } 435 } 436 437 // TODO(adonovan): don't rewrite cgo + generated files. 438 var nerrs, npkgs int 439 for _, info := range r.packages { 440 first := true 441 for _, f := range info.Files { 442 tokenFile := r.iprog.Fset.File(f.Pos()) 443 if filesToUpdate[tokenFile] { 444 if first { 445 npkgs++ 446 first = false 447 if Verbose { 448 log.Printf("Updating package %s", info.Pkg.Path()) 449 } 450 } 451 452 filename := tokenFile.Name() 453 var buf bytes.Buffer 454 if err := format.Node(&buf, r.iprog.Fset, f); err != nil { 455 log.Printf("failed to pretty-print syntax tree: %v", err) 456 nerrs++ 457 continue 458 } 459 if err := writeFile(filename, buf.Bytes()); err != nil { 460 log.Print(err) 461 nerrs++ 462 } 463 } 464 } 465 } 466 if !Diff { 467 fmt.Printf("Renamed %d occurrence%s in %d file%s in %d package%s.\n", 468 nidents, plural(nidents), 469 len(filesToUpdate), plural(len(filesToUpdate)), 470 npkgs, plural(npkgs)) 471 } 472 if nerrs > 0 { 473 return fmt.Errorf("failed to rewrite %d file%s", nerrs, plural(nerrs)) 474 } 475 return nil 476 } 477 478 func plural(n int) string { 479 if n != 1 { 480 return "s" 481 } 482 return "" 483 } 484 485 // writeFile is a seam for testing and for the -d flag. 486 var writeFile = reallyWriteFile 487 488 func reallyWriteFile(filename string, content []byte) error { 489 return ioutil.WriteFile(filename, content, 0644) 490 } 491 492 func diff(filename string, content []byte) error { 493 renamed := fmt.Sprintf("%s.%d.renamed", filename, os.Getpid()) 494 if err := ioutil.WriteFile(renamed, content, 0644); err != nil { 495 return err 496 } 497 defer os.Remove(renamed) 498 499 diff, err := exec.Command(DiffCmd, "-u", filename, renamed).CombinedOutput() 500 if len(diff) > 0 { 501 // diff exits with a non-zero status when the files don't match. 502 // Ignore that failure as long as we get output. 503 stdout.Write(diff) 504 return nil 505 } 506 if err != nil { 507 return fmt.Errorf("computing diff: %v", err) 508 } 509 return nil 510 }