//===-- AddUsingTests.cpp ---------------------------------------*- C++ -*-===//
//
// 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 "Config.h"
#include "TestTU.h"
#include "TweakTesting.h"
#include "gmock/gmock-matchers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

namespace clang {
namespace clangd {
namespace {

TWEAK_TEST(AddUsing);

TEST_F(AddUsingTest, Prepare) {
  Config Cfg;
  Cfg.Style.FullyQualifiedNamespaces.push_back("ban");
  WithContextValue WithConfig(Config::Key, std::move(Cfg));

  const std::string Header = R"cpp(
#define NS(name) one::two::name
namespace ban { void foo() {} }
namespace banana { void foo() {} }
namespace one {
void oo() {}
template<typename TT> class tt {};
namespace two {
enum ee {};
void ff() {}
class cc {
public:
  struct st {};
  static void mm() {}
  cc operator|(const cc& x) const { return x; }
};
}
})cpp";

  EXPECT_AVAILABLE(Header + "void fun() { o^n^e^:^:^t^w^o^:^:^f^f(); }");
  EXPECT_AVAILABLE(Header + "void fun() { o^n^e^::^o^o(); }");
  EXPECT_AVAILABLE(Header + "void fun() { o^n^e^:^:^t^w^o^:^:^e^e E; }");
  EXPECT_AVAILABLE(Header + "void fun() { o^n^e^:^:^t^w^o:^:^c^c C; }");
  EXPECT_UNAVAILABLE(Header +
                     "void fun() { o^n^e^:^:^t^w^o^:^:^c^c^:^:^m^m(); }");
  EXPECT_UNAVAILABLE(Header +
                     "void fun() { o^n^e^:^:^t^w^o^:^:^c^c^:^:^s^t inst; }");
  EXPECT_UNAVAILABLE(Header +
                     "void fun() { o^n^e^:^:^t^w^o^:^:^c^c^:^:^s^t inst; }");
  EXPECT_UNAVAILABLE(Header + "void fun() { N^S(c^c) inst; }");
  // This used to crash. Ideally we would support this case, but for now we just
  // test that we don't crash.
  EXPECT_UNAVAILABLE(Header +
                     "template<typename TT> using foo = one::tt<T^T>;");
  // Test that we don't crash or misbehave on unnamed DeclRefExpr.
  EXPECT_UNAVAILABLE(Header +
                     "void fun() { one::two::cc() ^| one::two::cc(); }");
  // Do not offer code action when operating on a banned namespace.
  EXPECT_UNAVAILABLE(Header + "void fun() { ban::fo^o(); }");
  EXPECT_UNAVAILABLE(Header + "void fun() { ::ban::fo^o(); }");
  EXPECT_AVAILABLE(Header + "void fun() { banana::fo^o(); }");

  // Do not offer code action on typo-corrections.
  EXPECT_UNAVAILABLE(Header + "/*error-ok*/c^c C;");

  // NestedNameSpecifier, but no namespace.
  EXPECT_UNAVAILABLE(Header + "class Foo {}; class F^oo foo;");

  // Check that we do not trigger in header files.
  FileName = "test.h";
  ExtraArgs.push_back("-xc++-header"); // .h file is treated a C by default.
  EXPECT_UNAVAILABLE(Header + "void fun() { one::two::f^f(); }");
  FileName = "test.hpp";
  EXPECT_UNAVAILABLE(Header + "void fun() { one::two::f^f(); }");
}

TEST_F(AddUsingTest, Apply) {
  FileName = "test.cpp";
  struct {
    llvm::StringRef TestSource;
    llvm::StringRef ExpectedSource;
  } Cases[]{{
                // Function, no other using, namespace.
                R"cpp(
#include "test.hpp"
namespace {
void fun() {
  ^o^n^e^:^:^t^w^o^:^:^f^f();
}
})cpp",
                R"cpp(
#include "test.hpp"
namespace {using one::two::ff;

void fun() {
  ff();
}
})cpp",
            },
            // Type, no other using, namespace.
            {
                R"cpp(
#include "test.hpp"
namespace {
void fun() {
  ::on^e::t^wo::c^c inst;
}
})cpp",
                R"cpp(
#include "test.hpp"
namespace {using ::one::two::cc;

void fun() {
  cc inst;
}
})cpp",
            },
            // Type, no other using, no namespace.
            {
                R"cpp(
#include "test.hpp"

void fun() {
  on^e::t^wo::e^e inst;
})cpp",
                R"cpp(
#include "test.hpp"

using one::two::ee;

void fun() {
  ee inst;
})cpp"},
            // Function, other usings.
            {
                R"cpp(
#include "test.hpp"

using one::two::cc;
using one::two::ee;

namespace {
void fun() {
  one::two::f^f();
}
})cpp",
                R"cpp(
#include "test.hpp"

using one::two::cc;
using one::two::ff;using one::two::ee;

namespace {
void fun() {
  ff();
}
})cpp",
            },
            // Function, other usings inside namespace.
            {
                R"cpp(
#include "test.hpp"

using one::two::cc;

namespace {

using one::two::ff;

void fun() {
  o^ne::o^o();
}
})cpp",
                R"cpp(
#include "test.hpp"

using one::two::cc;

