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.
29 "golang.org/x/tools/go/ast/astutil"
30 "golang.org/x/tools/go/packages"
31 "golang.org/x/tools/internal/gopathwalk"
34 // Debug controls verbose logging.
37 // LocalPrefix is a comma-separated string of import path prefixes, which, if
38 // set, instructs Process to sort the import paths with the given prefixes
39 // into another group after 3rd-party packages.
40 var LocalPrefix string
42 func localPrefixes() []string {
43 if LocalPrefix != "" {
44 return strings.Split(LocalPrefix, ",")
49 // importToGroup is a list of functions which map from an import path to
51 var importToGroup = []func(importPath string) (num int, ok bool){
52 func(importPath string) (num int, ok bool) {
53 for _, p := range localPrefixes() {
54 if strings.HasPrefix(importPath, p) || strings.TrimSuffix(p, "/") == importPath {
60 func(importPath string) (num int, ok bool) {
61 if strings.HasPrefix(importPath, "appengine") {
66 func(importPath string) (num int, ok bool) {
67 if strings.Contains(importPath, ".") {
74 func importGroup(importPath string) int {
75 for _, fn := range importToGroup {
76 if n, ok := fn(importPath); ok {
83 // An importInfo represents a single import statement.
84 type importInfo struct {
85 importPath string // import path, e.g. "crypto/rand".
86 name string // import name, e.g. "crand", or "" if none.
89 // A packageInfo represents what's known about a package.
90 type packageInfo struct {
91 name string // real package name, if known.
92 exports map[string]bool // known exports.
95 // parseOtherFiles parses all the Go files in srcDir except filename, including
96 // test files if filename looks like a test.
97 func parseOtherFiles(fset *token.FileSet, srcDir, filename string) []*ast.File {
98 // This could use go/packages but it doesn't buy much, and it fails
99 // with https://golang.org/issue/26296 in LoadFiles mode in some cases.
100 considerTests := strings.HasSuffix(filename, "_test.go")
102 fileBase := filepath.Base(filename)
103 packageFileInfos, err := ioutil.ReadDir(srcDir)
108 var files []*ast.File
109 for _, fi := range packageFileInfos {
110 if fi.Name() == fileBase || !strings.HasSuffix(fi.Name(), ".go") {
113 if !considerTests && strings.HasSuffix(fi.Name(), "_test.go") {
117 f, err := parser.ParseFile(fset, filepath.Join(srcDir, fi.Name()), nil, 0)
122 files = append(files, f)
128 // addGlobals puts the names of package vars into the provided map.
129 func addGlobals(f *ast.File, globals map[string]bool) {
130 for _, decl := range f.Decls {
131 genDecl, ok := decl.(*ast.GenDecl)
136 for _, spec := range genDecl.Specs {
137 valueSpec, ok := spec.(*ast.ValueSpec)
141 globals[valueSpec.Names[0].Name] = true
146 // collectReferences builds a map of selector expressions, from
147 // left hand side (X) to a set of right hand sides (Sel).
148 func collectReferences(f *ast.File) references {
152 visitor = func(node ast.Node) ast.Visitor {
156 switch v := node.(type) {
157 case *ast.SelectorExpr:
158 xident, ok := v.X.(*ast.Ident)
162 if xident.Obj != nil {
163 // If the parser can resolve it, it's not a package ref.
166 if !ast.IsExported(v.Sel.Name) {
167 // Whatever this is, it's not exported from a package.
170 pkgName := xident.Name
173 r = make(map[string]bool)
184 // collectImports returns all the imports in f, keyed by their package name as
185 // determined by pathToName. Unnamed imports (., _) and "C" are ignored.
186 func collectImports(f *ast.File) []*importInfo {
187 var imports []*importInfo
188 for _, imp := range f.Imports {
193 if imp.Path.Value == `"C"` || name == "_" || name == "." {
196 path := strings.Trim(imp.Path.Value, `"`)
197 imports = append(imports, &importInfo{
205 // findMissingImport searches pass's candidates for an import that provides
206 // pkg, containing all of syms.
207 func (p *pass) findMissingImport(pkg string, syms map[string]bool) *importInfo {
208 for _, candidate := range p.candidates {
209 pkgInfo, ok := p.knownPackages[candidate.importPath]
213 if p.importIdentifier(candidate) != pkg {
218 for right := range syms {
219 if !pkgInfo.exports[right] {
232 // references is set of references found in a Go file. The first map key is the
233 // left hand side of a selector expression, the second key is the right hand
234 // side, and the value should always be true.
235 type references map[string]map[string]bool
237 // A pass contains all the inputs and state necessary to fix a file's imports.
238 // It can be modified in some ways during use; see comments below.
240 // Inputs. These must be set before a call to load, and not modified after.
241 fset *token.FileSet // fset used to parse f and its siblings.
242 f *ast.File // the file being fixed.
243 srcDir string // the directory containing f.
244 fixEnv *fixEnv // the environment to use for go commands, etc.
245 loadRealPackageNames bool // if true, load package names from disk rather than guessing them.
246 otherFiles []*ast.File // sibling files.
248 // Intermediate state, generated by load.
249 existingImports map[string]*importInfo
251 missingRefs references
253 // Inputs to fix. These can be augmented between successive fix calls.
254 lastTry bool // indicates that this is the last call and fix should clean up as best it can.
255 candidates []*importInfo // candidate imports in priority order.
256 knownPackages map[string]*packageInfo // information about all known packages.
259 // loadPackageNames saves the package names for everything referenced by imports.
260 func (p *pass) loadPackageNames(imports []*importInfo) error {
262 for _, imp := range imports {
263 if _, ok := p.knownPackages[imp.importPath]; ok {
266 unknown = append(unknown, imp.importPath)
269 names, err := p.fixEnv.getResolver().loadPackageNames(unknown, p.srcDir)
274 for path, name := range names {
275 p.knownPackages[path] = &packageInfo{
277 exports: map[string]bool{},
283 // importIdentifier returns the identifier that imp will introduce. It will
284 // guess if the package name has not been loaded, e.g. because the source
286 func (p *pass) importIdentifier(imp *importInfo) string {
290 known := p.knownPackages[imp.importPath]
291 if known != nil && known.name != "" {
294 return importPathToAssumedName(imp.importPath)
297 // load reads in everything necessary to run a pass, and reports whether the
298 // file already has all the imports it needs. It fills in p.missingRefs with the
299 // file's missing symbols, if any, or removes unused imports if not.
300 func (p *pass) load() bool {
301 p.knownPackages = map[string]*packageInfo{}
302 p.missingRefs = references{}
303 p.existingImports = map[string]*importInfo{}
305 // Load basic information about the file in question.
306 p.allRefs = collectReferences(p.f)
308 // Load stuff from other files in the same package:
309 // global variables so we know they don't need resolving, and imports
310 // that we might want to mimic.
311 globals := map[string]bool{}
312 for _, otherFile := range p.otherFiles {
313 // Don't load globals from files that are in the same directory
314 // but a different package. Using them to suggest imports is OK.
315 if p.f.Name.Name == otherFile.Name.Name {
316 addGlobals(otherFile, globals)
318 p.candidates = append(p.candidates, collectImports(otherFile)...)
321 // Resolve all the import paths we've seen to package names, and store
322 // f's imports by the identifier they introduce.
323 imports := collectImports(p.f)
324 if p.loadRealPackageNames {
325 err := p.loadPackageNames(append(imports, p.candidates...))
328 log.Printf("loading package names: %v", err)
333 for _, imp := range imports {
334 p.existingImports[p.importIdentifier(imp)] = imp
337 // Find missing references.
338 for left, rights := range p.allRefs {
342 _, ok := p.existingImports[left]
344 p.missingRefs[left] = rights
348 if len(p.missingRefs) != 0 {
355 // fix attempts to satisfy missing imports using p.candidates. If it finds
356 // everything, or if p.lastTry is true, it adds the imports it found,
357 // removes anything unused, and returns true.
358 func (p *pass) fix() bool {
359 // Find missing imports.
360 var selected []*importInfo
361 for left, rights := range p.missingRefs {
362 if imp := p.findMissingImport(left, rights); imp != nil {
363 selected = append(selected, imp)
367 if !p.lastTry && len(selected) != len(p.missingRefs) {
371 // Found everything, or giving up. Add the new imports and remove any unused.
372 for _, imp := range p.existingImports {
373 // We deliberately ignore globals here, because we can't be sure
374 // they're in the same package. People do things like put multiple
375 // main packages in the same directory, and we don't want to
376 // remove imports if they happen to have the same name as a var in
377 // a different package.
378 if _, ok := p.allRefs[p.importIdentifier(imp)]; !ok {
379 astutil.DeleteNamedImport(p.fset, p.f, imp.name, imp.importPath)
383 for _, imp := range selected {
384 astutil.AddNamedImport(p.fset, p.f, imp.name, imp.importPath)
387 if p.loadRealPackageNames {
388 for _, imp := range p.f.Imports {
392 path := strings.Trim(imp.Path.Value, `""`)
393 ident := p.importIdentifier(&importInfo{importPath: path})
394 if ident != importPathToAssumedName(path) {
395 imp.Name = &ast.Ident{Name: ident, NamePos: imp.Pos()}
403 // assumeSiblingImportsValid assumes that siblings' use of packages is valid,
404 // adding the exports they use.
405 func (p *pass) assumeSiblingImportsValid() {
406 for _, f := range p.otherFiles {
407 refs := collectReferences(f)
408 imports := collectImports(f)
409 importsByName := map[string]*importInfo{}
410 for _, imp := range imports {
411 importsByName[p.importIdentifier(imp)] = imp
413 for left, rights := range refs {
414 if imp, ok := importsByName[left]; ok {
415 if _, ok := stdlib[imp.importPath]; ok {
416 // We have the stdlib in memory; no need to guess.
417 rights = stdlib[imp.importPath]
419 p.addCandidate(imp, &packageInfo{
420 // no name; we already know it.
428 // addCandidate adds a candidate import to p, and merges in the information
430 func (p *pass) addCandidate(imp *importInfo, pkg *packageInfo) {
431 p.candidates = append(p.candidates, imp)
432 if existing, ok := p.knownPackages[imp.importPath]; ok {
433 if existing.name == "" {
434 existing.name = pkg.name
436 for export := range pkg.exports {
437 existing.exports[export] = true
440 p.knownPackages[imp.importPath] = pkg
444 // fixImports adds and removes imports from f so that all its references are
445 // satisfied and there are no unused imports.
447 // This is declared as a variable rather than a function so goimports can
448 // easily be extended by adding a file with an init function.
449 var fixImports = fixImportsDefault
451 func fixImportsDefault(fset *token.FileSet, f *ast.File, filename string, env *fixEnv) error {
452 abs, err := filepath.Abs(filename)
456 srcDir := filepath.Dir(abs)
458 log.Printf("fixImports(filename=%q), abs=%q, srcDir=%q ...", filename, abs, srcDir)
461 // First pass: looking only at f, and using the naive algorithm to
462 // derive package names from import paths, see if the file is already
463 // complete. We can't add any imports yet, because we don't know
464 // if missing references are actually package vars.
465 p := &pass{fset: fset, f: f, srcDir: srcDir}
470 otherFiles := parseOtherFiles(fset, srcDir, filename)
472 // Second pass: add information from other files in the same package,
473 // like their package vars and imports.
474 p.otherFiles = otherFiles
479 // Now we can try adding imports from the stdlib.
480 p.assumeSiblingImportsValid()
481 addStdlibCandidates(p, p.missingRefs)
486 // Third pass: get real package names where we had previously used
487 // the naive algorithm. This is the first step that will use the
488 // environment, so we provide it here for the first time.
489 p = &pass{fset: fset, f: f, srcDir: srcDir, fixEnv: env}
490 p.loadRealPackageNames = true
491 p.otherFiles = otherFiles
496 addStdlibCandidates(p, p.missingRefs)
497 p.assumeSiblingImportsValid()
502 // Go look for candidates in $GOPATH, etc. We don't necessarily load
503 // the real exports of sibling imports, so keep assuming their contents.
504 if err := addExternalCandidates(p, p.missingRefs, filename); err != nil {
513 // fixEnv contains environment variables and settings that affect the use of
514 // the go command, the go/build package, etc.
516 // If non-empty, these will be used instead of the
517 // process-wide values.
518 GOPATH, GOROOT, GO111MODULE, GOPROXY, GOFLAGS string
521 // If true, use go/packages regardless of the environment.
527 func (e *fixEnv) env() []string {
529 add := func(k, v string) {
531 env = append(env, k+"="+v)
534 add("GOPATH", e.GOPATH)
535 add("GOROOT", e.GOROOT)
536 add("GO111MODULE", e.GO111MODULE)
537 add("GOPROXY", e.GOPROXY)
538 add("GOFLAGS", e.GOFLAGS)
539 if e.WorkingDir != "" {
540 add("PWD", e.WorkingDir)
545 func (e *fixEnv) getResolver() resolver {
546 if e.resolver != nil {
549 if e.ForceGoPackages {
550 return &goPackagesResolver{env: e}
553 out, err := e.invokeGo("env", "GOMOD")
554 if err != nil || len(bytes.TrimSpace(out.Bytes())) == 0 {
555 return &gopathResolver{env: e}
557 return &moduleResolver{env: e}
560 func (e *fixEnv) newPackagesConfig(mode packages.LoadMode) *packages.Config {
561 return &packages.Config{
568 func (e *fixEnv) buildContext() *build.Context {
570 ctx.GOROOT = e.GOROOT
571 ctx.GOPATH = e.GOPATH
575 func (e *fixEnv) invokeGo(args ...string) (*bytes.Buffer, error) {
576 cmd := exec.Command("go", args...)
577 stdout := &bytes.Buffer{}
578 stderr := &bytes.Buffer{}
582 cmd.Dir = e.WorkingDir
585 defer func(start time.Time) { log.Printf("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now())
587 if err := cmd.Run(); err != nil {
588 return nil, fmt.Errorf("running go: %v (stderr:\n%s)", err, stderr)
593 func cmdDebugStr(cmd *exec.Cmd) string {
594 env := make(map[string]string)
595 for _, kv := range cmd.Env {
596 split := strings.Split(kv, "=")
597 k, v := split[0], split[1]
601 return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], cmd.Args)
604 func addStdlibCandidates(pass *pass, refs references) {
605 add := func(pkg string) {
607 &importInfo{importPath: pkg},
608 &packageInfo{name: path.Base(pkg), exports: stdlib[pkg]})
610 for left := range refs {
612 // Make sure we try crypto/rand before math/rand.
617 for importPath := range stdlib {
618 if path.Base(importPath) == left {
625 // A resolver does the build-system-specific parts of goimports.
626 type resolver interface {
627 // loadPackageNames loads the package names in importPaths.
628 loadPackageNames(importPaths []string, srcDir string) (map[string]string, error)
629 // scan finds (at least) the packages satisfying refs. The returned slice is unordered.
630 scan(refs references) ([]*pkg, error)
633 // gopathResolver implements resolver for GOPATH and module workspaces using go/packages.
634 type goPackagesResolver struct {
638 func (r *goPackagesResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) {
639 cfg := r.env.newPackagesConfig(packages.LoadFiles)
640 pkgs, err := packages.Load(cfg, importPaths...)
644 names := map[string]string{}
645 for _, pkg := range pkgs {
646 names[VendorlessPath(pkg.PkgPath)] = pkg.Name
648 // We may not have found all the packages. Guess the rest.
649 for _, path := range importPaths {
650 if _, ok := names[path]; ok {
653 names[path] = importPathToAssumedName(path)
659 func (r *goPackagesResolver) scan(refs references) ([]*pkg, error) {
660 var loadQueries []string
661 for pkgName := range refs {
662 loadQueries = append(loadQueries, "iamashamedtousethedisabledqueryname="+pkgName)
664 sort.Strings(loadQueries)
665 cfg := r.env.newPackagesConfig(packages.LoadFiles)
666 goPackages, err := packages.Load(cfg, loadQueries...)
672 for _, goPackage := range goPackages {
673 scan = append(scan, &pkg{
674 dir: filepath.Dir(goPackage.CompiledGoFiles[0]),
675 importPathShort: VendorlessPath(goPackage.PkgPath),
676 goPackage: goPackage,
682 func addExternalCandidates(pass *pass, refs references, filename string) error {
683 dirScan, err := pass.fixEnv.getResolver().scan(refs)
688 // Search for imports matching potential package references.
693 results := make(chan result, len(refs))
695 ctx, cancel := context.WithCancel(context.TODO())
696 var wg sync.WaitGroup
703 firstErrOnce sync.Once
705 for pkgName, symbols := range refs {
707 go func(pkgName string, symbols map[string]bool) {
710 found, err := findImport(ctx, pass.fixEnv, dirScan, pkgName, symbols, filename)
713 firstErrOnce.Do(func() {
721 return // No matching package.
725 importPath: found.importPathShort,
732 results <- result{imp, pkg}
740 for result := range results {
741 pass.addCandidate(result.imp, result.pkg)
746 // notIdentifier reports whether ch is an invalid identifier character.
747 func notIdentifier(ch rune) bool {
748 return !('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' ||
749 '0' <= ch && ch <= '9' ||
751 ch >= utf8.RuneSelf && (unicode.IsLetter(ch) || unicode.IsDigit(ch)))
754 // importPathToAssumedName returns the assumed package name of an import path.
755 // It does this using only string parsing of the import path.
756 // It picks the last element of the path that does not look like a major
757 // version, and then picks the valid identifier off the start of that element.
758 // It is used to determine if a local rename should be added to an import for
760 // This function could be moved to a standard package and exported if we want
761 // for use in other tools.
762 func importPathToAssumedName(importPath string) string {
763 base := path.Base(importPath)
764 if strings.HasPrefix(base, "v") {
765 if _, err := strconv.Atoi(base[1:]); err == nil {
766 dir := path.Dir(importPath)
768 base = path.Base(dir)
772 base = strings.TrimPrefix(base, "go-")
773 if i := strings.IndexFunc(base, notIdentifier); i >= 0 {
779 // gopathResolver implements resolver for GOPATH workspaces.
780 type gopathResolver struct {
784 func (r *gopathResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) {
785 names := map[string]string{}
786 for _, path := range importPaths {
787 names[path] = importPathToName(r.env, path, srcDir)
792 // importPathToNameGoPath finds out the actual package name, as declared in its .go files.
793 // If there's a problem, it returns "".
794 func importPathToName(env *fixEnv, importPath, srcDir string) (packageName string) {
795 // Fast path for standard library without going to disk.
796 if _, ok := stdlib[importPath]; ok {
797 return path.Base(importPath) // stdlib packages always match their paths.
800 buildPkg, err := env.buildContext().Import(importPath, srcDir, build.FindOnly)
804 pkgName, err := packageDirToName(buildPkg.Dir)
811 // packageDirToName is a faster version of build.Import if
812 // the only thing desired is the package name. It uses build.FindOnly
813 // to find the directory and then only parses one file in the package,
814 // trusting that the files in the directory are consistent.
815 func packageDirToName(dir string) (packageName string, err error) {
816 d, err := os.Open(dir)
820 names, err := d.Readdirnames(-1)
825 sort.Strings(names) // to have predictable behavior
828 for _, name := range names {
829 if !strings.HasSuffix(name, ".go") {
832 if strings.HasSuffix(name, "_test.go") {
836 fullFile := filepath.Join(dir, name)
838 fset := token.NewFileSet()
839 f, err := parser.ParseFile(fset, fullFile, nil, parser.PackageClauseOnly)
844 pkgName := f.Name.Name
845 if pkgName == "documentation" {
846 // Special case from go/build.ImportDir, not
847 // handled by ctx.MatchFile.
850 if pkgName == "main" {
851 // Also skip package main, assuming it's a +build ignore generator or example.
852 // Since you can't import a package main anyway, there's no harm here.
860 return "", fmt.Errorf("no importable package found in %d Go files", nfile)
864 goPackage *packages.Package
865 dir string // absolute file path to pkg directory ("/usr/lib/go/src/net/http")
866 importPathShort string // vendorless import path ("net/http", "a/b")
869 type pkgDistance struct {
871 distance int // relative distance to target
874 // byDistanceOrImportPathShortLength sorts by relative distance breaking ties
875 // on the short import path length and then the import string itself.
876 type byDistanceOrImportPathShortLength []pkgDistance
878 func (s byDistanceOrImportPathShortLength) Len() int { return len(s) }
879 func (s byDistanceOrImportPathShortLength) Less(i, j int) bool {
880 di, dj := s[i].distance, s[j].distance
891 vi, vj := s[i].pkg.importPathShort, s[j].pkg.importPathShort
892 if len(vi) != len(vj) {
893 return len(vi) < len(vj)
897 func (s byDistanceOrImportPathShortLength) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
899 func distance(basepath, targetpath string) int {
900 p, err := filepath.Rel(basepath, targetpath)
907 return strings.Count(p, string(filepath.Separator)) + 1
910 func (r *gopathResolver) scan(_ references) ([]*pkg, error) {
911 dupCheck := make(map[string]bool)
916 add := func(root gopathwalk.Root, dir string) {
920 if _, dup := dupCheck[dir]; dup {
924 importpath := filepath.ToSlash(dir[len(root.Path)+len("/"):])
925 result = append(result, &pkg{
926 importPathShort: VendorlessPath(importpath),
930 gopathwalk.Walk(gopathwalk.SrcDirsRoots(r.env.buildContext()), add, gopathwalk.Options{Debug: Debug, ModulesEnabled: false})
934 // VendorlessPath returns the devendorized version of the import path ipath.
935 // For example, VendorlessPath("foo/bar/vendor/a/b") returns "a/b".
936 func VendorlessPath(ipath string) string {
937 // Devendorize for use in import statement.
938 if i := strings.LastIndex(ipath, "/vendor/"); i >= 0 {
939 return ipath[i+len("/vendor/"):]
941 if strings.HasPrefix(ipath, "vendor/") {
942 return ipath[len("vendor/"):]
947 // loadExports returns the set of exported symbols in the package at dir.
948 // It returns nil on error or if the package name in dir does not match expectPackage.
949 func loadExports(ctx context.Context, env *fixEnv, expectPackage string, pkg *pkg) (map[string]bool, error) {
951 log.Printf("loading exports in dir %s (seeking package %s)", pkg.dir, expectPackage)
953 if pkg.goPackage != nil {
954 exports := map[string]bool{}
955 fset := token.NewFileSet()
956 for _, fname := range pkg.goPackage.CompiledGoFiles {
957 f, err := parser.ParseFile(fset, fname, nil, 0)
959 return nil, fmt.Errorf("parsing %s: %v", fname, err)
961 for name := range f.Scope.Objects {
962 if ast.IsExported(name) {
970 exports := make(map[string]bool)
972 // Look for non-test, buildable .go files which could provide exports.
973 all, err := ioutil.ReadDir(pkg.dir)
977 var files []os.FileInfo
978 for _, fi := range all {
980 if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
983 match, err := env.buildContext().MatchFile(pkg.dir, fi.Name())
984 if err != nil || !match {
987 files = append(files, fi)
991 return nil, fmt.Errorf("dir %v contains no buildable, non-test .go files", pkg.dir)
994 fset := token.NewFileSet()
995 for _, fi := range files {
998 return nil, ctx.Err()
1002 fullFile := filepath.Join(pkg.dir, fi.Name())
1003 f, err := parser.ParseFile(fset, fullFile, nil, 0)
1005 return nil, fmt.Errorf("parsing %s: %v", fullFile, err)
1007 pkgName := f.Name.Name
1008 if pkgName == "documentation" {
1009 // Special case from go/build.ImportDir, not
1010 // handled by MatchFile above.
1013 if pkgName != expectPackage {
1014 return nil, fmt.Errorf("scan of dir %v is not expected package %v (actually %v)", pkg.dir, expectPackage, pkgName)
1016 for name := range f.Scope.Objects {
1017 if ast.IsExported(name) {
1018 exports[name] = true
1024 exportList := make([]string, 0, len(exports))
1025 for k := range exports {
1026 exportList = append(exportList, k)
1028 sort.Strings(exportList)
1029 log.Printf("loaded exports in dir %v (package %v): %v", pkg.dir, expectPackage, strings.Join(exportList, ", "))
1034 // findImport searches for a package with the given symbols.
1035 // If no package is found, findImport returns ("", false, nil)
1036 func findImport(ctx context.Context, env *fixEnv, dirScan []*pkg, pkgName string, symbols map[string]bool, filename string) (*pkg, error) {
1037 pkgDir, err := filepath.Abs(filename)
1041 pkgDir = filepath.Dir(pkgDir)
1043 // Find candidate packages, looking only at their directory names first.
1044 var candidates []pkgDistance
1045 for _, pkg := range dirScan {
1046 if pkg.dir != pkgDir && pkgIsCandidate(filename, pkgName, pkg) {
1047 candidates = append(candidates, pkgDistance{
1049 distance: distance(pkgDir, pkg.dir),
1054 // Sort the candidates by their import package length,
1055 // assuming that shorter package names are better than long
1056 // ones. Note that this sorts by the de-vendored name, so
1057 // there's no "penalty" for vendoring.
1058 sort.Sort(byDistanceOrImportPathShortLength(candidates))
1060 for i, c := range candidates {
1061 log.Printf("%s candidate %d/%d: %v in %v", pkgName, i+1, len(candidates), c.pkg.importPathShort, c.pkg.dir)
1065 // Collect exports for packages with matching names.
1067 rescv := make([]chan *pkg, len(candidates))
1068 for i := range candidates {
1069 rescv[i] = make(chan *pkg, 1)
1071 const maxConcurrentPackageImport = 4
1072 loadExportsSem := make(chan struct{}, maxConcurrentPackageImport)
1074 ctx, cancel := context.WithCancel(ctx)
1075 var wg sync.WaitGroup
1084 for i, c := range candidates {
1086 case loadExportsSem <- struct{}{}:
1092 go func(c pkgDistance, resc chan<- *pkg) {
1098 exports, err := loadExports(ctx, env, pkgName, c.pkg)
1101 log.Printf("loading exports in dir %s (seeking package %s): %v", c.pkg.dir, pkgName, err)
1107 // If it doesn't have the right
1108 // symbols, send nil to mean no match.
1109 for symbol := range symbols {
1110 if !exports[symbol] {
1120 for _, resc := range rescv {
1130 // pkgIsCandidate reports whether pkg is a candidate for satisfying the
1131 // finding which package pkgIdent in the file named by filename is trying
1134 // This check is purely lexical and is meant to be as fast as possible
1135 // because it's run over all $GOPATH directories to filter out poor
1136 // candidates in order to limit the CPU and I/O later parsing the
1137 // exports in candidate packages.
1139 // filename is the file being formatted.
1140 // pkgIdent is the package being searched for, like "client" (if
1141 // searching for "client.New")
1142 func pkgIsCandidate(filename, pkgIdent string, pkg *pkg) bool {
1143 // Check "internal" and "vendor" visibility:
1144 if !canUse(filename, pkg.dir) {
1148 // Speed optimization to minimize disk I/O:
1149 // the last two components on disk must contain the
1150 // package name somewhere.
1152 // This permits mismatch naming like directory
1153 // "go-foo" being package "foo", or "pkg.v3" being "pkg",
1154 // or directory "google.golang.org/api/cloudbilling/v1"
1155 // being package "cloudbilling", but doesn't
1156 // permit a directory "foo" to be package
1157 // "bar", which is strongly discouraged
1158 // anyway. There's no reason goimports needs
1159 // to be slow just to accommodate that.
1160 lastTwo := lastTwoComponents(pkg.importPathShort)
1161 if strings.Contains(lastTwo, pkgIdent) {
1164 if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(pkgIdent) {
1165 lastTwo = lowerASCIIAndRemoveHyphen(lastTwo)
1166 if strings.Contains(lastTwo, pkgIdent) {
1174 func hasHyphenOrUpperASCII(s string) bool {
1175 for i := 0; i < len(s); i++ {
1177 if b == '-' || ('A' <= b && b <= 'Z') {
1184 func lowerASCIIAndRemoveHyphen(s string) (ret string) {
1185 buf := make([]byte, 0, len(s))
1186 for i := 0; i < len(s); i++ {
1191 case 'A' <= b && b <= 'Z':
1192 buf = append(buf, b+('a'-'A'))
1194 buf = append(buf, b)
1200 // canUse reports whether the package in dir is usable from filename,
1201 // respecting the Go "internal" and "vendor" visibility rules.
1202 func canUse(filename, dir string) bool {
1203 // Fast path check, before any allocations. If it doesn't contain vendor
1204 // or internal, it's not tricky:
1205 // Note that this can false-negative on directories like "notinternal",
1206 // but we check it correctly below. This is just a fast path.
1207 if !strings.Contains(dir, "vendor") && !strings.Contains(dir, "internal") {
1211 dirSlash := filepath.ToSlash(dir)
1212 if !strings.Contains(dirSlash, "/vendor/") && !strings.Contains(dirSlash, "/internal/") && !strings.HasSuffix(dirSlash, "/internal") {
1215 // Vendor or internal directory only visible from children of parent.
1216 // That means the path from the current directory to the target directory
1217 // can contain ../vendor or ../internal but not ../foo/vendor or ../foo/internal
1218 // or bar/vendor or bar/internal.
1219 // After stripping all the leading ../, the only okay place to see vendor or internal
1220 // is at the very beginning of the path.
1221 absfile, err := filepath.Abs(filename)
1225 absdir, err := filepath.Abs(dir)
1229 rel, err := filepath.Rel(absfile, absdir)
1233 relSlash := filepath.ToSlash(rel)
1234 if i := strings.LastIndex(relSlash, "../"); i >= 0 {
1235 relSlash = relSlash[i+len("../"):]
1237 return !strings.Contains(relSlash, "/vendor/") && !strings.Contains(relSlash, "/internal/") && !strings.HasSuffix(relSlash, "/internal")
1240 // lastTwoComponents returns at most the last two path components
1241 // of v, using either / or \ as the path separator.
1242 func lastTwoComponents(v string) string {
1244 for i := len(v) - 1; i >= 0; i-- {
1245 if v[i] == '/' || v[i] == '\\' {
1255 type visitFn func(node ast.Node) ast.Visitor
1257 func (fn visitFn) Visit(node ast.Node) ast.Visitor {