forked from OSchip/llvm-project
565 lines
15 KiB
C++
565 lines
15 KiB
C++
//===-- 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 "TestFS.h"
|
|
#include "TweakTesting.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) ) {}",
|
|
},
|
|
{
|
|
"struct Bar{Bar();}; void fo^o(Bar x = {}) {}",
|
|
"struct Bar{Bar();}; void foo(Bar x = {}) ;",
|
|
"void foo(Bar x ) {}",
|
|
},
|
|
// 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
|