github.com/serversong/goreporter@v0.0.0-20200325104552-3cfaf44fd178/linters/simplecode/gotool/match.go (about)

     1  // Copyright (c) 2009 The Go Authors. All rights reserved.
     2  //
     3  // Redistribution and use in source and binary forms, with or without
     4  // modification, are permitted provided that the following conditions are
     5  // met:
     6  //
     7  //    * Redistributions of source code must retain the above copyright
     8  // notice, this list of conditions and the following disclaimer.
     9  //    * Redistributions in binary form must reproduce the above
    10  // copyright notice, this list of conditions and the following disclaimer
    11  // in the documentation and/or other materials provided with the
    12  // distribution.
    13  //    * Neither the name of Google Inc. nor the names of its
    14  // contributors may be used to endorse or promote products derived from
    15  // this software without specific prior written permission.
    16  //
    17  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    18  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    19  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    20  // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    21  // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    22  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    23  // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    24  // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    25  // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    26  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    27  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    28  
    29  package gotool
    30  
    31  import (
    32  	"fmt"
    33  	"go/build"
    34  	"log"
    35  	"os"
    36  	"path"
    37  	"path/filepath"
    38  	"regexp"
    39  	"strings"
    40  )
    41  
    42  // This file contains code from the Go distribution.
    43  
    44  // matchPattern(pattern)(name) reports whether
    45  // name matches pattern. Pattern is a limited glob
    46  // pattern in which '...' means 'any string' and there
    47  // is no other special syntax.
    48  func matchPattern(pattern string) func(name string) bool {
    49  	re := regexp.QuoteMeta(pattern)
    50  	re = strings.Replace(re, `\.\.\.`, `.*`, -1)
    51  	// Special case: foo/... matches foo too.
    52  	if strings.HasSuffix(re, `/.*`) {
    53  		re = re[:len(re)-len(`/.*`)] + `(/.*)?`
    54  	}
    55  	reg := regexp.MustCompile(`^` + re + `$`)
    56  	return func(name string) bool {
    57  		return reg.MatchString(name)
    58  	}
    59  }
    60  
    61  func (c *Context) matchPackages(pattern string) []string {
    62  	match := func(string) bool { return true }
    63  	treeCanMatch := func(string) bool { return true }
    64  	if !isMetaPackage(pattern) {
    65  		match = matchPattern(pattern)
    66  		treeCanMatch = treeCanMatchPattern(pattern)
    67  	}
    68  
    69  	have := map[string]bool{
    70  		"builtin": true, // ignore pseudo-package that exists only for documentation
    71  	}
    72  	if !c.BuildContext.CgoEnabled {
    73  		have["runtime/cgo"] = true // ignore during walk
    74  	}
    75  	var pkgs []string
    76  
    77  	for _, src := range c.BuildContext.SrcDirs() {
    78  		if (pattern == "std" || pattern == "cmd") && src != gorootSrc {
    79  			continue
    80  		}
    81  		src = filepath.Clean(src) + string(filepath.Separator)
    82  		root := src
    83  		if pattern == "cmd" {
    84  			root += "cmd" + string(filepath.Separator)
    85  		}
    86  		filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
    87  			if err != nil || !fi.IsDir() || path == src {
    88  				return nil
    89  			}
    90  
    91  			// Avoid .foo, _foo, and testdata directory trees.
    92  			_, elem := filepath.Split(path)
    93  			if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
    94  				return filepath.SkipDir
    95  			}
    96  
    97  			name := filepath.ToSlash(path[len(src):])
    98  			if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") {
    99  				// The name "std" is only the standard library.
   100  				// If the name is cmd, it's the root of the command tree.
   101  				return filepath.SkipDir
   102  			}
   103  			if !treeCanMatch(name) {
   104  				return filepath.SkipDir
   105  			}
   106  			if have[name] {
   107  				return nil
   108  			}
   109  			have[name] = true
   110  			if !match(name) {
   111  				return nil
   112  			}
   113  			_, err = c.BuildContext.ImportDir(path, 0)
   114  			if err != nil {
   115  				if _, noGo := err.(*build.NoGoError); noGo {
   116  					return nil
   117  				}
   118  			}
   119  			pkgs = append(pkgs, name)
   120  			return nil
   121  		})
   122  	}
   123  	return pkgs
   124  }
   125  
   126  // importPathsNoDotExpansion returns the import paths to use for the given
   127  // command line, but it does no ... expansion.
   128  func (c *Context) importPathsNoDotExpansion(args []string) []string {
   129  	if len(args) == 0 {
   130  		return []string{"."}
   131  	}
   132  	var out []string
   133  	for _, a := range args {
   134  		// Arguments are supposed to be import paths, but
   135  		// as a courtesy to Windows developers, rewrite \ to /
   136  		// in command-line arguments. Handles .\... and so on.
   137  		if filepath.Separator == '\\' {
   138  			a = strings.Replace(a, `\`, `/`, -1)
   139  		}
   140  
   141  		// Put argument in canonical form, but preserve leading ./.
   142  		if strings.HasPrefix(a, "./") {
   143  			a = "./" + path.Clean(a)
   144  			if a == "./." {
   145  				a = "."
   146  			}
   147  		} else {
   148  			a = path.Clean(a)
   149  		}
   150  		if isMetaPackage(a) {
   151  			out = append(out, c.allPackages(a)...)
   152  			continue
   153  		}
   154  		out = append(out, a)
   155  	}
   156  	return out
   157  }
   158  
   159  // importPaths returns the import paths to use for the given command line.
   160  func (c *Context) importPaths(args []string) []string {
   161  	args = c.importPathsNoDotExpansion(args)
   162  	var out []string
   163  	for _, a := range args {
   164  		if strings.Contains(a, "...") {
   165  			if build.IsLocalImport(a) {
   166  				out = append(out, c.allPackagesInFS(a)...)
   167  			} else {
   168  				out = append(out, c.allPackages(a)...)
   169  			}
   170  			continue
   171  		}
   172  		out = append(out, a)
   173  	}
   174  	return out
   175  }
   176  
   177  // allPackages returns all the packages that can be found
   178  // under the $GOPATH directories and $GOROOT matching pattern.
   179  // The pattern is either "all" (all packages), "std" (standard packages),
   180  // "cmd" (standard commands), or a path including "...".
   181  func (c *Context) allPackages(pattern string) []string {
   182  	pkgs := c.matchPackages(pattern)
   183  	if len(pkgs) == 0 {
   184  		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
   185  	}
   186  	return pkgs
   187  }
   188  
   189  // allPackagesInFS is like allPackages but is passed a pattern
   190  // beginning ./ or ../, meaning it should scan the tree rooted
   191  // at the given directory. There are ... in the pattern too.
   192  func (c *Context) allPackagesInFS(pattern string) []string {
   193  	pkgs := c.matchPackagesInFS(pattern)
   194  	if len(pkgs) == 0 {
   195  		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
   196  	}
   197  	return pkgs
   198  }
   199  
   200  func (c *Context) matchPackagesInFS(pattern string) []string {
   201  	// Find directory to begin the scan.
   202  	// Could be smarter but this one optimization
   203  	// is enough for now, since ... is usually at the
   204  	// end of a path.
   205  	i := strings.Index(pattern, "...")
   206  	dir, _ := path.Split(pattern[:i])
   207  
   208  	// pattern begins with ./ or ../.
   209  	// path.Clean will discard the ./ but not the ../.
   210  	// We need to preserve the ./ for pattern matching
   211  	// and in the returned import paths.
   212  	prefix := ""
   213  	if strings.HasPrefix(pattern, "./") {
   214  		prefix = "./"
   215  	}
   216  	match := matchPattern(pattern)
   217  
   218  	var pkgs []string
   219  	filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
   220  		if err != nil || !fi.IsDir() {
   221  			return nil
   222  		}
   223  		if path == dir {
   224  			// filepath.Walk starts at dir and recurses. For the recursive case,
   225  			// the path is the result of filepath.Join, which calls filepath.Clean.
   226  			// The initial case is not Cleaned, though, so we do this explicitly.
   227  			//
   228  			// This converts a path like "./io/" to "io". Without this step, running
   229  			// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
   230  			// package, because prepending the prefix "./" to the unclean path would
   231  			// result in "././io", and match("././io") returns false.
   232  			path = filepath.Clean(path)
   233  		}
   234  
   235  		// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
   236  		_, elem := filepath.Split(path)
   237  		dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
   238  		if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
   239  			return filepath.SkipDir
   240  		}
   241  
   242  		name := prefix + filepath.ToSlash(path)
   243  		if !match(name) {
   244  			return nil
   245  		}
   246  
   247  		// We keep the directory if we can import it, or if we can't import it
   248  		// due to invalid Go source files. This means that directories containing
   249  		// parse errors will be built (and fail) instead of being silently skipped
   250  		// as not matching the pattern. Go 1.5 and earlier skipped, but that
   251  		// behavior means people miss serious mistakes.
   252  		// See golang.org/issue/11407.
   253  		if p, err := c.BuildContext.ImportDir(path, 0); err != nil && shouldIgnoreImport(p) {
   254  			if _, noGo := err.(*build.NoGoError); !noGo {
   255  				log.Print(err)
   256  			}
   257  			return nil
   258  		}
   259  		pkgs = append(pkgs, name)
   260  		return nil
   261  	})
   262  	return pkgs
   263  }
   264  
   265  // isMetaPackage checks if name is a reserved package name that expands to multiple packages
   266  func isMetaPackage(name string) bool {
   267  	return name == "std" || name == "cmd" || name == "all"
   268  }
   269  
   270  // isStandardImportPath reports whether $GOROOT/src/path should be considered
   271  // part of the standard distribution. For historical reasons we allow people to add
   272  // their own code to $GOROOT instead of using $GOPATH, but we assume that
   273  // code will start with a domain name (dot in the first element).
   274  func isStandardImportPath(path string) bool {
   275  	i := strings.Index(path, "/")
   276  	if i < 0 {
   277  		i = len(path)
   278  	}
   279  	elem := path[:i]
   280  	return !strings.Contains(elem, ".")
   281  }
   282  
   283  // hasPathPrefix reports whether the path s begins with the
   284  // elements in prefix.
   285  func hasPathPrefix(s, prefix string) bool {
   286  	switch {
   287  	default:
   288  		return false
   289  	case len(s) == len(prefix):
   290  		return s == prefix
   291  	case len(s) > len(prefix):
   292  		if prefix != "" && prefix[len(prefix)-1] == '/' {
   293  			return strings.HasPrefix(s, prefix)
   294  		}
   295  		return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
   296  	}
   297  }
   298  
   299  // treeCanMatchPattern(pattern)(name) reports whether
   300  // name or children of name can possibly match pattern.
   301  // Pattern is the same limited glob accepted by matchPattern.
   302  func treeCanMatchPattern(pattern string) func(name string) bool {
   303  	wildCard := false
   304  	if i := strings.Index(pattern, "..."); i >= 0 {
   305  		wildCard = true
   306  		pattern = pattern[:i]
   307  	}
   308  	return func(name string) bool {
   309  		return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
   310  			wildCard && strings.HasPrefix(name, pattern)
   311  	}
   312  }