636 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8 9#include "TweakTesting.h"10#include "gmock/gmock.h"11#include "gtest/gtest.h"12 13using ::testing::HasSubstr;14using ::testing::StartsWith;15 16namespace clang {17namespace clangd {18namespace {19 20TWEAK_TEST(ExtractFunction);21 22TEST_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't37 // 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 construct40 // initializer/condition.41 EXPECT_THAT(apply(" for([[int i = 0;]];);"), HasSubstr("extracted"));42 // Extract certain return43 EXPECT_THAT(apply(" if(true) [[{ return; }]] "), HasSubstr("extracted"));44 // Don't extract uncertain return45 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 55TEST_F(ExtractFunctionTest, FileTest) {56 // Check all parameters are in order57 std::string ParameterCheckInput = R"cpp(58struct Foo {59 int x;60};61void 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(69struct Foo {70 int x;71};72void extracted(int &a, int &b, int * &ptr, Foo &foo) {73a += foo.x + b;74 *ptr++;75}76void 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 qualifier85 std::string ConstCheckInput = R"cpp(86void f(const int c) {87 [[while(c) {}]]88})cpp";89 std::string ConstCheckOutput = R"cpp(90void extracted(const int &c) {91while(c) {}92}93void f(const int c) {94 extracted(c);95})cpp";96 EXPECT_EQ(apply(ConstCheckInput), ConstCheckOutput);97 98 // Check const qualifier with namespace99 std::string ConstNamespaceCheckInput = R"cpp(100namespace X { struct Y { int z; }; }101int f(const X::Y &y) {102 [[return y.z + y.z;]]103})cpp";104 std::string ConstNamespaceCheckOutput = R"cpp(105namespace X { struct Y { int z; }; }106int extracted(const X::Y &y) {107return y.z + y.z;108}109int 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() {127int x;128}129void 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();148void f();149 };150 151 void T::extracted() {152int x;153}154void 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 hard161 // 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() {177int x = 0;178}179F (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 extracted196 EXPECT_EQ(apply(R"cpp(197 void call() { [[1+1]]; }198 )cpp"),199 "unavailable");200 // FIXME: Single expression statements are currently not extracted201 EXPECT_EQ(apply(R"cpp(202 void call() { [[1+1;]] }203 )cpp"),204 "unavailable");205}206 207TEST_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() {222int x;223}224void SomeClass::f() {225 extracted();226 }227 )cpp";228 229 std::string HeaderOutputCheck = R"cpp(230 class SomeClass {231 void extracted();232void 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 242TEST_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() {259int x;260}261void T::SomeClass::f() {262 extracted();263 }264 )cpp";265 266 std::string NestedHeaderOutputCheck = R"cpp(267 class T {268 class SomeClass {269 void extracted();270void 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 282TEST_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 {297int x;298}299constexpr void SomeClass::f() const {300 extracted();301 }302 )cpp";303 304 std::string HeaderOutputCheck = R"cpp(305 class SomeClass {306 constexpr void extracted() const;307constexpr 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 323TEST_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 {339int x;340}341consteval void SomeClass::f() const {342 extracted();343 }344 )cpp";345 346 std::string HeaderOutputCheck = R"cpp(347 class SomeClass {348 consteval void extracted() const;349consteval 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 365TEST_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 {380int x;381}382void SomeClass::f() const {383 extracted();384 }385 )cpp";386 387 std::string HeaderOutputCheck = R"cpp(388 class SomeClass {389 void extracted() const;390void 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 406TEST_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() {421int x;422}423void SomeClass::f() {424 extracted();425 }426 )cpp";427 428 std::string HeaderOutputCheck = R"cpp(429 class SomeClass {430 static void extracted();431static 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 447TEST_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 } // ns463 )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() {474A::C::RType x;475 return x;476}477ns::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();493static C::RType f();494 };495 };496 };497 } // ns498 )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 506TEST_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();520void f();521 }522 523 void ns::extracted() {524int x;525}526void ns::f() {527 extracted();528 }529 )cpp";530 531 EXPECT_EQ(apply(OutOfLineSource), OutOfLineSourceOutputCheck);532}533 534TEST_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 550TEST_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 braces567 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}577int 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 586TEST_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() {619foo(1, 2);620 foo(3, 4);621 stream << 42;622 stream + 42;623 stream | 42;624 foo(1, 2);625 foo(3, 4);626}627int main() {628 extracted();629 })cpp";630 EXPECT_EQ(apply(Before), After);631}632 633} // namespace634} // namespace clangd635} // namespace clang636