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  }