github.com/jd-ly/tools@v0.5.7/internal/imports/mod.go (about)

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