blob: e1120430b85b565ee4fd7e4a775537ddbad1fdf0 [file] [log] [blame]
//===-- XRefsTests.cpp ---------------------------*- C++ -*--------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "Annotations.h"
#include "ClangdUnit.h"
#include "Compiler.h"
#include "Matchers.h"
#include "SyncAPI.h"
#include "TestFS.h"
#include "XRefs.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/PCHContainerOperations.h"
#include "clang/Frontend/Utils.h"
#include "llvm/Support/Path.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace clang {
namespace clangd {
using namespace llvm;
void PrintTo(const DocumentHighlight &V, std::ostream *O) {
llvm::raw_os_ostream OS(*O);
OS << V.range;
if (V.kind == DocumentHighlightKind::Read)
OS << "(r)";
if (V.kind == DocumentHighlightKind::Write)
OS << "(w)";
}
namespace {
using testing::ElementsAre;
using testing::Field;
using testing::IsEmpty;
using testing::Matcher;
using testing::UnorderedElementsAreArray;
class IgnoreDiagnostics : public DiagnosticsConsumer {
void onDiagnosticsReady(PathRef File,
std::vector<Diag> Diagnostics) override {}
};
// FIXME: this is duplicated with FileIndexTests. Share it.
ParsedAST build(StringRef Code) {
auto CI = createInvocationFromCommandLine(
{"clang", "-xc++", testPath("Foo.cpp").c_str()});
auto Buf = MemoryBuffer::getMemBuffer(Code);
auto AST = ParsedAST::Build(std::move(CI), nullptr, std::move(Buf),
std::make_shared<PCHContainerOperations>(),
vfs::getRealFileSystem());
assert(AST.hasValue());
return std::move(*AST);
}
// Extracts ranges from an annotated example, and constructs a matcher for a
// highlight set. Ranges should be named $read/$write as appropriate.
Matcher<const std::vector<DocumentHighlight> &>
HighlightsFrom(const Annotations &Test) {
std::vector<DocumentHighlight> Expected;
auto Add = [&](const Range &R, DocumentHighlightKind K) {
Expected.emplace_back();
Expected.back().range = R;
Expected.back().kind = K;
};
for (const auto &Range : Test.ranges())
Add(Range, DocumentHighlightKind::Text);
for (const auto &Range : Test.ranges("read"))
Add(Range, DocumentHighlightKind::Read);
for (const auto &Range : Test.ranges("write"))
Add(Range, DocumentHighlightKind::Write);
return UnorderedElementsAreArray(Expected);
}
TEST(HighlightsTest, All) {
const char *Tests[] = {
R"cpp(// Local variable
int main() {
int [[bonjour]];
$write[[^bonjour]] = 2;
int test1 = $read[[bonjour]];
}
)cpp",
R"cpp(// Struct
namespace ns1 {
struct [[MyClass]] {
static void foo([[MyClass]]*) {}
};
} // namespace ns1
int main() {
ns1::[[My^Class]]* Params;
}
)cpp",
R"cpp(// Function
int [[^foo]](int) {}
int main() {
[[foo]]([[foo]](42));
auto *X = &[[foo]];
}
)cpp",
};
for (const char *Test : Tests) {
Annotations T(Test);
auto AST = build(T.code());
EXPECT_THAT(findDocumentHighlights(AST, T.point()), HighlightsFrom(T))
<< Test;
}
}
MATCHER_P(RangeIs, R, "") { return arg.range == R; }
TEST(GoToDefinition, All) {
const char *Tests[] = {
R"cpp(// Local variable
int main() {
int [[bonjour]];
^bonjour = 2;
int test1 = bonjour;
}
)cpp",
R"cpp(// Struct
namespace ns1 {
struct [[MyClass]] {};
} // namespace ns1
int main() {
ns1::My^Class* Params;
}
)cpp",
R"cpp(// Function definition via pointer
int [[foo]](int) {}
int main() {
auto *X = &^foo;
}
)cpp",
R"cpp(// Function declaration via call
int [[foo]](int);
int main() {
return ^foo(42);
}
)cpp",
R"cpp(// Field
struct Foo { int [[x]]; };
int main() {
Foo bar;
bar.^x;
}
)cpp",
R"cpp(// Field, member initializer
struct Foo {
int [[x]];
Foo() : ^x(0) {}
};
)cpp",
R"cpp(// Field, GNU old-style field designator
struct Foo { int [[x]]; };
int main() {
Foo bar = { ^x : 1 };
}
)cpp",
R"cpp(// Field, field designator
struct Foo { int [[x]]; };
int main() {
Foo bar = { .^x = 2 };
}
)cpp",
R"cpp(// Method call
struct Foo { int [[x]](); };
int main() {
Foo bar;
bar.^x();
}
)cpp",
R"cpp(// Typedef
typedef int [[Foo]];
int main() {
^Foo bar;
}
)cpp",
/* FIXME: clangIndex doesn't handle template type parameters
R"cpp(// Template type parameter
template <[[typename T]]>
void foo() { ^T t; }
)cpp", */
R"cpp(// Namespace
namespace [[ns]] {
struct Foo { static void bar(); }
} // namespace ns
int main() { ^ns::Foo::bar(); }
)cpp",
R"cpp(// Macro
#define MACRO 0
#define [[MACRO]] 1
int main() { return ^MACRO; }
#define MACRO 2
#undef macro
)cpp",
R"cpp(// Macro
class TTT { public: int a; };
#define [[FF]](S) if (int b = S.a) {}
void f() {
TTT t;
F^F(t);
}
)cpp",
R"cpp(// Forward class declaration
class Foo;
class [[Foo]] {};
F^oo* foo();
)cpp",
R"cpp(// Function declaration
void foo();
void g() { f^oo(); }
void [[foo]]() {}
)cpp",
R"cpp(
#define FF(name) class name##_Test {};
[[FF]](my);
void f() { my^_Test a; }
)cpp",
R"cpp(
#define FF() class [[Test]] {};
FF();
void f() { T^est a; }
)cpp",
};
for (const char *Test : Tests) {
Annotations T(Test);
auto AST = build(T.code());
EXPECT_THAT(findDefinitions(AST, T.point()),
ElementsAre(RangeIs(T.range())))
<< Test;
}
}
TEST(GoToDefinition, RelPathsInCompileCommand) {
Annotations SourceAnnotations(R"cpp(
int [[foo]];
int baz = f^oo;
)cpp");
IgnoreDiagnostics DiagConsumer;
MockCompilationDatabase CDB(/*UseRelPaths=*/true);
MockFSProvider FS;
ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
auto FooCpp = testPath("foo.cpp");
FS.Files[FooCpp] = "";
Server.addDocument(FooCpp, SourceAnnotations.code());
runAddDocument(Server, FooCpp, SourceAnnotations.code());
auto Locations =
runFindDefinitions(Server, FooCpp, SourceAnnotations.point());
EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(*Locations, ElementsAre(Location{URIForFile{FooCpp},
SourceAnnotations.range()}));
}
TEST(Hover, All) {
struct OneTest {
StringRef Input;
StringRef ExpectedHover;
};
OneTest Tests[] = {
{
R"cpp(// Local variable
int main() {
int bonjour;
^bonjour = 2;
int test1 = bonjour;
}
)cpp",
"Declared in function main\n\nint bonjour",
},
{
R"cpp(// Local variable in method
struct s {
void method() {
int bonjour;
^bonjour = 2;
}
};
)cpp",
"Declared in function s::method\n\nint bonjour",
},
{
R"cpp(// Struct
namespace ns1 {
struct MyClass {};
} // namespace ns1
int main() {
ns1::My^Class* Params;
}
)cpp",
"Declared in namespace ns1\n\nstruct MyClass {}",
},
{
R"cpp(// Class
namespace ns1 {
class MyClass {};
} // namespace ns1
int main() {
ns1::My^Class* Params;
}
)cpp",
"Declared in namespace ns1\n\nclass MyClass {}",
},
{
R"cpp(// Union
namespace ns1 {
union MyUnion { int x; int y; };
} // namespace ns1
int main() {
ns1::My^Union Params;
}
)cpp",
"Declared in namespace ns1\n\nunion MyUnion {}",
},
{
R"cpp(// Function definition via pointer
int foo(int) {}
int main() {
auto *X = &^foo;
}
)cpp",
"Declared in global namespace\n\nint foo(int)",
},
{
R"cpp(// Function declaration via call
int foo(int);
int main() {
return ^foo(42);
}
)cpp",
"Declared in global namespace\n\nint foo(int)",
},
{
R"cpp(// Field
struct Foo { int x; };
int main() {
Foo bar;
bar.^x;
}
)cpp",
"Declared in struct Foo\n\nint x",
},
{
R"cpp(// Field with initialization
struct Foo { int x = 5; };
int main() {
Foo bar;
bar.^x;
}
)cpp",
"Declared in struct Foo\n\nint x = 5",
},
{
R"cpp(// Static field
struct Foo { static int x; };
int main() {
Foo::^x;
}
)cpp",
"Declared in struct Foo\n\nstatic int x",
},
{
R"cpp(// Field, member initializer
struct Foo {
int x;
Foo() : ^x(0) {}
};
)cpp",
"Declared in struct Foo\n\nint x",
},
{
R"cpp(// Field, GNU old-style field designator
struct Foo { int x; };
int main() {
Foo bar = { ^x : 1 };
}
)cpp",
"Declared in struct Foo\n\nint x",
},
{
R"cpp(// Field, field designator
struct Foo { int x; };
int main() {
Foo bar = { .^x = 2 };
}
)cpp",
"Declared in struct Foo\n\nint x",
},
{
R"cpp(// Method call
struct Foo { int x(); };
int main() {
Foo bar;
bar.^x();
}
)cpp",
"Declared in struct Foo\n\nint x()",
},
{
R"cpp(// Static method call
struct Foo { static int x(); };
int main() {
Foo::^x();
}
)cpp",
"Declared in struct Foo\n\nstatic int x()",
},
{
R"cpp(// Typedef
typedef int Foo;
int main() {
^Foo bar;
}
)cpp",
"Declared in global namespace\n\ntypedef int Foo",
},
{
R"cpp(// Namespace
namespace ns {
struct Foo { static void bar(); }
} // namespace ns
int main() { ^ns::Foo::bar(); }
)cpp",
"Declared in global namespace\n\nnamespace ns {\n}",
},
{
R"cpp(// Anonymous namespace
namespace ns {
namespace {
int foo;
} // anonymous namespace
} // namespace ns
int main() { ns::f^oo++; }
)cpp",
"Declared in namespace ns::(anonymous)\n\nint foo",
},
{
R"cpp(// Macro
#define MACRO 0
#define MACRO 1
int main() { return ^MACRO; }
#define MACRO 2
#undef macro
)cpp",
"#define MACRO",
},
{
R"cpp(// Forward class declaration
class Foo;
class Foo {};
F^oo* foo();
)cpp",
"Declared in global namespace\n\nclass Foo {}",
},
{
R"cpp(// Function declaration
void foo();
void g() { f^oo(); }
void foo() {}
)cpp",
"Declared in global namespace\n\nvoid foo()",
},
{
R"cpp(// Enum declaration
enum Hello {
ONE, TWO, THREE,
};
void foo() {
Hel^lo hello = ONE;
}
)cpp",
"Declared in global namespace\n\nenum Hello {\n}",
},
{
R"cpp(// Enumerator
enum Hello {
ONE, TWO, THREE,
};
void foo() {
Hello hello = O^NE;
}
)cpp",
"Declared in enum Hello\n\nONE",
},
{
R"cpp(// Enumerator in anonymous enum
enum {
ONE, TWO, THREE,
};
void foo() {
int hello = O^NE;
}
)cpp",
"Declared in enum (anonymous)\n\nONE",
},
{
R"cpp(// Global variable
static int hey = 10;
void foo() {
he^y++;
}
)cpp",
"Declared in global namespace\n\nstatic int hey = 10",
},
{
R"cpp(// Global variable in namespace
namespace ns1 {
static int hey = 10;
}
void foo() {
ns1::he^y++;
}
)cpp",
"Declared in namespace ns1\n\nstatic int hey = 10",
},
{
R"cpp(// Field in anonymous struct
static struct {
int hello;
} s;
void foo() {
s.he^llo++;
}
)cpp",
"Declared in struct (anonymous)\n\nint hello",
},
{
R"cpp(// Templated function
template <typename T>
T foo() {
return 17;
}
void g() { auto x = f^oo<int>(); }
)cpp",
"Declared in global namespace\n\ntemplate <typename T> T foo()",
},
{
R"cpp(// Anonymous union
struct outer {
union {
int abc, def;
} v;
};
void g() { struct outer o; o.v.d^ef++; }
)cpp",
"Declared in union outer::(anonymous)\n\nint def",
},
};
for (const OneTest &Test : Tests) {
Annotations T(Test.Input);
auto AST = build(T.code());
Hover H = getHover(AST, T.point());
EXPECT_EQ(H.contents.value, Test.ExpectedHover) << Test.Input;
}
}
TEST(GoToInclude, All) {
MockFSProvider FS;
IgnoreDiagnostics DiagConsumer;
MockCompilationDatabase CDB;
ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
auto FooCpp = testPath("foo.cpp");
const char *SourceContents = R"cpp(
#include ^"$2^foo.h$3^"
#include "$4^invalid.h"
int b = a;
// test
int foo;
#in$5^clude "$6^foo.h"$7^
)cpp";
Annotations SourceAnnotations(SourceContents);
FS.Files[FooCpp] = SourceAnnotations.code();
auto FooH = testPath("foo.h");
auto FooHUri = URIForFile{FooH};
const char *HeaderContents = R"cpp([[]]#pragma once
int a;
)cpp";
Annotations HeaderAnnotations(HeaderContents);
FS.Files[FooH] = HeaderAnnotations.code();
Server.addDocument(FooH, HeaderAnnotations.code());
Server.addDocument(FooCpp, SourceAnnotations.code());
// Test include in preamble.
auto Locations =
runFindDefinitions(Server, FooCpp, SourceAnnotations.point());
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(*Locations,
ElementsAre(Location{FooHUri, HeaderAnnotations.range()}));
// Test include in preamble, last char.
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("2"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(*Locations,
ElementsAre(Location{FooHUri, HeaderAnnotations.range()}));
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("3"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(*Locations,
ElementsAre(Location{FooHUri, HeaderAnnotations.range()}));
// Test include outside of preamble.
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("6"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(*Locations,
ElementsAre(Location{FooHUri, HeaderAnnotations.range()}));
// Test a few positions that do not result in Locations.
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("4"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(*Locations, IsEmpty());
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("5"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(*Locations, IsEmpty());
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("7"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(*Locations, IsEmpty());
}
} // namespace
} // namespace clangd
} // namespace clang