Skip to content

Commit 761a007

Browse files
committed
gopls/mod: find references to required modules
With this CL, users will be able to click on a required module in a go.mod file, and see the import statements that reference its packages. In addition to integrating with LSP clients, this code avoids reporting imports of nested modules. Testing: there are marker tests, but self-contained. A lot of natural uses will be to find modules from the module cache. I tested by hand getting references from a go.mod file in the tools repository. For instance, when searching for references to golang/x/tools in gopls/go.mod, for marker_test.go it finds the 8 imports of packages in golang.org/x/tools, but none of the many golang.go/x/tools/gopls imports. Fixes golang/go#75810 Change-Id: I08022dd29173a6d4ea8c728c7eb8e54a462a5889 Reviewed-on: https://go-review.googlesource.com/c/tools/+/713160 Reviewed-by: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent 9136dd1 commit 761a007

File tree

7 files changed

+275
-1
lines changed

7 files changed

+275
-1
lines changed

‎gopls/doc/features/navigation.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ The references algorithm handles various parts of syntax as follows:
6666
definition (of a field).
6767
The `references` operation reports only the references to it [as a field](https://go.dev/issue/63521).
6868
To find references to the type, jump to the type declararation first.
69+
- The references to a module in a **require** directive in a **go.mod** file are
70+
the **import** statements in packages of the main module (defined by the go.mod file) that
71+
import packages of the required module.
6972

7073
Be aware that a references query returns information only about the
7174
build configuration used to analyze the selected file, so if you ask

‎gopls/doc/release/v0.22.0.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ title: "Gopls release v0.22.0 (forthcoming)"
33
---
44

55
## Configuration changes
6+
## Navigation features
7+
<!-- golang/go#75810 -->
8+
9+
- Asking for references while hovering over the name of a required module in a `go.mod` file
10+
will show all the imports of that module in the main module (the go.mod's module).
611
## Web-based features
712
## Editing features
813
## Analysis features

‎gopls/internal/cache/mod.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
// A ParsedModule contains the results of parsing a go.mod file.
2626
type ParsedModule struct {
2727
URI protocol.DocumentURI
28-
File *modfile.File
28+
File *modfile.File // maybe absent in case of error
2929
ReplaceMap map[module.Version]module.Version
3030
Mapper *protocol.Mapper
3131
ParseErrors []*Diagnostic

‎gopls/internal/mod/references.go‎

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright 2025 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 mod
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"maps"
11+
"slices"
12+
13+
"golang.org/x/tools/gopls/internal/cache"
14+
"golang.org/x/tools/gopls/internal/cache/metadata"
15+
"golang.org/x/tools/gopls/internal/cache/parsego"
16+
"golang.org/x/tools/gopls/internal/file"
17+
"golang.org/x/tools/gopls/internal/protocol"
18+
)
19+
20+
// References returns the locations of imports referenced by a required module
21+
func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, params *protocol.ReferenceParams) ([]protocol.Location, error) {
22+
// Th go.mod file will contain a module name (e.g., golang.org/x/tools)
23+
// but the import statements contain package names
24+
// (e.g., golang.org/x/tools/internal/astutil).
25+
// The code has to avoid imports from submodules, like golang.org/x/tools/gopls/internal/file
26+
// so simple string matching would not work.
27+
28+
// find modpath, the module path the user wants the references for
29+
gomod, err := snapshot.ParseMod(ctx, fh)
30+
if err != nil {
31+
return nil, err
32+
}
33+
if gomod.File == nil {
34+
return nil, fmt.Errorf("mysterious parse failure %s", fh.URI().Path())
35+
}
36+
// protocol.ReferenceParams contains both a Position and a Range.
37+
// The Range would contain the Position, so use Position.
38+
offset, err := gomod.Mapper.PositionOffset(params.Position)
39+
if err != nil {
40+
return nil, err
41+
}
42+
var modpath string
43+
for _, req := range gomod.File.Require {
44+
if req.Syntax.Start.Byte <= offset && offset <= req.Syntax.End.Byte {
45+
modpath = req.Mod.Path
46+
break
47+
}
48+
}
49+
if modpath == "" {
50+
// nothing to report
51+
return nil, nil
52+
}
53+
54+
locs := make(map[protocol.Location]bool)
55+
56+
includeDecl := params.Context.IncludeDeclaration
57+
// A. find the IDs of all packages in the module that was required
58+
ids := make(map[metadata.PackageID]bool)
59+
g := snapshot.MetadataGraph()
60+
for _, pkg := range g.Packages {
61+
if pkg.Module != nil && pkg.Module.Path == modpath {
62+
ids[pkg.ID] = true
63+
// once, if the definition is needed
64+
if includeDecl {
65+
fh, err := snapshot.ReadFile(ctx, protocol.URIFromPath(pkg.Module.GoMod))
66+
if err != nil {
67+
return nil, err
68+
}
69+
parsed, err := snapshot.ParseMod(ctx, fh)
70+
if err != nil {
71+
return nil, err
72+
}
73+
if parsed.File != nil && parsed.File.Module != nil {
74+
start, end := parsed.File.Module.Syntax.Span()
75+
loc, err := parsed.Mapper.OffsetLocation(start.Byte, end.Byte)
76+
if err != nil {
77+
return nil, err
78+
}
79+
locs[loc] = true
80+
includeDecl = false // no need to do this again
81+
}
82+
}
83+
}
84+
}
85+
// B. find all the importers of these packages that are in the go.mod's module
86+
uris := make(map[protocol.DocumentURI]*metadata.Package)
87+
for id := range ids {
88+
for _, importer := range g.ImportedBy[id] {
89+
if importer.Module.GoMod != gomod.URI.Path() {
90+
continue
91+
}
92+
// files to be read.
93+
// at least one of them has an import to be reported.
94+
for _, uri := range importer.CompiledGoFiles {
95+
upkgs := g.ForFile[uri]
96+
for _, upkg := range upkgs {
97+
if upkg.ID == importer.ID {
98+
uris[uri] = importer // may overwrite, but it doesn't matter
99+
break
100+
}
101+
}
102+
}
103+
}
104+
}
105+
// C. for each of the uris, pick out the locs of imports from the requested module
106+
for uri := range uris {
107+
fh, err := snapshot.ReadFile(ctx, uri)
108+
if err != nil {
109+
return nil, err
110+
}
111+
pgf, err := snapshot.ParseGo(ctx, fh, parsego.Header) // Header promotes cache hits
112+
if err != nil {
113+
return nil, err
114+
}
115+
xpkg := uris[uri]
116+
for _, spec := range pgf.File.Imports {
117+
importPath := metadata.UnquoteImportPath(spec)
118+
if ids[xpkg.DepsByImpPath[importPath]] {
119+
loc, err := pgf.NodeLocation(spec.Path)
120+
if err != nil {
121+
return nil, err
122+
}
123+
locs[loc] = true
124+
continue
125+
}
126+
}
127+
}
128+
129+
ans := slices.SortedFunc(maps.Keys(locs), protocol.CompareLocation)
130+
return ans, nil
131+
}

‎gopls/internal/server/references.go‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"golang.org/x/tools/gopls/internal/file"
1111
"golang.org/x/tools/gopls/internal/golang"
1212
"golang.org/x/tools/gopls/internal/label"
13+
"golang.org/x/tools/gopls/internal/mod"
1314
"golang.org/x/tools/gopls/internal/protocol"
1415
"golang.org/x/tools/gopls/internal/telemetry"
1516
"golang.org/x/tools/gopls/internal/template"
@@ -35,6 +36,8 @@ func (s *server) References(ctx context.Context, params *protocol.ReferenceParam
3536
return template.References(ctx, snapshot, fh, params)
3637
case file.Go:
3738
return golang.References(ctx, snapshot, fh, params.Position, params.Context.IncludeDeclaration)
39+
case file.Mod:
40+
return mod.References(ctx, snapshot, fh, params)
3841
}
3942
return nil, nil // empty result
4043
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
-- go.work --
2+
go 1.22
3+
4+
use ./a
5+
use ./b
6+
use ./c
7+
8+
-- a/go.mod --
9+
module example.com/a
10+
11+
go 1.22
12+
13+
require example.com/b v0.0.1 //@ refs("/b", bmod, a_b, a2_b, a3_b)
14+
15+
-- a/a.go --
16+
package a
17+
18+
import "example.com/b" //@ loc(a_b, `"example.com/b"`)
19+
20+
var _ = b.B
21+
22+
-- a/a2.go --
23+
package a
24+
25+
import "example.com/b" //@ loc(a2_b, `"example.com/b"`)
26+
27+
var _ = b.B
28+
29+
-- a/a3/a3.go --
30+
package a
31+
32+
import "example.com/b" //@ loc(a3_b, `"example.com/b"`)
33+
34+
var _ = b.B
35+
36+
-- b/go.mod --
37+
module example.com/b //@ loc(bmod, re"^.*?b")
38+
39+
go 1.22
40+
41+
-- b/b.go --
42+
package b
43+
44+
var B int
45+
46+
-- c/go.mod --
47+
module example.com/c
48+
49+
go 1.22
50+
51+
require example.com/a v0.0.1
52+
require example.com/b v0.0.1 //@ refs("/b",bmod, c_b)
53+
-- c/c.go --
54+
package c
55+
56+
import "example.com/b" //@ loc(c_b, `"example.com/b"`)
57+
58+
var _ = b.B
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
Find references to example.com/b, but not to example.com/b/x
2+
-- go.work --
3+
go 1.22
4+
5+
use ./a
6+
use ./b
7+
use ./b/x
8+
use ./c
9+
10+
-- a/go.mod --
11+
module example.com/a
12+
13+
go 1.22
14+
15+
require example.com/b v0.0.1 //@ refs("/b", bmod, a_b, a2_b, a3_b)
16+
-- a/a.go --
17+
package a
18+
19+
import "example.com/b" //@ loc(a_b, `"example.com/b"`)
20+
import "example.com/b/x" //@ loc(a4_x, `"example.com/b/x"`)
21+
22+
var _ = b.B
23+
var _ = x.X
24+
25+
-- a/a2.go --
26+
package a
27+
28+
import "example.com/b" //@ loc(a2_b, `"example.com/b"`)
29+
30+
var _ = b.B
31+
32+
-- a/a3/a3.go --
33+
package a
34+
35+
import "example.com/b" //@ loc(a3_b, `"example.com/b"`)
36+
import "example.com/b/x" //@ loc(a3_x, `"example.com/b/x"`)
37+
38+
39+
var _ = b.B
40+
var _ = x.X
41+
42+
-- b/go.mod --
43+
module example.com/b //@ loc(bmod, re"^.*?b")
44+
45+
go 1.22
46+
47+
-- b/b.go --
48+
package b
49+
50+
var B int
51+
52+
-- b/x/go.mod --
53+
module example.com/b/x
54+
55+
go 1.22
56+
57+
-- b/x/x.go --
58+
package x
59+
60+
var X int
61+
-- c/go.mod --
62+
module example.com/c
63+
64+
go 1.22
65+
66+
require example.com/a v0.0.1
67+
require example.com/b v0.0.1 //@ refs("/b", bmod, c_b)
68+
-- c/c.go --
69+
package c
70+
71+
import "example.com/b" //@ loc(c_b, `"example.com/b"`)
72+
73+
var _ = b.B
74+

0 commit comments

Comments
 (0)