blob: a9fd920317fde0f94e5f9ed938ff04ba5f5d466b [file] [log] [blame]
// Copyright 2021 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 misc
import (
"strings"
"testing"
"golang.org/x/tools/gopls/internal/lsp/protocol"
. "golang.org/x/tools/gopls/internal/lsp/regtest"
"golang.org/x/tools/gopls/internal/lsp/tests/compare"
"golang.org/x/tools/internal/testenv"
)
func TestPrepareRenameMainPackage(t *testing.T) {
const files = `
-- go.mod --
module mod.com
go 1.18
-- main.go --
package main
import (
"fmt"
)
func main() {
fmt.Println(1)
}
`
const wantErr = "can't rename package \"main\""
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("main.go")
pos := env.RegexpSearch("main.go", `main`)
tdpp := protocol.TextDocumentPositionParams{
TextDocument: env.Editor.TextDocumentIdentifier("main.go"),
Position: pos.ToProtocolPosition(),
}
params := &protocol.PrepareRenameParams{
TextDocumentPositionParams: tdpp,
}
_, err := env.Editor.Server.PrepareRename(env.Ctx, params)
if err == nil {
t.Errorf("missing can't rename package main error from PrepareRename")
}
if err.Error() != wantErr {
t.Errorf("got %v, want %v", err.Error(), wantErr)
}
})
}
// Test case for golang/go#56227
func TestRenameWithUnsafeSlice(t *testing.T) {
testenv.NeedsGo1Point(t, 17) // unsafe.Slice was added in Go 1.17
const files = `
-- go.mod --
module mod.com
go 1.18
-- p.go --
package p
import "unsafe"
type T struct{}
func (T) M() {}
func _() {
x := [3]int{1, 2, 3}
ptr := unsafe.Pointer(&x)
_ = unsafe.Slice((*int)(ptr), 3)
}
`
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("p.go")
env.Rename("p.go", env.RegexpSearch("p.go", "M"), "N") // must not panic
})
}
func TestPrepareRenameWithNoPackageDeclaration(t *testing.T) {
testenv.NeedsGo1Point(t, 15)
const files = `
go 1.14
-- lib/a.go --
import "fmt"
const A = 1
func bar() {
fmt.Println("Bar")
}
-- main.go --
package main
import "fmt"
func main() {
fmt.Println("Hello")
}
`
const wantErr = "no object found"
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("lib/a.go")
pos := env.RegexpSearch("lib/a.go", "fmt")
err := env.Editor.Rename(env.Ctx, "lib/a.go", pos, "fmt1")
if err == nil {
t.Errorf("missing no object found from Rename")
}
if err.Error() != wantErr {
t.Errorf("got %v, want %v", err.Error(), wantErr)
}
})
}
func TestPrepareRenameFailWithUnknownModule(t *testing.T) {
testenv.NeedsGo1Point(t, 17)
const files = `
go 1.14
-- lib/a.go --
package lib
const A = 1
-- main.go --
package main
import (
"mod.com/lib"
)
func main() {
println("Hello")
}
`
const wantErr = "can't rename package: missing module information for package"
Run(t, files, func(t *testing.T, env *Env) {
pos := env.RegexpSearch("lib/a.go", "lib")
tdpp := protocol.TextDocumentPositionParams{
TextDocument: env.Editor.TextDocumentIdentifier("lib/a.go"),
Position: pos.ToProtocolPosition(),
}
params := &protocol.PrepareRenameParams{
TextDocumentPositionParams: tdpp,
}
_, err := env.Editor.Server.PrepareRename(env.Ctx, params)
if err == nil || !strings.Contains(err.Error(), wantErr) {
t.Errorf("missing cannot rename packages with unknown module from PrepareRename")
}
})
}
func TestRenamePackageWithConflicts(t *testing.T) {
testenv.NeedsGo1Point(t, 17)
const files = `
-- go.mod --
module mod.com
go 1.18
-- lib/a.go --
package lib
const A = 1
-- lib/nested/a.go --
package nested
const B = 1
-- lib/x/a.go --
package nested1
const C = 1
-- main.go --
package main
import (
"mod.com/lib"
"mod.com/lib/nested"
nested1 "mod.com/lib/x"
)
func main() {
println("Hello")
}
`
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("lib/a.go")
pos := env.RegexpSearch("lib/a.go", "lib")
env.Rename("lib/a.go", pos, "nested")
// Check if the new package name exists.
env.RegexpSearch("nested/a.go", "package nested")
env.RegexpSearch("main.go", `nested2 "mod.com/nested"`)
env.RegexpSearch("main.go", "mod.com/nested/nested")
env.RegexpSearch("main.go", `nested1 "mod.com/nested/x"`)
})
}
func TestRenamePackageWithAlias(t *testing.T) {
testenv.NeedsGo1Point(t, 17)
const files = `
-- go.mod --
module mod.com
go 1.18
-- lib/a.go --
package lib
const A = 1
-- lib/nested/a.go --
package nested
const B = 1
-- main.go --
package main
import (
"mod.com/lib"
lib1 "mod.com/lib/nested"
)
func main() {
println("Hello")
}
`
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("lib/a.go")
pos := env.RegexpSearch("lib/a.go", "lib")
env.Rename("lib/a.go", pos, "nested")
// Check if the new package name exists.
env.RegexpSearch("nested/a.go", "package nested")
env.RegexpSearch("main.go", "mod.com/nested")
env.RegexpSearch("main.go", `lib1 "mod.com/nested/nested"`)
})
}
func TestRenamePackageWithDifferentDirectoryPath(t *testing.T) {
testenv.NeedsGo1Point(t, 17)
const files = `
-- go.mod --
module mod.com
go 1.18
-- lib/a.go --
package lib
const A = 1
-- lib/nested/a.go --
package foo
const B = 1
-- main.go --
package main
import (
"mod.com/lib"
foo "mod.com/lib/nested"
)
func main() {
println("Hello")
}
`
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("lib/a.go")
pos := env.RegexpSearch("lib/a.go", "lib")
env.Rename("lib/a.go", pos, "nested")
// Check if the new package name exists.
env.RegexpSearch("nested/a.go", "package nested")
env.RegexpSearch("main.go", "mod.com/nested")
env.RegexpSearch("main.go", `foo "mod.com/nested/nested"`)
})
}
func TestRenamePackage(t *testing.T) {
testenv.NeedsGo1Point(t, 17)
const files = `
-- go.mod --
module mod.com
go 1.18
-- lib/a.go --
package lib
const A = 1
-- lib/b.go --
package lib
const B = 1
-- lib/nested/a.go --
package nested
const C = 1
-- main.go --
package main
import (
"mod.com/lib"
"mod.com/lib/nested"
)
func main() {
println("Hello")
}
`
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("lib/a.go")
pos := env.RegexpSearch("lib/a.go", "lib")
env.Rename("lib/a.go", pos, "lib1")
// Check if the new package name exists.
env.RegexpSearch("lib1/a.go", "package lib1")
env.RegexpSearch("lib1/b.go", "package lib1")
env.RegexpSearch("main.go", "mod.com/lib1")
env.RegexpSearch("main.go", "mod.com/lib1/nested")
})
}
// Test for golang/go#47564.
func TestRenameInTestVariant(t *testing.T) {
const files = `
-- go.mod --
module mod.com
go 1.12
-- stringutil/stringutil.go --
package stringutil
func Identity(s string) string {
return s
}
-- stringutil/stringutil_test.go --
package stringutil
func TestIdentity(t *testing.T) {
if got := Identity("foo"); got != "foo" {
t.Errorf("bad")
}
}
-- main.go --
package main
import (
"fmt"
"mod.com/stringutil"
)
func main() {
fmt.Println(stringutil.Identity("hello world"))
}
`
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("main.go")
pos := env.RegexpSearch("main.go", `stringutil\.(Identity)`)
env.Rename("main.go", pos, "Identityx")
text := env.Editor.BufferText("stringutil/stringutil_test.go")
if !strings.Contains(text, "Identityx") {
t.Errorf("stringutil/stringutil_test.go: missing expected token `Identityx` after rename:\n%s", text)
}
})
}
// This is a test that rename operation initiated by the editor function as expected.
func TestRenameFileFromEditor(t *testing.T) {
const files = `
-- go.mod --
module mod.com
go 1.16
-- a/a.go --
package a
const X = 1
-- a/x.go --
package a
const X = 2
-- b/b.go --
package b
`
Run(t, files, func(t *testing.T, env *Env) {
// Rename files and verify that diagnostics are affected accordingly.
// Initially, we should have diagnostics on both X's, for their duplicate declaration.
env.Await(
OnceMet(
InitialWorkspaceLoad,
env.DiagnosticAtRegexp("a/a.go", "X"),
env.DiagnosticAtRegexp("a/x.go", "X"),
),
)
// Moving x.go should make the diagnostic go away.
env.RenameFile("a/x.go", "b/x.go")
env.Await(
OnceMet(
env.DoneWithChangeWatchedFiles(),
EmptyDiagnostics("a/a.go"), // no more duplicate declarations
env.DiagnosticAtRegexp("b/b.go", "package"), // as package names mismatch
),
)
// Renaming should also work on open buffers.
env.OpenFile("b/x.go")
// Moving x.go back to a/ should cause the diagnostics to reappear.
env.RenameFile("b/x.go", "a/x.go")
// TODO(rfindley): enable using a OnceMet precondition here. We can't
// currently do this because DidClose, DidOpen and DidChangeWatchedFiles
// are sent, and it is not easy to use all as a precondition.
env.Await(
env.DiagnosticAtRegexp("a/a.go", "X"),
env.DiagnosticAtRegexp("a/x.go", "X"),
)
// Renaming the entire directory should move both the open and closed file.
env.RenameFile("a", "x")
env.Await(
env.DiagnosticAtRegexp("x/a.go", "X"),
env.DiagnosticAtRegexp("x/x.go", "X"),
)
// As a sanity check, verify that x/x.go is open.
if text := env.Editor.BufferText("x/x.go"); text == "" {
t.Fatal("got empty buffer for x/x.go")
}
})
}
func TestRenamePackage_Tests(t *testing.T) {
testenv.NeedsGo1Point(t, 17)
const files = `
-- go.mod --
module mod.com
go 1.18
-- lib/a.go --
package lib
const A = 1
-- lib/b.go --
package lib
const B = 1
-- lib/a_test.go --
package lib_test
import (
"mod.com/lib"
"fmt
)
const C = 1
-- lib/b_test.go --
package lib
import (
"fmt
)
const D = 1
-- lib/nested/a.go --
package nested
const D = 1
-- main.go --
package main
import (
"mod.com/lib"
"mod.com/lib/nested"
)
func main() {
println("Hello")
}
`
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("lib/a.go")
pos := env.RegexpSearch("lib/a.go", "lib")
env.Rename("lib/a.go", pos, "lib1")
// Check if the new package name exists.
env.RegexpSearch("lib1/a.go", "package lib1")
env.RegexpSearch("lib1/b.go", "package lib1")
env.RegexpSearch("main.go", "mod.com/lib1")
env.RegexpSearch("main.go", "mod.com/lib1/nested")
// Check if the test package is renamed
env.RegexpSearch("lib1/a_test.go", "package lib1_test")
env.RegexpSearch("lib1/b_test.go", "package lib1")
})
}
func TestRenamePackage_NestedModule(t *testing.T) {
testenv.NeedsGo1Point(t, 17)
const files = `
-- go.mod --
module mod.com
go 1.18
require (
mod.com/foo/bar v0.0.0
)
replace mod.com/foo/bar => ./foo/bar
-- foo/foo.go --
package foo
import "fmt"
func Bar() {
fmt.Println("In foo before renamed to foox.")
}
-- foo/bar/go.mod --
module mod.com/foo/bar
-- foo/bar/bar.go --
package bar
const Msg = "Hi"
-- main.go --
package main
import (
"fmt"
"mod.com/foo/bar"
"mod.com/foo"
)
func main() {
foo.Bar()
fmt.Println(bar.Msg)
}
`
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("foo/foo.go")
pos := env.RegexpSearch("foo/foo.go", "foo")
env.Rename("foo/foo.go", pos, "foox")
env.RegexpSearch("foox/foo.go", "package foox")
env.OpenFile("foox/bar/bar.go")
env.OpenFile("foox/bar/go.mod")
env.RegexpSearch("main.go", "mod.com/foo/bar")
env.RegexpSearch("main.go", "mod.com/foox")
env.RegexpSearch("main.go", "foox.Bar()")
})
}
func TestRenamePackage_DuplicateImport(t *testing.T) {
testenv.NeedsGo1Point(t, 17)
const files = `
-- go.mod --
module mod.com
go 1.18
-- lib/a.go --
package lib
const A = 1
-- lib/nested/a.go --
package nested
const B = 1
-- main.go --
package main
import (
"mod.com/lib"
lib1 "mod.com/lib"
lib2 "mod.com/lib/nested"
)
func main() {
println("Hello")
}
`
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("lib/a.go")
pos := env.RegexpSearch("lib/a.go", "lib")
env.Rename("lib/a.go", pos, "nested")
// Check if the new package name exists.
env.RegexpSearch("nested/a.go", "package nested")
env.RegexpSearch("main.go", "mod.com/nested")
env.RegexpSearch("main.go", `lib1 "mod.com/nested"`)
env.RegexpSearch("main.go", `lib2 "mod.com/nested/nested"`)
})
}
func TestRenamePackage_DuplicateBlankImport(t *testing.T) {
testenv.NeedsGo1Point(t, 17)
const files = `
-- go.mod --
module mod.com
go 1.18
-- lib/a.go --
package lib
const A = 1
-- lib/nested/a.go --
package nested
const B = 1
-- main.go --
package main
import (
"mod.com/lib"
_ "mod.com/lib"
lib1 "mod.com/lib/nested"
)
func main() {
println("Hello")
}
`
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("lib/a.go")
pos := env.RegexpSearch("lib/a.go", "lib")
env.Rename("lib/a.go", pos, "nested")
// Check if the new package name exists.
env.RegexpSearch("nested/a.go", "package nested")
env.RegexpSearch("main.go", "mod.com/nested")
env.RegexpSearch("main.go", `_ "mod.com/nested"`)
env.RegexpSearch("main.go", `lib1 "mod.com/nested/nested"`)
})
}
func TestRenamePackage_TestVariant(t *testing.T) {
const files = `
-- go.mod --
module mod.com
go 1.12
-- foo/foo.go --
package foo
const Foo = 42
-- bar/bar.go --
package bar
import "mod.com/foo"
const Bar = foo.Foo
-- bar/bar_test.go --
package bar
import "mod.com/foo"
const Baz = foo.Foo
-- testdata/bar/bar.go --
package bar
import "mod.com/foox"
const Bar = foox.Foo
-- testdata/bar/bar_test.go --
package bar
import "mod.com/foox"
const Baz = foox.Foo
`
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("foo/foo.go")
env.Rename("foo/foo.go", env.RegexpSearch("foo/foo.go", "package (foo)"), "foox")
checkTestdata(t, env)
})
}
func TestRenamePackage_IntermediateTestVariant(t *testing.T) {
// In this test set up, we have the following import edges:
// bar_test -> baz -> foo -> bar
// bar_test -> foo -> bar
// bar_test -> bar
//
// As a consequence, bar_x_test.go is in the reverse closure of both
// `foo [bar.test]` and `baz [bar.test]`. This test confirms that we don't
// produce duplicate edits in this case.
const files = `
-- go.mod --
module foo.mod
go 1.12
-- foo/foo.go --
package foo
import "foo.mod/bar"
const Foo = 42
const _ = bar.Bar
-- baz/baz.go --
package baz
import "foo.mod/foo"
const Baz = foo.Foo
-- bar/bar.go --
package bar
var Bar = 123
-- bar/bar_test.go --
package bar
const _ = Bar
-- bar/bar_x_test.go --
package bar_test
import (
"foo.mod/bar"
"foo.mod/baz"
"foo.mod/foo"
)
const _ = bar.Bar + baz.Baz + foo.Foo
-- testdata/foox/foo.go --
package foox
import "foo.mod/bar"
const Foo = 42
const _ = bar.Bar
-- testdata/baz/baz.go --
package baz
import "foo.mod/foox"
const Baz = foox.Foo
-- testdata/bar/bar_x_test.go --
package bar_test
import (
"foo.mod/bar"
"foo.mod/baz"
"foo.mod/foox"
)
const _ = bar.Bar + baz.Baz + foox.Foo
`
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("foo/foo.go")
env.Rename("foo/foo.go", env.RegexpSearch("foo/foo.go", "package (foo)"), "foox")
checkTestdata(t, env)
})
}
func TestRenamePackage_Nesting(t *testing.T) {
testenv.NeedsGo1Point(t, 17)
const files = `
-- go.mod --
module mod.com
go 1.18
-- lib/a.go --
package lib
import "mod.com/lib/nested"
const A = 1 + nested.B
-- lib/nested/a.go --
package nested
const B = 1
-- other/other.go --
package other
import (
"mod.com/lib"
"mod.com/lib/nested"
)
const C = lib.A + nested.B
-- testdata/libx/a.go --
package libx
import "mod.com/libx/nested"
const A = 1 + nested.B
-- testdata/other/other.go --
package other
import (
"mod.com/libx"
"mod.com/libx/nested"
)
const C = libx.A + nested.B
`
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("lib/a.go")
pos := env.RegexpSearch("lib/a.go", "package (lib)")
env.Rename("lib/a.go", pos, "libx")
checkTestdata(t, env)
})
}
func TestRenamePackage_InvalidName(t *testing.T) {
testenv.NeedsGo1Point(t, 17)
const files = `
-- go.mod --
module mod.com
go 1.18
-- lib/a.go --
package lib
import "mod.com/lib/nested"
const A = 1 + nested.B
`
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("lib/a.go")
pos := env.RegexpSearch("lib/a.go", "package (lib)")
for _, badName := range []string{"$$$", "lib_test"} {
if err := env.Editor.Rename(env.Ctx, "lib/a.go", pos, badName); err == nil {
t.Errorf("Rename(lib, libx) succeeded, want non-nil error")
}
}
})
}
// checkTestdata checks that current buffer contents match their corresponding
// expected content in the testdata directory.
func checkTestdata(t *testing.T, env *Env) {
t.Helper()
files := env.ListFiles("testdata")
if len(files) == 0 {
t.Fatal("no files in testdata directory")
}
for _, file := range files {
suffix := strings.TrimPrefix(file, "testdata/")
got := env.Editor.BufferText(suffix)
want := env.ReadWorkspaceFile(file)
if diff := compare.Text(want, got); diff != "" {
t.Errorf("Rename: unexpected buffer content for %s (-want +got):\n%s", suffix, diff)
}
}
}