blob: a872341a871aaba898891a9fe1e4448422ce074c [file] [log] [blame]
//===-- DefineOutline.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::ElementsAre;
namespace clang {
namespace clangd {
namespace {
TWEAK_TEST(DefineOutline);
TEST_F(DefineOutlineTest, TriggersOnFunctionDecl) {
FileName = "Test.cpp";
// Not available unless in a header file.
EXPECT_UNAVAILABLE(R"cpp(
[[void [[f^o^o]]() [[{
return;
}]]]])cpp");
FileName = "Test.hpp";
// Not available unless function name or fully body is selected.
EXPECT_UNAVAILABLE(R"cpp(
// Not a definition
vo^i[[d^ ^f]]^oo();
[[vo^id ]]foo[[()]] {[[
[[(void)(5+3);
return;]]
}]])cpp");
// Available even if there are no implementation files.
EXPECT_AVAILABLE(R"cpp(
[[void [[f^o^o]]() [[{
return;
}]]]])cpp");
// Not available for out-of-line methods.
EXPECT_UNAVAILABLE(R"cpp(
class Bar {
void baz();
};
[[void [[Bar::[[b^a^z]]]]() [[{
return;
}]]]])cpp");
// Basic check for function body and signature.
EXPECT_AVAILABLE(R"cpp(
class Bar {
[[void [[f^o^o^]]() [[{ return; }]]]]
};
void foo();
[[void [[f^o^o]]() [[{
return;
}]]]])cpp");
// Not available on defaulted/deleted members.
EXPECT_UNAVAILABLE(R"cpp(
class Foo {
Fo^o() = default;
F^oo(const Foo&) = delete;
};)cpp");
// Not available within templated classes, as it is hard to spell class name
// out-of-line in such cases.
EXPECT_UNAVAILABLE(R"cpp(
template <typename> struct Foo { void fo^o(){} };
)cpp");
// Not available on function templates and specializations, as definition must
// be visible to all translation units.
EXPECT_UNAVAILABLE(R"cpp(
template <typename> void fo^o() {};
template <> void fo^o<int>() {};
)cpp");
}
TEST_F(DefineOutlineTest, FailsWithoutSource) {
FileName = "Test.hpp";
llvm::StringRef Test = "void fo^o() { return; }";
llvm::StringRef Expected =
"fail: Couldn't find a suitable implementation file.";
EXPECT_EQ(apply(Test), Expected);
}
TEST_F(DefineOutlineTest, ApplyTest) {
llvm::StringMap<std::string> EditedFiles;
ExtraFiles["Test.cpp"] = "";
FileName = "Test.hpp";
struct {
llvm::StringRef Test;
llvm::StringRef ExpectedHeader;
llvm::StringRef ExpectedSource;
} Cases[] = {
// Simple check
{
"void fo^o() { return; }",
"void foo() ;",
"void foo() { return; }",
},
// Default args.
{
"void fo^o(int x, int y = 5, int = 2, int (*foo)(int) = nullptr) {}",
"void foo(int x, int y = 5, int = 2, int (*foo)(int) = nullptr) ;",
"void foo(int x, int y , int , int (*foo)(int) ) {}",
},
// Constructors
{
R"cpp(
class Foo {public: Foo(); Foo(int);};
class Bar {
Ba^r() {}
Bar(int x) : f1(x) {}
Foo f1;
Foo f2 = 2;
};)cpp",
R"cpp(
class Foo {public: Foo(); Foo(int);};
class Bar {
Bar() ;
Bar(int x) : f1(x) {}
Foo f1;
Foo f2 = 2;
};)cpp",
"Bar::Bar() {}\n",
},
// Ctor with initializer.
{
R"cpp(
class Foo {public: Foo(); Foo(int);};
class Bar {
Bar() {}
B^ar(int x) : f1(x), f2(3) {}
Foo f1;
Foo f2 = 2;
};)cpp",
R"cpp(
class Foo {public: Foo(); Foo(int);};
class Bar {
Bar() {}
Bar(int x) ;
Foo f1;
Foo f2 = 2;
};)cpp",
"Bar::Bar(int x) : f1(x), f2(3) {}\n",
},
// Ctor initializer with attribute.
{
R"cpp(
class Foo {
F^oo(int z) __attribute__((weak)) : bar(2){}
int bar;
};)cpp",
R"cpp(
class Foo {
Foo(int z) __attribute__((weak)) ;
int bar;
};)cpp",
"Foo::Foo(int z) __attribute__((weak)) : bar(2){}\n",
},
// Virt specifiers.
{
R"cpp(
struct A {
virtual void f^oo() {}
};)cpp",
R"cpp(
struct A {
virtual void foo() ;
};)cpp",
" void A::foo() {}\n",
},
{
R"cpp(
struct A {
virtual virtual void virtual f^oo() {}
};)cpp",
R"cpp(
struct A {
virtual virtual void virtual foo() ;
};)cpp",
" void A::foo() {}\n",
},
{
R"cpp(
struct A {
virtual void foo() = 0;
};
struct B : A {
void fo^o() override {}
};)cpp",
R"cpp(
struct A {
virtual void foo() = 0;
};
struct B : A {
void foo() override ;
};)cpp",
"void B::foo() {}\n",
},
{
R"cpp(
struct A {
virtual void foo() = 0;
};
struct B : A {
void fo^o() final {}
};)cpp",
R"cpp(
struct A {
virtual void foo() = 0;
};
struct B : A {
void foo() final ;
};)cpp",
"void B::foo() {}\n",
},
{
R"cpp(
struct A {
virtual void foo() = 0;
};
struct B : A {
void fo^o() final override {}
};)cpp",
R"cpp(
struct A {
virtual void foo() = 0;
};
struct B : A {
void foo() final override ;
};)cpp",
"void B::foo() {}\n",
},
{
R"cpp(
struct A {
static void fo^o() {}
};)cpp",
R"cpp(
struct A {
static void foo() ;
};)cpp",
" void A::foo() {}\n",
},
{
R"cpp(
struct A {
static static void fo^o() {}
};)cpp",
R"cpp(
struct A {
static static void foo() ;
};)cpp",
" void A::foo() {}\n",
},
{
R"cpp(
struct Foo {
explicit Fo^o(int) {}
};)cpp",
R"cpp(
struct Foo {
explicit Foo(int) ;
};)cpp",
" Foo::Foo(int) {}\n",
},
{
R"cpp(
struct Foo {
explicit explicit Fo^o(int) {}
};)cpp",
R"cpp(
struct Foo {
explicit explicit Foo(int) ;
};)cpp",
" Foo::Foo(int) {}\n",
},
};
for (const auto &Case : Cases) {
SCOPED_TRACE(Case.Test);
EXPECT_EQ(apply(Case.Test, &EditedFiles), Case.ExpectedHeader);
EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(
testPath("Test.cpp"), Case.ExpectedSource)));
}
}
TEST_F(DefineOutlineTest, HandleMacros) {
llvm::StringMap<std::string> EditedFiles;
ExtraFiles["Test.cpp"] = "";
FileName = "Test.hpp";
ExtraArgs.push_back("-DVIRTUAL=virtual");
ExtraArgs.push_back("-DOVER=override");
struct {
llvm::StringRef Test;
llvm::StringRef ExpectedHeader;
llvm::StringRef ExpectedSource;
} Cases[] = {
{R"cpp(
#define BODY { return; }
void f^oo()BODY)cpp",
R"cpp(
#define BODY { return; }
void foo();)cpp",
"void foo()BODY"},
{R"cpp(
#define BODY return;
void f^oo(){BODY})cpp",
R"cpp(
#define BODY return;
void foo();)cpp",
"void foo(){BODY}"},
{R"cpp(
#define TARGET void foo()
[[TARGET]]{ return; })cpp",
R"cpp(
#define TARGET void foo()
TARGET;)cpp",
"TARGET{ return; }"},
{R"cpp(
#define TARGET foo
void [[TARGET]](){ return; })cpp",
R"cpp(
#define TARGET foo
void TARGET();)cpp",
"void TARGET(){ return; }"},
{R"cpp(#define VIRT virtual
struct A {
VIRT void f^oo() {}
};)cpp",
R"cpp(#define VIRT virtual
struct A {
VIRT void foo() ;
};)cpp",
" void A::foo() {}\n"},
{R"cpp(
struct A {
VIRTUAL void f^oo() {}
};)cpp",
R"cpp(
struct A {
VIRTUAL void foo() ;
};)cpp",
" void A::foo() {}\n"},
{R"cpp(
struct A {
virtual void foo() = 0;
};
struct B : A {
void fo^o() OVER {}
};)cpp",
R"cpp(
struct A {
virtual void foo() = 0;
};
struct B : A {
void foo() OVER ;
};)cpp",
"void B::foo() {}\n"},
{R"cpp(#define STUPID_MACRO(X) virtual
struct A {
STUPID_MACRO(sizeof sizeof int) void f^oo() {}
};)cpp",
R"cpp(#define STUPID_MACRO(X) virtual
struct A {
STUPID_MACRO(sizeof sizeof int) void foo() ;
};)cpp",
" void A::foo() {}\n"},
{R"cpp(#define STAT static
struct A {
STAT void f^oo() {}
};)cpp",
R"cpp(#define STAT static
struct A {
STAT void foo() ;
};)cpp",
" void A::foo() {}\n"},
{R"cpp(#define STUPID_MACRO(X) static
struct A {
STUPID_MACRO(sizeof sizeof int) void f^oo() {}
};)cpp",
R"cpp(#define STUPID_MACRO(X) static
struct A {
STUPID_MACRO(sizeof sizeof int) void foo() ;
};)cpp",
" void A::foo() {}\n"},
};
for (const auto &Case : Cases) {
SCOPED_TRACE(Case.Test);
EXPECT_EQ(apply(Case.Test, &EditedFiles), Case.ExpectedHeader);
EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(
testPath("Test.cpp"), Case.ExpectedSource)));
}
}
TEST_F(DefineOutlineTest, QualifyReturnValue) {
FileName = "Test.hpp";
ExtraFiles["Test.cpp"] = "";
struct {
llvm::StringRef Test;
llvm::StringRef ExpectedHeader;
llvm::StringRef ExpectedSource;
} Cases[] = {
{R"cpp(
namespace a { class Foo{}; }
using namespace a;
Foo fo^o() { return {}; })cpp",
R"cpp(
namespace a { class Foo{}; }
using namespace a;
Foo foo() ;)cpp",
"a::Foo foo() { return {}; }"},
{R"cpp(
namespace a {
class Foo {
class Bar {};
Bar fo^o() { return {}; }
};
})cpp",
R"cpp(
namespace a {
class Foo {
class Bar {};
Bar foo() ;
};
})cpp",
"a::Foo::Bar a::Foo::foo() { return {}; }\n"},
{R"cpp(
class Foo {};
Foo fo^o() { return {}; })cpp",
R"cpp(
class Foo {};
Foo foo() ;)cpp",
"Foo foo() { return {}; }"},
};
llvm::StringMap<std::string> EditedFiles;
for (auto &Case : Cases) {
apply(Case.Test, &EditedFiles);
EXPECT_EQ(apply(Case.Test, &EditedFiles), Case.ExpectedHeader);
EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(
testPath("Test.cpp"), Case.ExpectedSource)));
}
}
TEST_F(DefineOutlineTest, QualifyFunctionName) {
FileName = "Test.hpp";
struct {
llvm::StringRef TestHeader;
llvm::StringRef TestSource;
llvm::StringRef ExpectedHeader;
llvm::StringRef ExpectedSource;
} Cases[] = {
{
R"cpp(
namespace a {
namespace b {
class Foo {
void fo^o() {}
};
}
})cpp",
"",
R"cpp(
namespace a {
namespace b {
class Foo {
void foo() ;
};
}
})cpp",
"void a::b::Foo::foo() {}\n",
},
{
"namespace a { namespace b { void f^oo() {} } }",
"namespace a{}",
"namespace a { namespace b { void foo() ; } }",
"namespace a{void b::foo() {} }",
},
{
"namespace a { namespace b { void f^oo() {} } }",
"using namespace a;",
"namespace a { namespace b { void foo() ; } }",
// FIXME: Take using namespace directives in the source file into
// account. This can be spelled as b::foo instead.
"using namespace a;void a::b::foo() {} ",
},
};
llvm::StringMap<std::string> EditedFiles;
for (auto &Case : Cases) {
ExtraFiles["Test.cpp"] = std::string(Case.TestSource);
EXPECT_EQ(apply(Case.TestHeader, &EditedFiles), Case.ExpectedHeader);
EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(
testPath("Test.cpp"), Case.ExpectedSource)))
<< Case.TestHeader;
}
}
TEST_F(DefineOutlineTest, FailsMacroSpecifier) {
FileName = "Test.hpp";
ExtraFiles["Test.cpp"] = "";
ExtraArgs.push_back("-DFINALOVER=final override");
std::pair<StringRef, StringRef> Cases[] = {
{
R"cpp(
#define VIRT virtual void
struct A {
VIRT fo^o() {}
};)cpp",
"fail: define outline: couldn't remove `virtual` keyword."},
{
R"cpp(
#define OVERFINAL final override
struct A {
virtual void foo() {}
};
struct B : A {
void fo^o() OVERFINAL {}
};)cpp",
"fail: define outline: Can't move out of line as function has a "
"macro `override` specifier.\ndefine outline: Can't move out of line "
"as function has a macro `final` specifier."},
{
R"cpp(
struct A {
virtual void foo() {}
};
struct B : A {
void fo^o() FINALOVER {}
};)cpp",
"fail: define outline: Can't move out of line as function has a "
"macro `override` specifier.\ndefine outline: Can't move out of line "
"as function has a macro `final` specifier."},
};
for (const auto &Case : Cases) {
EXPECT_EQ(apply(Case.first), Case.second);
}
}
} // namespace
} // namespace clangd
} // namespace clang