clang-tools 23.0.0git
CallHierarchyTests.cpp
Go to the documentation of this file.
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-exception
6//
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,
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) {
54}
55template <class ItemMatcher>
56::testing::Matcher<CallHierarchyOutgoingCall> to(ItemMatcher M) {
58}
59template <class... RangeMatchers>
60::testing::Matcher<CallHierarchyIncomingCall> iFromRanges(RangeMatchers... M) {
62 UnorderedElementsAre(M...));
63}
64template <class... RangeMatchers>
65::testing::Matcher<CallHierarchyOutgoingCall> oFromRanges(RangeMatchers... M) {
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(""))),
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(""))),
101 iFromRanges(Source.range("Caller1A"),
102 Source.range("Caller1B"))),
103 AllOf(from(AllOf(withName("caller3"), withDetail(""))),
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(""))),
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 @end
132 )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(
142 IncomingLevel1,
143 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("MyClass"))),
144 iFromRanges(Source.range("Callee")))));
145 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
146 ASSERT_THAT(
147 IncomingLevel2,
148 ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail("MyClass"))),
149 iFromRanges(Source.range("Caller1A"),
150 Source.range("Caller1B"))),
151 AllOf(from(AllOf(withName("caller3"), withDetail("MyClass"))),
152 iFromRanges(Source.range("Caller1C")))));
153
154 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
155 ASSERT_THAT(
156 IncomingLevel3,
157 ElementsAre(AllOf(from(AllOf(withName("caller3"), withDetail("MyClass"))),
158 iFromRanges(Source.range("Caller2")))));
159
160 auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
161 EXPECT_THAT(IncomingLevel4, IsEmpty());
162}
163
164TEST(CallHierarchy, IncomingIncludeOverrides) {
165 Annotations Source(R"cpp(
166 void call^ee() {}
167 struct Interface {
168 virtual void Func() = 0;
169 };
170 struct Implementation : public Interface {
171 void Func() override {
172 $Callee[[callee]]();
173 }
174 };
175 void Test(Interface& cls){
176 cls.$FuncCall[[Func]]();
177 }
178 )cpp");
179 TestTU TU = TestTU::withCode(Source.code());
180 auto AST = TU.build();
181 auto Index = TU.index();
182
183 std::vector<CallHierarchyItem> Items =
184 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
185 ASSERT_THAT(Items, ElementsAre(withName("callee")));
186 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
187 ASSERT_THAT(IncomingLevel1,
188 ElementsAre(AllOf(
189 from(AllOf(withName("Func"), withDetail("Implementation"))),
190 iFromRanges(Source.range("Callee")))));
191 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
192 ASSERT_THAT(IncomingLevel2,
193 ElementsAre(AllOf(from(AllOf(withName("Test"), withDetail(""))),
194 iFromRanges(Source.range("FuncCall")))));
195
196 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
197 EXPECT_THAT(IncomingLevel3, IsEmpty());
198}
199
200TEST(CallHierarchy, MainFileOnlyRef) {
201 // In addition to testing that we store refs to main-file only symbols,
202 // this tests that anonymous namespaces do not interfere with the
203 // symbol re-identification process in callHierarchyItemToSymbo().
204 Annotations Source(R"cpp(
205 void call^ee(int);
206 namespace {
207 void caller1() {
208 $Callee[[callee]](42);
209 }
210 }
211 void caller2() {
212 $Caller1[[caller1]]();
213 }
214 )cpp");
215 TestTU TU = TestTU::withCode(Source.code());
216 auto AST = TU.build();
217 auto Index = TU.index();
218
219 std::vector<CallHierarchyItem> Items =
220 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
221 ASSERT_THAT(Items, ElementsAre(withName("callee")));
222 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
223 ASSERT_THAT(
224 IncomingLevel1,
225 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail(""))),
226 iFromRanges(Source.range("Callee")))));
227
228 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
229 EXPECT_THAT(
230 IncomingLevel2,
231 ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail(""))),
232 iFromRanges(Source.range("Caller1")))));
233}
234
235TEST(CallHierarchy, IncomingQualified) {
236 Annotations Source(R"cpp(
237 namespace ns {
238 struct Waldo {
239 void find();
240 };
241 void Waldo::find() {}
242 void caller1(Waldo &W) {
243 W.$Caller1[[f^ind]]();
244 }
245 void caller2(Waldo &W) {
246 W.$Caller2[[find]]();
247 }
248 }
249 )cpp");
250 TestTU TU = TestTU::withCode(Source.code());
251 auto AST = TU.build();
252 auto Index = TU.index();
253
254 std::vector<CallHierarchyItem> Items =
255 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
256 ASSERT_THAT(Items, ElementsAre(withName("Waldo::find")));
257 auto Incoming = incomingCalls(Items[0], Index.get());
258 EXPECT_THAT(
259 Incoming,
260 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("ns"))),
261 iFromRanges(Source.range("Caller1"))),
262 AllOf(from(AllOf(withName("caller2"), withDetail("ns"))),
263 iFromRanges(Source.range("Caller2")))));
264}
265
266TEST(CallHierarchy, OutgoingOneFile) {
267 // Test outgoing call on the main file, with namespaces and methods
268 Annotations Source(R"cpp(
269 void callee(int);
270 namespace ns {
271 struct Foo {
272 void caller1();
273 };
274 void Foo::caller1() {
275 $Callee[[callee]](42);
276 }
277 }
278 namespace {
279 void caller2(ns::Foo& F) {
280 F.$Caller1A[[caller1]]();
281 F.$Caller1B[[caller1]]();
282 }
283 }
284 void call^er3(ns::Foo& F) {
285 F.$Caller1C[[caller1]]();
286 $Caller2[[caller2]](F);
287 }
288 )cpp");
289 TestTU TU = TestTU::withCode(Source.code());
290 auto AST = TU.build();
291 auto Index = TU.index();
292
293 std::vector<CallHierarchyItem> Items =
294 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
295 ASSERT_THAT(Items, ElementsAre(withName("caller3")));
296 auto OugoingLevel1 = outgoingCalls(Items[0], Index.get());
297 ASSERT_THAT(
298 OugoingLevel1,
299 ElementsAre(AllOf(to(AllOf(withName("caller1"), withDetail("ns::Foo"))),
300 oFromRanges(Source.range("Caller1C"))),
301 AllOf(to(AllOf(withName("caller2"), withDetail(""))),
302 oFromRanges(Source.range("Caller2")))));
303
304 auto OutgoingLevel2 = outgoingCalls(OugoingLevel1[1].to, Index.get());
305 ASSERT_THAT(
306 OutgoingLevel2,
307 ElementsAre(AllOf(
308 to(AllOf(withName("caller1"), withDetail("ns::Foo"))),
309 oFromRanges(Source.range("Caller1A"), Source.range("Caller1B")))));
310
311 auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get());
312 ASSERT_THAT(OutgoingLevel3,
313 ElementsAre(AllOf(to(AllOf(withName("callee"), withDetail(""))),
314 oFromRanges(Source.range("Callee")))));
315
316 auto OutgoingLevel4 = outgoingCalls(OutgoingLevel3[0].to, Index.get());
317 EXPECT_THAT(OutgoingLevel4, IsEmpty());
318}
319
320TEST(CallHierarchy, MultiFileCpp) {
321 // The test uses a .hh suffix for header files to get clang
322 // to parse them in C++ mode. .h files are parsed in C mode
323 // by default, which causes problems because e.g. symbol
324 // USRs are different in C mode (do not include function signatures).
325
326 Annotations CalleeH(R"cpp(
327 void calle^e(int);
328 )cpp");
329 Annotations CalleeC(R"cpp(
330 #include "callee.hh"
331 void calle^e(int) {}
332 )cpp");
333 Annotations Caller1H(R"cpp(
334 namespace nsa {
335 void caller1();
336 }
337 )cpp");
338 Annotations Caller1C(R"cpp(
339 #include "callee.hh"
340 #include "caller1.hh"
341 namespace nsa {
342 void caller1() {
343 [[calle^e]](42);
344 }
345 }
346 )cpp");
347 Annotations Caller2H(R"cpp(
348 namespace nsb {
349 void caller2();
350 }
351 )cpp");
352 Annotations Caller2C(R"cpp(
353 #include "caller1.hh"
354 #include "caller2.hh"
355 namespace nsb {
356 void caller2() {
357 nsa::$A[[caller1]]();
358 nsa::$B[[caller1]]();
359 }
360 }
361 )cpp");
362 Annotations Caller3H(R"cpp(
363 namespace nsa {
364 void call^er3();
365 }
366 )cpp");
367 Annotations Caller3C(R"cpp(
368 #include "caller1.hh"
369 #include "caller2.hh"
370 namespace nsa {
371 void call^er3() {
372 $Caller1[[caller1]]();
373 nsb::$Caller2[[caller2]]();
374 }
375 }
376 )cpp");
377
378 TestWorkspace Workspace;
379 Workspace.addSource("callee.hh", CalleeH.code());
380 Workspace.addSource("caller1.hh", Caller1H.code());
381 Workspace.addSource("caller2.hh", Caller2H.code());
382 Workspace.addSource("caller3.hh", Caller3H.code());
383 Workspace.addMainFile("callee.cc", CalleeC.code());
384 Workspace.addMainFile("caller1.cc", Caller1C.code());
385 Workspace.addMainFile("caller2.cc", Caller2C.code());
386 Workspace.addMainFile("caller3.cc", Caller3C.code());
387
388 auto Index = Workspace.index();
389
390 auto CheckIncomingCalls = [&](ParsedAST &AST, Position Pos, PathRef TUPath) {
391 std::vector<CallHierarchyItem> Items =
392 prepareCallHierarchy(AST, Pos, TUPath);
393 ASSERT_THAT(Items, ElementsAre(withName("callee")));
394 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
395 ASSERT_THAT(
396 IncomingLevel1,
397 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("nsa"))),
398 iFromRanges(Caller1C.range()))));
399
400 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
401 ASSERT_THAT(
402 IncomingLevel2,
403 ElementsAre(
404 AllOf(from(AllOf(withName("caller2"), withDetail("nsb"))),
405 iFromRanges(Caller2C.range("A"), Caller2C.range("B"))),
406 AllOf(from(AllOf(withName("caller3"), withDetail("nsa"))),
407 iFromRanges(Caller3C.range("Caller1")))));
408
409 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
410 ASSERT_THAT(
411 IncomingLevel3,
412 ElementsAre(AllOf(from(AllOf(withName("caller3"), withDetail("nsa"))),
413 iFromRanges(Caller3C.range("Caller2")))));
414
415 auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
416 EXPECT_THAT(IncomingLevel4, IsEmpty());
417 };
418
419 auto CheckOutgoingCalls = [&](ParsedAST &AST, Position Pos, PathRef TUPath,
420 bool IsDeclaration) {
421 std::vector<CallHierarchyItem> Items =
422 prepareCallHierarchy(AST, Pos, TUPath);
423 ASSERT_THAT(
424 Items,
425 ElementsAre(AllOf(
426 withName("caller3"),
427 withFile(testPath(IsDeclaration ? "caller3.hh" : "caller3.cc")))));
428 auto OutgoingLevel1 = outgoingCalls(Items[0], Index.get());
429 ASSERT_THAT(
430 OutgoingLevel1,
431 // fromRanges are interpreted in the context of Items[0]'s file.
432 // If that's the header, we can't get ranges from the implementation
433 // file!
434 ElementsAre(
435 AllOf(to(AllOf(withName("caller1"), withDetail("nsa"))),
436 IsDeclaration ? oFromRanges()
437 : oFromRanges(Caller3C.range("Caller1"))),
438 AllOf(to(AllOf(withName("caller2"), withDetail("nsb"))),
439 IsDeclaration ? oFromRanges()
440 : oFromRanges(Caller3C.range("Caller2")))));
441
442 auto OutgoingLevel2 = outgoingCalls(OutgoingLevel1[1].to, Index.get());
443 ASSERT_THAT(OutgoingLevel2,
444 ElementsAre(AllOf(
445 to(AllOf(withName("caller1"), withDetail("nsa"))),
446 oFromRanges(Caller2C.range("A"), Caller2C.range("B")))));
447
448 auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get());
449 ASSERT_THAT(OutgoingLevel3,
450 ElementsAre(AllOf(to(AllOf(withName("callee"), withDetail(""))),
451 oFromRanges(Caller1C.range()))));
452
453 auto OutgoingLevel4 = outgoingCalls(OutgoingLevel3[0].to, Index.get());
454 EXPECT_THAT(OutgoingLevel4, IsEmpty());
455 };
456
457 // Check that invoking from a call site works.
458 auto AST = Workspace.openFile("caller1.cc");
459 ASSERT_TRUE(bool(AST));
460 CheckIncomingCalls(*AST, Caller1C.point(), testPath("caller1.cc"));
461
462 // Check that invoking from the declaration site works.
463 AST = Workspace.openFile("callee.hh");
464 ASSERT_TRUE(bool(AST));
465 CheckIncomingCalls(*AST, CalleeH.point(), testPath("callee.hh"));
466 AST = Workspace.openFile("caller3.hh");
467 ASSERT_TRUE(bool(AST));
468 CheckOutgoingCalls(*AST, Caller3H.point(), testPath("caller3.hh"), true);
469
470 // Check that invoking from the definition site works.
471 AST = Workspace.openFile("callee.cc");
472 ASSERT_TRUE(bool(AST));
473 CheckIncomingCalls(*AST, CalleeC.point(), testPath("callee.cc"));
474 AST = Workspace.openFile("caller3.cc");
475 ASSERT_TRUE(bool(AST));
476 CheckOutgoingCalls(*AST, Caller3C.point(), testPath("caller3.cc"), false);
477}
478
479TEST(CallHierarchy, IncomingMultiFileObjC) {
480 // The test uses a .mi suffix for header files to get clang
481 // to parse them in ObjC mode. .h files are parsed in C mode
482 // by default, which causes problems because e.g. symbol
483 // USRs are different in C mode (do not include function signatures).
484
485 Annotations CalleeH(R"objc(
486 @interface CalleeClass
487 +(void)call^ee;
488 @end
489 )objc");
490 Annotations CalleeC(R"objc(
491 #import "callee.mi"
492 @implementation CalleeClass {}
493 +(void)call^ee {}
494 @end
495 )objc");
496 Annotations Caller1H(R"objc(
497 @interface Caller1Class
498 +(void)caller1;
499 @end
500 )objc");
501 Annotations Caller1C(R"objc(
502 #import "callee.mi"
503 #import "caller1.mi"
504 @implementation Caller1Class {}
505 +(void)caller1 {
506 [CalleeClass [[calle^e]]];
507 }
508 @end
509 )objc");
510 Annotations Caller2H(R"objc(
511 @interface Caller2Class
512 +(void)caller2;
513 @end
514 )objc");
515 Annotations Caller2C(R"objc(
516 #import "caller1.mi"
517 #import "caller2.mi"
518 @implementation Caller2Class {}
519 +(void)caller2 {
520 [Caller1Class $A[[caller1]]];
521 [Caller1Class $B[[caller1]]];
522 }
523 @end
524 )objc");
525 Annotations Caller3C(R"objc(
526 #import "caller1.mi"
527 #import "caller2.mi"
528 @implementation Caller3Class {}
529 +(void)caller3 {
530 [Caller1Class $Caller1[[caller1]]];
531 [Caller2Class $Caller2[[caller2]]];
532 }
533 @end
534 )objc");
535
536 TestWorkspace Workspace;
537 Workspace.addSource("callee.mi", CalleeH.code());
538 Workspace.addSource("caller1.mi", Caller1H.code());
539 Workspace.addSource("caller2.mi", Caller2H.code());
540 Workspace.addMainFile("callee.m", CalleeC.code());
541 Workspace.addMainFile("caller1.m", Caller1C.code());
542 Workspace.addMainFile("caller2.m", Caller2C.code());
543 Workspace.addMainFile("caller3.m", Caller3C.code());
544 auto Index = Workspace.index();
545
546 auto CheckCallHierarchy = [&](ParsedAST &AST, Position Pos, PathRef TUPath) {
547 std::vector<CallHierarchyItem> Items =
548 prepareCallHierarchy(AST, Pos, TUPath);
549 ASSERT_THAT(Items, ElementsAre(withName("callee")));
550 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
551 ASSERT_THAT(IncomingLevel1,
552 ElementsAre(AllOf(from(withName("caller1")),
553 iFromRanges(Caller1C.range()))));
554
555 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
556 ASSERT_THAT(IncomingLevel2,
557 ElementsAre(AllOf(from(withName("caller2")),
558 iFromRanges(Caller2C.range("A"),
559 Caller2C.range("B"))),
560 AllOf(from(withName("caller3")),
561 iFromRanges(Caller3C.range("Caller1")))));
562
563 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
564 ASSERT_THAT(IncomingLevel3,
565 ElementsAre(AllOf(from(withName("caller3")),
566 iFromRanges(Caller3C.range("Caller2")))));
567
568 auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
569 EXPECT_THAT(IncomingLevel4, IsEmpty());
570 };
571
572 // Check that invoking from a call site works.
573 auto AST = Workspace.openFile("caller1.m");
574 ASSERT_TRUE(bool(AST));
575 CheckCallHierarchy(*AST, Caller1C.point(), testPath("caller1.m"));
576
577 // Check that invoking from the declaration site works.
578 AST = Workspace.openFile("callee.mi");
579 ASSERT_TRUE(bool(AST));
580 CheckCallHierarchy(*AST, CalleeH.point(), testPath("callee.mi"));
581
582 // Check that invoking from the definition site works.
583 AST = Workspace.openFile("callee.m");
584 ASSERT_TRUE(bool(AST));
585 CheckCallHierarchy(*AST, CalleeC.point(), testPath("callee.m"));
586}
587
588TEST(CallHierarchy, CallInLocalVarDecl) {
589 // Tests that local variable declarations are not treated as callers
590 // (they're not indexed, so they can't be represented as call hierarchy
591 // items); instead, the caller should be the containing function.
592 // However, namespace-scope variable declarations should be treated as
593 // callers because those are indexed and there is no enclosing entity
594 // that would be a useful caller.
595 Annotations Source(R"cpp(
596 int call^ee();
597 void caller1() {
598 $call1[[callee]]();
599 }
600 void caller2() {
601 int localVar = $call2[[callee]]();
602 }
603 int caller3 = $call3[[callee]]();
604 )cpp");
605 TestTU TU = TestTU::withCode(Source.code());
606 auto AST = TU.build();
607 auto Index = TU.index();
608
609 std::vector<CallHierarchyItem> Items =
610 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
611 ASSERT_THAT(Items, ElementsAre(withName("callee")));
612
613 auto Incoming = incomingCalls(Items[0], Index.get());
614 ASSERT_THAT(Incoming, ElementsAre(AllOf(from(withName("caller1")),
615 iFromRanges(Source.range("call1"))),
616 AllOf(from(withName("caller2")),
617 iFromRanges(Source.range("call2"))),
618 AllOf(from(withName("caller3")),
619 iFromRanges(Source.range("call3")))));
620}
621
622TEST(CallHierarchy, HierarchyOnField) {
623 // Tests that the call hierarchy works on fields.
624 Annotations Source(R"cpp(
625 struct Vars {
626 int v^ar1 = 1;
627 };
628 void caller() {
629 Vars values;
630 values.$Callee[[var1]];
631 }
632 )cpp");
633 TestTU TU = TestTU::withCode(Source.code());
634 auto AST = TU.build();
635 auto Index = TU.index();
636
637 std::vector<CallHierarchyItem> Items =
638 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
639 ASSERT_THAT(Items, ElementsAre(withName("var1")));
640 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
641 ASSERT_THAT(IncomingLevel1,
642 ElementsAre(AllOf(from(withName("caller")),
643 iFromRanges(Source.range("Callee")))));
644}
645
646TEST(CallHierarchy, HierarchyOnVar) {
647 // Tests that the call hierarchy works on non-local variables.
648 Annotations Source(R"cpp(
649 int v^ar = 1;
650 void caller() {
651 $Callee[[var]];
652 }
653 )cpp");
654 TestTU TU = TestTU::withCode(Source.code());
655 auto AST = TU.build();
656 auto Index = TU.index();
657
658 std::vector<CallHierarchyItem> Items =
659 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
660 ASSERT_THAT(Items, ElementsAre(withName("var")));
661 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
662 ASSERT_THAT(IncomingLevel1,
663 ElementsAre(AllOf(from(withName("caller")),
664 iFromRanges(Source.range("Callee")))));
665}
666
667TEST(CallHierarchy, HierarchyOnEnumConstant) {
668 // Tests that the call hierarchy works on enum constants.
669 Annotations Source(R"cpp(
670 enum class Coin { heads$Heads^ , tai$Tails^ls };
671 void caller() {
672 Coin::$CallerH[[heads]];
673 Coin::$CallerT[[tails]];
674 }
675 )cpp");
676 TestTU TU = TestTU::withCode(Source.code());
677 auto AST = TU.build();
678 auto Index = TU.index();
679
680 std::vector<CallHierarchyItem> Items =
681 prepareCallHierarchy(AST, Source.point("Heads"), testPath(TU.Filename));
682 ASSERT_THAT(Items, ElementsAre(withName("heads")));
683 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
684 ASSERT_THAT(IncomingLevel1,
685 ElementsAre(AllOf(from(withName("caller")),
686 iFromRanges(Source.range("CallerH")))));
687 Items =
688 prepareCallHierarchy(AST, Source.point("Tails"), testPath(TU.Filename));
689 ASSERT_THAT(Items, ElementsAre(withName("tails")));
690 IncomingLevel1 = incomingCalls(Items[0], Index.get());
691 ASSERT_THAT(IncomingLevel1,
692 ElementsAre(AllOf(from(withName("caller")),
693 iFromRanges(Source.range("CallerT")))));
694}
695
696TEST(CallHierarchy, CallInDifferentFileThanCaller) {
697 Annotations Header(R"cpp(
698 #define WALDO void caller() {
699 )cpp");
700 Annotations Source(R"cpp(
701 void call^ee();
702 WALDO
703 callee();
704 }
705 )cpp");
706 auto TU = TestTU::withCode(Source.code());
707 TU.HeaderCode = Header.code();
708 auto AST = TU.build();
709 auto Index = TU.index();
710
711 std::vector<CallHierarchyItem> Items =
712 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
713 ASSERT_THAT(Items, ElementsAre(withName("callee")));
714
715 auto Incoming = incomingCalls(Items[0], Index.get());
716
717 // The only call site is in the source file, which is a different file from
718 // the declaration of the function containing the call, which is in the
719 // header. The protocol does not allow us to represent such calls, so we drop
720 // them. (The call hierarchy item itself is kept.)
721 EXPECT_THAT(Incoming,
722 ElementsAre(AllOf(from(withName("caller")), iFromRanges())));
723}
724
725} // namespace
726} // namespace clangd
727} // namespace clang
Same as llvm::Annotations, but adjusts functions to LSP-specific types for positions and ranges.
Definition Annotations.h:23
Stores and provides access to parsed AST.
Definition ParsedAST.h:46
void addSource(llvm::StringRef Filename, llvm::StringRef Code)
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:44
std::vector< CallHierarchyIncomingCall > incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index)
Definition XRefs.cpp:2384
llvm::raw_ostream & operator<<(llvm::raw_ostream &OS, const CodeCompletion &C)
MATCHER_P(named, N, "")
std::string testPath(PathRef File, llvm::sys::path::Style Style)
Definition TestFS.cpp:94
TEST(BackgroundQueueTest, Priority)
llvm::StringRef PathRef
A typedef to represent a ref to file path.
Definition Path.h:29
std::vector< CallHierarchyOutgoingCall > outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index)
Definition XRefs.cpp:2466
std::vector< CallHierarchyItem > prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath)
Get call hierarchy information at Pos.
Definition XRefs.cpp:2358
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Represents an incoming call, e.g. a caller of a method or constructor.
Definition Protocol.h:1676
CallHierarchyItem from
The item that makes the call.
Definition Protocol.h:1678
std::vector< Range > fromRanges
The range at which the calls appear.
Definition Protocol.h:1682
Represents programming constructs like functions or constructors in the context of call hierarchy.
Definition Protocol.h:1636
std::string name
The name of this item.
Definition Protocol.h:1638
Range selectionRange
The range that should be selected and revealed when this symbol is being picked, e....
Definition Protocol.h:1659
std::vector< Range > fromRanges
The range at which this item is called.
Definition Protocol.h:1707
CallHierarchyItem to
The item that is called.
Definition Protocol.h:1703
static TestTU withCode(llvm::StringRef Code)
Definition TestTU.h:36