| //===-- XRefsTests.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 "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 "llvm/Support/ScopedPrinter.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| |
| namespace clang { |
| namespace clangd { |
| namespace { |
| |
| using testing::ElementsAre; |
| 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", |
| |
| R"cpp(// Function parameter in decl |
| void foo(int [[^bar]]); |
| )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_P3(Sym, Name, Decl, DefOrNone, "") { |
| llvm::Optional<Range> Def = DefOrNone; |
| if (Name != arg.Name) { |
| *result_listener << "Name is " << arg.Name; |
| return false; |
| } |
| if (Decl != arg.PreferredDeclaration.range) { |
| *result_listener << "Declaration is " |
| << llvm::to_string(arg.PreferredDeclaration); |
| return false; |
| } |
| if (Def && !arg.Definition) { |
| *result_listener << "Has no definition"; |
| return false; |
| } |
| if (Def && arg.Definition->range != *Def) { |
| *result_listener << "Definition is " << llvm::to_string(arg.Definition); |
| return false; |
| } |
| return true; |
| } |
| testing::Matcher<LocatedSymbol> Sym(std::string Name, Range Decl) { |
| return Sym(Name, Decl, llvm::None); |
| } |
| MATCHER_P(Sym, Name, "") { return arg.Name == Name; } |
| |
| MATCHER_P(RangeIs, R, "") { return arg.range == R; } |
| |
| TEST(LocateSymbol, 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 LocateWithIndex = [&Index](const Annotations &Main) { |
| auto AST = TestTU::withCode(Main.code()).build(); |
| return clangd::locateSymbolAt(AST, Main.point(), Index.get()); |
| }; |
| |
| Annotations Test(R"cpp(// only declaration in AST. |
| void [[f1]](); |
| int main() { |
| ^f1(); |
| } |
| )cpp"); |
| EXPECT_THAT(LocateWithIndex(Test), |
| ElementsAre(Sym("f1", Test.range(), SymbolCpp.range("f1")))); |
| |
| Test = Annotations(R"cpp(// definition in AST. |
| void [[f1]]() {} |
| int main() { |
| ^f1(); |
| } |
| )cpp"); |
| EXPECT_THAT(LocateWithIndex(Test), |
| ElementsAre(Sym("f1", SymbolHeader.range("f1"), Test.range()))); |
| |
| Test = Annotations(R"cpp(// forward declaration in AST. |
| class [[Foo]]; |
| F^oo* create(); |
| )cpp"); |
| EXPECT_THAT(LocateWithIndex(Test), |
| ElementsAre(Sym("Foo", Test.range(), SymbolHeader.range("foo")))); |
| |
| Test = Annotations(R"cpp(// defintion in AST. |
| class [[Forward]] {}; |
| F^orward create(); |
| )cpp"); |
| EXPECT_THAT( |
| LocateWithIndex(Test), |
| ElementsAre(Sym("Forward", SymbolHeader.range("forward"), Test.range()))); |
| } |
| |
| TEST(LocateSymbol, WithIndexPreferredLocation) { |
| Annotations SymbolHeader(R"cpp( |
| class $[[Proto]] {}; |
| )cpp"); |
| TestTU TU; |
| TU.HeaderCode = SymbolHeader.code(); |
| TU.HeaderFilename = "x.proto"; // Prefer locations in codegen files. |
| auto Index = TU.index(); |
| |
| Annotations Test(R"cpp(// only declaration in AST. |
| // Shift to make range different. |
| class [[Proto]]; |
| P^roto* create(); |
| )cpp"); |
| |
| auto AST = TestTU::withCode(Test.code()).build(); |
| auto Locs = clangd::locateSymbolAt(AST, Test.point(), Index.get()); |
| EXPECT_THAT(Locs, ElementsAre(Sym("Proto", SymbolHeader.range()))); |
| } |
| |
| TEST(LocateSymbol, All) { |
| // Ranges in tests: |
| // $decl is the declaration location (if absent, no symbol is located) |
| // $def is the definition location (if absent, symbol has no definition) |
| // unnamed range becomes both $decl and $def. |
| 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 $decl[[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 $decl[[x]](); }; |
| int main() { |
| Foo bar; |
| bar.^x(); |
| } |
| )cpp", |
| |
| R"cpp(// Typedef |
| typedef int $decl[[Foo]]; |
| int main() { |
| ^Foo bar; |
| } |
| )cpp", |
| |
| R"cpp(// Template type parameter |
| template <typename [[T]]> |
| void foo() { ^T t; } |
| )cpp", |
| |
| R"cpp(// Template template type parameter |
| template <template<typename> class [[T]]> |
| void foo() { ^T<int> t; } |
| )cpp", |
| |
| R"cpp(// Namespace |
| namespace $decl[[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", |
| |
| R"cpp(// explicit template specialization |
| template <typename T> |
| struct Foo { void bar() {} }; |
| |
| template <> |
| struct [[Foo]]<int> { void bar() {} }; |
| |
| void foo() { |
| Foo<char> abc; |
| Fo^o<int> b; |
| } |
| )cpp", |
| |
| R"cpp(// implicit template specialization |
| template <typename T> |
| struct [[Foo]] { void bar() {} }; |
| template <> |
| struct Foo<int> { void bar() {} }; |
| void foo() { |
| Fo^o<char> abc; |
| Foo<int> b; |
| } |
| )cpp", |
| |
| R"cpp(// partial template specialization |
| template <typename T> |
| struct Foo { void bar() {} }; |
| template <typename T> |
| struct [[Foo]]<T*> { void bar() {} }; |
| ^Foo<int*> x; |
| )cpp", |
| |
| R"cpp(// function template specializations |
| template <class T> |
| void foo(T) {} |
| template <> |
| void [[foo]](int) {} |
| void bar() { |
| fo^o(10); |
| } |
| )cpp", |
| |
| R"cpp(// variable template decls |
| template <class T> |
| T var = T(); |
| |
| template <> |
| double [[var]]<int> = 10; |
| |
| double y = va^r<int>; |
| )cpp", |
| |
| R"cpp(// No implicit constructors |
| class X { |
| X(X&& x) = default; |
| }; |
| X [[makeX]]() {} |
| void foo() { |
| auto x = m^akeX(); |
| } |
| )cpp", |
| }; |
| for (const char *Test : Tests) { |
| Annotations T(Test); |
| llvm::Optional<Range> WantDecl; |
| llvm::Optional<Range> WantDef; |
| if (!T.ranges().empty()) |
| WantDecl = WantDef = T.range(); |
| if (!T.ranges("decl").empty()) |
| WantDecl = T.range("decl"); |
| if (!T.ranges("def").empty()) |
| WantDef = T.range("def"); |
| |
| auto AST = TestTU::withCode(T.code()).build(); |
| auto Results = locateSymbolAt(AST, T.point()); |
| |
| if (!WantDecl) { |
| EXPECT_THAT(Results, IsEmpty()) << Test; |
| } else { |
| ASSERT_THAT(Results, ::testing::SizeIs(1)) << Test; |
| EXPECT_EQ(Results[0].PreferredDeclaration.range, *WantDecl) << Test; |
| llvm::Optional<Range> GotDef; |
| if (Results[0].Definition) |
| GotDef = Results[0].Definition->range; |
| EXPECT_EQ(WantDef, GotDef) << Test; |
| } |
| } |
| } |
| |
| TEST(LocateSymbol, Ambiguous) { |
| auto T = Annotations(R"cpp( |
| struct Foo { |
| Foo(); |
| Foo(Foo&&); |
| Foo(const char*); |
| }; |
| |
| Foo f(); |
| |
| void g(Foo foo); |
| |
| void call() { |
| const char* str = "123"; |
| Foo a = $1^str; |
| Foo b = Foo($2^str); |
| Foo c = $3^f(); |
| $4^g($5^f()); |
| g($6^str); |
| Foo ab$7^c; |
| Foo ab$8^cd("asdf"); |
| Foo foox = Fo$9^o("asdf"); |
| } |
| )cpp"); |
| auto AST = TestTU::withCode(T.code()).build(); |
| // Ordered assertions are deliberate: we expect a predictable order. |
| EXPECT_THAT(locateSymbolAt(AST, T.point("1")), ElementsAre(Sym("str"))); |
| EXPECT_THAT(locateSymbolAt(AST, T.point("2")), ElementsAre(Sym("str"))); |
| EXPECT_THAT(locateSymbolAt(AST, T.point("3")), ElementsAre(Sym("f"))); |
| EXPECT_THAT(locateSymbolAt(AST, T.point("4")), ElementsAre(Sym("g"))); |
| EXPECT_THAT(locateSymbolAt(AST, T.point("5")), ElementsAre(Sym("f"))); |
| EXPECT_THAT(locateSymbolAt(AST, T.point("6")), ElementsAre(Sym("str"))); |
| EXPECT_THAT(locateSymbolAt(AST, T.point("7")), ElementsAre(Sym("abc"))); |
| EXPECT_THAT(locateSymbolAt(AST, T.point("8")), |
| ElementsAre(Sym("Foo"), Sym("abcd"))); |
| EXPECT_THAT(locateSymbolAt(AST, T.point("9")), |
| // First one is class definition, second is the constructor. |
| ElementsAre(Sym("Foo"), Sym("Foo"))); |
| } |
| |
| TEST(LocateSymbol, 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 = |
| runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("p1")); |
| EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error"; |
| EXPECT_THAT(*Locations, ElementsAre(Sym("foo", SourceAnnotations.range()))); |
| |
| // Go to a definition in header_in_preamble.h. |
| Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("p2")); |
| EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error"; |
| EXPECT_THAT( |
| *Locations, |
| ElementsAre(Sym("bar_preamble", HeaderInPreambleAnnotations.range()))); |
| |
| // Go to a definition in header_not_in_preamble.h. |
| Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("p3")); |
| EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error"; |
| EXPECT_THAT(*Locations, |
| ElementsAre(Sym("bar_not_preamble", |
| 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 1", |
| }, |
| { |
| R"cpp(// Macro |
| #define MACRO 0 |
| #define MACRO2 ^MACRO |
| )cpp", |
| "#define MACRO 0", |
| }, |
| { |
| R"cpp(// Macro |
| #define MACRO {\ |
| return 0;\ |
| } |
| int main() ^MACRO |
| )cpp", |
| R"cpp(#define MACRO {\ |
| return 0;\ |
| })cpp", |
| }, |
| { |
| 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 = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point()); |
| ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; |
| EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); |
| |
| // Test include in preamble, last char. |
| Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("2")); |
| ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; |
| EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); |
| |
| Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("3")); |
| ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; |
| EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); |
| |
| // Test include outside of preamble. |
| Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("6")); |
| ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; |
| EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); |
| |
| // Test a few positions that do not result in Locations. |
| Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("4")); |
| ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; |
| EXPECT_THAT(*Locations, IsEmpty()); |
| |
| Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("5")); |
| ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; |
| EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); |
| |
| Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("7")); |
| ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; |
| EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); |
| |
| // Objective C #import directive. |
| Annotations ObjC(R"objc( |
| #import "^foo.h" |
| )objc"); |
| auto FooM = testPath("foo.m"); |
| FS.Files[FooM] = ObjC.code(); |
| |
| Server.addDocument(FooM, ObjC.code()); |
| Locations = runLocateSymbolAt(Server, FooM, ObjC.point()); |
| ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; |
| EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); |
| } |
| |
| TEST(LocateSymbol, 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()); |
| // LocateSymbol goes to a #include file: the result comes from the preamble. |
| EXPECT_THAT( |
| cantFail(runLocateSymbolAt(Server, FooCpp, FooWithHeader.point())), |
| ElementsAre(Sym("foo.h", 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(runLocateSymbolAt(Server, FooCpp, FooWithoutHeader.point())), |
| ElementsAre(Sym("foo", 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(runLocateSymbolAt(Server, FooCpp, FooWithoutHeader.point())), |
| ElementsAre(Sym("foo", 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(// Constructor |
| struct Foo { |
| [[F^oo]](int); |
| }; |
| void foo() { |
| Foo f = [[Foo]](42); |
| } |
| )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 |