//===-- RenameClassTest.cpp - unit tests for renaming classes -------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "ClangRenameTest.h" namespace clang { namespace clang_rename { namespace test { namespace { class RenameClassTest : public ClangRenameTest { public: RenameClassTest() { AppendToHeader(R"( namespace a { class Foo { public: struct Nested { enum NestedEnum {E1, E2}; }; void func() {} static int Constant; }; class Goo { public: struct Nested { enum NestedEnum {E1, E2}; }; }; int Foo::Constant = 1; } // namespace a namespace b { class Foo {}; } // namespace b #define MACRO(x) x template class ptr {}; )"); } }; INSTANTIATE_TEST_CASE_P( RenameClassTests, RenameClassTest, testing::ValuesIn(std::vector({ // basic classes {"a::Foo f;", "b::Bar f;", "", ""}, {"::a::Foo f;", "::b::Bar f;", "", ""}, {"void f(a::Foo f) {}", "void f(b::Bar f) {}", "", ""}, {"void f(a::Foo *f) {}", "void f(b::Bar *f) {}", "", ""}, {"a::Foo f() { return a::Foo(); }", "b::Bar f() { return b::Bar(); }", "", ""}, {"namespace a {a::Foo f() { return Foo(); }}", "namespace a {b::Bar f() { return b::Bar(); }}", "", ""}, {"void f(const a::Foo& a1) {}", "void f(const b::Bar& a1) {}", "", ""}, {"void f(const a::Foo* a1) {}", "void f(const b::Bar* a1) {}", "", ""}, {"namespace a { void f(Foo a1) {} }", "namespace a { void f(b::Bar a1) {} }", "", ""}, {"void f(MACRO(a::Foo) a1) {}", "void f(MACRO(b::Bar) a1) {}", "", ""}, {"void f(MACRO(a::Foo a1)) {}", "void f(MACRO(b::Bar a1)) {}", "", ""}, {"a::Foo::Nested ns;", "b::Bar::Nested ns;", "", ""}, {"auto t = a::Foo::Constant;", "auto t = b::Bar::Constant;", "", ""}, {"a::Foo::Nested ns;", "a::Foo::Nested2 ns;", "a::Foo::Nested", "a::Foo::Nested2"}, // use namespace and typedefs {"using a::Foo; Foo gA;", "using b::Bar; b::Bar gA;", "", ""}, {"using a::Foo; void f(Foo gA) {}", "using b::Bar; void f(Bar gA) {}", "", ""}, {"using a::Foo; namespace x { Foo gA; }", "using b::Bar; namespace x { Bar gA; }", "", ""}, {"struct S { using T = a::Foo; T a_; };", "struct S { using T = b::Bar; T a_; };", "", ""}, {"using T = a::Foo; T gA;", "using T = b::Bar; T gA;", "", ""}, {"typedef a::Foo T; T gA;", "typedef b::Bar T; T gA;", "", ""}, {"typedef MACRO(a::Foo) T; T gA;", "typedef MACRO(b::Bar) T; T gA;", "", ""}, // struct members and other oddities {"struct S : public a::Foo {};", "struct S : public b::Bar {};", "", ""}, {"struct F { void f(a::Foo a1) {} };", "struct F { void f(b::Bar a1) {} };", "", ""}, {"struct F { a::Foo a_; };", "struct F { b::Bar a_; };", "", ""}, {"struct F { ptr a_; };", "struct F { ptr a_; };", "", ""}, {"void f() { a::Foo::Nested ne; }", "void f() { b::Bar::Nested ne; }", "", ""}, {"void f() { a::Goo::Nested ne; }", "void f() { a::Goo::Nested ne; }", "", ""}, {"void f() { a::Foo::Nested::NestedEnum e; }", "void f() { b::Bar::Nested::NestedEnum e; }", "", ""}, {"void f() { auto e = a::Foo::Nested::NestedEnum::E1; }", "void f() { auto e = b::Bar::Nested::NestedEnum::E1; }", "", ""}, {"void f() { auto e = a::Foo::Nested::E1; }", "void f() { auto e = b::Bar::Nested::E1; }", "", ""}, // templates {"template struct Foo { T t; };\n" "void f() { Foo foo; }", "template struct Foo { T t; };\n" "void f() { Foo foo; }", "", ""}, {"template struct Foo { a::Foo a; };", "template struct Foo { b::Bar a; };", "", ""}, {"template void f(T t) {}\n" "void g() { f(a::Foo()); }", "template void f(T t) {}\n" "void g() { f(b::Bar()); }", "", ""}, {"template int f() { return 1; }\n" "template <> int f() { return 2; }\n" "int g() { return f(); }", "template int f() { return 1; }\n" "template <> int f() { return 2; }\n" "int g() { return f(); }", "", ""}, {"struct Foo { template T foo(); };\n" "void g() { Foo f; auto a = f.template foo(); }", "struct Foo { template T foo(); };\n" "void g() { Foo f; auto a = f.template foo(); }", "", ""}, // The following two templates are distilled from regressions found in // unique_ptr<> and type_traits.h {"template struct outer {\n" " typedef T type;\n" " type Baz();\n" " };\n" " outer g_A;", "template struct outer {\n" " typedef T type;\n" " type Baz();\n" " };\n" " outer g_A;", "", ""}, {"template struct nested { typedef T type; };\n" "template struct outer { typename nested::type Foo(); " "};\n" "outer g_A;", "template struct nested { typedef T type; };\n" "template struct outer { typename nested::type Foo(); " "};\n" "outer g_A;", "", ""}, // macros {"#define FOO(T, t) T t\n" "void f() { FOO(a::Foo, a1); FOO(a::Foo, a2); }", "#define FOO(T, t) T t\n" "void f() { FOO(b::Bar, a1); FOO(b::Bar, a2); }", "", ""}, {"#define FOO(n) a::Foo n\n" " void f() { FOO(a1); FOO(a2); }", "#define FOO(n) b::Bar n\n" " void f() { FOO(a1); FOO(a2); }", "", ""}, // Pointer to member functions {"auto gA = &a::Foo::func;", "auto gA = &b::Bar::func;", "", ""}, {"using a::Foo; auto gA = &Foo::func;", "using b::Bar; auto gA = &b::Bar::func;", "", ""}, {"using a::Foo; namespace x { auto gA = &Foo::func; }", "using b::Bar; namespace x { auto gA = &Bar::func; }", "", ""}, {"typedef a::Foo T; auto gA = &T::func;", "typedef b::Bar T; auto gA = &T::func;", "", ""}, {"auto gA = &MACRO(a::Foo)::func;", "auto gA = &MACRO(b::Bar)::func;", "", ""}, // Short match inside a namespace {"namespace a { void f(Foo a1) {} }", "namespace a { void f(b::Bar a1) {} }", "", ""}, // Correct match. {"using a::Foo; struct F { ptr a_; };", "using b::Bar; struct F { ptr a_; };", "", ""}, // avoid false positives {"void f(b::Foo a) {}", "void f(b::Foo a) {}", "", ""}, {"namespace b { void f(Foo a) {} }", "namespace b { void f(Foo a) {} }", "", ""}, // friends, everyone needs friends. {"class Foo { int i; friend class a::Foo; };", "class Foo { int i; friend class b::Bar; };", "", ""}, })), ); TEST_P(RenameClassTest, RenameClasses) { auto Param = GetParam(); std::string OldName = Param.OldName.empty() ? "a::Foo" : Param.OldName; std::string NewName = Param.NewName.empty() ? "b::Bar" : Param.NewName; std::string Actual = runClangRenameOnCode(Param.Before, OldName, NewName); CompareSnippets(Param.After, Actual); } class NamespaceDetectionTest : public ClangRenameTest { protected: NamespaceDetectionTest() { AppendToHeader(R"( class Old {}; namespace o1 { class Old {}; namespace o2 { class Old {}; namespace o3 { class Old {}; } // namespace o3 } // namespace o2 } // namespace o1 )"); } }; INSTANTIATE_TEST_CASE_P( RenameClassTest, NamespaceDetectionTest, ::testing::ValuesIn(std::vector({ // Test old and new namespace overlap. {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }", "namespace o1 { namespace o2 { namespace o3 { New moo; } } }", "o1::o2::o3::Old", "o1::o2::o3::New"}, {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }", "namespace o1 { namespace o2 { namespace o3 { n3::New moo; } } }", "o1::o2::o3::Old", "o1::o2::n3::New"}, {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }", "namespace o1 { namespace o2 { namespace o3 { n2::n3::New moo; } } }", "o1::o2::o3::Old", "o1::n2::n3::New"}, {"namespace o1 { namespace o2 { Old moo; } }", "namespace o1 { namespace o2 { New moo; } }", "::o1::o2::Old", "::o1::o2::New"}, {"namespace o1 { namespace o2 { Old moo; } }", "namespace o1 { namespace o2 { n2::New moo; } }", "::o1::o2::Old", "::o1::n2::New"}, {"namespace o1 { namespace o2 { Old moo; } }", "namespace o1 { namespace o2 { ::n1::n2::New moo; } }", "::o1::o2::Old", "::n1::n2::New"}, {"namespace o1 { namespace o2 { Old moo; } }", "namespace o1 { namespace o2 { n1::n2::New moo; } }", "::o1::o2::Old", "n1::n2::New"}, // Test old and new namespace with differing depths. {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }", "namespace o1 { namespace o2 { namespace o3 { New moo; } } }", "o1::o2::o3::Old", "::o1::New"}, {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }", "namespace o1 { namespace o2 { namespace o3 { New moo; } } }", "o1::o2::o3::Old", "::o1::o2::New"}, {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }", "namespace o1 { namespace o2 { namespace o3 { New moo; } } }", "o1::o2::o3::Old", "o1::New"}, {"namespace o1 { namespace o2 { namespace o3 { Old moo; } } }", "namespace o1 { namespace o2 { namespace o3 { New moo; } } }", "o1::o2::o3::Old", "o1::o2::New"}, {"Old moo;", "o1::New moo;", "::Old", "o1::New"}, {"Old moo;", "o1::New moo;", "Old", "o1::New"}, {"namespace o1 { ::Old moo; }", "namespace o1 { New moo; }", "Old", "o1::New"}, {"namespace o1 { namespace o2 { Old moo; } }", "namespace o1 { namespace o2 { ::New moo; } }", "::o1::o2::Old", "::New"}, {"namespace o1 { namespace o2 { Old moo; } }", "namespace o1 { namespace o2 { New moo; } }", "::o1::o2::Old", "New"}, // Test moving into the new namespace at different levels. {"namespace n1 { namespace n2 { o1::o2::Old moo; } }", "namespace n1 { namespace n2 { New moo; } }", "::o1::o2::Old", "::n1::n2::New"}, {"namespace n1 { namespace n2 { o1::o2::Old moo; } }", "namespace n1 { namespace n2 { New moo; } }", "::o1::o2::Old", "n1::n2::New"}, {"namespace n1 { namespace n2 { o1::o2::Old moo; } }", "namespace n1 { namespace n2 { o2::New moo; } }", "::o1::o2::Old", "::n1::o2::New"}, {"namespace n1 { namespace n2 { o1::o2::Old moo; } }", "namespace n1 { namespace n2 { o2::New moo; } }", "::o1::o2::Old", "n1::o2::New"}, {"namespace n1 { namespace n2 { o1::o2::Old moo; } }", "namespace n1 { namespace n2 { ::o1::o2::New moo; } }", "::o1::o2::Old", "::o1::o2::New"}, {"namespace n1 { namespace n2 { o1::o2::Old moo; } }", "namespace n1 { namespace n2 { o1::o2::New moo; } }", "::o1::o2::Old", "o1::o2::New"}, // Test friends declarations. {"class Foo { friend class o1::Old; };", "class Foo { friend class o1::New; };", "o1::Old", "o1::New"}, {"class Foo { int i; friend class o1::Old; };", "class Foo { int i; friend class ::o1::New; };", "::o1::Old", "::o1::New"}, {"namespace o1 { class Foo { int i; friend class Old; }; }", "namespace o1 { class Foo { int i; friend class New; }; }", "o1::Old", "o1::New"}, {"namespace o1 { class Foo { int i; friend class Old; }; }", "namespace o1 { class Foo { int i; friend class New; }; }", "::o1::Old", "::o1::New"}, })), ); TEST_P(NamespaceDetectionTest, RenameClasses) { auto Param = GetParam(); std::string Actual = runClangRenameOnCode(Param.Before, Param.OldName, Param.NewName); CompareSnippets(Param.After, Actual); } class TemplatedClassRenameTest : public ClangRenameTest { protected: TemplatedClassRenameTest() { AppendToHeader(R"( template struct Old { T t_; T f() { return T(); }; static T s(T t) { return t; } }; namespace ns { template struct Old { T t_; T f() { return T(); }; static T s(T t) { return t; } }; } // namespace ns namespace o1 { namespace o2 { namespace o3 { template struct Old { T t_; T f() { return T(); }; static T s(T t) { return t; } }; } // namespace o3 } // namespace o2 } // namespace o1 )"); } }; INSTANTIATE_TEST_CASE_P( RenameClassTests, TemplatedClassRenameTest, ::testing::ValuesIn(std::vector({ {"Old gI; Old gB;", "New gI; New gB;", "Old", "New"}, {"ns::Old gI; ns::Old gB;", "ns::New gI; ns::New gB;", "ns::Old", "ns::New"}, {"auto gI = &Old::f; auto gB = &Old::f;", "auto gI = &New::f; auto gB = &New::f;", "Old", "New"}, {"auto gI = &ns::Old::f;", "auto gI = &ns::New::f;", "ns::Old", "ns::New"}, {"int gI = Old::s(0); bool gB = Old::s(false);", "int gI = New::s(0); bool gB = New::s(false);", "Old", "New"}, {"int gI = ns::Old::s(0); bool gB = ns::Old::s(false);", "int gI = ns::New::s(0); bool gB = ns::New::s(false);", "ns::Old", "ns::New"}, {"struct S { Old o_; };", "struct S { New o_; };", "Old", "New"}, {"struct S { ns::Old o_; };", "struct S { ns::New o_; };", "ns::Old", "ns::New"}, {"auto a = reinterpret_cast*>(new Old);", "auto a = reinterpret_cast*>(new New);", "Old", "New"}, {"auto a = reinterpret_cast*>(new ns::Old);", "auto a = reinterpret_cast*>(new ns::New);", "ns::Old", "ns::New"}, {"auto a = reinterpret_cast*>(new Old);", "auto a = reinterpret_cast*>(new New);", "Old", "New"}, {"auto a = reinterpret_cast*>(new ns::Old);", "auto a = reinterpret_cast*>(new ns::New);", "ns::Old", "ns::New"}, {"Old& foo();", "New& foo();", "Old", "New"}, {"ns::Old& foo();", "ns::New& foo();", "ns::Old", "ns::New"}, {"o1::o2::o3::Old& foo();", "o1::o2::o3::New& foo();", "o1::o2::o3::Old", "o1::o2::o3::New"}, {"namespace ns { Old& foo(); }", "namespace ns { New& foo(); }", "ns::Old", "ns::New"}, {"const Old& foo();", "const New& foo();", "Old", "New"}, {"const ns::Old& foo();", "const ns::New& foo();", "ns::Old", "ns::New"}, // FIXME: figure out why this only works when Moo gets // specialized at some point. {"template struct Moo { Old o_; }; Moo m;", "template struct Moo { New o_; }; Moo m;", "Old", "New"}, {"template struct Moo { ns::Old o_; }; Moo m;", "template struct Moo { ns::New o_; }; Moo m;", "ns::Old", "ns::New"}, })), ); TEST_P(TemplatedClassRenameTest, RenameTemplateClasses) { auto Param = GetParam(); std::string Actual = runClangRenameOnCode(Param.Before, Param.OldName, Param.NewName); CompareSnippets(Param.After, Actual); } TEST_F(ClangRenameTest, RenameClassWithOutOfLineMembers) { std::string Before = R"( class Old { public: Old(); ~Old(); Old* next(); private: Old* next_; }; Old::Old() {} Old::~Old() {} Old* Old::next() { return next_; } )"; std::string Expected = R"( class New { public: New(); ~New(); New* next(); private: New* next_; }; New::New() {} New::~New() {} New* New::next() { return next_; } )"; std::string After = runClangRenameOnCode(Before, "Old", "New"); CompareSnippets(Expected, After); } TEST_F(ClangRenameTest, RenameClassWithInlineMembers) { std::string Before = R"( class Old { public: Old() {} ~Old() {} Old* next() { return next_; } private: Old* next_; }; )"; std::string Expected = R"( class New { public: New() {} ~New() {} New* next() { return next_; } private: New* next_; }; )"; std::string After = runClangRenameOnCode(Before, "Old", "New"); CompareSnippets(Expected, After); } TEST_F(ClangRenameTest, RenameClassWithNamespaceWithInlineMembers) { std::string Before = R"( namespace ns { class Old { public: Old() {} ~Old() {} Old* next() { return next_; } private: Old* next_; }; } // namespace ns )"; std::string Expected = R"( namespace ns { class New { public: New() {} ~New() {} New* next() { return next_; } private: New* next_; }; } // namespace ns )"; std::string After = runClangRenameOnCode(Before, "ns::Old", "ns::New"); CompareSnippets(Expected, After); } TEST_F(ClangRenameTest, RenameClassWithNamespaceWithOutOfInlineMembers) { std::string Before = R"( namespace ns { class Old { public: Old(); ~Old(); Old* next(); private: Old* next_; }; Old::Old() {} Old::~Old() {} Old* Old::next() { return next_; } } // namespace ns )"; std::string Expected = R"( namespace ns { class New { public: New(); ~New(); New* next(); private: New* next_; }; New::New() {} New::~New() {} New* New::next() { return next_; } } // namespace ns )"; std::string After = runClangRenameOnCode(Before, "ns::Old", "ns::New"); CompareSnippets(Expected, After); } TEST_F(ClangRenameTest, RenameClassInInheritedConstructor) { // `using Base::Base;` will generate an implicit constructor containing usage // of `::ns::Old` which should not be matched. std::string Before = R"( namespace ns { class Old; class Old { int x; }; class Base { protected: Old *moo_; public: Base(Old *moo) : moo_(moo) {} }; class Derived : public Base { public: using Base::Base; }; } // namespace ns int main() { ::ns::Old foo; ::ns::Derived d(&foo); return 0; })"; std::string Expected = R"( namespace ns { class New; class New { int x; }; class Base { protected: New *moo_; public: Base(New *moo) : moo_(moo) {} }; class Derived : public Base { public: using Base::Base; }; } // namespace ns int main() { ::ns::New foo; ::ns::Derived d(&foo); return 0; })"; std::string After = runClangRenameOnCode(Before, "ns::Old", "ns::New"); CompareSnippets(Expected, After); } TEST_F(ClangRenameTest, DontRenameReferencesInImplicitFunction) { std::string Before = R"( namespace ns { class Old { }; } // namespace ns struct S { int y; ns::Old old; }; void f() { S s1, s2, s3; // This causes an implicit assignment operator to be created. s1 = s2 = s3; } )"; std::string Expected = R"( namespace ns { class New { }; } // namespace ns struct S { int y; ::new_ns::New old; }; void f() { S s1, s2, s3; // This causes an implicit assignment operator to be created. s1 = s2 = s3; } )"; std::string After = runClangRenameOnCode(Before, "ns::Old", "::new_ns::New"); CompareSnippets(Expected, After); } TEST_F(ClangRenameTest, ReferencesInLambdaFunctionParameters) { std::string Before = R"( template class function; template class function { public: template function(Functor f) {} function() {} R operator()(ArgTypes...) const {} }; namespace ns { class Old {}; void f() { function func; } } // namespace ns)"; std::string Expected = R"( template class function; template class function { public: template function(Functor f) {} function() {} R operator()(ArgTypes...) const {} }; namespace ns { class New {}; void f() { function func; } } // namespace ns)"; std::string After = runClangRenameOnCode(Before, "ns::Old", "::new_ns::New"); CompareSnippets(Expected, After); } TEST_F(ClangRenameTest, DontChangeIfSameName) { std::string Before = R"( namespace foo { class Old { public: static void foo() {} }; } void f(foo::Old * x) { foo::Old::foo() ; } using foo::Old;)"; std::string Expected = R"( namespace foo { class Old { public: static void foo() {} }; } void f(foo::Old * x) { foo::Old::foo() ; } using foo::Old;)"; std::string After = runClangRenameOnCode(Before, "foo::Old", "foo::Old"); CompareSnippets(Expected, After); } TEST_F(ClangRenameTest, ChangeIfNewNameWithLeadingDotDot) { std::string Before = R"( namespace foo { class Old { public: static void foo() {} }; } void f(foo::Old * x) { foo::Old::foo() ; } using foo::Old;)"; std::string Expected = R"( namespace foo { class Old { public: static void foo() {} }; } void f(::foo::Old * x) { ::foo::Old::foo() ; } using ::foo::Old;)"; std::string After = runClangRenameOnCode(Before, "foo::Old", "::foo::Old"); CompareSnippets(Expected, After); } TEST_F(ClangRenameTest, ChangeIfSameNameWithLeadingDotDot) { std::string Before = R"( namespace foo { class Old { public: static void foo() {} }; } void f(foo::Old * x) { foo::Old::foo() ; } using foo::Old;)"; std::string Expected = R"( namespace foo { class Old { public: static void foo() {} }; } void f(::foo::Old * x) { ::foo::Old::foo() ; } using ::foo::Old;)"; std::string After = runClangRenameOnCode(Before, "::foo::Old", "::foo::Old"); CompareSnippets(Expected, After); } TEST_F(RenameClassTest, UsingAlias) { std::string Before = R"( namespace a { struct A {}; } namespace foo { using Alias = a::A; Alias a; })"; std::string Expected = R"( namespace a { struct B {}; } namespace foo { using Alias = b::B; Alias a; })"; std::string After = runClangRenameOnCode(Before, "a::A", "b::B"); CompareSnippets(Expected, After); } // FIXME: investigate why the test fails when adding a new USR to the USRSet. TEST_F(ClangRenameTest, DISABLED_NestedTemplates) { std::string Before = R"( namespace a { template struct A {}; } a::A> foo;)"; std::string Expected = R"( namespace a { template struct B {}; } b::B> foo;)"; std::string After = runClangRenameOnCode(Before, "a::A", "b::B"); CompareSnippets(Expected, After); } } // anonymous namespace } // namespace test } // namespace clang_rename } // namesdpace clang