blob: 94cc4b0a0d840405bacf1194a14071376a069c97 [file] [log] [blame]
//===-- 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);
// Check const qualifier with namespace
std::string ConstNamespaceCheckInput = R"cpp(
namespace X { struct Y { int z; }; }
int f(const X::Y &y) {
[[return y.z + y.z;]]
})cpp";
std::string ConstNamespaceCheckOutput = R"cpp(
namespace X { struct Y { int z; }; }
int extracted(const X::Y &y) {
return y.z + y.z;
}
int f(const X::Y &y) {
return extracted(y);
})cpp";
EXPECT_EQ(apply(ConstNamespaceCheckInput), ConstNamespaceCheckOutput);
// 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<typename T>
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