aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/golang.org/x/tools/imports/mod.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/tools/imports/mod.go')
-rw-r--r--vendor/golang.org/x/tools/imports/mod.go355
1 files changed, 355 insertions, 0 deletions
diff --git a/vendor/golang.org/x/tools/imports/mod.go b/vendor/golang.org/x/tools/imports/mod.go
new file mode 100644
index 00000000..018c43ce
--- /dev/null
+++ b/vendor/golang.org/x/tools/imports/mod.go
@@ -0,0 +1,355 @@
+package imports
+
+import (
+ "bytes"
+ "encoding/json"
+ "io/ioutil"
+ "log"
+ "os"
+ "path"
+ "path/filepath"
+ "regexp"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "golang.org/x/tools/internal/gopathwalk"
+ "golang.org/x/tools/internal/module"
+)
+
+// moduleResolver implements resolver for modules using the go command as little
+// as feasible.
+type moduleResolver struct {
+ env *fixEnv
+
+ initialized bool
+ main *moduleJSON
+ modsByModPath []*moduleJSON // All modules, ordered by # of path components in module Path...
+ modsByDir []*moduleJSON // ...or Dir.
+}
+
+type moduleJSON struct {
+ Path string // module path
+ Version string // module version
+ Versions []string // available module versions (with -versions)
+ Replace *moduleJSON // replaced by this module
+ Time *time.Time // time version was created
+ Update *moduleJSON // available update, if any (with -u)
+ Main bool // is this the main module?
+ Indirect bool // is this module only an indirect dependency of main module?
+ Dir string // directory holding files for this module, if any
+ GoMod string // path to go.mod file for this module, if any
+ Error *moduleErrorJSON // error loading module
+}
+
+type moduleErrorJSON struct {
+ Err string // the error itself
+}
+
+func (r *moduleResolver) init() error {
+ if r.initialized {
+ return nil
+ }
+ stdout, err := r.env.invokeGo("list", "-m", "-json", "...")
+ if err != nil {
+ return err
+ }
+ for dec := json.NewDecoder(stdout); dec.More(); {
+ mod := &moduleJSON{}
+ if err := dec.Decode(mod); err != nil {
+ return err
+ }
+ if mod.Dir == "" {
+ if Debug {
+ log.Printf("module %v has not been downloaded and will be ignored", mod.Path)
+ }
+ // Can't do anything with a module that's not downloaded.
+ continue
+ }
+ r.modsByModPath = append(r.modsByModPath, mod)
+ r.modsByDir = append(r.modsByDir, mod)
+ if mod.Main {
+ r.main = mod
+ }
+ }
+
+ sort.Slice(r.modsByModPath, func(i, j int) bool {
+ count := func(x int) int {
+ return strings.Count(r.modsByModPath[x].Path, "/")
+ }
+ return count(j) < count(i) // descending order
+ })
+ sort.Slice(r.modsByDir, func(i, j int) bool {
+ count := func(x int) int {
+ return strings.Count(r.modsByDir[x].Dir, "/")
+ }
+ return count(j) < count(i) // descending order
+ })
+
+ r.initialized = true
+ return nil
+}
+
+// findPackage returns the module and directory that contains the package at
+// the given import path, or returns nil, "" if no module is in scope.
+func (r *moduleResolver) findPackage(importPath string) (*moduleJSON, string) {
+ for _, m := range r.modsByModPath {
+ if !strings.HasPrefix(importPath, m.Path) {
+ continue
+ }
+ pathInModule := importPath[len(m.Path):]
+ pkgDir := filepath.Join(m.Dir, pathInModule)
+ if dirIsNestedModule(pkgDir, m) {
+ continue
+ }
+
+ pkgFiles, err := ioutil.ReadDir(pkgDir)
+ if err != nil {
+ continue
+ }
+
+ // A module only contains a package if it has buildable go
+ // files in that directory. If not, it could be provided by an
+ // outer module. See #29736.
+ for _, fi := range pkgFiles {
+ if ok, _ := r.env.buildContext().MatchFile(pkgDir, fi.Name()); ok {
+ return m, pkgDir
+ }
+ }
+ }
+ return nil, ""
+}
+
+// findModuleByDir returns the module that contains dir, or nil if no such
+// module is in scope.
+func (r *moduleResolver) findModuleByDir(dir string) *moduleJSON {
+ // This is quite tricky and may not be correct. dir could be:
+ // - a package in the main module.
+ // - a replace target underneath the main module's directory.
+ // - a nested module in the above.
+ // - a replace target somewhere totally random.
+ // - a nested module in the above.
+ // - in the mod cache.
+ // - in /vendor/ in -mod=vendor mode.
+ // - nested module? Dunno.
+ // Rumor has it that replace targets cannot contain other replace targets.
+ for _, m := range r.modsByDir {
+ if !strings.HasPrefix(dir, m.Dir) {
+ continue
+ }
+
+ if dirIsNestedModule(dir, m) {
+ continue
+ }
+
+ return m
+ }
+ return nil
+}
+
+// dirIsNestedModule reports if dir is contained in a nested module underneath
+// mod, not actually in mod.
+func dirIsNestedModule(dir string, mod *moduleJSON) bool {
+ if !strings.HasPrefix(dir, mod.Dir) {
+ return false
+ }
+ mf := findModFile(dir)
+ if mf == "" {
+ return false
+ }
+ return filepath.Dir(mf) != mod.Dir
+}
+
+func findModFile(dir string) string {
+ for {
+ f := filepath.Join(dir, "go.mod")
+ info, err := os.Stat(f)
+ if err == nil && !info.IsDir() {
+ return f
+ }
+ d := filepath.Dir(dir)
+ if len(d) >= len(dir) {
+ return "" // reached top of file system, no go.mod
+ }
+ dir = d
+ }
+}
+
+func (r *moduleResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) {
+ if err := r.init(); err != nil {
+ return nil, err
+ }
+ names := map[string]string{}
+ for _, path := range importPaths {
+ _, packageDir := r.findPackage(path)
+ if packageDir == "" {
+ continue
+ }
+ name, err := packageDirToName(packageDir)
+ if err != nil {
+ continue
+ }
+ names[path] = name
+ }
+ return names, nil
+}
+
+func (r *moduleResolver) scan(_ references) ([]*pkg, error) {
+ if err := r.init(); err != nil {
+ return nil, err
+ }
+
+ // Walk GOROOT, GOPATH/pkg/mod, and the main module.
+ roots := []gopathwalk.Root{
+ {filepath.Join(r.env.GOROOT, "/src"), gopathwalk.RootGOROOT},
+ }
+ if r.main != nil {
+ roots = append(roots, gopathwalk.Root{r.main.Dir, gopathwalk.RootCurrentModule})
+ }
+ for _, p := range filepath.SplitList(r.env.GOPATH) {
+ roots = append(roots, gopathwalk.Root{filepath.Join(p, "/pkg/mod"), gopathwalk.RootModuleCache})
+ }
+
+ // Walk replace targets, just in case they're not in any of the above.
+ for _, mod := range r.modsByModPath {
+ if mod.Replace != nil {
+ roots = append(roots, gopathwalk.Root{mod.Dir, gopathwalk.RootOther})
+ }
+ }
+
+ var result []*pkg
+ dupCheck := make(map[string]bool)
+ var mu sync.Mutex
+
+ gopathwalk.Walk(roots, func(root gopathwalk.Root, dir string) {
+ mu.Lock()
+ defer mu.Unlock()
+
+ if _, dup := dupCheck[dir]; dup {
+ return
+ }
+
+ dupCheck[dir] = true
+
+ subdir := ""
+ if dir != root.Path {
+ subdir = dir[len(root.Path)+len("/"):]
+ }
+ importPath := filepath.ToSlash(subdir)
+ if strings.HasPrefix(importPath, "vendor/") {
+ // Ignore vendor dirs. If -mod=vendor is on, then things
+ // should mostly just work, but when it's not vendor/
+ // is a mess. There's no easy way to tell if it's on.
+ // We can still find things in the mod cache and
+ // map them into /vendor when -mod=vendor is on.
+ return
+ }
+ switch root.Type {
+ case gopathwalk.RootCurrentModule:
+ importPath = path.Join(r.main.Path, filepath.ToSlash(subdir))
+ case gopathwalk.RootModuleCache:
+ matches := modCacheRegexp.FindStringSubmatch(subdir)
+ modPath, err := module.DecodePath(filepath.ToSlash(matches[1]))
+ if err != nil {
+ if Debug {
+ log.Printf("decoding module cache path %q: %v", subdir, err)
+ }
+ return
+ }
+ importPath = path.Join(modPath, filepath.ToSlash(matches[3]))
+ case gopathwalk.RootGOROOT:
+ importPath = subdir
+ }
+
+ // Check if the directory is underneath a module that's in scope.
+ if mod := r.findModuleByDir(dir); mod != nil {
+ // It is. If dir is the target of a replace directive,
+ // our guessed import path is wrong. Use the real one.
+ if mod.Dir == dir {
+ importPath = mod.Path
+ } else {
+ dirInMod := dir[len(mod.Dir)+len("/"):]
+ importPath = path.Join(mod.Path, filepath.ToSlash(dirInMod))
+ }
+ } else {
+ // The package is in an unknown module. Check that it's
+ // not obviously impossible to import.
+ var modFile string
+ switch root.Type {
+ case gopathwalk.RootModuleCache:
+ matches := modCacheRegexp.FindStringSubmatch(subdir)
+ modFile = filepath.Join(matches[1], "@", matches[2], "go.mod")
+ default:
+ modFile = findModFile(dir)
+ }
+
+ modBytes, err := ioutil.ReadFile(modFile)
+ if err == nil && !strings.HasPrefix(importPath, modulePath(modBytes)) {
+ // The module's declared path does not match
+ // its expected path. It probably needs a
+ // replace directive we don't have.
+ return
+ }
+ }
+ // We may have discovered a package that has a different version
+ // in scope already. Canonicalize to that one if possible.
+ if _, canonicalDir := r.findPackage(importPath); canonicalDir != "" {
+ dir = canonicalDir
+ }
+
+ result = append(result, &pkg{
+ importPathShort: VendorlessPath(importPath),
+ dir: dir,
+ })
+ }, gopathwalk.Options{Debug: Debug, ModulesEnabled: true})
+ return result, nil
+}
+
+// modCacheRegexp splits a path in a module cache into module, module version, and package.
+var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`)
+
+var (
+ slashSlash = []byte("//")
+ moduleStr = []byte("module")
+)
+
+// modulePath returns the module path from the gomod file text.
+// If it cannot find a module path, it returns an empty string.
+// It is tolerant of unrelated problems in the go.mod file.
+//
+// Copied from cmd/go/internal/modfile.
+func modulePath(mod []byte) string {
+ for len(mod) > 0 {
+ line := mod
+ mod = nil
+ if i := bytes.IndexByte(line, '\n'); i >= 0 {
+ line, mod = line[:i], line[i+1:]
+ }
+ if i := bytes.Index(line, slashSlash); i >= 0 {
+ line = line[:i]
+ }
+ line = bytes.TrimSpace(line)
+ if !bytes.HasPrefix(line, moduleStr) {
+ continue
+ }
+ line = line[len(moduleStr):]
+ n := len(line)
+ line = bytes.TrimSpace(line)
+ if len(line) == n || len(line) == 0 {
+ continue
+ }
+
+ if line[0] == '"' || line[0] == '`' {
+ p, err := strconv.Unquote(string(line))
+ if err != nil {
+ return "" // malformed quoted string or multiline module path
+ }
+ return p
+ }
+
+ return string(line)
+ }
+ return "" // missing module path
+}