internal/lsp/source: score in-memory unimported candidates
We were assuming that all in-memory packages were equally useful. That's
not true for projects with a large dependency tree. Call into the
imports code to score them.
While I'm here, score the main module above direct deps.
Updates golang/go#36591.
Change-Id: I07c56dd3ff7338e76f3643e18d35abc1b52d6763
Reviewed-on: https://go-review.googlesource.com/c/tools/+/215023
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/imports/fix.go b/internal/imports/fix.go
index 83349a0..ac8f6b1 100644
--- a/internal/imports/fix.go
+++ b/internal/imports/fix.go
@@ -643,6 +643,14 @@
return env.GetResolver().scan(ctx, scanFilter)
}
+func ScoreImportPaths(ctx context.Context, env *ProcessEnv, paths []string) map[string]int {
+ result := make(map[string]int)
+ for _, path := range paths {
+ result[path] = env.GetResolver().scoreImportPath(ctx, path)
+ }
+ return result
+}
+
func PrimeCache(ctx context.Context, env *ProcessEnv) error {
// Fully scan the disk for directories, but don't actually read any Go files.
callback := &scanCallback{
@@ -874,6 +882,8 @@
// loadExports returns the set of exported symbols in the package at dir.
// loadExports may be called concurrently.
loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error)
+ // scoreImportPath returns the relevance for an import path.
+ scoreImportPath(ctx context.Context, path string) int
ClearForNewScan()
}
@@ -1251,6 +1261,13 @@
return nil
}
+func (r *gopathResolver) scoreImportPath(ctx context.Context, path string) int {
+ if _, ok := stdlib[path]; ok {
+ return MaxRelevance
+ }
+ return MaxRelevance - 1
+}
+
func filterRoots(roots []gopathwalk.Root, include func(gopathwalk.Root) bool) []gopathwalk.Root {
var result []gopathwalk.Root
for _, root := range roots {
diff --git a/internal/imports/fix_test.go b/internal/imports/fix_test.go
index 37785fd..67ebe53 100644
--- a/internal/imports/fix_test.go
+++ b/internal/imports/fix_test.go
@@ -2540,13 +2540,13 @@
testConfig{
modules: []packagestest.Module{
{
- Name: "foo.com",
- Files: fm{"foo/foo.go": "package foo\n"},
- },
- {
Name: "bar.com",
Files: fm{"bar/bar.go": "package bar\n"},
},
+ {
+ Name: "foo.com",
+ Files: fm{"foo/foo.go": "package foo\n"},
+ },
},
}.test(t, func(t *goimportTest) {
var mu sync.Mutex
diff --git a/internal/imports/mod.go b/internal/imports/mod.go
index f38e683..553f216 100644
--- a/internal/imports/mod.go
+++ b/internal/imports/mod.go
@@ -480,6 +480,27 @@
return nil
}
+func (r *ModuleResolver) scoreImportPath(ctx context.Context, path string) int {
+ if _, ok := stdlib[path]; ok {
+ return MaxRelevance
+ }
+ mod, _ := r.findPackage(path)
+ return modRelevance(mod)
+}
+
+func modRelevance(mod *ModuleJSON) int {
+ switch {
+ case mod == nil: // out of scope
+ return MaxRelevance - 4
+ case mod.Indirect:
+ return MaxRelevance - 3
+ case !mod.Main:
+ return MaxRelevance - 2
+ default:
+ return MaxRelevance - 1 // main module ties with stdlib
+ }
+}
+
// canonicalize gets the result of canonicalizing the packages using the results
// of initializing the resolver from 'go list -m'.
func (r *ModuleResolver) canonicalize(info directoryPackageInfo) (*pkg, error) {
@@ -494,14 +515,9 @@
}
importPath := info.nonCanonicalImportPath
- relevance := MaxRelevance - 3
+ mod := r.findModuleByDir(info.dir)
// Check if the directory is underneath a module that's in scope.
- if mod := r.findModuleByDir(info.dir); mod != nil {
- if mod.Indirect {
- relevance = MaxRelevance - 2
- } else {
- relevance = MaxRelevance - 1
- }
+ if mod != nil {
// It is. If dir is the target of a replace directive,
// our guessed import path is wrong. Use the real one.
if mod.Dir == info.dir {
@@ -519,7 +535,7 @@
res := &pkg{
importPathShort: importPath,
dir: info.dir,
- relevance: relevance,
+ relevance: modRelevance(mod),
}
// We may have discovered a package that has a different version
// in scope already. Canonicalize to that one if possible.
diff --git a/internal/imports/mod_test.go b/internal/imports/mod_test.go
index fc022ae..882be4b 100644
--- a/internal/imports/mod_test.go
+++ b/internal/imports/mod_test.go
@@ -880,13 +880,14 @@
// Stdlib
{7, "bytes", "bytes"},
{7, "http", "net/http"},
- // Direct module deps
- {6, "quote", "rsc.io/quote"},
+ // Main module
{6, "rpackage", "example.com/rpackage"},
+ // Direct module deps
+ {5, "quote", "rsc.io/quote"},
// Indirect deps
- {5, "language", "golang.org/x/text/language"},
+ {4, "language", "golang.org/x/text/language"},
// Out of scope modules
- {4, "quote", "rsc.io/quote/v2"},
+ {3, "quote", "rsc.io/quote/v2"},
}
var mu sync.Mutex
var got []res
diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go
index 62e0f38..41b43fb 100644
--- a/internal/lsp/source/completion.go
+++ b/internal/lsp/source/completion.go
@@ -656,12 +656,24 @@
if err != nil {
return err
}
+ var paths []string
for path, pkg := range known {
if pkg.GetTypes().Name() != id.Name {
continue
}
- // We don't know what this is, so assign it the highest score.
- score := 0.01 * imports.MaxRelevance
+ paths = append(paths, path)
+ }
+ var relevances map[string]int
+ if len(paths) != 0 {
+ c.snapshot.View().RunProcessEnvFunc(c.ctx, func(opts *imports.Options) error {
+ relevances = imports.ScoreImportPaths(c.ctx, opts.Env, paths)
+ return nil
+ })
+ }
+ for path, pkg := range known {
+ if pkg.GetTypes().Name() != id.Name {
+ continue
+ }
imp := &importInfo{
importPath: path,
pkg: pkg,
@@ -669,7 +681,7 @@
if imports.ImportPathToAssumedName(path) != pkg.GetTypes().Name() {
imp.name = pkg.GetTypes().Name()
}
- c.packageMembers(pkg.GetTypes(), score, imp)
+ c.packageMembers(pkg.GetTypes(), .01*float64(relevances[path]), imp)
if len(c.items) >= unimportedTarget {
return nil
}