golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/internal/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 "bytes" 9 "context" 10 "encoding/json" 11 "fmt" 12 "go/ast" 13 "go/build" 14 "go/parser" 15 "go/token" 16 "go/types" 17 "io/fs" 18 "io/ioutil" 19 "os" 20 "path" 21 "path/filepath" 22 "reflect" 23 "sort" 24 "strconv" 25 "strings" 26 "sync" 27 "unicode" 28 "unicode/utf8" 29 30 "golang.org/x/tools/go/ast/astutil" 31 "golang.org/x/tools/internal/event" 32 "golang.org/x/tools/internal/gocommand" 33 "golang.org/x/tools/internal/gopathwalk" 34 "golang.org/x/tools/internal/stdlib" 35 ) 36 37 // importToGroup is a list of functions which map from an import path to 38 // a group number. 39 var importToGroup = []func(localPrefix, importPath string) (num int, ok bool){ 40 func(localPrefix, importPath string) (num int, ok bool) { 41 if localPrefix == "" { 42 return 43 } 44 for _, p := range strings.Split(localPrefix, ",") { 45 if strings.HasPrefix(importPath, p) || strings.TrimSuffix(p, "/") == importPath { 46 return 3, true 47 } 48 } 49 return 50 }, 51 func(_, importPath string) (num int, ok bool) { 52 if strings.HasPrefix(importPath, "appengine") { 53 return 2, true 54 } 55 return 56 }, 57 func(_, importPath string) (num int, ok bool) { 58 firstComponent := strings.Split(importPath, "/")[0] 59 if strings.Contains(firstComponent, ".") { 60 return 1, true 61 } 62 return 63 }, 64 } 65 66 func importGroup(localPrefix, importPath string) int { 67 for _, fn := range importToGroup { 68 if n, ok := fn(localPrefix, importPath); ok { 69 return n 70 } 71 } 72 return 0 73 } 74 75 type ImportFixType int 76 77 const ( 78 AddImport ImportFixType = iota 79 DeleteImport 80 SetImportName 81 ) 82 83 type ImportFix struct { 84 // StmtInfo represents the import statement this fix will add, remove, or change. 85 StmtInfo ImportInfo 86 // IdentName is the identifier that this fix will add or remove. 87 IdentName string 88 // FixType is the type of fix this is (AddImport, DeleteImport, SetImportName). 89 FixType ImportFixType 90 Relevance float64 // see pkg 91 } 92 93 // An ImportInfo represents a single import statement. 94 type ImportInfo struct { 95 ImportPath string // import path, e.g. "crypto/rand". 96 Name string // import name, e.g. "crand", or "" if none. 97 } 98 99 // A packageInfo represents what's known about a package. 100 type packageInfo struct { 101 name string // real package name, if known. 102 exports map[string]bool // known exports. 103 } 104 105 // parseOtherFiles parses all the Go files in srcDir except filename, including 106 // test files if filename looks like a test. 107 func parseOtherFiles(fset *token.FileSet, srcDir, filename string) []*ast.File { 108 // This could use go/packages but it doesn't buy much, and it fails 109 // with https://golang.org/issue/26296 in LoadFiles mode in some cases. 110 considerTests := strings.HasSuffix(filename, "_test.go") 111 112 fileBase := filepath.Base(filename) 113 packageFileInfos, err := os.ReadDir(srcDir) 114 if err != nil { 115 return nil 116 } 117 118 var files []*ast.File 119 for _, fi := range packageFileInfos { 120 if fi.Name() == fileBase || !strings.HasSuffix(fi.Name(), ".go") { 121 continue 122 } 123 if !considerTests && strings.HasSuffix(fi.Name(), "_test.go") { 124 continue 125 } 126 127 f, err := parser.ParseFile(fset, filepath.Join(srcDir, fi.Name()), nil, 0) 128 if err != nil { 129 continue 130 } 131 132 files = append(files, f) 133 } 134 135 return files 136 } 137 138 // addGlobals puts the names of package vars into the provided map. 139 func addGlobals(f *ast.File, globals map[string]bool) { 140 for _, decl := range f.Decls { 141 genDecl, ok := decl.(*ast.GenDecl) 142 if !ok { 143 continue 144 } 145 146 for _, spec := range genDecl.Specs { 147 valueSpec, ok := spec.(*ast.ValueSpec) 148 if !ok { 149 continue 150 } 151 globals[valueSpec.Names[0].Name] = true 152 } 153 } 154 } 155 156 // collectReferences builds a map of selector expressions, from 157 // left hand side (X) to a set of right hand sides (Sel). 158 func collectReferences(f *ast.File) references { 159 refs := references{} 160 161 var visitor visitFn 162 visitor = func(node ast.Node) ast.Visitor { 163 if node == nil { 164 return visitor 165 } 166 switch v := node.(type) { 167 case *ast.SelectorExpr: 168 xident, ok := v.X.(*ast.Ident) 169 if !ok { 170 break 171 } 172 if xident.Obj != nil { 173 // If the parser can resolve it, it's not a package ref. 174 break 175 } 176 if !ast.IsExported(v.Sel.Name) { 177 // Whatever this is, it's not exported from a package. 178 break 179 } 180 pkgName := xident.Name 181 r := refs[pkgName] 182 if r == nil { 183 r = make(map[string]bool) 184 refs[pkgName] = r 185 } 186 r[v.Sel.Name] = true 187 } 188 return visitor 189 } 190 ast.Walk(visitor, f) 191 return refs 192 } 193 194 // collectImports returns all the imports in f. 195 // Unnamed imports (., _) and "C" are ignored. 196 func collectImports(f *ast.File) []*ImportInfo { 197 var imports []*ImportInfo 198 for _, imp := range f.Imports { 199 var name string 200 if imp.Name != nil { 201 name = imp.Name.Name 202 } 203 if imp.Path.Value == `"C"` || name == "_" || name == "." { 204 continue 205 } 206 path := strings.Trim(imp.Path.Value, `"`) 207 imports = append(imports, &ImportInfo{ 208 Name: name, 209 ImportPath: path, 210 }) 211 } 212 return imports 213 } 214 215 // findMissingImport searches pass's candidates for an import that provides 216 // pkg, containing all of syms. 217 func (p *pass) findMissingImport(pkg string, syms map[string]bool) *ImportInfo { 218 for _, candidate := range p.candidates { 219 pkgInfo, ok := p.knownPackages[candidate.ImportPath] 220 if !ok { 221 continue 222 } 223 if p.importIdentifier(candidate) != pkg { 224 continue 225 } 226 227 allFound := true 228 for right := range syms { 229 if !pkgInfo.exports[right] { 230 allFound = false 231 break 232 } 233 } 234 235 if allFound { 236 return candidate 237 } 238 } 239 return nil 240 } 241 242 // references is set of references found in a Go file. The first map key is the 243 // left hand side of a selector expression, the second key is the right hand 244 // side, and the value should always be true. 245 type references map[string]map[string]bool 246 247 // A pass contains all the inputs and state necessary to fix a file's imports. 248 // It can be modified in some ways during use; see comments below. 249 type pass struct { 250 // Inputs. These must be set before a call to load, and not modified after. 251 fset *token.FileSet // fset used to parse f and its siblings. 252 f *ast.File // the file being fixed. 253 srcDir string // the directory containing f. 254 env *ProcessEnv // the environment to use for go commands, etc. 255 loadRealPackageNames bool // if true, load package names from disk rather than guessing them. 256 otherFiles []*ast.File // sibling files. 257 258 // Intermediate state, generated by load. 259 existingImports map[string][]*ImportInfo 260 allRefs references 261 missingRefs references 262 263 // Inputs to fix. These can be augmented between successive fix calls. 264 lastTry bool // indicates that this is the last call and fix should clean up as best it can. 265 candidates []*ImportInfo // candidate imports in priority order. 266 knownPackages map[string]*packageInfo // information about all known packages. 267 } 268 269 // loadPackageNames saves the package names for everything referenced by imports. 270 func (p *pass) loadPackageNames(imports []*ImportInfo) error { 271 if p.env.Logf != nil { 272 p.env.Logf("loading package names for %v packages", len(imports)) 273 defer func() { 274 p.env.Logf("done loading package names for %v packages", len(imports)) 275 }() 276 } 277 var unknown []string 278 for _, imp := range imports { 279 if _, ok := p.knownPackages[imp.ImportPath]; ok { 280 continue 281 } 282 unknown = append(unknown, imp.ImportPath) 283 } 284 285 resolver, err := p.env.GetResolver() 286 if err != nil { 287 return err 288 } 289 290 names, err := resolver.loadPackageNames(unknown, p.srcDir) 291 if err != nil { 292 return err 293 } 294 295 for path, name := range names { 296 p.knownPackages[path] = &packageInfo{ 297 name: name, 298 exports: map[string]bool{}, 299 } 300 } 301 return nil 302 } 303 304 // if there is a trailing major version, remove it 305 func withoutVersion(nm string) string { 306 if v := path.Base(nm); len(v) > 0 && v[0] == 'v' { 307 if _, err := strconv.Atoi(v[1:]); err == nil { 308 // this is, for instance, called with rand/v2 and returns rand 309 if len(v) < len(nm) { 310 xnm := nm[:len(nm)-len(v)-1] 311 return path.Base(xnm) 312 } 313 } 314 } 315 return nm 316 } 317 318 // importIdentifier returns the identifier that imp will introduce. It will 319 // guess if the package name has not been loaded, e.g. because the source 320 // is not available. 321 func (p *pass) importIdentifier(imp *ImportInfo) string { 322 if imp.Name != "" { 323 return imp.Name 324 } 325 known := p.knownPackages[imp.ImportPath] 326 if known != nil && known.name != "" { 327 return withoutVersion(known.name) 328 } 329 return ImportPathToAssumedName(imp.ImportPath) 330 } 331 332 // load reads in everything necessary to run a pass, and reports whether the 333 // file already has all the imports it needs. It fills in p.missingRefs with the 334 // file's missing symbols, if any, or removes unused imports if not. 335 func (p *pass) load() ([]*ImportFix, bool) { 336 p.knownPackages = map[string]*packageInfo{} 337 p.missingRefs = references{} 338 p.existingImports = map[string][]*ImportInfo{} 339 340 // Load basic information about the file in question. 341 p.allRefs = collectReferences(p.f) 342 343 // Load stuff from other files in the same package: 344 // global variables so we know they don't need resolving, and imports 345 // that we might want to mimic. 346 globals := map[string]bool{} 347 for _, otherFile := range p.otherFiles { 348 // Don't load globals from files that are in the same directory 349 // but a different package. Using them to suggest imports is OK. 350 if p.f.Name.Name == otherFile.Name.Name { 351 addGlobals(otherFile, globals) 352 } 353 p.candidates = append(p.candidates, collectImports(otherFile)...) 354 } 355 356 // Resolve all the import paths we've seen to package names, and store 357 // f's imports by the identifier they introduce. 358 imports := collectImports(p.f) 359 if p.loadRealPackageNames { 360 err := p.loadPackageNames(append(imports, p.candidates...)) 361 if err != nil { 362 if p.env.Logf != nil { 363 p.env.Logf("loading package names: %v", err) 364 } 365 return nil, false 366 } 367 } 368 for _, imp := range imports { 369 p.existingImports[p.importIdentifier(imp)] = append(p.existingImports[p.importIdentifier(imp)], imp) 370 } 371 372 // Find missing references. 373 for left, rights := range p.allRefs { 374 if globals[left] { 375 continue 376 } 377 _, ok := p.existingImports[left] 378 if !ok { 379 p.missingRefs[left] = rights 380 continue 381 } 382 } 383 if len(p.missingRefs) != 0 { 384 return nil, false 385 } 386 387 return p.fix() 388 } 389 390 // fix attempts to satisfy missing imports using p.candidates. If it finds 391 // everything, or if p.lastTry is true, it updates fixes to add the imports it found, 392 // delete anything unused, and update import names, and returns true. 393 func (p *pass) fix() ([]*ImportFix, bool) { 394 // Find missing imports. 395 var selected []*ImportInfo 396 for left, rights := range p.missingRefs { 397 if imp := p.findMissingImport(left, rights); imp != nil { 398 selected = append(selected, imp) 399 } 400 } 401 402 if !p.lastTry && len(selected) != len(p.missingRefs) { 403 return nil, false 404 } 405 406 // Found everything, or giving up. Add the new imports and remove any unused. 407 var fixes []*ImportFix 408 for _, identifierImports := range p.existingImports { 409 for _, imp := range identifierImports { 410 // We deliberately ignore globals here, because we can't be sure 411 // they're in the same package. People do things like put multiple 412 // main packages in the same directory, and we don't want to 413 // remove imports if they happen to have the same name as a var in 414 // a different package. 415 if _, ok := p.allRefs[p.importIdentifier(imp)]; !ok { 416 fixes = append(fixes, &ImportFix{ 417 StmtInfo: *imp, 418 IdentName: p.importIdentifier(imp), 419 FixType: DeleteImport, 420 }) 421 continue 422 } 423 424 // An existing import may need to update its import name to be correct. 425 if name := p.importSpecName(imp); name != imp.Name { 426 fixes = append(fixes, &ImportFix{ 427 StmtInfo: ImportInfo{ 428 Name: name, 429 ImportPath: imp.ImportPath, 430 }, 431 IdentName: p.importIdentifier(imp), 432 FixType: SetImportName, 433 }) 434 } 435 } 436 } 437 // Collecting fixes involved map iteration, so sort for stability. See 438 // golang/go#59976. 439 sortFixes(fixes) 440 441 // collect selected fixes in a separate slice, so that it can be sorted 442 // separately. Note that these fixes must occur after fixes to existing 443 // imports. TODO(rfindley): figure out why. 444 var selectedFixes []*ImportFix 445 for _, imp := range selected { 446 selectedFixes = append(selectedFixes, &ImportFix{ 447 StmtInfo: ImportInfo{ 448 Name: p.importSpecName(imp), 449 ImportPath: imp.ImportPath, 450 }, 451 IdentName: p.importIdentifier(imp), 452 FixType: AddImport, 453 }) 454 } 455 sortFixes(selectedFixes) 456 457 return append(fixes, selectedFixes...), true 458 } 459 460 func sortFixes(fixes []*ImportFix) { 461 sort.Slice(fixes, func(i, j int) bool { 462 fi, fj := fixes[i], fixes[j] 463 if fi.StmtInfo.ImportPath != fj.StmtInfo.ImportPath { 464 return fi.StmtInfo.ImportPath < fj.StmtInfo.ImportPath 465 } 466 if fi.StmtInfo.Name != fj.StmtInfo.Name { 467 return fi.StmtInfo.Name < fj.StmtInfo.Name 468 } 469 if fi.IdentName != fj.IdentName { 470 return fi.IdentName < fj.IdentName 471 } 472 return fi.FixType < fj.FixType 473 }) 474 } 475 476 // importSpecName gets the import name of imp in the import spec. 477 // 478 // When the import identifier matches the assumed import name, the import name does 479 // not appear in the import spec. 480 func (p *pass) importSpecName(imp *ImportInfo) string { 481 // If we did not load the real package names, or the name is already set, 482 // we just return the existing name. 483 if !p.loadRealPackageNames || imp.Name != "" { 484 return imp.Name 485 } 486 487 ident := p.importIdentifier(imp) 488 if ident == ImportPathToAssumedName(imp.ImportPath) { 489 return "" // ident not needed since the assumed and real names are the same. 490 } 491 return ident 492 } 493 494 // apply will perform the fixes on f in order. 495 func apply(fset *token.FileSet, f *ast.File, fixes []*ImportFix) { 496 for _, fix := range fixes { 497 switch fix.FixType { 498 case DeleteImport: 499 astutil.DeleteNamedImport(fset, f, fix.StmtInfo.Name, fix.StmtInfo.ImportPath) 500 case AddImport: 501 astutil.AddNamedImport(fset, f, fix.StmtInfo.Name, fix.StmtInfo.ImportPath) 502 case SetImportName: 503 // Find the matching import path and change the name. 504 for _, spec := range f.Imports { 505 path := strings.Trim(spec.Path.Value, `"`) 506 if path == fix.StmtInfo.ImportPath { 507 spec.Name = &ast.Ident{ 508 Name: fix.StmtInfo.Name, 509 NamePos: spec.Pos(), 510 } 511 } 512 } 513 } 514 } 515 } 516 517 // assumeSiblingImportsValid assumes that siblings' use of packages is valid, 518 // adding the exports they use. 519 func (p *pass) assumeSiblingImportsValid() { 520 for _, f := range p.otherFiles { 521 refs := collectReferences(f) 522 imports := collectImports(f) 523 importsByName := map[string]*ImportInfo{} 524 for _, imp := range imports { 525 importsByName[p.importIdentifier(imp)] = imp 526 } 527 for left, rights := range refs { 528 if imp, ok := importsByName[left]; ok { 529 if m, ok := stdlib.PackageSymbols[imp.ImportPath]; ok { 530 // We have the stdlib in memory; no need to guess. 531 rights = symbolNameSet(m) 532 } 533 p.addCandidate(imp, &packageInfo{ 534 // no name; we already know it. 535 exports: rights, 536 }) 537 } 538 } 539 } 540 } 541 542 // addCandidate adds a candidate import to p, and merges in the information 543 // in pkg. 544 func (p *pass) addCandidate(imp *ImportInfo, pkg *packageInfo) { 545 p.candidates = append(p.candidates, imp) 546 if existing, ok := p.knownPackages[imp.ImportPath]; ok { 547 if existing.name == "" { 548 existing.name = pkg.name 549 } 550 for export := range pkg.exports { 551 existing.exports[export] = true 552 } 553 } else { 554 p.knownPackages[imp.ImportPath] = pkg 555 } 556 } 557 558 // fixImports adds and removes imports from f so that all its references are 559 // satisfied and there are no unused imports. 560 // 561 // This is declared as a variable rather than a function so goimports can 562 // easily be extended by adding a file with an init function. 563 var fixImports = fixImportsDefault 564 565 func fixImportsDefault(fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv) error { 566 fixes, err := getFixes(context.Background(), fset, f, filename, env) 567 if err != nil { 568 return err 569 } 570 apply(fset, f, fixes) 571 return err 572 } 573 574 // getFixes gets the import fixes that need to be made to f in order to fix the imports. 575 // It does not modify the ast. 576 func getFixes(ctx context.Context, fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv) ([]*ImportFix, error) { 577 abs, err := filepath.Abs(filename) 578 if err != nil { 579 return nil, err 580 } 581 srcDir := filepath.Dir(abs) 582 if env.Logf != nil { 583 env.Logf("fixImports(filename=%q), abs=%q, srcDir=%q ...", filename, abs, srcDir) 584 } 585 586 // First pass: looking only at f, and using the naive algorithm to 587 // derive package names from import paths, see if the file is already 588 // complete. We can't add any imports yet, because we don't know 589 // if missing references are actually package vars. 590 p := &pass{fset: fset, f: f, srcDir: srcDir, env: env} 591 if fixes, done := p.load(); done { 592 return fixes, nil 593 } 594 595 otherFiles := parseOtherFiles(fset, srcDir, filename) 596 597 // Second pass: add information from other files in the same package, 598 // like their package vars and imports. 599 p.otherFiles = otherFiles 600 if fixes, done := p.load(); done { 601 return fixes, nil 602 } 603 604 // Now we can try adding imports from the stdlib. 605 p.assumeSiblingImportsValid() 606 addStdlibCandidates(p, p.missingRefs) 607 if fixes, done := p.fix(); done { 608 return fixes, nil 609 } 610 611 // Third pass: get real package names where we had previously used 612 // the naive algorithm. 613 p = &pass{fset: fset, f: f, srcDir: srcDir, env: env} 614 p.loadRealPackageNames = true 615 p.otherFiles = otherFiles 616 if fixes, done := p.load(); done { 617 return fixes, nil 618 } 619 620 if err := addStdlibCandidates(p, p.missingRefs); err != nil { 621 return nil, err 622 } 623 p.assumeSiblingImportsValid() 624 if fixes, done := p.fix(); done { 625 return fixes, nil 626 } 627 628 // Go look for candidates in $GOPATH, etc. We don't necessarily load 629 // the real exports of sibling imports, so keep assuming their contents. 630 if err := addExternalCandidates(ctx, p, p.missingRefs, filename); err != nil { 631 return nil, err 632 } 633 634 p.lastTry = true 635 fixes, _ := p.fix() 636 return fixes, nil 637 } 638 639 // MaxRelevance is the highest relevance, used for the standard library. 640 // Chosen arbitrarily to match pre-existing gopls code. 641 const MaxRelevance = 7.0 642 643 // getCandidatePkgs works with the passed callback to find all acceptable packages. 644 // It deduplicates by import path, and uses a cached stdlib rather than reading 645 // from disk. 646 func getCandidatePkgs(ctx context.Context, wrappedCallback *scanCallback, filename, filePkg string, env *ProcessEnv) error { 647 notSelf := func(p *pkg) bool { 648 return p.packageName != filePkg || p.dir != filepath.Dir(filename) 649 } 650 goenv, err := env.goEnv() 651 if err != nil { 652 return err 653 } 654 655 var mu sync.Mutex // to guard asynchronous access to dupCheck 656 dupCheck := map[string]struct{}{} 657 658 // Start off with the standard library. 659 for importPath, symbols := range stdlib.PackageSymbols { 660 p := &pkg{ 661 dir: filepath.Join(goenv["GOROOT"], "src", importPath), 662 importPathShort: importPath, 663 packageName: path.Base(importPath), 664 relevance: MaxRelevance, 665 } 666 dupCheck[importPath] = struct{}{} 667 if notSelf(p) && wrappedCallback.dirFound(p) && wrappedCallback.packageNameLoaded(p) { 668 var exports []stdlib.Symbol 669 for _, sym := range symbols { 670 switch sym.Kind { 671 case stdlib.Func, stdlib.Type, stdlib.Var, stdlib.Const: 672 exports = append(exports, sym) 673 } 674 } 675 wrappedCallback.exportsLoaded(p, exports) 676 } 677 } 678 679 scanFilter := &scanCallback{ 680 rootFound: func(root gopathwalk.Root) bool { 681 // Exclude goroot results -- getting them is relatively expensive, not cached, 682 // and generally redundant with the in-memory version. 683 return root.Type != gopathwalk.RootGOROOT && wrappedCallback.rootFound(root) 684 }, 685 dirFound: wrappedCallback.dirFound, 686 packageNameLoaded: func(pkg *pkg) bool { 687 mu.Lock() 688 defer mu.Unlock() 689 if _, ok := dupCheck[pkg.importPathShort]; ok { 690 return false 691 } 692 dupCheck[pkg.importPathShort] = struct{}{} 693 return notSelf(pkg) && wrappedCallback.packageNameLoaded(pkg) 694 }, 695 exportsLoaded: func(pkg *pkg, exports []stdlib.Symbol) { 696 // If we're an x_test, load the package under test's test variant. 697 if strings.HasSuffix(filePkg, "_test") && pkg.dir == filepath.Dir(filename) { 698 var err error 699 _, exports, err = loadExportsFromFiles(ctx, env, pkg.dir, true) 700 if err != nil { 701 return 702 } 703 } 704 wrappedCallback.exportsLoaded(pkg, exports) 705 }, 706 } 707 resolver, err := env.GetResolver() 708 if err != nil { 709 return err 710 } 711 return resolver.scan(ctx, scanFilter) 712 } 713 714 func ScoreImportPaths(ctx context.Context, env *ProcessEnv, paths []string) (map[string]float64, error) { 715 result := make(map[string]float64) 716 resolver, err := env.GetResolver() 717 if err != nil { 718 return nil, err 719 } 720 for _, path := range paths { 721 result[path] = resolver.scoreImportPath(ctx, path) 722 } 723 return result, nil 724 } 725 726 func PrimeCache(ctx context.Context, resolver Resolver) error { 727 // Fully scan the disk for directories, but don't actually read any Go files. 728 callback := &scanCallback{ 729 rootFound: func(root gopathwalk.Root) bool { 730 // See getCandidatePkgs: walking GOROOT is apparently expensive and 731 // unnecessary. 732 return root.Type != gopathwalk.RootGOROOT 733 }, 734 dirFound: func(pkg *pkg) bool { 735 return false 736 }, 737 // packageNameLoaded and exportsLoaded must never be called. 738 } 739 740 return resolver.scan(ctx, callback) 741 } 742 743 func candidateImportName(pkg *pkg) string { 744 if ImportPathToAssumedName(pkg.importPathShort) != pkg.packageName { 745 return pkg.packageName 746 } 747 return "" 748 } 749 750 // GetAllCandidates calls wrapped for each package whose name starts with 751 // searchPrefix, and can be imported from filename with the package name filePkg. 752 // 753 // Beware that the wrapped function may be called multiple times concurrently. 754 // TODO(adonovan): encapsulate the concurrency. 755 func GetAllCandidates(ctx context.Context, wrapped func(ImportFix), searchPrefix, filename, filePkg string, env *ProcessEnv) error { 756 callback := &scanCallback{ 757 rootFound: func(gopathwalk.Root) bool { 758 return true 759 }, 760 dirFound: func(pkg *pkg) bool { 761 if !canUse(filename, pkg.dir) { 762 return false 763 } 764 // Try the assumed package name first, then a simpler path match 765 // in case of packages named vN, which are not uncommon. 766 return strings.HasPrefix(ImportPathToAssumedName(pkg.importPathShort), searchPrefix) || 767 strings.HasPrefix(path.Base(pkg.importPathShort), searchPrefix) 768 }, 769 packageNameLoaded: func(pkg *pkg) bool { 770 if !strings.HasPrefix(pkg.packageName, searchPrefix) { 771 return false 772 } 773 wrapped(ImportFix{ 774 StmtInfo: ImportInfo{ 775 ImportPath: pkg.importPathShort, 776 Name: candidateImportName(pkg), 777 }, 778 IdentName: pkg.packageName, 779 FixType: AddImport, 780 Relevance: pkg.relevance, 781 }) 782 return false 783 }, 784 } 785 return getCandidatePkgs(ctx, callback, filename, filePkg, env) 786 } 787 788 // GetImportPaths calls wrapped for each package whose import path starts with 789 // searchPrefix, and can be imported from filename with the package name filePkg. 790 func GetImportPaths(ctx context.Context, wrapped func(ImportFix), searchPrefix, filename, filePkg string, env *ProcessEnv) error { 791 callback := &scanCallback{ 792 rootFound: func(gopathwalk.Root) bool { 793 return true 794 }, 795 dirFound: func(pkg *pkg) bool { 796 if !canUse(filename, pkg.dir) { 797 return false 798 } 799 return strings.HasPrefix(pkg.importPathShort, searchPrefix) 800 }, 801 packageNameLoaded: func(pkg *pkg) bool { 802 wrapped(ImportFix{ 803 StmtInfo: ImportInfo{ 804 ImportPath: pkg.importPathShort, 805 Name: candidateImportName(pkg), 806 }, 807 IdentName: pkg.packageName, 808 FixType: AddImport, 809 Relevance: pkg.relevance, 810 }) 811 return false 812 }, 813 } 814 return getCandidatePkgs(ctx, callback, filename, filePkg, env) 815 } 816 817 // A PackageExport is a package and its exports. 818 type PackageExport struct { 819 Fix *ImportFix 820 Exports []stdlib.Symbol 821 } 822 823 // GetPackageExports returns all known packages with name pkg and their exports. 824 func GetPackageExports(ctx context.Context, wrapped func(PackageExport), searchPkg, filename, filePkg string, env *ProcessEnv) error { 825 callback := &scanCallback{ 826 rootFound: func(gopathwalk.Root) bool { 827 return true 828 }, 829 dirFound: func(pkg *pkg) bool { 830 return pkgIsCandidate(filename, references{searchPkg: nil}, pkg) 831 }, 832 packageNameLoaded: func(pkg *pkg) bool { 833 return pkg.packageName == searchPkg 834 }, 835 exportsLoaded: func(pkg *pkg, exports []stdlib.Symbol) { 836 sortSymbols(exports) 837 wrapped(PackageExport{ 838 Fix: &ImportFix{ 839 StmtInfo: ImportInfo{ 840 ImportPath: pkg.importPathShort, 841 Name: candidateImportName(pkg), 842 }, 843 IdentName: pkg.packageName, 844 FixType: AddImport, 845 Relevance: pkg.relevance, 846 }, 847 Exports: exports, 848 }) 849 }, 850 } 851 return getCandidatePkgs(ctx, callback, filename, filePkg, env) 852 } 853 854 // TODO(rfindley): we should depend on GOOS and GOARCH, to provide accurate 855 // imports when doing cross-platform development. 856 var requiredGoEnvVars = []string{ 857 "GO111MODULE", 858 "GOFLAGS", 859 "GOINSECURE", 860 "GOMOD", 861 "GOMODCACHE", 862 "GONOPROXY", 863 "GONOSUMDB", 864 "GOPATH", 865 "GOPROXY", 866 "GOROOT", 867 "GOSUMDB", 868 "GOWORK", 869 } 870 871 // ProcessEnv contains environment variables and settings that affect the use of 872 // the go command, the go/build package, etc. 873 // 874 // ...a ProcessEnv *also* overwrites its Env along with derived state in the 875 // form of the resolver. And because it is lazily initialized, an env may just 876 // be broken and unusable, but there is no way for the caller to detect that: 877 // all queries will just fail. 878 // 879 // TODO(rfindley): refactor this package so that this type (perhaps renamed to 880 // just Env or Config) is an immutable configuration struct, to be exchanged 881 // for an initialized object via a constructor that returns an error. Perhaps 882 // the signature should be `func NewResolver(*Env) (*Resolver, error)`, where 883 // resolver is a concrete type used for resolving imports. Via this 884 // refactoring, we can avoid the need to call ProcessEnv.init and 885 // ProcessEnv.GoEnv everywhere, and implicitly fix all the places where this 886 // these are misused. Also, we'd delegate the caller the decision of how to 887 // handle a broken environment. 888 type ProcessEnv struct { 889 GocmdRunner *gocommand.Runner 890 891 BuildFlags []string 892 ModFlag string 893 894 // SkipPathInScan returns true if the path should be skipped from scans of 895 // the RootCurrentModule root type. The function argument is a clean, 896 // absolute path. 897 SkipPathInScan func(string) bool 898 899 // Env overrides the OS environment, and can be used to specify 900 // GOPROXY, GO111MODULE, etc. PATH cannot be set here, because 901 // exec.Command will not honor it. 902 // Specifying all of requiredGoEnvVars avoids a call to `go env`. 903 Env map[string]string 904 905 WorkingDir string 906 907 // If Logf is non-nil, debug logging is enabled through this function. 908 Logf func(format string, args ...interface{}) 909 910 // If set, ModCache holds a shared cache of directory info to use across 911 // multiple ProcessEnvs. 912 ModCache *DirInfoCache 913 914 initialized bool // see TODO above 915 916 // resolver and resolverErr are lazily evaluated (see GetResolver). 917 // This is unclean, but see the big TODO in the docstring for ProcessEnv 918 // above: for now, we can't be sure that the ProcessEnv is fully initialized. 919 resolver Resolver 920 resolverErr error 921 } 922 923 func (e *ProcessEnv) goEnv() (map[string]string, error) { 924 if err := e.init(); err != nil { 925 return nil, err 926 } 927 return e.Env, nil 928 } 929 930 func (e *ProcessEnv) matchFile(dir, name string) (bool, error) { 931 bctx, err := e.buildContext() 932 if err != nil { 933 return false, err 934 } 935 return bctx.MatchFile(dir, name) 936 } 937 938 // CopyConfig copies the env's configuration into a new env. 939 func (e *ProcessEnv) CopyConfig() *ProcessEnv { 940 copy := &ProcessEnv{ 941 GocmdRunner: e.GocmdRunner, 942 initialized: e.initialized, 943 BuildFlags: e.BuildFlags, 944 Logf: e.Logf, 945 WorkingDir: e.WorkingDir, 946 resolver: nil, 947 Env: map[string]string{}, 948 } 949 for k, v := range e.Env { 950 copy.Env[k] = v 951 } 952 return copy 953 } 954 955 func (e *ProcessEnv) init() error { 956 if e.initialized { 957 return nil 958 } 959 960 foundAllRequired := true 961 for _, k := range requiredGoEnvVars { 962 if _, ok := e.Env[k]; !ok { 963 foundAllRequired = false 964 break 965 } 966 } 967 if foundAllRequired { 968 e.initialized = true 969 return nil 970 } 971 972 if e.Env == nil { 973 e.Env = map[string]string{} 974 } 975 976 goEnv := map[string]string{} 977 stdout, err := e.invokeGo(context.TODO(), "env", append([]string{"-json"}, requiredGoEnvVars...)...) 978 if err != nil { 979 return err 980 } 981 if err := json.Unmarshal(stdout.Bytes(), &goEnv); err != nil { 982 return err 983 } 984 for k, v := range goEnv { 985 e.Env[k] = v 986 } 987 e.initialized = true 988 return nil 989 } 990 991 func (e *ProcessEnv) env() []string { 992 var env []string // the gocommand package will prepend os.Environ. 993 for k, v := range e.Env { 994 env = append(env, k+"="+v) 995 } 996 return env 997 } 998 999 func (e *ProcessEnv) GetResolver() (Resolver, error) { 1000 if err := e.init(); err != nil { 1001 return nil, err 1002 } 1003 1004 if e.resolver == nil && e.resolverErr == nil { 1005 // TODO(rfindley): we should only use a gopathResolver here if the working 1006 // directory is actually *in* GOPATH. (I seem to recall an open gopls issue 1007 // for this behavior, but I can't find it). 1008 // 1009 // For gopls, we can optionally explicitly choose a resolver type, since we 1010 // already know the view type. 1011 if len(e.Env["GOMOD"]) == 0 && len(e.Env["GOWORK"]) == 0 { 1012 e.resolver = newGopathResolver(e) 1013 } else if r, err := newModuleResolver(e, e.ModCache); err != nil { 1014 e.resolverErr = err 1015 } else { 1016 e.resolver = Resolver(r) 1017 } 1018 } 1019 1020 return e.resolver, e.resolverErr 1021 } 1022 1023 // buildContext returns the build.Context to use for matching files. 1024 // 1025 // TODO(rfindley): support dynamic GOOS, GOARCH here, when doing cross-platform 1026 // development. 1027 func (e *ProcessEnv) buildContext() (*build.Context, error) { 1028 ctx := build.Default 1029 goenv, err := e.goEnv() 1030 if err != nil { 1031 return nil, err 1032 } 1033 ctx.GOROOT = goenv["GOROOT"] 1034 ctx.GOPATH = goenv["GOPATH"] 1035 1036 // As of Go 1.14, build.Context has a Dir field 1037 // (see golang.org/issue/34860). 1038 // Populate it only if present. 1039 rc := reflect.ValueOf(&ctx).Elem() 1040 dir := rc.FieldByName("Dir") 1041 if dir.IsValid() && dir.Kind() == reflect.String { 1042 dir.SetString(e.WorkingDir) 1043 } 1044 1045 // Since Go 1.11, go/build.Context.Import may invoke 'go list' depending on 1046 // the value in GO111MODULE in the process's environment. We always want to 1047 // run in GOPATH mode when calling Import, so we need to prevent this from 1048 // happening. In Go 1.16, GO111MODULE defaults to "on", so this problem comes 1049 // up more frequently. 1050 // 1051 // HACK: setting any of the Context I/O hooks prevents Import from invoking 1052 // 'go list', regardless of GO111MODULE. This is undocumented, but it's 1053 // unlikely to change before GOPATH support is removed. 1054 ctx.ReadDir = ioutil.ReadDir 1055 1056 return &ctx, nil 1057 } 1058 1059 func (e *ProcessEnv) invokeGo(ctx context.Context, verb string, args ...string) (*bytes.Buffer, error) { 1060 inv := gocommand.Invocation{ 1061 Verb: verb, 1062 Args: args, 1063 BuildFlags: e.BuildFlags, 1064 Env: e.env(), 1065 Logf: e.Logf, 1066 WorkingDir: e.WorkingDir, 1067 } 1068 return e.GocmdRunner.Run(ctx, inv) 1069 } 1070 1071 func addStdlibCandidates(pass *pass, refs references) error { 1072 goenv, err := pass.env.goEnv() 1073 if err != nil { 1074 return err 1075 } 1076 localbase := func(nm string) string { 1077 ans := path.Base(nm) 1078 if ans[0] == 'v' { 1079 // this is called, for instance, with math/rand/v2 and returns rand/v2 1080 if _, err := strconv.Atoi(ans[1:]); err == nil { 1081 ix := strings.LastIndex(nm, ans) 1082 more := path.Base(nm[:ix]) 1083 ans = path.Join(more, ans) 1084 } 1085 } 1086 return ans 1087 } 1088 add := func(pkg string) { 1089 // Prevent self-imports. 1090 if path.Base(pkg) == pass.f.Name.Name && filepath.Join(goenv["GOROOT"], "src", pkg) == pass.srcDir { 1091 return 1092 } 1093 exports := symbolNameSet(stdlib.PackageSymbols[pkg]) 1094 pass.addCandidate( 1095 &ImportInfo{ImportPath: pkg}, 1096 &packageInfo{name: localbase(pkg), exports: exports}) 1097 } 1098 for left := range refs { 1099 if left == "rand" { 1100 // Make sure we try crypto/rand before any version of math/rand as both have Int() 1101 // and our policy is to recommend crypto 1102 add("crypto/rand") 1103 // if the user's no later than go1.21, this should be "math/rand" 1104 // but we have no way of figuring out what the user is using 1105 // TODO: investigate using the toolchain version to disambiguate in the stdlib 1106 add("math/rand/v2") 1107 continue 1108 } 1109 for importPath := range stdlib.PackageSymbols { 1110 if path.Base(importPath) == left { 1111 add(importPath) 1112 } 1113 } 1114 } 1115 return nil 1116 } 1117 1118 // A Resolver does the build-system-specific parts of goimports. 1119 type Resolver interface { 1120 // loadPackageNames loads the package names in importPaths. 1121 loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) 1122 1123 // scan works with callback to search for packages. See scanCallback for details. 1124 scan(ctx context.Context, callback *scanCallback) error 1125 1126 // loadExports returns the set of exported symbols in the package at dir. 1127 // loadExports may be called concurrently. 1128 loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []stdlib.Symbol, error) 1129 1130 // scoreImportPath returns the relevance for an import path. 1131 scoreImportPath(ctx context.Context, path string) float64 1132 1133 // ClearForNewScan returns a new Resolver based on the receiver that has 1134 // cleared its internal caches of directory contents. 1135 // 1136 // The new resolver should be primed and then set via 1137 // [ProcessEnv.UpdateResolver]. 1138 ClearForNewScan() Resolver 1139 } 1140 1141 // A scanCallback controls a call to scan and receives its results. 1142 // In general, minor errors will be silently discarded; a user should not 1143 // expect to receive a full series of calls for everything. 1144 type scanCallback struct { 1145 // rootFound is called before scanning a new root dir. If it returns true, 1146 // the root will be scanned. Returning false will not necessarily prevent 1147 // directories from that root making it to dirFound. 1148 rootFound func(gopathwalk.Root) bool 1149 // dirFound is called when a directory is found that is possibly a Go package. 1150 // pkg will be populated with everything except packageName. 1151 // If it returns true, the package's name will be loaded. 1152 dirFound func(pkg *pkg) bool 1153 // packageNameLoaded is called when a package is found and its name is loaded. 1154 // If it returns true, the package's exports will be loaded. 1155 packageNameLoaded func(pkg *pkg) bool 1156 // exportsLoaded is called when a package's exports have been loaded. 1157 exportsLoaded func(pkg *pkg, exports []stdlib.Symbol) 1158 } 1159 1160 func addExternalCandidates(ctx context.Context, pass *pass, refs references, filename string) error { 1161 ctx, done := event.Start(ctx, "imports.addExternalCandidates") 1162 defer done() 1163 1164 var mu sync.Mutex 1165 found := make(map[string][]pkgDistance) 1166 callback := &scanCallback{ 1167 rootFound: func(gopathwalk.Root) bool { 1168 return true // We want everything. 1169 }, 1170 dirFound: func(pkg *pkg) bool { 1171 return pkgIsCandidate(filename, refs, pkg) 1172 }, 1173 packageNameLoaded: func(pkg *pkg) bool { 1174 if _, want := refs[pkg.packageName]; !want { 1175 return false 1176 } 1177 if pkg.dir == pass.srcDir && pass.f.Name.Name == pkg.packageName { 1178 // The candidate is in the same directory and has the 1179 // same package name. Don't try to import ourselves. 1180 return false 1181 } 1182 if !canUse(filename, pkg.dir) { 1183 return false 1184 } 1185 mu.Lock() 1186 defer mu.Unlock() 1187 found[pkg.packageName] = append(found[pkg.packageName], pkgDistance{pkg, distance(pass.srcDir, pkg.dir)}) 1188 return false // We'll do our own loading after we sort. 1189 }, 1190 } 1191 resolver, err := pass.env.GetResolver() 1192 if err != nil { 1193 return err 1194 } 1195 if err = resolver.scan(context.Background(), callback); err != nil { 1196 return err 1197 } 1198 1199 // Search for imports matching potential package references. 1200 type result struct { 1201 imp *ImportInfo 1202 pkg *packageInfo 1203 } 1204 results := make(chan result, len(refs)) 1205 1206 ctx, cancel := context.WithCancel(context.TODO()) 1207 var wg sync.WaitGroup 1208 defer func() { 1209 cancel() 1210 wg.Wait() 1211 }() 1212 var ( 1213 firstErr error 1214 firstErrOnce sync.Once 1215 ) 1216 for pkgName, symbols := range refs { 1217 wg.Add(1) 1218 go func(pkgName string, symbols map[string]bool) { 1219 defer wg.Done() 1220 1221 found, err := findImport(ctx, pass, found[pkgName], pkgName, symbols) 1222 1223 if err != nil { 1224 firstErrOnce.Do(func() { 1225 firstErr = err 1226 cancel() 1227 }) 1228 return 1229 } 1230 1231 if found == nil { 1232 return // No matching package. 1233 } 1234 1235 imp := &ImportInfo{ 1236 ImportPath: found.importPathShort, 1237 } 1238 1239 pkg := &packageInfo{ 1240 name: pkgName, 1241 exports: symbols, 1242 } 1243 results <- result{imp, pkg} 1244 }(pkgName, symbols) 1245 } 1246 go func() { 1247 wg.Wait() 1248 close(results) 1249 }() 1250 1251 for result := range results { 1252 // Don't offer completions that would shadow predeclared 1253 // names, such as github.com/coreos/etcd/error. 1254 if types.Universe.Lookup(result.pkg.name) != nil { // predeclared 1255 // Ideally we would skip this candidate only 1256 // if the predeclared name is actually 1257 // referenced by the file, but that's a lot 1258 // trickier to compute and would still create 1259 // an import that is likely to surprise the 1260 // user before long. 1261 continue 1262 } 1263 pass.addCandidate(result.imp, result.pkg) 1264 } 1265 return firstErr 1266 } 1267 1268 // notIdentifier reports whether ch is an invalid identifier character. 1269 func notIdentifier(ch rune) bool { 1270 return !('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || 1271 '0' <= ch && ch <= '9' || 1272 ch == '_' || 1273 ch >= utf8.RuneSelf && (unicode.IsLetter(ch) || unicode.IsDigit(ch))) 1274 } 1275 1276 // ImportPathToAssumedName returns the assumed package name of an import path. 1277 // It does this using only string parsing of the import path. 1278 // It picks the last element of the path that does not look like a major 1279 // version, and then picks the valid identifier off the start of that element. 1280 // It is used to determine if a local rename should be added to an import for 1281 // clarity. 1282 // This function could be moved to a standard package and exported if we want 1283 // for use in other tools. 1284 func ImportPathToAssumedName(importPath string) string { 1285 base := path.Base(importPath) 1286 if strings.HasPrefix(base, "v") { 1287 if _, err := strconv.Atoi(base[1:]); err == nil { 1288 dir := path.Dir(importPath) 1289 if dir != "." { 1290 base = path.Base(dir) 1291 } 1292 } 1293 } 1294 base = strings.TrimPrefix(base, "go-") 1295 if i := strings.IndexFunc(base, notIdentifier); i >= 0 { 1296 base = base[:i] 1297 } 1298 return base 1299 } 1300 1301 // gopathResolver implements resolver for GOPATH workspaces. 1302 type gopathResolver struct { 1303 env *ProcessEnv 1304 walked bool 1305 cache *DirInfoCache 1306 scanSema chan struct{} // scanSema prevents concurrent scans. 1307 } 1308 1309 func newGopathResolver(env *ProcessEnv) *gopathResolver { 1310 r := &gopathResolver{ 1311 env: env, 1312 cache: NewDirInfoCache(), 1313 scanSema: make(chan struct{}, 1), 1314 } 1315 r.scanSema <- struct{}{} 1316 return r 1317 } 1318 1319 func (r *gopathResolver) ClearForNewScan() Resolver { 1320 return newGopathResolver(r.env) 1321 } 1322 1323 func (r *gopathResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) { 1324 names := map[string]string{} 1325 bctx, err := r.env.buildContext() 1326 if err != nil { 1327 return nil, err 1328 } 1329 for _, path := range importPaths { 1330 names[path] = importPathToName(bctx, path, srcDir) 1331 } 1332 return names, nil 1333 } 1334 1335 // importPathToName finds out the actual package name, as declared in its .go files. 1336 func importPathToName(bctx *build.Context, importPath, srcDir string) string { 1337 // Fast path for standard library without going to disk. 1338 if stdlib.HasPackage(importPath) { 1339 return path.Base(importPath) // stdlib packages always match their paths. 1340 } 1341 1342 buildPkg, err := bctx.Import(importPath, srcDir, build.FindOnly) 1343 if err != nil { 1344 return "" 1345 } 1346 pkgName, err := packageDirToName(buildPkg.Dir) 1347 if err != nil { 1348 return "" 1349 } 1350 return pkgName 1351 } 1352 1353 // packageDirToName is a faster version of build.Import if 1354 // the only thing desired is the package name. Given a directory, 1355 // packageDirToName then only parses one file in the package, 1356 // trusting that the files in the directory are consistent. 1357 func packageDirToName(dir string) (packageName string, err error) { 1358 d, err := os.Open(dir) 1359 if err != nil { 1360 return "", err 1361 } 1362 names, err := d.Readdirnames(-1) 1363 d.Close() 1364 if err != nil { 1365 return "", err 1366 } 1367 sort.Strings(names) // to have predictable behavior 1368 var lastErr error 1369 var nfile int 1370 for _, name := range names { 1371 if !strings.HasSuffix(name, ".go") { 1372 continue 1373 } 1374 if strings.HasSuffix(name, "_test.go") { 1375 continue 1376 } 1377 nfile++ 1378 fullFile := filepath.Join(dir, name) 1379 1380 fset := token.NewFileSet() 1381 f, err := parser.ParseFile(fset, fullFile, nil, parser.PackageClauseOnly) 1382 if err != nil { 1383 lastErr = err 1384 continue 1385 } 1386 pkgName := f.Name.Name 1387 if pkgName == "documentation" { 1388 // Special case from go/build.ImportDir, not 1389 // handled by ctx.MatchFile. 1390 continue 1391 } 1392 if pkgName == "main" { 1393 // Also skip package main, assuming it's a +build ignore generator or example. 1394 // Since you can't import a package main anyway, there's no harm here. 1395 continue 1396 } 1397 return pkgName, nil 1398 } 1399 if lastErr != nil { 1400 return "", lastErr 1401 } 1402 return "", fmt.Errorf("no importable package found in %d Go files", nfile) 1403 } 1404 1405 type pkg struct { 1406 dir string // absolute file path to pkg directory ("/usr/lib/go/src/net/http") 1407 importPathShort string // vendorless import path ("net/http", "a/b") 1408 packageName string // package name loaded from source if requested 1409 relevance float64 // a weakly-defined score of how relevant a package is. 0 is most relevant. 1410 } 1411 1412 type pkgDistance struct { 1413 pkg *pkg 1414 distance int // relative distance to target 1415 } 1416 1417 // byDistanceOrImportPathShortLength sorts by relative distance breaking ties 1418 // on the short import path length and then the import string itself. 1419 type byDistanceOrImportPathShortLength []pkgDistance 1420 1421 func (s byDistanceOrImportPathShortLength) Len() int { return len(s) } 1422 func (s byDistanceOrImportPathShortLength) Less(i, j int) bool { 1423 di, dj := s[i].distance, s[j].distance 1424 if di == -1 { 1425 return false 1426 } 1427 if dj == -1 { 1428 return true 1429 } 1430 if di != dj { 1431 return di < dj 1432 } 1433 1434 vi, vj := s[i].pkg.importPathShort, s[j].pkg.importPathShort 1435 if len(vi) != len(vj) { 1436 return len(vi) < len(vj) 1437 } 1438 return vi < vj 1439 } 1440 func (s byDistanceOrImportPathShortLength) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 1441 1442 func distance(basepath, targetpath string) int { 1443 p, err := filepath.Rel(basepath, targetpath) 1444 if err != nil { 1445 return -1 1446 } 1447 if p == "." { 1448 return 0 1449 } 1450 return strings.Count(p, string(filepath.Separator)) + 1 1451 } 1452 1453 func (r *gopathResolver) scan(ctx context.Context, callback *scanCallback) error { 1454 add := func(root gopathwalk.Root, dir string) { 1455 // We assume cached directories have not changed. We can skip them and their 1456 // children. 1457 if _, ok := r.cache.Load(dir); ok { 1458 return 1459 } 1460 1461 importpath := filepath.ToSlash(dir[len(root.Path)+len("/"):]) 1462 info := directoryPackageInfo{ 1463 status: directoryScanned, 1464 dir: dir, 1465 rootType: root.Type, 1466 nonCanonicalImportPath: VendorlessPath(importpath), 1467 } 1468 r.cache.Store(dir, info) 1469 } 1470 processDir := func(info directoryPackageInfo) { 1471 // Skip this directory if we were not able to get the package information successfully. 1472 if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil { 1473 return 1474 } 1475 1476 p := &pkg{ 1477 importPathShort: info.nonCanonicalImportPath, 1478 dir: info.dir, 1479 relevance: MaxRelevance - 1, 1480 } 1481 if info.rootType == gopathwalk.RootGOROOT { 1482 p.relevance = MaxRelevance 1483 } 1484 1485 if !callback.dirFound(p) { 1486 return 1487 } 1488 var err error 1489 p.packageName, err = r.cache.CachePackageName(info) 1490 if err != nil { 1491 return 1492 } 1493 1494 if !callback.packageNameLoaded(p) { 1495 return 1496 } 1497 if _, exports, err := r.loadExports(ctx, p, false); err == nil { 1498 callback.exportsLoaded(p, exports) 1499 } 1500 } 1501 stop := r.cache.ScanAndListen(ctx, processDir) 1502 defer stop() 1503 1504 goenv, err := r.env.goEnv() 1505 if err != nil { 1506 return err 1507 } 1508 var roots []gopathwalk.Root 1509 roots = append(roots, gopathwalk.Root{Path: filepath.Join(goenv["GOROOT"], "src"), Type: gopathwalk.RootGOROOT}) 1510 for _, p := range filepath.SplitList(goenv["GOPATH"]) { 1511 roots = append(roots, gopathwalk.Root{Path: filepath.Join(p, "src"), Type: gopathwalk.RootGOPATH}) 1512 } 1513 // The callback is not necessarily safe to use in the goroutine below. Process roots eagerly. 1514 roots = filterRoots(roots, callback.rootFound) 1515 // We can't cancel walks, because we need them to finish to have a usable 1516 // cache. Instead, run them in a separate goroutine and detach. 1517 scanDone := make(chan struct{}) 1518 go func() { 1519 select { 1520 case <-ctx.Done(): 1521 return 1522 case <-r.scanSema: 1523 } 1524 defer func() { r.scanSema <- struct{}{} }() 1525 gopathwalk.Walk(roots, add, gopathwalk.Options{Logf: r.env.Logf, ModulesEnabled: false}) 1526 close(scanDone) 1527 }() 1528 select { 1529 case <-ctx.Done(): 1530 case <-scanDone: 1531 } 1532 return nil 1533 } 1534 1535 func (r *gopathResolver) scoreImportPath(ctx context.Context, path string) float64 { 1536 if stdlib.HasPackage(path) { 1537 return MaxRelevance 1538 } 1539 return MaxRelevance - 1 1540 } 1541 1542 func filterRoots(roots []gopathwalk.Root, include func(gopathwalk.Root) bool) []gopathwalk.Root { 1543 var result []gopathwalk.Root 1544 for _, root := range roots { 1545 if !include(root) { 1546 continue 1547 } 1548 result = append(result, root) 1549 } 1550 return result 1551 } 1552 1553 func (r *gopathResolver) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []stdlib.Symbol, error) { 1554 if info, ok := r.cache.Load(pkg.dir); ok && !includeTest { 1555 return r.cache.CacheExports(ctx, r.env, info) 1556 } 1557 return loadExportsFromFiles(ctx, r.env, pkg.dir, includeTest) 1558 } 1559 1560 // VendorlessPath returns the devendorized version of the import path ipath. 1561 // For example, VendorlessPath("foo/bar/vendor/a/b") returns "a/b". 1562 func VendorlessPath(ipath string) string { 1563 // Devendorize for use in import statement. 1564 if i := strings.LastIndex(ipath, "/vendor/"); i >= 0 { 1565 return ipath[i+len("/vendor/"):] 1566 } 1567 if strings.HasPrefix(ipath, "vendor/") { 1568 return ipath[len("vendor/"):] 1569 } 1570 return ipath 1571 } 1572 1573 func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string, includeTest bool) (string, []stdlib.Symbol, error) { 1574 // Look for non-test, buildable .go files which could provide exports. 1575 all, err := os.ReadDir(dir) 1576 if err != nil { 1577 return "", nil, err 1578 } 1579 var files []fs.DirEntry 1580 for _, fi := range all { 1581 name := fi.Name() 1582 if !strings.HasSuffix(name, ".go") || (!includeTest && strings.HasSuffix(name, "_test.go")) { 1583 continue 1584 } 1585 match, err := env.matchFile(dir, fi.Name()) 1586 if err != nil || !match { 1587 continue 1588 } 1589 files = append(files, fi) 1590 } 1591 1592 if len(files) == 0 { 1593 return "", nil, fmt.Errorf("dir %v contains no buildable, non-test .go files", dir) 1594 } 1595 1596 var pkgName string 1597 var exports []stdlib.Symbol 1598 fset := token.NewFileSet() 1599 for _, fi := range files { 1600 select { 1601 case <-ctx.Done(): 1602 return "", nil, ctx.Err() 1603 default: 1604 } 1605 1606 fullFile := filepath.Join(dir, fi.Name()) 1607 f, err := parser.ParseFile(fset, fullFile, nil, 0) 1608 if err != nil { 1609 if env.Logf != nil { 1610 env.Logf("error parsing %v: %v", fullFile, err) 1611 } 1612 continue 1613 } 1614 if f.Name.Name == "documentation" { 1615 // Special case from go/build.ImportDir, not 1616 // handled by MatchFile above. 1617 continue 1618 } 1619 if includeTest && strings.HasSuffix(f.Name.Name, "_test") { 1620 // x_test package. We want internal test files only. 1621 continue 1622 } 1623 pkgName = f.Name.Name 1624 for name, obj := range f.Scope.Objects { 1625 if ast.IsExported(name) { 1626 var kind stdlib.Kind 1627 switch obj.Kind { 1628 case ast.Con: 1629 kind = stdlib.Const 1630 case ast.Typ: 1631 kind = stdlib.Type 1632 case ast.Var: 1633 kind = stdlib.Var 1634 case ast.Fun: 1635 kind = stdlib.Func 1636 } 1637 exports = append(exports, stdlib.Symbol{ 1638 Name: name, 1639 Kind: kind, 1640 Version: 0, // unknown; be permissive 1641 }) 1642 } 1643 } 1644 } 1645 sortSymbols(exports) 1646 1647 if env.Logf != nil { 1648 env.Logf("loaded exports in dir %v (package %v): %v", dir, pkgName, exports) 1649 } 1650 return pkgName, exports, nil 1651 } 1652 1653 func sortSymbols(syms []stdlib.Symbol) { 1654 sort.Slice(syms, func(i, j int) bool { 1655 return syms[i].Name < syms[j].Name 1656 }) 1657 } 1658 1659 // findImport searches for a package with the given symbols. 1660 // If no package is found, findImport returns ("", false, nil) 1661 func findImport(ctx context.Context, pass *pass, candidates []pkgDistance, pkgName string, symbols map[string]bool) (*pkg, error) { 1662 // Sort the candidates by their import package length, 1663 // assuming that shorter package names are better than long 1664 // ones. Note that this sorts by the de-vendored name, so 1665 // there's no "penalty" for vendoring. 1666 sort.Sort(byDistanceOrImportPathShortLength(candidates)) 1667 if pass.env.Logf != nil { 1668 for i, c := range candidates { 1669 pass.env.Logf("%s candidate %d/%d: %v in %v", pkgName, i+1, len(candidates), c.pkg.importPathShort, c.pkg.dir) 1670 } 1671 } 1672 resolver, err := pass.env.GetResolver() 1673 if err != nil { 1674 return nil, err 1675 } 1676 1677 // Collect exports for packages with matching names. 1678 rescv := make([]chan *pkg, len(candidates)) 1679 for i := range candidates { 1680 rescv[i] = make(chan *pkg, 1) 1681 } 1682 const maxConcurrentPackageImport = 4 1683 loadExportsSem := make(chan struct{}, maxConcurrentPackageImport) 1684 1685 ctx, cancel := context.WithCancel(ctx) 1686 var wg sync.WaitGroup 1687 defer func() { 1688 cancel() 1689 wg.Wait() 1690 }() 1691 1692 wg.Add(1) 1693 go func() { 1694 defer wg.Done() 1695 for i, c := range candidates { 1696 select { 1697 case loadExportsSem <- struct{}{}: 1698 case <-ctx.Done(): 1699 return 1700 } 1701 1702 wg.Add(1) 1703 go func(c pkgDistance, resc chan<- *pkg) { 1704 defer func() { 1705 <-loadExportsSem 1706 wg.Done() 1707 }() 1708 1709 if pass.env.Logf != nil { 1710 pass.env.Logf("loading exports in dir %s (seeking package %s)", c.pkg.dir, pkgName) 1711 } 1712 // If we're an x_test, load the package under test's test variant. 1713 includeTest := strings.HasSuffix(pass.f.Name.Name, "_test") && c.pkg.dir == pass.srcDir 1714 _, exports, err := resolver.loadExports(ctx, c.pkg, includeTest) 1715 if err != nil { 1716 if pass.env.Logf != nil { 1717 pass.env.Logf("loading exports in dir %s (seeking package %s): %v", c.pkg.dir, pkgName, err) 1718 } 1719 resc <- nil 1720 return 1721 } 1722 1723 exportsMap := make(map[string]bool, len(exports)) 1724 for _, sym := range exports { 1725 exportsMap[sym.Name] = true 1726 } 1727 1728 // If it doesn't have the right 1729 // symbols, send nil to mean no match. 1730 for symbol := range symbols { 1731 if !exportsMap[symbol] { 1732 resc <- nil 1733 return 1734 } 1735 } 1736 resc <- c.pkg 1737 }(c, rescv[i]) 1738 } 1739 }() 1740 1741 for _, resc := range rescv { 1742 pkg := <-resc 1743 if pkg == nil { 1744 continue 1745 } 1746 return pkg, nil 1747 } 1748 return nil, nil 1749 } 1750 1751 // pkgIsCandidate reports whether pkg is a candidate for satisfying the 1752 // finding which package pkgIdent in the file named by filename is trying 1753 // to refer to. 1754 // 1755 // This check is purely lexical and is meant to be as fast as possible 1756 // because it's run over all $GOPATH directories to filter out poor 1757 // candidates in order to limit the CPU and I/O later parsing the 1758 // exports in candidate packages. 1759 // 1760 // filename is the file being formatted. 1761 // pkgIdent is the package being searched for, like "client" (if 1762 // searching for "client.New") 1763 func pkgIsCandidate(filename string, refs references, pkg *pkg) bool { 1764 // Check "internal" and "vendor" visibility: 1765 if !canUse(filename, pkg.dir) { 1766 return false 1767 } 1768 1769 // Speed optimization to minimize disk I/O: 1770 // the last two components on disk must contain the 1771 // package name somewhere. 1772 // 1773 // This permits mismatch naming like directory 1774 // "go-foo" being package "foo", or "pkg.v3" being "pkg", 1775 // or directory "google.golang.org/api/cloudbilling/v1" 1776 // being package "cloudbilling", but doesn't 1777 // permit a directory "foo" to be package 1778 // "bar", which is strongly discouraged 1779 // anyway. There's no reason goimports needs 1780 // to be slow just to accommodate that. 1781 for pkgIdent := range refs { 1782 lastTwo := lastTwoComponents(pkg.importPathShort) 1783 if strings.Contains(lastTwo, pkgIdent) { 1784 return true 1785 } 1786 if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(pkgIdent) { 1787 lastTwo = lowerASCIIAndRemoveHyphen(lastTwo) 1788 if strings.Contains(lastTwo, pkgIdent) { 1789 return true 1790 } 1791 } 1792 } 1793 return false 1794 } 1795 1796 func hasHyphenOrUpperASCII(s string) bool { 1797 for i := 0; i < len(s); i++ { 1798 b := s[i] 1799 if b == '-' || ('A' <= b && b <= 'Z') { 1800 return true 1801 } 1802 } 1803 return false 1804 } 1805 1806 func lowerASCIIAndRemoveHyphen(s string) (ret string) { 1807 buf := make([]byte, 0, len(s)) 1808 for i := 0; i < len(s); i++ { 1809 b := s[i] 1810 switch { 1811 case b == '-': 1812 continue 1813 case 'A' <= b && b <= 'Z': 1814 buf = append(buf, b+('a'-'A')) 1815 default: 1816 buf = append(buf, b) 1817 } 1818 } 1819 return string(buf) 1820 } 1821 1822 // canUse reports whether the package in dir is usable from filename, 1823 // respecting the Go "internal" and "vendor" visibility rules. 1824 func canUse(filename, dir string) bool { 1825 // Fast path check, before any allocations. If it doesn't contain vendor 1826 // or internal, it's not tricky: 1827 // Note that this can false-negative on directories like "notinternal", 1828 // but we check it correctly below. This is just a fast path. 1829 if !strings.Contains(dir, "vendor") && !strings.Contains(dir, "internal") { 1830 return true 1831 } 1832 1833 dirSlash := filepath.ToSlash(dir) 1834 if !strings.Contains(dirSlash, "/vendor/") && !strings.Contains(dirSlash, "/internal/") && !strings.HasSuffix(dirSlash, "/internal") { 1835 return true 1836 } 1837 // Vendor or internal directory only visible from children of parent. 1838 // That means the path from the current directory to the target directory 1839 // can contain ../vendor or ../internal but not ../foo/vendor or ../foo/internal 1840 // or bar/vendor or bar/internal. 1841 // After stripping all the leading ../, the only okay place to see vendor or internal 1842 // is at the very beginning of the path. 1843 absfile, err := filepath.Abs(filename) 1844 if err != nil { 1845 return false 1846 } 1847 absdir, err := filepath.Abs(dir) 1848 if err != nil { 1849 return false 1850 } 1851 rel, err := filepath.Rel(absfile, absdir) 1852 if err != nil { 1853 return false 1854 } 1855 relSlash := filepath.ToSlash(rel) 1856 if i := strings.LastIndex(relSlash, "../"); i >= 0 { 1857 relSlash = relSlash[i+len("../"):] 1858 } 1859 return !strings.Contains(relSlash, "/vendor/") && !strings.Contains(relSlash, "/internal/") && !strings.HasSuffix(relSlash, "/internal") 1860 } 1861 1862 // lastTwoComponents returns at most the last two path components 1863 // of v, using either / or \ as the path separator. 1864 func lastTwoComponents(v string) string { 1865 nslash := 0 1866 for i := len(v) - 1; i >= 0; i-- { 1867 if v[i] == '/' || v[i] == '\\' { 1868 nslash++ 1869 if nslash == 2 { 1870 return v[i:] 1871 } 1872 } 1873 } 1874 return v 1875 } 1876 1877 type visitFn func(node ast.Node) ast.Visitor 1878 1879 func (fn visitFn) Visit(node ast.Node) ast.Visitor { 1880 return fn(node) 1881 } 1882 1883 func symbolNameSet(symbols []stdlib.Symbol) map[string]bool { 1884 names := make(map[string]bool) 1885 for _, sym := range symbols { 1886 switch sym.Kind { 1887 case stdlib.Const, stdlib.Var, stdlib.Type, stdlib.Func: 1888 names[sym.Name] = true 1889 } 1890 } 1891 return names 1892 }