github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/internal/imports/mod.go (about)

     1  // Copyright 2019 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 imports
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"encoding/json"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"os"
    14  	"path"
    15  	"path/filepath"
    16  	"regexp"
    17  	"sort"
    18  	"strconv"
    19  	"strings"
    20  
    21  	"golang.org/x/mod/module"
    22  	"golang.org/x/tools/internal/gocommand"
    23  	"golang.org/x/tools/internal/gopathwalk"
    24  )
    25  
    26  // ModuleResolver implements resolver for modules using the go command as little
    27  // as feasible.
    28  type ModuleResolver struct {
    29  	env            *ProcessEnv
    30  	moduleCacheDir string
    31  	dummyVendorMod *gocommand.ModuleJSON // If vendoring is enabled, the pseudo-module that represents the /vendor directory.
    32  	roots          []gopathwalk.Root
    33  	scanSema       chan struct{} // scanSema prevents concurrent scans and guards scannedRoots.
    34  	scannedRoots   map[gopathwalk.Root]bool
    35  
    36  	initialized   bool
    37  	mains         []*gocommand.ModuleJSON
    38  	mainByDir     map[string]*gocommand.ModuleJSON
    39  	modsByModPath []*gocommand.ModuleJSON // All modules, ordered by # of path components in module Path...
    40  	modsByDir     []*gocommand.ModuleJSON // ...or Dir.
    41  
    42  	// moduleCacheCache stores information about the module cache.
    43  	moduleCacheCache *dirInfoCache
    44  	otherCache       *dirInfoCache
    45  }
    46  
    47  func newModuleResolver(e *ProcessEnv) *ModuleResolver {
    48  	r := &ModuleResolver{
    49  		env:      e,
    50  		scanSema: make(chan struct{}, 1),
    51  	}
    52  	r.scanSema <- struct{}{}
    53  	return r
    54  }
    55  
    56  func (r *ModuleResolver) init() error {
    57  	if r.initialized {
    58  		return nil
    59  	}
    60  
    61  	goenv, err := r.env.goEnv()
    62  	if err != nil {
    63  		return err
    64  	}
    65  	inv := gocommand.Invocation{
    66  		BuildFlags: r.env.BuildFlags,
    67  		ModFlag:    r.env.ModFlag,
    68  		ModFile:    r.env.ModFile,
    69  		Env:        r.env.env(),
    70  		Logf:       r.env.Logf,
    71  		WorkingDir: r.env.WorkingDir,
    72  	}
    73  
    74  	vendorEnabled := false
    75  	var mainModVendor *gocommand.ModuleJSON
    76  
    77  	// Module vendor directories are ignored in workspace mode:
    78  	// https://go.googlesource.com/proposal/+/master/design/45713-workspace.md
    79  	if len(r.env.Env["GOWORK"]) == 0 {
    80  		vendorEnabled, mainModVendor, err = gocommand.VendorEnabled(context.TODO(), inv, r.env.GocmdRunner)
    81  		if err != nil {
    82  			return err
    83  		}
    84  	}
    85  
    86  	if mainModVendor != nil && vendorEnabled {
    87  		// Vendor mode is on, so all the non-Main modules are irrelevant,
    88  		// and we need to search /vendor for everything.
    89  		r.mains = []*gocommand.ModuleJSON{mainModVendor}
    90  		r.dummyVendorMod = &gocommand.ModuleJSON{
    91  			Path: "",
    92  			Dir:  filepath.Join(mainModVendor.Dir, "vendor"),
    93  		}
    94  		r.modsByModPath = []*gocommand.ModuleJSON{mainModVendor, r.dummyVendorMod}
    95  		r.modsByDir = []*gocommand.ModuleJSON{mainModVendor, r.dummyVendorMod}
    96  	} else {
    97  		// Vendor mode is off, so run go list -m ... to find everything.
    98  		err := r.initAllMods()
    99  		// We expect an error when running outside of a module with
   100  		// GO111MODULE=on. Other errors are fatal.
   101  		if err != nil {
   102  			if errMsg := err.Error(); !strings.Contains(errMsg, "working directory is not part of a module") && !strings.Contains(errMsg, "go.mod file not found") {
   103  				return err
   104  			}
   105  		}
   106  	}
   107  
   108  	if gmc := r.env.Env["GOMODCACHE"]; gmc != "" {
   109  		r.moduleCacheDir = gmc
   110  	} else {
   111  		gopaths := filepath.SplitList(goenv["GOPATH"])
   112  		if len(gopaths) == 0 {
   113  			return fmt.Errorf("empty GOPATH")
   114  		}
   115  		r.moduleCacheDir = filepath.Join(gopaths[0], "/pkg/mod")
   116  	}
   117  
   118  	sort.Slice(r.modsByModPath, func(i, j int) bool {
   119  		count := func(x int) int {
   120  			return strings.Count(r.modsByModPath[x].Path, "/")
   121  		}
   122  		return count(j) < count(i) // descending order
   123  	})
   124  	sort.Slice(r.modsByDir, func(i, j int) bool {
   125  		count := func(x int) int {
   126  			return strings.Count(r.modsByDir[x].Dir, "/")
   127  		}
   128  		return count(j) < count(i) // descending order
   129  	})
   130  
   131  	r.roots = []gopathwalk.Root{
   132  		{Path: filepath.Join(goenv["GOROOT"], "/src"), Type: gopathwalk.RootGOROOT},
   133  	}
   134  	r.mainByDir = make(map[string]*gocommand.ModuleJSON)
   135  	for _, main := range r.mains {
   136  		r.roots = append(r.roots, gopathwalk.Root{Path: main.Dir, Type: gopathwalk.RootCurrentModule})
   137  		r.mainByDir[main.Dir] = main
   138  	}
   139  	if vendorEnabled {
   140  		r.roots = append(r.roots, gopathwalk.Root{Path: r.dummyVendorMod.Dir, Type: gopathwalk.RootOther})
   141  	} else {
   142  		addDep := func(mod *gocommand.ModuleJSON) {
   143  			if mod.Replace == nil {
   144  				// This is redundant with the cache, but we'll skip it cheaply enough.
   145  				r.roots = append(r.roots, gopathwalk.Root{Path: mod.Dir, Type: gopathwalk.RootModuleCache})
   146  			} else {
   147  				r.roots = append(r.roots, gopathwalk.Root{Path: mod.Dir, Type: gopathwalk.RootOther})
   148  			}
   149  		}
   150  		// Walk dependent modules before scanning the full mod cache, direct deps first.
   151  		for _, mod := range r.modsByModPath {
   152  			if !mod.Indirect && !mod.Main {
   153  				addDep(mod)
   154  			}
   155  		}
   156  		for _, mod := range r.modsByModPath {
   157  			if mod.Indirect && !mod.Main {
   158  				addDep(mod)
   159  			}
   160  		}
   161  		r.roots = append(r.roots, gopathwalk.Root{Path: r.moduleCacheDir, Type: gopathwalk.RootModuleCache})
   162  	}
   163  
   164  	r.scannedRoots = map[gopathwalk.Root]bool{}
   165  	if r.moduleCacheCache == nil {
   166  		r.moduleCacheCache = &dirInfoCache{
   167  			dirs:      map[string]*directoryPackageInfo{},
   168  			listeners: map[*int]cacheListener{},
   169  		}
   170  	}
   171  	if r.otherCache == nil {
   172  		r.otherCache = &dirInfoCache{
   173  			dirs:      map[string]*directoryPackageInfo{},
   174  			listeners: map[*int]cacheListener{},
   175  		}
   176  	}
   177  	r.initialized = true
   178  	return nil
   179  }
   180  
   181  func (r *ModuleResolver) initAllMods() error {
   182  	stdout, err := r.env.invokeGo(context.TODO(), "list", "-m", "-e", "-json", "...")
   183  	if err != nil {
   184  		return err
   185  	}
   186  	for dec := json.NewDecoder(stdout); dec.More(); {
   187  		mod := &gocommand.ModuleJSON{}
   188  		if err := dec.Decode(mod); err != nil {
   189  			return err
   190  		}
   191  		if mod.Dir == "" {
   192  			if r.env.Logf != nil {
   193  				r.env.Logf("module %v has not been downloaded and will be ignored", mod.Path)
   194  			}
   195  			// Can't do anything with a module that's not downloaded.
   196  			continue
   197  		}
   198  		// golang/go#36193: the go command doesn't always clean paths.
   199  		mod.Dir = filepath.Clean(mod.Dir)
   200  		r.modsByModPath = append(r.modsByModPath, mod)
   201  		r.modsByDir = append(r.modsByDir, mod)
   202  		if mod.Main {
   203  			r.mains = append(r.mains, mod)
   204  		}
   205  	}
   206  	return nil
   207  }
   208  
   209  func (r *ModuleResolver) ClearForNewScan() {
   210  	<-r.scanSema
   211  	r.scannedRoots = map[gopathwalk.Root]bool{}
   212  	r.otherCache = &dirInfoCache{
   213  		dirs:      map[string]*directoryPackageInfo{},
   214  		listeners: map[*int]cacheListener{},
   215  	}
   216  	r.scanSema <- struct{}{}
   217  }
   218  
   219  func (r *ModuleResolver) ClearForNewMod() {
   220  	<-r.scanSema
   221  	*r = ModuleResolver{
   222  		env:              r.env,
   223  		moduleCacheCache: r.moduleCacheCache,
   224  		otherCache:       r.otherCache,
   225  		scanSema:         r.scanSema,
   226  	}
   227  	r.init()
   228  	r.scanSema <- struct{}{}
   229  }
   230  
   231  // findPackage returns the module and directory that contains the package at
   232  // the given import path, or returns nil, "" if no module is in scope.
   233  func (r *ModuleResolver) findPackage(importPath string) (*gocommand.ModuleJSON, string) {
   234  	// This can't find packages in the stdlib, but that's harmless for all
   235  	// the existing code paths.
   236  	for _, m := range r.modsByModPath {
   237  		if !strings.HasPrefix(importPath, m.Path) {
   238  			continue
   239  		}
   240  		pathInModule := importPath[len(m.Path):]
   241  		pkgDir := filepath.Join(m.Dir, pathInModule)
   242  		if r.dirIsNestedModule(pkgDir, m) {
   243  			continue
   244  		}
   245  
   246  		if info, ok := r.cacheLoad(pkgDir); ok {
   247  			if loaded, err := info.reachedStatus(nameLoaded); loaded {
   248  				if err != nil {
   249  					continue // No package in this dir.
   250  				}
   251  				return m, pkgDir
   252  			}
   253  			if scanned, err := info.reachedStatus(directoryScanned); scanned && err != nil {
   254  				continue // Dir is unreadable, etc.
   255  			}
   256  			// This is slightly wrong: a directory doesn't have to have an
   257  			// importable package to count as a package for package-to-module
   258  			// resolution. package main or _test files should count but
   259  			// don't.
   260  			// TODO(heschi): fix this.
   261  			if _, err := r.cachePackageName(info); err == nil {
   262  				return m, pkgDir
   263  			}
   264  		}
   265  
   266  		// Not cached. Read the filesystem.
   267  		pkgFiles, err := ioutil.ReadDir(pkgDir)
   268  		if err != nil {
   269  			continue
   270  		}
   271  		// A module only contains a package if it has buildable go
   272  		// files in that directory. If not, it could be provided by an
   273  		// outer module. See #29736.
   274  		for _, fi := range pkgFiles {
   275  			if ok, _ := r.env.matchFile(pkgDir, fi.Name()); ok {
   276  				return m, pkgDir
   277  			}
   278  		}
   279  	}
   280  	return nil, ""
   281  }
   282  
   283  func (r *ModuleResolver) cacheLoad(dir string) (directoryPackageInfo, bool) {
   284  	if info, ok := r.moduleCacheCache.Load(dir); ok {
   285  		return info, ok
   286  	}
   287  	return r.otherCache.Load(dir)
   288  }
   289  
   290  func (r *ModuleResolver) cacheStore(info directoryPackageInfo) {
   291  	if info.rootType == gopathwalk.RootModuleCache {
   292  		r.moduleCacheCache.Store(info.dir, info)
   293  	} else {
   294  		r.otherCache.Store(info.dir, info)
   295  	}
   296  }
   297  
   298  func (r *ModuleResolver) cacheKeys() []string {
   299  	return append(r.moduleCacheCache.Keys(), r.otherCache.Keys()...)
   300  }
   301  
   302  // cachePackageName caches the package name for a dir already in the cache.
   303  func (r *ModuleResolver) cachePackageName(info directoryPackageInfo) (string, error) {
   304  	if info.rootType == gopathwalk.RootModuleCache {
   305  		return r.moduleCacheCache.CachePackageName(info)
   306  	}
   307  	return r.otherCache.CachePackageName(info)
   308  }
   309  
   310  func (r *ModuleResolver) cacheExports(ctx context.Context, env *ProcessEnv, info directoryPackageInfo) (string, []string, error) {
   311  	if info.rootType == gopathwalk.RootModuleCache {
   312  		return r.moduleCacheCache.CacheExports(ctx, env, info)
   313  	}
   314  	return r.otherCache.CacheExports(ctx, env, info)
   315  }
   316  
   317  // findModuleByDir returns the module that contains dir, or nil if no such
   318  // module is in scope.
   319  func (r *ModuleResolver) findModuleByDir(dir string) *gocommand.ModuleJSON {
   320  	// This is quite tricky and may not be correct. dir could be:
   321  	// - a package in the main module.
   322  	// - a replace target underneath the main module's directory.
   323  	//    - a nested module in the above.
   324  	// - a replace target somewhere totally random.
   325  	//    - a nested module in the above.
   326  	// - in the mod cache.
   327  	// - in /vendor/ in -mod=vendor mode.
   328  	//    - nested module? Dunno.
   329  	// Rumor has it that replace targets cannot contain other replace targets.
   330  	for _, m := range r.modsByDir {
   331  		if !strings.HasPrefix(dir, m.Dir) {
   332  			continue
   333  		}
   334  
   335  		if r.dirIsNestedModule(dir, m) {
   336  			continue
   337  		}
   338  
   339  		return m
   340  	}
   341  	return nil
   342  }
   343  
   344  // dirIsNestedModule reports if dir is contained in a nested module underneath
   345  // mod, not actually in mod.
   346  func (r *ModuleResolver) dirIsNestedModule(dir string, mod *gocommand.ModuleJSON) bool {
   347  	if !strings.HasPrefix(dir, mod.Dir) {
   348  		return false
   349  	}
   350  	if r.dirInModuleCache(dir) {
   351  		// Nested modules in the module cache are pruned,
   352  		// so it cannot be a nested module.
   353  		return false
   354  	}
   355  	if mod != nil && mod == r.dummyVendorMod {
   356  		// The /vendor pseudomodule is flattened and doesn't actually count.
   357  		return false
   358  	}
   359  	modDir, _ := r.modInfo(dir)
   360  	if modDir == "" {
   361  		return false
   362  	}
   363  	return modDir != mod.Dir
   364  }
   365  
   366  func (r *ModuleResolver) modInfo(dir string) (modDir string, modName string) {
   367  	readModName := func(modFile string) string {
   368  		modBytes, err := ioutil.ReadFile(modFile)
   369  		if err != nil {
   370  			return ""
   371  		}
   372  		return modulePath(modBytes)
   373  	}
   374  
   375  	if r.dirInModuleCache(dir) {
   376  		if matches := modCacheRegexp.FindStringSubmatch(dir); len(matches) == 3 {
   377  			index := strings.Index(dir, matches[1]+"@"+matches[2])
   378  			modDir := filepath.Join(dir[:index], matches[1]+"@"+matches[2])
   379  			return modDir, readModName(filepath.Join(modDir, "go.mod"))
   380  		}
   381  	}
   382  	for {
   383  		if info, ok := r.cacheLoad(dir); ok {
   384  			return info.moduleDir, info.moduleName
   385  		}
   386  		f := filepath.Join(dir, "go.mod")
   387  		info, err := os.Stat(f)
   388  		if err == nil && !info.IsDir() {
   389  			return dir, readModName(f)
   390  		}
   391  
   392  		d := filepath.Dir(dir)
   393  		if len(d) >= len(dir) {
   394  			return "", "" // reached top of file system, no go.mod
   395  		}
   396  		dir = d
   397  	}
   398  }
   399  
   400  func (r *ModuleResolver) dirInModuleCache(dir string) bool {
   401  	if r.moduleCacheDir == "" {
   402  		return false
   403  	}
   404  	return strings.HasPrefix(dir, r.moduleCacheDir)
   405  }
   406  
   407  func (r *ModuleResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) {
   408  	if err := r.init(); err != nil {
   409  		return nil, err
   410  	}
   411  	names := map[string]string{}
   412  	for _, path := range importPaths {
   413  		_, packageDir := r.findPackage(path)
   414  		if packageDir == "" {
   415  			continue
   416  		}
   417  		name, err := packageDirToName(packageDir)
   418  		if err != nil {
   419  			continue
   420  		}
   421  		names[path] = name
   422  	}
   423  	return names, nil
   424  }
   425  
   426  func (r *ModuleResolver) scan(ctx context.Context, callback *scanCallback) error {
   427  	if err := r.init(); err != nil {
   428  		return err
   429  	}
   430  
   431  	processDir := func(info directoryPackageInfo) {
   432  		// Skip this directory if we were not able to get the package information successfully.
   433  		if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil {
   434  			return
   435  		}
   436  		pkg, err := r.canonicalize(info)
   437  		if err != nil {
   438  			return
   439  		}
   440  
   441  		if !callback.dirFound(pkg) {
   442  			return
   443  		}
   444  		pkg.packageName, err = r.cachePackageName(info)
   445  		if err != nil {
   446  			return
   447  		}
   448  
   449  		if !callback.packageNameLoaded(pkg) {
   450  			return
   451  		}
   452  		_, exports, err := r.loadExports(ctx, pkg, false)
   453  		if err != nil {
   454  			return
   455  		}
   456  		callback.exportsLoaded(pkg, exports)
   457  	}
   458  
   459  	// Start processing everything in the cache, and listen for the new stuff
   460  	// we discover in the walk below.
   461  	stop1 := r.moduleCacheCache.ScanAndListen(ctx, processDir)
   462  	defer stop1()
   463  	stop2 := r.otherCache.ScanAndListen(ctx, processDir)
   464  	defer stop2()
   465  
   466  	// We assume cached directories are fully cached, including all their
   467  	// children, and have not changed. We can skip them.
   468  	skip := func(root gopathwalk.Root, dir string) bool {
   469  		if r.env.SkipPathInScan != nil && root.Type == gopathwalk.RootCurrentModule {
   470  			if root.Path == dir {
   471  				return false
   472  			}
   473  
   474  			if r.env.SkipPathInScan(filepath.Clean(dir)) {
   475  				return true
   476  			}
   477  		}
   478  
   479  		info, ok := r.cacheLoad(dir)
   480  		if !ok {
   481  			return false
   482  		}
   483  		// This directory can be skipped as long as we have already scanned it.
   484  		// Packages with errors will continue to have errors, so there is no need
   485  		// to rescan them.
   486  		packageScanned, _ := info.reachedStatus(directoryScanned)
   487  		return packageScanned
   488  	}
   489  
   490  	// Add anything new to the cache, and process it if we're still listening.
   491  	add := func(root gopathwalk.Root, dir string) {
   492  		r.cacheStore(r.scanDirForPackage(root, dir))
   493  	}
   494  
   495  	// r.roots and the callback are not necessarily safe to use in the
   496  	// goroutine below. Process them eagerly.
   497  	roots := filterRoots(r.roots, callback.rootFound)
   498  	// We can't cancel walks, because we need them to finish to have a usable
   499  	// cache. Instead, run them in a separate goroutine and detach.
   500  	scanDone := make(chan struct{})
   501  	go func() {
   502  		select {
   503  		case <-ctx.Done():
   504  			return
   505  		case <-r.scanSema:
   506  		}
   507  		defer func() { r.scanSema <- struct{}{} }()
   508  		// We have the lock on r.scannedRoots, and no other scans can run.
   509  		for _, root := range roots {
   510  			if ctx.Err() != nil {
   511  				return
   512  			}
   513  
   514  			if r.scannedRoots[root] {
   515  				continue
   516  			}
   517  			gopathwalk.WalkSkip([]gopathwalk.Root{root}, add, skip, gopathwalk.Options{Logf: r.env.Logf, ModulesEnabled: true})
   518  			r.scannedRoots[root] = true
   519  		}
   520  		close(scanDone)
   521  	}()
   522  	select {
   523  	case <-ctx.Done():
   524  	case <-scanDone:
   525  	}
   526  	return nil
   527  }
   528  
   529  func (r *ModuleResolver) scoreImportPath(ctx context.Context, path string) float64 {
   530  	if _, ok := stdlib[path]; ok {
   531  		return MaxRelevance
   532  	}
   533  	mod, _ := r.findPackage(path)
   534  	return modRelevance(mod)
   535  }
   536  
   537  func modRelevance(mod *gocommand.ModuleJSON) float64 {
   538  	var relevance float64
   539  	switch {
   540  	case mod == nil: // out of scope
   541  		return MaxRelevance - 4
   542  	case mod.Indirect:
   543  		relevance = MaxRelevance - 3
   544  	case !mod.Main:
   545  		relevance = MaxRelevance - 2
   546  	default:
   547  		relevance = MaxRelevance - 1 // main module ties with stdlib
   548  	}
   549  
   550  	_, versionString, ok := module.SplitPathVersion(mod.Path)
   551  	if ok {
   552  		index := strings.Index(versionString, "v")
   553  		if index == -1 {
   554  			return relevance
   555  		}
   556  		if versionNumber, err := strconv.ParseFloat(versionString[index+1:], 64); err == nil {
   557  			relevance += versionNumber / 1000
   558  		}
   559  	}
   560  
   561  	return relevance
   562  }
   563  
   564  // canonicalize gets the result of canonicalizing the packages using the results
   565  // of initializing the resolver from 'go list -m'.
   566  func (r *ModuleResolver) canonicalize(info directoryPackageInfo) (*pkg, error) {
   567  	// Packages in GOROOT are already canonical, regardless of the std/cmd modules.
   568  	if info.rootType == gopathwalk.RootGOROOT {
   569  		return &pkg{
   570  			importPathShort: info.nonCanonicalImportPath,
   571  			dir:             info.dir,
   572  			packageName:     path.Base(info.nonCanonicalImportPath),
   573  			relevance:       MaxRelevance,
   574  		}, nil
   575  	}
   576  
   577  	importPath := info.nonCanonicalImportPath
   578  	mod := r.findModuleByDir(info.dir)
   579  	// Check if the directory is underneath a module that's in scope.
   580  	if mod != nil {
   581  		// It is. If dir is the target of a replace directive,
   582  		// our guessed import path is wrong. Use the real one.
   583  		if mod.Dir == info.dir {
   584  			importPath = mod.Path
   585  		} else {
   586  			dirInMod := info.dir[len(mod.Dir)+len("/"):]
   587  			importPath = path.Join(mod.Path, filepath.ToSlash(dirInMod))
   588  		}
   589  	} else if !strings.HasPrefix(importPath, info.moduleName) {
   590  		// The module's name doesn't match the package's import path. It
   591  		// probably needs a replace directive we don't have.
   592  		return nil, fmt.Errorf("package in %q is not valid without a replace statement", info.dir)
   593  	}
   594  
   595  	res := &pkg{
   596  		importPathShort: importPath,
   597  		dir:             info.dir,
   598  		relevance:       modRelevance(mod),
   599  	}
   600  	// We may have discovered a package that has a different version
   601  	// in scope already. Canonicalize to that one if possible.
   602  	if _, canonicalDir := r.findPackage(importPath); canonicalDir != "" {
   603  		res.dir = canonicalDir
   604  	}
   605  	return res, nil
   606  }
   607  
   608  func (r *ModuleResolver) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error) {
   609  	if err := r.init(); err != nil {
   610  		return "", nil, err
   611  	}
   612  	if info, ok := r.cacheLoad(pkg.dir); ok && !includeTest {
   613  		return r.cacheExports(ctx, r.env, info)
   614  	}
   615  	return loadExportsFromFiles(ctx, r.env, pkg.dir, includeTest)
   616  }
   617  
   618  func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) directoryPackageInfo {
   619  	subdir := ""
   620  	if dir != root.Path {
   621  		subdir = dir[len(root.Path)+len("/"):]
   622  	}
   623  	importPath := filepath.ToSlash(subdir)
   624  	if strings.HasPrefix(importPath, "vendor/") {
   625  		// Only enter vendor directories if they're explicitly requested as a root.
   626  		return directoryPackageInfo{
   627  			status: directoryScanned,
   628  			err:    fmt.Errorf("unwanted vendor directory"),
   629  		}
   630  	}
   631  	switch root.Type {
   632  	case gopathwalk.RootCurrentModule:
   633  		importPath = path.Join(r.mainByDir[root.Path].Path, filepath.ToSlash(subdir))
   634  	case gopathwalk.RootModuleCache:
   635  		matches := modCacheRegexp.FindStringSubmatch(subdir)
   636  		if len(matches) == 0 {
   637  			return directoryPackageInfo{
   638  				status: directoryScanned,
   639  				err:    fmt.Errorf("invalid module cache path: %v", subdir),
   640  			}
   641  		}
   642  		modPath, err := module.UnescapePath(filepath.ToSlash(matches[1]))
   643  		if err != nil {
   644  			if r.env.Logf != nil {
   645  				r.env.Logf("decoding module cache path %q: %v", subdir, err)
   646  			}
   647  			return directoryPackageInfo{
   648  				status: directoryScanned,
   649  				err:    fmt.Errorf("decoding module cache path %q: %v", subdir, err),
   650  			}
   651  		}
   652  		importPath = path.Join(modPath, filepath.ToSlash(matches[3]))
   653  	}
   654  
   655  	modDir, modName := r.modInfo(dir)
   656  	result := directoryPackageInfo{
   657  		status:                 directoryScanned,
   658  		dir:                    dir,
   659  		rootType:               root.Type,
   660  		nonCanonicalImportPath: importPath,
   661  		moduleDir:              modDir,
   662  		moduleName:             modName,
   663  	}
   664  	if root.Type == gopathwalk.RootGOROOT {
   665  		// stdlib packages are always in scope, despite the confusing go.mod
   666  		return result
   667  	}
   668  	return result
   669  }
   670  
   671  // modCacheRegexp splits a path in a module cache into module, module version, and package.
   672  var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`)
   673  
   674  var (
   675  	slashSlash = []byte("//")
   676  	moduleStr  = []byte("module")
   677  )
   678  
   679  // modulePath returns the module path from the gomod file text.
   680  // If it cannot find a module path, it returns an empty string.
   681  // It is tolerant of unrelated problems in the go.mod file.
   682  //
   683  // Copied from cmd/go/internal/modfile.
   684  func modulePath(mod []byte) string {
   685  	for len(mod) > 0 {
   686  		line := mod
   687  		mod = nil
   688  		if i := bytes.IndexByte(line, '\n'); i >= 0 {
   689  			line, mod = line[:i], line[i+1:]
   690  		}
   691  		if i := bytes.Index(line, slashSlash); i >= 0 {
   692  			line = line[:i]
   693  		}
   694  		line = bytes.TrimSpace(line)
   695  		if !bytes.HasPrefix(line, moduleStr) {
   696  			continue
   697  		}
   698  		line = line[len(moduleStr):]
   699  		n := len(line)
   700  		line = bytes.TrimSpace(line)
   701  		if len(line) == n || len(line) == 0 {
   702  			continue
   703  		}
   704  
   705  		if line[0] == '"' || line[0] == '`' {
   706  			p, err := strconv.Unquote(string(line))
   707  			if err != nil {
   708  				return "" // malformed quoted string or multiline module path
   709  			}
   710  			return p
   711  		}
   712  
   713  		return string(line)
   714  	}
   715  	return "" // missing module path
   716  }