blob: ed6bf334edacece820e706ad4889c521f7e80281 [file] [log] [blame]
//===-- FindSymbolsTests.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 "Annotations.h"
#include "ClangdServer.h"
#include "FindSymbols.h"
#include "SyncAPI.h"
#include "TestFS.h"
#include "TestTU.h"
#include "llvm/ADT/StringRef.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace clang {
namespace clangd {
namespace {
using ::testing::AllOf;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::UnorderedElementsAre;
// GMock helpers for matching SymbolInfos items.
MATCHER_P(QName, Name, "") {
if (arg.containerName.empty())
return arg.name == Name;
return (arg.containerName + "::" + arg.name) == Name;
}
MATCHER_P(WithName, N, "") { return arg.name == N; }
MATCHER_P(WithKind, Kind, "") { return arg.kind == Kind; }
MATCHER_P(WithDetail, Detail, "") { return arg.detail == Detail; }
MATCHER_P(SymRange, Range, "") { return arg.range == Range; }
// GMock helpers for matching DocumentSymbol.
MATCHER_P(SymNameRange, Range, "") { return arg.selectionRange == Range; }
template <class... ChildMatchers>
::testing::Matcher<DocumentSymbol> Children(ChildMatchers... ChildrenM) {
return Field(&DocumentSymbol::children, UnorderedElementsAre(ChildrenM...));
}
std::vector<SymbolInformation> getSymbols(TestTU &TU, llvm::StringRef Query,
int Limit = 0) {
auto SymbolInfos = getWorkspaceSymbols(Query, Limit, TU.index().get(),
testPath(TU.Filename));
EXPECT_TRUE(bool(SymbolInfos)) << "workspaceSymbols returned an error";
return *SymbolInfos;
}
TEST(WorkspaceSymbols, Macros) {
TestTU TU;
TU.Code = R"cpp(
#define MACRO X
)cpp";
// LSP's SymbolKind doesn't have a "Macro" kind, and
// indexSymbolKindToSymbolKind() currently maps macros
// to SymbolKind::String.
EXPECT_THAT(getSymbols(TU, "macro"),
ElementsAre(AllOf(QName("MACRO"), WithKind(SymbolKind::String))));
}
TEST(WorkspaceSymbols, NoLocals) {
TestTU TU;
TU.Code = R"cpp(
void test(int FirstParam, int SecondParam) {
struct LocalClass {};
int local_var;
})cpp";
EXPECT_THAT(getSymbols(TU, "l"), ElementsAre(QName("LocalClass")));
EXPECT_THAT(getSymbols(TU, "p"), IsEmpty());
}
TEST(WorkspaceSymbols, Globals) {
TestTU TU;
TU.AdditionalFiles["foo.h"] = R"cpp(
int global_var;
int global_func();
struct GlobalStruct {};)cpp";
TU.Code = R"cpp(
#include "foo.h"
)cpp";
EXPECT_THAT(getSymbols(TU, "global"),
UnorderedElementsAre(
AllOf(QName("GlobalStruct"), WithKind(SymbolKind::Struct)),
AllOf(QName("global_func"), WithKind(SymbolKind::Function)),
AllOf(QName("global_var"), WithKind(SymbolKind::Variable))));
}
TEST(WorkspaceSymbols, Unnamed) {
TestTU TU;
TU.AdditionalFiles["foo.h"] = R"cpp(
struct {
int InUnnamed;
} UnnamedStruct;)cpp";
TU.Code = R"cpp(
#include "foo.h"
)cpp";
EXPECT_THAT(getSymbols(TU, "UnnamedStruct"),
ElementsAre(AllOf(QName("UnnamedStruct"),
WithKind(SymbolKind::Variable))));
EXPECT_THAT(getSymbols(TU, "InUnnamed"),
ElementsAre(AllOf(QName("(anonymous struct)::InUnnamed"),
WithKind(SymbolKind::Field))));
}
TEST(WorkspaceSymbols, InMainFile) {
TestTU TU;
TU.Code = R"cpp(
int test() {}
static void test2() {}
)cpp";
EXPECT_THAT(getSymbols(TU, "test"),
ElementsAre(QName("test"), QName("test2")));
}
TEST(WorkspaceSymbols, Namespaces) {
TestTU TU;
TU.AdditionalFiles["foo.h"] = R"cpp(
namespace ans1 {
int ai1;
namespace ans2 {
int ai2;
namespace ans3 {
int ai3;
}
}
}
)cpp";
TU.Code = R"cpp(
#include "foo.h"
)cpp";
EXPECT_THAT(getSymbols(TU, "a"),
UnorderedElementsAre(
QName("ans1"), QName("ans1::ai1"), QName("ans1::ans2"),
QName("ans1::ans2::ai2"), QName("ans1::ans2::ans3"),
QName("ans1::ans2::ans3::ai3")));
EXPECT_THAT(getSymbols(TU, "::"), ElementsAre(QName("ans1")));
EXPECT_THAT(getSymbols(TU, "::a"), ElementsAre(QName("ans1")));
EXPECT_THAT(getSymbols(TU, "ans1::"),
UnorderedElementsAre(QName("ans1::ai1"), QName("ans1::ans2"),
QName("ans1::ans2::ai2"),
QName("ans1::ans2::ans3"),
QName("ans1::ans2::ans3::ai3")));
EXPECT_THAT(getSymbols(TU, "ans2::"),
UnorderedElementsAre(QName("ans1::ans2::ai2"),
QName("ans1::ans2::ans3"),
QName("ans1::ans2::ans3::ai3")));
EXPECT_THAT(getSymbols(TU, "::ans1"), ElementsAre(QName("ans1")));
EXPECT_THAT(getSymbols(TU, "::ans1::"),
UnorderedElementsAre(QName("ans1::ai1"), QName("ans1::ans2")));
EXPECT_THAT(getSymbols(TU, "::ans1::ans2"), ElementsAre(QName("ans1::ans2")));
EXPECT_THAT(getSymbols(TU, "::ans1::ans2::"),
ElementsAre(QName("ans1::ans2::ai2"), QName("ans1::ans2::ans3")));
// Ensure sub-sequence matching works.
EXPECT_THAT(getSymbols(TU, "ans1::ans3::ai"),
UnorderedElementsAre(QName("ans1::ans2::ans3::ai3")));
}
TEST(WorkspaceSymbols, AnonymousNamespace) {
TestTU TU;
TU.Code = R"cpp(
namespace {
void test() {}
}
)cpp";
EXPECT_THAT(getSymbols(TU, "test"), ElementsAre(QName("test")));
}
TEST(WorkspaceSymbols, MultiFile) {
TestTU TU;
TU.AdditionalFiles["foo.h"] = R"cpp(
int foo() {
}
)cpp";
TU.AdditionalFiles["foo2.h"] = R"cpp(
int foo2() {
}
)cpp";
TU.Code = R"cpp(
#include "foo.h"
#include "foo2.h"
)cpp";
EXPECT_THAT(getSymbols(TU, "foo"),
UnorderedElementsAre(QName("foo"), QName("foo2")));
}
TEST(WorkspaceSymbols, GlobalNamespaceQueries) {
TestTU TU;
TU.AdditionalFiles["foo.h"] = R"cpp(
int foo() {
}
class Foo {
int a;
};
namespace ns {
int foo2() {
}
}
)cpp";
TU.Code = R"cpp(
#include "foo.h"
)cpp";
EXPECT_THAT(getSymbols(TU, "::"),
UnorderedElementsAre(
AllOf(QName("Foo"), WithKind(SymbolKind::Class)),
AllOf(QName("foo"), WithKind(SymbolKind::Function)),
AllOf(QName("ns"), WithKind(SymbolKind::Namespace))));
EXPECT_THAT(getSymbols(TU, ":"), IsEmpty());
EXPECT_THAT(getSymbols(TU, ""),
UnorderedElementsAre(QName("foo"), QName("Foo"), QName("Foo::a"),
QName("ns"), QName("ns::foo2")));
}
TEST(WorkspaceSymbols, Enums) {
TestTU TU;
TU.AdditionalFiles["foo.h"] = R"cpp(
enum {
Red
};
enum Color {
Green
};
enum class Color2 {
Yellow
};
namespace ns {
enum {
Black
};
enum Color3 {
Blue
};
enum class Color4 {
White
};
}
)cpp";
TU.Code = R"cpp(
#include "foo.h"
)cpp";
EXPECT_THAT(getSymbols(TU, "Red"), ElementsAre(QName("Red")));
EXPECT_THAT(getSymbols(TU, "::Red"), ElementsAre(QName("Red")));
EXPECT_THAT(getSymbols(TU, "Green"), ElementsAre(QName("Green")));
EXPECT_THAT(getSymbols(TU, "Green"), ElementsAre(QName("Green")));
EXPECT_THAT(getSymbols(TU, "Color2::Yellow"),
ElementsAre(QName("Color2::Yellow")));
EXPECT_THAT(getSymbols(TU, "Yellow"), ElementsAre(QName("Color2::Yellow")));
EXPECT_THAT(getSymbols(TU, "ns::Black"), ElementsAre(QName("ns::Black")));
EXPECT_THAT(getSymbols(TU, "ns::Blue"), ElementsAre(QName("ns::Blue")));
EXPECT_THAT(getSymbols(TU, "ns::Color4::White"),
ElementsAre(QName("ns::Color4::White")));
}
TEST(WorkspaceSymbols, Ranking) {
TestTU TU;
TU.AdditionalFiles["foo.h"] = R"cpp(
namespace ns{}
void func();
)cpp";
TU.Code = R"cpp(
#include "foo.h"
)cpp";
EXPECT_THAT(getSymbols(TU, "::"), ElementsAre(QName("func"), QName("ns")));
}
TEST(WorkspaceSymbols, RankingPartialNamespace) {
TestTU TU;
TU.Code = R"cpp(
namespace ns1 {
namespace ns2 { struct Foo {}; }
}
namespace ns2 { struct FooB {}; })cpp";
EXPECT_THAT(getSymbols(TU, "ns2::f"),
ElementsAre(QName("ns2::FooB"), QName("ns1::ns2::Foo")));
}
TEST(WorkspaceSymbols, WithLimit) {
TestTU TU;
TU.AdditionalFiles["foo.h"] = R"cpp(
int foo;
int foo2;
)cpp";
TU.Code = R"cpp(
#include "foo.h"
)cpp";
// Foo is higher ranked because of exact name match.
EXPECT_THAT(getSymbols(TU, "foo"),
UnorderedElementsAre(
AllOf(QName("foo"), WithKind(SymbolKind::Variable)),
AllOf(QName("foo2"), WithKind(SymbolKind::Variable))));
EXPECT_THAT(getSymbols(TU, "foo", 1), ElementsAre(QName("foo")));
}
TEST(WorkspaceSymbols, TempSpecs) {
TestTU TU;
TU.ExtraArgs = {"-xc++"};
TU.Code = R"cpp(
template <typename T, typename U, int X = 5> class Foo {};
template <typename T> class Foo<int, T> {};
template <> class Foo<bool, int> {};
template <> class Foo<bool, int, 3> {};
)cpp";
// Foo is higher ranked because of exact name match.
EXPECT_THAT(
getSymbols(TU, "Foo"),
UnorderedElementsAre(
AllOf(QName("Foo"), WithKind(SymbolKind::Class)),
AllOf(QName("Foo<int, T>"), WithKind(SymbolKind::Class)),
AllOf(QName("Foo<bool, int>"), WithKind(SymbolKind::Class)),
AllOf(QName("Foo<bool, int, 3>"), WithKind(SymbolKind::Class))));
}
std::vector<DocumentSymbol> getSymbols(ParsedAST AST) {
auto SymbolInfos = getDocumentSymbols(AST);
EXPECT_TRUE(bool(SymbolInfos)) << "documentSymbols returned an error";
return *SymbolInfos;
}
TEST(DocumentSymbols, BasicSymbols) {
TestTU TU;
Annotations Main(R"(
class Foo;
class Foo {
Foo() {}
Foo(int a) {}
void $decl[[f]]();
friend void f1();
friend class Friend;
Foo& operator=(const Foo&);
~Foo();
class Nested {
void f();
};
};
class Friend {
};
void f1();
inline void f2() {}
static const int KInt = 2;
const char* kStr = "123";
void f1() {}
namespace foo {
// Type alias
typedef int int32;
using int32_t = int32;
// Variable
int v1;
// Namespace
namespace bar {
int v2;
}
// Namespace alias
namespace baz = bar;
using bar::v2;
} // namespace foo
)");
TU.Code = Main.code().str();
EXPECT_THAT(
getSymbols(TU.build()),
ElementsAreArray(
{AllOf(WithName("Foo"), WithKind(SymbolKind::Class),
WithDetail("class"), Children()),
AllOf(WithName("Foo"), WithKind(SymbolKind::Class),
WithDetail("class"),
Children(
AllOf(WithName("Foo"), WithKind(SymbolKind::Constructor),
WithDetail("()"), Children()),
AllOf(WithName("Foo"), WithKind(SymbolKind::Constructor),
WithDetail("(int)"), Children()),
AllOf(WithName("f"), WithKind(SymbolKind::Method),
WithDetail("void ()"), Children()),
AllOf(WithName("operator="), WithKind(SymbolKind::Method),
WithDetail("Foo &(const Foo &)"), Children()),
AllOf(WithName("~Foo"), WithKind(SymbolKind::Constructor),
WithDetail(""), Children()),
AllOf(WithName("Nested"), WithKind(SymbolKind::Class),
WithDetail("class"),
Children(AllOf(
WithName("f"), WithKind(SymbolKind::Method),
WithDetail("void ()"), Children()))))),
AllOf(WithName("Friend"), WithKind(SymbolKind::Class),
WithDetail("class"), Children()),
AllOf(WithName("f1"), WithKind(SymbolKind::Function),
WithDetail("void ()"), Children()),
AllOf(WithName("f2"), WithKind(SymbolKind::Function),
WithDetail("void ()"), Children()),
AllOf(WithName("KInt"), WithKind(SymbolKind::Variable),
WithDetail("const int"), Children()),
AllOf(WithName("kStr"), WithKind(SymbolKind::Variable),
WithDetail("const char *"), Children()),
AllOf(WithName("f1"), WithKind(SymbolKind::Function),
WithDetail("void ()"), Children()),
AllOf(
WithName("foo"), WithKind(SymbolKind::Namespace), WithDetail(""),
Children(AllOf(WithName("int32"), WithKind(SymbolKind::Class),
WithDetail("type alias"), Children()),
AllOf(WithName("int32_t"), WithKind(SymbolKind::Class),
WithDetail("type alias"), Children()),
AllOf(WithName("v1"), WithKind(SymbolKind::Variable),
WithDetail("int"), Children()),
AllOf(WithName("bar"), WithKind(SymbolKind::Namespace),
WithDetail(""),
Children(AllOf(WithName("v2"),
WithKind(SymbolKind::Variable),
WithDetail("int"), Children()))),
AllOf(WithName("baz"), WithKind(SymbolKind::Namespace),
WithDetail(""), Children()),
AllOf(WithName("v2"), WithKind(SymbolKind::Namespace),
WithDetail(""))))}));
}
TEST(DocumentSymbols, DeclarationDefinition) {
TestTU TU;
Annotations Main(R"(
class Foo {
void $decl[[f]]();
};
void Foo::$def[[f]]() {
}
)");
TU.Code = Main.code().str();
EXPECT_THAT(
getSymbols(TU.build()),
ElementsAre(
AllOf(WithName("Foo"), WithKind(SymbolKind::Class),
WithDetail("class"),
Children(AllOf(WithName("f"), WithKind(SymbolKind::Method),
WithDetail("void ()"),
SymNameRange(Main.range("decl"))))),
AllOf(WithName("Foo::f"), WithKind(SymbolKind::Method),
WithDetail("void ()"), SymNameRange(Main.range("def")))));
}
TEST(DocumentSymbols, Concepts) {
TestTU TU;
TU.ExtraArgs = {"-std=c++20"};
TU.Code = "template <typename T> concept C = requires(T t) { t.foo(); };";
EXPECT_THAT(getSymbols(TU.build()),
ElementsAre(AllOf(WithName("C"), WithDetail("concept"))));
}
TEST(DocumentSymbols, ExternSymbol) {
TestTU TU;
TU.AdditionalFiles["foo.h"] = R"cpp(
extern int var;
)cpp";
TU.Code = R"cpp(
#include "foo.h"
)cpp";
EXPECT_THAT(getSymbols(TU.build()), IsEmpty());
}
TEST(DocumentSymbols, ExternContext) {
TestTU TU;
TU.Code = R"cpp(
extern "C" {
void foo();
class Foo {};
}
namespace ns {
extern "C" {
void bar();
class Bar {};
}
})cpp";
EXPECT_THAT(getSymbols(TU.build()),
ElementsAre(WithName("foo"), WithName("Foo"),
AllOf(WithName("ns"),
Children(WithName("bar"), WithName("Bar")))));
}
TEST(DocumentSymbols, ExportContext) {
TestTU TU;
TU.ExtraArgs = {"-std=c++20"};
TU.Code = R"cpp(
export module test;
export {
void foo();
class Foo {};
})cpp";
EXPECT_THAT(getSymbols(TU.build()),
ElementsAre(WithName("foo"), WithName("Foo")));
}
TEST(DocumentSymbols, NoLocals) {
TestTU TU;
TU.Code = R"cpp(
void test(int FirstParam, int SecondParam) {
struct LocalClass {};
int local_var;
})cpp";
EXPECT_THAT(getSymbols(TU.build()), ElementsAre(WithName("test")));
}
TEST(DocumentSymbols, Unnamed) {
TestTU TU;
TU.Code = R"cpp(
struct {
int InUnnamed;
} UnnamedStruct;
)cpp";
EXPECT_THAT(
getSymbols(TU.build()),
ElementsAre(AllOf(WithName("(anonymous struct)"),
WithKind(SymbolKind::Struct), WithDetail("struct"),
Children(AllOf(WithName("InUnnamed"),
WithKind(SymbolKind::Field),
WithDetail("int"), Children()))),
AllOf(WithName("UnnamedStruct"),
WithKind(SymbolKind::Variable),
WithDetail("struct (unnamed)"), Children())));
}
TEST(DocumentSymbols, InHeaderFile) {
TestTU TU;
TU.AdditionalFiles["bar.h"] = R"cpp(
int foo() {
}
)cpp";
TU.Code = R"cpp(
int i; // declaration to finish preamble
#include "bar.h"
int test() {
}
)cpp";
EXPECT_THAT(getSymbols(TU.build()),
ElementsAre(WithName("i"), WithName("test")));
}
TEST(DocumentSymbols, Template) {
TestTU TU;
TU.Code = R"(
template <class T> struct Tmpl {T x = 0;};
template <> struct Tmpl<int> {
int y = 0;
};
extern template struct Tmpl<float>;
template struct Tmpl<double>;
template <class T, class U, class Z = float>
int funcTmpl(U a);
template <>
int funcTmpl<int>(double a);
template <class T, class U = double>
int varTmpl = T();
template <>
double varTmpl<int> = 10.0;
)";
EXPECT_THAT(
getSymbols(TU.build()),
ElementsAre(
AllOf(WithName("Tmpl"), WithKind(SymbolKind::Struct),
WithDetail("template struct"),
Children(AllOf(WithName("x"), WithKind(SymbolKind::Field),
WithDetail("T")))),
AllOf(WithName("Tmpl<int>"), WithKind(SymbolKind::Struct),
WithDetail("struct"),
Children(AllOf(WithName("y"), WithDetail("int")))),
AllOf(WithName("Tmpl<float>"), WithKind(SymbolKind::Struct),
WithDetail("struct"), Children()),
AllOf(WithName("Tmpl<double>"), WithKind(SymbolKind::Struct),
WithDetail("struct"), Children()),
AllOf(WithName("funcTmpl"), WithDetail("template int (U)"),
Children()),
AllOf(WithName("funcTmpl<int>"), WithDetail("int (double)"),
Children()),
AllOf(WithName("varTmpl"), WithDetail("template int"), Children()),
AllOf(WithName("varTmpl<int>"), WithDetail("double"), Children())));
}
TEST(DocumentSymbols, Namespaces) {
TestTU TU;
TU.Code = R"cpp(
namespace ans1 {
int ai1;
namespace ans2 {
int ai2;
}
}
namespace {
void test() {}
}
namespace na {
inline namespace nb {
class Foo {};
}
}
namespace na {
// This is still inlined.
namespace nb {
class Bar {};
}
}
)cpp";
EXPECT_THAT(
getSymbols(TU.build()),
ElementsAreArray<::testing::Matcher<DocumentSymbol>>(
{AllOf(WithName("ans1"),
Children(AllOf(WithName("ai1"), Children()),
AllOf(WithName("ans2"), Children(WithName("ai2"))))),
AllOf(WithName("(anonymous namespace)"), Children(WithName("test"))),
AllOf(WithName("na"),
Children(AllOf(WithName("nb"), Children(WithName("Foo"))))),
AllOf(WithName("na"),
Children(AllOf(WithName("nb"), Children(WithName("Bar")))))}));
}
TEST(DocumentSymbols, Enums) {
TestTU TU;
TU.Code = R"(
enum {
Red
};
enum Color {
Green
};
enum class Color2 {
Yellow
};
namespace ns {
enum {
Black
};
}
)";
EXPECT_THAT(
getSymbols(TU.build()),
ElementsAre(
AllOf(WithName("(anonymous enum)"), WithDetail("enum"),
Children(AllOf(WithName("Red"), WithDetail("(unnamed)")))),
AllOf(WithName("Color"), WithDetail("enum"),
Children(AllOf(WithName("Green"), WithDetail("Color")))),
AllOf(WithName("Color2"), WithDetail("enum"),
Children(AllOf(WithName("Yellow"), WithDetail("Color2")))),
AllOf(WithName("ns"),
Children(AllOf(WithName("(anonymous enum)"), WithDetail("enum"),
Children(AllOf(WithName("Black"),
WithDetail("(unnamed)"))))))));
}
TEST(DocumentSymbols, Macro) {
struct Test {
const char *Code;
testing::Matcher<DocumentSymbol> Matcher;
} Tests[] = {
{
R"cpp(
// Basic macro that generates symbols.
#define DEFINE_FLAG(X) bool FLAGS_##X; bool FLAGS_no##X
DEFINE_FLAG(pretty);
)cpp",
AllOf(WithName("DEFINE_FLAG"), WithDetail("(pretty)"),
Children(WithName("FLAGS_pretty"), WithName("FLAGS_nopretty"))),
},
{
R"cpp(
// Hierarchy is determined by primary (name) location.
#define ID(X) X
namespace ID(ns) { int ID(y); }
)cpp",
AllOf(WithName("ID"), WithDetail("(ns)"),
Children(AllOf(WithName("ns"),
Children(AllOf(WithName("ID"), WithDetail("(y)"),
Children(WithName("y"))))))),
},
{
R"cpp(
// More typical example where macro only generates part of a decl.
#define TEST(A, B) class A##_##B { void go(); }; void A##_##B::go()
TEST(DocumentSymbols, Macro) { }
)cpp",
AllOf(WithName("TEST"), WithDetail("(DocumentSymbols, Macro)"),
Children(AllOf(WithName("DocumentSymbols_Macro"),
Children(WithName("go"))),
WithName("DocumentSymbols_Macro::go"))),
},
{
R"cpp(
// Nested macros.
#define NAMESPACE(NS, BODY) namespace NS { BODY }
NAMESPACE(a, NAMESPACE(b, int x;))
)cpp",
AllOf(
WithName("NAMESPACE"), WithDetail("(a, NAMESPACE(b, int x;))"),
Children(AllOf(
WithName("a"),
Children(AllOf(WithName("NAMESPACE"),
// FIXME: nested expansions not in TokenBuffer
WithDetail(""),
Children(AllOf(WithName("b"),
Children(WithName("x"))))))))),
},
{
R"cpp(
// Macro invoked from body is not exposed.
#define INNER(X) int X
#define OUTER(X) INNER(X)
OUTER(foo);
)cpp",
AllOf(WithName("OUTER"), WithDetail("(foo)"),
Children(WithName("foo"))),
},
};
for (const Test &T : Tests) {
auto TU = TestTU::withCode(T.Code);
EXPECT_THAT(getSymbols(TU.build()), ElementsAre(T.Matcher)) << T.Code;
}
}
TEST(DocumentSymbols, RangeFromMacro) {
TestTU TU;
Annotations Main(R"(
#define FF(name) \
class name##_Test {};
$expansion1[[FF]](abc);
#define FF2() \
class Test {}
$expansion2parens[[$expansion2[[FF2]]()]];
#define FF3() \
void waldo()
$fullDef[[FF3() {
int var = 42;
}]]
)");
TU.Code = Main.code().str();
EXPECT_THAT(
getSymbols(TU.build()),
ElementsAre(
AllOf(WithName("FF"), WithDetail("(abc)"),
Children(AllOf(WithName("abc_Test"), WithDetail("class"),
SymNameRange(Main.range("expansion1"))))),
AllOf(WithName("FF2"), WithDetail("()"),
SymNameRange(Main.range("expansion2")),
SymRange(Main.range("expansion2parens")),
Children(AllOf(WithName("Test"), WithDetail("class"),
SymNameRange(Main.range("expansion2"))))),
AllOf(WithName("FF3"), WithDetail("()"),
SymRange(Main.range("fullDef")),
Children(AllOf(WithName("waldo"), WithDetail("void ()"),
SymRange(Main.range("fullDef")))))));
}
TEST(DocumentSymbols, FuncTemplates) {
TestTU TU;
Annotations Source(R"cpp(
template <class T>
T foo() {}
auto x = foo<int>();
auto y = foo<double>();
)cpp");
TU.Code = Source.code().str();
// Make sure we only see the template declaration, not instantiations.
EXPECT_THAT(getSymbols(TU.build()),
ElementsAre(AllOf(WithName("foo"), WithDetail("template T ()")),
AllOf(WithName("x"), WithDetail("int")),
AllOf(WithName("y"), WithDetail("double"))));
}
TEST(DocumentSymbols, UsingDirectives) {
TestTU TU;
Annotations Source(R"cpp(
namespace ns {
int foo;
}
namespace ns_alias = ns;
using namespace ::ns; // check we don't loose qualifiers.
using namespace ns_alias; // and namespace aliases.
)cpp");
TU.Code = Source.code().str();
EXPECT_THAT(getSymbols(TU.build()),
ElementsAre(WithName("ns"), WithName("ns_alias"),
WithName("using namespace ::ns"),
WithName("using namespace ns_alias")));
}
TEST(DocumentSymbols, TempSpecs) {
TestTU TU;
TU.Code = R"cpp(
template <typename T, typename U, int X = 5> class Foo {};
template <typename T> class Foo<int, T> {};
template <> class Foo<bool, int> {};
template <> class Foo<bool, int, 3> {};
)cpp";
// Foo is higher ranked because of exact name match.
EXPECT_THAT(getSymbols(TU.build()),
UnorderedElementsAre(
AllOf(WithName("Foo"), WithKind(SymbolKind::Class),
WithDetail("template class")),
AllOf(WithName("Foo<int, T>"), WithKind(SymbolKind::Class),
WithDetail("template class")),
AllOf(WithName("Foo<bool, int>"), WithKind(SymbolKind::Class),
WithDetail("class")),
AllOf(WithName("Foo<bool, int, 3>"),
WithKind(SymbolKind::Class), WithDetail("class"))));
}
TEST(DocumentSymbols, Qualifiers) {
TestTU TU;
TU.Code = R"cpp(
namespace foo { namespace bar {
struct Cls;
int func1();
int func2();
int func3();
int func4();
}}
struct foo::bar::Cls { };
int foo::bar::func1() { return 10; }
int ::foo::bar::func2() { return 20; }
using namespace foo;
int bar::func3() { return 30; }
namespace alias = foo::bar;
int ::alias::func4() { return 40; }
)cpp";
// All the qualifiers should be preserved exactly as written.
EXPECT_THAT(getSymbols(TU.build()),
UnorderedElementsAre(
WithName("foo"), WithName("foo::bar::Cls"),
WithName("foo::bar::func1"), WithName("::foo::bar::func2"),
WithName("using namespace foo"), WithName("bar::func3"),
WithName("alias"), WithName("::alias::func4")));
}
TEST(DocumentSymbols, QualifiersWithTemplateArgs) {
TestTU TU;
TU.Code = R"cpp(
template <typename T, typename U = double> class Foo;
template <>
class Foo<int, double> {
int method1();
int method2();
int method3();
};
using int_type = int;
// Typedefs should be preserved!
int Foo<int_type, double>::method1() { return 10; }
// Default arguments should not be shown!
int Foo<int>::method2() { return 20; }
using Foo_type = Foo<int>;
// If the whole type is aliased, this should be preserved too!
int Foo_type::method3() { return 30; }
)cpp";
EXPECT_THAT(getSymbols(TU.build()),
UnorderedElementsAre(
AllOf(WithName("Foo"), WithDetail("template class")),
AllOf(WithName("Foo<int, double>"), WithDetail("class")),
AllOf(WithName("int_type"), WithDetail("type alias")),
AllOf(WithName("Foo<int_type, double>::method1"),
WithDetail("int ()")),
AllOf(WithName("Foo<int>::method2"), WithDetail("int ()")),
AllOf(WithName("Foo_type"), WithDetail("type alias")),
AllOf(WithName("Foo_type::method3"), WithDetail("int ()"))));
}
TEST(DocumentSymbolsTest, Ranges) {
TestTU TU;
Annotations Main(R"(
$foo[[int foo(bool Argument) {
return 42;
}]]
$variable[[char GLOBAL_VARIABLE]];
$ns[[namespace ns {
$bar[[class Bar {
public:
$ctor[[Bar() {}]]
$dtor[[~Bar()]];
private:
$field[[unsigned Baz]];
$getbaz[[unsigned getBaz() { return Baz; }]]
}]];
}]] // namespace ns
$forwardclass[[class ForwardClassDecl]];
$struct[[struct StructDefinition {
$structfield[[int *Pointer = nullptr]];
}]];
$forwardstruct[[struct StructDeclaration]];
$forwardfunc[[void forwardFunctionDecl(int Something)]];
)");
TU.Code = Main.code().str();
EXPECT_THAT(
getSymbols(TU.build()),
UnorderedElementsAre(
AllOf(WithName("foo"), WithKind(SymbolKind::Function),
WithDetail("int (bool)"), SymRange(Main.range("foo"))),
AllOf(WithName("GLOBAL_VARIABLE"), WithKind(SymbolKind::Variable),
WithDetail("char"), SymRange(Main.range("variable"))),
AllOf(
WithName("ns"), WithKind(SymbolKind::Namespace),
SymRange(Main.range("ns")),
Children(AllOf(
WithName("Bar"), WithKind(SymbolKind::Class),
WithDetail("class"), SymRange(Main.range("bar")),
Children(
AllOf(WithName("Bar"), WithKind(SymbolKind::Constructor),
WithDetail("()"), SymRange(Main.range("ctor"))),
AllOf(WithName("~Bar"), WithKind(SymbolKind::Constructor),
WithDetail(""), SymRange(Main.range("dtor"))),
AllOf(WithName("Baz"), WithKind(SymbolKind::Field),
WithDetail("unsigned int"),
SymRange(Main.range("field"))),
AllOf(WithName("getBaz"), WithKind(SymbolKind::Method),
WithDetail("unsigned int ()"),
SymRange(Main.range("getbaz"))))))),
AllOf(WithName("ForwardClassDecl"), WithKind(SymbolKind::Class),
WithDetail("class"), SymRange(Main.range("forwardclass"))),
AllOf(WithName("StructDefinition"), WithKind(SymbolKind::Struct),
WithDetail("struct"), SymRange(Main.range("struct")),
Children(AllOf(WithName("Pointer"), WithKind(SymbolKind::Field),
WithDetail("int *"),
SymRange(Main.range("structfield"))))),
AllOf(WithName("StructDeclaration"), WithKind(SymbolKind::Struct),
WithDetail("struct"), SymRange(Main.range("forwardstruct"))),
AllOf(WithName("forwardFunctionDecl"), WithKind(SymbolKind::Function),
WithDetail("void (int)"),
SymRange(Main.range("forwardfunc")))));
}
TEST(DocumentSymbolsTest, DependentType) {
TestTU TU;
TU.Code = R"(
template <typename T> auto plus(T x, T y) -> decltype(x + y) { return x + y; }
template <typename Key, typename Value> class Pair {};
template <typename Key, typename Value>
struct Context : public Pair<Key, Value> {
using Pair<Key, Value>::Pair;
};
)";
EXPECT_THAT(
getSymbols(TU.build()),
ElementsAre(
AllOf(WithName("plus"),
WithDetail("template auto (T, T) -> decltype(x + y)")),
AllOf(WithName("Pair"), WithDetail("template class")),
AllOf(WithName("Context"), WithDetail("template struct"),
Children(AllOf(
WithName("Pair<type-parameter-0-0, type-parameter-0-1>"),
WithDetail("<dependent type>"))))));
}
TEST(DocumentSymbolsTest, ObjCCategoriesAndClassExtensions) {
TestTU TU;
TU.ExtraArgs = {"-xobjective-c++", "-Wno-objc-root-class"};
Annotations Main(R"cpp(
$Cat[[@interface Cat
+ (id)sharedCat;
@end]]
$SneakyCat[[@interface Cat (Sneaky)
- (id)sneak:(id)behavior;
@end]]
$MeowCat[[@interface Cat ()
- (void)meow;
@end]]
$PurCat[[@interface Cat ()
- (void)pur;
@end]]
)cpp");
TU.Code = Main.code().str();
EXPECT_THAT(
getSymbols(TU.build()),
ElementsAre(
AllOf(WithName("Cat"), SymRange(Main.range("Cat")),
Children(AllOf(WithName("+sharedCat"),
WithKind(SymbolKind::Method)))),
AllOf(WithName("Cat(Sneaky)"), SymRange(Main.range("SneakyCat")),
Children(
AllOf(WithName("-sneak:"), WithKind(SymbolKind::Method)))),
AllOf(
WithName("Cat()"), SymRange(Main.range("MeowCat")),
Children(AllOf(WithName("-meow"), WithKind(SymbolKind::Method)))),
AllOf(WithName("Cat()"), SymRange(Main.range("PurCat")),
Children(
AllOf(WithName("-pur"), WithKind(SymbolKind::Method))))));
}
TEST(DocumentSymbolsTest, PragmaMarkGroups) {
TestTU TU;
TU.ExtraArgs = {"-xobjective-c++", "-Wno-objc-root-class"};
Annotations Main(R"cpp(
$DogDef[[@interface Dog
@end]]
$DogImpl[[@implementation Dog
+ (id)sharedDoggo { return 0; }
#pragma $Overrides[[mark - Overrides
- (id)init {
return self;
}
- (void)bark {}]]
#pragma $Specifics[[mark - Dog Specifics
- (int)isAGoodBoy {
return 1;
}]]
@]]end // FIXME: Why doesn't this include the 'end'?
#pragma $End[[mark - End
]]
)cpp");
TU.Code = Main.code().str();
EXPECT_THAT(
getSymbols(TU.build()),
UnorderedElementsAre(
AllOf(WithName("Dog"), SymRange(Main.range("DogDef"))),
AllOf(WithName("Dog"), SymRange(Main.range("DogImpl")),
Children(AllOf(WithName("+sharedDoggo"),
WithKind(SymbolKind::Method)),
AllOf(WithName("Overrides"),
SymRange(Main.range("Overrides")),
Children(AllOf(WithName("-init"),
WithKind(SymbolKind::Method)),
AllOf(WithName("-bark"),
WithKind(SymbolKind::Method)))),
AllOf(WithName("Dog Specifics"),
SymRange(Main.range("Specifics")),
Children(AllOf(WithName("-isAGoodBoy"),
WithKind(SymbolKind::Method)))))),
AllOf(WithName("End"), SymRange(Main.range("End")))));
}
TEST(DocumentSymbolsTest, PragmaMarkGroupsNesting) {
TestTU TU;
TU.ExtraArgs = {"-xobjective-c++", "-Wno-objc-root-class"};
Annotations Main(R"cpp(
#pragma mark - Foo
struct Foo {
#pragma mark - Bar
void bar() {
#pragma mark - NotTopDecl
}
};
void bar() {}
)cpp");
TU.Code = Main.code().str();
EXPECT_THAT(
getSymbols(TU.build()),
UnorderedElementsAre(AllOf(
WithName("Foo"),
Children(AllOf(WithName("Foo"),
Children(AllOf(WithName("Bar"),
Children(AllOf(WithName("bar"),
Children(WithName(
"NotTopDecl"))))))),
WithName("bar")))));
}
TEST(DocumentSymbolsTest, PragmaMarkGroupsNoNesting) {
TestTU TU;
TU.ExtraArgs = {"-xobjective-c++", "-Wno-objc-root-class"};
Annotations Main(R"cpp(
#pragma mark Helpers
void helpA(id obj) {}
#pragma mark -
#pragma mark Core
void coreMethod() {}
)cpp");
TU.Code = Main.code().str();
EXPECT_THAT(getSymbols(TU.build()),
UnorderedElementsAre(WithName("Helpers"), WithName("helpA"),
WithName("(unnamed group)"),
WithName("Core"), WithName("coreMethod")));
}
} // namespace
} // namespace clangd
} // namespace clang