internal/refactor: AddImport: remove unnecessary result
And improve doc comment, and simplify the test.
Change-Id: I8ea0e3e71152245d2b3e675585175a84dc18b750
Reviewed-on: https://go-review.googlesource.com/c/tools/+/710315
Reviewed-by: Robert Findley <rfindley@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/go/analysis/passes/inline/gofix.go b/go/analysis/passes/inline/gofix.go
index 00d87b0..fe1032c 100644
--- a/go/analysis/passes/inline/gofix.go
+++ b/go/analysis/passes/inline/gofix.go
@@ -288,7 +288,7 @@
} else if _, ok := importPrefixes[pkgPath]; !ok {
// Use AddImport to add pkgPath if it's not there already. Associate the prefix it assigns
// with the package path for use by the TypeString qualifier below.
- _, prefix, eds := refactor.AddImport(
+ prefix, eds := refactor.AddImport(
a.pass.TypesInfo, curFile, pkgName, pkgPath, tn.Name(), id.Pos())
importPrefixes[pkgPath] = strings.TrimSuffix(prefix, ".")
edits = append(edits, eds...)
@@ -459,7 +459,7 @@
edits []analysis.TextEdit
)
if incon.RHSPkgPath != a.pass.Pkg.Path() {
- _, importPrefix, edits = refactor.AddImport(
+ importPrefix, edits = refactor.AddImport(
a.pass.TypesInfo, curFile, incon.RHSPkgName, incon.RHSPkgPath, incon.RHSName, n.Pos())
}
// If n is qualified by a package identifier, we'll need the full selector expression.
diff --git a/go/analysis/passes/modernize/maps.go b/go/analysis/passes/modernize/maps.go
index d8d9b6e..3072cf6 100644
--- a/go/analysis/passes/modernize/maps.go
+++ b/go/analysis/passes/modernize/maps.go
@@ -166,7 +166,7 @@
// Report diagnostic, and suggest fix.
rng := curRange.Node()
- _, prefix, importEdits := refactor.AddImport(info, file, "maps", "maps", funcName, rng.Pos())
+ prefix, importEdits := refactor.AddImport(info, file, "maps", "maps", funcName, rng.Pos())
var (
newText []byte
start, end token.Pos
diff --git a/go/analysis/passes/modernize/slices.go b/go/analysis/passes/modernize/slices.go
index 52e58f2..032f874 100644
--- a/go/analysis/passes/modernize/slices.go
+++ b/go/analysis/passes/modernize/slices.go
@@ -164,7 +164,7 @@
//
// This is unsound if s is empty and its nilness
// differs from zerocap (#73557).
- _, prefix, importEdits := refactor.AddImport(info, file, clonepkg, clonepkg, "Clone", call.Pos())
+ prefix, importEdits := refactor.AddImport(info, file, clonepkg, clonepkg, "Clone", call.Pos())
message := fmt.Sprintf("Replace append with %s.Clone", clonepkg)
pass.Report(analysis.Diagnostic{
Pos: call.Pos(),
@@ -185,7 +185,7 @@
// append(append(append(base, a...), b..., c...) -> slices.Concat(base, a, b, c)
//
// This is unsound if all slices are empty and base is non-nil (#73557).
- _, prefix, importEdits := refactor.AddImport(info, file, "slices", "slices", "Concat", call.Pos())
+ prefix, importEdits := refactor.AddImport(info, file, "slices", "slices", "Concat", call.Pos())
pass.Report(analysis.Diagnostic{
Pos: call.Pos(),
End: call.End(),
diff --git a/go/analysis/passes/modernize/slicescontains.go b/go/analysis/passes/modernize/slicescontains.go
index 2d521dd..b3c2e56 100644
--- a/go/analysis/passes/modernize/slicescontains.go
+++ b/go/analysis/passes/modernize/slicescontains.go
@@ -202,7 +202,7 @@
}
// Prepare slices.Contains{,Func} call.
- _, prefix, importEdits := refactor.AddImport(info, file, "slices", "slices", funcName, rng.Pos())
+ prefix, importEdits := refactor.AddImport(info, file, "slices", "slices", funcName, rng.Pos())
contains := fmt.Sprintf("%s%s(%s, %s)",
prefix,
funcName,
diff --git a/go/analysis/passes/modernize/slicesdelete.go b/go/analysis/passes/modernize/slicesdelete.go
index 305000f..b3e063d 100644
--- a/go/analysis/passes/modernize/slicesdelete.go
+++ b/go/analysis/passes/modernize/slicesdelete.go
@@ -62,7 +62,7 @@
return false
}
- _, prefix, edits := refactor.AddImport(info, file, "slices", "slices", "Delete", call.Pos())
+ prefix, edits := refactor.AddImport(info, file, "slices", "slices", "Delete", call.Pos())
// append's indices may be any integer type; slices.Delete requires int.
// Insert int conversions as needed (and if possible).
if isIntShadowed() && (!isIntExpr(slice1.High) || !isIntExpr(slice2.Low)) {
diff --git a/go/analysis/passes/modernize/sortslice.go b/go/analysis/passes/modernize/sortslice.go
index b2d04e1..66af16d 100644
--- a/go/analysis/passes/modernize/sortslice.go
+++ b/go/analysis/passes/modernize/sortslice.go
@@ -90,7 +90,7 @@
fileUses(info, file, "go1.21") {
// Have: sort.Slice(s, func(i, j int) bool { return s[i] < s[j] })
- _, prefix, importEdits := refactor.AddImport(
+ prefix, importEdits := refactor.AddImport(
info, file, "slices", "slices", "Sort", call.Pos())
pass.Report(analysis.Diagnostic{
diff --git a/go/analysis/passes/modernize/stringsbuilder.go b/go/analysis/passes/modernize/stringsbuilder.go
index ef128d5..56d0ba7 100644
--- a/go/analysis/passes/modernize/stringsbuilder.go
+++ b/go/analysis/passes/modernize/stringsbuilder.go
@@ -102,7 +102,7 @@
}
// Add strings import.
- _, prefix, importEdits := refactor.AddImport(
+ prefix, importEdits := refactor.AddImport(
pass.TypesInfo, astutil.EnclosingFile(def), "strings", "strings", "Builder", v.Pos())
edits = append(edits, importEdits...)
@@ -141,7 +141,7 @@
// => var s strings.Builder; s.WriteString(expr)
// Add strings import.
- _, prefix, importEdits := refactor.AddImport(
+ prefix, importEdits := refactor.AddImport(
pass.TypesInfo, astutil.EnclosingFile(def), "strings", "strings", "Builder", v.Pos())
edits = append(edits, importEdits...)
diff --git a/go/analysis/passes/modernize/stringscutprefix.go b/go/analysis/passes/modernize/stringscutprefix.go
index b2a5ae7..9e76f95 100644
--- a/go/analysis/passes/modernize/stringscutprefix.go
+++ b/go/analysis/passes/modernize/stringscutprefix.go
@@ -128,7 +128,7 @@
// shadow variables won't be valid because we only access the first statement (ditto Suffix).
if astutil.EqualSyntax(s0, s) && astutil.EqualSyntax(pre0, pre) {
after := refactor.FreshName(info.Scopes[ifStmt], ifStmt.Pos(), varName)
- _, prefix, importEdits := refactor.AddImport(
+ prefix, importEdits := refactor.AddImport(
info,
curFile.Node().(*ast.File),
obj1.Pkg().Name(),
@@ -213,7 +213,7 @@
// We use AddImport not to add an import (since it exists already)
// but to compute the correct prefix in the dot-import case.
- _, prefix, importEdits := refactor.AddImport(
+ prefix, importEdits := refactor.AddImport(
info,
curFile.Node().(*ast.File),
obj.Pkg().Name(),
diff --git a/go/analysis/passes/stringintconv/string.go b/go/analysis/passes/stringintconv/string.go
index 164fb27..7a02d85 100644
--- a/go/analysis/passes/stringintconv/string.go
+++ b/go/analysis/passes/stringintconv/string.go
@@ -198,7 +198,7 @@
// the type has methods, as some {String,GoString,Format}
// may change the behavior of fmt.Sprint.
if len(ttypes) == 1 && len(vtypes) == 1 && types.NewMethodSet(V0).Len() == 0 {
- _, prefix, importEdits := refactor.AddImport(pass.TypesInfo, file, "fmt", "fmt", "Sprint", arg.Pos())
+ prefix, importEdits := refactor.AddImport(pass.TypesInfo, file, "fmt", "fmt", "Sprint", arg.Pos())
if types.Identical(T0, types.Typ[types.String]) {
// string(x) -> fmt.Sprint(x)
addFix("Format the number as a decimal", append(importEdits,
diff --git a/gopls/internal/analysis/embeddirective/embeddirective.go b/gopls/internal/analysis/embeddirective/embeddirective.go
index b5218ce..2284055 100644
--- a/gopls/internal/analysis/embeddirective/embeddirective.go
+++ b/gopls/internal/analysis/embeddirective/embeddirective.go
@@ -47,7 +47,7 @@
if !hasEmbedImport {
// Add blank import of "embed".
- _, _, edits := refactor.AddImport(pass.TypesInfo, f, "_", "embed", "", c.Pos())
+ _, edits := refactor.AddImport(pass.TypesInfo, f, "_", "embed", "", c.Pos())
if len(edits) > 0 {
pass.Report(analysis.Diagnostic{
Pos: pos,
diff --git a/internal/refactor/imports.go b/internal/refactor/imports.go
index e52567c..1ba3a96 100644
--- a/internal/refactor/imports.go
+++ b/internal/refactor/imports.go
@@ -17,28 +17,25 @@
"golang.org/x/tools/internal/analysisinternal"
)
-// AddImport checks whether this file already imports pkgpath and that
-// the import is in scope at pos. If so, it returns the name under
-// which it was imported and no edits. Otherwise, it adds a new import
-// of pkgpath, using a name derived from the preferred name, and
-// returns the chosen name, a prefix to be concatenated with member to
-// form a qualified name, and the edit for the new import.
+// AddImport returns the prefix (either "pkg." or "") that should be
+// used to qualify references to the desired symbol (member) imported
+// from the specified package, plus any necessary edits to the file's
+// import declaration to add a new import.
//
-// The member argument indicates the name of the desired symbol within
-// the imported package. This is needed in the case when the existing
-// import is a dot import, because then it is possible that the
-// desired symbol is shadowed by other declarations in the current
-// package. If member is not shadowed at pos, AddImport returns (".",
-// "", nil). (AddImport accepts the caller's implicit claim that the
-// imported package declares member.)
+// If the import already exists, and is accessible at pos, AddImport
+// returns the existing name and no edits. (If the existing import is
+// a dot import, the prefix is "".)
//
-// Use a preferredName of "_" to request a blank import;
-// member is ignored in this case.
+// Otherwise, it adds a new import, using a local name derived from
+// the preferred name. To request a blank import, use a preferredName
+// of "_", and discard the prefix result; member is ignored in this
+// case.
//
-// It does not mutate its arguments.
+// AddImport accepts the caller's implicit claim that the imported
+// package declares member.
//
-// TODO(adonovan): needs dedicated tests.
-func AddImport(info *types.Info, file *ast.File, preferredName, pkgpath, member string, pos token.Pos) (name, prefix string, newImport []analysis.TextEdit) {
+// AddImport does not mutate its arguments.
+func AddImport(info *types.Info, file *ast.File, preferredName, pkgpath, member string, pos token.Pos) (prefix string, edits []analysis.TextEdit) {
// Find innermost enclosing lexical block.
scope := info.Scopes[file].Innermost(pos)
if scope == nil {
@@ -50,18 +47,18 @@
for _, spec := range file.Imports {
pkgname := info.PkgNameOf(spec)
if pkgname != nil && pkgname.Imported().Path() == pkgpath {
- name = pkgname.Name()
+ name := pkgname.Name()
if preferredName == "_" {
// Request for blank import; any existing import will do.
- return name, "", nil
+ return "", nil
}
if name == "." {
// The scope of ident must be the file scope.
if s, _ := scope.LookupParent(member, pos); s == info.Scopes[file] {
- return name, "", nil
+ return "", nil
}
} else if _, obj := scope.LookupParent(name, pos); obj == pkgname {
- return name, name + ".", nil
+ return name + ".", nil
}
}
}
@@ -122,7 +119,7 @@
pos = before.Pos()
newText = "import " + newText + "\n\n"
}
- return newName, newName + ".", []analysis.TextEdit{{
+ return newName + ".", []analysis.TextEdit{{
Pos: pos,
End: pos,
NewText: []byte(newText),
diff --git a/internal/refactor/imports_test.go b/internal/refactor/imports_test.go
index db40c25..072a9a9 100644
--- a/internal/refactor/imports_test.go
+++ b/internal/refactor/imports_test.go
@@ -31,10 +31,9 @@
panic("runtime.Caller failed")
}
- // Each test case contains a «name pkgpath»
- // section to be replaced with a reference
- // to a valid import of pkgpath,
- // ideally of the specified name.
+ // Each test case contains a «name pkgpath member»
+ // triple to be replaced with a valid qualified identifier
+ // to pkgpath.member, ideally of the specified name.
for _, test := range []struct {
descr, src, want string
}{
@@ -42,13 +41,13 @@
descr: descr("simple add import"),
src: `package a
func _() {
- «fmt fmt»
+ «fmt fmt Print»
}`,
want: `package a
import "fmt"
func _() {
- fmt
+ fmt.Print
}`,
},
{
@@ -58,14 +57,14 @@
import "fmt"
func _(fmt.Stringer) {
- «fmt fmt»
+ «fmt fmt Print»
}`,
want: `package a
import "fmt"
func _(fmt.Stringer) {
- fmt
+ fmt.Print
}`,
},
{
@@ -75,7 +74,7 @@
import _ "fmt"
func _() {
- «fmt fmt»
+ «fmt fmt Print»
}`,
want: `package a
@@ -84,7 +83,7 @@
import _ "fmt"
func _() {
- fmt
+ fmt.Print
}`,
},
{
@@ -96,7 +95,7 @@
var fmt int
func _(fmtpkg.Stringer) {
- «fmt fmt»
+ «fmt fmt Print»
}`,
want: `package a
@@ -105,7 +104,7 @@
var fmt int
func _(fmtpkg.Stringer) {
- fmtpkg
+ fmtpkg.Print
}`,
},
{
@@ -117,7 +116,7 @@
var _ fmt.Stringer
func _(fmt int) {
- «fmt fmt»
+ «fmt fmt Print»
}`,
want: `package a
@@ -128,7 +127,7 @@
var _ fmt.Stringer
func _(fmt int) {
- fmt0
+ fmt0.Print
}`,
},
{
@@ -138,7 +137,7 @@
import "fmt"
func _(fmt fmt.Stringer) {
- «fmt fmt»
+ «fmt fmt Print»
}`,
want: `package a
@@ -147,7 +146,7 @@
import "fmt"
func _(fmt fmt.Stringer) {
- fmt0
+ fmt0.Print
}`,
},
{
@@ -159,7 +158,7 @@
// world
func _() {
- «fmt fmt»
+ «fmt fmt Print»
}`,
want: `package a
@@ -170,7 +169,7 @@
// world
func _() {
- fmt
+ fmt.Print
}`,
},
{
@@ -178,14 +177,14 @@
src: `package a
func _() {
- «foo encoding/json»
+ «foo encoding/json Marshal»
}`,
want: `package a
import foo "encoding/json"
func _() {
- foo
+ foo.Marshal
}`,
},
{
@@ -195,14 +194,14 @@
import . "fmt"
func _() {
- «. fmt»
+ «. fmt Print»
}`,
want: `package a
import . "fmt"
func _() {
- .
+ Print
}`,
},
{
@@ -212,7 +211,7 @@
import . "fmt"
func _(Print fmt.Stringer) {
- «fmt fmt»
+ «fmt fmt Print»
}`,
want: `package a
@@ -221,7 +220,7 @@
import . "fmt"
func _(Print fmt.Stringer) {
- fmt
+ fmt.Print
}`,
},
{
@@ -233,7 +232,7 @@
)
func _(io.Reader) {
- «fmt fmt»
+ «fmt fmt Print»
}`,
want: `package a
@@ -243,7 +242,7 @@
)
func _(io.Reader) {
- fmt
+ fmt.Print
}`,
},
{
@@ -257,7 +256,7 @@
)
func _(io.Reader) {
- «fmt fmt»
+ «fmt fmt Print»
}`,
want: `package a
@@ -269,7 +268,7 @@
)
func _(io.Reader) {
- fmt
+ fmt.Print
}`,
},
{
@@ -281,7 +280,7 @@
import "vendor/golang.org/x/net/dns/dnsmessage"
func _(io.Reader) {
- «fmt fmt»
+ «fmt fmt Print»
}`,
want: `package a
@@ -292,7 +291,7 @@
import "vendor/golang.org/x/net/dns/dnsmessage"
func _(io.Reader) {
- fmt
+ fmt.Print
}`,
},
{
@@ -305,7 +304,7 @@
)
func _(io.Reader) {
- «fmt fmt»
+ «fmt fmt Print»
}`,
want: `package a
@@ -317,19 +316,23 @@
)
func _(io.Reader) {
- fmt
+ fmt.Print
}`,
},
} {
t.Run(test.descr, func(t *testing.T) {
- // splice marker
+ // splice marker (name pkgpath member)
before, mid, ok1 := strings.Cut(test.src, "«")
mid, after, ok2 := strings.Cut(mid, "»")
if !ok1 || !ok2 {
- t.Fatal("no «name path» marker")
+ t.Fatal("no «name path member» marker")
}
src := before + "/*!*/" + after
- name, path, _ := strings.Cut(mid, " ")
+ fields := strings.Fields(mid)
+ if len(fields) != 3 {
+ t.Fatalf("splice marker needs 3 fields (got %q)", mid)
+ }
+ name, path, member := fields[0], fields[1], fields[2]
// parse
fset := token.NewFileSet()
@@ -355,9 +358,7 @@
}
conf.Check(f.Name.Name, fset, []*ast.File{f}, info)
- // add import
- // The "Print" argument is only relevant for dot-import tests.
- name, prefix, edits := refactor.AddImport(info, f, name, path, "Print", pos)
+ prefix, edits := refactor.AddImport(info, f, name, path, member, pos)
var edit analysis.TextEdit
switch len(edits) {
@@ -368,20 +369,11 @@
t.Fatalf("expected at most one edit, got %d", len(edits))
}
- // prefix is a simple function of name.
- wantPrefix := name + "."
- if name == "." {
- wantPrefix = ""
- }
- if prefix != wantPrefix {
- t.Errorf("got prefix %q, want %q", prefix, wantPrefix)
- }
-
// apply patch
start := fset.Position(edit.Pos)
end := fset.Position(edit.End)
output := src[:start.Offset] + string(edit.NewText) + src[end.Offset:]
- output = strings.ReplaceAll(output, "/*!*/", name)
+ output = strings.ReplaceAll(output, "/*!*/", prefix+member)
if output != test.want {
t.Errorf("\n--got--\n%s\n--want--\n%s\n--diff--\n%s",
output, test.want, cmp.Diff(test.want, output))