Skip to content

Commit 8266eea

Browse files
committed
internal/imports: provide export completions for unimported packages
Add a function that returns all the exported identifiers associated with a package name that doesn't have an import yet. This will allow completions like rand<> to return rand.Seed (from math/rand) and rand.Prime (from crypto/rand). Updates golang/go#31906 Change-Id: Iee290c786de263d42acbfabd76bf0edbf303afc9 Reviewed-on: https://go-review.googlesource.com/c/tools/+/204204 Run-TryBot: Heschi Kreinick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent 979d74e commit 8266eea

File tree

3 files changed

+142
-31
lines changed

3 files changed

+142
-31
lines changed

internal/imports/fix.go

Lines changed: 92 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -585,65 +585,128 @@ func getFixes(fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv
585585
return fixes, nil
586586
}
587587

588-
// getAllCandidates gets all of the candidates to be imported, regardless of if they are needed.
589-
func getAllCandidates(filename string, env *ProcessEnv) ([]ImportFix, error) {
588+
// getCandidatePkgs returns the list of pkgs that are accessible from filename,
589+
// optionall filtered to only packages named pkgName.
590+
func getCandidatePkgs(pkgName, filename string, env *ProcessEnv) ([]*pkg, error) {
590591
// TODO(heschi): filter out current package. (Don't forget x_test can import x.)
591592

593+
var result []*pkg
592594
// Start off with the standard library.
593-
var imports []ImportFix
594595
for importPath := range stdlib {
595-
imports = append(imports, ImportFix{
596-
StmtInfo: ImportInfo{
597-
ImportPath: importPath,
598-
},
599-
IdentName: path.Base(importPath),
600-
FixType: AddImport,
596+
if pkgName != "" && path.Base(importPath) != pkgName {
597+
continue
598+
}
599+
result = append(result, &pkg{
600+
dir: filepath.Join(env.GOROOT, "src", importPath),
601+
importPathShort: importPath,
602+
packageName: path.Base(importPath),
603+
relevance: 0,
601604
})
602605
}
603-
// Sort the stdlib bits solely by name.
604-
sort.Slice(imports, func(i int, j int) bool {
605-
return imports[i].StmtInfo.ImportPath < imports[j].StmtInfo.ImportPath
606-
})
607606

608607
// Exclude goroot results -- getting them is relatively expensive, not cached,
609608
// and generally redundant with the in-memory version.
610609
exclude := []gopathwalk.RootType{gopathwalk.RootGOROOT}
611610
// Only the go/packages resolver uses the first argument, and nobody uses that resolver.
612-
pkgs, err := env.GetResolver().scan(nil, true, exclude)
611+
scannedPkgs, err := env.GetResolver().scan(nil, true, exclude)
613612
if err != nil {
614613
return nil, err
615614
}
616-
// Sort first by relevance, then by name, so that when we add them they're
617-
// still in order.
618-
sort.Slice(pkgs, func(i, j int) bool {
619-
pi, pj := pkgs[i], pkgs[j]
620-
if pi.relevance < pj.relevance {
621-
return true
622-
}
623-
if pi.relevance > pj.relevance {
624-
return false
625-
}
626-
return pi.packageName < pj.packageName
627-
})
628615

629616
dupCheck := map[string]struct{}{}
630-
for _, pkg := range pkgs {
617+
for _, pkg := range scannedPkgs {
618+
if pkgName != "" && pkg.packageName != pkgName {
619+
continue
620+
}
631621
if !canUse(filename, pkg.dir) {
632622
continue
633623
}
634624
if _, ok := dupCheck[pkg.importPathShort]; ok {
635625
continue
636626
}
637627
dupCheck[pkg.importPathShort] = struct{}{}
638-
imports = append(imports, ImportFix{
628+
result = append(result, pkg)
629+
}
630+
631+
// Sort first by relevance, then by package name, with import path as a tiebreaker.
632+
sort.Slice(result, func(i, j int) bool {
633+
pi, pj := result[i], result[j]
634+
if pi.relevance != pj.relevance {
635+
return pi.relevance < pj.relevance
636+
}
637+
if pi.packageName != pj.packageName {
638+
return pi.packageName < pj.packageName
639+
}
640+
return pi.importPathShort < pj.importPathShort
641+
})
642+
643+
return result, nil
644+
}
645+
646+
// getAllCandidates gets all of the candidates to be imported, regardless of if they are needed.
647+
func getAllCandidates(filename string, env *ProcessEnv) ([]ImportFix, error) {
648+
pkgs, err := getCandidatePkgs("", filename, env)
649+
if err != nil {
650+
return nil, err
651+
}
652+
result := make([]ImportFix, 0, len(pkgs))
653+
for _, pkg := range pkgs {
654+
result = append(result, ImportFix{
655+
StmtInfo: ImportInfo{
656+
ImportPath: pkg.importPathShort,
657+
},
658+
IdentName: pkg.packageName,
659+
FixType: AddImport,
660+
})
661+
}
662+
return result, nil
663+
}
664+
665+
// A PackageExport is a package and its exports.
666+
type PackageExport struct {
667+
Fix *ImportFix
668+
Exports []string
669+
}
670+
671+
func getPackageExports(completePackage, filename string, env *ProcessEnv) ([]PackageExport, error) {
672+
pkgs, err := getCandidatePkgs(completePackage, filename, env)
673+
if err != nil {
674+
return nil, err
675+
}
676+
677+
results := make([]PackageExport, 0, len(pkgs))
678+
for _, pkg := range pkgs {
679+
fix := &ImportFix{
639680
StmtInfo: ImportInfo{
640681
ImportPath: pkg.importPathShort,
641682
},
642683
IdentName: pkg.packageName,
643684
FixType: AddImport,
685+
}
686+
var exportsMap map[string]bool
687+
if e, ok := stdlib[pkg.importPathShort]; ok {
688+
exportsMap = e
689+
} else {
690+
exportsMap, err = env.GetResolver().loadExports(context.TODO(), completePackage, pkg)
691+
if err != nil {
692+
if env.Debug {
693+
env.Logf("while completing %q, error loading exports from %q: %v", completePackage, pkg.importPathShort, err)
694+
}
695+
continue
696+
}
697+
}
698+
var exports []string
699+
for export := range exportsMap {
700+
exports = append(exports, export)
701+
}
702+
sort.Strings(exports)
703+
results = append(results, PackageExport{
704+
Fix: fix,
705+
Exports: exports,
644706
})
645707
}
646-
return imports, nil
708+
709+
return results, nil
647710
}
648711

649712
// ProcessEnv contains environment variables and settings that affect the use of

internal/imports/fix_test.go

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2522,9 +2522,9 @@ func TestGetCandidates(t *testing.T) {
25222522
}
25232523
want := []res{
25242524
{"bytes", "bytes"},
2525+
{"http", "net/http"},
25252526
{"rand", "crypto/rand"},
25262527
{"rand", "math/rand"},
2527-
{"http", "net/http"},
25282528
{"bar", "bar.com/bar"},
25292529
{"foo", "foo.com/foo"},
25302530
}
@@ -2560,6 +2560,45 @@ func TestGetCandidates(t *testing.T) {
25602560
})
25612561
}
25622562

2563+
func TestGetPackageCompletions(t *testing.T) {
2564+
type res struct {
2565+
name, path, symbol string
2566+
}
2567+
want := []res{
2568+
{"rand", "crypto/rand", "Prime"},
2569+
{"rand", "math/rand", "Seed"},
2570+
{"rand", "bar.com/rand", "Bar"},
2571+
}
2572+
2573+
testConfig{
2574+
modules: []packagestest.Module{
2575+
{
2576+
Name: "bar.com",
2577+
Files: fm{"rand/bar.go": "package rand\nvar Bar int\n"},
2578+
},
2579+
},
2580+
goPackagesIncompatible: true, // getPackageCompletions doesn't support the go/packages resolver.
2581+
}.test(t, func(t *goimportTest) {
2582+
candidates, err := getPackageExports("rand", "x.go", t.env)
2583+
if err != nil {
2584+
t.Fatalf("getPackageCompletions() = %v", err)
2585+
}
2586+
var got []res
2587+
for _, c := range candidates {
2588+
for _, csym := range c.Exports {
2589+
for _, w := range want {
2590+
if c.Fix.StmtInfo.ImportPath == w.path && csym == w.symbol {
2591+
got = append(got, res{c.Fix.IdentName, c.Fix.StmtInfo.ImportPath, csym})
2592+
}
2593+
}
2594+
}
2595+
}
2596+
if !reflect.DeepEqual(want, got) {
2597+
t.Errorf("wanted stdlib results in order %v, got %v", want, got)
2598+
}
2599+
})
2600+
}
2601+
25632602
// Tests #34895: process should not panic on concurrent calls.
25642603
func TestConcurrentProcess(t *testing.T) {
25652604
testConfig{

internal/imports/imports.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,22 @@ func ApplyFixes(fixes []*ImportFix, filename string, src []byte, opt *Options) (
105105
// GetAllCandidates gets all of the standard library candidate packages to import in
106106
// sorted order on import path.
107107
func GetAllCandidates(filename string, opt *Options) (pkgs []ImportFix, err error) {
108-
_, opt, err = initialize(filename, []byte{}, opt)
108+
_, opt, err = initialize(filename, nil, opt)
109109
if err != nil {
110110
return nil, err
111111
}
112112
return getAllCandidates(filename, opt.Env)
113113
}
114114

115+
// GetPackageExports returns all known packages with name pkg and their exports.
116+
func GetPackageExports(pkg, filename string, opt *Options) (exports []PackageExport, err error) {
117+
_, opt, err = initialize(filename, nil, opt)
118+
if err != nil {
119+
return nil, err
120+
}
121+
return getPackageExports(pkg, filename, opt.Env)
122+
}
123+
115124
// initialize sets the values for opt and src.
116125
// If they are provided, they are not changed. Otherwise opt is set to the
117126
// default values and src is read from the file system.

0 commit comments

Comments
 (0)