Source file src/cmd/go/internal/search/search.go

     1  // Copyright 2017 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 search
     6  
     7  import (
     8  	"cmd/go/internal/base"
     9  	"cmd/go/internal/cfg"
    10  	"cmd/go/internal/fsys"
    11  	"cmd/go/internal/imports"
    12  	"cmd/go/internal/modindex"
    13  	"cmd/go/internal/str"
    14  	"cmd/internal/pkgpattern"
    15  	"errors"
    16  	"fmt"
    17  	"go/build"
    18  	"io/fs"
    19  	"os"
    20  	"path"
    21  	"path/filepath"
    22  	"strings"
    23  
    24  	"golang.org/x/mod/modfile"
    25  )
    26  
    27  // A Match represents the result of matching a single package pattern.
    28  type Match struct {
    29  	pattern string   // the pattern itself
    30  	Dirs    []string // if the pattern is local, directories that potentially contain matching packages
    31  	Pkgs    []string // matching packages (import paths)
    32  	Errs    []error  // errors matching the patterns to packages, NOT errors loading those packages
    33  
    34  	// Errs may be non-empty even if len(Pkgs) > 0, indicating that some matching
    35  	// packages could be located but results may be incomplete.
    36  	// If len(Pkgs) == 0 && len(Errs) == 0, the pattern is well-formed but did not
    37  	// match any packages.
    38  }
    39  
    40  // NewMatch returns a Match describing the given pattern,
    41  // without resolving its packages or errors.
    42  func NewMatch(pattern string) *Match {
    43  	return &Match{pattern: pattern}
    44  }
    45  
    46  // Pattern returns the pattern to be matched.
    47  func (m *Match) Pattern() string { return m.pattern }
    48  
    49  // AddError appends a MatchError wrapping err to m.Errs.
    50  func (m *Match) AddError(err error) {
    51  	m.Errs = append(m.Errs, &MatchError{Match: m, Err: err})
    52  }
    53  
    54  // IsLiteral reports whether the pattern is free of wildcards and meta-patterns.
    55  //
    56  // A literal pattern must match at most one package.
    57  func (m *Match) IsLiteral() bool {
    58  	return !strings.Contains(m.pattern, "...") && !m.IsMeta()
    59  }
    60  
    61  // IsLocal reports whether the pattern must be resolved from a specific root or
    62  // directory, such as a filesystem path or a single module.
    63  func (m *Match) IsLocal() bool {
    64  	return build.IsLocalImport(m.pattern) || filepath.IsAbs(m.pattern)
    65  }
    66  
    67  // IsMeta reports whether the pattern is a “meta-package” keyword that represents
    68  // multiple packages, such as "std", "cmd", "tool", "work", or "all".
    69  func (m *Match) IsMeta() bool {
    70  	return IsMetaPackage(m.pattern)
    71  }
    72  
    73  // IsMetaPackage checks if name is a reserved package name that expands to multiple packages.
    74  func IsMetaPackage(name string) bool {
    75  	return name == "std" || name == "cmd" || name == "tool" || name == "work" || name == "all"
    76  }
    77  
    78  // A MatchError indicates an error that occurred while attempting to match a
    79  // pattern.
    80  type MatchError struct {
    81  	Match *Match
    82  	Err   error
    83  }
    84  
    85  func (e *MatchError) Error() string {
    86  	if e.Match.IsLiteral() {
    87  		return fmt.Sprintf("%s: %v", e.Match.Pattern(), e.Err)
    88  	}
    89  	return fmt.Sprintf("pattern %s: %v", e.Match.Pattern(), e.Err)
    90  }
    91  
    92  func (e *MatchError) Unwrap() error {
    93  	return e.Err
    94  }
    95  
    96  // MatchPackages sets m.Pkgs to a non-nil slice containing all the packages that
    97  // can be found under the $GOPATH directories and $GOROOT that match the
    98  // pattern. The pattern must be either "all" (all packages), "std" (standard
    99  // packages), "cmd" (standard commands), or a path including "...".
   100  //
   101  // If any errors may have caused the set of packages to be incomplete,
   102  // MatchPackages appends those errors to m.Errs.
   103  func (m *Match) MatchPackages() {
   104  	m.Pkgs = []string{}
   105  	if m.IsLocal() {
   106  		m.AddError(fmt.Errorf("internal error: MatchPackages: %s is not a valid package pattern", m.pattern))
   107  		return
   108  	}
   109  
   110  	if m.IsLiteral() {
   111  		m.Pkgs = []string{m.pattern}
   112  		return
   113  	}
   114  
   115  	match := func(string) bool { return true }
   116  	treeCanMatch := func(string) bool { return true }
   117  	if !m.IsMeta() {
   118  		match = pkgpattern.MatchPattern(m.pattern)
   119  		treeCanMatch = pkgpattern.TreeCanMatchPattern(m.pattern)
   120  	}
   121  
   122  	have := map[string]bool{
   123  		"builtin": true, // ignore pseudo-package that exists only for documentation
   124  	}
   125  	if !cfg.BuildContext.CgoEnabled {
   126  		have["runtime/cgo"] = true // ignore during walk
   127  	}
   128  
   129  	for _, src := range cfg.BuildContext.SrcDirs() {
   130  		if (m.pattern == "std" || m.pattern == "cmd") && src != cfg.GOROOTsrc {
   131  			continue
   132  		}
   133  
   134  		// If the root itself is a symlink to a directory,
   135  		// we want to follow it (see https://go.dev/issue/50807).
   136  		// Add a trailing separator to force that to happen.
   137  		src = str.WithFilePathSeparator(filepath.Clean(src))
   138  		root := src
   139  		if m.pattern == "cmd" {
   140  			root += "cmd" + string(filepath.Separator)
   141  		}
   142  
   143  		err := fsys.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
   144  			if err != nil {
   145  				return err // Likely a permission error, which could interfere with matching.
   146  			}
   147  			if path == src {
   148  				return nil // GOROOT/src and GOPATH/src cannot contain packages.
   149  			}
   150  
   151  			want := true
   152  			// Avoid .foo, _foo, and testdata directory trees.
   153  			_, elem := filepath.Split(path)
   154  			if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
   155  				want = false
   156  			}
   157  
   158  			name := filepath.ToSlash(path[len(src):])
   159  			if m.pattern == "std" && (!IsStandardImportPath(name) || name == "cmd") {
   160  				// The name "std" is only the standard library.
   161  				// If the name is cmd, it's the root of the command tree.
   162  				want = false
   163  			}
   164  			if !treeCanMatch(name) {
   165  				want = false
   166  			}
   167  
   168  			if !d.IsDir() {
   169  				if d.Type()&fs.ModeSymlink != 0 && want && strings.Contains(m.pattern, "...") {
   170  					if target, err := fsys.Stat(path); err == nil && target.IsDir() {
   171  						fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
   172  					}
   173  				}
   174  				return nil
   175  			}
   176  			if !want {
   177  				return filepath.SkipDir
   178  			}
   179  
   180  			if have[name] {
   181  				return nil
   182  			}
   183  			have[name] = true
   184  			if !match(name) {
   185  				return nil
   186  			}
   187  			pkg, err := cfg.BuildContext.ImportDir(path, 0)
   188  			if err != nil {
   189  				if _, noGo := err.(*build.NoGoError); noGo {
   190  					// The package does not actually exist, so record neither the package
   191  					// nor the error.
   192  					return nil
   193  				}
   194  				// There was an error importing path, but not matching it,
   195  				// which is all that Match promises to do.
   196  				// Ignore the import error.
   197  			}
   198  
   199  			// If we are expanding "cmd", skip main
   200  			// packages under cmd/vendor. At least as of
   201  			// March, 2017, there is one there for the
   202  			// vendored pprof tool.
   203  			if m.pattern == "cmd" && pkg != nil && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" {
   204  				return nil
   205  			}
   206  
   207  			m.Pkgs = append(m.Pkgs, name)
   208  			return nil
   209  		})
   210  		if err != nil {
   211  			m.AddError(err)
   212  		}
   213  	}
   214  }
   215  
   216  // IgnorePatterns is normalized with normalizePath.
   217  type IgnorePatterns struct {
   218  	relativePatterns []string
   219  	anyPatterns      []string
   220  }
   221  
   222  // ShouldIgnore returns true if the given directory should be ignored
   223  // based on the ignore patterns.
   224  //
   225  // An ignore pattern "x" will cause any file or directory named "x"
   226  // (and its entire subtree) to be ignored, regardless of its location
   227  // within the module.
   228  //
   229  // An ignore pattern "./x" will only cause the specific file or directory
   230  // named "x" at the root of the module to be ignored.
   231  // Wildcards in ignore patterns are not supported.
   232  func (ignorePatterns *IgnorePatterns) ShouldIgnore(dir string) bool {
   233  	if dir == "" {
   234  		return false
   235  	}
   236  	dir = normalizePath(dir)
   237  	for _, pattern := range ignorePatterns.relativePatterns {
   238  		if strings.HasPrefix(dir, pattern) {
   239  			return true
   240  		}
   241  	}
   242  	for _, pattern := range ignorePatterns.anyPatterns {
   243  		if strings.Contains(dir, pattern) {
   244  			return true
   245  		}
   246  	}
   247  	return false
   248  }
   249  
   250  func NewIgnorePatterns(patterns []string) *IgnorePatterns {
   251  	var relativePatterns, anyPatterns []string
   252  	for _, pattern := range patterns {
   253  		ignorePatternPath, isRelative := strings.CutPrefix(pattern, "./")
   254  		ignorePatternPath = normalizePath(ignorePatternPath)
   255  		if isRelative {
   256  			relativePatterns = append(relativePatterns, ignorePatternPath)
   257  		} else {
   258  			anyPatterns = append(anyPatterns, ignorePatternPath)
   259  		}
   260  	}
   261  	return &IgnorePatterns{
   262  		relativePatterns: relativePatterns,
   263  		anyPatterns:      anyPatterns,
   264  	}
   265  }
   266  
   267  // normalizePath adds slashes to the front and end of the given path.
   268  func normalizePath(path string) string {
   269  	path = filepath.ToSlash(path)
   270  	if !strings.HasPrefix(path, "/") {
   271  		path = "/" + path
   272  	}
   273  	if !strings.HasSuffix(path, "/") {
   274  		path += "/"
   275  	}
   276  	return path
   277  }
   278  
   279  // MatchDirs sets m.Dirs to a non-nil slice containing all directories that
   280  // potentially match a local pattern. The pattern must begin with an absolute
   281  // path, or "./", or "../". On Windows, the pattern may use slash or backslash
   282  // separators or a mix of both.
   283  //
   284  // If any errors may have caused the set of directories to be incomplete,
   285  // MatchDirs appends those errors to m.Errs.
   286  func (m *Match) MatchDirs(modRoots []string) {
   287  	m.Dirs = []string{}
   288  	if !m.IsLocal() {
   289  		m.AddError(fmt.Errorf("internal error: MatchDirs: %s is not a valid filesystem pattern", m.pattern))
   290  		return
   291  	}
   292  
   293  	if m.IsLiteral() {
   294  		m.Dirs = []string{m.pattern}
   295  		return
   296  	}
   297  
   298  	// Clean the path and create a matching predicate.
   299  	// filepath.Clean removes "./" prefixes (and ".\" on Windows). We need to
   300  	// preserve these, since they are meaningful in MatchPattern and in
   301  	// returned import paths.
   302  	cleanPattern := filepath.Clean(m.pattern)
   303  	isLocal := strings.HasPrefix(m.pattern, "./") || (os.PathSeparator == '\\' && strings.HasPrefix(m.pattern, `.\`))
   304  	prefix := ""
   305  	if cleanPattern != "." && isLocal {
   306  		prefix = "./"
   307  		cleanPattern = "." + string(os.PathSeparator) + cleanPattern
   308  	}
   309  	slashPattern := filepath.ToSlash(cleanPattern)
   310  	match := pkgpattern.MatchPattern(slashPattern)
   311  
   312  	// Find directory to begin the scan.
   313  	// Could be smarter but this one optimization
   314  	// is enough for now, since ... is usually at the
   315  	// end of a path.
   316  	i := strings.Index(cleanPattern, "...")
   317  	dir, _ := filepath.Split(cleanPattern[:i])
   318  
   319  	// pattern begins with ./ or ../.
   320  	// path.Clean will discard the ./ but not the ../.
   321  	// We need to preserve the ./ for pattern matching
   322  	// and in the returned import paths.
   323  
   324  	var modRoot string
   325  	if len(modRoots) > 0 {
   326  		abs, err := filepath.Abs(dir)
   327  		if err != nil {
   328  			m.AddError(err)
   329  			return
   330  		}
   331  		var found bool
   332  		for _, mr := range modRoots {
   333  			if mr != "" && str.HasFilePathPrefix(abs, mr) {
   334  				found = true
   335  				modRoot = mr
   336  			}
   337  		}
   338  		if !found {
   339  			plural := ""
   340  			if len(modRoots) > 1 {
   341  				plural = "s"
   342  			}
   343  			m.AddError(fmt.Errorf("directory %s is outside module root%s (%s)", abs, plural, strings.Join(modRoots, ", ")))
   344  		}
   345  	}
   346  
   347  	ignorePatterns := parseIgnorePatterns(modRoot)
   348  	tags := imports.Tags()
   349  	// If dir is actually a symlink to a directory,
   350  	// we want to follow it (see https://go.dev/issue/50807).
   351  	// Add a trailing separator to force that to happen.
   352  	dir = str.WithFilePathSeparator(dir)
   353  	err := fsys.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
   354  		if err != nil {
   355  			return err // Likely a permission error, which could interfere with matching.
   356  		}
   357  		if !d.IsDir() {
   358  			return nil
   359  		}
   360  		top := false
   361  		if path == dir {
   362  			// Walk starts at dir and recurses. For the recursive case,
   363  			// the path is the result of filepath.Join, which calls filepath.Clean.
   364  			// The initial case is not Cleaned, though, so we do this explicitly.
   365  			//
   366  			// This converts a path like "./io/" to "io". Without this step, running
   367  			// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
   368  			// package, because prepending the prefix "./" to the unclean path would
   369  			// result in "././io", and match("././io") returns false.
   370  			top = true
   371  			path = filepath.Clean(path)
   372  		}
   373  
   374  		// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
   375  		_, elem := filepath.Split(path)
   376  		dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
   377  		if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
   378  			return filepath.SkipDir
   379  		}
   380  		absPath, err := filepath.Abs(path)
   381  		if err != nil {
   382  			return err
   383  		}
   384  
   385  		if ignorePatterns != nil && ignorePatterns.ShouldIgnore(InDir(absPath, modRoot)) {
   386  			if cfg.BuildX {
   387  				fmt.Fprintf(os.Stderr, "# ignoring directory %s\n", absPath)
   388  			}
   389  			return filepath.SkipDir
   390  		}
   391  
   392  		if !top && cfg.ModulesEnabled {
   393  			// Ignore other modules found in subdirectories.
   394  			if info, err := fsys.Stat(filepath.Join(path, "go.mod")); err == nil && !info.IsDir() {
   395  				return filepath.SkipDir
   396  			}
   397  		}
   398  
   399  		name := prefix + filepath.ToSlash(path)
   400  		if !match(name) {
   401  			return nil
   402  		}
   403  
   404  		if modRoot := moduleRootContaining(modRoots, absPath); modRoot != "" {
   405  			hasPackage, ok, err := indexedPackage(modRoot, absPath, tags)
   406  			if err != nil {
   407  				return err
   408  			}
   409  			if ok {
   410  				if hasPackage {
   411  					m.Dirs = append(m.Dirs, name)
   412  				}
   413  				return nil
   414  			}
   415  		}
   416  
   417  		// We keep the directory if we can import it, or if we can't import it
   418  		// due to invalid Go source files. This means that directories containing
   419  		// parse errors will be built (and fail) instead of being silently skipped
   420  		// as not matching the pattern. Go 1.5 and earlier skipped, but that
   421  		// behavior means people miss serious mistakes.
   422  		// See golang.org/issue/11407.
   423  		if p, err := cfg.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) {
   424  			if _, noGo := err.(*build.NoGoError); noGo {
   425  				// The package does not actually exist, so record neither the package
   426  				// nor the error.
   427  				return nil
   428  			}
   429  			// There was an error importing path, but not matching it,
   430  			// which is all that Match promises to do.
   431  			// Ignore the import error.
   432  		}
   433  		m.Dirs = append(m.Dirs, name)
   434  		return nil
   435  	})
   436  	if err != nil {
   437  		m.AddError(err)
   438  	}
   439  }
   440  
   441  func moduleRootContaining(modRoots []string, absPath string) string {
   442  	var modRoot string
   443  	for _, root := range modRoots {
   444  		if root != "" && str.HasFilePathPrefix(absPath, root) && len(root) > len(modRoot) {
   445  			modRoot = root
   446  		}
   447  	}
   448  	return modRoot
   449  }
   450  
   451  func indexedPackage(modRoot, absPath string, tags map[string]bool) (hasPackage, ok bool, err error) {
   452  	ip, err := modindex.GetPackage(modRoot, absPath)
   453  	if errors.Is(err, modindex.ErrNotIndexed) {
   454  		return false, false, nil
   455  	}
   456  	if err != nil {
   457  		return false, true, err
   458  	}
   459  	_, _, err = ip.ScanDir(tags)
   460  	return err != imports.ErrNoGo, true, nil
   461  }
   462  
   463  // WarnUnmatched warns about patterns that didn't match any packages.
   464  func WarnUnmatched(matches []*Match) {
   465  	for _, m := range matches {
   466  		if len(m.Pkgs) == 0 && len(m.Errs) == 0 {
   467  			fmt.Fprintf(os.Stderr, "go: warning: %q matched no packages\n", m.pattern)
   468  		}
   469  	}
   470  }
   471  
   472  // ImportPaths returns the matching paths to use for the given command line.
   473  // It calls ImportPathsQuiet and then WarnUnmatched.
   474  func ImportPaths(patterns []string) []*Match {
   475  	matches := ImportPathsQuiet(patterns)
   476  	WarnUnmatched(matches)
   477  	return matches
   478  }
   479  
   480  // ImportPathsQuiet is like ImportPaths but does not warn about patterns with no matches.
   481  func ImportPathsQuiet(patterns []string) []*Match {
   482  	patterns = CleanPatterns(patterns)
   483  	out := make([]*Match, 0, len(patterns))
   484  	for _, a := range patterns {
   485  		m := NewMatch(a)
   486  		if m.IsLocal() {
   487  			m.MatchDirs(nil)
   488  
   489  			// Change the file import path to a regular import path if the package
   490  			// is in GOPATH or GOROOT. We don't report errors here; LoadImport
   491  			// (or something similar) will report them later.
   492  			m.Pkgs = make([]string, len(m.Dirs))
   493  			for i, dir := range m.Dirs {
   494  				absDir := dir
   495  				if !filepath.IsAbs(dir) {
   496  					absDir = filepath.Join(base.Cwd(), dir)
   497  				}
   498  				if bp, _ := cfg.BuildContext.ImportDir(absDir, build.FindOnly); bp.ImportPath != "" && bp.ImportPath != "." {
   499  					m.Pkgs[i] = bp.ImportPath
   500  				} else {
   501  					m.Pkgs[i] = dir
   502  				}
   503  			}
   504  		} else {
   505  			m.MatchPackages()
   506  		}
   507  
   508  		out = append(out, m)
   509  	}
   510  	return out
   511  }
   512  
   513  // CleanPatterns returns the patterns to use for the given command line. It
   514  // canonicalizes the patterns but does not evaluate any matches. For patterns
   515  // that are not local or absolute paths, it preserves text after '@' to avoid
   516  // modifying version queries.
   517  func CleanPatterns(patterns []string) []string {
   518  	if len(patterns) == 0 {
   519  		return []string{"."}
   520  	}
   521  	out := make([]string, 0, len(patterns))
   522  	for _, a := range patterns {
   523  		var p, v string
   524  		if build.IsLocalImport(a) || filepath.IsAbs(a) {
   525  			p = a
   526  		} else if i := strings.IndexByte(a, '@'); i < 0 {
   527  			p = a
   528  		} else {
   529  			p = a[:i]
   530  			v = a[i:]
   531  		}
   532  
   533  		// Arguments may be either file paths or import paths.
   534  		// As a courtesy to Windows developers, rewrite \ to /
   535  		// in arguments that look like import paths.
   536  		// Don't replace slashes in absolute paths.
   537  		if filepath.IsAbs(p) {
   538  			p = filepath.Clean(p)
   539  		} else {
   540  			p = strings.ReplaceAll(p, `\`, `/`)
   541  
   542  			// Put argument in canonical form, but preserve leading ./.
   543  			if strings.HasPrefix(p, "./") {
   544  				p = "./" + path.Clean(p)
   545  				if p == "./." {
   546  					p = "."
   547  				}
   548  			} else {
   549  				p = path.Clean(p)
   550  			}
   551  		}
   552  
   553  		out = append(out, p+v)
   554  	}
   555  	return out
   556  }
   557  
   558  // IsStandardImportPath reports whether $GOROOT/src/path should be considered
   559  // part of the standard distribution. For historical reasons we allow people to add
   560  // their own code to $GOROOT instead of using $GOPATH, but we assume that
   561  // code will start with a domain name (dot in the first element).
   562  //
   563  // Note that this function is meant to evaluate whether a directory found in GOROOT
   564  // should be treated as part of the standard library. It should not be used to decide
   565  // that a directory found in GOPATH should be rejected: directories in GOPATH
   566  // need not have dots in the first element, and they just take their chances
   567  // with future collisions in the standard library.
   568  func IsStandardImportPath(path string) bool {
   569  	i := strings.Index(path, "/")
   570  	if i < 0 {
   571  		i = len(path)
   572  	}
   573  	elem := path[:i]
   574  	return !strings.Contains(elem, ".")
   575  }
   576  
   577  // IsRelativePath reports whether pattern should be interpreted as a directory
   578  // path relative to the current directory, as opposed to a pattern matching
   579  // import paths.
   580  func IsRelativePath(pattern string) bool {
   581  	return strings.HasPrefix(pattern, "./") || strings.HasPrefix(pattern, "../") || pattern == "." || pattern == ".."
   582  }
   583  
   584  // InDir checks whether path is in the file tree rooted at dir.
   585  // If so, InDir returns an equivalent path relative to dir.
   586  // If not, InDir returns an empty string.
   587  // InDir makes some effort to succeed even in the presence of symbolic links.
   588  func InDir(path, dir string) string {
   589  	// inDirLex reports whether path is lexically in dir,
   590  	// without considering symbolic or hard links.
   591  	inDirLex := func(path, dir string) (string, bool) {
   592  		if dir == "" {
   593  			return path, true
   594  		}
   595  		rel := str.TrimFilePathPrefix(path, dir)
   596  		if rel == path {
   597  			return "", false
   598  		}
   599  		if rel == "" {
   600  			return ".", true
   601  		}
   602  		return rel, true
   603  	}
   604  
   605  	if rel, ok := inDirLex(path, dir); ok {
   606  		return rel
   607  	}
   608  	xpath, err := filepath.EvalSymlinks(path)
   609  	if err != nil || xpath == path {
   610  		xpath = ""
   611  	} else {
   612  		if rel, ok := inDirLex(xpath, dir); ok {
   613  			return rel
   614  		}
   615  	}
   616  
   617  	xdir, err := filepath.EvalSymlinks(dir)
   618  	if err == nil && xdir != dir {
   619  		if rel, ok := inDirLex(path, xdir); ok {
   620  			return rel
   621  		}
   622  		if xpath != "" {
   623  			if rel, ok := inDirLex(xpath, xdir); ok {
   624  				return rel
   625  			}
   626  		}
   627  	}
   628  	return ""
   629  }
   630  
   631  // parseIgnorePatterns reads the go.mod file at the given module root
   632  // and extracts the ignore patterns defined within it.
   633  // If modRoot is empty, it returns nil.
   634  func parseIgnorePatterns(modRoot string) *IgnorePatterns {
   635  	if modRoot == "" {
   636  		return nil
   637  	}
   638  	data, err := os.ReadFile(filepath.Join(modRoot, "go.mod"))
   639  	if err != nil {
   640  		return nil
   641  	}
   642  	modFile, err := modfile.Parse("go.mod", data, nil)
   643  	if err != nil {
   644  		return nil
   645  	}
   646  	var patterns []string
   647  	for _, i := range modFile.Ignore {
   648  		patterns = append(patterns, i.Path)
   649  	}
   650  	return NewIgnorePatterns(patterns)
   651  }
   652  

View as plain text