mirror of
https://github.com/golang/go
synced 2024-11-18 14:54:40 -07:00
df13fa7beb
I got tired of spurious 'git' diffs while a 'go test' was running, so I fixed the test that produced the diffs. (We need to do that anyway in order to run them in the module cache, plus it's just good hygiene not to have tests interfering with each other's sources.) Tested using: $ chmod -R ugo-w . && go test ./...; chmod -R u+w . Updates golang/go#28387 Change-Id: Ie17e31aecf0e3cb022df5503d7c443000366a5c6 Reviewed-on: https://go-review.googlesource.com/c/tools/+/192577 Run-TryBot: Bryan C. Mills <bcmills@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
1375 lines
26 KiB
Go
1375 lines
26 KiB
Go
// Copyright 2014 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package rename
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"go/build"
|
|
"go/token"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"golang.org/x/tools/go/buildutil"
|
|
"golang.org/x/tools/internal/testenv"
|
|
)
|
|
|
|
// TODO(adonovan): test reported source positions, somehow.
|
|
|
|
func TestConflicts(t *testing.T) {
|
|
defer func(savedWriteFile func(string, []byte) error, savedReportError func(token.Position, string)) {
|
|
writeFile = savedWriteFile
|
|
reportError = savedReportError
|
|
}(writeFile, reportError)
|
|
writeFile = func(string, []byte) error { return nil }
|
|
|
|
var ctxt *build.Context
|
|
for _, test := range []struct {
|
|
ctxt *build.Context // nil => use previous
|
|
offset, from, to string // values of the -offset/-from and -to flags
|
|
want string // regexp to match conflict errors, or "OK"
|
|
}{
|
|
// init() checks
|
|
{
|
|
ctxt: fakeContext(map[string][]string{
|
|
"fmt": {`package fmt; type Stringer interface { String() }`},
|
|
"main": {`
|
|
package main
|
|
|
|
import foo "fmt"
|
|
|
|
var v foo.Stringer
|
|
|
|
func f() { v.String(); f() }
|
|
`,
|
|
`package main; var w int`},
|
|
}),
|
|
from: "main.v", to: "init",
|
|
want: `you cannot have a var at package level named "init"`,
|
|
},
|
|
{
|
|
from: "main.f", to: "init",
|
|
want: `renaming this func "f" to "init" would make it a package initializer.*` +
|
|
`but references to it exist`,
|
|
},
|
|
{
|
|
from: "/go/src/main/0.go::foo", to: "init",
|
|
want: `"init" is not a valid imported package name`,
|
|
},
|
|
|
|
// Export checks
|
|
{
|
|
from: "fmt.Stringer", to: "stringer",
|
|
want: `renaming this type "Stringer" to "stringer" would make it unexported.*` +
|
|
`breaking references from packages such as "main"`,
|
|
},
|
|
{
|
|
from: "(fmt.Stringer).String", to: "string",
|
|
want: `renaming this method "String" to "string" would make it unexported.*` +
|
|
`breaking references from packages such as "main"`,
|
|
},
|
|
|
|
// Lexical scope checks
|
|
{
|
|
// file/package conflict, same file
|
|
from: "main.v", to: "foo",
|
|
want: `renaming this var "v" to "foo" would conflict.*` +
|
|
`with this imported package name`,
|
|
},
|
|
{
|
|
// file/package conflict, same file
|
|
from: "main::foo", to: "v",
|
|
want: `renaming this imported package name "foo" to "v" would conflict.*` +
|
|
`with this package member var`,
|
|
},
|
|
{
|
|
// file/package conflict, different files
|
|
from: "main.w", to: "foo",
|
|
want: `renaming this var "w" to "foo" would conflict.*` +
|
|
`with this imported package name`,
|
|
},
|
|
{
|
|
// file/package conflict, different files
|
|
from: "main::foo", to: "w",
|
|
want: `renaming this imported package name "foo" to "w" would conflict.*` +
|
|
`with this package member var`,
|
|
},
|
|
{
|
|
ctxt: main(`
|
|
package main
|
|
|
|
var x, z int
|
|
|
|
func f(y int) {
|
|
print(x)
|
|
print(y)
|
|
}
|
|
|
|
func g(w int) {
|
|
print(x)
|
|
x := 1
|
|
print(x)
|
|
}`),
|
|
from: "main.x", to: "y",
|
|
want: `renaming this var "x" to "y".*` +
|
|
`would cause this reference to become shadowed.*` +
|
|
`by this intervening var definition`,
|
|
},
|
|
{
|
|
from: "main.g::x", to: "w",
|
|
want: `renaming this var "x" to "w".*` +
|
|
`conflicts with var in same block`,
|
|
},
|
|
{
|
|
from: "main.f::y", to: "x",
|
|
want: `renaming this var "y" to "x".*` +
|
|
`would shadow this reference.*` +
|
|
`to the var declared here`,
|
|
},
|
|
{
|
|
from: "main.g::w", to: "x",
|
|
want: `renaming this var "w" to "x".*` +
|
|
`conflicts with var in same block`,
|
|
},
|
|
{
|
|
from: "main.z", to: "y", want: "OK",
|
|
},
|
|
|
|
// Label checks
|
|
{
|
|
ctxt: main(`
|
|
package main
|
|
|
|
func f() {
|
|
foo:
|
|
goto foo
|
|
bar:
|
|
goto bar
|
|
func(x int) {
|
|
wiz:
|
|
goto wiz
|
|
}(0)
|
|
}
|
|
`),
|
|
from: "main.f::foo", to: "bar",
|
|
want: `renaming this label "foo" to "bar".*` +
|
|
`would conflict with this one`,
|
|
},
|
|
{
|
|
from: "main.f::foo", to: "wiz", want: "OK",
|
|
},
|
|
{
|
|
from: "main.f::wiz", to: "x", want: "OK",
|
|
},
|
|
{
|
|
from: "main.f::x", to: "wiz", want: "OK",
|
|
},
|
|
{
|
|
from: "main.f::wiz", to: "foo", want: "OK",
|
|
},
|
|
|
|
// Struct fields
|
|
{
|
|
ctxt: main(`
|
|
package main
|
|
|
|
type U struct { u int }
|
|
type V struct { v int }
|
|
|
|
func (V) x() {}
|
|
|
|
type W (struct {
|
|
U
|
|
V
|
|
w int
|
|
})
|
|
|
|
func f() {
|
|
var w W
|
|
print(w.u) // NB: there is no selection of w.v
|
|
var _ struct { yy, zz int }
|
|
}
|
|
`),
|
|
// field/field conflict in named struct declaration
|
|
from: "(main.W).U", to: "w",
|
|
want: `renaming this field "U" to "w".*` +
|
|
`would conflict with this field`,
|
|
},
|
|
{
|
|
// rename type used as embedded field
|
|
// => rename field
|
|
// => field/field conflict
|
|
// This is an entailed renaming;
|
|
// it would be nice if we checked source positions.
|
|
from: "main.U", to: "w",
|
|
want: `renaming this field "U" to "w".*` +
|
|
`would conflict with this field`,
|
|
},
|
|
{
|
|
// field/field conflict in unnamed struct declaration
|
|
from: "main.f::zz", to: "yy",
|
|
want: `renaming this field "zz" to "yy".*` +
|
|
`would conflict with this field`,
|
|
},
|
|
|
|
// Now we test both directions of (u,v) (u,w) (v,w) (u,x) (v,x).
|
|
// Too bad we don't test position info...
|
|
{
|
|
// field/field ambiguity at same promotion level ('from' selection)
|
|
from: "(main.U).u", to: "v",
|
|
want: `renaming this field "u" to "v".*` +
|
|
`would make this reference ambiguous.*` +
|
|
`with this field`,
|
|
},
|
|
{
|
|
// field/field ambiguity at same promotion level ('to' selection)
|
|
from: "(main.V).v", to: "u",
|
|
want: `renaming this field "v" to "u".*` +
|
|
`would make this reference ambiguous.*` +
|
|
`with this field`,
|
|
},
|
|
{
|
|
// field/method conflict at different promotion level ('from' selection)
|
|
from: "(main.U).u", to: "w",
|
|
want: `renaming this field "u" to "w".*` +
|
|
`would change the referent of this selection.*` +
|
|
`of this field`,
|
|
},
|
|
{
|
|
// field/field shadowing at different promotion levels ('to' selection)
|
|
from: "(main.W).w", to: "u",
|
|
want: `renaming this field "w" to "u".*` +
|
|
`would shadow this selection.*` +
|
|
`of the field declared here`,
|
|
},
|
|
{
|
|
from: "(main.V).v", to: "w",
|
|
want: "OK", // since no selections are made ambiguous
|
|
},
|
|
{
|
|
from: "(main.W).w", to: "v",
|
|
want: "OK", // since no selections are made ambiguous
|
|
},
|
|
{
|
|
// field/method ambiguity at same promotion level ('from' selection)
|
|
from: "(main.U).u", to: "x",
|
|
want: `renaming this field "u" to "x".*` +
|
|
`would make this reference ambiguous.*` +
|
|
`with this method`,
|
|
},
|
|
{
|
|
// field/field ambiguity at same promotion level ('to' selection)
|
|
from: "(main.V).x", to: "u",
|
|
want: `renaming this method "x" to "u".*` +
|
|
`would make this reference ambiguous.*` +
|
|
`with this field`,
|
|
},
|
|
{
|
|
// field/method conflict at named struct declaration
|
|
from: "(main.V).v", to: "x",
|
|
want: `renaming this field "v" to "x".*` +
|
|
`would conflict with this method`,
|
|
},
|
|
{
|
|
// field/method conflict at named struct declaration
|
|
from: "(main.V).x", to: "v",
|
|
want: `renaming this method "x" to "v".*` +
|
|
`would conflict with this field`,
|
|
},
|
|
|
|
// Methods
|
|
{
|
|
ctxt: main(`
|
|
package main
|
|
type C int
|
|
func (C) f()
|
|
func (C) g()
|
|
type D int
|
|
func (*D) f()
|
|
func (*D) g()
|
|
type I interface { f(); g() }
|
|
type J interface { I; h() }
|
|
var _ I = new(D)
|
|
var _ interface {f()} = C(0)
|
|
`),
|
|
from: "(main.I).f", to: "g",
|
|
want: `renaming this interface method "f" to "g".*` +
|
|
`would conflict with this method`,
|
|
},
|
|
{
|
|
from: `("main".I).f`, to: "h", // NB: exercises quoted import paths too
|
|
want: `renaming this interface method "f" to "h".*` +
|
|
`would conflict with this method.*` +
|
|
`in named interface type "J"`,
|
|
},
|
|
{
|
|
// type J interface { h; h() } is not a conflict, amusingly.
|
|
from: "main.I", to: "h",
|
|
want: `OK`,
|
|
},
|
|
{
|
|
from: "(main.J).h", to: "f",
|
|
want: `renaming this interface method "h" to "f".*` +
|
|
`would conflict with this method`,
|
|
},
|
|
{
|
|
from: "(main.C).f", to: "e",
|
|
want: `renaming this method "f" to "e".*` +
|
|
`would make main.C no longer assignable to interface{f..}.*` +
|
|
`(rename interface{f..}.f if you intend to change both types)`,
|
|
},
|
|
{
|
|
from: "(main.D).g", to: "e",
|
|
want: `renaming this method "g" to "e".*` +
|
|
`would make \*main.D no longer assignable to interface I.*` +
|
|
`(rename main.I.g if you intend to change both types)`,
|
|
},
|
|
{
|
|
from: "(main.I).f", to: "e",
|
|
want: `OK`,
|
|
},
|
|
// Indirect C/I method coupling via another concrete type D.
|
|
{
|
|
ctxt: main(`
|
|
package main
|
|
type I interface { f() }
|
|
type C int
|
|
func (C) f()
|
|
type D struct{C}
|
|
var _ I = D{}
|
|
`),
|
|
from: "(main.C).f", to: "F",
|
|
want: `renaming this method "f" to "F".*` +
|
|
`would make main.D no longer assignable to interface I.*` +
|
|
`(rename main.I.f if you intend to change both types)`,
|
|
},
|
|
// Renaming causes promoted method to become shadowed; C no longer satisfies I.
|
|
{
|
|
ctxt: main(`
|
|
package main
|
|
type I interface { f() }
|
|
type C struct { I }
|
|
func (C) g() int
|
|
var _ I = C{}
|
|
`),
|
|
from: "main.I.f", to: "g",
|
|
want: `renaming this method "f" to "g".*` +
|
|
`would change the g method of main.C invoked via interface main.I.*` +
|
|
`from \(main.I\).g.*` +
|
|
`to \(main.C\).g`,
|
|
},
|
|
// Renaming causes promoted method to become ambiguous; C no longer satisfies I.
|
|
{
|
|
ctxt: main(`
|
|
package main
|
|
type I interface{f()}
|
|
type C int
|
|
func (C) f()
|
|
type D int
|
|
func (D) g()
|
|
type E struct{C;D}
|
|
var _ I = E{}
|
|
`),
|
|
from: "main.I.f", to: "g",
|
|
want: `renaming this method "f" to "g".*` +
|
|
`would make the g method of main.E invoked via interface main.I ambiguous.*` +
|
|
`with \(main.D\).g`,
|
|
},
|
|
} {
|
|
var conflicts []string
|
|
reportError = func(posn token.Position, message string) {
|
|
conflicts = append(conflicts, message)
|
|
}
|
|
if test.ctxt != nil {
|
|
ctxt = test.ctxt
|
|
}
|
|
err := Main(ctxt, test.offset, test.from, test.to)
|
|
var prefix string
|
|
if test.offset == "" {
|
|
prefix = fmt.Sprintf("-from %q -to %q", test.from, test.to)
|
|
} else {
|
|
prefix = fmt.Sprintf("-offset %q -to %q", test.offset, test.to)
|
|
}
|
|
if err == ConflictError {
|
|
got := strings.Join(conflicts, "\n")
|
|
if false {
|
|
t.Logf("%s: %s", prefix, got)
|
|
}
|
|
pattern := "(?s:" + test.want + ")" // enable multi-line matching
|
|
if !regexp.MustCompile(pattern).MatchString(got) {
|
|
t.Errorf("%s: conflict does not match pattern:\n"+
|
|
"Conflict:\t%s\n"+
|
|
"Pattern: %s",
|
|
prefix, got, test.want)
|
|
}
|
|
} else if err != nil {
|
|
t.Errorf("%s: unexpected error: %s", prefix, err)
|
|
} else if test.want != "OK" {
|
|
t.Errorf("%s: unexpected success, want conflicts matching:\n%s",
|
|
prefix, test.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInvalidIdentifiers(t *testing.T) {
|
|
ctxt := fakeContext(map[string][]string{
|
|
"main": {`
|
|
package main
|
|
|
|
func f() { }
|
|
`}})
|
|
|
|
for _, test := range []struct {
|
|
from, to string // values of the -offset/-from and -to flags
|
|
want string // expected error message
|
|
}{
|
|
{
|
|
from: "main.f", to: "_",
|
|
want: `-to "_": not a valid identifier`,
|
|
},
|
|
{
|
|
from: "main.f", to: "123",
|
|
want: `-to "123": not a valid identifier`,
|
|
},
|
|
{
|
|
from: "main.f", to: "for",
|
|
want: `-to "for": not a valid identifier`,
|
|
},
|
|
{
|
|
from: "switch", to: "v",
|
|
want: `-from "switch": invalid expression`,
|
|
},
|
|
} {
|
|
err := Main(ctxt, "", test.from, test.to)
|
|
prefix := fmt.Sprintf("-from %q -to %q", test.from, test.to)
|
|
if err == nil {
|
|
t.Errorf("%s: expected error %q", prefix, test.want)
|
|
} else if err.Error() != test.want {
|
|
t.Errorf("%s: unexpected error\nwant: %s\n got: %s", prefix, test.want, err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRewrites(t *testing.T) {
|
|
defer func(savedWriteFile func(string, []byte) error) {
|
|
writeFile = savedWriteFile
|
|
}(writeFile)
|
|
|
|
var ctxt *build.Context
|
|
for _, test := range []struct {
|
|
ctxt *build.Context // nil => use previous
|
|
offset, from, to string // values of the -from/-offset and -to flags
|
|
want map[string]string // contents of updated files
|
|
}{
|
|
// Elimination of renaming import.
|
|
{
|
|
ctxt: fakeContext(map[string][]string{
|
|
"foo": {`package foo; type T int`},
|
|
"main": {`package main
|
|
|
|
import foo2 "foo"
|
|
|
|
var _ foo2.T
|
|
`},
|
|
}),
|
|
from: "main::foo2", to: "foo",
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
import "foo"
|
|
|
|
var _ foo.T
|
|
`,
|
|
},
|
|
},
|
|
// Introduction of renaming import.
|
|
{
|
|
ctxt: fakeContext(map[string][]string{
|
|
"foo": {`package foo; type T int`},
|
|
"main": {`package main
|
|
|
|
import "foo"
|
|
|
|
var _ foo.T
|
|
`},
|
|
}),
|
|
offset: "/go/src/main/0.go:#36", to: "foo2", // the "foo" in foo.T
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
import foo2 "foo"
|
|
|
|
var _ foo2.T
|
|
`,
|
|
},
|
|
},
|
|
// Renaming of package-level member.
|
|
{
|
|
from: "foo.T", to: "U",
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
import "foo"
|
|
|
|
var _ foo.U
|
|
`,
|
|
"/go/src/foo/0.go": `package foo
|
|
|
|
type U int
|
|
`,
|
|
},
|
|
},
|
|
// Rename package-level func plus doc
|
|
{
|
|
ctxt: main(`package main
|
|
|
|
// Foo is a no-op.
|
|
// Calling Foo does nothing.
|
|
func Foo() {
|
|
}
|
|
`),
|
|
from: "main.Foo", to: "FooBar",
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
// FooBar is a no-op.
|
|
// Calling FooBar does nothing.
|
|
func FooBar() {
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
// Rename method plus doc
|
|
{
|
|
ctxt: main(`package main
|
|
|
|
type Foo struct{}
|
|
|
|
// Bar does nothing.
|
|
func (Foo) Bar() {
|
|
}
|
|
`),
|
|
from: "main.Foo.Bar", to: "Baz",
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
type Foo struct{}
|
|
|
|
// Baz does nothing.
|
|
func (Foo) Baz() {
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
// Rename type spec plus doc
|
|
{
|
|
ctxt: main(`package main
|
|
|
|
type (
|
|
// Test but not Testing.
|
|
Test struct{}
|
|
)
|
|
`),
|
|
from: "main.Test", to: "Type",
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
type (
|
|
// Type but not Testing.
|
|
Type struct{}
|
|
)
|
|
`,
|
|
},
|
|
},
|
|
// Rename type in gen decl plus doc
|
|
{
|
|
ctxt: main(`package main
|
|
|
|
// T is a test type.
|
|
type T struct{}
|
|
`),
|
|
from: "main.T", to: "Type",
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
// Type is a test type.
|
|
type Type struct{}
|
|
`,
|
|
},
|
|
},
|
|
// Rename value spec with doc
|
|
{
|
|
ctxt: main(`package main
|
|
|
|
const (
|
|
// C is the speed of light.
|
|
C = 2.998e8
|
|
)
|
|
`),
|
|
from: "main.C", to: "Lightspeed",
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
const (
|
|
// Lightspeed is the speed of light.
|
|
Lightspeed = 2.998e8
|
|
)
|
|
`,
|
|
},
|
|
},
|
|
// Rename value inside gen decl with doc
|
|
{
|
|
ctxt: main(`package main
|
|
|
|
var out *string
|
|
`),
|
|
from: "main.out", to: "discard",
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
var discard *string
|
|
`,
|
|
},
|
|
},
|
|
// Rename field plus doc
|
|
{
|
|
ctxt: main(`package main
|
|
|
|
type Struct struct {
|
|
// Field is a struct field.
|
|
Field string
|
|
}
|
|
`),
|
|
from: "main.Struct.Field", to: "Foo",
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
type Struct struct {
|
|
// Foo is a struct field.
|
|
Foo string
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
// Label renamings.
|
|
{
|
|
ctxt: main(`package main
|
|
func f() {
|
|
loop:
|
|
loop := 0
|
|
go func() {
|
|
loop:
|
|
goto loop
|
|
}()
|
|
loop++
|
|
goto loop
|
|
}
|
|
`),
|
|
offset: "/go/src/main/0.go:#25", to: "loop2", // def of outer label "loop"
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
func f() {
|
|
loop2:
|
|
loop := 0
|
|
go func() {
|
|
loop:
|
|
goto loop
|
|
}()
|
|
loop++
|
|
goto loop2
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
offset: "/go/src/main/0.go:#70", to: "loop2", // ref to inner label "loop"
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
func f() {
|
|
loop:
|
|
loop := 0
|
|
go func() {
|
|
loop2:
|
|
goto loop2
|
|
}()
|
|
loop++
|
|
goto loop
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
// Renaming of type used as embedded field.
|
|
{
|
|
ctxt: main(`package main
|
|
|
|
type T int
|
|
type U struct { T }
|
|
|
|
var _ = U{}.T
|
|
`),
|
|
from: "main.T", to: "T2",
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
type T2 int
|
|
type U struct{ T2 }
|
|
|
|
var _ = U{}.T2
|
|
`,
|
|
},
|
|
},
|
|
// Renaming of embedded field.
|
|
{
|
|
ctxt: main(`package main
|
|
|
|
type T int
|
|
type U struct { T }
|
|
|
|
var _ = U{}.T
|
|
`),
|
|
offset: "/go/src/main/0.go:#58", to: "T2", // T in "U{}.T"
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
type T2 int
|
|
type U struct{ T2 }
|
|
|
|
var _ = U{}.T2
|
|
`,
|
|
},
|
|
},
|
|
// Renaming of pointer embedded field.
|
|
{
|
|
ctxt: main(`package main
|
|
|
|
type T int
|
|
type U struct { *T }
|
|
|
|
var _ = U{}.T
|
|
`),
|
|
offset: "/go/src/main/0.go:#59", to: "T2", // T in "U{}.T"
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
type T2 int
|
|
type U struct{ *T2 }
|
|
|
|
var _ = U{}.T2
|
|
`,
|
|
},
|
|
},
|
|
|
|
// Lexical scope tests.
|
|
{
|
|
ctxt: main(`package main
|
|
|
|
var y int
|
|
|
|
func f() {
|
|
print(y)
|
|
y := ""
|
|
print(y)
|
|
}
|
|
`),
|
|
from: "main.y", to: "x",
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
var x int
|
|
|
|
func f() {
|
|
print(x)
|
|
y := ""
|
|
print(y)
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
from: "main.f::y", to: "x",
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
var y int
|
|
|
|
func f() {
|
|
print(y)
|
|
x := ""
|
|
print(x)
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
// Renaming of typeswitch vars (a corner case).
|
|
{
|
|
ctxt: main(`package main
|
|
|
|
func f(z interface{}) {
|
|
switch y := z.(type) {
|
|
case int:
|
|
print(y)
|
|
default:
|
|
print(y)
|
|
}
|
|
}
|
|
`),
|
|
offset: "/go/src/main/0.go:#46", to: "x", // def of y
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
func f(z interface{}) {
|
|
switch x := z.(type) {
|
|
case int:
|
|
print(x)
|
|
default:
|
|
print(x)
|
|
}
|
|
}
|
|
`},
|
|
},
|
|
{
|
|
offset: "/go/src/main/0.go:#81", to: "x", // ref of y in case int
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
func f(z interface{}) {
|
|
switch x := z.(type) {
|
|
case int:
|
|
print(x)
|
|
default:
|
|
print(x)
|
|
}
|
|
}
|
|
`},
|
|
},
|
|
{
|
|
offset: "/go/src/main/0.go:#102", to: "x", // ref of y in default case
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
func f(z interface{}) {
|
|
switch x := z.(type) {
|
|
case int:
|
|
print(x)
|
|
default:
|
|
print(x)
|
|
}
|
|
}
|
|
`},
|
|
},
|
|
|
|
// Renaming of embedded field that is a qualified reference.
|
|
// (Regression test for bug 8924.)
|
|
{
|
|
ctxt: fakeContext(map[string][]string{
|
|
"foo": {`package foo; type T int`},
|
|
"main": {`package main
|
|
|
|
import "foo"
|
|
|
|
type _ struct{ *foo.T }
|
|
`},
|
|
}),
|
|
offset: "/go/src/main/0.go:#48", to: "U", // the "T" in *foo.T
|
|
want: map[string]string{
|
|
"/go/src/foo/0.go": `package foo
|
|
|
|
type U int
|
|
`,
|
|
"/go/src/main/0.go": `package main
|
|
|
|
import "foo"
|
|
|
|
type _ struct{ *foo.U }
|
|
`,
|
|
},
|
|
},
|
|
|
|
// Renaming of embedded field that is a qualified reference with the '-from' flag.
|
|
// (Regression test for bug 12038.)
|
|
{
|
|
ctxt: fakeContext(map[string][]string{
|
|
"foo": {`package foo; type T int`},
|
|
"main": {`package main
|
|
|
|
import "foo"
|
|
|
|
type V struct{ *foo.T }
|
|
`},
|
|
}),
|
|
from: "(main.V).T", to: "U", // the "T" in *foo.T
|
|
want: map[string]string{
|
|
"/go/src/foo/0.go": `package foo
|
|
|
|
type U int
|
|
`,
|
|
"/go/src/main/0.go": `package main
|
|
|
|
import "foo"
|
|
|
|
type V struct{ *foo.U }
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
ctxt: fakeContext(map[string][]string{
|
|
"foo": {`package foo; type T int`},
|
|
"main": {`package main
|
|
|
|
import "foo"
|
|
|
|
type V struct{ foo.T }
|
|
`},
|
|
}),
|
|
from: "(main.V).T", to: "U", // the "T" in *foo.T
|
|
want: map[string]string{
|
|
"/go/src/foo/0.go": `package foo
|
|
|
|
type U int
|
|
`,
|
|
"/go/src/main/0.go": `package main
|
|
|
|
import "foo"
|
|
|
|
type V struct{ foo.U }
|
|
`,
|
|
},
|
|
},
|
|
|
|
// Interface method renaming.
|
|
{
|
|
ctxt: fakeContext(map[string][]string{
|
|
"main": {`
|
|
package main
|
|
type I interface {
|
|
f()
|
|
}
|
|
type J interface { f(); g() }
|
|
type A int
|
|
func (A) f()
|
|
type B int
|
|
func (B) f()
|
|
func (B) g()
|
|
type C int
|
|
func (C) f()
|
|
func (C) g()
|
|
var _, _ I = A(0), B(0)
|
|
var _, _ J = B(0), C(0)
|
|
`,
|
|
},
|
|
}),
|
|
offset: "/go/src/main/0.go:#34", to: "F", // abstract method I.f
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
type I interface {
|
|
F()
|
|
}
|
|
type J interface {
|
|
F()
|
|
g()
|
|
}
|
|
type A int
|
|
|
|
func (A) F()
|
|
|
|
type B int
|
|
|
|
func (B) F()
|
|
func (B) g()
|
|
|
|
type C int
|
|
|
|
func (C) F()
|
|
func (C) g()
|
|
|
|
var _, _ I = A(0), B(0)
|
|
var _, _ J = B(0), C(0)
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
offset: "/go/src/main/0.go:#59", to: "F", // abstract method J.f
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
type I interface {
|
|
F()
|
|
}
|
|
type J interface {
|
|
F()
|
|
g()
|
|
}
|
|
type A int
|
|
|
|
func (A) F()
|
|
|
|
type B int
|
|
|
|
func (B) F()
|
|
func (B) g()
|
|
|
|
type C int
|
|
|
|
func (C) F()
|
|
func (C) g()
|
|
|
|
var _, _ I = A(0), B(0)
|
|
var _, _ J = B(0), C(0)
|
|
`,
|
|
},
|
|
},
|
|
{
|
|
offset: "/go/src/main/0.go:#64", to: "G", // abstract method J.g
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
type I interface {
|
|
f()
|
|
}
|
|
type J interface {
|
|
f()
|
|
G()
|
|
}
|
|
type A int
|
|
|
|
func (A) f()
|
|
|
|
type B int
|
|
|
|
func (B) f()
|
|
func (B) G()
|
|
|
|
type C int
|
|
|
|
func (C) f()
|
|
func (C) G()
|
|
|
|
var _, _ I = A(0), B(0)
|
|
var _, _ J = B(0), C(0)
|
|
`,
|
|
},
|
|
},
|
|
// Indirect coupling of I.f to C.f from D->I assignment and anonymous field of D.
|
|
{
|
|
ctxt: fakeContext(map[string][]string{
|
|
"main": {`
|
|
package main
|
|
type I interface {
|
|
f()
|
|
}
|
|
type C int
|
|
func (C) f()
|
|
type D struct{C}
|
|
var _ I = D{}
|
|
`,
|
|
},
|
|
}),
|
|
offset: "/go/src/main/0.go:#34", to: "F", // abstract method I.f
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
type I interface {
|
|
F()
|
|
}
|
|
type C int
|
|
|
|
func (C) F()
|
|
|
|
type D struct{ C }
|
|
|
|
var _ I = D{}
|
|
`,
|
|
},
|
|
},
|
|
// Interface embedded in struct. No conflict if C need not satisfy I.
|
|
{
|
|
ctxt: fakeContext(map[string][]string{
|
|
"main": {`
|
|
package main
|
|
type I interface {
|
|
f()
|
|
}
|
|
type C struct{I}
|
|
func (C) g() int
|
|
var _ int = C{}.g()
|
|
`,
|
|
},
|
|
}),
|
|
offset: "/go/src/main/0.go:#34", to: "g", // abstract method I.f
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
type I interface {
|
|
g()
|
|
}
|
|
type C struct{ I }
|
|
|
|
func (C) g() int
|
|
|
|
var _ int = C{}.g()
|
|
`,
|
|
},
|
|
},
|
|
// A type assertion causes method coupling iff signatures match.
|
|
{
|
|
ctxt: fakeContext(map[string][]string{
|
|
"main": {`package main
|
|
type I interface{
|
|
f()
|
|
}
|
|
type J interface{
|
|
f()
|
|
}
|
|
var _ = I(nil).(J)
|
|
`,
|
|
},
|
|
}),
|
|
offset: "/go/src/main/0.go:#32", to: "g", // abstract method I.f
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
type I interface {
|
|
g()
|
|
}
|
|
type J interface {
|
|
g()
|
|
}
|
|
|
|
var _ = I(nil).(J)
|
|
`,
|
|
},
|
|
},
|
|
// Impossible type assertion: no method coupling.
|
|
{
|
|
ctxt: fakeContext(map[string][]string{
|
|
"main": {`package main
|
|
type I interface{
|
|
f()
|
|
}
|
|
type J interface{
|
|
f()int
|
|
}
|
|
var _ = I(nil).(J)
|
|
`,
|
|
},
|
|
}),
|
|
offset: "/go/src/main/0.go:#32", to: "g", // abstract method I.f
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
type I interface {
|
|
g()
|
|
}
|
|
type J interface {
|
|
f() int
|
|
}
|
|
|
|
var _ = I(nil).(J)
|
|
`,
|
|
},
|
|
},
|
|
// Impossible type assertion: no method coupling C.f<->J.f.
|
|
{
|
|
ctxt: fakeContext(map[string][]string{
|
|
"main": {`package main
|
|
type I interface{
|
|
f()
|
|
}
|
|
type C int
|
|
func (C) f()
|
|
type J interface{
|
|
f()int
|
|
}
|
|
var _ = I(C(0)).(J)
|
|
`,
|
|
},
|
|
}),
|
|
offset: "/go/src/main/0.go:#32", to: "g", // abstract method I.f
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
type I interface {
|
|
g()
|
|
}
|
|
type C int
|
|
|
|
func (C) g()
|
|
|
|
type J interface {
|
|
f() int
|
|
}
|
|
|
|
var _ = I(C(0)).(J)
|
|
`,
|
|
},
|
|
},
|
|
// Progress after "soft" type errors (Go issue 14596).
|
|
{
|
|
ctxt: fakeContext(map[string][]string{
|
|
"main": {`package main
|
|
|
|
func main() {
|
|
var unused, x int
|
|
print(x)
|
|
}
|
|
`,
|
|
},
|
|
}),
|
|
offset: "/go/src/main/0.go:#54", to: "y", // var x
|
|
want: map[string]string{
|
|
"/go/src/main/0.go": `package main
|
|
|
|
func main() {
|
|
var unused, y int
|
|
print(y)
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
} {
|
|
if test.ctxt != nil {
|
|
ctxt = test.ctxt
|
|
}
|
|
|
|
got := make(map[string]string)
|
|
writeFile = func(filename string, content []byte) error {
|
|
got[filepath.ToSlash(filename)] = string(content)
|
|
return nil
|
|
}
|
|
|
|
err := Main(ctxt, test.offset, test.from, test.to)
|
|
var prefix string
|
|
if test.offset == "" {
|
|
prefix = fmt.Sprintf("-from %q -to %q", test.from, test.to)
|
|
} else {
|
|
prefix = fmt.Sprintf("-offset %q -to %q", test.offset, test.to)
|
|
}
|
|
if err != nil {
|
|
t.Errorf("%s: unexpected error: %s", prefix, err)
|
|
continue
|
|
}
|
|
|
|
for file, wantContent := range test.want {
|
|
gotContent, ok := got[file]
|
|
delete(got, file)
|
|
if !ok {
|
|
t.Errorf("%s: file %s not rewritten", prefix, file)
|
|
continue
|
|
}
|
|
if gotContent != wantContent {
|
|
t.Errorf("%s: rewritten file %s does not match expectation; got <<<%s>>>\n"+
|
|
"want <<<%s>>>", prefix, file, gotContent, wantContent)
|
|
}
|
|
}
|
|
// got should now be empty
|
|
for file := range got {
|
|
t.Errorf("%s: unexpected rewrite of file %s", prefix, file)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDiff(t *testing.T) {
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
if os.Getenv("GO_BUILDER_NAME") != "" {
|
|
if _, err := exec.LookPath(DiffCmd); err != nil {
|
|
t.Skipf("diff tool non-existent for %s on builders", runtime.GOOS)
|
|
}
|
|
}
|
|
case "plan9":
|
|
t.Skipf("plan9 diff tool doesn't support -u flag")
|
|
}
|
|
testenv.NeedsTool(t, DiffCmd)
|
|
testenv.NeedsTool(t, "go") // to locate the package to be renamed
|
|
|
|
defer func() {
|
|
Diff = false
|
|
stdout = os.Stdout
|
|
}()
|
|
Diff = true
|
|
stdout = new(bytes.Buffer)
|
|
|
|
// Set up a fake GOPATH in a temporary directory,
|
|
// and ensure we're in GOPATH mode.
|
|
tmpdir, err := ioutil.TempDir("", "TestDiff")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpdir)
|
|
buildCtx := build.Default
|
|
buildCtx.GOPATH = tmpdir
|
|
|
|
pkgDir := filepath.Join(tmpdir, "src", "example.com", "rename")
|
|
if err := os.MkdirAll(pkgDir, 0777); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
prevWD, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.Chdir(prevWD)
|
|
|
|
if err := os.Chdir(pkgDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
const goFile = `package rename
|
|
|
|
func justHereForTestingDiff() {
|
|
justHereForTestingDiff()
|
|
}
|
|
`
|
|
if err := ioutil.WriteFile(filepath.Join(pkgDir, "rename_test.go"), []byte(goFile), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := Main(&buildCtx, "", `"example.com/rename".justHereForTestingDiff`, "Foo"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// NB: there are tabs in the string literal!
|
|
if !strings.Contains(stdout.(fmt.Stringer).String(), `
|
|
-func justHereForTestingDiff() {
|
|
- justHereForTestingDiff()
|
|
+func Foo() {
|
|
+ Foo()
|
|
}
|
|
`) {
|
|
t.Errorf("unexpected diff:\n<<%s>>", stdout)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Simplifying wrapper around buildutil.FakeContext for packages whose
|
|
// filenames are sequentially numbered (%d.go). pkgs maps a package
|
|
// import path to its list of file contents.
|
|
func fakeContext(pkgs map[string][]string) *build.Context {
|
|
pkgs2 := make(map[string]map[string]string)
|
|
for path, files := range pkgs {
|
|
filemap := make(map[string]string)
|
|
for i, contents := range files {
|
|
filemap[fmt.Sprintf("%d.go", i)] = contents
|
|
}
|
|
pkgs2[path] = filemap
|
|
}
|
|
return buildutil.FakeContext(pkgs2)
|
|
}
|
|
|
|
// helper for single-file main packages with no imports.
|
|
func main(content string) *build.Context {
|
|
return fakeContext(map[string][]string{"main": {content}})
|
|
}
|