//===-- 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
