Skip to content

Commit 8a39d47

Browse files
vikblomgopherbot
authored andcommitted
gopls/internal/golang: Add "Eliminate dot import" code action.
The code action will qualify identifiers if possible. If there are names in scope which will shadow the package name then the code action fails. Updates golang/go#70319. Change-Id: I7c1ff1c60d592cb6f1093ab653c04a44d7092607 Reviewed-on: https://go-review.googlesource.com/c/tools/+/642016 Auto-Submit: Robert Findley <[email protected]> Reviewed-by: Robert Findley <[email protected]> Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 99337eb commit 8a39d47

File tree

5 files changed

+166
-9
lines changed

5 files changed

+166
-9
lines changed

gopls/doc/features/transformation.md

+9
Original file line numberDiff line numberDiff line change
@@ -814,3 +814,12 @@ which HTML documents are composed:
814814
815815
![Before "Add cases for Addr"](../assets/fill-switch-enum-before.png)
816816
![After "Add cases for Addr"](../assets/fill-switch-enum-after.png)
817+
818+
819+
<a name='refactor.rewrite.eliminateDotImport'></a>
820+
### `refactor.rewrite.eliminateDotImport`: Eliminate dot import
821+
822+
When the cursor is on a dot import gopls can offer the "Eliminate dot import"
823+
code action, which removes the dot from the import and qualifies uses of the
824+
package throughout the file. This code action is offered only if
825+
each use of the package can be qualified without collisions with existing names.

gopls/doc/release/v0.19.0.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# New features
2+
3+
## "Eliminate dot import" code action
4+
5+
This code action, available on a dotted import, will offer to replace
6+
the import with a regular one and qualify each use of the package
7+
with its name.

gopls/internal/golang/codeaction.go

+100
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ var codeActionProducers = [...]codeActionProducer{
260260
{kind: settings.RefactorRewriteMoveParamLeft, fn: refactorRewriteMoveParamLeft, needPkg: true},
261261
{kind: settings.RefactorRewriteMoveParamRight, fn: refactorRewriteMoveParamRight, needPkg: true},
262262
{kind: settings.RefactorRewriteSplitLines, fn: refactorRewriteSplitLines, needPkg: true},
263+
{kind: settings.RefactorRewriteEliminateDotImport, fn: refactorRewriteEliminateDotImport, needPkg: true},
263264

264265
// Note: don't forget to update the allow-list in Server.CodeAction
265266
// when adding new query operations like GoTest and GoDoc that
@@ -678,6 +679,105 @@ func refactorRewriteSplitLines(ctx context.Context, req *codeActionsRequest) err
678679
return nil
679680
}
680681

