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  }