blob: b0c2d71328bd16c7416efb1a7fcdc0d761b25b84 [file] [log] [blame]
//===-- 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