internal/lsp: support renaming of import spec

This change allows renamings of the name of an import spec.
Since there is not always explicit identifier available to select and
rename, allow renaming packages from positions within the import spec.

Change-Id: I0a8aaa92c26e1795ddb9c31a1165b2f2ee89aa34
Reviewed-on: https://go-review.googlesource.com/c/tools/+/191165
Run-TryBot: Suzy Mueller <suzmue@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
diff --git a/internal/lsp/source/rename.go b/internal/lsp/source/rename.go
index 5ac65fb..00f26f5 100644
--- a/internal/lsp/source/rename.go
+++ b/internal/lsp/source/rename.go
@@ -40,14 +40,19 @@
 	ctx, done := trace.StartSpan(ctx, "source.Rename")
 	defer done()
 
+	// If the object declaration is nil, assume it is an import spec.
+	if i.decl.obj == nil {
+		// Find the corresponding package name for this import spec
+		// and rename that instead.
+		ident, err := i.getPkgName(ctx)
+		if err != nil {
+			return nil, err
+		}
+		return ident.Rename(ctx, newName)
+	}
 	if i.Name == newName {
 		return nil, errors.Errorf("old and new names are the same: %s", newName)
 	}
-	// If the object declaration is nil, assume it is an import spec and return an error.
-	// TODO(suzmue): support renaming of identifiers in an import spec.
-	if i.decl.obj == nil {
-		return nil, errors.Errorf("renaming import %q not supported", i.Name)
-	}
 	if !isValidIdentifier(i.Name) {
 		return nil, errors.Errorf("invalid identifier to rename: %q", i.Name)
 	}
@@ -104,6 +109,52 @@
 	return changes, nil
 }
 
+// getPkgName gets the pkg name associated with an identifer representing
+// the import path in an import spec.
+func (i *IdentifierInfo) getPkgName(ctx context.Context) (*IdentifierInfo, error) {
+	file := i.File.FileSet().File(i.Range.Start)
+	pkgLine := file.Line(i.Range.Start)
+
+	for _, obj := range i.pkg.GetTypesInfo().Defs {
+		pkgName, ok := obj.(*types.PkgName)
+		if ok && file.Line(pkgName.Pos()) == pkgLine {
+			return getPkgNameIdentifier(ctx, i, pkgName)
+		}
+	}
+	for _, obj := range i.pkg.GetTypesInfo().Implicits {
+		pkgName, ok := obj.(*types.PkgName)
+		if ok && file.Line(pkgName.Pos()) == pkgLine {
+			return getPkgNameIdentifier(ctx, i, pkgName)
+		}
+	}
+	return nil, errors.Errorf("no package name for %q", i.Name)
+}
+
+// getPkgNameIdentifier returns an IdentifierInfo representing pkgName.
+// pkgName must be in the same package and file as ident.
+func getPkgNameIdentifier(ctx context.Context, ident *IdentifierInfo, pkgName *types.PkgName) (*IdentifierInfo, error) {
+	decl := declaration{
+		obj:         pkgName,
+		wasImplicit: true,
+	}
+	var err error
+	if decl.rng, err = objToRange(ctx, ident.File.FileSet(), decl.obj); err != nil {
+		return nil, err
+	}
+	if decl.node, err = objToNode(ctx, ident.File.View(), ident.pkg.GetTypes(), decl.obj, decl.rng); err != nil {
+		return nil, err
+	}
+	return &IdentifierInfo{
+		Name:             pkgName.Name(),
+		Range:            decl.rng,
+		File:             ident.File,
+		decl:             decl,
+		pkg:              ident.pkg,
+		wasEmbeddedField: false,
+		qf:               ident.qf,
+	}, nil
+}
+
 // Rename all references to the identifier.
 func (r *renamer) update() (map[span.URI][]diff.TextEdit, error) {
 	result := make(map[span.URI][]diff.TextEdit)
diff --git a/internal/lsp/testdata/rename/a/random.go.golden b/internal/lsp/testdata/rename/a/random.go.golden
index 518757f..b1a20ff 100644
--- a/internal/lsp/testdata/rename/a/random.go.golden
+++ b/internal/lsp/testdata/rename/a/random.go.golden
@@ -5,7 +5,7 @@
 import (
 	lg "log"
 	"fmt" //@rename("fmt", "fmty")
-	f2 "fmt"
+	f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
 )
 
 func Random() int {
@@ -43,6 +43,96 @@
 	}
 }
 
