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 EXPECT_EQ(apply("void f(int a = [[1]]);"), "unavailable"); 186 // Don't extract if we select the entire function body (CompoundStmt). 187 std::string CompoundFailInput = R"cpp( 188 void f() [[{ 189 int a; 190 }]] 191 )cpp"; 192 EXPECT_EQ(apply(CompoundFailInput), "unavailable"); 193 194 ExtraArgs.push_back("-std=c++14"); 195 // FIXME: Expressions are currently not extracted 196 EXPECT_EQ(apply(R"cpp( 197 void call() { [[1+1]]; } 198 )cpp"), 199 "unavailable"); 200 // FIXME: Single expression statements are currently not extracted 201 EXPECT_EQ(apply(R"cpp( 202 void call() { [[1+1;]] } 203 )cpp"), 204 "unavailable"); 205 } 206 207 TEST_F(ExtractFunctionTest, DifferentHeaderSourceTest) { 208 Header = R"cpp( 209 class SomeClass { 210 void f(); 211 }; 212 )cpp"; 213 214 std::string OutOfLineSource = R"cpp( 215 void SomeClass::f() { 216 [[int x;]] 217 } 218 )cpp"; 219 220 std::string OutOfLineSourceOutputCheck = R"cpp( 221 void SomeClass::extracted() { 222 int x; 223 } 224 void SomeClass::f() { 225 extracted(); 226 } 227 )cpp"; 228 229 std::string HeaderOutputCheck = R"cpp( 230 class SomeClass { 231 void extracted(); 232 void f(); 233 }; 234 )cpp"; 235 236 llvm::StringMap<std::string> EditedFiles; 237 238 EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck); 239 EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck); 240 } 241 242 TEST_F(ExtractFunctionTest, DifferentFilesNestedTest) { 243 Header = R"cpp( 244 class T { 245 class SomeClass { 246 void f(); 247 }; 248 }; 249 )cpp"; 250 251 std::string NestedOutOfLineSource = R"cpp( 252 void T::SomeClass::f() { 253 [[int x;]] 254 } 255 )cpp"; 256 257 std::string NestedOutOfLineSourceOutputCheck = R"cpp( 258 void T::SomeClass::extracted() { 259 int x; 260 } 261 void T::SomeClass::f() { 262 extracted(); 263 } 264 )cpp"; 265 266 std::string NestedHeaderOutputCheck = R"cpp( 267 class T { 268 class SomeClass { 269 void extracted(); 270 void f(); 271 }; 272 }; 273 )cpp"; 274 275 llvm::StringMap<std::string> EditedFiles; 276 277 EXPECT_EQ(apply(NestedOutOfLineSource, &EditedFiles), 278 NestedOutOfLineSourceOutputCheck); 279 EXPECT_EQ(EditedFiles.begin()->second, NestedHeaderOutputCheck); 280 } 281 282 TEST_F(ExtractFunctionTest, ConstexprDifferentHeaderSourceTest) { 283 Header = R"cpp( 284 class SomeClass { 285 constexpr void f() const; 286 }; 287 )cpp"; 288 289 std::string OutOfLineSource = R"cpp( 290 constexpr void SomeClass::f() const { 291 [[int x;]] 292 } 293 )cpp"; 294 295 std::string OutOfLineSourceOutputCheck = R"cpp( 296 constexpr void SomeClass::extracted() const { 297 int x; 298 } 299 constexpr void SomeClass::f() const { 300 extracted(); 301 } 302 )cpp"; 303 304 std::string HeaderOutputCheck = R"cpp( 305 class SomeClass { 306 constexpr void extracted() const; 307 constexpr void f() const; 308 }; 309 )cpp"; 310 311 llvm::StringMap<std::string> EditedFiles; 312 313 EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck); 314 EXPECT_NE(EditedFiles.begin(), EditedFiles.end()) 315 << "The header should be edited and receives the declaration of the new " 316 "function"; 317 318 if (EditedFiles.begin() != EditedFiles.end()) { 319 EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck); 320 } 321 } 322 323 TEST_F(ExtractFunctionTest, ConstevalDifferentHeaderSourceTest) { 324 ExtraArgs.push_back("--std=c++20"); 325 Header = R"cpp( 326 class SomeClass { 327 consteval void f() const; 328 }; 329 )cpp"; 330 331 std::string OutOfLineSource = R"cpp( 332 consteval void SomeClass::f() const { 333 [[int x;]] 334 } 335 )cpp"; 336 337 std::string OutOfLineSourceOutputCheck = R"cpp( 338 consteval void SomeClass::extracted() const { 339 int x; 340 } 341 consteval void SomeClass::f() const { 342 extracted(); 343 } 344 )cpp"; 345 346 std::string HeaderOutputCheck = R"cpp( 347 class SomeClass { 348 consteval void extracted() const; 349 consteval void f() const; 350 }; 351 )cpp"; 352 353 llvm::StringMap<std::string> EditedFiles; 354 355 EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck); 356 EXPECT_NE(EditedFiles.begin(), EditedFiles.end()) 357 << "The header should be edited and receives the declaration of the new " 358 "function"; 359 360 if (EditedFiles.begin() != EditedFiles.end()) { 361 EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck); 362 } 363 } 364 365 TEST_F(ExtractFunctionTest, ConstDifferentHeaderSourceTest) { 366 Header = R"cpp( 367 class SomeClass { 368 void f() const; 369 }; 370 )cpp"; 371 372 std::string OutOfLineSource = R"cpp( 373 void SomeClass::f() const { 374 [[int x;]] 375 } 376 )cpp"; 377 378 std::string OutOfLineSourceOutputCheck = R"cpp( 379 void SomeClass::extracted() const { 380 int x; 381 } 382 void SomeClass::f() const { 383 extracted(); 384 } 385 )cpp"; 386 387 std::string HeaderOutputCheck = R"cpp( 388 class SomeClass { 389 void extracted() const; 390 void f() const; 391 }; 392 )cpp"; 393 394 llvm::StringMap<std::string> EditedFiles; 395 396 EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck); 397 EXPECT_NE(EditedFiles.begin(), EditedFiles.end()) 398 << "The header should be edited and receives the declaration of the new " 399 "function"; 400 401 if (EditedFiles.begin() != EditedFiles.end()) { 402 EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck); 403 } 404 } 405 406 TEST_F(ExtractFunctionTest, StaticDifferentHeaderSourceTest) { 407 Header = R"cpp( 408 class SomeClass { 409 static void f(); 410 }; 411 )cpp"; 412 413 std::string OutOfLineSource = R"cpp( 414 void SomeClass::f() { 415 [[int x;]] 416 } 417 )cpp"; 418 419 std::string OutOfLineSourceOutputCheck = R"cpp( 420 void SomeClass::extracted() { 421 int x; 422 } 423 void SomeClass::f() { 424 extracted(); 425 } 426 )cpp"; 427 428 std::string HeaderOutputCheck = R"cpp( 429 class SomeClass { 430 static void extracted(); 431 static void f(); 432 }; 433 )cpp"; 434 435 llvm::StringMap<std::string> EditedFiles; 436 437 EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck); 438 EXPECT_NE(EditedFiles.begin(), EditedFiles.end()) 439 << "The header should be edited and receives the declaration of the new " 440 "function"; 441 442 if (EditedFiles.begin() != EditedFiles.end()) { 443 EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck); 444 } 445 } 446 447 TEST_F(ExtractFunctionTest, DifferentContextHeaderSourceTest) { 448 Header = R"cpp( 449 namespace ns{ 450 class A { 451 class C { 452 public: 453 class RType {}; 454 }; 455 456 class T { 457 class SomeClass { 458 static C::RType f(); 459 }; 460 }; 461 }; 462 } // ns 463 )cpp"; 464 465 std::string OutOfLineSource = R"cpp( 466 ns::A::C::RType ns::A::T::SomeClass::f() { 467 [[A::C::RType x; 468 return x;]] 469 } 470 )cpp"; 471 472 std::string OutOfLineSourceOutputCheck = R"cpp( 473 ns::A::C::RType ns::A::T::SomeClass::extracted() { 474 A::C::RType x; 475 return x; 476 } 477 ns::A::C::RType ns::A::T::SomeClass::f() { 478 return extracted(); 479 } 480 )cpp"; 481 482 std::string HeaderOutputCheck = R"cpp( 483 namespace ns{ 484 class A { 485 class C { 486 public: 487 class RType {}; 488 }; 489 490 class T { 491 class SomeClass { 492 static ns::A::C::RType extracted(); 493 static C::RType f(); 494 }; 495 }; 496 }; 497 } // ns 498 )cpp"; 499 500 llvm::StringMap<std::string> EditedFiles; 501 502 EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck); 503 EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck); 504 } 505 506 TEST_F(ExtractFunctionTest, DifferentSyntacticContextNamespace) { 507 std::string OutOfLineSource = R"cpp( 508 namespace ns { 509 void f(); 510 } 511 512 void ns::f() { 513 [[int x;]] 514 } 515 )cpp"; 516 517 std::string OutOfLineSourceOutputCheck = R"cpp( 518 namespace ns { 519 void extracted(); 520 void f(); 521 } 522 523 void ns::extracted() { 524 int x; 525 } 526 void ns::f() { 527 extracted(); 528 } 529 )cpp"; 530 531 EXPECT_EQ(apply(OutOfLineSource), OutOfLineSourceOutputCheck); 532 } 533 534 TEST_F(ExtractFunctionTest, ControlFlow) { 535 Context = Function; 536 // We should be able to extract break/continue with a parent loop/switch. 537 EXPECT_THAT(apply(" [[for(;;) if(1) break;]] "), HasSubstr("extracted")); 538 EXPECT_THAT(apply(" for(;;) [[while(1) break;]] "), HasSubstr("extracted")); 539 EXPECT_THAT(apply(" [[switch(1) { break; }]]"), HasSubstr("extracted")); 540 EXPECT_THAT(apply(" [[while(1) switch(1) { continue; }]]"), 541 HasSubstr("extracted")); 542 // Don't extract break and continue without a loop/switch parent. 543 EXPECT_THAT(apply(" for(;;) [[if(1) continue;]] "), StartsWith("fail")); 544 EXPECT_THAT(apply(" while(1) [[if(1) break;]] "), StartsWith("fail")); 545 EXPECT_THAT(apply(" switch(1) { [[break;]] }"), StartsWith("fail")); 546 EXPECT_THAT(apply(" for(;;) { [[while(1) break; break;]] }"), 547 StartsWith("fail")); 548 } 549 550 TEST_F(ExtractFunctionTest, ExistingReturnStatement) { 551 Context = File; 552 const char *Before = R"cpp( 553 bool lucky(int N); 554 int getNum(bool Superstitious, int Min, int Max) { 555 if (Superstitious) [[{ 556 for (int I = Min; I <= Max; ++I) 557 if (lucky(I)) 558 return I; 559 return -1; 560 }]] else { 561 return (Min + Max) / 2; 562 } 563 } 564 )cpp"; 565 // FIXME: min/max should be by value. 566 // FIXME: avoid emitting redundant braces 567 const char *After = R"cpp( 568 bool lucky(int N); 569 int extracted(int &Min, int &Max) { 570 { 571 for (int I = Min; I <= Max; ++I) 572 if (lucky(I)) 573 return I; 574 return -1; 575 } 576 } 577 int getNum(bool Superstitious, int Min, int Max) { 578 if (Superstitious) return extracted(Min, Max); else { 579 return (Min + Max) / 2; 580 } 581 } 582 )cpp"; 583 EXPECT_EQ(apply(Before), After); 584 } 585 586 TEST_F(ExtractFunctionTest, OverloadedOperators) { 587 Context = File; 588 std::string Before = R"cpp(struct A { 589 int operator+(int x) { return x; } 590 }; 591 A &operator<<(A &, int); 592 A &operator|(A &, int); 593 594 A stream{}; 595 596 void foo(int, int); 597 598 int main() { 599 [[foo(1, 2); 600 foo(3, 4); 601 stream << 42; 602 stream + 42; 603 stream | 42; 604 foo(1, 2); 605 foo(3, 4);]] 606 })cpp"; 607 std::string After = 608 R"cpp(struct A { 609 int operator+(int x) { return x; } 610 }; 611 A &operator<<(A &, int); 612 A &operator|(A &, int); 613 614 A stream{}; 615 616 void foo(int, int); 617 618 void extracted() { 619 foo(1, 2); 620 foo(3, 4); 621 stream << 42; 622 stream + 42; 623 stream | 42; 624 foo(1, 2); 625 foo(3, 4); 626 } 627 int main() { 628 extracted(); 629 })cpp"; 630 EXPECT_EQ(apply(Before), After); 631 } 632 633 } // namespace 634 } // namespace clangd 635 } // namespace clang 636