1 //===-- ExtractFunctionTests.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-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "TweakTesting.h" 10 #include "gmock/gmock.h" 11 #include "gtest/gtest.h" 12 13 using ::testing::HasSubstr; 14 using ::testing::StartsWith; 15 16 namespace clang { 17 namespace clangd { 18 namespace { 19 20 TWEAK_TEST(ExtractFunction); 21 22 TEST_F(ExtractFunctionTest, FunctionTest) { 23 Context = Function; 24 25 // Root statements should have common parent. 26 EXPECT_EQ(apply("for(;;) [[1+2; 1+2;]]"), "unavailable"); 27 // Expressions aren't extracted. 28 EXPECT_EQ(apply("int x = 0; [[x++;]]"), "unavailable"); 29 // We don't support extraction from lambdas. 30 EXPECT_EQ(apply("auto lam = [](){ [[int x;]] }; "), "unavailable"); 31 // Partial statements aren't extracted. 32 EXPECT_THAT(apply("int [[x = 0]];"), "unavailable"); 33 // FIXME: Support hoisting. 34 EXPECT_THAT(apply(" [[int a = 5;]] a++; "), "unavailable"); 35 36 // Ensure that end of Zone and Beginning of PostZone being adjacent doesn't 37 // lead to break being included in the extraction zone. 38 EXPECT_THAT(apply("for(;;) { [[int x;]]break; }"), HasSubstr("extracted")); 39 // FIXME: ExtractFunction should be unavailable inside loop construct 40 // initializer/condition. 41 EXPECT_THAT(apply(" for([[int i = 0;]];);"), HasSubstr("extracted")); 42 // Extract certain return 43 EXPECT_THAT(apply(" if(true) [[{ return; }]] "), HasSubstr("extracted")); 44 // Don't extract uncertain return 45 EXPECT_THAT(apply(" if(true) [[if (false) return;]] "), 46 StartsWith("unavailable")); 47 EXPECT_THAT( 48 apply("#define RETURN_IF_ERROR(x) if (x) return\nRETU^RN_IF_ERROR(4);"), 49 StartsWith("unavailable")); 50 51 FileName = "a.c"; 52 EXPECT_THAT(apply(" for([[int i = 0;]];);"), HasSubstr("unavailable")); 53 } 54 55 TEST_F(ExtractFunctionTest, FileTest) { 56 // Check all parameters are in order 57 std::string ParameterCheckInput = R"cpp( 58 struct Foo { 59 int x; 60 }; 61 void f(int a) { 62 int b; 63 int *ptr = &a; 64 Foo foo; 65 [[a += foo.x + b; 66 *ptr++;]] 67 })cpp"; 68 std::string ParameterCheckOutput = R"cpp( 69 struct Foo { 70 int x; 71 }; 72 void extracted(int &a, int &b, int * &ptr, Foo &foo) { 73 a += foo.x + b; 74 *ptr++; 75 } 76 void f(int a) { 77 int b; 78 int *ptr = &a; 79 Foo foo; 80 extracted(a, b, ptr, foo); 81 })cpp"; 82 EXPECT_EQ(apply(ParameterCheckInput), ParameterCheckOutput); 83 84 // Check const qualifier 85 std::string ConstCheckInput = R"cpp( 86 void f(const int c) { 87 [[while(c) {}]] 88 })cpp"; 89 std::string ConstCheckOutput = R"cpp( 90 void extracted(const int &c) { 91 while(c) {} 92 } 93 void f(const int c) { 94 extracted(c); 95 })cpp"; 96 EXPECT_EQ(apply(ConstCheckInput), ConstCheckOutput); 97 98 // Check const qualifier with namespace 99 std::string ConstNamespaceCheckInput = R"cpp( 100 namespace X { struct Y { int z; }; } 101 int f(const X::Y &y) { 102 [[return y.z + y.z;]] 103 })cpp"; 104 std::string ConstNamespaceCheckOutput = R"cpp( 105 namespace X { struct Y { int z; }; } 106 int extracted(const X::Y &y) { 107 return y.z + y.z; 108 } 109 int f(const X::Y &y) { 110 return extracted(y); 111 })cpp"; 112 EXPECT_EQ(apply(ConstNamespaceCheckInput), ConstNamespaceCheckOutput); 113 114 // Don't extract when we need to make a function as a parameter. 115 EXPECT_THAT(apply("void f() { [[int a; f();]] }"), StartsWith("fail")); 116 117 std::string MethodInput = R"cpp( 118 class T { 119 void f() { 120 [[int x;]] 121 } 122 }; 123 )cpp"; 124 std::string MethodCheckOutput = R"cpp( 125 class T { 126 void extracted() { 127 int x; 128 } 129 void f() { 130 extracted(); 131 } 132 }; 133 )cpp"; 134 EXPECT_EQ(apply(MethodInput), MethodCheckOutput); 135 136 std::string OutOfLineMethodInput = R"cpp( 137 class T { 138 void f(); 139 }; 140 141 void T::f() { 142 [[int x;]] 143 } 144 )cpp"; 145 std::string OutOfLineMethodCheckOutput = R"cpp( 146 class T { 147 void extracted(); 148 void f(); 149 }; 150 151 void T::extracted() { 152 int x; 153 } 154 void T::f() { 155 extracted(); 156 } 157 )cpp"; 158 EXPECT_EQ(apply(OutOfLineMethodInput), OutOfLineMethodCheckOutput); 159 160 // We don't extract from templated functions for now as templates are hard 161 // to deal with. 162 std::string TemplateFailInput = R"cpp( 163 template<typename T> 164 void f() { 165 [[int x;]] 166 } 167 )cpp"; 168 EXPECT_EQ(apply(TemplateFailInput), "unavailable"); 169 170 std::string MacroInput = R"cpp( 171 #define F(BODY) void f() { BODY } 172 F ([[int x = 0;]]) 173 )cpp"; 174 std::string MacroOutput = R"cpp( 175 #define F(BODY) void f() { BODY } 176 void extracted() { 177 int x = 0; 178 } 179 F (extracted();) 180 )cpp"; 181 EXPECT_EQ(apply(MacroInput), MacroOutput); 182 183 // Shouldn't crash. 184 EXPECT_EQ(apply("void f([[int a]]);"), "unavailable"); 185 // Don't extract if we select the entire function body (CompoundStmt). 186 std::string CompoundFailInput = R"cpp( 187 void f() [[{ 188 int a; 189 }]] 190 )cpp"; 191 EXPECT_EQ(apply(CompoundFailInput), "unavailable"); 192 } 193 194 TEST_F(ExtractFunctionTest, DifferentHeaderSourceTest) { 195 Header = R"cpp( 196 class SomeClass { 197 void f(); 198 }; 199 )cpp"; 200 201 std::string OutOfLineSource = R"cpp( 202 void SomeClass::f() { 203 [[int x;]] 204 } 205 )cpp"; 206 207 std::string OutOfLineSourceOutputCheck = R"cpp( 208 void SomeClass::extracted() { 209 int x; 210 } 211 void SomeClass::f() { 212 extracted(); 213 } 214 )cpp"; 215 216 std::string HeaderOutputCheck = R"cpp( 217 class SomeClass { 218 void extracted(); 219 void f(); 220 }; 221 )cpp"; 222 223 llvm::StringMap<std::string> EditedFiles; 224 225 EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck); 226 EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck); 227 } 228 229 TEST_F(ExtractFunctionTest, DifferentFilesNestedTest) { 230 Header = R"cpp( 231 class T { 232 class SomeClass { 233 void f(); 234 }; 235 }; 236 )cpp"; 237 238 std::string NestedOutOfLineSource = R"cpp( 239 void T::SomeClass::f() { 240 [[int x;]] 241 } 242 )cpp"; 243 244 std::string NestedOutOfLineSourceOutputCheck = R"cpp( 245 void T::SomeClass::extracted() { 246 int x; 247 } 248 void T::SomeClass::f() { 249 extracted(); 250 } 251 )cpp"; 252 253 std::string NestedHeaderOutputCheck = R"cpp( 254 class T { 255 class SomeClass { 256 void extracted(); 257 void f(); 258 }; 259 }; 260 )cpp"; 261 262 llvm::StringMap<std::string> EditedFiles; 263 264 EXPECT_EQ(apply(NestedOutOfLineSource, &EditedFiles), 265 NestedOutOfLineSourceOutputCheck); 266 EXPECT_EQ(EditedFiles.begin()->second, NestedHeaderOutputCheck); 267 } 268 269 TEST_F(ExtractFunctionTest, ConstexprDifferentHeaderSourceTest) { 270 Header = R"cpp( 271 class SomeClass { 272 constexpr void f() const; 273 }; 274 )cpp"; 275 276 std::string OutOfLineSource = R"cpp( 277 constexpr void SomeClass::f() const { 278 [[int x;]] 279 } 280 )cpp"; 281 282 std::string OutOfLineSourceOutputCheck = R"cpp( 283 constexpr void SomeClass::extracted() const { 284 int x; 285 } 286 constexpr void SomeClass::f() const { 287 extracted(); 288 } 289 )cpp"; 290 291 std::string HeaderOutputCheck = R"cpp( 292 class SomeClass { 293 constexpr void extracted() const; 294 constexpr void f() const; 295 }; 296 )cpp"; 297 298 llvm::StringMap<std::string> EditedFiles; 299 300 EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck); 301 EXPECT_NE(EditedFiles.begin(), EditedFiles.end()) 302 << "The header should be edited and receives the declaration of the new " 303 "function"; 304 305 if (EditedFiles.begin() != EditedFiles.end()) { 306 EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck); 307 } 308 } 309 310 TEST_F(ExtractFunctionTest, ConstevalDifferentHeaderSourceTest) { 311 ExtraArgs.push_back("--std=c++20"); 312 Header = R"cpp( 313 class SomeClass { 314 consteval void f() const; 315 }; 316 )cpp"; 317 318 std::string OutOfLineSource = R"cpp( 319 consteval void SomeClass::f() const { 320 [[int x;]] 321 } 322 )cpp"; 323 324 std::string OutOfLineSourceOutputCheck = R"cpp( 325 consteval void SomeClass::extracted() const { 326 int x; 327 } 328 consteval void SomeClass::f() const { 329 extracted(); 330 } 331 )cpp"; 332 333 std::string HeaderOutputCheck = R"cpp( 334 class SomeClass { 335 consteval void extracted() const; 336 consteval void f() const; 337 }; 338 )cpp"; 339 340 llvm::StringMap<std::string> EditedFiles; 341 342 EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck); 343 EXPECT_NE(EditedFiles.begin(), EditedFiles.end()) 344 << "The header should be edited and receives the declaration of the new " 345 "function"; 346 347 if (EditedFiles.begin() != EditedFiles.end()) { 348 EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck); 349 } 350 } 351 352 TEST_F(ExtractFunctionTest, ConstDifferentHeaderSourceTest) { 353 Header = R"cpp( 354 class SomeClass { 355 void f() const; 356 }; 357 )cpp"; 358 359 std::string OutOfLineSource = R"cpp( 360 void SomeClass::f() const { 361 [[int x;]] 362 } 363 )cpp"; 364 365 std::string OutOfLineSourceOutputCheck = R"cpp( 366 void SomeClass::extracted() const { 367 int x; 368 } 369 void SomeClass::f() const { 370 extracted(); 371 } 372 )cpp"; 373 374 std::string HeaderOutputCheck = R"cpp( 375 class SomeClass { 376 void extracted() const; 377 void f() const; 378 }; 379 )cpp"; 380 381 llvm::StringMap<std::string> EditedFiles; 382 383 EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck); 384 EXPECT_NE(EditedFiles.begin(), EditedFiles.end()) 385 << "The header should be edited and receives the declaration of the new " 386 "function"; 387 388 if (EditedFiles.begin() != EditedFiles.end()) { 389 EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck); 390 } 391 } 392 393 TEST_F(ExtractFunctionTest, StaticDifferentHeaderSourceTest) { 394 Header = R"cpp( 395 class SomeClass { 396 static void f(); 397 }; 398 )cpp"; 399 400 std::string OutOfLineSource = R"cpp( 401 void SomeClass::f() { 402 [[int x;]] 403 } 404 )cpp"; 405 406 std::string OutOfLineSourceOutputCheck = R"cpp( 407 void SomeClass::extracted() { 408 int x; 409 } 410 void SomeClass::f() { 411 extracted(); 412 } 413 )cpp"; 414 415 std::string HeaderOutputCheck = R"cpp( 416 class SomeClass { 417 static void extracted(); 418 static void f(); 419 }; 420 )cpp"; 421 422 llvm::StringMap<std::string> EditedFiles; 423 424 EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck); 425 EXPECT_NE(EditedFiles.begin(), EditedFiles.end()) 426 << "The header should be edited and receives the declaration of the new " 427 "function"; 428 429 if (EditedFiles.begin() != EditedFiles.end()) { 430 EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck); 431 } 432 } 433 434 TEST_F(ExtractFunctionTest, DifferentContextHeaderSourceTest) { 435 Header = R"cpp( 436 namespace ns{ 437 class A { 438 class C { 439 public: 440 class RType {}; 441 }; 442 443 class T { 444 class SomeClass { 445 static C::RType f(); 446 }; 447 }; 448 }; 449 } // ns 450 )cpp"; 451 452 std::string OutOfLineSource = R"cpp( 453 ns::A::C::RType ns::A::T::SomeClass::f() { 454 [[A::C::RType x; 455 return x;]] 456 } 457 )cpp"; 458 459 std::string OutOfLineSourceOutputCheck = R"cpp( 460 ns::A::C::RType ns::A::T::SomeClass::extracted() { 461 A::C::RType x; 462 return x; 463 } 464 ns::A::C::RType ns::A::T::SomeClass::f() { 465 return extracted(); 466 } 467 )cpp"; 468 469 std::string HeaderOutputCheck = R"cpp( 470 namespace ns{ 471 class A { 472 class C { 473 public: 474 class RType {}; 475 }; 476 477 class T { 478 class SomeClass { 479 static ns::A::C::RType extracted(); 480 static C::RType f(); 481 }; 482 }; 483 }; 484 } // ns 485 )cpp"; 486 487 llvm::StringMap<std::string> EditedFiles; 488 489 EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck); 490 EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck); 491 } 492 493 TEST_F(ExtractFunctionTest, DifferentSyntacticContextNamespace) { 494 std::string OutOfLineSource = R"cpp( 495 namespace ns { 496 void f(); 497 } 498 499 void ns::f() { 500 [[int x;]] 501 } 502 )cpp"; 503 504 std::string OutOfLineSourceOutputCheck = R"cpp( 505 namespace ns { 506 void extracted(); 507 void f(); 508 } 509 510 void ns::extracted() { 511 int x; 512 } 513 void ns::f() { 514 extracted(); 515 } 516 )cpp"; 517 518 EXPECT_EQ(apply(OutOfLineSource), OutOfLineSourceOutputCheck); 519 } 520 521 TEST_F(ExtractFunctionTest, ControlFlow) { 522 Context = Function; 523 // We should be able to extract break/continue with a parent loop/switch. 524 EXPECT_THAT(apply(" [[for(;;) if(1) break;]] "), HasSubstr("extracted")); 525 EXPECT_THAT(apply(" for(;;) [[while(1) break;]] "), HasSubstr("extracted")); 526 EXPECT_THAT(apply(" [[switch(1) { break; }]]"), HasSubstr("extracted")); 527 EXPECT_THAT(apply(" [[while(1) switch(1) { continue; }]]"), 528 HasSubstr("extracted")); 529 // Don't extract break and continue without a loop/switch parent. 530 EXPECT_THAT(apply(" for(;;) [[if(1) continue;]] "), StartsWith("fail")); 531 EXPECT_THAT(apply(" while(1) [[if(1) break;]] "), StartsWith("fail")); 532 EXPECT_THAT(apply(" switch(1) { [[break;]] }"), StartsWith("fail")); 533 EXPECT_THAT(apply(" for(;;) { [[while(1) break; break;]] }"), 534 StartsWith("fail")); 535 } 536 537 TEST_F(ExtractFunctionTest, ExistingReturnStatement) { 538 Context = File; 539 const char *Before = R"cpp( 540 bool lucky(int N); 541 int getNum(bool Superstitious, int Min, int Max) { 542 if (Superstitious) [[{ 543 for (int I = Min; I <= Max; ++I) 544 if (lucky(I)) 545 return I; 546 return -1; 547 }]] else { 548 return (Min + Max) / 2; 549 } 550 } 551 )cpp"; 552 // FIXME: min/max should be by value. 553 // FIXME: avoid emitting redundant braces 554 const char *After = R"cpp( 555 bool lucky(int N); 556 int extracted(int &Min, int &Max) { 557 { 558 for (int I = Min; I <= Max; ++I) 559 if (lucky(I)) 560 return I; 561 return -1; 562 } 563 } 564 int getNum(bool Superstitious, int Min, int Max) { 565 if (Superstitious) return extracted(Min, Max); else { 566 return (Min + Max) / 2; 567 } 568 } 569 )cpp"; 570 EXPECT_EQ(apply(Before), After); 571 } 572 573 } // namespace 574 } // namespace clangd 575 } // namespace clang 576