//===-- 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 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 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(); }"); // 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"}}; llvm::StringMap 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; })cpp"; EXPECT_EQ(apply(SubCase, &EditedFiles), Case.ExpectedSource); } } } } // namespace } // namespace clangd } // namespace clang