584 lines
10 KiB
C++
584 lines
10 KiB
C++
|
//===-- 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 "TweakTesting.h"
|
||
|
#include "support/Context.h"
|
||
|
#include "llvm/ADT/StringMap.h"
|
||
|
#include "llvm/ADT/StringRef.h"
|
||
|
#include "gtest/gtest.h"
|
||
|
#include <string>
|
||
|
#include <utility>
|
||
|
|
||
|
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 { ee_enum_value };
|
||
|
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(); }");
|
||
|
|
||
|
// NestedNameSpecifier, but no namespace.
|
||
|
EXPECT_UNAVAILABLE(Header + "class Foo {}; class F^oo foo;");
|
||
|
|
||
|
// Nested macro case.
|
||
|
EXPECT_AVAILABLE(R"cpp(
|
||
|
#define ID2(X) X
|
||
|
#define ID(Y, X) Y;ID2(X)
|
||
|
namespace ns { struct Foo{}; }
|
||
|
ID(int xyz, ns::F^oo) f;)cpp");
|
||
|
|
||
|
// 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, Crash1072) {
|
||
|
// Used to crash when traversing catch(...)
|
||
|
// https://github.com/clangd/clangd/issues/1072
|
||
|
const char *Code = R"cpp(
|
||
|
namespace ns { class A; }
|
||
|
ns::^A *err;
|
||
|
void catchall() {
|
||
|
try {} catch(...) {}
|
||
|
}
|
||
|
)cpp";
|
||
|
EXPECT_AVAILABLE(Code);
|
||
|
}
|
||
|
|
||
|
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() {
|
||
|
^one::two::ff();
|
||
|
}
|
||
|
})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() {
|
||
|
::one::t^wo::cc 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() {
|
||
|
one::two::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::oo();
|
||
|
}
|
||
|
})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"},
|
||
|
// Inside a lambda.
|
||
|
{
|
||
|
R"cpp(
|
||
|
namespace NS {
|
||
|
void unrelated();
|
||
|
void foo();
|
||
|
}
|
||
|
|
||
|
auto L = [] {
|
||
|
using NS::unrelated;
|
||
|
NS::f^oo();
|
||
|
};)cpp",
|
||
|
R"cpp(
|
||
|
namespace NS {
|
||
|
void unrelated();
|
||
|
void foo();
|
||
|
}
|
||
|
|
||
|
auto L = [] {
|
||
|
using NS::foo;using NS::unrelated;
|
||
|
foo();
|
||
|
};)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"},
|
||
|
// Typo correction.
|
||
|
{R"cpp(
|
||
|
// error-ok
|
||
|
#include "test.hpp"
|
||
|
c^c C;
|
||
|
)cpp",
|
||
|
R"cpp(
|
||
|
// error-ok
|
||
|
#include "test.hpp"
|
||
|
using one::two::cc;
|
||
|
|
||
|
cc C;
|
||
|
)cpp"},
|
||
|
{R"cpp(
|
||
|
// error-ok
|
||
|
#include "test.hpp"
|
||
|
void foo() {
|
||
|
switch(one::two::ee{}) { case two::ee_^one:break; }
|
||
|
}
|
||
|
)cpp",
|
||
|
R"cpp(
|
||
|
// error-ok
|
||
|
#include "test.hpp"
|
||
|
using one::two::ee_one;
|
||
|
|
||
|
void foo() {
|
||
|
switch(one::two::ee{}) { case ee_one:break; }
|
||
|
}
|
||
|
)cpp"},
|
||
|
{R"cpp(
|
||
|
#include "test.hpp"
|
||
|
void foo() {
|
||
|
one::f^unc_temp<int>();
|
||
|
})cpp",
|
||
|
R"cpp(
|
||
|
#include "test.hpp"
|
||
|
using one::func_temp;
|
||
|
|
||
|
void foo() {
|
||
|
func_temp<int>();
|
||
|
})cpp"},
|
||
|
{R"cpp(
|
||
|
#include "test.hpp"
|
||
|
void foo() {
|
||
|
one::va^r_temp<int>;
|
||
|
})cpp",
|
||
|
R"cpp(
|
||
|
#include "test.hpp"
|
||
|
using one::var_temp;
|
||
|
|
||
|
void foo() {
|
||
|
var_temp<int>;
|
||
|
})cpp"},
|
||
|
};
|
||
|
llvm::StringMap<std::string> EditedFiles;
|
||
|
for (const auto &Case : Cases) {
|
||
|
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 {};
|
||
|
template <typename T> void func_temp();
|
||
|
template <typename T> T var_temp();
|
||
|
})cpp";
|
||
|
// Typo correction is disabled in msvc-compatibility mode.
|
||
|
ExtraArgs.push_back("-fno-ms-compatibility");
|
||
|
EXPECT_EQ(apply(Case.TestSource, &EditedFiles), Case.ExpectedSource);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
} // namespace clangd
|
||
|
} // namespace clang
|