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