gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/github.com/kisielk/gotool/match18.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  // +build !go1.9
    30  
    31  package gotool
    32  
    33  import (
    34  	"fmt"
    35  	"go/build"
    36  	"log"
    37  	"os"
    38  	"path"
    39  	"path/filepath"
    40  	"regexp"
    41  	"strings"
    42  )
    43  
    44  // This file contains code from the Go distribution.
    45  
    46  // matchPattern(pattern)(name) reports whether
    47  // name matches pattern. Pattern is a limited glob
    48  // pattern in which '...' means 'any string' and there
    49  // is no other special syntax.
    50  func matchPattern(pattern string) func(name string) bool {
    51  	re := regexp.QuoteMeta(pattern)
    52  	re = strings.Replace(re, `\.\.\.`, `.*`, -1)
    53  	// Special case: foo/... matches foo too.
    54  	if strings.HasSuffix(re, `/.*`) {
    55  		re = re[:len(re)-len(`/.*`)] + `(/.*)?`
    56  	}
    57  	reg := regexp.MustCompile(`^` + re + `$`)
    58  	return reg.MatchString
    59  }
    60  
    61  // matchPackages returns a list of package paths matching pattern
    62  // (see go help packages for pattern syntax).
    63  func (c *Context) matchPackages(pattern string) []string {
    64  	match := func(string) bool { return true }
    65  	treeCanMatch := func(string) bool { return true }
    66  	if !isMetaPackage(pattern) {
    67  		match = matchPattern(pattern)
    68  		treeCanMatch = treeCanMatchPattern(pattern)
    69  	}
    70  
    71  	have := map[string]bool{
    72  		"builtin": true, // ignore pseudo-package that exists only for documentation
    73  	}
    74  	if !c.BuildContext.CgoEnabled {
    75  		have["runtime/cgo"] = true // ignore during walk
    76  	}
    77  	var pkgs []string
    78  
    79  	for _, src := range c.BuildContext.SrcDirs() {
    80  		if (pattern == "std" || pattern == "cmd") && src != gorootSrc {
    81  			continue
    82  		}
    83  		src = filepath.Clean(src) + string(filepath.Separator)
    84  		root := src
    85  		if pattern == "cmd" {
    86  			root += "cmd" + string(filepath.Separator)
    87  		}
    88  		filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
    89  			if err != nil || !fi.IsDir() || path == src {
    90  				return nil
    91  			}
    92  
    93  			// Avoid .foo, _foo, and testdata directory trees.
    94  			_, elem := filepath.Split(path)
    95  			if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
    96  				return filepath.SkipDir
    97  			}
    98  
    99  			name := filepath.ToSlash(path[len(src):])
   100  			if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") {
   101  				// The name "std" is only the standard library.
   102  				// If the name is cmd, it's the root of the command tree.
   103  				return filepath.SkipDir
   104  			}
   105  			if !treeCanMatch(name) {
   106  				return filepath.SkipDir
   107  			}
   108  			if have[name] {
   109  				return nil
   110  			}
   111  			have[name] = true
   112  			if !match(name) {
   113  				return nil
   114  			}
   115  			_, err = c.BuildContext.ImportDir(path, 0)
   116  			if err != nil {
   117  				if _, noGo := err.(*build.NoGoError); noGo {
   118  					return nil
   119  				}
   120  			}
   121  			pkgs = append(pkgs, name)
   122  			return nil
   123  		})
   124  	}
   125  	return pkgs
   126  }
   127  
   128  // importPathsNoDotExpansion returns the import paths to use for the given
   129  // command line, but it does no ... expansion.
   130  func (c *Context) importPathsNoDotExpansion(args []string) []string {
   131  	if len(args) == 0 {
   132  		return []string{"."}
   133  	}
   134  	var out []string
   135  	for _, a := range args {
   136  		// Arguments are supposed to be import paths, but
   137  		// as a courtesy to Windows developers, rewrite \ to /
   138  		// in command-line arguments. Handles .\... and so on.
   139  		if filepath.Separator == '\\' {
   140  			a = strings.Replace(a, `\`, `/`, -1)
   141  		}
   142  
   143  		// Put argument in canonical form, but preserve leading ./.
   144  		if strings.HasPrefix(a, "./") {
   145  			a = "./" + path.Clean(a)
   146  			if a == "./." {
   147  				a = "."
   148  			}
   149  		} else {
   150  			a = path.Clean(a)
   151  		}
   152  		if isMetaPackage(a) {
   153  			out = append(out, c.allPackages(a)...)
   154  			continue
   155  		}
   156  		out = append(out, a)
   157  	}
   158  	return out
   159  }
   160  
   161  // importPaths returns the import paths to use for the given command line.
   162  func (c *Context) importPaths(args []string) []string {
   163  	args = c.importPathsNoDotExpansion(args)
   164  	var out []string
   165  	for _, a := range args {
   166  		if strings.Contains(a, "...") {
   167  			if build.IsLocalImport(a) {
   168  				out = append(out, c.allPackagesInFS(a)...)
   169  			} else {
   170  				out = append(out, c.allPackages(a)...)
   171  			}
   172  			continue
   173  		}
   174  		out = append(out, a)
   175  	}
   176  	return out
   177  }
   178  
   179  // allPackages returns all the packages that can be found
   180  // under the $GOPATH directories and $GOROOT matching pattern.
   181  // The pattern is either "all" (all packages), "std" (standard packages),
   182  // "cmd" (standard commands), or a path including "...".
   183  func (c *Context) allPackages(pattern string) []string {
   184  	pkgs := c.matchPackages(pattern)
   185  	if len(pkgs) == 0 {
   186  		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
   187  	}
   188  	return pkgs
   189  }
   190  
   191  // allPackagesInFS is like allPackages but is passed a pattern
   192  // beginning ./ or ../, meaning it should scan the tree rooted
   193  // at the given directory. There are ... in the pattern too.
   194  func (c *Context) allPackagesInFS(pattern string) []string {
   195  	pkgs := c.matchPackagesInFS(pattern)
   196  	if len(pkgs) == 0 {
   197  		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
   198  	}
   199  	return pkgs
   200  }
   201  
   202  // matchPackagesInFS returns a list of package paths matching pattern,
   203  // which must begin with ./ or ../
   204  // (see go help packages for pattern syntax).
   205  func (c *Context) matchPackagesInFS(pattern string) []string {
   206  	// Find directory to begin the scan.
   207  	// Could be smarter but this one optimization
   208  	// is enough for now, since ... is usually at the
   209  	// end of a path.
   210  	i := strings.Index(pattern, "...")
   211  	dir, _ := path.Split(pattern[:i])
   212  
   213  	// pattern begins with ./ or ../.
   214  	// path.Clean will discard the ./ but not the ../.
   215  	// We need to preserve the ./ for pattern matching
   216  	// and in the returned import paths.
   217  	prefix := ""
   218  	if strings.HasPrefix(pattern, "./") {
   219  		prefix = "./"
   220  	}
   221  	match := matchPattern(pattern)
   222  
   223  	var pkgs []string
   224  	filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
   225  		if err != nil || !fi.IsDir() {
   226  			return nil
   227  		}
   228  		if path == dir {
   229  			// filepath.Walk starts at dir and recurses. For the recursive case,
   230  			// the path is the result of filepath.Join, which calls filepath.Clean.
   231  			// The initial case is not Cleaned, though, so we do this explicitly.
   232  			//
   233  			// This converts a path like "./io/" to "io". Without this step, running
   234  			// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
   235  			// package, because prepending the prefix "./" to the unclean path would
   236  			// result in "././io", and match("././io") returns false.
   237  			path = filepath.Clean(path)
   238  		}
   239  
   240  		// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
   241  		_, elem := filepath.Split(path)
   242  		dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
   243  		if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
   244  			return filepath.SkipDir
   245  		}
   246  
   247  		name := prefix + filepath.ToSlash(path)
   248  		if !match(name) {
   249  			return nil
   250  		}
   251  
   252  		// We keep the directory if we can import it, or if we can't import it
   253  		// due to invalid Go source files. This means that directories containing
   254  		// parse errors will be built (and fail) instead of being silently skipped
   255  		// as not matching the pattern. Go 1.5 and earlier skipped, but that
   256  		// behavior means people miss serious mistakes.
   257  		// See golang.org/issue/11407.
   258  		if p, err := c.BuildContext.ImportDir(path, 0); err != nil && shouldIgnoreImport(p) {
   259  			if _, noGo := err.(*build.NoGoError); !noGo {
   260  				log.Print(err)
   261  			}
   262  			return nil
   263  		}
   264  		pkgs = append(pkgs, name)
   265  		return nil
   266  	})
   267  	return pkgs
   268  }
   269  
   270  // isMetaPackage checks if name is a reserved package name that expands to multiple packages.
   271  func isMetaPackage(name string) bool {
   272  	return name == "std" || name == "cmd" || name == "all"
   273  }
   274  
   275  // isStandardImportPath reports whether $GOROOT/src/path should be considered
   276  // part of the standard distribution. For historical reasons we allow people to add
   277  // their own code to $GOROOT instead of using $GOPATH, but we assume that
   278  // code will start with a domain name (dot in the first element).
   279  func isStandardImportPath(path string) bool {
   280  	i := strings.Index(path, "/")
   281  	if i < 0 {
   282  		i = len(path)
   283  	}
   284  	elem := path[:i]
   285  	return !strings.Contains(elem, ".")
   286  }
   287  
   288  // hasPathPrefix reports whether the path s begins with the
   289  // elements in prefix.
   290  func hasPathPrefix(s, prefix string) bool {
   291  	switch {
   292  	default:
   293  		return false
   294  	case len(s) == len(prefix):
   295  		return s == prefix
   296  	case len(s) > len(prefix):
   297  		if prefix != "" && prefix[len(prefix)-1] == '/' {
   298  			return strings.HasPrefix(s, prefix)
   299  		}
   300  		return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
   301  	}
   302  }
   303  
   304  // treeCanMatchPattern(pattern)(name) reports whether
   305  // name or children of name can possibly match pattern.
   306  // Pattern is the same limited glob accepted by matchPattern.
   307  func treeCanMatchPattern(pattern string) func(name string) bool {
   308  	wildCard := false
   309  	if i := strings.Index(pattern, "..."); i >= 0 {
   310  		wildCard = true
   311  		pattern = pattern[:i]
   312  	}
   313  	return func(name string) bool {
   314  		return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
   315  			wildCard && strings.HasPrefix(name, pattern)
   316  	}
   317  }