//===-- 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 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 &> HighlightsFrom(const Annotations &Test) { std::vector 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> 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 T foo() { return 17; } void g() { auto x = f^oo(); } )cpp", "Declared in global namespace\n\ntemplate 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 initializer_list {}; } void foo() { ^auto i = {1,2}; } )cpp", "class std::initializer_list", }, { 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 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(I)) J = static_cast(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"; EXPECT_THAT(*Locations, ElementsAre(FileRange(FooH, 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, ElementsAre(FileRange(FooH, HeaderAnnotations.range()))); Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("7")); ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error"; EXPECT_THAT(*Locations, ElementsAre(FileRange(FooH, HeaderAnnotations.range()))); } 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> 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> 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> RefIDs; void refs(const RefsRequest &Req, llvm::function_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