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