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 / cmd / fiximports / main.go
diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201105173854-bc9fc8d8c4bc/cmd/fiximports/main.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201105173854-bc9fc8d8c4bc/cmd/fiximports/main.go
new file mode 100644 (file)
index 0000000..53a9944
--- /dev/null
@@ -0,0 +1,521 @@
+// Copyright 2015 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.
+
+// The fiximports command fixes import declarations to use the canonical
+// import path for packages that have an "import comment" as defined by
+// https://golang.org/s/go14customimport.
+//
+//
+// Background
+//
+// The Go 1 custom import path mechanism lets the maintainer of a
+// package give it a stable name by which clients may import and "go
+// get" it, independent of the underlying version control system (such
+// as Git) or server (such as github.com) that hosts it.  Requests for
+// the custom name are redirected to the underlying name.  This allows
+// packages to be migrated from one underlying server or system to
+// another without breaking existing clients.
+//
+// Because this redirect mechanism creates aliases for existing
+// packages, it's possible for a single program to import the same
+// package by its canonical name and by an alias.  The resulting
+// executable will contain two copies of the package, which is wasteful
+// at best and incorrect at worst.
+//
+// To avoid this, "go build" reports an error if it encounters a special
+// comment like the one below, and if the import path in the comment
+// does not match the path of the enclosing package relative to
+// GOPATH/src:
+//
+//      $ grep ^package $GOPATH/src/github.com/bob/vanity/foo/foo.go
+//     package foo // import "vanity.com/foo"
+//
+// The error from "go build" indicates that the package canonically
+// known as "vanity.com/foo" is locally installed under the
+// non-canonical name "github.com/bob/vanity/foo".
+//
+//
+// Usage
+//
+// When a package that you depend on introduces a custom import comment,
+// and your workspace imports it by the non-canonical name, your build
+// will stop working as soon as you update your copy of that package
+// using "go get -u".
+//
+// The purpose of the fiximports tool is to fix up all imports of the
+// non-canonical path within a Go workspace, replacing them with imports
+// of the canonical path.  Following a run of fiximports, the workspace
+// will no longer depend on the non-canonical copy of the package, so it
+// should be safe to delete.  It may be necessary to run "go get -u"
+// again to ensure that the package is locally installed under its
+// canonical path, if it was not already.
+//
+// The fiximports tool operates locally; it does not make HTTP requests
+// and does not discover new custom import comments.  It only operates
+// on non-canonical packages present in your workspace.
+//
+// The -baddomains flag is a list of domain names that should always be
+// considered non-canonical.  You can use this if you wish to make sure
+// that you no longer have any dependencies on packages from that
+// domain, even those that do not yet provide a canonical import path
+// comment.  For example, the default value of -baddomains includes the
+// moribund code hosting site code.google.com, so fiximports will report
+// an error for each import of a package from this domain remaining
+// after canonicalization.
+//
+// To see the changes fiximports would make without applying them, use
+// the -n flag.
+//
+package main
+
+import (
+       "bytes"
+       "encoding/json"
+       "flag"
+       "fmt"
+       "go/ast"
+       "go/build"
+       "go/format"
+       "go/parser"
+       "go/token"
+       "io"
+       "io/ioutil"
+       "log"
+       "os"
+       "os/exec"
+       "path"
+       "path/filepath"
+       "sort"
+       "strconv"
+       "strings"
+)
+
+// flags
+var (
+       dryrun     = flag.Bool("n", false, "dry run: show changes, but don't apply them")
+       badDomains = flag.String("baddomains", "code.google.com",
+               "a comma-separated list of domains from which packages should not be imported")
+       replaceFlag = flag.String("replace", "",
+               "a comma-separated list of noncanonical=canonical pairs of package paths.  If both items in a pair end with '...', they are treated as path prefixes.")
+)
+
+// seams for testing
+var (
+       stderr    io.Writer = os.Stderr
+       writeFile           = ioutil.WriteFile
+)
+
+const usage = `fiximports: rewrite import paths to use canonical package names.
+
+Usage: fiximports [-n] package...
+
+The package... arguments specify a list of packages
+in the style of the go tool; see "go help packages".
+Hint: use "all" or "..." to match the entire workspace.
+
+For details, see https://pkg.go.dev/golang.org/x/tools/cmd/fiximports
+
+Flags:
+  -n:         dry run: show changes, but don't apply them
+  -baddomains  a comma-separated list of domains from which packages
+               should not be imported
+`
+
+func main() {
+       flag.Parse()
+
+       if len(flag.Args()) == 0 {
+               fmt.Fprint(stderr, usage)
+               os.Exit(1)
+       }
+       if !fiximports(flag.Args()...) {
+               os.Exit(1)
+       }
+}
+
+type canonicalName struct{ path, name string }
+
+// fiximports fixes imports in the specified packages.
+// Invariant: a false result implies an error was already printed.
+func fiximports(packages ...string) bool {
+       // importedBy is the transpose of the package import graph.
+       importedBy := make(map[string]map[*build.Package]bool)
+
+       // addEdge adds an edge to the import graph.
+       addEdge := func(from *build.Package, to string) {
+               if to == "C" || to == "unsafe" {
+                       return // fake
+               }
+               pkgs := importedBy[to]
+               if pkgs == nil {
+                       pkgs = make(map[*build.Package]bool)
+                       importedBy[to] = pkgs
+               }
+               pkgs[from] = true
+       }
+
+       // List metadata for all packages in the workspace.
+       pkgs, err := list("...")
+       if err != nil {
+               fmt.Fprintf(stderr, "importfix: %v\n", err)
+               return false
+       }
+
+       // packageName maps each package's path to its name.
+       packageName := make(map[string]string)
+       for _, p := range pkgs {
+               packageName[p.ImportPath] = p.Package.Name
+       }
+
+       // canonical maps each non-canonical package path to
+       // its canonical path and name.
+       // A present nil value indicates that the canonical package
+       // is unknown: hosted on a bad domain with no redirect.
+       canonical := make(map[string]canonicalName)
+       domains := strings.Split(*badDomains, ",")
+
+       type replaceItem struct {
+               old, new    string
+               matchPrefix bool
+       }
+       var replace []replaceItem
+       for _, pair := range strings.Split(*replaceFlag, ",") {
+               if pair == "" {
+                       continue
+               }
+               words := strings.Split(pair, "=")
+               if len(words) != 2 {
+                       fmt.Fprintf(stderr, "importfix: -replace: %q is not of the form \"canonical=noncanonical\".\n", pair)
+                       return false
+               }
+               replace = append(replace, replaceItem{
+                       old: strings.TrimSuffix(words[0], "..."),
+                       new: strings.TrimSuffix(words[1], "..."),
+                       matchPrefix: strings.HasSuffix(words[0], "...") &&
+                               strings.HasSuffix(words[1], "..."),
+               })
+       }
+
+       // Find non-canonical packages and populate importedBy graph.
+       for _, p := range pkgs {
+               if p.Error != nil {
+                       msg := p.Error.Err
+                       if strings.Contains(msg, "code in directory") &&
+                               strings.Contains(msg, "expects import") {
+                               // don't show the very errors we're trying to fix
+                       } else {
+                               fmt.Fprintln(stderr, p.Error)
+                       }
+               }
+
+               for _, imp := range p.Imports {
+                       addEdge(&p.Package, imp)
+               }
+               for _, imp := range p.TestImports {
+                       addEdge(&p.Package, imp)
+               }
+               for _, imp := range p.XTestImports {
+                       addEdge(&p.Package, imp)
+               }
+
+               // Does package have an explicit import comment?
+               if p.ImportComment != "" {
+                       if p.ImportComment != p.ImportPath {
+                               canonical[p.ImportPath] = canonicalName{
+                                       path: p.Package.ImportComment,
+                                       name: p.Package.Name,
+                               }
+                       }
+               } else {
+                       // Is package matched by a -replace item?
+                       var newPath string
+                       for _, item := range replace {
+                               if item.matchPrefix {
+                                       if strings.HasPrefix(p.ImportPath, item.old) {
+                                               newPath = item.new + p.ImportPath[len(item.old):]
+                                               break
+                                       }
+                               } else if p.ImportPath == item.old {
+                                       newPath = item.new
+                                       break
+                               }
+                       }
+                       if newPath != "" {
+                               newName := packageName[newPath]
+                               if newName == "" {
+                                       newName = filepath.Base(newPath) // a guess
+                               }
+                               canonical[p.ImportPath] = canonicalName{
+                                       path: newPath,
+                                       name: newName,
+                               }
+                               continue
+                       }
+
+                       // Is package matched by a -baddomains item?
+                       for _, domain := range domains {
+                               slash := strings.Index(p.ImportPath, "/")
+                               if slash < 0 {
+                                       continue // no slash: standard package
+                               }
+                               if p.ImportPath[:slash] == domain {
+                                       // Package comes from bad domain and has no import comment.
+                                       // Report an error each time this package is imported.
+                                       canonical[p.ImportPath] = canonicalName{}
+
+                                       // TODO(adonovan): should we make an HTTP request to
+                                       // see if there's an HTTP redirect, a "go-import" meta tag,
+                                       // or an import comment in the latest revision?
+                                       // It would duplicate a lot of logic from "go get".
+                               }
+                               break
+                       }
+               }
+       }
+
+       // Find all clients (direct importers) of canonical packages.
+       // These are the packages that need fixing up.
+       clients := make(map[*build.Package]bool)
+       for path := range canonical {
+               for client := range importedBy[path] {
+                       clients[client] = true
+               }
+       }
+
+       // Restrict rewrites to the set of packages specified by the user.
+       if len(packages) == 1 && (packages[0] == "all" || packages[0] == "...") {
+               // no restriction
+       } else {
+               pkgs, err := list(packages...)
+               if err != nil {
+                       fmt.Fprintf(stderr, "importfix: %v\n", err)
+                       return false
+               }
+               seen := make(map[string]bool)
+               for _, p := range pkgs {
+                       seen[p.ImportPath] = true
+               }
+               for client := range clients {
+                       if !seen[client.ImportPath] {
+                               delete(clients, client)
+                       }
+               }
+       }
+
+       // Rewrite selected client packages.
+       ok := true
+       for client := range clients {
+               if !rewritePackage(client, canonical) {
+                       ok = false
+
+                       // There were errors.
+                       // Show direct and indirect imports of client.
+                       seen := make(map[string]bool)
+                       var direct, indirect []string
+                       for p := range importedBy[client.ImportPath] {
+                               direct = append(direct, p.ImportPath)
+                               seen[p.ImportPath] = true
+                       }
+
+                       var visit func(path string)
+                       visit = func(path string) {
+                               for q := range importedBy[path] {
+                                       qpath := q.ImportPath
+                                       if !seen[qpath] {
+                                               seen[qpath] = true
+                                               indirect = append(indirect, qpath)
+                                               visit(qpath)
+                                       }
+                               }
+                       }
+
+                       if direct != nil {
+                               fmt.Fprintf(stderr, "\timported directly by:\n")
+                               sort.Strings(direct)
+                               for _, path := range direct {
+                                       fmt.Fprintf(stderr, "\t\t%s\n", path)
+                                       visit(path)
+                               }
+
+                               if indirect != nil {
+                                       fmt.Fprintf(stderr, "\timported indirectly by:\n")
+                                       sort.Strings(indirect)
+                                       for _, path := range indirect {
+                                               fmt.Fprintf(stderr, "\t\t%s\n", path)
+                                       }
+                               }
+                       }
+               }
+       }
+
+       return ok
+}
+
+// Invariant: false result => error already printed.
+func rewritePackage(client *build.Package, canonical map[string]canonicalName) bool {
+       ok := true
+
+       used := make(map[string]bool)
+       var filenames []string
+       filenames = append(filenames, client.GoFiles...)
+       filenames = append(filenames, client.TestGoFiles...)
+       filenames = append(filenames, client.XTestGoFiles...)
+       var first bool
+       for _, filename := range filenames {
+               if !first {
+                       first = true
+                       fmt.Fprintf(stderr, "%s\n", client.ImportPath)
+               }
+               err := rewriteFile(filepath.Join(client.Dir, filename), canonical, used)
+               if err != nil {
+                       fmt.Fprintf(stderr, "\tERROR: %v\n", err)
+                       ok = false
+               }
+       }
+
+       // Show which imports were renamed in this package.
+       var keys []string
+       for key := range used {
+               keys = append(keys, key)
+       }
+       sort.Strings(keys)
+       for _, key := range keys {
+               if p := canonical[key]; p.path != "" {
+                       fmt.Fprintf(stderr, "\tfixed: %s -> %s\n", key, p.path)
+               } else {
+                       fmt.Fprintf(stderr, "\tERROR: %s has no import comment\n", key)
+                       ok = false
+               }
+       }
+
+       return ok
+}
+
+// rewrite reads, modifies, and writes filename, replacing all imports
+// of packages P in canonical by canonical[P].
+// It records in used which canonical packages were imported.
+// used[P]=="" indicates that P was imported but its canonical path is unknown.
+func rewriteFile(filename string, canonical map[string]canonicalName, used map[string]bool) error {
+       fset := token.NewFileSet()
+       f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
+       if err != nil {
+               return err
+       }
+       var changed bool
+       for _, imp := range f.Imports {
+               impPath, err := strconv.Unquote(imp.Path.Value)
+               if err != nil {
+                       log.Printf("%s: bad import spec %q: %v",
+                               fset.Position(imp.Pos()), imp.Path.Value, err)
+                       continue
+               }
+               canon, ok := canonical[impPath]
+               if !ok {
+                       continue // import path is canonical
+               }
+
+               used[impPath] = true
+
+               if canon.path == "" {
+                       // The canonical path is unknown (a -baddomain).
+                       // Show the offending import.
+                       // TODO(adonovan): should we show the actual source text?
+                       fmt.Fprintf(stderr, "\t%s:%d: import %q\n",
+                               shortPath(filename),
+                               fset.Position(imp.Pos()).Line, impPath)
+                       continue
+               }
+
+               changed = true
+
+               imp.Path.Value = strconv.Quote(canon.path)
+
+               // Add a renaming import if necessary.
+               //
+               // This is a guess at best.  We can't see whether a 'go
+               // get' of the canonical import path would have the same
+               // name or not.  Assume it's the last segment.
+               newBase := path.Base(canon.path)
+               if imp.Name == nil && newBase != canon.name {
+                       imp.Name = &ast.Ident{Name: canon.name}
+               }
+       }
+
+       if changed && !*dryrun {
+               var buf bytes.Buffer
+               if err := format.Node(&buf, fset, f); err != nil {
+                       return fmt.Errorf("%s: couldn't format file: %v", filename, err)
+               }
+               return writeFile(filename, buf.Bytes(), 0644)
+       }
+
+       return nil
+}
+
+// listPackage is a copy of cmd/go/list.Package.
+// It has more fields than build.Package and we need some of them.
+type listPackage struct {
+       build.Package
+       Error *packageError // error loading package
+}
+
+// A packageError describes an error loading information about a package.
+type packageError struct {
+       ImportStack []string // shortest path from package named on command line to this one
+       Pos         string   // position of error
+       Err         string   // the error itself
+}
+
+func (e packageError) Error() string {
+       if e.Pos != "" {
+               return e.Pos + ": " + e.Err
+       }
+       return e.Err
+}
+
+// list runs 'go list' with the specified arguments and returns the
+// metadata for matching packages.
+func list(args ...string) ([]*listPackage, error) {
+       cmd := exec.Command("go", append([]string{"list", "-e", "-json"}, args...)...)
+       cmd.Stdout = new(bytes.Buffer)
+       cmd.Stderr = stderr
+       if err := cmd.Run(); err != nil {
+               return nil, err
+       }
+
+       dec := json.NewDecoder(cmd.Stdout.(io.Reader))
+       var pkgs []*listPackage
+       for {
+               var p listPackage
+               if err := dec.Decode(&p); err == io.EOF {
+                       break
+               } else if err != nil {
+                       return nil, err
+               }
+               pkgs = append(pkgs, &p)
+       }
+       return pkgs, nil
+}
+
+// cwd contains the current working directory of the tool.
+//
+// It is initialized directly so that its value will be set for any other
+// package variables or init functions that depend on it, such as the gopath
+// variable in main_test.go.
+var cwd string = func() string {
+       cwd, err := os.Getwd()
+       if err != nil {
+               log.Fatalf("os.Getwd: %v", err)
+       }
+       return cwd
+}()
+
+// shortPath returns an absolute or relative name for path, whatever is shorter.
+// Plundered from $GOROOT/src/cmd/go/build.go.
+func shortPath(path string) string {
+       if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) {
+               return rel
+       }
+       return path
+}