Skip to content

Commit 9f84208

Browse files
committed
gopls/unimported: prefer packages in go.mod
When all else fails, unimported completions look in the go module cache. After this CL, it will preferentially use packages mentioned in the go.mod file rather than returning all the matches. Fixes: golang/go#61208 Change-Id: Ic1b42f30673821ae4c69ec0bf0e07e5191ba5e8e Reviewed-on: https://go-review.googlesource.com/c/tools/+/708475 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Madeline Kalil <mkalil@google.com>
1 parent 9447ff9 commit 9f84208

File tree

2 files changed

+106
-0
lines changed

2 files changed

+106
-0
lines changed

gopls/internal/golang/completion/unimported.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,53 @@ func (c *completer) unimported(ctx context.Context, pkgname metadata.PackageName
7272

7373
// look in the module cache
7474
items, err := c.modcacheMatches(pkgname, prefix)
75+
items = c.filterGoMod(ctx, items)
7576
if err == nil && c.scoreList(items) {
7677
return
7778
}
7879

7980
// out of things to do
8081
}
8182

83+
// prefer completion items that are referenced in the go.mod file
84+
func (c *completer) filterGoMod(ctx context.Context, items []CompletionItem) []CompletionItem {
85+
gomod := c.pkg.Metadata().Module.GoMod
86+
uri := protocol.URIFromPath(gomod)
87+
fh, err := c.snapshot.ReadFile(ctx, uri)
88+
if err != nil {
89+
return items
90+
}
91+
pm, err := c.snapshot.ParseMod(ctx, fh)
92+
if err != nil || pm == nil {
93+
return items
94+
}
95+
// if any of the items match any of the req, just return those
96+
reqnames := []string{}
97+
for _, req := range pm.File.Require {
98+
reqnames = append(reqnames, req.Mod.Path)
99+
}
100+
better := []CompletionItem{}
101+
for _, compl := range items {
102+
if len(compl.AdditionalTextEdits) == 0 {
103+
continue
104+
}
105+
// import "foof/pkg"
106+
flds := strings.FieldsFunc(compl.AdditionalTextEdits[0].NewText, func(r rune) bool {
107+
return r == '"' || r == '/'
108+
})
109+
if len(flds) < 3 {
110+
continue
111+
}
112+
if slices.Contains(reqnames, flds[1]) {
113+
better = append(better, compl)
114+
}
115+
}
116+
if len(better) > 0 {
117+
return better
118+
}
119+
return items
120+
}
121+
82122
// see if some file in the current package satisfied a foo. import
83123
// because foo is an explicit package name (import foo "a.b.c")
84124
func (c *completer) explicitPkgName(ctx context.Context, pkgname metadata.PackageName, prefix string) bool {

gopls/internal/test/integration/completion/completion_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package completion
77
import (
88
"fmt"
99
"os"
10+
"path/filepath"
1011
"slices"
1112
"sort"
1213
"strings"
@@ -1464,6 +1465,71 @@ func Join() {}
14641465
})
14651466
}
14661467

1468+
// when completing using the module cache, prefer things mentioned
1469+
// in the go.mod file.
1470+
func TestIssue61208(t *testing.T) {
1471+
1472+
const cache = `
1473+
-- example.com@v1.2.3/go.mod --
1474+
module example.com
1475+
1476+
go 1.22
1477+
-- example.com@v1.2.3/blah/blah.go --
1478+
package blah
1479+
1480+
const Name = "Blah"
1481+
-- random.org@v1.2.3/go.mod --
1482+
module random.org
1483+
1484+
go 1.22
1485+
-- random.org@v1.2.3/blah/blah.go --
1486+
package blah
1487+
1488+
const Name = "Hello"
1489+
`
1490+
const files = `
1491+
-- go.mod --
1492+
module mod.com
1493+
go 1.22
1494+
require random.org v1.2.3
1495+
-- main.go --
1496+
package main
1497+
var _ = blah.
1498+
`
1499+
modcache := t.TempDir()
1500+
defer CleanModCache(t, modcache)
1501+
mx := fake.UnpackTxt(cache)
1502+
for k, v := range mx {
1503+
fname := filepath.Join(modcache, k)
1504+
dir := filepath.Dir(fname)
1505+
os.MkdirAll(dir, 0777) // ignore error
1506+
if err := os.WriteFile(fname, v, 0644); err != nil {
1507+
t.Fatal(err)
1508+
}
1509+
}
1510+
1511+
WithOptions(
1512+
EnvVars{"GOMODCACHE": modcache},
1513+
WriteGoSum("."),
1514+
Settings{"importsSource": settings.ImportsSourceGopls},
1515+
NoLogsOnError(),
1516+
).Run(t, files, func(t *testing.T, env *Env) {
1517+
env.OpenFile("main.go")
1518+
env.Await(env.DoneWithOpen())
1519+
loc := env.RegexpSearch("main.go", "blah.()")
1520+
completions := env.Completion(loc)
1521+
if len(completions.Items) != 1 {
1522+
t.Errorf("got %d, expected 1", len(completions.Items))
1523+
for _, x := range completions.Items {
1524+
t.Logf("%#v", x.AdditionalTextEdits[0].NewText)
1525+
}
1526+
}
1527+
if got := completions.Items[0].AdditionalTextEdits[0].NewText; !strings.Contains(got, `"random.org`) {
1528+
t.Errorf("got %q, expected a `random.org`", got)
1529+
}
1530+
})
1531+
}
1532+
14671533
// show that the efficacy counters get exercised. Fortuntely a small program
14681534
// exercises them all
14691535
func TestCounters(t *testing.T) {

0 commit comments

Comments
 (0)