| //===-- RenameFunctionTest.cpp - unit tests for renaming functions --------===// |
| // |
| // 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 RenameFunctionTest : public ClangRenameTest { |
| public: |
| RenameFunctionTest() { |
| AppendToHeader(R"( |
| struct A { |
| static bool Foo(); |
| static bool Spam(); |
| }; |
| struct B { |
| static void Same(); |
| static bool Foo(); |
| static int Eric(int x); |
| }; |
| void Same(int x); |
| int Eric(int x); |
| namespace base { |
| void Same(); |
| void ToNanoSeconds(); |
| void ToInt64NanoSeconds(); |
| })"); |
| } |
| }; |
| |
| TEST_F(RenameFunctionTest, RefactorsAFoo) { |
| std::string Before = R"( |
| void f() { |
| A::Foo(); |
| ::A::Foo(); |
| })"; |
| std::string Expected = R"( |
| void f() { |
| A::Bar(); |
| ::A::Bar(); |
| })"; |
| |
| std::string After = runClangRenameOnCode(Before, "A::Foo", "A::Bar"); |
| CompareSnippets(Expected, After); |
| } |
| |
| TEST_F(RenameFunctionTest, RefactorsNonCallingAFoo) { |
| std::string Before = R"( |
| bool g(bool (*func)()) { |
| return func(); |
| } |
| void f() { |
| auto *ref1 = A::Foo; |
| auto *ref2 = ::A::Foo; |
| g(A::Foo); |
| })"; |
| std::string Expected = R"( |
| bool g(bool (*func)()) { |
| return func(); |
| } |
| void f() { |
| auto *ref1 = A::Bar; |
| auto *ref2 = ::A::Bar; |
| g(A::Bar); |
| })"; |
| std::string After = runClangRenameOnCode(Before, "A::Foo", "A::Bar"); |
| CompareSnippets(Expected, After); |
| } |
| |
| TEST_F(RenameFunctionTest, RefactorsEric) { |
| std::string Before = R"( |
| void f() { |
| if (Eric(3)==4) ::Eric(2); |
| })"; |
| std::string Expected = R"( |
| void f() { |
| if (Larry(3)==4) ::Larry(2); |
| })"; |
| std::string After = runClangRenameOnCode(Before, "Eric", "Larry"); |
| CompareSnippets(Expected, After); |
| } |
| |
| TEST_F(RenameFunctionTest, RefactorsNonCallingEric) { |
| std::string Before = R"( |
| int g(int (*func)(int)) { |
| return func(1); |
| } |
| void f() { |
| auto *ref = ::Eric; |
| g(Eric); |
| })"; |
| std::string Expected = R"( |
| int g(int (*func)(int)) { |
| return func(1); |
| } |
| void f() { |
| auto *ref = ::Larry; |
| g(Larry); |
| })"; |
| std::string After = runClangRenameOnCode(Before, "Eric", "Larry"); |
| CompareSnippets(Expected, After); |
| } |
| |
| TEST_F(RenameFunctionTest, DoesNotRefactorBFoo) { |
| std::string Before = R"( |
| void f() { |
| B::Foo(); |
| })"; |
| std::string After = runClangRenameOnCode(Before, "A::Foo", "A::Bar"); |
| CompareSnippets(Before, After); |
| } |
| |
| TEST_F(RenameFunctionTest, DoesNotRefactorBEric) { |
| std::string Before = R"( |
| void f() { |
| B::Eric(2); |
| })"; |
| std::string After = runClangRenameOnCode(Before, "Eric", "Larry"); |
| CompareSnippets(Before, After); |
| } |
| |
| TEST_F(RenameFunctionTest, DoesNotRefactorCEric) { |
| std::string Before = R"( |
| namespace C { int Eric(int x); } |
| void f() { |
| if (C::Eric(3)==4) ::C::Eric(2); |
| })"; |
| std::string Expected = R"( |
| namespace C { int Eric(int x); } |
| void f() { |
| if (C::Eric(3)==4) ::C::Eric(2); |
| })"; |
| std::string After = runClangRenameOnCode(Before, "Eric", "Larry"); |
| CompareSnippets(Expected, After); |
| } |
| |
| TEST_F(RenameFunctionTest, DoesNotRefactorEricInNamespaceC) { |
| std::string Before = R"( |
| namespace C { |
| int Eric(int x); |
| void f() { |
| if (Eric(3)==4) Eric(2); |
| } |
| } // namespace C)"; |
| std::string After = runClangRenameOnCode(Before, "Eric", "Larry"); |
| CompareSnippets(Before, After); |
| } |
| |
| TEST_F(RenameFunctionTest, NamespaceQualified) { |
| std::string Before = R"( |
| void f() { |
| base::ToNanoSeconds(); |
| ::base::ToNanoSeconds(); |
| } |
| void g() { |
| using base::ToNanoSeconds; |
| base::ToNanoSeconds(); |
| ::base::ToNanoSeconds(); |
| ToNanoSeconds(); |
| } |
| namespace foo { |
| namespace base { |
| void ToNanoSeconds(); |
| void f() { |
| base::ToNanoSeconds(); |
| } |
| } |
| void f() { |
| ::base::ToNanoSeconds(); |
| } |
| })"; |
| std::string Expected = R"( |
| void f() { |
| base::ToInt64NanoSeconds(); |
| ::base::ToInt64NanoSeconds(); |
| } |
| void g() { |
| using base::ToInt64NanoSeconds; |
| base::ToInt64NanoSeconds(); |
| ::base::ToInt64NanoSeconds(); |
| base::ToInt64NanoSeconds(); |
| } |
| namespace foo { |
| namespace base { |
| void ToNanoSeconds(); |
| void f() { |
| base::ToNanoSeconds(); |
| } |
| } |
| void f() { |
| ::base::ToInt64NanoSeconds(); |
| } |
| })"; |
| std::string After = runClangRenameOnCode(Before, "base::ToNanoSeconds", |
| "base::ToInt64NanoSeconds"); |
| CompareSnippets(Expected, After); |
| } |
| |
| TEST_F(RenameFunctionTest, RenameFunctionDecls) { |
| std::string Before = R"( |
| namespace na { |
| void X(); |
| void X() {} |
| })"; |
| std::string Expected = R"( |
| namespace na { |
| void Y(); |
| void Y() {} |
| })"; |
| std::string After = runClangRenameOnCode(Before, "na::X", "na::Y"); |
| CompareSnippets(Expected, After); |
| } |
| |
| TEST_F(RenameFunctionTest, RenameTemplateFunctions) { |
| std::string Before = R"( |
| namespace na { |
| template<typename T> T X(); |
| } |
| namespace na { void f() { X<int>(); } } |
| namespace nb { void g() { na::X <int>(); } } |
| )"; |
| std::string Expected = R"( |
| namespace na { |
| template<typename T> T Y(); |
| } |
| namespace na { void f() { nb::Y<int>(); } } |
| namespace nb { void g() { Y<int>(); } } |
| )"; |
| std::string After = runClangRenameOnCode(Before, "na::X", "nb::Y"); |
| CompareSnippets(Expected, After); |
| } |
| |
| TEST_F(RenameFunctionTest, RenameOutOfLineFunctionDecls) { |
| std::string Before = R"( |
| namespace na { |
| void X(); |
| } |
| void na::X() {} |
| )"; |
| std::string Expected = R"( |
| namespace na { |
| void Y(); |
| } |
| void na::Y() {} |
| )"; |
| std::string After = runClangRenameOnCode(Before, "na::X", "na::Y"); |
| CompareSnippets(Expected, After); |
| } |
| |
| TEST_F(RenameFunctionTest, NewNamespaceWithoutLeadingDotDot) { |
| std::string Before = R"( |
| namespace old_ns { |
| void X(); |
| void X() {} |
| } |
| // Assume that the reference is in another file. |
| void f() { old_ns::X(); } |
| namespace old_ns { void g() { X(); } } |
| namespace new_ns { void h() { ::old_ns::X(); } } |
| )"; |
| std::string Expected = R"( |
| namespace old_ns { |
| void Y(); |
| void Y() {} |
| } |
| // Assume that the reference is in another file. |
| void f() { new_ns::Y(); } |
| namespace old_ns { void g() { new_ns::Y(); } } |
| namespace new_ns { void h() { Y(); } } |
| )"; |
| std::string After = runClangRenameOnCode(Before, "::old_ns::X", "new_ns::Y"); |
| CompareSnippets(Expected, After); |
| } |
| |
| TEST_F(RenameFunctionTest, NewNamespaceWithLeadingDotDot) { |
| std::string Before = R"( |
| namespace old_ns { |
| void X(); |
| void X() {} |
| } |
| // Assume that the reference is in another file. |
| void f() { old_ns::X(); } |
| namespace old_ns { void g() { X(); } } |
| namespace new_ns { void h() { ::old_ns::X(); } } |
| )"; |
| std::string Expected = R"( |
| namespace old_ns { |
| void Y(); |
| void Y() {} |
| } |
| // Assume that the reference is in another file. |
| void f() { ::new_ns::Y(); } |
| namespace old_ns { void g() { ::new_ns::Y(); } } |
| namespace new_ns { void h() { Y(); } } |
| )"; |
| std::string After = |
| runClangRenameOnCode(Before, "::old_ns::X", "::new_ns::Y"); |
| CompareSnippets(Expected, After); |
| } |
| |
| TEST_F(RenameFunctionTest, DontRenameSymbolsDefinedInAnonymousNamespace) { |
| std::string Before = R"( |
| namespace old_ns { |
| class X {}; |
| namespace { |
| void X(); |
| void X() {} |
| void f() { X(); } |
| } |
| } |
| )"; |
| std::string Expected = R"( |
| namespace old_ns { |
| class Y {}; |
| namespace { |
| void X(); |
| void X() {} |
| void f() { X(); } |
| } |
| } |
| )"; |
| std::string After = |
| runClangRenameOnCode(Before, "::old_ns::X", "::old_ns::Y"); |
| CompareSnippets(Expected, After); |
| } |
| |
| TEST_F(RenameFunctionTest, NewNestedNamespace) { |
| std::string Before = R"( |
| namespace old_ns { |
| void X(); |
| void X() {} |
| } |
| // Assume that the reference is in another file. |
| namespace old_ns { |
| void f() { X(); } |
| } |
| )"; |
| std::string Expected = R"( |
| namespace old_ns { |
| void X(); |
| void X() {} |
| } |
| // Assume that the reference is in another file. |
| namespace old_ns { |
| void f() { older_ns::X(); } |
| } |
| )"; |
| std::string After = |
| runClangRenameOnCode(Before, "::old_ns::X", "::old_ns::older_ns::X"); |
| CompareSnippets(Expected, After); |
| } |
| |
| TEST_F(RenameFunctionTest, MoveFromGlobalToNamespaceWithoutLeadingDotDot) { |
| std::string Before = R"( |
| void X(); |
| void X() {} |
| |
| // Assume that the reference is in another file. |
| namespace some_ns { |
| void f() { X(); } |
| } |
| )"; |
| std::string Expected = R"( |
| void X(); |
| void X() {} |
| |
| // Assume that the reference is in another file. |
| namespace some_ns { |
| void f() { ns::X(); } |
| } |
| )"; |
| std::string After = |
| runClangRenameOnCode(Before, "::X", "ns::X"); |
| CompareSnippets(Expected, After); |
| } |
| |
| TEST_F(RenameFunctionTest, MoveFromGlobalToNamespaceWithLeadingDotDot) { |
| std::string Before = R"( |
| void Y() {} |
| |
| // Assume that the reference is in another file. |
| namespace some_ns { |
| void f() { Y(); } |
| } |
| )"; |
| std::string Expected = R"( |
| void Y() {} |
| |
| // Assume that the reference is in another file. |
| namespace some_ns { |
| void f() { ::ns::Y(); } |
| } |
| )"; |
| std::string After = |
| runClangRenameOnCode(Before, "::Y", "::ns::Y"); |
| CompareSnippets(Expected, After); |
| } |
| |
| // FIXME: the rename of overloaded operator is not fully supported yet. |
| TEST_F(RenameFunctionTest, DISABLED_DoNotRenameOverloadedOperatorCalls) { |
| std::string Before = R"( |
| namespace old_ns { |
| class T { public: int x; }; |
| bool operator==(const T& lhs, const T& rhs) { |
| return lhs.x == rhs.x; |
| } |
| } // namespace old_ns |
| |
| // Assume that the reference is in another file. |
| bool f() { |
| auto eq = old_ns::operator==; |
| old_ns::T t1, t2; |
| old_ns::operator==(t1, t2); |
| return t1 == t2; |
| } |
| )"; |
| std::string Expected = R"( |
| namespace old_ns { |
| class T { public: int x; }; |
| bool operator==(const T& lhs, const T& rhs) { |
| return lhs.x == rhs.x; |
| } |
| } // namespace old_ns |
| |
| // Assume that the reference is in another file. |
| bool f() { |
| auto eq = new_ns::operator==; |
| old_ns::T t1, t2; |
| new_ns::operator==(t1, t2); |
| return t1 == t2; |
| } |
| )"; |
| std::string After = |
| runClangRenameOnCode(Before, "old_ns::operator==", "new_ns::operator=="); |
| CompareSnippets(Expected, After); |
| } |
| |
| TEST_F(RenameFunctionTest, FunctionRefAsTemplate) { |
| std::string Before = R"( |
| void X(); |
| |
| // Assume that the reference is in another file. |
| namespace some_ns { |
| template <void (*Func)(void)> |
| class TIterator {}; |
| |
| template <void (*Func)(void)> |
| class T { |
| public: |
| typedef TIterator<Func> IterType; |
| using TI = TIterator<Func>; |
| void g() { |
| Func(); |
| auto func = Func; |
| TIterator<Func> iter; |
| } |
| }; |
| |
| |
| void f() { T<X> tx; tx.g(); } |
| } // namespace some_ns |
| )"; |
| std::string Expected = R"( |
| void X(); |
| |
| // Assume that the reference is in another file. |
| namespace some_ns { |
| template <void (*Func)(void)> |
| class TIterator {}; |
| |
| template <void (*Func)(void)> |
| class T { |
| public: |
| typedef TIterator<Func> IterType; |
| using TI = TIterator<Func>; |
| void g() { |
| Func(); |
| auto func = Func; |
| TIterator<Func> iter; |
| } |
| }; |
| |
| |
| void f() { T<ns::X> tx; tx.g(); } |
| } // namespace some_ns |
| )"; |
| std::string After = runClangRenameOnCode(Before, "::X", "ns::X"); |
| CompareSnippets(Expected, After); |
| } |
| |
| TEST_F(RenameFunctionTest, RenameFunctionInUsingDecl) { |
| std::string Before = R"( |
| using base::ToNanoSeconds; |
| namespace old_ns { |
| using base::ToNanoSeconds; |
| void f() { |
| using base::ToNanoSeconds; |
| } |
| } |
| )"; |
| std::string Expected = R"( |
| using base::ToInt64NanoSeconds; |
| namespace old_ns { |
| using base::ToInt64NanoSeconds; |
| void f() { |
| using base::ToInt64NanoSeconds; |
| } |
| } |
| )"; |
| std::string After = runClangRenameOnCode(Before, "base::ToNanoSeconds", |
| "base::ToInt64NanoSeconds"); |
| CompareSnippets(Expected, After); |
| } |
| |
| // FIXME: Fix the complex the case where the symbol being renamed is located in |
| // `std::function<decltype<renamed_symbol>>`. |
| TEST_F(ClangRenameTest, DISABLED_ReferencesInLambdaFunctionParameters) { |
| std::string Before = R"( |
| template <class T> |
| class function; |
| template <class R, class... ArgTypes> |
| class function<R(ArgTypes...)> { |
| public: |
| template <typename Functor> |
| function(Functor f) {} |
| |
| function() {} |
| |
| R operator()(ArgTypes...) const {} |
| }; |
| |
| namespace ns { |
| void Old() {} |
| void f() { |
| function<decltype(Old)> func; |
| } |
| } // namespace ns)"; |
| std::string Expected = R"( |
| template <class T> |
| class function; |
| template <class R, class... ArgTypes> |
| class function<R(ArgTypes...)> { |
| public: |
| template <typename Functor> |
| function(Functor f) {} |
| |
| function() {} |
| |
| R operator()(ArgTypes...) const {} |
| }; |
| |
| namespace ns { |
| void New() {} |
| void f() { |
| function<decltype(::new_ns::New)> func; |
| } |
| } // namespace ns)"; |
| std::string After = runClangRenameOnCode(Before, "ns::Old", "::new_ns::New"); |
| CompareSnippets(Expected, After); |
| } |
| |
| } // anonymous namespace |
| } // namespace test |
| } // namespace clang_rename |
| } // namesdpace clang |