Skip to content

Commit 933c7cc

Browse files
adonovangopherbot
authored andcommitted
internal/lsp/source: use exact match in import highlighting
Previously, hovering on a package name such as http.XYZ would highlight its import path ("net/http"), but also any other one that contained it as a substring, such as "net/http/httptest". (This behavior was there from the outset in CL 215258, but wasn't remarked upon during the review.) This change uses exact matching based on type-checker objects, not strings,, adds a test of same, and clarifies the logic. Fixes golang/go#60435 Change-Id: I9cc07dbcdaf54707d17be2a162bfcb0a22aa440a Reviewed-on: https://go-review.googlesource.com/c/tools/+/498268 Auto-Submit: Alan Donovan <[email protected]> Reviewed-by: Robert Findley <[email protected]> Run-TryBot: Alan Donovan <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 5974258 commit 933c7cc

File tree

7 files changed

+76
-77
lines changed

7 files changed

+76
-77
lines changed

gopls/internal/lsp/source/highlight.go

+55-59
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"go/ast"
1111
"go/token"
1212
"go/types"
13-
"strings"
1413

1514
"golang.org/x/tools/go/ast/astutil"
1615
"golang.org/x/tools/gopls/internal/lsp/protocol"
@@ -67,10 +66,27 @@ func highlightPath(path []ast.Node, file *ast.File, info *types.Info) (map[posRa
6766
result := make(map[posRange]struct{})
6867
switch node := path[0].(type) {
6968
case *ast.BasicLit:
69+
// Import path string literal?
7070
if len(path) > 1 {
71-
if _, ok := path[1].(*ast.ImportSpec); ok {
72-
err := highlightImportUses(path, info, result)
73-
return result, err
71+
if imp, ok := path[1].(*ast.ImportSpec); ok {
72+
highlight := func(n ast.Node) {
73+
result[posRange{start: n.Pos(), end: n.End()}] = struct{}{}
74+
}
75+
76+
// Highlight the import itself...
77+
highlight(imp)
78+
79+
// ...and all references to it in the file.
80+
if pkgname, ok := ImportedPkgName(info, imp); ok {
81+
ast.Inspect(file, func(n ast.Node) bool {
82+
if id, ok := n.(*ast.Ident); ok &&
83+
info.Uses[id] == pkgname {
84+
highlight(id)
85+
}
86+
return true
87+
})
88+
}
89+
return result, nil
7490
}
7591
}
7692
highlightFuncControlFlow(path, result)
@@ -419,66 +435,46 @@ Outer:
419435
})
420436
}
421437

422-
func highlightImportUses(path []ast.Node, info *types.Info, result map[posRange]struct{}) error {
423-
basicLit, ok := path[0].(*ast.BasicLit)
424-
if !ok {
425-
return fmt.Errorf("highlightImportUses called with an ast.Node of type %T", basicLit)
426-
}
427-
ast.Inspect(path[len(path)-1], func(node ast.Node) bool {
428-
if imp, ok := node.(*ast.ImportSpec); ok && imp.Path == basicLit {
429-
result[posRange{start: node.Pos(), end: node.End()}] = struct{}{}
430-
return false
431-
}
432-
n, ok := node.(*ast.Ident)
433-
if !ok {
434-
return true
435-
}
436-
obj, ok := info.ObjectOf(n).(*types.PkgName)
437-
if !ok {
438-
return true
439-
}
440-
if !strings.Contains(basicLit.Value, obj.Name()) {
441-
return true
442-
}
438+
func highlightIdentifier(id *ast.Ident, file *ast.File, info *types.Info, result map[posRange]struct{}) {
439+
highlight := func(n ast.Node) {
443440
result[posRange{start: n.Pos(), end: n.End()}] = struct{}{}
444-
return false
445-
})
446-
return nil
447-
}
441+
}
448442

449-
func highlightIdentifier(id *ast.Ident, file *ast.File, info *types.Info, result map[posRange]struct{}) {
450-
// TODO(rfindley): idObj may be nil. Note that returning early in this case
451-
// causes tests to fail (because the nObj == idObj check below was succeeded
452-
// for nil == nil!)
453-
//
454-
// Revisit this. If ObjectOf is nil, there are type errors, and it seems
455-
// reasonable for identifier highlighting not to work.
456-
idObj := info.ObjectOf(id)
457-
pkgObj, isImported := idObj.(*types.PkgName)
458-
ast.Inspect(file, func(node ast.Node) bool {
459-
if imp, ok := node.(*ast.ImportSpec); ok && isImported {
460-
highlightImport(pkgObj, imp, result)
461-
}
462-
n, ok := node.(*ast.Ident)
463-
if !ok {
464-
return true
465-
}
466-
if n.Name != id.Name {
467-
return false
468-
}
469-
if nObj := info.ObjectOf(n); nObj == idObj {
470-
result[posRange{start: n.Pos(), end: n.End()}] = struct{}{}
443+
// obj may be nil if the Ident is undefined.
444+
// In this case, the behavior expected by tests is
445+
// to match other undefined Idents of the same name.
446+
obj := info.ObjectOf(id)
447+
448+
ast.Inspect(file, func(n ast.Node) bool {
449+
switch n := n.(type) {
450+
case *ast.Ident:
451+
if n.Name == id.Name && info.ObjectOf(n) == obj {
452+
highlight(n)
453+
}
454+
455+
case *ast.ImportSpec:
456+
pkgname, ok := ImportedPkgName(info, n)
457+
if ok && pkgname == obj {
458+
if n.Name != nil {
459+
highlight(n.Name)
460+
} else {
461+
highlight(n)
462+
}
463+
}
471464
}
472-
return false
465+
return true
473466
})
474467
}
475468

476-
func highlightImport(obj *types.PkgName, imp *ast.ImportSpec, result map[posRange]struct{}) {
477-
if imp.Name != nil || imp.Path == nil {
478-
return
479-
}
480-
if !strings.Contains(imp.Path.Value, obj.Name()) {
481-
return
469+
// ImportedPkgName returns the PkgName object declared by an ImportSpec.
470+
// TODO(adonovan): make this a method of types.Info.
471+
func ImportedPkgName(info *types.Info, imp *ast.ImportSpec) (*types.PkgName, bool) {
472+
var obj types.Object
473+
if imp.Name != nil {
474+
obj = info.Defs[imp.Name]
475+
} else {
476+
obj = info.Implicits[imp]
482477
}
483-
result[posRange{start: imp.Path.Pos(), end: imp.Path.End()}] = struct{}{}
478+
pkgname, ok := obj.(*types.PkgName)
479+
return pkgname, ok
484480
}

gopls/internal/lsp/source/util.go

+1-7
Original file line numberDiff line numberDiff line change
@@ -255,13 +255,7 @@ func Qualifier(f *ast.File, pkg *types.Package, info *types.Info) types.Qualifie
255255
// Construct mapping of import paths to their defined or implicit names.
256256
imports := make(map[*types.Package]string)
257257
for _, imp := range f.Imports {
258-
var obj types.Object
259-
if imp.Name != nil {
260-
obj = info.Defs[imp.Name]
261-
} else {
262-
obj = info.Implicits[imp]
263-
}
264-
if pkgname, ok := obj.(*types.PkgName); ok {
258+
if pkgname, ok := ImportedPkgName(info, imp); ok {
265259
imports[pkgname.Imported()] = pkgname.Name()
266260
}
267261
}

gopls/internal/lsp/source/xrefs/xrefs.go

+3-8
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,11 @@ func Index(files []*source.ParsedGoFile, pkg *types.Package, info *types.Info) [
8787
case *ast.ImportSpec:
8888
// Report a reference from each import path
8989
// string to the imported package.
90-
var obj types.Object
91-
if n.Name != nil {
92-
obj = info.Defs[n.Name]
93-
} else {
94-
obj = info.Implicits[n]
95-
}
96-
if obj == nil {
90+
pkgname, ok := source.ImportedPkgName(info, n)
91+
if !ok {
9792
return true // missing import
9893
}
99-
objects := getObjects(obj.(*types.PkgName).Imported())
94+
objects := getObjects(pkgname.Imported())
10095
gobObj, ok := objects[nil]
10196
if !ok {
10297
gobObj = &gobObject{Path: ""}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package highlights
2+
3+
import (
4+
"net/http" //@mark(httpImp, `"net/http"`)
5+
"net/http/httptest" //@mark(httptestImp, `"net/http/httptest"`)
6+
)
7+
8+
// This is a regression test for issue 60435:
9+
// Highlighting "net/http" shouldn't have any effect
10+
// on an import path that contains it as a substring,
11+
// such as httptest.
12+
13+
var _ = httptest.NewRequest
14+
var _ = http.NewRequest //@mark(here, "http"), highlight(here, here, httpImp)

gopls/internal/lsp/testdata/summary.txt.golden

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ SuggestedFixCount = 73
1515
MethodExtractionCount = 6
1616
DefinitionsCount = 46
1717
TypeDefinitionsCount = 18
18-
HighlightsCount = 69
18+
HighlightsCount = 70
1919
InlayHintsCount = 4
2020
RenamesCount = 41
2121
PrepareRenamesCount = 7

gopls/internal/lsp/testdata/summary_go1.18.txt.golden

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ SuggestedFixCount = 79
1515
MethodExtractionCount = 6
1616
DefinitionsCount = 46
1717
TypeDefinitionsCount = 18
18-
HighlightsCount = 69
18+
HighlightsCount = 70
1919
InlayHintsCount = 5
2020
RenamesCount = 48
2121
PrepareRenamesCount = 7

gopls/internal/lsp/testdata/summary_go1.21.txt.golden

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ SuggestedFixCount = 79
1515
MethodExtractionCount = 6
1616
DefinitionsCount = 46
1717
TypeDefinitionsCount = 18
18-
HighlightsCount = 69
18+
HighlightsCount = 70
1919
InlayHintsCount = 5
2020
RenamesCount = 48
2121
PrepareRenamesCount = 7

0 commit comments

Comments
 (0)