//===-- ExtractFunctionTests.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 "TestTU.h" #include "TweakTesting.h" #include "gmock/gmock-matchers.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using ::testing::HasSubstr; using ::testing::StartsWith; namespace clang { namespace clangd { namespace { TWEAK_TEST(ExtractFunction); TEST_F(ExtractFunctionTest, FunctionTest) { Context = Function; // Root statements should have common parent. EXPECT_EQ(apply("for(;;) [[1+2; 1+2;]]"), "unavailable"); // Expressions aren't extracted. EXPECT_EQ(apply("int x = 0; [[x++;]]"), "unavailable"); // We don't support extraction from lambdas. EXPECT_EQ(apply("auto lam = [](){ [[int x;]] }; "), "unavailable"); // Partial statements aren't extracted. EXPECT_THAT(apply("int [[x = 0]];"), "unavailable"); // FIXME: Support hoisting. EXPECT_THAT(apply(" [[int a = 5;]] a++; "), "unavailable"); // Ensure that end of Zone and Beginning of PostZone being adjacent doesn't // lead to break being included in the extraction zone. EXPECT_THAT(apply("for(;;) { [[int x;]]break; }"), HasSubstr("extracted")); // FIXME: ExtractFunction should be unavailable inside loop construct // initializer/condition. EXPECT_THAT(apply(" for([[int i = 0;]];);"), HasSubstr("extracted")); // Extract certain return EXPECT_THAT(apply(" if(true) [[{ return; }]] "), HasSubstr("extracted")); // Don't extract uncertain return EXPECT_THAT(apply(" if(true) [[if (false) return;]] "), StartsWith("unavailable")); EXPECT_THAT( apply("#define RETURN_IF_ERROR(x) if (x) return\nRETU^RN_IF_ERROR(4);"), StartsWith("unavailable")); FileName = "a.c"; EXPECT_THAT(apply(" for([[int i = 0;]];);"), HasSubstr("unavailable")); } TEST_F(ExtractFunctionTest, FileTest) { // Check all parameters are in order std::string ParameterCheckInput = R"cpp( struct Foo { int x; }; void f(int a) { int b; int *ptr = &a; Foo foo; [[a += foo.x + b; *ptr++;]] })cpp"; std::string ParameterCheckOutput = R"cpp( struct Foo { int x; }; void extracted(int &a, int &b, int * &ptr, Foo &foo) { a += foo.x + b; *ptr++; } void f(int a) { int b; int *ptr = &a; Foo foo; extracted(a, b, ptr, foo); })cpp"; EXPECT_EQ(apply(ParameterCheckInput), ParameterCheckOutput); // Check const qualifier std::string ConstCheckInput = R"cpp( void f(const int c) { [[while(c) {}]] })cpp"; std::string ConstCheckOutput = R"cpp( void extracted(const int &c) { while(c) {} } void f(const int c) { extracted(c); })cpp"; EXPECT_EQ(apply(ConstCheckInput), ConstCheckOutput); // Don't extract when we need to make a function as a parameter. EXPECT_THAT(apply("void f() { [[int a; f();]] }"), StartsWith("fail")); // We don't extract from methods for now since they may involve multi-file // edits std::string MethodFailInput = R"cpp( class T { void f() { [[int x;]] } }; )cpp"; EXPECT_EQ(apply(MethodFailInput), "unavailable"); // We don't extract from templated functions for now as templates are hard // to deal with. std::string TemplateFailInput = R"cpp( template void f() { [[int x;]] } )cpp"; EXPECT_EQ(apply(TemplateFailInput), "unavailable"); std::string MacroInput = R"cpp( #define F(BODY) void f() { BODY } F ([[int x = 0;]]) )cpp"; std::string MacroOutput = R"cpp( #define F(BODY) void f() { BODY } void extracted() { int x = 0; } F (extracted();) )cpp"; EXPECT_EQ(apply(MacroInput), MacroOutput); // Shouldn't crash. EXPECT_EQ(apply("void f([[int a]]);"), "unavailable"); // Don't extract if we select the entire function body (CompoundStmt). std::string CompoundFailInput = R"cpp( void f() [[{ int a; }]] )cpp"; EXPECT_EQ(apply(CompoundFailInput), "unavailable"); } TEST_F(ExtractFunctionTest, ControlFlow) { Context = Function; // We should be able to extract break/continue with a parent loop/switch. EXPECT_THAT(apply(" [[for(;;) if(1) break;]] "), HasSubstr("extracted")); EXPECT_THAT(apply(" for(;;) [[while(1) break;]] "), HasSubstr("extracted")); EXPECT_THAT(apply(" [[switch(1) { break; }]]"), HasSubstr("extracted")); EXPECT_THAT(apply(" [[while(1) switch(1) { continue; }]]"), HasSubstr("extracted")); // Don't extract break and continue without a loop/switch parent. EXPECT_THAT(apply(" for(;;) [[if(1) continue;]] "), StartsWith("fail")); EXPECT_THAT(apply(" while(1) [[if(1) break;]] "), StartsWith("fail")); EXPECT_THAT(apply(" switch(1) { [[break;]] }"), StartsWith("fail")); EXPECT_THAT(apply(" for(;;) { [[while(1) break; break;]] }"), StartsWith("fail")); } TEST_F(ExtractFunctionTest, ExistingReturnStatement) { Context = File; const char *Before = R"cpp( bool lucky(int N); int getNum(bool Superstitious, int Min, int Max) { if (Superstitious) [[{ for (int I = Min; I <= Max; ++I) if (lucky(I)) return I; return -1; }]] else { return (Min + Max) / 2; } } )cpp"; // FIXME: min/max should be by value. // FIXME: avoid emitting redundant braces const char *After = R"cpp( bool lucky(int N); int extracted(int &Min, int &Max) { { for (int I = Min; I <= Max; ++I) if (lucky(I)) return I; return -1; } } int getNum(bool Superstitious, int Min, int Max) { if (Superstitious) return extracted(Min, Max); else { return (Min + Max) / 2; } } )cpp"; EXPECT_EQ(apply(Before), After); } } // namespace } // namespace clangd } // namespace clang