+-- f2name-rename --
+random.go:
+package a
+
+import (
+	lg "log"
+	"fmt" //@rename("fmt", "fmty")
+	f2name "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
+)
+
+func Random() int {
+	y := 6 + 7
+	return y
+}
+
+func Random2(y int) int { //@rename("y", "z")
+	return y
+}
+
+type Pos struct {
+	x, y int
+}
+
+func (p *Pos) Sum() int {
+	return p.x + p.y //@rename("x", "myX")
+}
+
+func _() {
+	var p Pos   //@rename("p", "pos")
+	_ = p.Sum() //@rename("Sum", "GetSum")
+}
+
+func sw() {
+	var x interface{}
+
+	switch y := x.(type) { //@rename("y", "y0")
+	case int:
+		fmt.Printf("%d", y) //@rename("y", "y1"),rename("fmt", "format")
+	case string:
+		lg.Printf("%s", y) //@rename("y", "y2"),rename("lg","log")
+	default:
+		f2name.Printf("%v", y) //@rename("y", "y3"),rename("f2","fmt2")
+	}
+}
+
+-- f2y-rename --
+random.go:
+package a
+
+import (
+	lg "log"
+	"fmt" //@rename("fmt", "fmty")
+	f2y "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
+)
+
+func Random() int {
+	y := 6 + 7
+	return y
+}
+
+func Random2(y int) int { //@rename("y", "z")
+	return y
+}
+
+type Pos struct {
+	x, y int
+}
+
+func (p *Pos) Sum() int {
+	return p.x + p.y //@rename("x", "myX")
+}
+
+func _() {
+	var p Pos   //@rename("p", "pos")
+	_ = p.Sum() //@rename("Sum", "GetSum")
+}
+
+func sw() {
+	var x interface{}
+
+	switch y := x.(type) { //@rename("y", "y0")
+	case int:
+		fmt.Printf("%d", y) //@rename("y", "y1"),rename("fmt", "format")
+	case string:
+		lg.Printf("%s", y) //@rename("y", "y2"),rename("lg","log")
+	default:
+		f2y.Printf("%v", y) //@rename("y", "y3"),rename("f2","fmt2")
+	}
+}
+
 -- fmt2-rename --
 random.go:
 package a
@@ -50,7 +140,7 @@
 import (
 	lg "log"
 	"fmt" //@rename("fmt", "fmty")
-	fmt2 "fmt"
+	fmt2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
 )
 
 func Random() int {
@@ -89,7 +179,50 @@
 }
 
 -- fmty-rename --
-renaming import "fmt" not supported
+random.go:
+package a
+
+import (
+	lg "log"
+	fmty "fmt" //@rename("fmt", "fmty")
+	f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
+)
+
+func Random() int {
+	y := 6 + 7
+	return y
+}
+
+func Random2(y int) int { //@rename("y", "z")
+	return y
+}
+
+type Pos struct {
+	x, y int
+}
+
+func (p *Pos) Sum() int {
+	return p.x + p.y //@rename("x", "myX")
+}
+
+func _() {
+	var p Pos   //@rename("p", "pos")
+	_ = p.Sum() //@rename("Sum", "GetSum")
+}
+
+func sw() {
+	var x interface{}
+
+	switch y := x.(type) { //@rename("y", "y0")
+	case int:
+		fmty.Printf("%d", y) //@rename("y", "y1"),rename("fmt", "format")
+	case string:
+		lg.Printf("%s", y) //@rename("y", "y2"),rename("lg","log")
+	default:
+		f2.Printf("%v", y) //@rename("y", "y3"),rename("f2","fmt2")
+	}
+}
+
 -- format-rename --
 random.go:
 package a
@@ -97,7 +230,7 @@
 import (
 	lg "log"
 	format "fmt" //@rename("fmt", "fmty")
-	f2 "fmt"
+	f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
 )
 
 func Random() int {
@@ -142,7 +275,7 @@
 import (
 	"log"
 	"fmt" //@rename("fmt", "fmty")
-	f2 "fmt"
+	f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
 )
 
 func Random() int {
@@ -187,7 +320,7 @@
 import (
 	lg "log"
 	"fmt" //@rename("fmt", "fmty")
-	f2 "fmt"
+	f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
 )
 
 func Random() int {
@@ -232,7 +365,7 @@
 import (
 	lg "log"
 	"fmt" //@rename("fmt", "fmty")
-	f2 "fmt"
+	f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
 )
 
 func Random() int {
@@ -277,7 +410,7 @@
 import (
 	lg "log"
 	"fmt" //@rename("fmt", "fmty")
-	f2 "fmt"
+	f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
 )
 
 func Random() int {
@@ -322,7 +455,7 @@
 import (
 	lg "log"
 	"fmt" //@rename("fmt", "fmty")
-	f2 "fmt"
+	f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
 )
 
 func Random() int {
@@ -367,7 +500,7 @@
 import (
 	lg "log"
 	"fmt" //@rename("fmt", "fmty")
-	f2 "fmt"
+	f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
 )
 
 func Random() int {
@@ -412,7 +545,7 @@
 import (
 	lg "log"
 	"fmt" //@rename("fmt", "fmty")
-	f2 "fmt"
+	f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
 )
 
 func Random() int {
@@ -457,7 +590,7 @@
 import (
 	lg "log"
 	"fmt" //@rename("fmt", "fmty")
-	f2 "fmt"
+	f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
 )
 
 func Random() int {
diff --git a/internal/lsp/testdata/rename/a/random.go.in b/internal/lsp/testdata/rename/a/random.go.in
index 12026d0..069db27 100644
--- a/internal/lsp/testdata/rename/a/random.go.in
+++ b/internal/lsp/testdata/rename/a/random.go.in
@@ -3,7 +3,7 @@
 import (
 	lg "log"
 	"fmt" //@rename("fmt", "fmty")
-	f2 "fmt"
+	f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
 )
 
 func Random() int {
diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go
index a79c1e3..c0a5b31 100644
--- a/internal/lsp/tests/tests.go
+++ b/internal/lsp/tests/tests.go
@@ -38,7 +38,7 @@
 	ExpectedTypeDefinitionsCount   = 2
 	ExpectedHighlightsCount        = 2
 	ExpectedReferencesCount        = 5
-	ExpectedRenamesCount           = 18
+	ExpectedRenamesCount           = 20
 	ExpectedSymbolsCount           = 1
 	ExpectedSignaturesCount        = 21
 	ExpectedLinksCount             = 4