577 lines
14 KiB
C++
577 lines
14 KiB
C++
//===-- 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 "TweakTesting.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"));
|
|
|
|
std::string MethodInput = R"cpp(
|
|
class T {
|
|
void f() {
|
|
[[int x;]]
|
|
}
|
|
};
|
|
)cpp";
|
|
std::string MethodCheckOutput = R"cpp(
|
|
class T {
|
|
void extracted() {
|
|
int x;
|
|
}
|
|
void f() {
|
|
extracted();
|
|
}
|
|
};
|
|
)cpp";
|
|
EXPECT_EQ(apply(MethodInput), MethodCheckOutput);
|
|
|
|
std::string OutOfLineMethodInput = R"cpp(
|
|
class T {
|
|
void f();
|
|
};
|
|
|
|
void T::f() {
|
|
[[int x;]]
|
|
}
|
|
)cpp";
|
|
std::string OutOfLineMethodCheckOutput = R"cpp(
|
|
class T {
|
|
void extracted();
|
|
void f();
|
|
};
|
|
|
|
void T::extracted() {
|
|
int x;
|
|
}
|
|
void T::f() {
|
|
extracted();
|
|
}
|
|
)cpp";
|
|
EXPECT_EQ(apply(OutOfLineMethodInput), OutOfLineMethodCheckOutput);
|
|
|
|
// 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");
|
|
EXPECT_EQ(apply("void f(int a = [[1]]);"), "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, DifferentHeaderSourceTest) {
|
|
Header = R"cpp(
|
|
class SomeClass {
|
|
void f();
|
|
};
|
|
)cpp";
|
|
|
|
std::string OutOfLineSource = R"cpp(
|
|
void SomeClass::f() {
|
|
[[int x;]]
|
|
}
|
|
)cpp";
|
|
|
|
std::string OutOfLineSourceOutputCheck = R"cpp(
|
|
void SomeClass::extracted() {
|
|
int x;
|
|
}
|
|
void SomeClass::f() {
|
|
extracted();
|
|
}
|
|
)cpp";
|
|
|
|
std::string HeaderOutputCheck = R"cpp(
|
|
class SomeClass {
|
|
void extracted();
|
|
void f();
|
|
};
|
|
)cpp";
|
|
|
|
llvm::StringMap<std::string> EditedFiles;
|
|
|
|
EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
|
|
EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
|
|
}
|
|
|
|
TEST_F(ExtractFunctionTest, DifferentFilesNestedTest) {
|
|
Header = R"cpp(
|
|
class T {
|
|
class SomeClass {
|
|
void f();
|
|
};
|
|
};
|
|
)cpp";
|
|
|
|
std::string NestedOutOfLineSource = R"cpp(
|
|
void T::SomeClass::f() {
|
|
[[int x;]]
|
|
}
|
|
)cpp";
|
|
|
|
std::string NestedOutOfLineSourceOutputCheck = R"cpp(
|
|
void T::SomeClass::extracted() {
|
|
int x;
|
|
}
|
|
void T::SomeClass::f() {
|
|
extracted();
|
|
}
|
|
)cpp";
|
|
|
|
std::string NestedHeaderOutputCheck = R"cpp(
|
|
class T {
|
|
class SomeClass {
|
|
void extracted();
|
|
void f();
|
|
};
|
|
};
|
|
)cpp";
|
|
|
|
llvm::StringMap<std::string> EditedFiles;
|
|
|
|
EXPECT_EQ(apply(NestedOutOfLineSource, &EditedFiles),
|
|
NestedOutOfLineSourceOutputCheck);
|
|
EXPECT_EQ(EditedFiles.begin()->second, NestedHeaderOutputCheck);
|
|
}
|
|
|
|
TEST_F(ExtractFunctionTest, ConstexprDifferentHeaderSourceTest) {
|
|
Header = R"cpp(
|
|
class SomeClass {
|
|
constexpr void f() const;
|
|
};
|
|
)cpp";
|
|
|
|
std::string OutOfLineSource = R"cpp(
|
|
constexpr void SomeClass::f() const {
|
|
[[int x;]]
|
|
}
|
|
)cpp";
|
|
|
|
std::string OutOfLineSourceOutputCheck = R"cpp(
|
|
constexpr void SomeClass::extracted() const {
|
|
int x;
|
|
}
|
|
constexpr void SomeClass::f() const {
|
|
extracted();
|
|
}
|
|
)cpp";
|
|
|
|
std::string HeaderOutputCheck = R"cpp(
|
|
class SomeClass {
|
|
constexpr void extracted() const;
|
|
constexpr void f() const;
|
|
};
|
|
)cpp";
|
|
|
|
llvm::StringMap<std::string> EditedFiles;
|
|
|
|
EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
|
|
EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
|
|
<< "The header should be edited and receives the declaration of the new "
|
|
"function";
|
|
|
|
if (EditedFiles.begin() != EditedFiles.end()) {
|
|
EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
|
|
}
|
|
}
|
|
|
|
TEST_F(ExtractFunctionTest, ConstevalDifferentHeaderSourceTest) {
|
|
ExtraArgs.push_back("--std=c++20");
|
|
Header = R"cpp(
|
|
class SomeClass {
|
|
consteval void f() const;
|
|
};
|
|
)cpp";
|
|
|
|
std::string OutOfLineSource = R"cpp(
|
|
consteval void SomeClass::f() const {
|
|
[[int x;]]
|
|
}
|
|
)cpp";
|
|
|
|
std::string OutOfLineSourceOutputCheck = R"cpp(
|
|
consteval void SomeClass::extracted() const {
|
|
int x;
|
|
}
|
|
consteval void SomeClass::f() const {
|
|
extracted();
|
|
}
|
|
)cpp";
|
|
|
|
std::string HeaderOutputCheck = R"cpp(
|
|
class SomeClass {
|
|
consteval void extracted() const;
|
|
consteval void f() const;
|
|
};
|
|
)cpp";
|
|
|
|
llvm::StringMap<std::string> EditedFiles;
|
|
|
|
EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
|
|
EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
|
|
<< "The header should be edited and receives the declaration of the new "
|
|
"function";
|
|
|
|
if (EditedFiles.begin() != EditedFiles.end()) {
|
|
EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
|
|
}
|
|
}
|
|
|
|
TEST_F(ExtractFunctionTest, ConstDifferentHeaderSourceTest) {
|
|
Header = R"cpp(
|
|
class SomeClass {
|
|
void f() const;
|
|
};
|
|
)cpp";
|
|
|
|
std::string OutOfLineSource = R"cpp(
|
|
void SomeClass::f() const {
|
|
[[int x;]]
|
|
}
|
|
)cpp";
|
|
|
|
std::string OutOfLineSourceOutputCheck = R"cpp(
|
|
void SomeClass::extracted() const {
|
|
int x;
|
|
}
|
|
void SomeClass::f() const {
|
|
extracted();
|
|
}
|
|
)cpp";
|
|
|
|
std::string HeaderOutputCheck = R"cpp(
|
|
class SomeClass {
|
|
void extracted() const;
|
|
void f() const;
|
|
};
|
|
)cpp";
|
|
|
|
llvm::StringMap<std::string> EditedFiles;
|
|
|
|
EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
|
|
EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
|
|
<< "The header should be edited and receives the declaration of the new "
|
|
"function";
|
|
|
|
if (EditedFiles.begin() != EditedFiles.end()) {
|
|
EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
|
|
}
|
|
}
|
|
|
|
TEST_F(ExtractFunctionTest, StaticDifferentHeaderSourceTest) {
|
|
Header = R"cpp(
|
|
class SomeClass {
|
|
static void f();
|
|
};
|
|
)cpp";
|
|
|
|
std::string OutOfLineSource = R"cpp(
|
|
void SomeClass::f() {
|
|
[[int x;]]
|
|
}
|
|
)cpp";
|
|
|
|
std::string OutOfLineSourceOutputCheck = R"cpp(
|
|
void SomeClass::extracted() {
|
|
int x;
|
|
}
|
|
void SomeClass::f() {
|
|
extracted();
|
|
}
|
|
)cpp";
|
|
|
|
std::string HeaderOutputCheck = R"cpp(
|
|
class SomeClass {
|
|
static void extracted();
|
|
static void f();
|
|
};
|
|
)cpp";
|
|
|
|
llvm::StringMap<std::string> EditedFiles;
|
|
|
|
EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
|
|
EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
|
|
<< "The header should be edited and receives the declaration of the new "
|
|
"function";
|
|
|
|
if (EditedFiles.begin() != EditedFiles.end()) {
|
|
EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
|
|
}
|
|
}
|
|
|
|
TEST_F(ExtractFunctionTest, DifferentContextHeaderSourceTest) {
|
|
Header = R"cpp(
|
|
namespace ns{
|
|
class A {
|
|
class C {
|
|
public:
|
|
class RType {};
|
|
};
|
|
|
|
class T {
|
|
class SomeClass {
|
|
static C::RType f();
|
|
};
|
|
};
|
|
};
|
|
} // ns
|
|
)cpp";
|
|
|
|
std::string OutOfLineSource = R"cpp(
|
|
ns::A::C::RType ns::A::T::SomeClass::f() {
|
|
[[A::C::RType x;
|
|
return x;]]
|
|
}
|
|
)cpp";
|
|
|
|
std::string OutOfLineSourceOutputCheck = R"cpp(
|
|
ns::A::C::RType ns::A::T::SomeClass::extracted() {
|
|
A::C::RType x;
|
|
return x;
|
|
}
|
|
ns::A::C::RType ns::A::T::SomeClass::f() {
|
|
return extracted();
|
|
}
|
|
)cpp";
|
|
|
|
std::string HeaderOutputCheck = R"cpp(
|
|
namespace ns{
|
|
class A {
|
|
class C {
|
|
public:
|
|
class RType {};
|
|
};
|
|
|
|
class T {
|
|
class SomeClass {
|
|
static ns::A::C::RType extracted();
|
|
static C::RType f();
|
|
};
|
|
};
|
|
};
|
|
} // ns
|
|
)cpp";
|
|
|
|
llvm::StringMap<std::string> EditedFiles;
|
|
|
|
EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
|
|
EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
|
|
}
|
|
|
|
TEST_F(ExtractFunctionTest, DifferentSyntacticContextNamespace) {
|
|
std::string OutOfLineSource = R"cpp(
|
|
namespace ns {
|
|
void f();
|
|
}
|
|
|
|
void ns::f() {
|
|
[[int x;]]
|
|
}
|
|
)cpp";
|
|
|
|
std::string OutOfLineSourceOutputCheck = R"cpp(
|
|
namespace ns {
|
|
void extracted();
|
|
void f();
|
|
}
|
|
|
|
void ns::extracted() {
|
|
int x;
|
|
}
|
|
void ns::f() {
|
|
extracted();
|
|
}
|
|
)cpp";
|
|
|
|
EXPECT_EQ(apply(OutOfLineSource), OutOfLineSourceOutputCheck);
|
|
}
|
|
|
|
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
|