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; }
49
50template <typename... Tags>
51::testing::Matcher<CallHierarchyItem> withSymbolTags(Tags... tags) {
52 // Matches the tags vector ignoring element order.
53 return Field(&CallHierarchyItem::tags, UnorderedElementsAre(tags...));
54}
55
56template <class ItemMatcher>
57::testing::Matcher<CallHierarchyIncomingCall> from(ItemMatcher M) {
59}
60template <class ItemMatcher>
61::testing::Matcher<CallHierarchyOutgoingCall> to(ItemMatcher M) {
63}
64template <class... RangeMatchers>
65::testing::Matcher<CallHierarchyIncomingCall> iFromRanges(RangeMatchers... M) {
67 UnorderedElementsAre(M...));
68}
69template <class... RangeMatchers>
70::testing::Matcher<CallHierarchyOutgoingCall> oFromRanges(RangeMatchers... M) {
72 UnorderedElementsAre(M...));
73}
74
75TEST(CallHierarchy, IncomingOneFileCpp) {
76 Annotations Source(R"cpp(
77 void call^ee(int);
78 void caller1() {
79 $Callee[[callee]](42);
80 }
81 void caller2() {
82 $Caller1A[[caller1]]();
83 $Caller1B[[caller1]]();
84 }
85 void caller3() {
86 $Caller1C[[caller1]]();
87 $Caller2[[caller2]]();
88 }
89 )cpp");
90 TestTU TU = TestTU::withCode(Source.code());
91 auto AST = TU.build();
92 auto Index = TU.index();
93
94 std::vector<CallHierarchyItem> Items =
95 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
96 ASSERT_THAT(Items, ElementsAre(withName("callee")));
97 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
98 ASSERT_THAT(
99 IncomingLevel1,
100 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail(""))),
101 iFromRanges(Source.range("Callee")))));
102 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
103 ASSERT_THAT(
104 IncomingLevel2,
105 ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail(""))),
106 iFromRanges(Source.range("Caller1A"),
107 Source.range("Caller1B"))),
108 AllOf(from(AllOf(withName("caller3"), withDetail(""))),
109 iFromRanges(Source.range("Caller1C")))));
110
111 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
112 ASSERT_THAT(
113 IncomingLevel3,
114 ElementsAre(AllOf(from(AllOf(withName("caller3"), withDetail(""))),
115 iFromRanges(Source.range("Caller2")))));
116
117 auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
118 EXPECT_THAT(IncomingLevel4, IsEmpty());
119}
120
121TEST(CallHierarchy, IncomingOneFileObjC) {
122 Annotations Source(R"objc(
123 @implementation MyClass {}
124 +(void)call^ee {}
125 +(void) caller1 {
126 [MyClass $Callee[[callee]]];
127 }
128 +(void) caller2 {
129 [MyClass $Caller1A[[caller1]]];
130 [MyClass $Caller1B[[caller1]]];
131 }
132 +(void) caller3 {
133 [MyClass $Caller1C[[caller1]]];
134 [MyClass $Caller2[[caller2]]];
135 }
136 @end
137 )objc");
138 TestTU TU = TestTU::withCode(Source.code());
139 TU.Filename = "TestTU.m";
140 auto AST = TU.build();
141 auto Index = TU.index();
142 std::vector<CallHierarchyItem> Items =
143 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
144 ASSERT_THAT(Items, ElementsAre(withName("callee")));
145 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
146 ASSERT_THAT(
147 IncomingLevel1,
148 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("MyClass"))),
149 iFromRanges(Source.range("Callee")))));
150 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
151 ASSERT_THAT(
152 IncomingLevel2,
153 ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail("MyClass"))),
154 iFromRanges(Source.range("Caller1A"),
155 Source.range("Caller1B"))),
156 AllOf(from(AllOf(withName("caller3"), withDetail("MyClass"))),
157 iFromRanges(Source.range("Caller1C")))));
158
159 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
160 ASSERT_THAT(
161 IncomingLevel3,
162 ElementsAre(AllOf(from(AllOf(withName("caller3"), withDetail("MyClass"))),
163 iFromRanges(Source.range("Caller2")))));
164
165 auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
166 EXPECT_THAT(IncomingLevel4, IsEmpty());
167}
168
169TEST(CallHierarchy, IncomingIncludeOverrides) {
170 Annotations Source(R"cpp(
171 void call^ee() {}
172 struct Interface {
173 virtual void Func() = 0;
174 };
175 struct Implementation : public Interface {
176 void Func() override {
177 $Callee[[callee]]();
178 }
179 };
180 void Test(Interface& cls){
181 cls.$FuncCall[[Func]]();
182 }
183 )cpp");
184 TestTU TU = TestTU::withCode(Source.code());
185 auto AST = TU.build();
186 auto Index = TU.index();
187
188 std::vector<CallHierarchyItem> Items =
189 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
190 ASSERT_THAT(Items, ElementsAre(withName("callee")));
191 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
192 ASSERT_THAT(IncomingLevel1,
193 ElementsAre(AllOf(
194 from(AllOf(withName("Func"), withDetail("Implementation"))),
195 iFromRanges(Source.range("Callee")))));
196 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
197 ASSERT_THAT(IncomingLevel2,
198 ElementsAre(AllOf(from(AllOf(withName("Test"), withDetail(""))),
199 iFromRanges(Source.range("FuncCall")))));
200
201 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
202 EXPECT_THAT(IncomingLevel3, IsEmpty());
203}
204
205TEST(CallHierarchy, MainFileOnlyRef) {
206 // In addition to testing that we store refs to main-file only symbols,
207 // this tests that anonymous namespaces do not interfere with the
208 // symbol re-identification process in callHierarchyItemToSymbo().
209 Annotations Source(R"cpp(
210 void call^ee(int);
211 namespace {
212 void caller1() {
213 $Callee[[callee]](42);
214 }
215 }
216 void caller2() {
217 $Caller1[[caller1]]();
218 }
219 )cpp");
220 TestTU TU = TestTU::withCode(Source.code());
221 auto AST = TU.build();
222 auto Index = TU.index();
223
224 std::vector<CallHierarchyItem> Items =
225 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
226 ASSERT_THAT(Items, ElementsAre(withName("callee")));
227 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
228 ASSERT_THAT(
229 IncomingLevel1,
230 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail(""))),
231 iFromRanges(Source.range("Callee")))));
232
233 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
234 EXPECT_THAT(
235 IncomingLevel2,
236 ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail(""))),
237 iFromRanges(Source.range("Caller1")))));
238}
239
240TEST(CallHierarchy, IncomingQualified) {
241 Annotations Source(R"cpp(
242 namespace ns {
243 struct Waldo {
244 void find();
245 };
246 void Waldo::find() {}
247 void caller1(Waldo &W) {
248 W.$Caller1[[f^ind]]();
249 }
250 void caller2(Waldo &W) {
251 W.$Caller2[[find]]();
252 }
253 }
254 )cpp");
255 TestTU TU = TestTU::withCode(Source.code());
256 auto AST = TU.build();
257 auto Index = TU.index();
258
259 std::vector<CallHierarchyItem> Items =
260 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
261 ASSERT_THAT(Items, ElementsAre(withName("Waldo::find")));
262 auto Incoming = incomingCalls(Items[0], Index.get());
263 EXPECT_THAT(
264 Incoming,
265 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("ns"))),
266 iFromRanges(Source.range("Caller1"))),
267 AllOf(from(AllOf(withName("caller2"), withDetail("ns"))),
268 iFromRanges(Source.range("Caller2")))));
269}
270
271TEST(CallHierarchy, OutgoingOneFile) {
272 // Test outgoing call on the main file, with namespaces and methods
273 Annotations Source(R"cpp(
274 void callee(int);
275 namespace ns {
276 struct Foo {
277 void caller1();
278 };
279 void Foo::caller1() {
280 $Callee[[callee]](42);
281 }
282 }
283 namespace {
284 void caller2(ns::Foo& F) {
285 F.$Caller1A[[caller1]]();
286 F.$Caller1B[[caller1]]();
287 }
288 }
289 void call^er3(ns::Foo& F) {
290 F.$Caller1C[[caller1]]();
291 $Caller2[[caller2]](F);
292 }
293 )cpp");
294 TestTU TU = TestTU::withCode(Source.code());
295 auto AST = TU.build();
296 auto Index = TU.index();
297
298 std::vector<CallHierarchyItem> Items =
299 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
300 ASSERT_THAT(Items, ElementsAre(withName("caller3")));
301 auto OugoingLevel1 = outgoingCalls(Items[0], Index.get());
302 ASSERT_THAT(
303 OugoingLevel1,
304 ElementsAre(AllOf(to(AllOf(withName("caller1"), withDetail("ns::Foo"))),
305 oFromRanges(Source.range("Caller1C"))),
306 AllOf(to(AllOf(withName("caller2"), withDetail(""))),
307 oFromRanges(Source.range("Caller2")))));
308
309 auto OutgoingLevel2 = outgoingCalls(OugoingLevel1[1].to, Index.get());
310 ASSERT_THAT(
311 OutgoingLevel2,
312 ElementsAre(AllOf(
313 to(AllOf(withName("caller1"), withDetail("ns::Foo"))),
314 oFromRanges(Source.range("Caller1A"), Source.range("Caller1B")))));
315
316 auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get());
317 ASSERT_THAT(OutgoingLevel3,
318 ElementsAre(AllOf(to(AllOf(withName("callee"), withDetail(""))),
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 clang
327 // to parse them in C++ mode. .h files are parsed in C mode
328 // by default, which causes problems because e.g. symbol
329 // 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(
401 IncomingLevel1,
402 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("nsa"))),
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"))),
410 iFromRanges(Caller2C.range("A"), Caller2C.range("B"))),
411 AllOf(from(AllOf(withName("caller3"), withDetail("nsa"))),
412 iFromRanges(Caller3C.range("Caller1")))));
413
414 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
415 ASSERT_THAT(
416 IncomingLevel3,
417 ElementsAre(AllOf(from(AllOf(withName("caller3"), withDetail("nsa"))),
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 implementation
438 // file!
439 ElementsAre(
440 AllOf(to(AllOf(withName("caller1"), withDetail("nsa"))),
441 IsDeclaration ? oFromRanges()
442 : oFromRanges(Caller3C.range("Caller1"))),
443 AllOf(to(AllOf(withName("caller2"), withDetail("nsb"))),
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"))),
451 oFromRanges(Caller2C.range("A"), Caller2C.range("B")))));
452
453 auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get());
454 ASSERT_THAT(OutgoingLevel3,
455 ElementsAre(AllOf(to(AllOf(withName("callee"), withDetail(""))),
456 oFromRanges(Caller1C.range()))));
457
458 auto OutgoingLevel4 = outgoingCalls(OutgoingLevel3[0].to, Index.get());
459 EXPECT_THAT(OutgoingLevel4, IsEmpty());
460 };
461
462 // Check that invoking from a call site works.
463 auto AST = Workspace.openFile("caller1.cc");
464 ASSERT_TRUE(bool(AST));
465 CheckIncomingCalls(*AST, Caller1C.point(), testPath("caller1.cc"));
466
467 // Check that invoking from the declaration site works.
468 AST = Workspace.openFile("callee.hh");
469 ASSERT_TRUE(bool(AST));
470 CheckIncomingCalls(*AST, CalleeH.point(), testPath("callee.hh"));
471 AST = Workspace.openFile("caller3.hh");
472 ASSERT_TRUE(bool(AST));
473 CheckOutgoingCalls(*AST, Caller3H.point(), testPath("caller3.hh"), true);
474
475 // Check that invoking from the definition site works.
476 AST = Workspace.openFile("callee.cc");
477 ASSERT_TRUE(bool(AST));
478 CheckIncomingCalls(*AST, CalleeC.point(), testPath("callee.cc"));
479 AST = Workspace.openFile("caller3.cc");
480 ASSERT_TRUE(bool(AST));
481 CheckOutgoingCalls(*AST, Caller3C.point(), testPath("caller3.cc"), false);
482}
483
484TEST(CallHierarchy, IncomingMultiFileObjC) {
485 // The test uses a .mi suffix for header files to get clang
486 // to parse them in ObjC mode. .h files are parsed in C mode
487 // by default, which causes problems because e.g. symbol
488 // USRs are different in C mode (do not include function signatures).
489
490 Annotations CalleeH(R"objc(
491 @interface CalleeClass
492 +(void)call^ee;
493 @end
494 )objc");
495 Annotations CalleeC(R"objc(
496 #import "callee.mi"
497 @implementation CalleeClass {}
498 +(void)call^ee {}
499 @end
500 )objc");
501 Annotations Caller1H(R"objc(
502 @interface Caller1Class
503 +(void)caller1;
504 @end
505 )objc");
506 Annotations Caller1C(R"objc(
507 #import "callee.mi"
508 #import "caller1.mi"
509 @implementation Caller1Class {}
510 +(void)caller1 {
511 [CalleeClass [[calle^e]]];
512 }
513 @end
514 )objc");
515 Annotations Caller2H(R"objc(
516 @interface Caller2Class
517 +(void)caller2;
518 @end
519 )objc");
520 Annotations Caller2C(R"objc(
521 #import "caller1.mi"
522 #import "caller2.mi"
523 @implementation Caller2Class {}
524 +(void)caller2 {
525 [Caller1Class $A[[caller1]]];
526 [Caller1Class $B[[caller1]]];
527 }
528 @end
529 )objc");
530 Annotations Caller3C(R"objc(
531 #import "caller1.mi"
532 #import "caller2.mi"
533 @implementation Caller3Class {}
534 +(void)caller3 {
535 [Caller1Class $Caller1[[caller1]]];
536 [Caller2Class $Caller2[[caller2]]];
537 }
538 @end
539 )objc");
540
541 TestWorkspace Workspace;
542 Workspace.addSource("callee.mi", CalleeH.code());
543 Workspace.addSource("caller1.mi", Caller1H.code());
544 Workspace.addSource("caller2.mi", Caller2H.code());
545 Workspace.addMainFile("callee.m", CalleeC.code());
546 Workspace.addMainFile("caller1.m", Caller1C.code());
547 Workspace.addMainFile("caller2.m", Caller2C.code());
548 Workspace.addMainFile("caller3.m", Caller3C.code());
549 auto Index = Workspace.index();
550
551 auto CheckCallHierarchy = [&](ParsedAST &AST, Position Pos, PathRef TUPath) {
552 std::vector<CallHierarchyItem> Items =
553 prepareCallHierarchy(AST, Pos, TUPath);
554 ASSERT_THAT(Items, ElementsAre(withName("callee")));
555 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
556 ASSERT_THAT(IncomingLevel1,
557 ElementsAre(AllOf(from(withName("caller1")),
558 iFromRanges(Caller1C.range()))));
559
560 auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
561 ASSERT_THAT(IncomingLevel2,
562 ElementsAre(AllOf(from(withName("caller2")),
563 iFromRanges(Caller2C.range("A"),
564 Caller2C.range("B"))),
565 AllOf(from(withName("caller3")),
566 iFromRanges(Caller3C.range("Caller1")))));
567
568 auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
569 ASSERT_THAT(IncomingLevel3,
570 ElementsAre(AllOf(from(withName("caller3")),
571 iFromRanges(Caller3C.range("Caller2")))));
572
573 auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
574 EXPECT_THAT(IncomingLevel4, IsEmpty());
575 };
576
577 // Check that invoking from a call site works.
578 auto AST = Workspace.openFile("caller1.m");
579 ASSERT_TRUE(bool(AST));
580 CheckCallHierarchy(*AST, Caller1C.point(), testPath("caller1.m"));
581
582 // Check that invoking from the declaration site works.
583 AST = Workspace.openFile("callee.mi");
584 ASSERT_TRUE(bool(AST));
585 CheckCallHierarchy(*AST, CalleeH.point(), testPath("callee.mi"));
586
587 // Check that invoking from the definition site works.
588 AST = Workspace.openFile("callee.m");
589 ASSERT_TRUE(bool(AST));
590 CheckCallHierarchy(*AST, CalleeC.point(), testPath("callee.m"));
591}
592
593TEST(CallHierarchy, CallInLocalVarDecl) {
594 // Tests that local variable declarations are not treated as callers
595 // (they're not indexed, so they can't be represented as call hierarchy
596 // items); instead, the caller should be the containing function.
597 // However, namespace-scope variable declarations should be treated as
598 // callers because those are indexed and there is no enclosing entity
599 // that would be a useful caller.
600 Annotations Source(R"cpp(
601 int call^ee();
602 void caller1() {
603 $call1[[callee]]();
604 }
605 void caller2() {
606 int localVar = $call2[[callee]]();
607 }
608 int caller3 = $call3[[callee]]();
609 )cpp");
610 TestTU TU = TestTU::withCode(Source.code());
611 auto AST = TU.build();
612 auto Index = TU.index();
613
614 std::vector<CallHierarchyItem> Items =
615 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
616 ASSERT_THAT(Items, ElementsAre(withName("callee")));
617
618 auto Incoming = incomingCalls(Items[0], Index.get());
619 ASSERT_THAT(Incoming, ElementsAre(AllOf(from(withName("caller1")),
620 iFromRanges(Source.range("call1"))),
621 AllOf(from(withName("caller2")),
622 iFromRanges(Source.range("call2"))),
623 AllOf(from(withName("caller3")),
624 iFromRanges(Source.range("call3")))));
625}
626
627TEST(CallHierarchy, HierarchyOnField) {
628 // Tests that the call hierarchy works on fields.
629 Annotations Source(R"cpp(
630 struct Vars {
631 int v^ar1 = 1;
632 };
633 void caller() {
634 Vars values;
635 values.$Callee[[var1]];
636 }
637 )cpp");
638 TestTU TU = TestTU::withCode(Source.code());
639 auto AST = TU.build();
640 auto Index = TU.index();
641
642 std::vector<CallHierarchyItem> Items =
643 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
644 ASSERT_THAT(Items, ElementsAre(withName("var1")));
645 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
646 ASSERT_THAT(IncomingLevel1,
647 ElementsAre(AllOf(from(withName("caller")),
648 iFromRanges(Source.range("Callee")))));
649}
650
651TEST(CallHierarchy, HierarchyOnVar) {
652 // Tests that the call hierarchy works on non-local variables.
653 Annotations Source(R"cpp(
654 int v^ar = 1;
655 void caller() {
656 $Callee[[var]];
657 }
658 )cpp");
659 TestTU TU = TestTU::withCode(Source.code());
660 auto AST = TU.build();
661 auto Index = TU.index();
662
663 std::vector<CallHierarchyItem> Items =
664 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
665 ASSERT_THAT(Items, ElementsAre(withName("var")));
666 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
667 ASSERT_THAT(IncomingLevel1,
668 ElementsAre(AllOf(from(withName("caller")),
669 iFromRanges(Source.range("Callee")))));
670}
671
672TEST(CallHierarchy, HierarchyOnEnumConstant) {
673 // Tests that the call hierarchy works on enum constants.
674 Annotations Source(R"cpp(
675 enum class Coin { heads$Heads^ , tai$Tails^ls };
676 void caller() {
677 Coin::$CallerH[[heads]];
678 Coin::$CallerT[[tails]];
679 }
680 )cpp");
681 TestTU TU = TestTU::withCode(Source.code());
682 auto AST = TU.build();
683 auto Index = TU.index();
684
685 std::vector<CallHierarchyItem> Items =
686 prepareCallHierarchy(AST, Source.point("Heads"), testPath(TU.Filename));
687 ASSERT_THAT(Items, ElementsAre(withName("heads")));
688 auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
689 ASSERT_THAT(IncomingLevel1,
690 ElementsAre(AllOf(from(withName("caller")),
691 iFromRanges(Source.range("CallerH")))));
692 Items =
693 prepareCallHierarchy(AST, Source.point("Tails"), testPath(TU.Filename));
694 ASSERT_THAT(Items, ElementsAre(withName("tails")));
695 IncomingLevel1 = incomingCalls(Items[0], Index.get());
696 ASSERT_THAT(IncomingLevel1,
697 ElementsAre(AllOf(from(withName("caller")),
698 iFromRanges(Source.range("CallerT")))));
699}
700
701TEST(CallHierarchy, CallInDifferentFileThanCaller) {
702 Annotations Header(R"cpp(
703 #define WALDO void caller() {
704 )cpp");
705 Annotations Source(R"cpp(
706 void call^ee();
707 WALDO
708 callee();
709 }
710 )cpp");
711 auto TU = TestTU::withCode(Source.code());
712 TU.HeaderCode = Header.code();
713 auto AST = TU.build();
714 auto Index = TU.index();
715
716 std::vector<CallHierarchyItem> Items =
717 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
718 ASSERT_THAT(Items, ElementsAre(withName("callee")));
719
720 auto Incoming = incomingCalls(Items[0], Index.get());
721
722 // The only call site is in the source file, which is a different file from
723 // the declaration of the function containing the call, which is in the
724 // header. The protocol does not allow us to represent such calls, so we drop
725 // them. (The call hierarchy item itself is kept.)
726 EXPECT_THAT(Incoming,
727 ElementsAre(AllOf(from(withName("caller")), iFromRanges())));
728}
729
730TEST(CallHierarchy, IncomingCalls) {
731 Annotations Source(R"cpp(
732 class A {
733 public:
734 void call^ee() {};
735 };
736 void caller(A &a) {
737 a.callee();
738 }
739 )cpp");
740 TestTU TU = TestTU::withCode(Source.code());
741 auto AST = TU.build();
742 auto Index = TU.index();
743
744 std::vector<CallHierarchyItem> Items =
745 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
746 ASSERT_THAT(Items, ElementsAre(withName("callee")));
747
748 auto Incoming = incomingCalls(Items[0], Index.get());
749 EXPECT_THAT(
750 Incoming,
751 UnorderedElementsAre(AllOf(from(
752 AllOf(withName("caller"), withSymbolTags(SymbolTag::Declaration,
754}
755
756TEST(CallHierarchy, OutgoingCalls) {
757 Annotations Source(R"cpp(
758 void callee() {}
759 class A {
760 public:
761 void call^er() {
762 callee();
763 };
764 };
765 )cpp");
766 TestTU TU = TestTU::withCode(Source.code());
767 auto AST = TU.build();
768 auto Index = TU.index();
769
770 std::vector<CallHierarchyItem> Items =
771 prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
772 ASSERT_THAT(Items, ElementsAre(withName("caller")));
773
774 auto Outgoing = outgoingCalls(Items[0], Index.get());
775 EXPECT_THAT(Outgoing, UnorderedElementsAre(AllOf(
776 to(AllOf(withName("callee"),
777 withSymbolTags(SymbolTag::Declaration,
779}
780} // namespace
781} // namespace clangd
782} // 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:47
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:2461
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:2543
std::vector< CallHierarchyItem > prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath)
Get call hierarchy information at Pos.
Definition XRefs.cpp:2435
===– 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