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"
|
cmd/goimports, imports: make goimports great again
I felt the burn of my laptop on my legs, spinning away while processing
goimports, and felt that it was time to make goimports great again.
Over the past few years goimports fell into a slow state of disrepair
with too many feature additions and no attention to the performance
death by a thousand cuts. This was particularly terrible on OS X with
its lackluster filesystem buffering.
This CL makes goimports stronger, together with various optimizations
and more visibility into what goimports is doing.
* adds more internal documentation
* avoids scanning $GOPATH for answers when running goimports on a file
under $GOROOT (for Go core hackers)
* don't read all $GOROOT & $GOPATH directories' Go code looking for
their package names until much later. Require the package name of
missing imports to be present in the last two directory path
components. Then only try importing them in order from best to
worst (shortest to longest, as before), so we can stop early.
* when adding imports, add names to imports when the imported package name
doesn't match the baes of its import path. For example:
import foo "example.net/foo/v1"
* don't read all *.go files in a package directory once the first file
in a directory has revealed itself to be a package we're not looking
for. For example, if we're looking for the right "client" for "client.Foo",
we used to consider a directory "bar/client" as a candidate and read
all 50 of its *.go files instead of stopping after its first *.go
file had a "package main" line.
* add some fast paths to remove allocations
* add some fast paths to remove disk I/O when looking up the base
package name of a standard library import (of existing imports in a
file, which are very common)
* adds a special case for import "C", to avoid some disk I/O.
* add a -verbose flag to goimports for debugging
On my Mac laptop with a huge $GOPATH, with a test file like:
package foo
import (
"fmt"
"net/http"
)
/*
*/
import "C"
var _ = cloudbilling.New
var _ = http.NewRequest
var _ = client.New
... this took like 10 seconds before, and now 1.3 seconds. (Still
slow; disk-based caching can come later)
Updates golang/go#16367 (goimports is slow)
Updates golang/go#16384 (refactor TestRename is broken on Windows)
Change-Id: I97e85d3016afc9f2ad5501f97babad30c7989183
Reviewed-on: https://go-review.googlesource.com/24941
Reviewed-by: Andrew Gerrand <adg@golang.org>
Reviewed-by: Rob Pike <r@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2016-07-14 18:08:27 -06:00
|
|
|
"runtime"
|
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",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
cmd/goimports, imports: make goimports great again
I felt the burn of my laptop on my legs, spinning away while processing
goimports, and felt that it was time to make goimports great again.
Over the past few years goimports fell into a slow state of disrepair
with too many feature additions and no attention to the performance
death by a thousand cuts. This was particularly terrible on OS X with
its lackluster filesystem buffering.
This CL makes goimports stronger, together with various optimizations
and more visibility into what goimports is doing.
* adds more internal documentation
* avoids scanning $GOPATH for answers when running goimports on a file
under $GOROOT (for Go core hackers)
* don't read all $GOROOT & $GOPATH directories' Go code looking for
their package names until much later. Require the package name of
missing imports to be present in the last two directory path
components. Then only try importing them in order from best to
worst (shortest to longest, as before), so we can stop early.
* when adding imports, add names to imports when the imported package name
doesn't match the baes of its import path. For example:
import foo "example.net/foo/v1"
* don't read all *.go files in a package directory once the first file
in a directory has revealed itself to be a package we're not looking
for. For example, if we're looking for the right "client" for "client.Foo",
we used to consider a directory "bar/client" as a candidate and read
all 50 of its *.go files instead of stopping after its first *.go
file had a "package main" line.
* add some fast paths to remove allocations
* add some fast paths to remove disk I/O when looking up the base
package name of a standard library import (of existing imports in a
file, which are very common)
* adds a special case for import "C", to avoid some disk I/O.
* add a -verbose flag to goimports for debugging
On my Mac laptop with a huge $GOPATH, with a test file like:
package foo
import (
"fmt"
"net/http"
)
/*
*/
import "C"
var _ = cloudbilling.New
var _ = http.NewRequest
var _ = client.New
... this took like 10 seconds before, and now 1.3 seconds. (Still
slow; disk-based caching can come later)
Updates golang/go#16367 (goimports is slow)
Updates golang/go#16384 (refactor TestRename is broken on Windows)
Change-Id: I97e85d3016afc9f2ad5501f97babad30c7989183
Reviewed-on: https://go-review.googlesource.com/24941
Reviewed-by: Andrew Gerrand <adg@golang.org>
Reviewed-by: Rob Pike <r@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2016-07-14 18:08:27 -06:00
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
t.Skip("broken on Windows; see golang.org/issue/16384")
|
|
|
|
}
|
2014-12-20 16:33:48 -07:00
|
|
|
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
|
|
|
`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
warnings = append(warnings, posn.String()+": "+message)
|
|
|
|
}
|
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: 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|