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 }