734 lines · cpp
1//===-- CallHierarchyTests.cpp ---------------------------*- C++ -*-------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8#include "Annotations.h"9#include "ParsedAST.h"10#include "TestFS.h"11#include "TestTU.h"12#include "TestWorkspace.h"13#include "XRefs.h"14#include "llvm/Support/Path.h"15#include "gmock/gmock.h"16#include "gtest/gtest.h"17 18namespace clang {19namespace clangd {20 21llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,22 const CallHierarchyItem &Item) {23 return Stream << Item.name << "@" << Item.selectionRange;24}25 26llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,27 const CallHierarchyIncomingCall &Call) {28 Stream << "{ from: " << Call.from << ", ranges: [";29 for (const auto &R : Call.fromRanges) {30 Stream << R;31 Stream << ", ";32 }33 return Stream << "] }";34}35 36namespace {37 38using ::testing::AllOf;39using ::testing::ElementsAre;40using ::testing::Field;41using ::testing::IsEmpty;42using ::testing::Matcher;43using ::testing::UnorderedElementsAre;44 45// Helpers for matching call hierarchy data structures.46MATCHER_P(withName, N, "") { return arg.name == N; }47MATCHER_P(withDetail, N, "") { return arg.detail == N; }48MATCHER_P(withFile, N, "") { return arg.uri.file() == N; }49MATCHER_P(withSelectionRange, R, "") { return arg.selectionRange == R; }50 51template <class ItemMatcher>52::testing::Matcher<CallHierarchyIncomingCall> from(ItemMatcher M) {53 return Field(&CallHierarchyIncomingCall::from, M);54}55template <class ItemMatcher>56::testing::Matcher<CallHierarchyOutgoingCall> to(ItemMatcher M) {57 return Field(&CallHierarchyOutgoingCall::to, M);58}59template <class... RangeMatchers>60::testing::Matcher<CallHierarchyIncomingCall> iFromRanges(RangeMatchers... M) {61 return Field(&CallHierarchyIncomingCall::fromRanges,62 UnorderedElementsAre(M...));63}64template <class... RangeMatchers>65::testing::Matcher<CallHierarchyOutgoingCall> oFromRanges(RangeMatchers... M) {66 return Field(&CallHierarchyOutgoingCall::fromRanges,67 UnorderedElementsAre(M...));68}69 70TEST(CallHierarchy, IncomingOneFileCpp) {71 Annotations Source(R"cpp(72 void call^ee(int);73 void caller1() {74 $Callee[[callee]](42);75 }76 void caller2() {77 $Caller1A[[caller1]]();78 $Caller1B[[caller1]]();79 }80 void caller3() {81 $Caller1C[[caller1]]();82 $Caller2[[caller2]]();83 }84 )cpp");85 TestTU TU = TestTU::withCode(Source.code());86 auto AST = TU.build();87 auto Index = TU.index();88 89 std::vector<CallHierarchyItem> Items =90 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));91 ASSERT_THAT(Items, ElementsAre(withName("callee")));92 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());93 ASSERT_THAT(94 IncomingLevel1,95 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("caller1"))),96 iFromRanges(Source.range("Callee")))));97 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());98 ASSERT_THAT(99 IncomingLevel2,100 ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail("caller2"))),101 iFromRanges(Source.range("Caller1A"),102 Source.range("Caller1B"))),103 AllOf(from(AllOf(withName("caller3"), withDetail("caller3"))),104 iFromRanges(Source.range("Caller1C")))));105 106 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());107 ASSERT_THAT(108 IncomingLevel3,109 ElementsAre(AllOf(from(AllOf(withName("caller3"), withDetail("caller3"))),110 iFromRanges(Source.range("Caller2")))));111 112 auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());113 EXPECT_THAT(IncomingLevel4, IsEmpty());114}115 116TEST(CallHierarchy, IncomingOneFileObjC) {117 Annotations Source(R"objc(118 @implementation MyClass {}119 +(void)call^ee {}120 +(void) caller1 {121 [MyClass $Callee[[callee]]];122 }123 +(void) caller2 {124 [MyClass $Caller1A[[caller1]]];125 [MyClass $Caller1B[[caller1]]];126 }127 +(void) caller3 {128 [MyClass $Caller1C[[caller1]]];129 [MyClass $Caller2[[caller2]]];130 }131 @end132 )objc");133 TestTU TU = TestTU::withCode(Source.code());134 TU.Filename = "TestTU.m";135 auto AST = TU.build();136 auto Index = TU.index();137 std::vector<CallHierarchyItem> Items =138 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));139 ASSERT_THAT(Items, ElementsAre(withName("callee")));140 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());141 ASSERT_THAT(IncomingLevel1,142 ElementsAre(AllOf(from(AllOf(withName("caller1"),143 withDetail("MyClass::caller1"))),144 iFromRanges(Source.range("Callee")))));145 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());146 ASSERT_THAT(IncomingLevel2,147 ElementsAre(AllOf(from(AllOf(withName("caller2"),148 withDetail("MyClass::caller2"))),149 iFromRanges(Source.range("Caller1A"),150 Source.range("Caller1B"))),151 AllOf(from(AllOf(withName("caller3"),152 withDetail("MyClass::caller3"))),153 iFromRanges(Source.range("Caller1C")))));154 155 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());156 ASSERT_THAT(IncomingLevel3,157 ElementsAre(AllOf(from(AllOf(withName("caller3"),158 withDetail("MyClass::caller3"))),159 iFromRanges(Source.range("Caller2")))));160 161 auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());162 EXPECT_THAT(IncomingLevel4, IsEmpty());163}164 165TEST(CallHierarchy, IncomingIncludeOverrides) {166 Annotations Source(R"cpp(167 void call^ee() {}168 struct Interface {169 virtual void Func() = 0;170 };171 struct Implementation : public Interface {172 void Func() override {173 $Callee[[callee]]();174 }175 };176 void Test(Interface& cls){177 cls.$FuncCall[[Func]]();178 }179 )cpp");180 TestTU TU = TestTU::withCode(Source.code());181 auto AST = TU.build();182 auto Index = TU.index();183 184 std::vector<CallHierarchyItem> Items =185 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));186 ASSERT_THAT(Items, ElementsAre(withName("callee")));187 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());188 ASSERT_THAT(IncomingLevel1,189 ElementsAre(AllOf(from(AllOf(withName("Func"),190 withDetail("Implementation::Func"))),191 iFromRanges(Source.range("Callee")))));192 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());193 ASSERT_THAT(194 IncomingLevel2,195 ElementsAre(AllOf(from(AllOf(withName("Test"), withDetail("Test"))),196 iFromRanges(Source.range("FuncCall")))));197 198 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());199 EXPECT_THAT(IncomingLevel3, IsEmpty());200}201 202TEST(CallHierarchy, MainFileOnlyRef) {203 // In addition to testing that we store refs to main-file only symbols,204 // this tests that anonymous namespaces do not interfere with the205 // symbol re-identification process in callHierarchyItemToSymbo().206 Annotations Source(R"cpp(207 void call^ee(int);208 namespace {209 void caller1() {210 $Callee[[callee]](42);211 }212 }213 void caller2() {214 $Caller1[[caller1]]();215 }216 )cpp");217 TestTU TU = TestTU::withCode(Source.code());218 auto AST = TU.build();219 auto Index = TU.index();220 221 std::vector<CallHierarchyItem> Items =222 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));223 ASSERT_THAT(Items, ElementsAre(withName("callee")));224 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());225 ASSERT_THAT(226 IncomingLevel1,227 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("caller1"))),228 iFromRanges(Source.range("Callee")))));229 230 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());231 EXPECT_THAT(232 IncomingLevel2,233 ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail("caller2"))),234 iFromRanges(Source.range("Caller1")))));235}236 237TEST(CallHierarchy, IncomingQualified) {238 Annotations Source(R"cpp(239 namespace ns {240 struct Waldo {241 void find();242 };243 void Waldo::find() {}244 void caller1(Waldo &W) {245 W.$Caller1[[f^ind]]();246 }247 void caller2(Waldo &W) {248 W.$Caller2[[find]]();249 }250 }251 )cpp");252 TestTU TU = TestTU::withCode(Source.code());253 auto AST = TU.build();254 auto Index = TU.index();255 256 std::vector<CallHierarchyItem> Items =257 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));258 ASSERT_THAT(Items, ElementsAre(withName("Waldo::find")));259 auto Incoming = incomingCalls(Items[0], Index.get());260 EXPECT_THAT(261 Incoming,262 ElementsAre(263 AllOf(from(AllOf(withName("caller1"), withDetail("ns::caller1"))),264 iFromRanges(Source.range("Caller1"))),265 AllOf(from(AllOf(withName("caller2"), withDetail("ns::caller2"))),266 iFromRanges(Source.range("Caller2")))));267}268 269TEST(CallHierarchy, OutgoingOneFile) {270 // Test outgoing call on the main file, with namespaces and methods271 Annotations Source(R"cpp(272 void callee(int);273 namespace ns {274 struct Foo {275 void caller1();276 };277 void Foo::caller1() {278 $Callee[[callee]](42);279 }280 }281 namespace {282 void caller2(ns::Foo& F) {283 F.$Caller1A[[caller1]]();284 F.$Caller1B[[caller1]]();285 }286 }287 void call^er3(ns::Foo& F) {288 F.$Caller1C[[caller1]]();289 $Caller2[[caller2]](F);290 }291 )cpp");292 TestTU TU = TestTU::withCode(Source.code());293 auto AST = TU.build();294 auto Index = TU.index();295 296 std::vector<CallHierarchyItem> Items =297 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));298 ASSERT_THAT(Items, ElementsAre(withName("caller3")));299 auto OugoingLevel1 = outgoingCalls(Items[0], Index.get());300 ASSERT_THAT(301 OugoingLevel1,302 ElementsAre(303 AllOf(to(AllOf(withName("caller1"), withDetail("ns::Foo::caller1"))),304 oFromRanges(Source.range("Caller1C"))),305 AllOf(to(AllOf(withName("caller2"), withDetail("caller2"))),306 oFromRanges(Source.range("Caller2")))));307 308 auto OutgoingLevel2 = outgoingCalls(OugoingLevel1[1].to, Index.get());309 ASSERT_THAT(310 OutgoingLevel2,311 ElementsAre(AllOf(312 to(AllOf(withName("caller1"), withDetail("ns::Foo::caller1"))),313 oFromRanges(Source.range("Caller1A"), Source.range("Caller1B")))));314 315 auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get());316 ASSERT_THAT(317 OutgoingLevel3,318 ElementsAre(AllOf(to(AllOf(withName("callee"), withDetail("callee"))),319 oFromRanges(Source.range("Callee")))));320 321 auto OutgoingLevel4 = outgoingCalls(OutgoingLevel3[0].to, Index.get());322 EXPECT_THAT(OutgoingLevel4, IsEmpty());323}324 325TEST(CallHierarchy, MultiFileCpp) {326 // The test uses a .hh suffix for header files to get clang327 // to parse them in C++ mode. .h files are parsed in C mode328 // by default, which causes problems because e.g. symbol329 // USRs are different in C mode (do not include function signatures).330 331 Annotations CalleeH(R"cpp(332 void calle^e(int);333 )cpp");334 Annotations CalleeC(R"cpp(335 #include "callee.hh"336 void calle^e(int) {}337 )cpp");338 Annotations Caller1H(R"cpp(339 namespace nsa {340 void caller1();341 }342 )cpp");343 Annotations Caller1C(R"cpp(344 #include "callee.hh"345 #include "caller1.hh"346 namespace nsa {347 void caller1() {348 [[calle^e]](42);349 }350 }351 )cpp");352 Annotations Caller2H(R"cpp(353 namespace nsb {354 void caller2();355 }356 )cpp");357 Annotations Caller2C(R"cpp(358 #include "caller1.hh"359 #include "caller2.hh"360 namespace nsb {361 void caller2() {362 nsa::$A[[caller1]]();363 nsa::$B[[caller1]]();364 }365 }366 )cpp");367 Annotations Caller3H(R"cpp(368 namespace nsa {369 void call^er3();370 }371 )cpp");372 Annotations Caller3C(R"cpp(373 #include "caller1.hh"374 #include "caller2.hh"375 namespace nsa {376 void call^er3() {377 $Caller1[[caller1]]();378 nsb::$Caller2[[caller2]]();379 }380 }381 )cpp");382 383 TestWorkspace Workspace;384 Workspace.addSource("callee.hh", CalleeH.code());385 Workspace.addSource("caller1.hh", Caller1H.code());386 Workspace.addSource("caller2.hh", Caller2H.code());387 Workspace.addSource("caller3.hh", Caller3H.code());388 Workspace.addMainFile("callee.cc", CalleeC.code());389 Workspace.addMainFile("caller1.cc", Caller1C.code());390 Workspace.addMainFile("caller2.cc", Caller2C.code());391 Workspace.addMainFile("caller3.cc", Caller3C.code());392 393 auto Index = Workspace.index();394 395 auto CheckIncomingCalls = [&](ParsedAST &AST, Position Pos, PathRef TUPath) {396 std::vector<CallHierarchyItem> Items =397 prepareCallHierarchy(AST, Pos, TUPath);398 ASSERT_THAT(Items, ElementsAre(withName("callee")));399 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());400 ASSERT_THAT(IncomingLevel1,401 ElementsAre(AllOf(from(AllOf(withName("caller1"),402 withDetail("nsa::caller1"))),403 iFromRanges(Caller1C.range()))));404 405 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());406 ASSERT_THAT(407 IncomingLevel2,408 ElementsAre(409 AllOf(from(AllOf(withName("caller2"), withDetail("nsb::caller2"))),410 iFromRanges(Caller2C.range("A"), Caller2C.range("B"))),411 AllOf(from(AllOf(withName("caller3"), withDetail("nsa::caller3"))),412 iFromRanges(Caller3C.range("Caller1")))));413 414 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());415 ASSERT_THAT(IncomingLevel3,416 ElementsAre(AllOf(from(AllOf(withName("caller3"),417 withDetail("nsa::caller3"))),418 iFromRanges(Caller3C.range("Caller2")))));419 420 auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());421 EXPECT_THAT(IncomingLevel4, IsEmpty());422 };423 424 auto CheckOutgoingCalls = [&](ParsedAST &AST, Position Pos, PathRef TUPath,425 bool IsDeclaration) {426 std::vector<CallHierarchyItem> Items =427 prepareCallHierarchy(AST, Pos, TUPath);428 ASSERT_THAT(429 Items,430 ElementsAre(AllOf(431 withName("caller3"),432 withFile(testPath(IsDeclaration ? "caller3.hh" : "caller3.cc")))));433 auto OutgoingLevel1 = outgoingCalls(Items[0], Index.get());434 ASSERT_THAT(435 OutgoingLevel1,436 // fromRanges are interpreted in the context of Items[0]'s file.437 // If that's the header, we can't get ranges from the implementation438 // file!439 ElementsAre(440 AllOf(to(AllOf(withName("caller1"), withDetail("nsa::caller1"))),441 IsDeclaration ? oFromRanges()442 : oFromRanges(Caller3C.range("Caller1"))),443 AllOf(to(AllOf(withName("caller2"), withDetail("nsb::caller2"))),444 IsDeclaration ? oFromRanges()445 : oFromRanges(Caller3C.range("Caller2")))));446 447 auto OutgoingLevel2 = outgoingCalls(OutgoingLevel1[1].to, Index.get());448 ASSERT_THAT(OutgoingLevel2,449 ElementsAre(AllOf(450 to(AllOf(withName("caller1"), withDetail("nsa::caller1"))),451 oFromRanges(Caller2C.range("A"), Caller2C.range("B")))));452 453 auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get());454 ASSERT_THAT(455 OutgoingLevel3,456 ElementsAre(AllOf(to(AllOf(withName("callee"), withDetail("callee"))),457 oFromRanges(Caller1C.range()))));458 459 auto OutgoingLevel4 = outgoingCalls(OutgoingLevel3[0].to, Index.get());460 EXPECT_THAT(OutgoingLevel4, IsEmpty());461 };462 463 // Check that invoking from a call site works.464 auto AST = Workspace.openFile("caller1.cc");465 ASSERT_TRUE(bool(AST));466 CheckIncomingCalls(*AST, Caller1C.point(), testPath("caller1.cc"));467 468 // Check that invoking from the declaration site works.469 AST = Workspace.openFile("callee.hh");470 ASSERT_TRUE(bool(AST));471 CheckIncomingCalls(*AST, CalleeH.point(), testPath("callee.hh"));472 AST = Workspace.openFile("caller3.hh");473 ASSERT_TRUE(bool(AST));474 CheckOutgoingCalls(*AST, Caller3H.point(), testPath("caller3.hh"), true);475 476 // Check that invoking from the definition site works.477 AST = Workspace.openFile("callee.cc");478 ASSERT_TRUE(bool(AST));479 CheckIncomingCalls(*AST, CalleeC.point(), testPath("callee.cc"));480 AST = Workspace.openFile("caller3.cc");481 ASSERT_TRUE(bool(AST));482 CheckOutgoingCalls(*AST, Caller3C.point(), testPath("caller3.cc"), false);483}484 485TEST(CallHierarchy, IncomingMultiFileObjC) {486 // The test uses a .mi suffix for header files to get clang487 // to parse them in ObjC mode. .h files are parsed in C mode488 // by default, which causes problems because e.g. symbol489 // USRs are different in C mode (do not include function signatures).490 491 Annotations CalleeH(R"objc(492 @interface CalleeClass493 +(void)call^ee;494 @end495 )objc");496 Annotations CalleeC(R"objc(497 #import "callee.mi"498 @implementation CalleeClass {}499 +(void)call^ee {}500 @end501 )objc");502 Annotations Caller1H(R"objc(503 @interface Caller1Class504 +(void)caller1;505 @end506 )objc");507 Annotations Caller1C(R"objc(508 #import "callee.mi"509 #import "caller1.mi"510 @implementation Caller1Class {}511 +(void)caller1 {512 [CalleeClass [[calle^e]]];513 }514 @end515 )objc");516 Annotations Caller2H(R"objc(517 @interface Caller2Class518 +(void)caller2;519 @end520 )objc");521 Annotations Caller2C(R"objc(522 #import "caller1.mi"523 #import "caller2.mi"524 @implementation Caller2Class {}525 +(void)caller2 {526 [Caller1Class $A[[caller1]]];527 [Caller1Class $B[[caller1]]];528 }529 @end530 )objc");531 Annotations Caller3C(R"objc(532 #import "caller1.mi"533 #import "caller2.mi"534 @implementation Caller3Class {}535 +(void)caller3 {536 [Caller1Class $Caller1[[caller1]]];537 [Caller2Class $Caller2[[caller2]]];538 }539 @end540 )objc");541 542 TestWorkspace Workspace;543 Workspace.addSource("callee.mi", CalleeH.code());544 Workspace.addSource("caller1.mi", Caller1H.code());545 Workspace.addSource("caller2.mi", Caller2H.code());546 Workspace.addMainFile("callee.m", CalleeC.code());547 Workspace.addMainFile("caller1.m", Caller1C.code());548 Workspace.addMainFile("caller2.m", Caller2C.code());549 Workspace.addMainFile("caller3.m", Caller3C.code());550 auto Index = Workspace.index();551 552 auto CheckCallHierarchy = [&](ParsedAST &AST, Position Pos, PathRef TUPath) {553 std::vector<CallHierarchyItem> Items =554 prepareCallHierarchy(AST, Pos, TUPath);555 ASSERT_THAT(Items, ElementsAre(withName("callee")));556 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());557 ASSERT_THAT(IncomingLevel1,558 ElementsAre(AllOf(from(withName("caller1")),559 iFromRanges(Caller1C.range()))));560 561 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());562 ASSERT_THAT(IncomingLevel2,563 ElementsAre(AllOf(from(withName("caller2")),564 iFromRanges(Caller2C.range("A"),565 Caller2C.range("B"))),566 AllOf(from(withName("caller3")),567 iFromRanges(Caller3C.range("Caller1")))));568 569 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());570 ASSERT_THAT(IncomingLevel3,571 ElementsAre(AllOf(from(withName("caller3")),572 iFromRanges(Caller3C.range("Caller2")))));573 574 auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());575 EXPECT_THAT(IncomingLevel4, IsEmpty());576 };577 578 // Check that invoking from a call site works.579 auto AST = Workspace.openFile("caller1.m");580 ASSERT_TRUE(bool(AST));581 CheckCallHierarchy(*AST, Caller1C.point(), testPath("caller1.m"));582 583 // Check that invoking from the declaration site works.584 AST = Workspace.openFile("callee.mi");585 ASSERT_TRUE(bool(AST));586 CheckCallHierarchy(*AST, CalleeH.point(), testPath("callee.mi"));587 588 // Check that invoking from the definition site works.589 AST = Workspace.openFile("callee.m");590 ASSERT_TRUE(bool(AST));591 CheckCallHierarchy(*AST, CalleeC.point(), testPath("callee.m"));592}593 594TEST(CallHierarchy, CallInLocalVarDecl) {595 // Tests that local variable declarations are not treated as callers596 // (they're not indexed, so they can't be represented as call hierarchy597 // items); instead, the caller should be the containing function.598 // However, namespace-scope variable declarations should be treated as599 // callers because those are indexed and there is no enclosing entity600 // that would be a useful caller.601 Annotations Source(R"cpp(602 int call^ee();603 void caller1() {604 $call1[[callee]]();605 }606 void caller2() {607 int localVar = $call2[[callee]]();608 }609 int caller3 = $call3[[callee]]();610 )cpp");611 TestTU TU = TestTU::withCode(Source.code());612 auto AST = TU.build();613 auto Index = TU.index();614 615 std::vector<CallHierarchyItem> Items =616 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));617 ASSERT_THAT(Items, ElementsAre(withName("callee")));618 619 auto Incoming = incomingCalls(Items[0], Index.get());620 ASSERT_THAT(Incoming, ElementsAre(AllOf(from(withName("caller1")),621 iFromRanges(Source.range("call1"))),622 AllOf(from(withName("caller2")),623 iFromRanges(Source.range("call2"))),624 AllOf(from(withName("caller3")),625 iFromRanges(Source.range("call3")))));626}627 628TEST(CallHierarchy, HierarchyOnField) {629 // Tests that the call hierarchy works on fields.630 Annotations Source(R"cpp(631 struct Vars {632 int v^ar1 = 1;633 };634 void caller() {635 Vars values;636 values.$Callee[[var1]];637 }638 )cpp");639 TestTU TU = TestTU::withCode(Source.code());640 auto AST = TU.build();641 auto Index = TU.index();642 643 std::vector<CallHierarchyItem> Items =644 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));645 ASSERT_THAT(Items, ElementsAre(withName("var1")));646 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());647 ASSERT_THAT(IncomingLevel1,648 ElementsAre(AllOf(from(withName("caller")),649 iFromRanges(Source.range("Callee")))));650}651 652TEST(CallHierarchy, HierarchyOnVar) {653 // Tests that the call hierarchy works on non-local variables.654 Annotations Source(R"cpp(655 int v^ar = 1;656 void caller() {657 $Callee[[var]];658 }659 )cpp");660 TestTU TU = TestTU::withCode(Source.code());661 auto AST = TU.build();662 auto Index = TU.index();663 664 std::vector<CallHierarchyItem> Items =665 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));666 ASSERT_THAT(Items, ElementsAre(withName("var")));667 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());668 ASSERT_THAT(IncomingLevel1,669 ElementsAre(AllOf(from(withName("caller")),670 iFromRanges(Source.range("Callee")))));671}672 673TEST(CallHierarchy, HierarchyOnEnumConstant) {674 // Tests that the call hierarchy works on enum constants.675 Annotations Source(R"cpp(676 enum class Coin { heads$Heads^ , tai$Tails^ls };677 void caller() {678 Coin::$CallerH[[heads]];679 Coin::$CallerT[[tails]];680 }681 )cpp");682 TestTU TU = TestTU::withCode(Source.code());683 auto AST = TU.build();684 auto Index = TU.index();685 686 std::vector<CallHierarchyItem> Items =687 prepareCallHierarchy(AST, Source.point("Heads"), testPath(TU.Filename));688 ASSERT_THAT(Items, ElementsAre(withName("heads")));689 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());690 ASSERT_THAT(IncomingLevel1,691 ElementsAre(AllOf(from(withName("caller")),692 iFromRanges(Source.range("CallerH")))));693 Items =694 prepareCallHierarchy(AST, Source.point("Tails"), testPath(TU.Filename));695 ASSERT_THAT(Items, ElementsAre(withName("tails")));696 IncomingLevel1 = incomingCalls(Items[0], Index.get());697 ASSERT_THAT(IncomingLevel1,698 ElementsAre(AllOf(from(withName("caller")),699 iFromRanges(Source.range("CallerT")))));700}701 702TEST(CallHierarchy, CallInDifferentFileThanCaller) {703 Annotations Header(R"cpp(704 #define WALDO void caller() {705 )cpp");706 Annotations Source(R"cpp(707 void call^ee();708 WALDO709 callee();710 }711 )cpp");712 auto TU = TestTU::withCode(Source.code());713 TU.HeaderCode = Header.code();714 auto AST = TU.build();715 auto Index = TU.index();716 717 std::vector<CallHierarchyItem> Items =718 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));719 ASSERT_THAT(Items, ElementsAre(withName("callee")));720 721 auto Incoming = incomingCalls(Items[0], Index.get());722 723 // The only call site is in the source file, which is a different file from724 // the declaration of the function containing the call, which is in the725 // header. The protocol does not allow us to represent such calls, so we drop726 // them. (The call hierarchy item itself is kept.)727 EXPECT_THAT(Incoming,728 ElementsAre(AllOf(from(withName("caller")), iFromRanges())));729}730 731} // namespace732} // namespace clangd733} // namespace clang734