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

     1  // Copyright 2022 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 pkgpattern
     6  
     7  import (
     8  	"regexp"
     9  	"strings"
    10  )
    11  
    12  // Note: most of this code was originally part of the cmd/go/internal/search
    13  // package; it was migrated here in order to support the use case of
    14  // commands other than cmd/go that need to accept package pattern args.
    15  
    16  // TreeCanMatchPattern(pattern)(name) reports whether
    17  // name or children of name can possibly match pattern.
    18  // Pattern is the same limited glob accepted by MatchPattern.
    19  func TreeCanMatchPattern(pattern string) func(name string) bool {
    20  	wildCard := false
    21  	if i := strings.Index(pattern, "..."); i >= 0 {
    22  		wildCard = true
    23  		pattern = pattern[:i]
    24  	}
    25  	return func(name string) bool {
    26  		return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
    27  			wildCard && strings.HasPrefix(name, pattern)
    28  	}
    29  }
    30  
    31  // MatchPattern(pattern)(name) reports whether
    32  // name matches pattern. Pattern is a limited glob
    33  // pattern in which '...' means 'any string' and there
    34  // is no other special syntax.
    35  // Unfortunately, there are two special cases. Quoting "go help packages":
    36  //
    37  // First, /... at the end of the pattern can match an empty string,
    38  // so that net/... matches both net and packages in its subdirectories, like net/http.
    39  // Second, any slash-separated pattern element containing a wildcard never
    40  // participates in a match of the "vendor" element in the path of a vendored
    41  // package, so that ./... does not match packages in subdirectories of
    42  // ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
    43  // Note, however, that a directory named vendor that itself contains code
    44  // is not a vendored package: cmd/vendor would be a command named vendor,
    45  // and the pattern cmd/... matches it.
    46  func MatchPattern(pattern string) func(name string) bool {
    47  	return matchPatternInternal(pattern, true)
    48  }
    49  
    50  // MatchSimplePattern returns a function that can be used to check
    51  // whether a given name matches a pattern, where pattern is a limited
    52  // glob pattern in which '...' means 'any string', with no other
    53  // special syntax. There is one special case for MatchPatternSimple:
    54  // according to the rules in "go help packages": a /... at the end of
    55  // the pattern can match an empty string, so that net/... matches both
    56  // net and packages in its subdirectories, like net/http.
    57  func MatchSimplePattern(pattern string) func(name string) bool {
    58  	return matchPatternInternal(pattern, false)
    59  }
    60  
    61  func matchPatternInternal(pattern string, vendorExclude bool) func(name string) bool {
    62  	// Convert pattern to regular expression.
    63  	// The strategy for the trailing /... is to nest it in an explicit ? expression.
    64  	// The strategy for the vendor exclusion is to change the unmatchable
    65  	// vendor strings to a disallowed code point (vendorChar) and to use
    66  	// "(anything but that codepoint)*" as the implementation of the ... wildcard.
    67  	// This is a bit complicated but the obvious alternative,
    68  	// namely a hand-written search like in most shell glob matchers,
    69  	// is too easy to make accidentally exponential.
    70  	// Using package regexp guarantees linear-time matching.
    71  
    72  	const vendorChar = "\x00"
    73  
    74  	if vendorExclude && strings.Contains(pattern, vendorChar) {
    75  		return func(name string) bool { return false }
    76  	}
    77  
    78  	re := regexp.QuoteMeta(pattern)
    79  	wild := `.*`
    80  	if vendorExclude {
    81  		wild = `[^` + vendorChar + `]*`
    82  		re = replaceVendor(re, vendorChar)
    83  		switch {
    84  		case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
    85  			re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
    86  		case re == vendorChar+`/\.\.\.`:
    87  			re = `(/vendor|/` + vendorChar + `/\.\.\.)`
    88  		}
    89  	}
    90  	if strings.HasSuffix(re, `/\.\.\.`) {
    91  		re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
    92  	}
    93  	re = strings.ReplaceAll(re, `\.\.\.`, wild)
    94  
    95  	reg := regexp.MustCompile(`^` + re + `$`)
    96  
    97  	return func(name string) bool {
    98  		if vendorExclude {
    99  			if strings.Contains(name, vendorChar) {
   100  				return false
   101  			}
   102  			name = replaceVendor(name, vendorChar)
   103  		}
   104  		return reg.MatchString(name)
   105  	}
   106  }
   107  
   108  // hasPathPrefix reports whether the path s begins with the
   109  // elements in prefix.
   110  func hasPathPrefix(s, prefix string) bool {
   111  	switch {
   112  	default:
   113  		return false
   114  	case len(s) == len(prefix):
   115  		return s == prefix
   116  	case len(s) > len(prefix):
   117  		if prefix != "" && prefix[len(prefix)-1] == '/' {
   118  			return strings.HasPrefix(s, prefix)
   119  		}
   120  		return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
   121  	}
   122  }
   123  
   124  // replaceVendor returns the result of replacing
   125  // non-trailing vendor path elements in x with repl.
   126  func replaceVendor(x, repl string) string {
   127  	if !strings.Contains(x, "vendor") {
   128  		return x
   129  	}
   130  	elem := strings.Split(x, "/")
   131  	for i := 0; i < len(elem)-1; i++ {
   132  		if elem[i] == "vendor" {
   133  			elem[i] = repl
   134  		}
   135  	}
   136  	return strings.Join(elem, "/")
   137  }
   138  

View as plain text