//===-- 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 #include 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 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 using foo = one::tt;"); // 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 foo; )cpp", R"cpp( #include "test.hpp" using one::vec; vec 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(); })cpp", R"cpp( #include "test.hpp" using one::func_temp; void foo() { func_temp(); })cpp"}, {R"cpp( #include "test.hpp" void foo() { one::va^r_temp; })cpp", R"cpp( #include "test.hpp" using one::var_temp; void foo() { var_temp; })cpp"}, }; llvm::StringMap 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 struct vec {}; template void func_temp(); template 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