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

View as plain text