[tools] gopls/unimported: prefer packages in go.mod

1 view
Skip to first unread message

Peter Weinberger (Gerrit)

unread,
Oct 1, 2025, 3:16:44 PM (13 hours ago) Oct 1
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Peter Weinberger has uploaded the change for review

Commit message

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.
Change-Id: Ic1b42f30673821ae4c69ec0bf0e07e5191ba5e8e

Change diff

diff --git a/gopls/internal/golang/completion/unimported.go b/gopls/internal/golang/completion/unimported.go
index ea92afa..0f8beef 100644
--- a/gopls/internal/golang/completion/unimported.go
+++ b/gopls/internal/golang/completion/unimported.go
@@ -28,6 +28,7 @@
"go/ast"
"go/printer"
"go/token"
+ "log"
"path"
"slices"
"strings"
@@ -72,6 +73,7 @@

// look in the module cache
items, err := c.modcacheMatches(pkgname, prefix)
+ items = c.filterGoMod(ctx, items)
if err == nil && c.scoreList(items) {
return
}
@@ -79,6 +81,49 @@
// out of things to do
}

+func init() {
+ log.SetFlags(log.Lshortfile)
+}
+
+// prefer completion items that are referenced in the go.mod file
+func (c *completer) filterGoMod(ctx context.Context, items []CompletionItem) []CompletionItem {
+ gomod := c.pkg.Metadata().Module.GoMod
+ uri := protocol.URIFromPath(gomod)
+ fh, err := c.snapshot.ReadFile(ctx, uri)
+ if err != nil {
+ return items
+ }
+ pm, err := c.snapshot.ParseMod(ctx, fh)
+ if err != nil || pm == nil {
+ return items
+ }
+ // if any of the items match any of the req, just return those
+ reqnames := []string{}
+ for _, req := range pm.File.Require {
+ reqnames = append(reqnames, req.Mod.Path)
+ }
+ better := []CompletionItem{}
+ for _, compl := range items {
+ if len(compl.AdditionalTextEdits) == 0 {
+ continue
+ }
+ // import "foof/pkg"
+ flds := strings.FieldsFunc(compl.AdditionalTextEdits[0].NewText, func(r rune) bool {
+ return r == '"' || r == '/'
+ })
+ if len(flds) < 3 {
+ continue
+ }
+ if slices.Contains(reqnames, flds[1]) {
+ better = append(better, compl)
+ }
+ }
+ if len(better) > 0 {
+ return better
+ }
+ return items
+}
+
// see if some file in the current package satisfied a foo. import
// because foo is an explicit package name (import foo "a.b.c")
func (c *completer) explicitPkgName(ctx context.Context, pkgname metadata.PackageName, prefix string) bool {
diff --git a/gopls/internal/test/integration/completion/completion_test.go b/gopls/internal/test/integration/completion/completion_test.go
index 2ca9626..69a0635 100644
--- a/gopls/internal/test/integration/completion/completion_test.go
+++ b/gopls/internal/test/integration/completion/completion_test.go
@@ -7,6 +7,7 @@
import (
"fmt"
"os"
+ "path/filepath"
"slices"
"sort"
"strings"
@@ -366,6 +367,7 @@
_ = blah.Hello
}
`
+
WithOptions(
WriteGoSum("."),
ProxyFiles(proxy),
@@ -1464,6 +1466,71 @@
})
}

+// when completing using the module cache, prefer things mentioned
+// in the go.mod file.
+func TestIssue61208(t *testing.T) {
+
+ const cache = `
+-- examp...@v1.2.3/go.mod --
+module example.com
+
+go 1.22
+-- examp...@v1.2.3/blah/blah.go --
+package blah
+
+const Name = "Blah"
+-- rando...@v1.2.3/go.mod --
+module random.org
+
+go 1.22
+-- rando...@v1.2.3/blah/blah.go --
+package blah
+
+const Name = "Hello"
+`
+ const files = `
+-- go.mod --
+module mod.com
+go 1.22
+require random.org v1.2.3
+-- main.go --
+package main
+var _ = blah.
+`
+ modcache := t.TempDir()
+ defer CleanModCache(t, modcache)
+ mx := fake.UnpackTxt(cache)
+ for k, v := range mx {
+ fname := filepath.Join(modcache, k)
+ dir := filepath.Dir(fname)
+ os.MkdirAll(dir, 0777) // ignore error
+ if err := os.WriteFile(fname, v, 0644); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ WithOptions(
+ EnvVars{"GOMODCACHE": modcache},
+ WriteGoSum("."),
+ Settings{"importsSource": settings.ImportsSourceGopls},
+ NoLogsOnError(),
+ ).Run(t, files, func(t *testing.T, env *Env) {
+ env.OpenFile("main.go")
+ env.Await(env.DoneWithOpen())
+ loc := env.RegexpSearch("main.go", "blah.()")
+ completions := env.Completion(loc)
+ if len(completions.Items) != 1 {
+ t.Errorf("got %d, expected 1", len(completions.Items))
+ for _, x := range completions.Items {
+ t.Logf("%#v", x.AdditionalTextEdits[0].NewText)
+ }
+ }
+ if got := completions.Items[0].AdditionalTextEdits[0].NewText; !strings.Contains(got, `"random.org`) {
+ t.Errorf("got %q, expected a `random.org`", got)
+ }
+ })
+}
+
// show that the efficacy counters get exercised. Fortuntely a small program
// exercises them all
func TestCounters(t *testing.T) {

Change information

Files:
  • M gopls/internal/golang/completion/unimported.go
  • M gopls/internal/test/integration/completion/completion_test.go
Change size: M
Delta: 2 files changed, 112 insertions(+), 0 deletions(-)
Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newchange
Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: Ic1b42f30673821ae4c69ec0bf0e07e5191ba5e8e
Gerrit-Change-Number: 708475
Gerrit-PatchSet: 1
Gerrit-Owner: Peter Weinberger <p...@google.com>
Gerrit-Reviewer: Peter Weinberger <p...@google.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

Peter Weinberger (Gerrit)

unread,
Oct 1, 2025, 3:50:23 PM (13 hours ago) Oct 1
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Peter Weinberger uploaded new patchset

Peter Weinberger uploaded patch set #2 to this change.
Following approvals got outdated and were removed:
  • TryBots-Pass: LUCI-TryBot-Result+1 by Go LUCI
Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newpatchset
Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: Ic1b42f30673821ae4c69ec0bf0e07e5191ba5e8e
Gerrit-Change-Number: 708475
Gerrit-PatchSet: 2
Gerrit-Owner: Peter Weinberger <p...@google.com>
Gerrit-Reviewer: Peter Weinberger <p...@google.com>
unsatisfied_requirement
satisfied_requirement
open
diffy
Reply all
Reply to author
Forward
0 new messages