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 / mvpkg.go
1 // Copyright 2015 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // licence that can be found in the LICENSE file.
4
5 // This file contains the implementation of the 'gomvpkg' command
6 // whose main function is in golang.org/x/tools/cmd/gomvpkg.
7
8 package rename
9
10 // TODO(matloob):
11 // - think about what happens if the package is moving across version control systems.
12 // - dot imports are not supported. Make sure it's clearly documented.
13
14 import (
15         "bytes"
16         "fmt"
17         "go/ast"
18         "go/build"
19         "go/format"
20         "go/token"
21         "log"
22         "os"
23         "os/exec"
24         "path"
25         "path/filepath"
26         "regexp"
27         "runtime"
28         "strconv"
29         "strings"
30         "text/template"
31
32         "golang.org/x/tools/go/buildutil"
33         "golang.org/x/tools/go/loader"
34         "golang.org/x/tools/refactor/importgraph"
35 )
36
37 // Move, given a package path and a destination package path, will try
38 // to move the given package to the new path. The Move function will
39 // first check for any conflicts preventing the move, such as a
40 // package already existing at the destination package path. If the
41 // move can proceed, it builds an import graph to find all imports of
42 // the packages whose paths need to be renamed. This includes uses of
43 // the subpackages of the package to be moved as those packages will
44 // also need to be moved. It then renames all imports to point to the
45 // new paths, and then moves the packages to their new paths.
46 func Move(ctxt *build.Context, from, to, moveTmpl string) error {
47         srcDir, err := srcDir(ctxt, from)
48         if err != nil {
49                 return err
50         }
51
52         // This should be the only place in the program that constructs
53         // file paths.
54         fromDir := buildutil.JoinPath(ctxt, srcDir, filepath.FromSlash(from))
55         toDir := buildutil.JoinPath(ctxt, srcDir, filepath.FromSlash(to))
56         toParent := filepath.Dir(toDir)
57         if !buildutil.IsDir(ctxt, toParent) {
58                 return fmt.Errorf("parent directory does not exist for path %s", toDir)
59         }
60
61         // Build the import graph and figure out which packages to update.
62         _, rev, errors := importgraph.Build(ctxt)
63         if len(errors) > 0 {
64                 // With a large GOPATH tree, errors are inevitable.
65                 // Report them but proceed.
66                 fmt.Fprintf(os.Stderr, "While scanning Go workspace:\n")
67                 for path, err := range errors {
68                         fmt.Fprintf(os.Stderr, "Package %q: %s.\n", path, err)
69                 }
70         }
71
72         // Determine the affected packages---the set of packages whose import
73         // statements need updating.
74         affectedPackages := map[string]bool{from: true}
75         destinations := make(map[string]string) // maps old import path to new import path
76         for pkg := range subpackages(ctxt, srcDir, from) {
77                 for r := range rev[pkg] {
78                         affectedPackages[r] = true
79                 }
80                 destinations[pkg] = strings.Replace(pkg, from, to, 1)
81         }
82
83         // Load all the affected packages.
84         iprog, err := loadProgram(ctxt, affectedPackages)
85         if err != nil {
86                 return err
87         }
88
89         // Prepare the move command, if one was supplied.
90         var cmd string
91         if moveTmpl != "" {
92                 if cmd, err = moveCmd(moveTmpl, fromDir, toDir); err != nil {
93                         return err
94                 }
95         }
96
97         m := mover{
98                 ctxt:             ctxt,
99                 rev:              rev,
100                 iprog:            iprog,
101                 from:             from,
102                 to:               to,
103                 fromDir:          fromDir,
104                 toDir:            toDir,
105                 affectedPackages: affectedPackages,
106                 destinations:     destinations,
107                 cmd:              cmd,
108         }
109
110         if err := m.checkValid(); err != nil {
111                 return err
112         }
113
114         m.move()
115
116         return nil
117 }
118
119 // srcDir returns the absolute path of the srcdir containing pkg.
120 func srcDir(ctxt *build.Context, pkg string) (string, error) {
121         for _, srcDir := range ctxt.SrcDirs() {
122                 path := buildutil.JoinPath(ctxt, srcDir, pkg)
123                 if buildutil.IsDir(ctxt, path) {
124                         return srcDir, nil
125                 }
126         }
127         return "", fmt.Errorf("src dir not found for package: %s", pkg)
128 }
129
130 // subpackages returns the set of packages in the given srcDir whose
131 // import path equals to root, or has "root/" as the prefix.
132 func subpackages(ctxt *build.Context, srcDir string, root string) map[string]bool {
133         var subs = make(map[string]bool)
134         buildutil.ForEachPackage(ctxt, func(pkg string, err error) {
135                 if err != nil {
136                         log.Fatalf("unexpected error in ForEachPackage: %v", err)
137                 }
138
139                 // Only process the package root, or a sub-package of it.
140                 if !(strings.HasPrefix(pkg, root) &&
141                         (len(pkg) == len(root) || pkg[len(root)] == '/')) {
142                         return
143                 }
144
145                 p, err := ctxt.Import(pkg, "", build.FindOnly)
146                 if err != nil {
147                         log.Fatalf("unexpected: package %s can not be located by build context: %s", pkg, err)
148                 }
149                 if p.SrcRoot == "" {
150                         log.Fatalf("unexpected: could not determine srcDir for package %s: %s", pkg, err)
151                 }
152                 if p.SrcRoot != srcDir {
153                         return
154                 }
155
156                 subs[pkg] = true
157         })
158         return subs
159 }
160
161 type mover struct {
162         // iprog contains all packages whose contents need to be updated
163         // with new package names or import paths.
164         iprog *loader.Program
165         ctxt  *build.Context
166         // rev is the reverse import graph.
167         rev importgraph.Graph
168         // from and to are the source and destination import
169         // paths. fromDir and toDir are the source and destination
170         // absolute paths that package source files will be moved between.
171         from, to, fromDir, toDir string
172         // affectedPackages is the set of all packages whose contents need
173         // to be updated to reflect new package names or import paths.
174         affectedPackages map[string]bool
175         // destinations maps each subpackage to be moved to its
176         // destination path.
177         destinations map[string]string
178         // cmd, if not empty, will be executed to move fromDir to toDir.
179         cmd string
180 }
181
182 func (m *mover) checkValid() error {
183         const prefix = "invalid move destination"
184
185         match, err := regexp.MatchString("^[_\\pL][_\\pL\\p{Nd}]*$", path.Base(m.to))
186         if err != nil {
187                 panic("regexp.MatchString failed")
188         }
189         if !match {
190                 return fmt.Errorf("%s: %s; gomvpkg does not support move destinations "+
191                         "whose base names are not valid go identifiers", prefix, m.to)
192         }
193
194         if buildutil.FileExists(m.ctxt, m.toDir) {
195                 return fmt.Errorf("%s: %s conflicts with file %s", prefix, m.to, m.toDir)
196         }
197         if buildutil.IsDir(m.ctxt, m.toDir) {
198                 return fmt.Errorf("%s: %s conflicts with directory %s", prefix, m.to, m.toDir)
199         }
200
201         for _, toSubPkg := range m.destinations {
202                 if _, err := m.ctxt.Import(toSubPkg, "", build.FindOnly); err == nil {
203                         return fmt.Errorf("%s: %s; package or subpackage %s already exists",
204                                 prefix, m.to, toSubPkg)
205                 }
206         }
207
208         return nil
209 }
210
211 // moveCmd produces the version control move command used to move fromDir to toDir by
212 // executing the given template.
213 func moveCmd(moveTmpl, fromDir, toDir string) (string, error) {
214         tmpl, err := template.New("movecmd").Parse(moveTmpl)
215         if err != nil {
216                 return "", err
217         }
218
219         var buf bytes.Buffer
220         err = tmpl.Execute(&buf, struct {
221                 Src string
222                 Dst string
223         }{fromDir, toDir})
224         return buf.String(), err
225 }
226
227 func (m *mover) move() error {
228         filesToUpdate := make(map[*ast.File]bool)
229
230         // Change the moved package's "package" declaration to its new base name.
231         pkg, ok := m.iprog.Imported[m.from]
232         if !ok {
233                 log.Fatalf("unexpected: package %s is not in import map", m.from)
234         }
235         newName := filepath.Base(m.to)
236         for _, f := range pkg.Files {
237                 // Update all import comments.
238                 for _, cg := range f.Comments {
239                         c := cg.List[0]
240                         if c.Slash >= f.Name.End() &&
241                                 sameLine(m.iprog.Fset, c.Slash, f.Name.End()) &&
242                                 (f.Decls == nil || c.Slash < f.Decls[0].Pos()) {
243                                 if strings.HasPrefix(c.Text, `// import "`) {
244                                         c.Text = `// import "` + m.to + `"`
245                                         break
246                                 }
247                                 if strings.HasPrefix(c.Text, `/* import "`) {
248                                         c.Text = `/* import "` + m.to + `" */`
249                                         break
250                                 }
251                         }
252                 }
253                 f.Name.Name = newName // change package decl
254                 filesToUpdate[f] = true
255         }
256
257         // Look through the external test packages (m.iprog.Created contains the external test packages).
258         for _, info := range m.iprog.Created {
259                 // Change the "package" declaration of the external test package.
260                 if info.Pkg.Path() == m.from+"_test" {
261                         for _, f := range info.Files {
262                                 f.Name.Name = newName + "_test" // change package decl
263                                 filesToUpdate[f] = true
264                         }
265                 }
266
267                 // Mark all the loaded external test packages, which import the "from" package,
268                 // as affected packages and update the imports.
269                 for _, imp := range info.Pkg.Imports() {
270                         if imp.Path() == m.from {
271                                 m.affectedPackages[info.Pkg.Path()] = true
272                                 m.iprog.Imported[info.Pkg.Path()] = info
273                                 if err := importName(m.iprog, info, m.from, path.Base(m.from), newName); err != nil {
274                                         return err
275                                 }
276                         }
277                 }
278         }
279
280         // Update imports of that package to use the new import name.
281         // None of the subpackages will change their name---only the from package
282         // itself will.
283         for p := range m.rev[m.from] {
284                 if err := importName(m.iprog, m.iprog.Imported[p], m.from, path.Base(m.from), newName); err != nil {
285                         return err
286                 }
287         }
288
289         // Update import paths for all imports by affected packages.
290         for ap := range m.affectedPackages {
291                 info, ok := m.iprog.Imported[ap]
292                 if !ok {
293                         log.Fatalf("unexpected: package %s is not in import map", ap)
294                 }
295                 for _, f := range info.Files {
296                         for _, imp := range f.Imports {
297                                 importPath, _ := strconv.Unquote(imp.Path.Value)
298                                 if newPath, ok := m.destinations[importPath]; ok {
299                                         imp.Path.Value = strconv.Quote(newPath)
300
301                                         oldName := path.Base(importPath)
302                                         if imp.Name != nil {
303                                                 oldName = imp.Name.Name
304                                         }
305
306                                         newName := path.Base(newPath)
307                                         if imp.Name == nil && oldName != newName {
308                                                 imp.Name = ast.NewIdent(oldName)
309                                         } else if imp.Name == nil || imp.Name.Name == newName {
310                                                 imp.Name = nil
311                                         }
312                                         filesToUpdate[f] = true
313                                 }
314                         }
315                 }
316         }
317
318         for f := range filesToUpdate {
319                 var buf bytes.Buffer
320                 if err := format.Node(&buf, m.iprog.Fset, f); err != nil {
321                         log.Printf("failed to pretty-print syntax tree: %v", err)
322                         continue
323                 }
324                 tokenFile := m.iprog.Fset.File(f.Pos())
325                 writeFile(tokenFile.Name(), buf.Bytes())
326         }
327
328         // Move the directories.
329         // If either the fromDir or toDir are contained under version control it is
330         // the user's responsibility to provide a custom move command that updates
331         // version control to reflect the move.
332         // TODO(matloob): If the parent directory of toDir does not exist, create it.
333         //      For now, it's required that it does exist.
334
335         if m.cmd != "" {
336                 // TODO(matloob): Verify that the windows and plan9 cases are correct.
337                 var cmd *exec.Cmd
338                 switch runtime.GOOS {
339                 case "windows":
340                         cmd = exec.Command("cmd", "/c", m.cmd)
341                 case "plan9":
342                         cmd = exec.Command("rc", "-c", m.cmd)
343                 default:
344                         cmd = exec.Command("sh", "-c", m.cmd)
345                 }
346                 cmd.Stderr = os.Stderr
347                 cmd.Stdout = os.Stdout
348                 if err := cmd.Run(); err != nil {
349                         return fmt.Errorf("version control system's move command failed: %v", err)
350                 }
351
352                 return nil
353         }
354
355         return moveDirectory(m.fromDir, m.toDir)
356 }
357
358 // sameLine reports whether two positions in the same file are on the same line.
359 func sameLine(fset *token.FileSet, x, y token.Pos) bool {
360         return fset.Position(x).Line == fset.Position(y).Line
361 }
362
363 var moveDirectory = func(from, to string) error {
364         return os.Rename(from, to)
365 }