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