| //===-- 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 "TestTU.h" |
| #include "XRefs.h" |
| #include "index/FileIndex.h" |
| #include "index/SymbolCollector.h" |
| #include "clang/Index/IndexingAction.h" |
| #include "llvm/Support/Path.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| |
| namespace clang { |
| namespace clangd { |
| 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 {} |
| }; |
| |
| MATCHER_P2(FileRange, File, Range, "") { |
| return Location{URIForFile::canonicalize(File, testRoot()), Range} == arg; |
| } |
| |
| // 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 = TestTU::withCode(T.code()).build(); |
| EXPECT_THAT(findDocumentHighlights(AST, T.point()), HighlightsFrom(T)) |
| << Test; |
| } |
| } |
| |
| MATCHER_P(RangeIs, R, "") { return arg.range == R; } |
| |
| TEST(GoToDefinition, WithIndex) { |
| Annotations SymbolHeader(R"cpp( |
| class $forward[[Forward]]; |
| class $foo[[Foo]] {}; |
| |
| void $f1[[f1]](); |
| |
| inline void $f2[[f2]]() {} |
| )cpp"); |
| Annotations SymbolCpp(R"cpp( |
| class $forward[[forward]] {}; |
| void $f1[[f1]]() {} |
| )cpp"); |
| |
| TestTU TU; |
| TU.Code = SymbolCpp.code(); |
| TU.HeaderCode = SymbolHeader.code(); |
| auto Index = TU.index(); |
| auto runFindDefinitionsWithIndex = [&Index](const Annotations &Main) { |
| auto AST = TestTU::withCode(Main.code()).build(); |
| return clangd::findDefinitions(AST, Main.point(), Index.get()); |
| }; |
| |
| Annotations Test(R"cpp(// only declaration in AST. |
| void [[f1]](); |
| int main() { |
| ^f1(); |
| } |
| )cpp"); |
| EXPECT_THAT(runFindDefinitionsWithIndex(Test), |
| testing::ElementsAreArray( |
| {RangeIs(SymbolCpp.range("f1")), RangeIs(Test.range())})); |
| |
| Test = Annotations(R"cpp(// definition in AST. |
| void [[f1]]() {} |
| int main() { |
| ^f1(); |
| } |
| )cpp"); |
| EXPECT_THAT(runFindDefinitionsWithIndex(Test), |
| testing::ElementsAreArray( |
| {RangeIs(Test.range()), RangeIs(SymbolHeader.range("f1"))})); |
| |
| Test = Annotations(R"cpp(// forward declaration in AST. |
| class [[Foo]]; |
| F^oo* create(); |
| )cpp"); |
| EXPECT_THAT(runFindDefinitionsWithIndex(Test), |
| testing::ElementsAreArray( |
| {RangeIs(SymbolHeader.range("foo")), RangeIs(Test.range())})); |
| |
| Test = Annotations(R"cpp(// defintion in AST. |
| class [[Forward]] {}; |
| F^orward create(); |
| )cpp"); |
| EXPECT_THAT(runFindDefinitionsWithIndex(Test), |
| testing::ElementsAreArray({ |
| RangeIs(Test.range()), |
| RangeIs(SymbolHeader.range("forward")), |
| })); |
| } |
| |
| 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(// Macro argument |
| int [[i]]; |
| #define ADDRESSOF(X) &X; |
| int *j = ADDRESSOF(^i); |
| )cpp", |
| |
| R"cpp(// Symbol concatenated inside macro (not supported) |
| int *pi; |
| #define POINTER(X) p # X; |
| int i = *POINTER(^i); |
| )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 = TestTU::withCode(T.code()).build(); |
| std::vector<Matcher<Location>> ExpectedLocations; |
| for (const auto &R : T.ranges()) |
| ExpectedLocations.push_back(RangeIs(R)); |
| EXPECT_THAT(findDefinitions(AST, T.point()), |
| ElementsAreArray(ExpectedLocations)) |
| << Test; |
| } |
| } |
| |
| TEST(GoToDefinition, Rank) { |
| auto T = Annotations(R"cpp( |
| struct $foo1[[Foo]] { |
| $foo2[[Foo]](); |
| $foo3[[Foo]](Foo&&); |
| $foo4[[Foo]](const char*); |
| }; |
| |
| Foo $f[[f]](); |
| |
| void $g[[g]](Foo foo); |
| |
| void call() { |
| const char* $str[[str]] = "123"; |
| Foo a = $1^str; |
| Foo b = Foo($2^str); |
| Foo c = $3^f(); |
| $4^g($5^f()); |
| g($6^str); |
| } |
| )cpp"); |
| auto AST = TestTU::withCode(T.code()).build(); |
| EXPECT_THAT(findDefinitions(AST, T.point("1")), |
| ElementsAre(RangeIs(T.range("str")), RangeIs(T.range("foo4")))); |
| EXPECT_THAT(findDefinitions(AST, T.point("2")), |
| ElementsAre(RangeIs(T.range("str")))); |
| EXPECT_THAT(findDefinitions(AST, T.point("3")), |
| ElementsAre(RangeIs(T.range("f")), RangeIs(T.range("foo3")))); |
| EXPECT_THAT(findDefinitions(AST, T.point("4")), |
| ElementsAre(RangeIs(T.range("g")))); |
| EXPECT_THAT(findDefinitions(AST, T.point("5")), |
| ElementsAre(RangeIs(T.range("f")), RangeIs(T.range("foo3")))); |
| |
| auto DefinitionAtPoint6 = findDefinitions(AST, T.point("6")); |
| EXPECT_EQ(3ul, DefinitionAtPoint6.size()); |
| EXPECT_THAT(DefinitionAtPoint6, HasSubsequence(RangeIs(T.range("str")), |
| RangeIs(T.range("foo4")))); |
| EXPECT_THAT(DefinitionAtPoint6, HasSubsequence(RangeIs(T.range("str")), |
| RangeIs(T.range("foo3")))); |
| } |
| |
| TEST(GoToDefinition, RelPathsInCompileCommand) { |
| // The source is in "/clangd-test/src". |
| // We build in "/clangd-test/build". |
| |
| Annotations SourceAnnotations(R"cpp( |
| #include "header_in_preamble.h" |
| int [[foo]]; |
| #include "header_not_in_preamble.h" |
| int baz = f$p1^oo + bar_pre$p2^amble + bar_not_pre$p3^amble; |
| )cpp"); |
| |
| Annotations HeaderInPreambleAnnotations(R"cpp( |
| int [[bar_preamble]]; |
| )cpp"); |
| |
| Annotations HeaderNotInPreambleAnnotations(R"cpp( |
| int [[bar_not_preamble]]; |
| )cpp"); |
| |
| // Make the compilation paths appear as ../src/foo.cpp in the compile |
| // commands. |
| SmallString<32> RelPathPrefix(".."); |
| llvm::sys::path::append(RelPathPrefix, "src"); |
| std::string BuildDir = testPath("build"); |
| MockCompilationDatabase CDB(BuildDir, RelPathPrefix); |
| |
| IgnoreDiagnostics DiagConsumer; |
| MockFSProvider FS; |
| ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); |
| |
| // Fill the filesystem. |
| auto FooCpp = testPath("src/foo.cpp"); |
| FS.Files[FooCpp] = ""; |
| auto HeaderInPreambleH = testPath("src/header_in_preamble.h"); |
| FS.Files[HeaderInPreambleH] = HeaderInPreambleAnnotations.code(); |
| auto HeaderNotInPreambleH = testPath("src/header_not_in_preamble.h"); |
| FS.Files[HeaderNotInPreambleH] = HeaderNotInPreambleAnnotations.code(); |
| |
| runAddDocument(Server, FooCpp, SourceAnnotations.code()); |
| |
| // Go to a definition in main source file. |
| auto Locations = |
| runFindDefinitions(Server, FooCpp, SourceAnnotations.point("p1")); |
| EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error"; |
| EXPECT_THAT(*Locations, |
| ElementsAre(FileRange(FooCpp, SourceAnnotations.range()))); |
| |
| // Go to a definition in header_in_preamble.h. |
| Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("p2")); |
| EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error"; |
| EXPECT_THAT(*Locations, |
| ElementsAre(FileRange(HeaderInPreambleH, |
| HeaderInPreambleAnnotations.range()))); |
| |
| // Go to a definition in header_not_in_preamble.h. |
| Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("p3")); |
| EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error"; |
| EXPECT_THAT(*Locations, |
| ElementsAre(FileRange(HeaderNotInPreambleH, |
| HeaderNotInPreambleAnnotations.range()))); |
| } |
| |
| TEST(Hover, All) { |
| struct OneTest { |
| StringRef Input; |
| StringRef ExpectedHover; |
| }; |
| |
| OneTest Tests[] = { |
| { |
| R"cpp(// No hover |
| ^int main() { |
| } |
| )cpp", |
| "", |
| }, |
| { |
| 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", |
| }, |
| { |
| R"cpp(// Nothing |
| void foo() { |
| ^ |
| } |
| )cpp", |
| "", |
| }, |
| { |
| R"cpp(// Simple initialization with auto |
| void foo() { |
| ^auto i = 1; |
| } |
| )cpp", |
| "int", |
| }, |
| { |
| R"cpp(// Simple initialization with const auto |
| void foo() { |
| const ^auto i = 1; |
| } |
| )cpp", |
| "int", |
| }, |
| { |
| R"cpp(// Simple initialization with const auto& |
| void foo() { |
| const ^auto& i = 1; |
| } |
| )cpp", |
| "int", |
| }, |
| { |
| R"cpp(// Simple initialization with auto& |
| void foo() { |
| ^auto& i = 1; |
| } |
| )cpp", |
| "int", |
| }, |
| { |
| R"cpp(// Simple initialization with auto* |
| void foo() { |
| int a = 1; |
| ^auto* i = &a; |
| } |
| )cpp", |
| "int", |
| }, |
| { |
| R"cpp(// Auto with initializer list. |
| namespace std |
| { |
| template<class _E> |
| class initializer_list {}; |
| } |
| void foo() { |
| ^auto i = {1,2}; |
| } |
| )cpp", |
| "class std::initializer_list<int>", |
| }, |
| { |
| R"cpp(// User defined conversion to auto |
| struct Bar { |
| operator ^auto() const { return 10; } |
| }; |
| )cpp", |
| "int", |
| }, |
| { |
| R"cpp(// Simple initialization with decltype(auto) |
| void foo() { |
| ^decltype(auto) i = 1; |
| } |
| )cpp", |
| "int", |
| }, |
| { |
| R"cpp(// Simple initialization with const decltype(auto) |
| void foo() { |
| const int j = 0; |
| ^decltype(auto) i = j; |
| } |
| )cpp", |
| "const int", |
| }, |
| { |
| R"cpp(// Simple initialization with const& decltype(auto) |
| void foo() { |
| int k = 0; |
| const int& j = k; |
| ^decltype(auto) i = j; |
| } |
| )cpp", |
| "const int &", |
| }, |
| { |
| R"cpp(// Simple initialization with & decltype(auto) |
| void foo() { |
| int k = 0; |
| int& j = k; |
| ^decltype(auto) i = j; |
| } |
| )cpp", |
| "int &", |
| }, |
| { |
| R"cpp(// decltype with initializer list: nothing |
| namespace std |
| { |
| template<class _E> |
| class initializer_list {}; |
| } |
| void foo() { |
| ^decltype(auto) i = {1,2}; |
| } |
| )cpp", |
| "", |
| }, |
| { |
| R"cpp(// simple trailing return type |
| ^auto main() -> int { |
| return 0; |
| } |
| )cpp", |
| "int", |
| }, |
| { |
| R"cpp(// auto function return with trailing type |
| struct Bar {}; |
| ^auto test() -> decltype(Bar()) { |
| return Bar(); |
| } |
| )cpp", |
| "struct Bar", |
| }, |
| { |
| R"cpp(// trailing return type |
| struct Bar {}; |
| auto test() -> ^decltype(Bar()) { |
| return Bar(); |
| } |
| )cpp", |
| "struct Bar", |
| }, |
| { |
| R"cpp(// auto in function return |
| struct Bar {}; |
| ^auto test() { |
| return Bar(); |
| } |
| )cpp", |
| "struct Bar", |
| }, |
| { |
| R"cpp(// auto& in function return |
| struct Bar {}; |
| ^auto& test() { |
| return Bar(); |
| } |
| )cpp", |
| "struct Bar", |
| }, |
| { |
| R"cpp(// auto* in function return |
| struct Bar {}; |
| ^auto* test() { |
| Bar* bar; |
| return bar; |
| } |
| )cpp", |
| "struct Bar", |
| }, |
| { |
| R"cpp(// const auto& in function return |
| struct Bar {}; |
| const ^auto& test() { |
| return Bar(); |
| } |
| )cpp", |
| "struct Bar", |
| }, |
| { |
| R"cpp(// decltype(auto) in function return |
| struct Bar {}; |
| ^decltype(auto) test() { |
| return Bar(); |
| } |
| )cpp", |
| "struct Bar", |
| }, |
| { |
| R"cpp(// decltype(auto) reference in function return |
| struct Bar {}; |
| ^decltype(auto) test() { |
| int a; |
| return (a); |
| } |
| )cpp", |
| "int &", |
| }, |
| { |
| R"cpp(// decltype lvalue reference |
| void foo() { |
| int I = 0; |
| ^decltype(I) J = I; |
| } |
| )cpp", |
| "int", |
| }, |
| { |
| R"cpp(// decltype lvalue reference |
| void foo() { |
| int I= 0; |
| int &K = I; |
| ^decltype(K) J = I; |
| } |
| )cpp", |
| "int &", |
| }, |
| { |
| R"cpp(// decltype lvalue reference parenthesis |
| void foo() { |
| int I = 0; |
| ^decltype((I)) J = I; |
| } |
| )cpp", |
| "int &", |
| }, |
| { |
| R"cpp(// decltype rvalue reference |
| void foo() { |
| int I = 0; |
| ^decltype(static_cast<int&&>(I)) J = static_cast<int&&>(I); |
| } |
| )cpp", |
| "int &&", |
| }, |
| { |
| R"cpp(// decltype rvalue reference function call |
| int && bar(); |
| void foo() { |
| int I = 0; |
| ^decltype(bar()) J = bar(); |
| } |
| )cpp", |
| "int &&", |
| }, |
| { |
| R"cpp(// decltype of function with trailing return type. |
| struct Bar {}; |
| auto test() -> decltype(Bar()) { |
| return Bar(); |
| } |
| void foo() { |
| ^decltype(test()) i = test(); |
| } |
| )cpp", |
| "struct Bar", |
| }, |
| { |
| R"cpp(// decltype of var with decltype. |
| void foo() { |
| int I = 0; |
| decltype(I) J = I; |
| ^decltype(J) K = J; |
| } |
| )cpp", |
| "int", |
| }, |
| { |
| R"cpp(// structured binding. Not supported yet |
| struct Bar {}; |
| void foo() { |
| Bar a[2]; |
| ^auto [x,y] = a; |
| } |
| )cpp", |
| "", |
| }, |
| { |
| R"cpp(// Template auto parameter. Nothing (Not useful). |
| template<^auto T> |
| void func() { |
| } |
| void foo() { |
| func<1>(); |
| } |
| )cpp", |
| "", |
| }, |
| { |
| R"cpp(// More compilcated structured types. |
| int bar(); |
| ^auto (*foo)() = bar; |
| )cpp", |
| "int", |
| }, |
| }; |
| |
| for (const OneTest &Test : Tests) { |
| Annotations T(Test.Input); |
| TestTU TU = TestTU::withCode(T.code()); |
| TU.ExtraArgs.push_back("-std=c++17"); |
| auto AST = TU.build(); |
| if (auto H = getHover(AST, T.point())) { |
| EXPECT_NE("", Test.ExpectedHover) << Test.Input; |
| EXPECT_EQ(H->contents.value, Test.ExpectedHover.str()) << Test.Input; |
| } else |
| EXPECT_EQ("", Test.ExpectedHover.str()) << 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"); |
| |
| 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(FileRange(FooH, 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(FileRange(FooH, HeaderAnnotations.range()))); |
| |
| Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("3")); |
| ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error"; |
| EXPECT_THAT(*Locations, |
| ElementsAre(FileRange(FooH, HeaderAnnotations.range()))); |
| |
| // Test include outside of preamble. |
| Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("6")); |
| ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error"; |
| |
| // 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"; |
| |
| Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("7")); |
| ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error"; |
| } |
| |
| TEST(GoToDefinition, WithPreamble) { |
| // Test stragety: AST should always use the latest preamble instead of last |
| // good preamble. |
| MockFSProvider FS; |
| IgnoreDiagnostics DiagConsumer; |
| MockCompilationDatabase CDB; |
| ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); |
| |
| auto FooCpp = testPath("foo.cpp"); |
| // The trigger locations must be the same. |
| Annotations FooWithHeader(R"cpp(#include "fo^o.h")cpp"); |
| Annotations FooWithoutHeader(R"cpp(double [[fo^o]]();)cpp"); |
| |
| FS.Files[FooCpp] = FooWithHeader.code(); |
| |
| auto FooH = testPath("foo.h"); |
| Annotations FooHeader(R"cpp([[]])cpp"); |
| FS.Files[FooH] = FooHeader.code(); |
| |
| runAddDocument(Server, FooCpp, FooWithHeader.code()); |
| // GoToDefinition goes to a #include file: the result comes from the preamble. |
| EXPECT_THAT( |
| cantFail(runFindDefinitions(Server, FooCpp, FooWithHeader.point())), |
| ElementsAre(FileRange(FooH, FooHeader.range()))); |
| |
| // Only preamble is built, and no AST is built in this request. |
| Server.addDocument(FooCpp, FooWithoutHeader.code(), WantDiagnostics::No); |
| // We build AST here, and it should use the latest preamble rather than the |
| // stale one. |
| EXPECT_THAT( |
| cantFail(runFindDefinitions(Server, FooCpp, FooWithoutHeader.point())), |
| ElementsAre(FileRange(FooCpp, FooWithoutHeader.range()))); |
| |
| // Reset test environment. |
| runAddDocument(Server, FooCpp, FooWithHeader.code()); |
| // Both preamble and AST are built in this request. |
| Server.addDocument(FooCpp, FooWithoutHeader.code(), WantDiagnostics::Yes); |
| // Use the AST being built in above request. |
| EXPECT_THAT( |
| cantFail(runFindDefinitions(Server, FooCpp, FooWithoutHeader.point())), |
| ElementsAre(FileRange(FooCpp, FooWithoutHeader.range()))); |
| } |
| |
| TEST(FindReferences, WithinAST) { |
| const char *Tests[] = { |
| R"cpp(// Local variable |
| int main() { |
| int [[foo]]; |
| [[^foo]] = 2; |
| int test1 = [[foo]]; |
| } |
| )cpp", |
| |
| R"cpp(// Struct |
| namespace ns1 { |
| struct [[Foo]] {}; |
| } // namespace ns1 |
| int main() { |
| ns1::[[Fo^o]]* Params; |
| } |
| )cpp", |
| |
| R"cpp(// Forward declaration |
| class [[Foo]]; |
| class [[Foo]] {} |
| int main() { |
| [[Fo^o]] foo; |
| } |
| )cpp", |
| |
| R"cpp(// Function |
| int [[foo]](int) {} |
| int main() { |
| auto *X = &[[^foo]]; |
| [[foo]](42) |
| } |
| )cpp", |
| |
| R"cpp(// Field |
| struct Foo { |
| int [[foo]]; |
| Foo() : [[foo]](0) {} |
| }; |
| int main() { |
| Foo f; |
| f.[[f^oo]] = 1; |
| } |
| )cpp", |
| |
| R"cpp(// Method call |
| struct Foo { int [[foo]](); }; |
| int Foo::[[foo]]() {} |
| int main() { |
| Foo f; |
| f.[[^foo]](); |
| } |
| )cpp", |
| |
| R"cpp(// Typedef |
| typedef int [[Foo]]; |
| int main() { |
| [[^Foo]] bar; |
| } |
| )cpp", |
| |
| R"cpp(// Namespace |
| namespace [[ns]] { |
| struct Foo {}; |
| } // namespace ns |
| int main() { [[^ns]]::Foo foo; } |
| )cpp", |
| }; |
| for (const char *Test : Tests) { |
| Annotations T(Test); |
| auto AST = TestTU::withCode(T.code()).build(); |
| std::vector<Matcher<Location>> ExpectedLocations; |
| for (const auto &R : T.ranges()) |
| ExpectedLocations.push_back(RangeIs(R)); |
| EXPECT_THAT(findReferences(AST, T.point(), 0), |
| ElementsAreArray(ExpectedLocations)) |
| << Test; |
| } |
| } |
| |
| TEST(FindReferences, ExplicitSymbols) { |
| const char *Tests[] = { |
| R"cpp( |
| struct Foo { Foo* [self]() const; }; |
| void f() { |
| if (Foo* T = foo.[^self]()) {} // Foo member call expr. |
| } |
| )cpp", |
| |
| R"cpp( |
| struct Foo { Foo(int); }; |
| Foo f() { |
| int [b]; |
| return [^b]; // Foo constructor expr. |
| } |
| )cpp", |
| |
| R"cpp( |
| struct Foo {}; |
| void g(Foo); |
| Foo [f](); |
| void call() { |
| g([^f]()); // Foo constructor expr. |
| } |
| )cpp", |
| |
| R"cpp( |
| void [foo](int); |
| void [foo](double); |
| |
| namespace ns { |
| using ::[fo^o]; |
| } |
| )cpp", |
| }; |
| for (const char *Test : Tests) { |
| Annotations T(Test); |
| auto AST = TestTU::withCode(T.code()).build(); |
| std::vector<Matcher<Location>> ExpectedLocations; |
| for (const auto &R : T.ranges()) |
| ExpectedLocations.push_back(RangeIs(R)); |
| EXPECT_THAT(findReferences(AST, T.point(), 0), |
| ElementsAreArray(ExpectedLocations)) |
| << Test; |
| } |
| } |
| |
| TEST(FindReferences, NeedsIndex) { |
| const char *Header = "int foo();"; |
| Annotations Main("int main() { [[f^oo]](); }"); |
| TestTU TU; |
| TU.Code = Main.code(); |
| TU.HeaderCode = Header; |
| auto AST = TU.build(); |
| |
| // References in main file are returned without index. |
| EXPECT_THAT(findReferences(AST, Main.point(), 0, /*Index=*/nullptr), |
| ElementsAre(RangeIs(Main.range()))); |
| Annotations IndexedMain(R"cpp( |
| int main() { [[f^oo]](); } |
| )cpp"); |
| |
| // References from indexed files are included. |
| TestTU IndexedTU; |
| IndexedTU.Code = IndexedMain.code(); |
| IndexedTU.Filename = "Indexed.cpp"; |
| IndexedTU.HeaderCode = Header; |
| EXPECT_THAT(findReferences(AST, Main.point(), 0, IndexedTU.index().get()), |
| ElementsAre(RangeIs(Main.range()), RangeIs(IndexedMain.range()))); |
| |
| EXPECT_EQ(1u, findReferences(AST, Main.point(), /*Limit*/ 1, |
| IndexedTU.index().get()) |
| .size()); |
| |
| // If the main file is in the index, we don't return duplicates. |
| // (even if the references are in a different location) |
| TU.Code = ("\n\n" + Main.code()).str(); |
| EXPECT_THAT(findReferences(AST, Main.point(), 0, TU.index().get()), |
| ElementsAre(RangeIs(Main.range()))); |
| } |
| |
| TEST(FindReferences, NoQueryForLocalSymbols) { |
| struct RecordingIndex : public MemIndex { |
| mutable Optional<llvm::DenseSet<SymbolID>> RefIDs; |
| void refs(const RefsRequest &Req, |
| llvm::function_ref<void(const Ref &)>) const override { |
| RefIDs = Req.IDs; |
| } |
| }; |
| |
| struct Test { |
| StringRef AnnotatedCode; |
| bool WantQuery; |
| } Tests[] = { |
| {"int ^x;", true}, |
| // For now we don't assume header structure which would allow skipping. |
| {"namespace { int ^x; }", true}, |
| {"static int ^x;", true}, |
| // Anything in a function certainly can't be referenced though. |
| {"void foo() { int ^x; }", false}, |
| {"void foo() { struct ^x{}; }", false}, |
| {"auto lambda = []{ int ^x; };", false}, |
| }; |
| for (Test T : Tests) { |
| Annotations File(T.AnnotatedCode); |
| RecordingIndex Rec; |
| auto AST = TestTU::withCode(File.code()).build(); |
| findReferences(AST, File.point(), 0, &Rec); |
| if (T.WantQuery) |
| EXPECT_NE(Rec.RefIDs, None) << T.AnnotatedCode; |
| else |
| EXPECT_EQ(Rec.RefIDs, None) << T.AnnotatedCode; |
| } |
| } |
| |
| } // namespace |
| } // namespace clangd |
| } // namespace clang |