682+
func refactorRewriteEliminateDotImport(ctx context.Context, req *codeActionsRequest) error {
683+
// Figure out if the request is placed over a dot import.
684+
var importSpec *ast.ImportSpec
685+
for _, imp := range req.pgf.File.Imports {
686+
if posRangeContains(imp.Pos(), imp.End(), req.start, req.end) {
687+
importSpec = imp
688+
break
689+
}
690+
}
691+
if importSpec == nil {
692+
return nil
693+
}
694+
if importSpec.Name == nil || importSpec.Name.Name != "." {
695+
return nil
696+
}
697+
698+
// dotImported package path and its imported name after removing the dot.
699+
imported := req.pkg.TypesInfo().PkgNameOf(importSpec).Imported()
700+
newName := imported.Name()
701+
702+
rng, err := req.pgf.PosRange(importSpec.Name.Pos(), importSpec.Path.Pos())
703+
if err != nil {
704+
return err
705+
}
706+
// Delete the '.' part of the import.
707+
edits := []protocol.TextEdit{{
708+
Range: rng,
709+
}}
710+
711+
fileScope, ok := req.pkg.TypesInfo().Scopes[req.pgf.File]
712+
if !ok {
713+
return nil
714+
}
715+
716+
// Go through each use of the dot imported package, checking its scope for
717+
// shadowing and calculating an edit to qualify the identifier.
718+
var stack []ast.Node
719+
ast.Inspect(req.pgf.File, func(n ast.Node) bool {
720+
if n == nil {
721+
stack = stack[:len(stack)-1] // pop
722+
return false
723+
}
724+
stack = append(stack, n) // push
725+
726+
ident, ok := n.(*ast.Ident)
727+
if !ok {
728+
return true
729+
}
730+
// Only keep identifiers that use a symbol from the
731+
// dot imported package.
732+
use := req.pkg.TypesInfo().Uses[ident]
733+
if use == nil || use.Pkg() == nil {
734+
return true
735+
}
736+
if use.Pkg() != imported {
737+
return true
738+
}
739+
740+
// Only qualify unqualified identifiers (due to dot imports).
741+
// All other references to a symbol imported from another package
742+
// are nested within a select expression (pkg.Foo, v.Method, v.Field).
743+
if is[*ast.SelectorExpr](stack[len(stack)-2]) {
744+
return true
745+
}
746+
747+
// Make sure that the package name will not be shadowed by something else in scope.
748+
// If it is then we cannot offer this particular code action.
749+
//
750+
// TODO: If the object found in scope is the package imported without a
751+
// dot, or some builtin not used in the file, the code action could be
752+
// allowed to go through.
753+
sc := fileScope.Innermost(ident.Pos())
754+
if sc == nil {
755+
return true
756+
}
757+
_, obj := sc.LookupParent(newName, ident.Pos())
758+
if obj != nil {
759+
return true
760+
}
761+
762+
rng, err := req.pgf.PosRange(ident.Pos(), ident.Pos()) // sic, zero-width range before ident
763+
if err != nil {
764+
return true
765+
}
766+
edits = append(edits, protocol.TextEdit{
767+
Range: rng,
768+
NewText: newName + ".",
769+
})
770+
771+
return true
772+
})
773+
774+
req.addEditAction("Eliminate dot import", nil, protocol.DocumentChangeEdit(
775+
req.fh,
776+
edits,
777+
))
778+
return nil
779+
}
780+
681781
// refactorRewriteJoinLines produces "Join ITEMS into one line" code actions.
682782
// See [joinLines] for command implementation.
683783
func refactorRewriteJoinLines(ctx context.Context, req *codeActionsRequest) error {

gopls/internal/settings/codeactionkind.go

+10-9
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,16 @@ const (
8686
GoplsDocFeatures protocol.CodeActionKind = "gopls.doc.features"
8787

8888
// refactor.rewrite
89-
RefactorRewriteChangeQuote protocol.CodeActionKind = "refactor.rewrite.changeQuote"
90-
RefactorRewriteFillStruct protocol.CodeActionKind = "refactor.rewrite.fillStruct"
91-
RefactorRewriteFillSwitch protocol.CodeActionKind = "refactor.rewrite.fillSwitch"
92-
RefactorRewriteInvertIf protocol.CodeActionKind = "refactor.rewrite.invertIf"
93-
RefactorRewriteJoinLines protocol.CodeActionKind = "refactor.rewrite.joinLines"
94-
RefactorRewriteRemoveUnusedParam protocol.CodeActionKind = "refactor.rewrite.removeUnusedParam"
95-
RefactorRewriteMoveParamLeft protocol.CodeActionKind = "refactor.rewrite.moveParamLeft"
96-
RefactorRewriteMoveParamRight protocol.CodeActionKind = "refactor.rewrite.moveParamRight"
97-
RefactorRewriteSplitLines protocol.CodeActionKind = "refactor.rewrite.splitLines"
89+
RefactorRewriteChangeQuote protocol.CodeActionKind = "refactor.rewrite.changeQuote"
90+
RefactorRewriteFillStruct protocol.CodeActionKind = "refactor.rewrite.fillStruct"
91+
RefactorRewriteFillSwitch protocol.CodeActionKind = "refactor.rewrite.fillSwitch"
92+
RefactorRewriteInvertIf protocol.CodeActionKind = "refactor.rewrite.invertIf"
93+
RefactorRewriteJoinLines protocol.CodeActionKind = "refactor.rewrite.joinLines"
94+
RefactorRewriteRemoveUnusedParam protocol.CodeActionKind = "refactor.rewrite.removeUnusedParam"
95+
RefactorRewriteMoveParamLeft protocol.CodeActionKind = "refactor.rewrite.moveParamLeft"
96+
RefactorRewriteMoveParamRight protocol.CodeActionKind = "refactor.rewrite.moveParamRight"
97+
RefactorRewriteSplitLines protocol.CodeActionKind = "refactor.rewrite.splitLines"
98+
RefactorRewriteEliminateDotImport protocol.CodeActionKind = "refactor.rewrite.eliminateDotImport"
9899

99100
// refactor.inline
100101
RefactorInlineCall protocol.CodeActionKind = "refactor.inline.call"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
This test checks the behavior of the 'remove dot import' code action.
2+
3+
-- go.mod --
4+
module golang.org/lsptests/removedotimport
5+
6+
go 1.18
7+
8+
-- a.go --
9+
package dotimport
10+
11+
// Base case: action is OK.
12+
13+
import (
14+
. "fmt" //@codeaction(`.`, "refactor.rewrite.eliminateDotImport", edit=a1)
15+
. "bytes" //@codeaction(`.`, "refactor.rewrite.eliminateDotImport", edit=a2)
16+
)
17+
18+
var _ = a
19+
20+
func a() {
21+
Println("hello")
22+
23+
buf := NewBuffer(nil)
24+
buf.Grow(10)
25+
}
26+
27+
-- @a1/a.go --
28+
@@ -6 +6 @@
29+
- . "fmt" //@codeaction(`.`, "refactor.rewrite.eliminateDotImport", edit=a1)
30+
+ "fmt" //@codeaction(`.`, "refactor.rewrite.eliminateDotImport", edit=a1)
31+
@@ -13 +13 @@
32+
- Println("hello")
33+
+ fmt.Println("hello")
34+
-- @a2/a.go --
35+
@@ -7 +7 @@
36+
- . "bytes" //@codeaction(`.`, "refactor.rewrite.eliminateDotImport", edit=a2)
37+
+ "bytes" //@codeaction(`.`, "refactor.rewrite.eliminateDotImport", edit=a2)
38+
@@ -15 +15 @@
39+
- buf := NewBuffer(nil)
40+
+ buf := bytes.NewBuffer(nil)

0 commit comments

Comments
 (0)