namespace {

using one::oo;using one::two::ff;

void fun() {
  oo();
}
})cpp"},
            // Using comes after cursor.
            {
                R"cpp(
#include "test.hpp"

namespace {

void fun() {
  one::t^wo::ff();
}

using one::two::cc;

})cpp",
                R"cpp(
#include "test.hpp"

namespace {using one::two::ff;


void fun() {
  ff();
}

using one::two::cc;

})cpp"},
            // Pointer type.
            {R"cpp(
#include "test.hpp"

void fun() {
  one::two::c^c *p;
})cpp",
             R"cpp(
#include "test.hpp"

using one::two::cc;

void fun() {
  cc *p;
})cpp"},
            // Namespace declared via macro.
            {R"cpp(
#include "test.hpp"
#define NS_BEGIN(name) namespace name {

NS_BEGIN(foo)

void fun() {
  one::two::f^f();
}
})cpp",
             R"cpp(
#include "test.hpp"
#define NS_BEGIN(name) namespace name {

using one::two::ff;

NS_BEGIN(foo)

void fun() {
  ff();
}
})cpp"},
            // Inside macro argument.
            {R"cpp(
#include "test.hpp"
#define CALL(name) name()

void fun() {
  CALL(one::t^wo::ff);
})cpp",
             R"cpp(
#include "test.hpp"
#define CALL(name) name()

using one::two::ff;

void fun() {
  CALL(ff);
})cpp"},
            // Parent namespace != lexical parent namespace
            {R"cpp(
#include "test.hpp"
namespace foo { void fun(); }

void foo::fun() {
  one::two::f^f();
})cpp",
             R"cpp(
#include "test.hpp"
using one::two::ff;

namespace foo { void fun(); }

void foo::fun() {
  ff();
})cpp"},
            // If all other using are fully qualified, add ::
            {R"cpp(
#include "test.hpp"

using ::one::two::cc;
using ::one::two::ee;

void fun() {
  one::two::f^f();
})cpp",
             R"cpp(
#include "test.hpp"

using ::one::two::cc;
using ::one::two::ff;using ::one::two::ee;

void fun() {
  ff();
})cpp"},
            // Make sure we don't add :: if it's already there
            {R"cpp(
#include "test.hpp"

using ::one::two::cc;
using ::one::two::ee;

void fun() {
  ::one::two::f^f();
})cpp",
             R"cpp(
#include "test.hpp"

using ::one::two::cc;
using ::one::two::ff;using ::one::two::ee;

void fun() {
  ff();
})cpp"},
            // If even one using doesn't start with ::, do not add it
            {R"cpp(
#include "test.hpp"

using ::one::two::cc;
using one::two::ee;

void fun() {
  one::two::f^f();
})cpp",
             R"cpp(
#include "test.hpp"

using ::one::two::cc;
using one::two::ff;using one::two::ee;

void fun() {
  ff();
})cpp"},
            // using alias; insert using for the spelled name.
            {R"cpp(
#include "test.hpp"

void fun() {
  one::u^u u;
})cpp",
             R"cpp(
#include "test.hpp"

using one::uu;

void fun() {
  uu u;
})cpp"},
            // using namespace.
            {R"cpp(
#include "test.hpp"
using namespace one;
namespace {
two::c^c C;
})cpp",
             R"cpp(
#include "test.hpp"
using namespace one;
namespace {using two::cc;

cc C;
})cpp"},
            // Type defined in main file, make sure using is after that.
            {R"cpp(
namespace xx {
  struct yy {};
}

x^x::yy X;
)cpp",
             R"cpp(
namespace xx {
  struct yy {};
}

using xx::yy;

yy X;
)cpp"},
            // Type defined in main file via "using", insert after that.
            {R"cpp(
#include "test.hpp"

namespace xx {
  using yy = one::two::cc;
}

x^x::yy X;
)cpp",
             R"cpp(
#include "test.hpp"

namespace xx {
  using yy = one::two::cc;
}

using xx::yy;

yy X;
)cpp"},
            // Using must come after function definition.
            {R"cpp(
namespace xx {
  void yy();
}

void fun() {
  x^x::yy();
}
)cpp",
             R"cpp(
namespace xx {
  void yy();
}

using xx::yy;

void fun() {
  yy();
}
)cpp"},
            // Existing using with non-namespace part.
            {R"cpp(
#include "test.hpp"
using one::two::ee::ee_one;
one::t^wo::cc c;
)cpp",
             R"cpp(
#include "test.hpp"
using one::two::cc;using one::two::ee::ee_one;
cc c;
)cpp"},
            // Template (like std::vector).
            {R"cpp(
#include "test.hpp"
one::v^ec<int> foo;
)cpp",
             R"cpp(
#include "test.hpp"
using one::vec;

vec<int> foo;
)cpp"}};
  llvm::StringMap<std::string> EditedFiles;
  for (const auto &Case : Cases) {
    for (const auto &SubCase : expandCases(Case.TestSource)) {
      ExtraFiles["test.hpp"] = R"cpp(
namespace one {
void oo() {}
namespace two {
enum ee {ee_one};
void ff() {}
class cc {
public:
  struct st { struct nested {}; };
  static void mm() {}
};
}
using uu = two::cc;
template<typename T> struct vec {};
})cpp";
      EXPECT_EQ(apply(SubCase, &EditedFiles), Case.ExpectedSource);
    }
  }
}

} // namespace
} // namespace clangd
} // namespace clang
