2014-12-20 16:33:48 -07:00
|
|
|
// Copyright 2015 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// licence that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package rename
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"go/build"
|
2016-07-14 12:17:37 -06:00
|
|
|
"go/token"
|
2014-12-20 16:33:48 -07:00
|
|
|
"io/ioutil"
|
|
|
|
"path/filepath"
|
2016-07-14 12:17:37 -06:00
|
|
|
"reflect"
|
2015-01-21 16:40:50 -07:00
|
|
|
"regexp"
|
2014-12-20 16:33:48 -07:00
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"golang.org/x/tools/go/buildutil"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestErrors(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
ctxt *build.Context
|
|
|
|
from, to string
|
|
|
|
want string // regexp to match error, or "OK"
|
|
|
|
}{
|
|
|
|
// Simple example.
|
|
|
|
{
|
|
|
|
ctxt: fakeContext(map[string][]string{
|
|
|
|
"foo": {`package foo; type T int`},
|
|
|
|
"bar": {`package bar`},
|
|
|
|
"main": {`package main
|
|
|
|
|
|
|
|
import "foo"
|
|
|
|
|
|
|
|
var _ foo.T
|
|
|
|
`},
|
|
|
|
}),
|
|
|
|
from: "foo", to: "bar",
|
2015-01-21 16:40:50 -07:00
|
|
|
want: `invalid move destination: bar conflicts with directory .go.src.bar`,
|
2014-12-20 16:33:48 -07:00
|
|
|
},
|
|
|
|
// Subpackage already exists.
|
|
|
|
{
|
|
|
|
ctxt: fakeContext(map[string][]string{
|
|
|
|
"foo": {`package foo; type T int`},
|
|
|
|
"foo/sub": {`package sub`},
|
|
|
|
"bar/sub": {`package sub`},
|
|
|
|
"main": {`package main
|
|
|
|
|
|
|
|
import "foo"
|
|
|
|
|
|
|
|
var _ foo.T
|
|
|
|
`},
|
|
|
|
}),
|
|
|
|
from: "foo", to: "bar",
|
|
|
|
want: "invalid move destination: bar; package or subpackage bar/sub already exists",
|
|
|
|
},
|
|
|
|
// Invalid base name.
|
|
|
|
{
|
|
|
|
ctxt: fakeContext(map[string][]string{
|
|
|
|
"foo": {`package foo; type T int`},
|
|
|
|
"main": {`package main
|
|
|
|
|
|
|
|
import "foo"
|
|
|
|
|
|
|
|
var _ foo.T
|
|
|
|
`},
|
|
|
|
}),
|
|
|
|
from: "foo", to: "bar-v2.0",
|
|
|
|
want: "invalid move destination: bar-v2.0; gomvpkg does not " +
|
|
|
|
"support move destinations whose base names are not valid " +
|
|
|
|
"go identifiers",
|
|
|
|
},
|
2017-02-23 10:31:04 -07:00
|
|
|
{
|
|
|
|
ctxt: fakeContext(map[string][]string{
|
|
|
|
"foo": {``},
|
|
|
|
"bar": {`package bar`},
|
|
|
|
}),
|
|
|
|
from: "foo", to: "bar",
|
|
|
|
want: `no initial packages were loaded`,
|
|
|
|
},
|
2014-12-20 16:33:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
ctxt := test.ctxt
|
|
|
|
|
|
|
|
got := make(map[string]string)
|
2015-12-29 14:26:28 -07:00
|
|
|
writeFile = func(filename string, content []byte) error {
|
|
|
|
got[filename] = string(content)
|
2014-12-20 16:33:48 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
moveDirectory = func(from, to string) error {
|
|
|
|
for path, contents := range got {
|
|
|
|
if strings.HasPrefix(path, from) {
|
|
|
|
newPath := strings.Replace(path, from, to, 1)
|
|
|
|
delete(got, path)
|
|
|
|
got[newPath] = contents
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
err := Move(ctxt, test.from, test.to, "")
|
|
|
|
prefix := fmt.Sprintf("-from %q -to %q", test.from, test.to)
|
|
|
|
if err == nil {
|
|
|
|
t.Errorf("%s: nil error. Expected error: %s", prefix, test.want)
|
|
|
|
continue
|
|
|
|
}
|
2015-01-21 16:40:50 -07:00
|
|
|
matched, err2 := regexp.MatchString(test.want, err.Error())
|
|
|
|
if err2 != nil {
|
|
|
|
t.Errorf("regexp.MatchString failed %s", err2)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !matched {
|
2014-12-20 16:33:48 -07:00
|
|
|
t.Errorf("%s: conflict does not match expectation:\n"+
|
|
|
|
"Error: %q\n"+
|
|
|
|
"Pattern: %q",
|
|
|
|
prefix, err.Error(), test.want)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMoves(t *testing.T) {
|
|
|
|
tests := []struct {
|
2016-07-14 12:17:37 -06:00
|
|
|
ctxt *build.Context
|
|
|
|
from, to string
|
|
|
|
want map[string]string
|
|
|
|
wantWarnings []string
|
2014-12-20 16:33:48 -07:00
|
|
|
}{
|
|
|
|
// Simple example.
|
|
|
|
{
|
|
|
|
ctxt: fakeContext(map[string][]string{
|
|
|
|
"foo": {`package foo; type T int`},
|
|
|
|
"main": {`package main
|
|
|
|
|
|
|
|
import "foo"
|
|
|
|
|
|
|
|
var _ foo.T
|
|
|
|
`},
|
|
|
|
}),
|
|
|
|
from: "foo", to: "bar",
|
|
|
|
want: map[string]string{
|
|
|
|
"/go/src/main/0.go": `package main
|
|
|
|
|
|
|
|
import "bar"
|
|
|
|
|
|
|
|
var _ bar.T
|
|
|
|
`,
|
|
|
|
"/go/src/bar/0.go": `package bar
|
|
|
|
|
|
|
|
type T int
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Example with subpackage.
|
|
|
|
{
|
|
|
|
ctxt: fakeContext(map[string][]string{
|
|
|
|
"foo": {`package foo; type T int`},
|
|
|
|
"foo/sub": {`package sub; type T int`},
|
|
|
|
"main": {`package main
|
|
|
|
|
|
|
|
import "foo"
|
|
|
|
import "foo/sub"
|
|
|
|
|
|
|
|
var _ foo.T
|
|
|
|
var _ sub.T
|
|
|
|
`},
|
|
|
|
}),
|
|
|
|
from: "foo", to: "bar",
|
|
|
|
want: map[string]string{
|
|
|
|
"/go/src/main/0.go": `package main
|
|
|
|
|
|
|
|
import "bar"
|
|
|
|
import "bar/sub"
|
|
|
|
|
|
|
|
var _ bar.T
|
|
|
|
var _ sub.T
|
|
|
|
`,
|
|
|
|
"/go/src/bar/0.go": `package bar
|
|
|
|
|
|
|
|
type T int
|
|
|
|
`,
|
|
|
|
"/go/src/bar/sub/0.go": `package sub; type T int`,
|
|
|
|
},
|
|
|
|
},
|
2015-02-10 14:18:58 -07:00
|
|
|
|
|
|
|
// References into subpackages
|
|
|
|
{
|
|
|
|
ctxt: fakeContext(map[string][]string{
|
|
|
|
"foo": {`package foo; import "foo/a"; var _ a.T`},
|
|
|
|
"foo/a": {`package a; type T int`},
|
|
|
|
"foo/b": {`package b; import "foo/a"; var _ a.T`},
|
|
|
|
}),
|
|
|
|
from: "foo", to: "bar",
|
|
|
|
want: map[string]string{
|
|
|
|
"/go/src/bar/0.go": `package bar
|
|
|
|
|
|
|
|
import "bar/a"
|
|
|
|
|
|
|
|
var _ a.T
|
|
|
|
`,
|
|
|
|
"/go/src/bar/a/0.go": `package a; type T int`,
|
|
|
|
"/go/src/bar/b/0.go": `package b
|
|
|
|
|
|
|
|
import "bar/a"
|
|
|
|
|
|
|
|
var _ a.T
|
2015-07-26 09:55:23 -06:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2017-09-24 08:03:04 -06:00
|
|
|
// References into subpackages where directories have overlapped names
|
|
|
|
{
|
|
|
|
ctxt: fakeContext(map[string][]string{
|
|
|
|
"foo": {},
|
|
|
|
"foo/a": {`package a`},
|
|
|
|
"foo/aa": {`package bar`},
|
|
|
|
"foo/c": {`package c; import _ "foo/bar";`},
|
|
|
|
}),
|
|
|
|
from: "foo/a", to: "foo/spam",
|
|
|
|
want: map[string]string{
|
|
|
|
"/go/src/foo/spam/0.go": `package spam
|
|
|
|
`,
|
|
|
|
"/go/src/foo/aa/0.go": `package bar`,
|
|
|
|
"/go/src/foo/c/0.go": `package c; import _ "foo/bar";`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2015-07-26 09:55:23 -06:00
|
|
|
// External test packages
|
|
|
|
{
|
|
|
|
ctxt: buildutil.FakeContext(map[string]map[string]string{
|
|
|
|
"foo": {
|
|
|
|
"0.go": `package foo; type T int`,
|
|
|
|
"0_test.go": `package foo_test; import "foo"; var _ foo.T`,
|
|
|
|
},
|
|
|
|
"baz": {
|
|
|
|
"0_test.go": `package baz_test; import "foo"; var _ foo.T`,
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
from: "foo", to: "bar",
|
|
|
|
want: map[string]string{
|
|
|
|
"/go/src/bar/0.go": `package bar
|
|
|
|
|
|
|
|
type T int
|
|
|
|
`,
|
|
|
|
"/go/src/bar/0_test.go": `package bar_test
|
|
|
|
|
|
|
|
import "bar"
|
|
|
|
|
|
|
|
var _ bar.T
|
|
|
|
`,
|
|
|
|
"/go/src/baz/0_test.go": `package baz_test
|
|
|
|
|
|
|
|
import "bar"
|
|
|
|
|
|
|
|
var _ bar.T
|
2015-02-10 14:18:58 -07:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
},
|
2015-10-06 11:51:57 -06:00
|
|
|
// package import comments
|
|
|
|
{
|
|
|
|
ctxt: fakeContext(map[string][]string{"foo": {`package foo // import "baz"`}}),
|
|
|
|
from: "foo", to: "bar",
|
|
|
|
want: map[string]string{"/go/src/bar/0.go": `package bar // import "bar"
|
|
|
|
`},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ctxt: fakeContext(map[string][]string{"foo": {`package foo /* import "baz" */`}}),
|
|
|
|
from: "foo", to: "bar",
|
|
|
|
want: map[string]string{"/go/src/bar/0.go": `package bar /* import "bar" */
|
|
|
|
`},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ctxt: fakeContext(map[string][]string{"foo": {`package foo // import "baz"`}}),
|
|
|
|
from: "foo", to: "bar",
|
|
|
|
want: map[string]string{"/go/src/bar/0.go": `package bar // import "bar"
|
|
|
|
`},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ctxt: fakeContext(map[string][]string{"foo": {`package foo
|
|
|
|
// import " this is not an import comment`}}),
|
|
|
|
from: "foo", to: "bar",
|
|
|
|
want: map[string]string{"/go/src/bar/0.go": `package bar
|
|
|
|
|
|
|
|
// import " this is not an import comment
|
|
|
|
`},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ctxt: fakeContext(map[string][]string{"foo": {`package foo
|
|
|
|
/* import " this is not an import comment */`}}),
|
|
|
|
from: "foo", to: "bar",
|
|
|
|
want: map[string]string{"/go/src/bar/0.go": `package bar
|
|
|
|
|
|
|
|
/* import " this is not an import comment */
|
|
|
|
`},
|
|
|
|
},
|
2016-07-14 12:17:37 -06:00
|
|
|
// Import name conflict generates a warning, not an error.
|
|
|
|
{
|
|
|
|
ctxt: fakeContext(map[string][]string{
|
|
|
|
"x": {},
|
|
|
|
"a": {`package a; type A int`},
|
|
|
|
"b": {`package b; type B int`},
|
|
|
|
"conflict": {`package conflict
|
|
|
|
|
|
|
|
import "a"
|
|
|
|
import "b"
|
|
|
|
var _ a.A
|
|
|
|
var _ b.B
|
|
|
|
`},
|
|
|
|
"ok": {`package ok
|
|
|
|
import "b"
|
|
|
|
var _ b.B
|
|
|
|
`},
|
|
|
|
}),
|
|
|
|
from: "b", to: "x/a",
|
|
|
|
want: map[string]string{
|
|
|
|
"/go/src/a/0.go": `package a; type A int`,
|
|
|
|
"/go/src/ok/0.go": `package ok
|
|
|
|
|
|
|
|
import "x/a"
|
|
|
|
|
|
|
|
var _ a.B
|
|
|
|
`,
|
|
|
|
"/go/src/conflict/0.go": `package conflict
|
|
|
|
|
|
|
|
import "a"
|
|
|
|
import "x/a"
|
|
|
|
|
|
|
|
var _ a.A
|
|
|
|
var _ b.B
|
|
|
|
`,
|
|
|
|
"/go/src/x/a/0.go": `package a
|
|
|
|
|
|
|
|
type B int
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
wantWarnings: []string{
|
|
|
|
`/go/src/conflict/0.go:4:8: renaming this imported package name "b" to "a"`,
|
|
|
|
`/go/src/conflict/0.go:3:8: conflicts with imported package name in same block`,
|
|
|
|
`/go/src/conflict/0.go:3:8: skipping update of this file`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Rename with same base name.
|
|
|
|
{
|
|
|
|
ctxt: fakeContext(map[string][]string{
|
|
|
|
"x": {},
|
|
|
|
"y": {},
|
|
|
|
"x/foo": {`package foo
|
|
|
|
|
|
|
|
type T int
|
|
|
|
`},
|
|
|
|
"main": {`package main; import "x/foo"; var _ foo.T`},
|
|
|
|
}),
|
|
|
|
from: "x/foo", to: "y/foo",
|
|
|
|
want: map[string]string{
|
|
|
|
"/go/src/y/foo/0.go": `package foo
|
|
|
|
|
|
|
|
type T int
|
|
|
|
`,
|
|
|
|
"/go/src/main/0.go": `package main
|
|
|
|
|
|
|
|
import "y/foo"
|
|
|
|
|
|
|
|
var _ foo.T
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
},
|
2014-12-20 16:33:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
ctxt := test.ctxt
|
|
|
|
|
|
|
|
got := make(map[string]string)
|
|
|
|
// Populate got with starting file set. rewriteFile and moveDirectory
|
|
|
|
// will mutate got to produce resulting file set.
|
|
|
|
buildutil.ForEachPackage(ctxt, func(importPath string, err error) {
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
path := filepath.Join("/go/src", importPath, "0.go")
|
|
|
|
if !buildutil.FileExists(ctxt, path) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
f, err := ctxt.OpenFile(path)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("unexpected error opening file: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
bytes, err := ioutil.ReadAll(f)
|
2015-01-23 12:53:23 -07:00
|
|
|
f.Close()
|
2014-12-20 16:33:48 -07:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("unexpected error reading file: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
got[path] = string(bytes)
|
|
|
|
})
|
2016-07-14 12:17:37 -06:00
|
|
|
var warnings []string
|
|
|
|
reportError = func(posn token.Position, message string) {
|
2016-07-15 08:26:15 -06:00
|
|
|
warning := fmt.Sprintf("%s:%d:%d: %s",
|
|
|
|
filepath.ToSlash(posn.Filename), // for MS Windows
|
|
|
|
posn.Line,
|
|
|
|
posn.Column,
|
|
|
|
message)
|
|
|
|
warnings = append(warnings, warning)
|
|
|
|
|
2016-07-14 12:17:37 -06:00
|
|
|
}
|
2015-12-29 14:26:28 -07:00
|
|
|
writeFile = func(filename string, content []byte) error {
|
|
|
|
got[filename] = string(content)
|
2014-12-20 16:33:48 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
moveDirectory = func(from, to string) error {
|
|
|
|
for path, contents := range got {
|
2017-09-24 08:03:04 -06:00
|
|
|
if !(strings.HasPrefix(path, from) &&
|
|
|
|
(len(path) == len(from) || path[len(from)] == filepath.Separator)) {
|
|
|
|
continue
|
2014-12-20 16:33:48 -07:00
|
|
|
}
|
2017-09-24 08:03:04 -06:00
|
|
|
newPath := strings.Replace(path, from, to, 1)
|
|
|
|
delete(got, path)
|
|
|
|
got[newPath] = contents
|
2014-12-20 16:33:48 -07:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
err := Move(ctxt, test.from, test.to, "")
|
|
|
|
prefix := fmt.Sprintf("-from %q -to %q", test.from, test.to)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("%s: unexpected error: %s", prefix, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2016-07-14 12:17:37 -06:00
|
|
|
if !reflect.DeepEqual(warnings, test.wantWarnings) {
|
|
|
|
t.Errorf("%s: unexpected warnings:\n%s\nwant:\n%s",
|
|
|
|
prefix,
|
|
|
|
strings.Join(warnings, "\n"),
|
|
|
|
strings.Join(test.wantWarnings, "\n"))
|
|
|
|
}
|
|
|
|
|
2014-12-20 16:33:48 -07:00
|
|
|
for file, wantContent := range test.want {
|
2015-01-21 16:40:50 -07:00
|
|
|
k := filepath.FromSlash(file)
|
|
|
|
gotContent, ok := got[k]
|
|
|
|
delete(got, k)
|
2014-12-20 16:33:48 -07:00
|
|
|
if !ok {
|
|
|
|
// TODO(matloob): some testcases might have files that won't be
|
|
|
|
// rewritten
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|