584 lines · cpp
1//===-- AddUsingTests.cpp ---------------------------------------*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "Config.h"10#include "TweakTesting.h"11#include "support/Context.h"12#include "llvm/ADT/StringMap.h"13#include "llvm/ADT/StringRef.h"14#include "gtest/gtest.h"15#include <string>16#include <utility>17 18namespace clang {19namespace clangd {20namespace {21 22TWEAK_TEST(AddUsing);23 24TEST_F(AddUsingTest, Prepare) {25 Config Cfg;26 Cfg.Style.FullyQualifiedNamespaces.push_back("ban");27 WithContextValue WithConfig(Config::Key, std::move(Cfg));28 29 const std::string Header = R"cpp(30#define NS(name) one::two::name31namespace ban { void foo() {} }32namespace banana { void foo() {} }33namespace one {34void oo() {}35template<typename TT> class tt {};36namespace two {37enum ee { ee_enum_value };38void ff() {}39class cc {40public:41 struct st {};42 static void mm() {}43 cc operator|(const cc& x) const { return x; }44};45}46})cpp";47 48 EXPECT_AVAILABLE(Header + "void fun() { o^n^e^:^:^t^w^o^:^:^f^f(); }");49 EXPECT_AVAILABLE(Header + "void fun() { o^n^e^::^o^o(); }");50 EXPECT_AVAILABLE(Header + "void fun() { o^n^e^:^:^t^w^o^:^:^e^e E; }");51 EXPECT_AVAILABLE(Header + "void fun() { o^n^e^:^:^t^w^o:^:^c^c C; }");52 EXPECT_UNAVAILABLE(Header +53 "void fun() { o^n^e^:^:^t^w^o^:^:^c^c^:^:^m^m(); }");54 EXPECT_UNAVAILABLE(Header +55 "void fun() { o^n^e^:^:^t^w^o^:^:^c^c^:^:^s^t inst; }");56 EXPECT_UNAVAILABLE(Header +57 "void fun() { o^n^e^:^:^t^w^o^:^:^c^c^:^:^s^t inst; }");58 EXPECT_UNAVAILABLE(Header + "void fun() { N^S(c^c) inst; }");59 // This used to crash. Ideally we would support this case, but for now we just60 // test that we don't crash.61 EXPECT_UNAVAILABLE(Header +62 "template<typename TT> using foo = one::tt<T^T>;");63 // Test that we don't crash or misbehave on unnamed DeclRefExpr.64 EXPECT_UNAVAILABLE(Header +65 "void fun() { one::two::cc() ^| one::two::cc(); }");66 // Do not offer code action when operating on a banned namespace.67 EXPECT_UNAVAILABLE(Header + "void fun() { ban::fo^o(); }");68 EXPECT_UNAVAILABLE(Header + "void fun() { ::ban::fo^o(); }");69 EXPECT_AVAILABLE(Header + "void fun() { banana::fo^o(); }");70 71 // NestedNameSpecifier, but no namespace.72 EXPECT_UNAVAILABLE(Header + "class Foo {}; class F^oo foo;");73 74 // Nested macro case.75 EXPECT_AVAILABLE(R"cpp(76 #define ID2(X) X77 #define ID(Y, X) Y;ID2(X)78 namespace ns { struct Foo{}; }79 ID(int xyz, ns::F^oo) f;)cpp");80 81 // Check that we do not trigger in header files.82 FileName = "test.h";83 ExtraArgs.push_back("-xc++-header"); // .h file is treated a C by default.84 EXPECT_UNAVAILABLE(Header + "void fun() { one::two::f^f(); }");85 FileName = "test.hpp";86 EXPECT_UNAVAILABLE(Header + "void fun() { one::two::f^f(); }");87}88 89TEST_F(AddUsingTest, Crash1072) {90 // Used to crash when traversing catch(...)91 // https://github.com/clangd/clangd/issues/107292 const char *Code = R"cpp(93 namespace ns { class A; }94 ns::^A *err;95 void catchall() {96 try {} catch(...) {}97 }98 )cpp";99 EXPECT_AVAILABLE(Code);100}101 102TEST_F(AddUsingTest, Apply) {103 FileName = "test.cpp";104 struct {105 llvm::StringRef TestSource;106 llvm::StringRef ExpectedSource;107 } Cases[]{108 {109 // Function, no other using, namespace.110 R"cpp(111#include "test.hpp"112namespace {113void fun() {114 ^one::two::ff();115}116})cpp",117 R"cpp(118#include "test.hpp"119namespace {using one::two::ff;120 121void fun() {122 ff();123}124})cpp",125 },126 // Type, no other using, namespace.127 {128 R"cpp(129#include "test.hpp"130namespace {131void fun() {132 ::one::t^wo::cc inst;133}134})cpp",135 R"cpp(136#include "test.hpp"137namespace {using ::one::two::cc;138 139void fun() {140 cc inst;141}142})cpp",143 },144 // Type, no other using, no namespace.145 {146 R"cpp(147#include "test.hpp"148 149void fun() {150 one::two::e^e inst;151})cpp",152 R"cpp(153#include "test.hpp"154 155using one::two::ee;156 157void fun() {158 ee inst;159})cpp"},160 // Function, other usings.161 {162 R"cpp(163#include "test.hpp"164 165using one::two::cc;166using one::two::ee;167 168namespace {169void fun() {170 one::two::f^f();171}172})cpp",173 R"cpp(174#include "test.hpp"175 176using one::two::cc;177using one::two::ff;using one::two::ee;178 179namespace {180void fun() {181 ff();182}183})cpp",184 },185 // Function, other usings inside namespace.186 {187 R"cpp(188#include "test.hpp"189 190using one::two::cc;191 192namespace {193 194using one::two::ff;195 196void fun() {197 o^ne::oo();198}199})cpp",200 R"cpp(201#include "test.hpp"202 203using one::two::cc;204 205namespace {206 207using one::oo;using one::two::ff;208 209void fun() {210 oo();211}212})cpp"},213 // Using comes after cursor.214 {215 R"cpp(216#include "test.hpp"217 218namespace {219 220void fun() {221 one::t^wo::ff();222}223 224using one::two::cc;225 226})cpp",227 R"cpp(228#include "test.hpp"229 230namespace {using one::two::ff;231 232 233void fun() {234 ff();235}236 237using one::two::cc;238 239})cpp"},240 // Pointer type.241 {R"cpp(242#include "test.hpp"243 244void fun() {245 one::two::c^c *p;246})cpp",247 R"cpp(248#include "test.hpp"249 250using one::two::cc;251 252void fun() {253 cc *p;254})cpp"},255 // Namespace declared via macro.256 {R"cpp(257#include "test.hpp"258#define NS_BEGIN(name) namespace name {259 260NS_BEGIN(foo)261 262void fun() {263 one::two::f^f();264}265})cpp",266 R"cpp(267#include "test.hpp"268#define NS_BEGIN(name) namespace name {269 270using one::two::ff;271 272NS_BEGIN(foo)273 274void fun() {275 ff();276}277})cpp"},278 // Inside macro argument.279 {R"cpp(280#include "test.hpp"281#define CALL(name) name()282 283void fun() {284 CALL(one::t^wo::ff);285})cpp",286 R"cpp(287#include "test.hpp"288#define CALL(name) name()289 290using one::two::ff;291 292void fun() {293 CALL(ff);294})cpp"},295 // Parent namespace != lexical parent namespace296 {R"cpp(297#include "test.hpp"298namespace foo { void fun(); }299 300void foo::fun() {301 one::two::f^f();302})cpp",303 R"cpp(304#include "test.hpp"305using one::two::ff;306 307namespace foo { void fun(); }308 309void foo::fun() {310 ff();311})cpp"},312 // Inside a lambda.313 {314 R"cpp(315namespace NS {316void unrelated();317void foo();318}319 320auto L = [] {321 using NS::unrelated;322 NS::f^oo();323};)cpp",324 R"cpp(325namespace NS {326void unrelated();327void foo();328}329 330auto L = [] {331 using NS::foo;using NS::unrelated;332 foo();333};)cpp",334 },335 // If all other using are fully qualified, add ::336 {R"cpp(337#include "test.hpp"338 339using ::one::two::cc;340using ::one::two::ee;341 342void fun() {343 one::two::f^f();344})cpp",345 R"cpp(346#include "test.hpp"347 348using ::one::two::cc;349using ::one::two::ff;using ::one::two::ee;350 351void fun() {352 ff();353})cpp"},354 // Make sure we don't add :: if it's already there355 {R"cpp(356#include "test.hpp"357 358using ::one::two::cc;359using ::one::two::ee;360 361void fun() {362 ::one::two::f^f();363})cpp",364 R"cpp(365#include "test.hpp"366 367using ::one::two::cc;368using ::one::two::ff;using ::one::two::ee;369 370void fun() {371 ff();372})cpp"},373 // If even one using doesn't start with ::, do not add it374 {R"cpp(375#include "test.hpp"376 377using ::one::two::cc;378using one::two::ee;379 380void fun() {381 one::two::f^f();382})cpp",383 R"cpp(384#include "test.hpp"385 386using ::one::two::cc;387using one::two::ff;using one::two::ee;388 389void fun() {390 ff();391})cpp"},392 // using alias; insert using for the spelled name.393 {R"cpp(394#include "test.hpp"395 396void fun() {397 one::u^u u;398})cpp",399 R"cpp(400#include "test.hpp"401 402using one::uu;403 404void fun() {405 uu u;406})cpp"},407 // using namespace.408 {R"cpp(409#include "test.hpp"410using namespace one;411namespace {412two::c^c C;413})cpp",414 R"cpp(415#include "test.hpp"416using namespace one;417namespace {using two::cc;418 419cc C;420})cpp"},421 // Type defined in main file, make sure using is after that.422 {R"cpp(423namespace xx {424 struct yy {};425}426 427x^x::yy X;428)cpp",429 R"cpp(430namespace xx {431 struct yy {};432}433 434using xx::yy;435 436yy X;437)cpp"},438 // Type defined in main file via "using", insert after that.439 {R"cpp(440#include "test.hpp"441 442namespace xx {443 using yy = one::two::cc;444}445 446x^x::yy X;447)cpp",448 R"cpp(449#include "test.hpp"450 451namespace xx {452 using yy = one::two::cc;453}454 455using xx::yy;456 457yy X;458)cpp"},459 // Using must come after function definition.460 {R"cpp(461namespace xx {462 void yy();463}464 465void fun() {466 x^x::yy();467}468)cpp",469 R"cpp(470namespace xx {471 void yy();472}473 474using xx::yy;475 476void fun() {477 yy();478}479)cpp"},480 // Existing using with non-namespace part.481 {R"cpp(482#include "test.hpp"483using one::two::ee::ee_one;484one::t^wo::cc c;485)cpp",486 R"cpp(487#include "test.hpp"488using one::two::cc;using one::two::ee::ee_one;489cc c;490)cpp"},491 // Template (like std::vector).492 {R"cpp(493#include "test.hpp"494one::v^ec<int> foo;495)cpp",496 R"cpp(497#include "test.hpp"498using one::vec;499 500vec<int> foo;501)cpp"},502 // Typo correction.503 {R"cpp(504// error-ok505#include "test.hpp"506c^c C;507)cpp",508 R"cpp(509// error-ok510#include "test.hpp"511using one::two::cc;512 513cc C;514)cpp"},515 {R"cpp(516// error-ok517#include "test.hpp"518void foo() {519 switch(one::two::ee{}) { case two::ee_^one:break; }520}521)cpp",522 R"cpp(523// error-ok524#include "test.hpp"525using one::two::ee_one;526 527void foo() {528 switch(one::two::ee{}) { case ee_one:break; }529}530)cpp"},531 {R"cpp(532#include "test.hpp"533void foo() {534 one::f^unc_temp<int>();535})cpp",536 R"cpp(537#include "test.hpp"538using one::func_temp;539 540void foo() {541 func_temp<int>();542})cpp"},543 {R"cpp(544#include "test.hpp"545void foo() {546 one::va^r_temp<int>;547})cpp",548 R"cpp(549#include "test.hpp"550using one::var_temp;551 552void foo() {553 var_temp<int>;554})cpp"},555 };556 llvm::StringMap<std::string> EditedFiles;557 for (const auto &Case : Cases) {558 ExtraFiles["test.hpp"] = R"cpp(559namespace one {560void oo() {}561namespace two {562enum ee {ee_one};563void ff() {}564class cc {565public:566 struct st { struct nested {}; };567 static void mm() {}568};569}570using uu = two::cc;571template<typename T> struct vec {};572template <typename T> void func_temp();573template <typename T> T var_temp();574})cpp";575 // Typo correction is disabled in msvc-compatibility mode.576 ExtraArgs.push_back("-fno-ms-compatibility");577 EXPECT_EQ(apply(Case.TestSource, &EditedFiles), Case.ExpectedSource);578 }579}580 581} // namespace582} // namespace clangd583} // namespace clang584