clang-tools 24.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 <typename... Tags>
52::testing::Matcher<CallHierarchyItem> withSymbolTags(Tags... tags) {
53 // Matches the tags vector ignoring element order.
54 return Field(&CallHierarchyItem::tags, UnorderedElementsAre(tags...));
55}
56
57template <class ItemMatcher>
58::testing::Matcher<CallHierarchyIncomingCall> from(ItemMatcher M) {
60}
61template <class ItemMatcher>
62::testing::Matcher<CallHierarchyOutgoingCall> to(ItemMatcher M) {
64}
65template <class... RangeMatchers>
66::testing::Matcher<CallHierarchyIncomingCall> iFromRanges(RangeMatchers... M) {
68 UnorderedElementsAre(M...));
69}
70template <class... RangeMatchers>
71::testing::Matcher<CallHierarchyOutgoingCall> oFromRanges(RangeMatchers... M) {
73 UnorderedElementsAre(M...));
74}
75
76TEST(CallHierarchy, IncomingOneFileCpp) {
77 Annotations Source(R"cpp(
78 void call^ee(int);
79 void caller1() {
80 $Callee[[callee]](42);
81 }
82 void caller2() {
83 $Caller1A[[caller1]]();
84 $Caller1B[[caller1]]();
85 }
86 void caller3() {
87 $Caller1C[[caller1]]();
88 $Caller2[[caller2]]();
89 }
90 )cpp");
91 TestTU TU = TestTU::withCode(Source.code());
92 auto AST = TU.build();
93 auto Index = TU.index();
94
95 std::vector<CallHierarchyItem> Items =
96 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
97 ASSERT_THAT(Items, ElementsAre(withName("callee")));
98 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
99 ASSERT_THAT(
100 IncomingLevel1,
101 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail(""))),
102 iFromRanges(Source.range("Callee")))));
103 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
104 ASSERT_THAT(
105 IncomingLevel2,
106 ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail(""))),
107 iFromRanges(Source.range("Caller1A"),
108 Source.range("Caller1B"))),
109 AllOf(from(AllOf(withName("caller3"), withDetail(""))),
110 iFromRanges(Source.range("Caller1C")))));
111
112 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
113 ASSERT_THAT(
114 IncomingLevel3,
115 ElementsAre(AllOf(from(AllOf(withName("caller3"), withDetail(""))),
116 iFromRanges(Source.range("Caller2")))));
117
118 auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
119 EXPECT_THAT(IncomingLevel4, IsEmpty());
120}
121
122TEST(CallHierarchy, IncomingOneFileObjC) {
123 Annotations Source(R"objc(
124 @implementation MyClass {}
125 +(void)call^ee {}
126 +(void) caller1 {
127 [MyClass $Callee[[callee]]];
128 }
129 +(void) caller2 {
130 [MyClass $Caller1A[[caller1]]];
131 [MyClass $Caller1B[[caller1]]];
132 }
133 +(void) caller3 {
134 [MyClass $Caller1C[[caller1]]];
135 [MyClass $Caller2[[caller2]]];
136 }
137 @end
138 )objc");
139 TestTU TU = TestTU::withCode(Source.code());
140 TU.Filename = "TestTU.m";
141 auto AST = TU.build();
142 auto Index = TU.index();
143 std::vector<CallHierarchyItem> Items =
144 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
145 ASSERT_THAT(Items, ElementsAre(withName("callee")));
146 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
147 ASSERT_THAT(
148 IncomingLevel1,
149 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("MyClass"))),
150 iFromRanges(Source.range("Callee")))));
151 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
152 ASSERT_THAT(
153 IncomingLevel2,
154 ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail("MyClass"))),
155 iFromRanges(Source.range("Caller1A"),
156 Source.range("Caller1B"))),
157 AllOf(from(AllOf(withName("caller3"), withDetail("MyClass"))),
158 iFromRanges(Source.range("Caller1C")))));
159
160 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
161 ASSERT_THAT(
162 IncomingLevel3,
163 ElementsAre(AllOf(from(AllOf(withName("caller3"), withDetail("MyClass"))),
164 iFromRanges(Source.range("Caller2")))));
165
166 auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
167 EXPECT_THAT(IncomingLevel4, IsEmpty());
168}
169
170TEST(CallHierarchy, IncomingIncludeOverrides) {
171 Annotations Source(R"cpp(
172 void call^ee() {}
173 struct Interface {
174 virtual void Func() = 0;
175 };
176 struct Implementation : public Interface {
177 void Func() override {
178 $Callee[[callee]]();
179 }
180 };
181 void Test(Interface& cls){
182 cls.$FuncCall[[Func]]();
183 }
184 )cpp");
185 TestTU TU = TestTU::withCode(Source.code());
186 auto AST = TU.build();
187 auto Index = TU.index();
188
189 std::vector<CallHierarchyItem> Items =
190 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
191 ASSERT_THAT(Items, ElementsAre(withName("callee")));
192 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
193 ASSERT_THAT(IncomingLevel1,
194 ElementsAre(AllOf(
195 from(AllOf(withName("Func"), withDetail("Implementation"))),
196 iFromRanges(Source.range("Callee")))));
197 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
198 ASSERT_THAT(IncomingLevel2,
199 ElementsAre(AllOf(from(AllOf(withName("Test"), withDetail(""))),
200 iFromRanges(Source.range("FuncCall")))));
201
202 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
203 EXPECT_THAT(IncomingLevel3, IsEmpty());
204}
205
206TEST(CallHierarchy, MainFileOnlyRef) {
207 // In addition to testing that we store refs to main-file only symbols,
208 // this tests that anonymous namespaces do not interfere with the
209 // symbol re-identification process in callHierarchyItemToSymbo().
210 Annotations Source(R"cpp(
211 void call^ee(int);
212 namespace {
213 void caller1() {
214 $Callee[[callee]](42);
215 }
216 }
217 void caller2() {
218 $Caller1[[caller1]]();
219 }
220 )cpp");
221 TestTU TU = TestTU::withCode(Source.code());
222 auto AST = TU.build();
223 auto Index = TU.index();
224
225 std::vector<CallHierarchyItem> Items =
226 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
227 ASSERT_THAT(Items, ElementsAre(withName("callee")));
228 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
229 ASSERT_THAT(
230 IncomingLevel1,
231 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail(""))),
232 iFromRanges(Source.range("Callee")))));
233
234 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
235 EXPECT_THAT(
236 IncomingLevel2,
237 ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail(""))),
238 iFromRanges(Source.range("Caller1")))));
239}
240
241TEST(CallHierarchy, IncomingQualified) {
242 Annotations Source(R"cpp(
243 namespace ns {
244 struct Waldo {
245 void find();
246 };
247 void Waldo::find() {}
248 void caller1(Waldo &W) {
249 W.$Caller1[[f^ind]]();
250 }
251 void caller2(Waldo &W) {
252 W.$Caller2[[find]]();
253 }
254 }
255 )cpp");
256 TestTU TU = TestTU::withCode(Source.code());
257 auto AST = TU.build();
258 auto Index = TU.index();
259
260 std::vector<CallHierarchyItem> Items =
261 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
262 ASSERT_THAT(Items, ElementsAre(withName("Waldo::find")));
263 auto Incoming = incomingCalls(Items[0], Index.get());
264 EXPECT_THAT(
265 Incoming,
266 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("ns"))),
267 iFromRanges(Source.range("Caller1"))),
268 AllOf(from(AllOf(withName("caller2"), withDetail("ns"))),
269 iFromRanges(Source.range("Caller2")))));
270}
271
272TEST(CallHierarchy, OutgoingOneFile) {
273 // Test outgoing call on the main file, with namespaces and methods
274 Annotations Source(R"cpp(
275 void callee(int);
276 namespace ns {
277 struct Foo {
278 void caller1();
279 };
280 void Foo::caller1() {
281 $Callee[[callee]](42);
282 }
283 }
284 namespace {
285 void caller2(ns::Foo& F) {
286 F.$Caller1A[[caller1]]();
287 F.$Caller1B[[caller1]]();
288 }
289 }
290 void call^er3(ns::Foo& F) {
291 F.$Caller1C[[caller1]]();
292 $Caller2[[caller2]](F);
293 }
294 )cpp");
295 TestTU TU = TestTU::withCode(Source.code());
296 auto AST = TU.build();
297 auto Index = TU.index();
298
299 std::vector<CallHierarchyItem> Items =
300 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
301 ASSERT_THAT(Items, ElementsAre(withName("caller3")));
302 auto OugoingLevel1 = outgoingCalls(Items[0], Index.get());
303 ASSERT_THAT(
304 OugoingLevel1,
305 ElementsAre(AllOf(to(AllOf(withName("caller1"), withDetail("ns::Foo"))),
306 oFromRanges(Source.range("Caller1C"))),
307 AllOf(to(AllOf(withName("caller2"), withDetail(""))),
308 oFromRanges(Source.range("Caller2")))));
309
310 auto OutgoingLevel2 = outgoingCalls(OugoingLevel1[1].to, Index.get());
311 ASSERT_THAT(
312 OutgoingLevel2,
313 ElementsAre(AllOf(
314 to(AllOf(withName("caller1"), withDetail("ns::Foo"))),
315 oFromRanges(Source.range("Caller1A"), Source.range("Caller1B")))));
316
317 auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get());
318 ASSERT_THAT(OutgoingLevel3,
319 ElementsAre(AllOf(to(AllOf(withName("callee"), withDetail(""))),
320 oFromRanges(Source.range("Callee")))));
321
322 auto OutgoingLevel4 = outgoingCalls(OutgoingLevel3[0].to, Index.get());
323 EXPECT_THAT(OutgoingLevel4, IsEmpty());
324}
325
326TEST(CallHierarchy, MultiFileCpp) {
327 // The test uses a .hh suffix for header files to get clang
328 // to parse them in C++ mode. .h files are parsed in C mode
329 // by default, which causes problems because e.g. symbol
330 // USRs are different in C mode (do not include function signatures).
331
332 Annotations CalleeH(R"cpp(
333 void calle^e(int);
334 )cpp");
335 Annotations CalleeC(R"cpp(
336 #include "callee.hh"
337 void calle^e(int) {}
338 )cpp");
339 Annotations Caller1H(R"cpp(
340 namespace nsa {
341 void caller1();
342 }
343 )cpp");
344 Annotations Caller1C(R"cpp(
345 #include "callee.hh"
346 #include "caller1.hh"
347 namespace nsa {
348 void caller1() {
349 [[calle^e]](42);
350 }
351 }
352 )cpp");
353 Annotations Caller2H(R"cpp(
354 namespace nsb {
355 void caller2();
356 }
357 )cpp");
358 Annotations Caller2C(R"cpp(
359 #include "caller1.hh"
360 #include "caller2.hh"
361 namespace nsb {
362 void caller2() {
363 nsa::$A[[caller1]]();
364 nsa::$B[[caller1]]();
365 }
366 }
367 )cpp");
368 Annotations Caller3H(R"cpp(
369 namespace nsa {
370 void call^er3();
371 }
372 )cpp");
373 Annotations Caller3C(R"cpp(
374 #include "caller1.hh"
375 #include "caller2.hh"
376 namespace nsa {
377 void call^er3() {
378 $Caller1[[caller1]]();
379 nsb::$Caller2[[caller2]]();
380 }
381 }
382 )cpp");
383
384 TestWorkspace Workspace;
385 Workspace.addSource("callee.hh", CalleeH.code());
386 Workspace.addSource("caller1.hh", Caller1H.code());
387 Workspace.addSource("caller2.hh", Caller2H.code());
388 Workspace.addSource("caller3.hh", Caller3H.code());
389 Workspace.addMainFile("callee.cc", CalleeC.code());
390 Workspace.addMainFile("caller1.cc", Caller1C.code());
391 Workspace.addMainFile("caller2.cc", Caller2C.code());
392 Workspace.addMainFile("caller3.cc", Caller3C.code());
393
394 auto Index = Workspace.index();
395
396 auto CheckIncomingCalls = [&](ParsedAST &AST, Position Pos, PathRef TUPath) {
397 std::vector<CallHierarchyItem> Items =
398 prepareCallHierarchy(AST, Pos, TUPath);
399 ASSERT_THAT(Items, ElementsAre(withName("callee")));
400 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
401 ASSERT_THAT(
402 IncomingLevel1,
403 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("nsa"))),
404 iFromRanges(Caller1C.range()))));
405
406 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
407 ASSERT_THAT(
408 IncomingLevel2,
409 ElementsAre(
410 AllOf(from(AllOf(withName("caller2"), withDetail("nsb"))),
411 iFromRanges(Caller2C.range("A"), Caller2C.range("B"))),
412 AllOf(from(AllOf(withName("caller3"), withDetail("nsa"))),
413 iFromRanges(Caller3C.range("Caller1")))));
414
415 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
416 ASSERT_THAT(
417 IncomingLevel3,
418 ElementsAre(AllOf(from(AllOf(withName("caller3"), withDetail("nsa"))),
419 iFromRanges(Caller3C.range("Caller2")))));
420
421 auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
422 EXPECT_THAT(IncomingLevel4, IsEmpty());
423 };
424
425 auto CheckOutgoingCalls = [&](ParsedAST &AST, Position Pos, PathRef TUPath,
426 bool IsDeclaration) {
427 std::vector<CallHierarchyItem> Items =
428 prepareCallHierarchy(AST, Pos, TUPath);
429 ASSERT_THAT(
430 Items,
431 ElementsAre(AllOf(
432 withName("caller3"),
433 withFile(testPath(IsDeclaration ? "caller3.hh" : "caller3.cc")))));
434 auto OutgoingLevel1 = outgoingCalls(Items[0], Index.get());
435 ASSERT_THAT(
436 OutgoingLevel1,
437 // fromRanges are interpreted in the context of Items[0]'s file.
438 // If that's the header, we can't get ranges from the implementation
439 // file!
440 ElementsAre(
441 AllOf(to(AllOf(withName("caller1"), withDetail("nsa"))),
442 IsDeclaration ? oFromRanges()
443 : oFromRanges(Caller3C.range("Caller1"))),
444 AllOf(to(AllOf(withName("caller2"), withDetail("nsb"))),
445 IsDeclaration ? oFromRanges()
446 : oFromRanges(Caller3C.range("Caller2")))));
447
448 auto OutgoingLevel2 = outgoingCalls(OutgoingLevel1[1].to, Index.get());
449 ASSERT_THAT(OutgoingLevel2,
450 ElementsAre(AllOf(
451 to(AllOf(withName("caller1"), withDetail("nsa"))),
452 oFromRanges(Caller2C.range("A"), Caller2C.range("B")))));
453
454 auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get());
455 ASSERT_THAT(OutgoingLevel3,
456 ElementsAre(AllOf(to(AllOf(withName("callee"), withDetail(""))),
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 clang
487 // to parse them in ObjC mode. .h files are parsed in C mode
488 // by default, which causes problems because e.g. symbol
489 // USRs are different in C mode (do not include function signatures).
490
491 Annotations CalleeH(R"objc(
492 @interface CalleeClass
493 +(void)call^ee;
494 @end
495 )objc");
496 Annotations CalleeC(R"objc(
497 #import "callee.mi"
498 @implementation CalleeClass {}
499 +(void)call^ee {}
500 @end
501 )objc");
502 Annotations Caller1H(R"objc(
503 @interface Caller1Class
504 +(void)caller1;
505 @end
506 )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 @end
515 )objc");
516 Annotations Caller2H(R"objc(
517 @interface Caller2Class
518 +(void)caller2;
519 @end
520 )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 @end
530 )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 @end
540 )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 callers
596 // (they're not indexed, so they can't be represented as call hierarchy
597 // items); instead, the caller should be the containing function.
598 // However, namespace-scope variable declarations should be treated as
599 // callers because those are indexed and there is no enclosing entity
600 // 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 WALDO
709 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 from
724 // the declaration of the function containing the call, which is in the
725 // header. The protocol does not allow us to represent such calls, so we drop
726 // them. (The call hierarchy item itself is kept.)
727 EXPECT_THAT(Incoming,
728 ElementsAre(AllOf(from(withName("caller")), iFromRanges())));
729}
730
731TEST(CallHierarchy, IncomingCalls) {
732 Annotations Source(R"cpp(
733 class A {
734 public:
735 void call^ee() {};
736 };
737 void caller(A &a) {
738 a.callee();
739 }
740 )cpp");
741 TestTU TU = TestTU::withCode(Source.code());
742 auto AST = TU.build();
743 auto Index = TU.index();
744
745 std::vector<CallHierarchyItem> Items =
746 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
747 ASSERT_THAT(Items, ElementsAre(withName("callee")));
748
749 auto Incoming = incomingCalls(Items[0], Index.get());
750 EXPECT_THAT(
751 Incoming,
752 UnorderedElementsAre(AllOf(from(
753 AllOf(withName("caller"), withSymbolTags(SymbolTag::Declaration,
755}
756
757TEST(CallHierarchy, OutgoingCalls) {
758 Annotations Source(R"cpp(
759 void callee() {}
760 class A {
761 public:
762 void call^er() {
763 callee();
764 };
765 };
766 )cpp");
767 TestTU TU = TestTU::withCode(Source.code());
768 auto AST = TU.build();
769 auto Index = TU.index();
770
771 std::vector<CallHierarchyItem> Items =
772 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
773 ASSERT_THAT(Items, ElementsAre(withName("caller")));
774
775 auto Outgoing = outgoingCalls(Items[0], Index.get());
776 EXPECT_THAT(Outgoing, UnorderedElementsAre(AllOf(
777 to(AllOf(withName("callee"),
778 withSymbolTags(SymbolTag::Declaration,
780}
781} // namespace
782} // namespace clangd
783} // 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:2409
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:2491
std::vector< CallHierarchyItem > prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath)
Get call hierarchy information at Pos.
Definition XRefs.cpp:2383
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Represents an incoming call, e.g. a caller of a method or constructor.
Definition Protocol.h:1690
CallHierarchyItem from
The item that makes the call.
Definition Protocol.h:1692
std::vector< Range > fromRanges
The range at which the calls appear.
Definition Protocol.h:1696
Represents programming constructs like functions or constructors in the context of call hierarchy.
Definition Protocol.h:1650
std::string name
The name of this item.
Definition Protocol.h:1652
std::vector< SymbolTag > tags
Tags for this item.
Definition Protocol.h:1658
Range selectionRange
The range that should be selected and revealed when this symbol is being picked, e....
Definition Protocol.h:1673
std::vector< Range > fromRanges
The range at which this item is called.
Definition Protocol.h:1721
CallHierarchyItem to
The item that is called.
Definition Protocol.h:1717
static TestTU withCode(llvm::StringRef Code)
Definition TestTU.h:36