internal/lsp: find references to package
Update References to detect if the package is referenced and a regtest to test within and external package references.
Updates golang/go#41567
Change-Id: I607a47bf15f1c9f8236336f795fcef081db49d6a
Reviewed-on: https://go-review.googlesource.com/c/tools/+/408714
Reviewed-by: Robert Findley <rfindley@google.com>
Run-TryBot: Dylan Le <dungtuanle@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
diff --git a/gopls/internal/regtest/misc/references_test.go b/gopls/internal/regtest/misc/references_test.go
index 7682516..de2e9b9 100644
--- a/gopls/internal/regtest/misc/references_test.go
+++ b/gopls/internal/regtest/misc/references_test.go
@@ -5,6 +5,8 @@
package misc
import (
+ "fmt"
+ "strings"
"testing"
. "golang.org/x/tools/internal/lsp/regtest"
@@ -81,3 +83,89 @@
}
})
}
+
+func TestPackageReferences(t *testing.T) {
+ tests := []struct {
+ packageName string
+ wantRefCount int
+ wantFiles []string
+ }{
+ {
+ "lib1",
+ 3,
+ []string{
+ "main.go",
+ "lib1/a.go",
+ "lib1/b.go",
+ },
+ },
+ {
+ "lib2",
+ 2,
+ []string{
+ "main.go",
+ "lib2/a.go",
+ },
+ },
+ }
+
+ const files = `
+-- go.mod --
+module mod.com
+
+go 1.18
+-- lib1/a.go --
+package lib1
+
+const A = 1
+
+-- lib1/b.go --
+package lib1
+
+const B = 1
+
+-- lib2/a.go --
+package lib2
+
+const C = 1
+
+-- main.go --
+package main
+
+import (
+ "mod.com/lib1"
+ "mod.com/lib2"
+)
+
+func main() {
+ println("Hello")
+}
+`
+ Run(t, files, func(t *testing.T, env *Env) {
+ for _, test := range tests {
+ f := fmt.Sprintf("%s/a.go", test.packageName)
+ env.OpenFile(f)
+ pos := env.RegexpSearch(f, test.packageName)
+ refs := env.References(fmt.Sprintf("%s/a.go", test.packageName), pos)
+ if len(refs) != test.wantRefCount {
+ t.Fatalf("got %v reference(s), want %d", len(refs), test.wantRefCount)
+ }
+ var refURIs []string
+ for _, ref := range refs {
+ refURIs = append(refURIs, string(ref.URI))
+ }
+ for _, base := range test.wantFiles {
+ hasBase := false
+ for _, ref := range refURIs {
+ if strings.HasSuffix(ref, base) {
+ hasBase = true
+ break
+ }
+ }
+ if !hasBase {
+ t.Fatalf("got [%v], want reference ends with \"%v\"", strings.Join(refURIs, ","), base)
+ }
+ }
+ }
+ })
+}
diff --git a/internal/lsp/source/references.go b/internal/lsp/source/references.go
index 3541600..85bf41a 100644
--- a/internal/lsp/source/references.go
+++ b/internal/lsp/source/references.go
@@ -9,12 +9,15 @@
"errors"
"fmt"
"go/ast"
+
"go/token"
"go/types"
"sort"
+ "strconv"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/lsp/protocol"
+ "golang.org/x/tools/internal/lsp/safetoken"
"golang.org/x/tools/internal/span"
)
@@ -34,6 +37,63 @@
ctx, done := event.Start(ctx, "source.References")
defer done()
+ // Find position of the package name declaration
+ pgf, err := s.ParseGo(ctx, f, ParseFull)
+ if err != nil {
+ return nil, err
+ }
+
+ cursorOffset, err := pgf.Mapper.Offset(pp)
+ if err != nil {
+ return nil, err
+ }
+
+ packageNameStart, err := safetoken.Offset(pgf.Tok, pgf.File.Name.Pos())
+ if err != nil {
+ return nil, err
+ }
+
+ packageNameEnd, err := safetoken.Offset(pgf.Tok, pgf.File.Name.End())
+ if err != nil {
+ return nil, err
+ }
+
+ if packageNameStart <= cursorOffset && cursorOffset < packageNameEnd {
+ renamingPkg, err := s.PackageForFile(ctx, f.URI(), TypecheckAll, NarrowestPackage)
+ if err != nil {
+ return nil, err
+ }
+
+ // Find external references to the package.
+ rdeps, err := s.GetReverseDependencies(ctx, renamingPkg.ID())
+ if err != nil {
+ return nil, err
+ }
+ var refs []*ReferenceInfo
+ for _, dep := range rdeps {
+ for _, f := range dep.CompiledGoFiles() {
+ for _, imp := range f.File.Imports {
+ if path, err := strconv.Unquote(imp.Path.Value); err == nil && path == renamingPkg.PkgPath() {
+ refs = append(refs, &ReferenceInfo{
+ Name: pgf.File.Name.Name,
+ MappedRange: NewMappedRange(s.FileSet(), f.Mapper, imp.Pos(), imp.End()),
+ })
+ }
+ }
+ }
+ }
+
+ // Find internal references to the package within the package itself
+ for _, f := range renamingPkg.CompiledGoFiles() {
+ refs = append(refs, &ReferenceInfo{
+ Name: pgf.File.Name.Name,
+ MappedRange: NewMappedRange(s.FileSet(), f.Mapper, f.File.Name.Pos(), f.File.Name.End()),
+ })
+ }
+
+ return refs, nil
+ }
+
qualifiedObjs, err := qualifiedObjsAtProtocolPos(ctx, s, f.URI(), pp)
// Don't return references for builtin types.
if errors.Is(err, errBuiltin) {