| // 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" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "regexp" |
| "runtime" |
| "strings" |
| "testing" |
| |
| "golang.org/x/tools/go/buildutil" |
| "golang.org/x/tools/internal/aliases" |
| "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 |
| alias bool // requires materialized aliases |
| }{ |
| // 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 |
| `, |
| }, |
| }, |
| // Renaming of embedded field alias. |
| { |
| alias: true, |
| ctxt: main(`package main |
| |
| type T int |
| type A = T |
| type U struct{ A } |
| |
| var _ = U{}.A |
| var a A |
| `), |
| offset: "/go/src/main/0.go:#68", to: "A2", // A in "U{}.A" |
| want: map[string]string{ |
| "/go/src/main/0.go": `package main |
| |
| type T int |
| type A2 = T |
| type U struct{ A2 } |
| |
| var _ = U{}.A2 |
| var a A2 |
| `, |
| }, |
| }, |
| // Renaming of embedded field pointer to alias. |
| { |
| alias: true, |
| ctxt: main(`package main |
| |
| type T int |
| type A = T |
| type U struct{ *A } |
| |
| var _ = U{}.A |
| var a A |
| `), |
| offset: "/go/src/main/0.go:#69", to: "A2", // A in "U{}.A" |
| want: map[string]string{ |
| "/go/src/main/0.go": `package main |
| |
| type T int |
| type A2 = T |
| type U struct{ *A2 } |
| |
| var _ = U{}.A2 |
| var a A2 |
| `, |
| }, |
| }, |
| // Renaming of alias |
| { |
| ctxt: main(`package main |
| |
| type A = int |
| |
| func _() A { |
| return A(0) |
| } |
| `), |
| offset: "/go/src/main/0.go:#49", to: "A2", // A in "A(0)" |
| want: map[string]string{ |
| "/go/src/main/0.go": `package main |
| |
| type A2 = int |
| |
| func _() A2 { |
| return A2(0) |
| } |
| `, |
| }, |
| }, |
| |
| // 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 |
| } |
| |
| // Skip tests that require aliases when not enables. |
| // (No test requires _no_ aliases, |
| // so there is no contrapositive case.) |
| if test.alias && !aliases.Enabled() { |
| t.Log("test requires aliases") |
| continue |
| } |
| |
| 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 := os.MkdirTemp("", "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 modFile = `module example.com/rename |
| |
| go 1.15 |
| ` |
| if err := os.WriteFile(filepath.Join(pkgDir, "go.mod"), []byte(modFile), 0644); err != nil { |
| t.Fatal(err) |
| } |
| |
| const goFile = `package rename |
| |
| func justHereForTestingDiff() { |
| justHereForTestingDiff() |
| } |
| ` |
| if err := os.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}}) |
| } |