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
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 // Package rename contains the implementation of the 'gorename' command
6 // whose main function is in golang.org/x/tools/cmd/gorename.
7 // See the Usage constant for the command documentation.
8 package rename // import "golang.org/x/tools/refactor/rename"
9
10 import (
11         "bytes"
12         "errors"
13         "fmt"
14         "go/ast"
15         "go/build"
16         "go/format"
17         "go/parser"
18         "go/token"
19         "go/types"
20         "io"
21         "io/ioutil"
22         "log"
23         "os"
24         "os/exec"
25         "path"
26         "regexp"
27         "sort"
28         "strconv"
29         "strings"
30
31         "golang.org/x/tools/go/loader"
32         "golang.org/x/tools/go/types/typeutil"
33         "golang.org/x/tools/refactor/importgraph"
34         "golang.org/x/tools/refactor/satisfy"
35 )
36
37 const Usage = `gorename: precise type-safe renaming of identifiers in Go source code.
38
39 Usage:
40
41  gorename (-from <spec> | -offset <file>:#<byte-offset>) -to <name> [-force]
42
43 You must specify the object (named entity) to rename using the -offset
44 or -from flag.  Exactly one must be specified.
45
46 Flags:
47
48 -offset    specifies the filename and byte offset of an identifier to rename.
49            This form is intended for use by text editors.
50
51 -from      specifies the object to rename using a query notation;
52            This form is intended for interactive use at the command line.
53            A legal -from query has one of the following forms:
54
55   "encoding/json".Decoder.Decode        method of package-level named type
56   (*"encoding/json".Decoder).Decode     ditto, alternative syntax
57   "encoding/json".Decoder.buf           field of package-level named struct type
58   "encoding/json".HTMLEscape            package member (const, func, var, type)
59   "encoding/json".Decoder.Decode::x     local object x within a method
60   "encoding/json".HTMLEscape::x         local object x within a function
61   "encoding/json"::x                    object x anywhere within a package
62   json.go::x                            object x within file json.go
63
64            Double-quotes must be escaped when writing a shell command.
65            Quotes may be omitted for single-segment import paths such as "fmt".
66
67            For methods, the parens and '*' on the receiver type are both
68            optional.
69
70            It is an error if one of the ::x queries matches multiple
71            objects.
72
73 -to        the new name.
74
75 -force     causes the renaming to proceed even if conflicts were reported.
76            The resulting program may be ill-formed, or experience a change
77            in behaviour.
78
79            WARNING: this flag may even cause the renaming tool to crash.
80            (In due course this bug will be fixed by moving certain
81            analyses into the type-checker.)
82
83 -d         display diffs instead of rewriting files
84
85 -v         enables verbose logging.
86
87 gorename automatically computes the set of packages that might be
88 affected.  For a local renaming, this is just the package specified by
89 -from or -offset, but for a potentially exported name, gorename scans
90 the workspace ($GOROOT and $GOPATH).
91
92 gorename rejects renamings of concrete methods that would change the
93 assignability relation between types and interfaces. If the interface
94 change was intentional, initiate the renaming at the interface method.
95
96 gorename rejects any renaming that would create a conflict at the point
97 of declaration, or a reference conflict (ambiguity or shadowing), or
98 anything else that could cause the resulting program not to compile.
99
100
101 Examples:
102
103 $ gorename -offset file.go:#123 -to foo
104
105   Rename the object whose identifier is at byte offset 123 within file file.go.
106
107 $ gorename -from '"bytes".Buffer.Len' -to Size
108
109   Rename the "Len" method of the *bytes.Buffer type to "Size".
110 `
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 var (
139         // Force enables patching of the source files even if conflicts were reported.
140         // The resulting program may be ill-formed.
141         // It may even cause gorename to crash.  TODO(adonovan): fix that.
142         Force bool
143
144         // Diff causes the tool to display diffs instead of rewriting files.
145         Diff bool
146
147         // DiffCmd specifies the diff command used by the -d feature.
148         // (The command must accept a -u flag and two filename arguments.)
149         DiffCmd = "diff"
150
151         // ConflictError is returned by Main when it aborts the renaming due to conflicts.
152         // (It is distinguished because the interesting errors are the conflicts themselves.)
153         ConflictError = errors.New("renaming aborted due to conflicts")
154
155         // Verbose enables extra logging.
156         Verbose bool
157 )
158
159 var stdout io.Writer = os.Stdout
160
161 type renamer struct {
162         iprog              *loader.Program
163         objsToUpdate       map[types.Object]bool
164         hadConflicts       bool
165         from, to           string
166         satisfyConstraints map[satisfy.Constraint]bool
167         packages           map[*types.Package]*loader.PackageInfo // subset of iprog.AllPackages to inspect
168         msets              typeutil.MethodSetCache
169         changeMethods      bool
170 }
171
172 var reportError = func(posn token.Position, message string) {
173         fmt.Fprintf(os.Stderr, "%s: %s\n", posn, message)
174 }
175
176 // importName renames imports of fromPath within the package specified by info.
177 // If fromName is not empty, importName renames only imports as fromName.
178 // If the renaming would lead to a conflict, the file is left unchanged.
179 func importName(iprog *loader.Program, info *loader.PackageInfo, fromPath, fromName, to string) error {
180         if fromName == to {
181                 return nil // no-op (e.g. rename x/foo to y/foo)
182         }
183         for _, f := range info.Files {
184                 var from types.Object
185                 for _, imp := range f.Imports {
186                         importPath, _ := strconv.Unquote(imp.Path.Value)
187                         importName := path.Base(importPath)
188                         if imp.Name != nil {
189                                 importName = imp.Name.Name
190                         }
191                         if importPath == fromPath && (fromName == "" || importName == fromName) {
192                                 from = info.Implicits[imp]
193                                 break
194                         }
195                 }
196                 if from == nil {
197                         continue
198                 }
199                 r := renamer{
200                         iprog:        iprog,
201                         objsToUpdate: make(map[types.Object]bool),
202                         to:           to,
203                         packages:     map[*types.Package]*loader.PackageInfo{info.Pkg: info},
204                 }
205                 r.check(from)
206                 if r.hadConflicts {
207                         reportError(iprog.Fset.Position(f.Imports[0].Pos()),
208                                 "skipping update of this file")
209                         continue // ignore errors; leave the existing name
210                 }
211                 if err := r.update(); err != nil {
212                         return err
213                 }
214         }
215         return nil
216 }
217
218 func Main(ctxt *build.Context, offsetFlag, fromFlag, to string) error {
219         // -- Parse the -from or -offset specifier ----------------------------
220
221         if (offsetFlag == "") == (fromFlag == "") {
222                 return fmt.Errorf("exactly one of the -from and -offset flags must be specified")
223         }
224
225         if !isValidIdentifier(to) {
226                 return fmt.Errorf("-to %q: not a valid identifier", to)
227         }
228
229         if Diff {
230                 defer func(saved func(string, []byte) error) { writeFile = saved }(writeFile)
231                 writeFile = diff
232         }
233
234         var spec *spec
235         var err error
236         if fromFlag != "" {
237                 spec, err = parseFromFlag(ctxt, fromFlag)
238         } else {
239                 spec, err = parseOffsetFlag(ctxt, offsetFlag)
240         }
241         if err != nil {
242                 return err
243         }
244
245         if spec.fromName == to {
246                 return fmt.Errorf("the old and new names are the same: %s", to)
247         }
248
249         // -- Load the program consisting of the initial package  -------------
250
251         iprog, err := loadProgram(ctxt, map[string]bool{spec.pkg: true})
252         if err != nil {
253                 return err
254         }
255
256         fromObjects, err := findFromObjects(iprog, spec)
257         if err != nil {
258                 return err
259         }
260
261         // -- Load a larger program, for global renamings ---------------------
262
263         if requiresGlobalRename(fromObjects, to) {
264                 // For a local refactoring, we needn't load more
265                 // packages, but if the renaming affects the package's
266                 // API, we we must load all packages that depend on the
267                 // package defining the object, plus their tests.
268
269                 if Verbose {
270                         log.Print("Potentially global renaming; scanning workspace...")
271                 }
272
273                 // Scan the workspace and build the import graph.
274                 _, rev, errors := importgraph.Build(ctxt)
275                 if len(errors) > 0 {
276                         // With a large GOPATH tree, errors are inevitable.
277                         // Report them but proceed.
278                         fmt.Fprintf(os.Stderr, "While scanning Go workspace:\n")
279                         for path, err := range errors {
280                                 fmt.Fprintf(os.Stderr, "Package %q: %s.\n", path, err)
281                         }
282                 }
283
284                 // Enumerate the set of potentially affected packages.
285                 affectedPackages := make(map[string]bool)
286                 for _, obj := range fromObjects {
287                         // External test packages are never imported,
288                         // so they will never appear in the graph.
289                         for path := range rev.Search(obj.Pkg().Path()) {
290                                 affectedPackages[path] = true
291                         }
292                 }
293
294                 // TODO(adonovan): allow the user to specify the scope,
295                 // or -ignore patterns?  Computing the scope when we
296                 // don't (yet) support inputs containing errors can make
297                 // the tool rather brittle.
298
299                 // Re-load the larger program.
300                 iprog, err = loadProgram(ctxt, affectedPackages)
301                 if err != nil {
302                         return err
303                 }
304
305                 fromObjects, err = findFromObjects(iprog, spec)
306                 if err != nil {
307                         return err
308                 }
309         }
310
311         // -- Do the renaming -------------------------------------------------
312
313         r := renamer{
314                 iprog:        iprog,
315                 objsToUpdate: make(map[types.Object]bool),
316                 from:         spec.fromName,
317                 to:           to,
318                 packages:     make(map[*types.Package]*loader.PackageInfo),
319         }
320
321         // A renaming initiated at an interface method indicates the
322         // intention to rename abstract and concrete methods as needed
323         // to preserve assignability.
324         for _, obj := range fromObjects {
325                 if obj, ok := obj.(*types.Func); ok {
326                         recv := obj.Type().(*types.Signature).Recv()
327                         if recv != nil && isInterface(recv.Type().Underlying()) {
328                                 r.changeMethods = true
329                                 break
330                         }
331                 }
332         }
333
334         // Only the initially imported packages (iprog.Imported) and
335         // their external tests (iprog.Created) should be inspected or
336         // modified, as only they have type-checked functions bodies.
337         // The rest are just dependencies, needed only for package-level
338         // type information.
339         for _, info := range iprog.Imported {
340                 r.packages[info.Pkg] = info
341         }
342         for _, info := range iprog.Created { // (tests)
343                 r.packages[info.Pkg] = info
344         }
345
346         for _, from := range fromObjects {
347                 r.check(from)
348         }
349         if r.hadConflicts && !Force {
350                 return ConflictError
351         }
352         return r.update()
353 }
354
355 // loadProgram loads the specified set of packages (plus their tests)
356 // and all their dependencies, from source, through the specified build
357 // context.  Only packages in pkgs will have their functions bodies typechecked.
358 func loadProgram(ctxt *build.Context, pkgs map[string]bool) (*loader.Program, error) {
359         conf := loader.Config{
360                 Build:      ctxt,
361                 ParserMode: parser.ParseComments,
362
363                 // TODO(adonovan): enable this.  Requires making a lot of code more robust!
364                 AllowErrors: false,
365         }
366         // Optimization: don't type-check the bodies of functions in our
367         // dependencies, since we only need exported package members.
368         conf.TypeCheckFuncBodies = func(p string) bool {
369                 return pkgs[p] || pkgs[strings.TrimSuffix(p, "_test")]
370         }
371
372         if Verbose {
373                 var list []string
374                 for pkg := range pkgs {
375                         list = append(list, pkg)
376                 }
377                 sort.Strings(list)
378                 for _, pkg := range list {
379                         log.Printf("Loading package: %s", pkg)
380                 }
381         }
382
383         for pkg := range pkgs {
384                 conf.ImportWithTests(pkg)
385         }
386
387         // Ideally we would just return conf.Load() here, but go/types
388         // reports certain "soft" errors that gc does not (Go issue 14596).
389         // As a workaround, we set AllowErrors=true and then duplicate
390         // the loader's error checking but allow soft errors.
391         // It would be nice if the loader API permitted "AllowErrors: soft".
392         conf.AllowErrors = true
393         prog, err := conf.Load()
394         if err != nil {
395                 return nil, err
396         }
397
398         var errpkgs []string
399         // Report hard errors in indirectly imported packages.
400         for _, info := range prog.AllPackages {
401                 if containsHardErrors(info.Errors) {
402                         errpkgs = append(errpkgs, info.Pkg.Path())
403                 }
404         }
405         if errpkgs != nil {
406                 var more string
407                 if len(errpkgs) > 3 {
408                         more = fmt.Sprintf(" and %d more", len(errpkgs)-3)
409                         errpkgs = errpkgs[:3]
410                 }
411                 return nil, fmt.Errorf("couldn't load packages due to errors: %s%s",
412                         strings.Join(errpkgs, ", "), more)
413         }
414         return prog, nil
415 }
416
417 func containsHardErrors(errors []error) bool {
418         for _, err := range errors {
419                 if err, ok := err.(types.Error); ok && err.Soft {
420                         continue
421                 }
422                 return true
423         }
424         return false
425 }
426
427 // requiresGlobalRename reports whether this renaming could potentially
428 // affect other packages in the Go workspace.
429 func requiresGlobalRename(fromObjects []types.Object, to string) bool {
430         var tfm bool
431         for _, from := range fromObjects {
432                 if from.Exported() {
433                         return true
434                 }
435                 switch objectKind(from) {
436                 case "type", "field", "method":
437                         tfm = true
438                 }
439         }
440         if ast.IsExported(to) && tfm {
441                 // A global renaming may be necessary even if we're
442                 // exporting a previous unexported name, since if it's
443                 // the name of a type, field or method, this could
444                 // change selections in other packages.
445                 // (We include "type" in this list because a type
446                 // used as an embedded struct field entails a field
447                 // renaming.)
448                 return true
449         }
450         return false
451 }
452
453 // update updates the input files.
454 func (r *renamer) update() error {
455         // We use token.File, not filename, since a file may appear to
456         // belong to multiple packages and be parsed more than once.
457         // token.File captures this distinction; filename does not.
458
459         var nidents int
460         var filesToUpdate = make(map[*token.File]bool)
461         docRegexp := regexp.MustCompile(`\b` + r.from + `\b`)
462         for _, info := range r.packages {
463                 // Mutate the ASTs and note the filenames.
464                 for id, obj := range info.Defs {
465                         if r.objsToUpdate[obj] {
466                                 nidents++
467                                 id.Name = r.to
468                                 filesToUpdate[r.iprog.Fset.File(id.Pos())] = true
469                                 // Perform the rename in doc comments too.
470                                 if doc := r.docComment(id); doc != nil {
471                                         for _, comment := range doc.List {
472                                                 comment.Text = docRegexp.ReplaceAllString(comment.Text, r.to)
473                                         }
474                                 }
475                         }
476                 }
477
478                 for id, obj := range info.Uses {
479                         if r.objsToUpdate[obj] {
480                                 nidents++
481                                 id.Name = r.to
482                                 filesToUpdate[r.iprog.Fset.File(id.Pos())] = true
483                         }
484                 }
485         }
486
487         // Renaming not supported if cgo files are affected.
488         var generatedFileNames []string
489         for _, info := range r.packages {
490                 for _, f := range info.Files {
491                         tokenFile := r.iprog.Fset.File(f.Pos())
492                         if filesToUpdate[tokenFile] && generated(f, tokenFile) {
493                                 generatedFileNames = append(generatedFileNames, tokenFile.Name())
494                         }
495                 }
496         }
497         if !Force && len(generatedFileNames) > 0 {
498                 return fmt.Errorf("refusing to modify generated file%s containing DO NOT EDIT marker: %v", plural(len(generatedFileNames)), generatedFileNames)
499         }
500
501         // Write affected files.
502         var nerrs, npkgs int
503         for _, info := range r.packages {
504                 first := true
505                 for _, f := range info.Files {
506                         tokenFile := r.iprog.Fset.File(f.Pos())
507                         if filesToUpdate[tokenFile] {
508                                 if first {
509                                         npkgs++
510                                         first = false
511                                         if Verbose {
512                                                 log.Printf("Updating package %s", info.Pkg.Path())
513                                         }
514                                 }
515
516                                 filename := tokenFile.Name()
517                                 var buf bytes.Buffer
518                                 if err := format.Node(&buf, r.iprog.Fset, f); err != nil {
519                                         log.Printf("failed to pretty-print syntax tree: %v", err)
520                                         nerrs++
521                                         continue
522                                 }
523                                 if err := writeFile(filename, buf.Bytes()); err != nil {
524                                         log.Print(err)
525                                         nerrs++
526                                 }
527                         }
528                 }
529         }
530         if !Diff {
531                 fmt.Printf("Renamed %d occurrence%s in %d file%s in %d package%s.\n",
532                         nidents, plural(nidents),
533                         len(filesToUpdate), plural(len(filesToUpdate)),
534                         npkgs, plural(npkgs))
535         }
536         if nerrs > 0 {
537                 return fmt.Errorf("failed to rewrite %d file%s", nerrs, plural(nerrs))
538         }
539         return nil
540 }
541
542 // docComment returns the doc for an identifier.
543 func (r *renamer) docComment(id *ast.Ident) *ast.CommentGroup {
544         _, nodes, _ := r.iprog.PathEnclosingInterval(id.Pos(), id.End())
545         for _, node := range nodes {
546                 switch decl := node.(type) {
547                 case *ast.FuncDecl:
548                         return decl.Doc
549                 case *ast.Field:
550                         return decl.Doc
551                 case *ast.GenDecl:
552                         return decl.Doc
553                 // For {Type,Value}Spec, if the doc on the spec is absent,
554                 // search for the enclosing GenDecl
555                 case *ast.TypeSpec:
556                         if decl.Doc != nil {
557                                 return decl.Doc
558                         }
559                 case *ast.ValueSpec:
560                         if decl.Doc != nil {
561                                 return decl.Doc
562                         }
563                 case *ast.Ident:
564                 default:
565                         return nil
566                 }
567         }
568         return nil
569 }
570
571 func plural(n int) string {
572         if n != 1 {
573                 return "s"
574         }
575         return ""
576 }
577
578 // writeFile is a seam for testing and for the -d flag.
579 var writeFile = reallyWriteFile
580
581 func reallyWriteFile(filename string, content []byte) error {
582         return ioutil.WriteFile(filename, content, 0644)
583 }
584
585 func diff(filename string, content []byte) error {
586         renamed := fmt.Sprintf("%s.%d.renamed", filename, os.Getpid())
587         if err := ioutil.WriteFile(renamed, content, 0644); err != nil {
588                 return err
589         }
590         defer os.Remove(renamed)
591
592         diff, err := exec.Command(DiffCmd, "-u", filename, renamed).CombinedOutput()
593         if len(diff) > 0 {
594                 // diff exits with a non-zero status when the files don't match.
595                 // Ignore that failure as long as we get output.
596                 stdout.Write(diff)
597                 return nil
598         }
599         if err != nil {
600                 return fmt.Errorf("computing diff: %v", err)
601         }
602         return nil
603 }