github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/imports/fix.go (about) 1 // Copyright 2013 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 imports 6 7 import ( 8 "fmt" 9 "go/ast" 10 "go/build" 11 "go/parser" 12 "go/token" 13 "os" 14 "path" 15 "path/filepath" 16 "strings" 17 "sync" 18 19 "golang.org/x/tools/go/ast/astutil" 20 ) 21 22 // importToGroup is a list of functions which map from an import path to 23 // a group number. 24 var importToGroup = []func(importPath string) (num int, ok bool){ 25 func(importPath string) (num int, ok bool) { 26 if strings.HasPrefix(importPath, "appengine") { 27 return 2, true 28 } 29 return 30 }, 31 func(importPath string) (num int, ok bool) { 32 if strings.Contains(importPath, ".") { 33 return 1, true 34 } 35 return 36 }, 37 } 38 39 func importGroup(importPath string) int { 40 for _, fn := range importToGroup { 41 if n, ok := fn(importPath); ok { 42 return n 43 } 44 } 45 return 0 46 } 47 48 func fixImports(fset *token.FileSet, f *ast.File) (added []string, err error) { 49 // refs are a set of possible package references currently unsatisfied by imports. 50 // first key: either base package (e.g. "fmt") or renamed package 51 // second key: referenced package symbol (e.g. "Println") 52 refs := make(map[string]map[string]bool) 53 54 // decls are the current package imports. key is base package or renamed package. 55 decls := make(map[string]*ast.ImportSpec) 56 57 // collect potential uses of packages. 58 var visitor visitFn 59 visitor = visitFn(func(node ast.Node) ast.Visitor { 60 if node == nil { 61 return visitor 62 } 63 switch v := node.(type) { 64 case *ast.ImportSpec: 65 if v.Name != nil { 66 decls[v.Name.Name] = v 67 } else { 68 local := importPathToName(strings.Trim(v.Path.Value, `\"`)) 69 decls[local] = v 70 } 71 case *ast.SelectorExpr: 72 xident, ok := v.X.(*ast.Ident) 73 if !ok { 74 break 75 } 76 if xident.Obj != nil { 77 // if the parser can resolve it, it's not a package ref 78 break 79 } 80 pkgName := xident.Name 81 if refs[pkgName] == nil { 82 refs[pkgName] = make(map[string]bool) 83 } 84 if decls[pkgName] == nil { 85 refs[pkgName][v.Sel.Name] = true 86 } 87 } 88 return visitor 89 }) 90 ast.Walk(visitor, f) 91 92 // Nil out any unused ImportSpecs, to be removed in following passes 93 unusedImport := map[string]bool{} 94 for pkg, is := range decls { 95 if refs[pkg] == nil && pkg != "_" && pkg != "." { 96 unusedImport[strings.Trim(is.Path.Value, `"`)] = true 97 } 98 } 99 for ipath := range unusedImport { 100 if ipath == "C" { 101 // Don't remove cgo stuff. 102 continue 103 } 104 astutil.DeleteImport(fset, f, ipath) 105 } 106 107 // Search for imports matching potential package references. 108 searches := 0 109 type result struct { 110 ipath string 111 name string 112 err error 113 } 114 results := make(chan result) 115 for pkgName, symbols := range refs { 116 if len(symbols) == 0 { 117 continue // skip over packages already imported 118 } 119 go func(pkgName string, symbols map[string]bool) { 120 ipath, rename, err := findImport(pkgName, symbols) 121 r := result{ipath: ipath, err: err} 122 if rename { 123 r.name = pkgName 124 } 125 results <- r 126 }(pkgName, symbols) 127 searches++ 128 } 129 for i := 0; i < searches; i++ { 130 result := <-results 131 if result.err != nil { 132 return nil, result.err 133 } 134 if result.ipath != "" { 135 if result.name != "" { 136 astutil.AddNamedImport(fset, f, result.name, result.ipath) 137 } else { 138 astutil.AddImport(fset, f, result.ipath) 139 } 140 added = append(added, result.ipath) 141 } 142 } 143 144 return added, nil 145 } 146 147 // importPathToName returns the package name for the given import path. 148 var importPathToName = importPathToNameGoPath 149 150 // importPathToNameBasic assumes the package name is the base of import path. 151 func importPathToNameBasic(importPath string) (packageName string) { 152 return path.Base(importPath) 153 } 154 155 // importPathToNameGoPath finds out the actual package name, as declared in its .go files. 156 // If there's a problem, it falls back to using importPathToNameBasic. 157 func importPathToNameGoPath(importPath string) (packageName string) { 158 if buildPkg, err := build.Import(importPath, "", 0); err == nil { 159 return buildPkg.Name 160 } else { 161 return importPathToNameBasic(importPath) 162 } 163 } 164 165 type pkg struct { 166 importpath string // full pkg import path, e.g. "net/http" 167 dir string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt" 168 } 169 170 var pkgIndexOnce sync.Once 171 172 var pkgIndex struct { 173 sync.Mutex 174 m map[string][]pkg // shortname => []pkg, e.g "http" => "net/http" 175 } 176 177 // gate is a semaphore for limiting concurrency. 178 type gate chan struct{} 179 180 func (g gate) enter() { g <- struct{}{} } 181 func (g gate) leave() { <-g } 182 183 // fsgate protects the OS & filesystem from too much concurrency. 184 // Too much disk I/O -> too many threads -> swapping and bad scheduling. 185 var fsgate = make(gate, 8) 186 187 func loadPkgIndex() { 188 pkgIndex.Lock() 189 pkgIndex.m = make(map[string][]pkg) 190 pkgIndex.Unlock() 191 192 var wg sync.WaitGroup 193 for _, path := range build.Default.SrcDirs() { 194 fsgate.enter() 195 f, err := os.Open(path) 196 if err != nil { 197 fsgate.leave() 198 fmt.Fprint(os.Stderr, err) 199 continue 200 } 201 children, err := f.Readdir(-1) 202 f.Close() 203 fsgate.leave() 204 if err != nil { 205 fmt.Fprint(os.Stderr, err) 206 continue 207 } 208 for _, child := range children { 209 if child.IsDir() { 210 wg.Add(1) 211 go func(path, name string) { 212 defer wg.Done() 213 loadPkg(&wg, path, name) 214 }(path, child.Name()) 215 } 216 } 217 } 218 wg.Wait() 219 } 220 221 func loadPkg(wg *sync.WaitGroup, root, pkgrelpath string) { 222 importpath := filepath.ToSlash(pkgrelpath) 223 dir := filepath.Join(root, importpath) 224 225 fsgate.enter() 226 defer fsgate.leave() 227 pkgDir, err := os.Open(dir) 228 if err != nil { 229 return 230 } 231 children, err := pkgDir.Readdir(-1) 232 pkgDir.Close() 233 if err != nil { 234 return 235 } 236 // hasGo tracks whether a directory actually appears to be a 237 // Go source code directory. If $GOPATH == $HOME, and 238 // $HOME/src has lots of other large non-Go projects in it, 239 // then the calls to importPathToName below can be expensive. 240 hasGo := false 241 for _, child := range children { 242 // Avoid .foo, _foo, and testdata directory trees. 243 name := child.Name() 244 if name == "" || name[0] == '.' || name[0] == '_' || name == "testdata" { 245 continue 246 } 247 if strings.HasSuffix(name, ".go") { 248 hasGo = true 249 } 250 if child.IsDir() { 251 wg.Add(1) 252 go func(root, name string) { 253 defer wg.Done() 254 loadPkg(wg, root, name) 255 }(root, filepath.Join(importpath, name)) 256 } 257 } 258 if hasGo { 259 shortName := importPathToName(importpath) 260 pkgIndex.Lock() 261 pkgIndex.m[shortName] = append(pkgIndex.m[shortName], pkg{ 262 importpath: importpath, 263 dir: dir, 264 }) 265 pkgIndex.Unlock() 266 } 267 268 } 269 270 // loadExports returns a list exports for a package. 271 var loadExports = loadExportsGoPath 272 273 func loadExportsGoPath(dir string) map[string]bool { 274 exports := make(map[string]bool) 275 buildPkg, err := build.ImportDir(dir, 0) 276 if err != nil { 277 if strings.Contains(err.Error(), "no buildable Go source files in") { 278 return nil 279 } 280 fmt.Fprintf(os.Stderr, "could not import %q: %v\n", dir, err) 281 return nil 282 } 283 fset := token.NewFileSet() 284 for _, files := range [...][]string{buildPkg.GoFiles, buildPkg.CgoFiles} { 285 for _, file := range files { 286 f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0) 287 if err != nil { 288 fmt.Fprintf(os.Stderr, "could not parse %q: %v\n", file, err) 289 continue 290 } 291 for name := range f.Scope.Objects { 292 if ast.IsExported(name) { 293 exports[name] = true 294 } 295 } 296 } 297 } 298 return exports 299 } 300 301 // findImport searches for a package with the given symbols. 302 // If no package is found, findImport returns "". 303 // Declared as a variable rather than a function so goimports can be easily 304 // extended by adding a file with an init function. 305 var findImport = findImportGoPath 306 307 func findImportGoPath(pkgName string, symbols map[string]bool) (string, bool, error) { 308 // Fast path for the standard library. 309 // In the common case we hopefully never have to scan the GOPATH, which can 310 // be slow with moving disks. 311 if pkg, rename, ok := findImportStdlib(pkgName, symbols); ok { 312 return pkg, rename, nil 313 } 314 315 // TODO(sameer): look at the import lines for other Go files in the 316 // local directory, since the user is likely to import the same packages 317 // in the current Go file. Return rename=true when the other Go files 318 // use a renamed package that's also used in the current file. 319 320 pkgIndexOnce.Do(loadPkgIndex) 321 322 // Collect exports for packages with matching names. 323 var wg sync.WaitGroup 324 var pkgsMu sync.Mutex // guards pkgs 325 // full importpath => exported symbol => True 326 // e.g. "net/http" => "Client" => True 327 pkgs := make(map[string]map[string]bool) 328 pkgIndex.Lock() 329 for _, pkg := range pkgIndex.m[pkgName] { 330 wg.Add(1) 331 go func(importpath, dir string) { 332 defer wg.Done() 333 exports := loadExports(dir) 334 if exports != nil { 335 pkgsMu.Lock() 336 pkgs[importpath] = exports 337 pkgsMu.Unlock() 338 } 339 }(pkg.importpath, pkg.dir) 340 } 341 pkgIndex.Unlock() 342 wg.Wait() 343 344 // Filter out packages missing required exported symbols. 345 for symbol := range symbols { 346 for importpath, exports := range pkgs { 347 if !exports[symbol] { 348 delete(pkgs, importpath) 349 } 350 } 351 } 352 if len(pkgs) == 0 { 353 return "", false, nil 354 } 355 356 // If there are multiple candidate packages, the shortest one wins. 357 // This is a heuristic to prefer the standard library (e.g. "bytes") 358 // over e.g. "github.com/foo/bar/bytes". 359 shortest := "" 360 for importPath := range pkgs { 361 if shortest == "" || len(importPath) < len(shortest) { 362 shortest = importPath 363 } 364 } 365 return shortest, false, nil 366 } 367 368 type visitFn func(node ast.Node) ast.Visitor 369 370 func (fn visitFn) Visit(node ast.Node) ast.Visitor { 371 return fn(node) 372 } 373 374 func findImportStdlib(shortPkg string, symbols map[string]bool) (importPath string, rename, ok bool) { 375 for symbol := range symbols { 376 path := stdlib[shortPkg+"."+symbol] 377 if path == "" { 378 return "", false, false 379 } 380 if importPath != "" && importPath != path { 381 // Ambiguous. Symbols pointed to different things. 382 return "", false, false 383 } 384 importPath = path 385 } 386 return importPath, false, importPath != "" 387 }