Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / refactor / rename / rename.go
diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201105173854-bc9fc8d8c4bc/refactor/rename/rename.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201105173854-bc9fc8d8c4bc/refactor/rename/rename.go
new file mode 100644 (file)
index 0000000..3651c62
--- /dev/null
@@ -0,0 +1,603 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package rename contains the implementation of the 'gorename' command
+// whose main function is in golang.org/x/tools/cmd/gorename.
+// See the Usage constant for the command documentation.
+package rename // import "golang.org/x/tools/refactor/rename"
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "go/ast"
+       "go/build"
+       "go/format"
+       "go/parser"
+       "go/token"
+       "go/types"
+       "io"
+       "io/ioutil"
+       "log"
+       "os"
+       "os/exec"
+       "path"
+       "regexp"
+       "sort"
+       "strconv"
+       "strings"
+
+       "golang.org/x/tools/go/loader"
+       "golang.org/x/tools/go/types/typeutil"
+       "golang.org/x/tools/refactor/importgraph"
+       "golang.org/x/tools/refactor/satisfy"
+)
+
+const Usage = `gorename: precise type-safe renaming of identifiers in Go source code.
+
+Usage:
+
+ gorename (-from <spec> | -offset <file>:#<byte-offset>) -to <name> [-force]
+
+You must specify the object (named entity) to rename using the -offset
+or -from flag.  Exactly one must be specified.
+
+Flags:
+
+-offset    specifies the filename and byte offset of an identifier to rename.
+           This form is intended for use by text editors.
+
+-from      specifies the object to rename using a query notation;
+           This form is intended for interactive use at the command line.
+           A legal -from query has one of the following forms:
+
+  "encoding/json".Decoder.Decode        method of package-level named type
+  (*"encoding/json".Decoder).Decode     ditto, alternative syntax
+  "encoding/json".Decoder.buf           field of package-level named struct type
+  "encoding/json".HTMLEscape            package member (const, func, var, type)
+  "encoding/json".Decoder.Decode::x     local object x within a method
+  "encoding/json".HTMLEscape::x         local object x within a function
+  "encoding/json"::x                    object x anywhere within a package
+  json.go::x                            object x within file json.go
+
+           Double-quotes must be escaped when writing a shell command.
+           Quotes may be omitted for single-segment import paths such as "fmt".
+
+           For methods, the parens and '*' on the receiver type are both
+           optional.
+
+           It is an error if one of the ::x queries matches multiple
+           objects.
+
+-to        the new name.
+
+-force     causes the renaming to proceed even if conflicts were reported.
+           The resulting program may be ill-formed, or experience a change
+           in behaviour.
+
+           WARNING: this flag may even cause the renaming tool to crash.
+           (In due course this bug will be fixed by moving certain
+           analyses into the type-checker.)
+
+-d         display diffs instead of rewriting files
+
+-v         enables verbose logging.
+
+gorename automatically computes the set of packages that might be
+affected.  For a local renaming, this is just the package specified by
+-from or -offset, but for a potentially exported name, gorename scans
+the workspace ($GOROOT and $GOPATH).
+
+gorename rejects renamings of concrete methods that would change the
+assignability relation between types and interfaces. If the interface
+change was intentional, initiate the renaming at the interface method.
+
+gorename rejects any renaming that would create a conflict at the point
+of declaration, or a reference conflict (ambiguity or shadowing), or
+anything else that could cause the resulting program not to compile.
+
+
+Examples:
+
+$ gorename -offset file.go:#123 -to foo
+
+  Rename the object whose identifier is at byte offset 123 within file file.go.
+
+$ gorename -from '"bytes".Buffer.Len' -to Size
+
+  Rename the "Len" method of the *bytes.Buffer type to "Size".
+`
+
+// ---- TODO ----
+
+// Correctness:
+// - handle dot imports correctly
+// - document limitations (reflection, 'implements' algorithm).
+// - sketch a proof of exhaustiveness.
+
+// Features:
+// - support running on packages specified as *.go files on the command line
+// - support running on programs containing errors (loader.Config.AllowErrors)
+// - allow users to specify a scope other than "global" (to avoid being
+//   stuck by neglected packages in $GOPATH that don't build).
+// - support renaming the package clause (no object)
+// - support renaming an import path (no ident or object)
+//   (requires filesystem + SCM updates).
+// - detect and reject edits to autogenerated files (cgo, protobufs)
+//   and optionally $GOROOT packages.
+// - report all conflicts, or at least all qualitatively distinct ones.
+//   Sometimes we stop to avoid redundancy, but
+//   it may give a disproportionate sense of safety in -force mode.
+// - support renaming all instances of a pattern, e.g.
+//   all receiver vars of a given type,
+//   all local variables of a given type,
+//   all PkgNames for a given package.
+// - emit JSON output for other editors and tools.
+
+var (
+       // Force enables patching of the source files even if conflicts were reported.
+       // The resulting program may be ill-formed.
+       // It may even cause gorename to crash.  TODO(adonovan): fix that.
+       Force bool
+
+       // Diff causes the tool to display diffs instead of rewriting files.
+       Diff bool
+
+       // DiffCmd specifies the diff command used by the -d feature.
+       // (The command must accept a -u flag and two filename arguments.)
+       DiffCmd = "diff"
+
+       // ConflictError is returned by Main when it aborts the renaming due to conflicts.
+       // (It is distinguished because the interesting errors are the conflicts themselves.)
+       ConflictError = errors.New("renaming aborted due to conflicts")
+
+       // Verbose enables extra logging.
+       Verbose bool
+)
+
+var stdout io.Writer = os.Stdout
+
+type renamer struct {
+       iprog              *loader.Program
+       objsToUpdate       map[types.Object]bool
+       hadConflicts       bool
+       from, to           string
+       satisfyConstraints map[satisfy.Constraint]bool
+       packages           map[*types.Package]*loader.PackageInfo // subset of iprog.AllPackages to inspect
+       msets              typeutil.MethodSetCache
+       changeMethods      bool
+}
+
+var reportError = func(posn token.Position, message string) {
+       fmt.Fprintf(os.Stderr, "%s: %s\n", posn, message)
+}
+
+// importName renames imports of fromPath within the package specified by info.
+// If fromName is not empty, importName renames only imports as fromName.
+// If the renaming would lead to a conflict, the file is left unchanged.
+func importName(iprog *loader.Program, info *loader.PackageInfo, fromPath, fromName, to string) error {
+       if fromName == to {
+               return nil // no-op (e.g. rename x/foo to y/foo)
+       }
+       for _, f := range info.Files {
+               var from types.Object
+               for _, imp := range f.Imports {
+                       importPath, _ := strconv.Unquote(imp.Path.Value)
+                       importName := path.Base(importPath)
+                       if imp.Name != nil {
+                               importName = imp.Name.Name
+                       }
+                       if importPath == fromPath && (fromName == "" || importName == fromName) {
+                               from = info.Implicits[imp]
+                               break
+                       }
+               }
+               if from == nil {
+                       continue
+               }
+               r := renamer{
+                       iprog:        iprog,
+                       objsToUpdate: make(map[types.Object]bool),
+                       to:           to,
+                       packages:     map[*types.Package]*loader.PackageInfo{info.Pkg: info},
+               }
+               r.check(from)
+               if r.hadConflicts {
+                       reportError(iprog.Fset.Position(f.Imports[0].Pos()),
+                               "skipping update of this file")
+                       continue // ignore errors; leave the existing name
+               }
+               if err := r.update(); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+func Main(ctxt *build.Context, offsetFlag, fromFlag, to string) error {
+       // -- Parse the -from or -offset specifier ----------------------------
+
+       if (offsetFlag == "") == (fromFlag == "") {
+               return fmt.Errorf("exactly one of the -from and -offset flags must be specified")
+       }
+
+       if !isValidIdentifier(to) {
+               return fmt.Errorf("-to %q: not a valid identifier", to)
+       }
+
+       if Diff {
+               defer func(saved func(string, []byte) error) { writeFile = saved }(writeFile)
+               writeFile = diff
+       }
+
+       var spec *spec
+       var err error
+       if fromFlag != "" {
+               spec, err = parseFromFlag(ctxt, fromFlag)
+       } else {
+               spec, err = parseOffsetFlag(ctxt, offsetFlag)
+       }
+       if err != nil {
+               return err
+       }
+
+       if spec.fromName == to {
+               return fmt.Errorf("the old and new names are the same: %s", to)
+       }
+
+       // -- Load the program consisting of the initial package  -------------
+
+       iprog, err := loadProgram(ctxt, map[string]bool{spec.pkg: true})
+       if err != nil {
+               return err
+       }
+
+       fromObjects, err := findFromObjects(iprog, spec)
+       if err != nil {
+               return err
+       }
+
+       // -- Load a larger program, for global renamings ---------------------
+
+       if requiresGlobalRename(fromObjects, to) {
+               // For a local refactoring, we needn't load more
+               // packages, but if the renaming affects the package's
+               // API, we we must load all packages that depend on the
+               // package defining the object, plus their tests.
+
+               if Verbose {
+                       log.Print("Potentially global renaming; scanning workspace...")
+               }
+
+               // Scan the workspace and build the import graph.
+               _, rev, errors := importgraph.Build(ctxt)
+               if len(errors) > 0 {
+                       // With a large GOPATH tree, errors are inevitable.
+                       // Report them but proceed.
+                       fmt.Fprintf(os.Stderr, "While scanning Go workspace:\n")
+                       for path, err := range errors {
+                               fmt.Fprintf(os.Stderr, "Package %q: %s.\n", path, err)
+                       }
+               }
+
+               // Enumerate the set of potentially affected packages.
+               affectedPackages := make(map[string]bool)
+               for _, obj := range fromObjects {
+                       // External test packages are never imported,
+                       // so they will never appear in the graph.
+                       for path := range rev.Search(obj.Pkg().Path()) {
+                               affectedPackages[path] = true
+                       }
+               }
+
+               // TODO(adonovan): allow the user to specify the scope,
+               // or -ignore patterns?  Computing the scope when we
+               // don't (yet) support inputs containing errors can make
+               // the tool rather brittle.
+
+               // Re-load the larger program.
+               iprog, err = loadProgram(ctxt, affectedPackages)
+               if err != nil {
+                       return err
+               }
+
+               fromObjects, err = findFromObjects(iprog, spec)
+               if err != nil {
+                       return err
+               }
+       }
+
+       // -- Do the renaming -------------------------------------------------
+
+       r := renamer{
+               iprog:        iprog,
+               objsToUpdate: make(map[types.Object]bool),
+               from:         spec.fromName,
+               to:           to,
+               packages:     make(map[*types.Package]*loader.PackageInfo),
+       }
+
+       // A renaming initiated at an interface method indicates the
+       // intention to rename abstract and concrete methods as needed
+       // to preserve assignability.
+       for _, obj := range fromObjects {
+               if obj, ok := obj.(*types.Func); ok {
+                       recv := obj.Type().(*types.Signature).Recv()
+                       if recv != nil && isInterface(recv.Type().Underlying()) {
+                               r.changeMethods = true
+                               break
+                       }
+               }
+       }
+
+       // Only the initially imported packages (iprog.Imported) and
+       // their external tests (iprog.Created) should be inspected or
+       // modified, as only they have type-checked functions bodies.
+       // The rest are just dependencies, needed only for package-level
+       // type information.
+       for _, info := range iprog.Imported {
+               r.packages[info.Pkg] = info
+       }
+       for _, info := range iprog.Created { // (tests)
+               r.packages[info.Pkg] = info
+       }
+
+       for _, from := range fromObjects {
+               r.check(from)
+       }
+       if r.hadConflicts && !Force {
+               return ConflictError
+       }
+       return r.update()
+}
+
+// loadProgram loads the specified set of packages (plus their tests)
+// and all their dependencies, from source, through the specified build
+// context.  Only packages in pkgs will have their functions bodies typechecked.
+func loadProgram(ctxt *build.Context, pkgs map[string]bool) (*loader.Program, error) {
+       conf := loader.Config{
+               Build:      ctxt,
+               ParserMode: parser.ParseComments,
+
+               // TODO(adonovan): enable this.  Requires making a lot of code more robust!
+               AllowErrors: false,
+       }
+       // Optimization: don't type-check the bodies of functions in our
+       // dependencies, since we only need exported package members.
+       conf.TypeCheckFuncBodies = func(p string) bool {
+               return pkgs[p] || pkgs[strings.TrimSuffix(p, "_test")]
+       }
+
+       if Verbose {
+               var list []string
+               for pkg := range pkgs {
+                       list = append(list, pkg)
+               }
+               sort.Strings(list)
+               for _, pkg := range list {
+                       log.Printf("Loading package: %s", pkg)
+               }
+       }
+
+       for pkg := range pkgs {
+               conf.ImportWithTests(pkg)
+       }
+
+       // Ideally we would just return conf.Load() here, but go/types
+       // reports certain "soft" errors that gc does not (Go issue 14596).
+       // As a workaround, we set AllowErrors=true and then duplicate
+       // the loader's error checking but allow soft errors.
+       // It would be nice if the loader API permitted "AllowErrors: soft".
+       conf.AllowErrors = true
+       prog, err := conf.Load()
+       if err != nil {
+               return nil, err
+       }
+
+       var errpkgs []string
+       // Report hard errors in indirectly imported packages.
+       for _, info := range prog.AllPackages {
+               if containsHardErrors(info.Errors) {
+                       errpkgs = append(errpkgs, info.Pkg.Path())
+               }
+       }
+       if errpkgs != nil {
+               var more string
+               if len(errpkgs) > 3 {
+                       more = fmt.Sprintf(" and %d more", len(errpkgs)-3)
+                       errpkgs = errpkgs[:3]
+               }
+               return nil, fmt.Errorf("couldn't load packages due to errors: %s%s",
+                       strings.Join(errpkgs, ", "), more)
+       }
+       return prog, nil
+}
+
+func containsHardErrors(errors []error) bool {
+       for _, err := range errors {
+               if err, ok := err.(types.Error); ok && err.Soft {
+                       continue
+               }
+               return true
+       }
+       return false
+}
+
+// requiresGlobalRename reports whether this renaming could potentially
+// affect other packages in the Go workspace.
+func requiresGlobalRename(fromObjects []types.Object, to string) bool {
+       var tfm bool
+       for _, from := range fromObjects {
+               if from.Exported() {
+                       return true
+               }
+               switch objectKind(from) {
+               case "type", "field", "method":
+                       tfm = true
+               }
+       }
+       if ast.IsExported(to) && tfm {
+               // A global renaming may be necessary even if we're
+               // exporting a previous unexported name, since if it's
+               // the name of a type, field or method, this could
+               // change selections in other packages.
+               // (We include "type" in this list because a type
+               // used as an embedded struct field entails a field
+               // renaming.)
+               return true
+       }
+       return false
+}
+
+// update updates the input files.
+func (r *renamer) update() error {
+       // We use token.File, not filename, since a file may appear to
+       // belong to multiple packages and be parsed more than once.
+       // 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 {
+                       if r.objsToUpdate[obj] {
+                               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++
+                               id.Name = r.to
+                               filesToUpdate[r.iprog.Fset.File(id.Pos())] = true
+                       }
+               }
+       }
+
+       // Renaming not supported if cgo files are affected.
+       var generatedFileNames []string
+       for _, info := range r.packages {
+               for _, f := range info.Files {
+                       tokenFile := r.iprog.Fset.File(f.Pos())
+                       if filesToUpdate[tokenFile] && generated(f, tokenFile) {
+                               generatedFileNames = append(generatedFileNames, tokenFile.Name())
+                       }
+               }
+       }
+       if !Force && len(generatedFileNames) > 0 {
+               return fmt.Errorf("refusing to modify generated file%s containing DO NOT EDIT marker: %v", plural(len(generatedFileNames)), generatedFileNames)
+       }
+
+       // Write affected files.
+       var nerrs, npkgs int
+       for _, info := range r.packages {
+               first := true
+               for _, f := range info.Files {
+                       tokenFile := r.iprog.Fset.File(f.Pos())
+                       if filesToUpdate[tokenFile] {
+                               if first {
+                                       npkgs++
+                                       first = false
+                                       if Verbose {
+                                               log.Printf("Updating package %s", info.Pkg.Path())
+                                       }
+                               }
+
+                               filename := tokenFile.Name()
+                               var buf bytes.Buffer
+                               if err := format.Node(&buf, r.iprog.Fset, f); err != nil {
+                                       log.Printf("failed to pretty-print syntax tree: %v", err)
+                                       nerrs++
+                                       continue
+                               }
+                               if err := writeFile(filename, buf.Bytes()); err != nil {
+                                       log.Print(err)
+                                       nerrs++
+                               }
+                       }
+               }
+       }
+       if !Diff {
+               fmt.Printf("Renamed %d occurrence%s in %d file%s in %d package%s.\n",
+                       nidents, plural(nidents),
+                       len(filesToUpdate), plural(len(filesToUpdate)),
+                       npkgs, plural(npkgs))
+       }
+       if nerrs > 0 {
+               return fmt.Errorf("failed to rewrite %d file%s", nerrs, plural(nerrs))
+       }
+       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"
+       }
+       return ""
+}
+
+// writeFile is a seam for testing and for the -d flag.
+var writeFile = reallyWriteFile
+
+func reallyWriteFile(filename string, content []byte) error {
+       return ioutil.WriteFile(filename, content, 0644)
+}
+
+func diff(filename string, content []byte) error {
+       renamed := fmt.Sprintf("%s.%d.renamed", filename, os.Getpid())
+       if err := ioutil.WriteFile(renamed, content, 0644); err != nil {
+               return err
+       }
+       defer os.Remove(renamed)
+
+       diff, err := exec.Command(DiffCmd, "-u", filename, renamed).CombinedOutput()
+       if len(diff) > 0 {
+               // diff exits with a non-zero status when the files don't match.
+               // Ignore that failure as long as we get output.
+               stdout.Write(diff)
+               return nil
+       }
+       if err != nil {
+               return fmt.Errorf("computing diff: %v", err)
+       }
+       return nil
+}