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,
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(Code: Source.code());
86 auto AST = TU.build();
87 auto Index = TU.index();
88
89 std::vector<CallHierarchyItem> Items =
90 prepareCallHierarchy(AST, Pos: Source.point(), TUPath: testPath(File: TU.Filename));
91 ASSERT_THAT(Items, ElementsAre(withName("callee")));
92 auto IncomingLevel1 = incomingCalls(Item: Items[0], Index: 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(Item: IncomingLevel1[0].from, Index: 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(Item: IncomingLevel2[0].from, Index: 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(Item: IncomingLevel3[0].from, Index: 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(Code: 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, Pos: Source.point(), TUPath: testPath(File: TU.Filename));
139 ASSERT_THAT(Items, ElementsAre(withName("callee")));
140 auto IncomingLevel1 = incomingCalls(Item: Items[0], Index: 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(Item: IncomingLevel1[0].from, Index: 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(Item: IncomingLevel2[0].from, Index: 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(Item: IncomingLevel3[0].from, Index: Index.get());
162 EXPECT_THAT(IncomingLevel4, IsEmpty());
163}
164
165TEST(CallHierarchy, MainFileOnlyRef) {
166 // In addition to testing that we store refs to main-file only symbols,
167 // this tests that anonymous namespaces do not interfere with the
168 // symbol re-identification process in callHierarchyItemToSymbo().
169 Annotations Source(R"cpp(
170 void call^ee(int);
171 namespace {
172 void caller1() {
173 $Callee[[callee]](42);
174 }
175 }
176 void caller2() {
177 $Caller1[[caller1]]();
178 }
179 )cpp");
180 TestTU TU = TestTU::withCode(Code: Source.code());
181 auto AST = TU.build();
182 auto Index = TU.index();
183
184 std::vector<CallHierarchyItem> Items =
185 prepareCallHierarchy(AST, Pos: Source.point(), TUPath: testPath(File: TU.Filename));
186 ASSERT_THAT(Items, ElementsAre(withName("callee")));
187 auto IncomingLevel1 = incomingCalls(Item: Items[0], Index: Index.get());
188 ASSERT_THAT(
189 IncomingLevel1,
190 ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("caller1"))),
191 iFromRanges(Source.range("Callee")))));
192
193 auto IncomingLevel2 = incomingCalls(Item: IncomingLevel1[0].from, Index: Index.get());
194 EXPECT_THAT(
195 IncomingLevel2,
196 ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail("caller2"))),
197 iFromRanges(Source.range("Caller1")))));
198}
199
200TEST(CallHierarchy, IncomingQualified) {
201 Annotations Source(R"cpp(
202 namespace ns {
203 struct Waldo {
204 void find();
205 };
206 void Waldo::find() {}
207 void caller1(Waldo &W) {
208 W.$Caller1[[f^ind]]();
209 }
210 void caller2(Waldo &W) {
211 W.$Caller2[[find]]();
212 }
213 }
214 )cpp");
215 TestTU TU = TestTU::withCode(Code: Source.code());
216 auto AST = TU.build();
217 auto Index = TU.index();
218
219 std::vector<CallHierarchyItem> Items =
220 prepareCallHierarchy(AST, Pos: Source.point(), TUPath: testPath(File: TU.Filename));
221 ASSERT_THAT(Items, ElementsAre(withName("Waldo::find")));
222 auto Incoming = incomingCalls(Item: Items[0], Index: Index.get());
223 EXPECT_THAT(
224 Incoming,
225 ElementsAre(
226 AllOf(from(AllOf(withName("caller1"), withDetail("ns::caller1"))),
227 iFromRanges(Source.range("Caller1"))),
228 AllOf(from(AllOf(withName("caller2"), withDetail("ns::caller2"))),
229 iFromRanges(Source.range("Caller2")))));
230}
231
232TEST(CallHierarchy, OutgoingOneFile) {
233 // Test outgoing call on the main file, with namespaces and methods
234 Annotations Source(R"cpp(
235 void callee(int);
236 namespace ns {
237 struct Foo {
238 void caller1();
239 };
240 void Foo::caller1() {
241 $Callee[[callee]](42);
242 }
243 }
244 namespace {
245 void caller2(ns::Foo& F) {
246 F.$Caller1A[[caller1]]();
247 F.$Caller1B[[caller1]]();
248 }
249 }
250 void call^er3(ns::Foo& F) {
251 F.$Caller1C[[caller1]]();
252 $Caller2[[caller2]](F);
253 }
254 )cpp");
255 TestTU TU = TestTU::withCode(Code: Source.code());
256 auto AST = TU.build();
257 auto Index = TU.index();
258
259 std::vector<CallHierarchyItem> Items =
260 prepareCallHierarchy(AST, Pos: Source.point(), TUPath: testPath(File: TU.Filename));
261 ASSERT_THAT(Items, ElementsAre(withName("caller3")));
262 auto OugoingLevel1 = outgoingCalls(Item: Items[0], Index: Index.get());
263 ASSERT_THAT(
264 OugoingLevel1,
265 ElementsAre(
266 AllOf(to(AllOf(withName("caller1"), withDetail("ns::Foo::caller1"))),
267 oFromRanges(Source.range("Caller1C"))),
268 AllOf(to(AllOf(withName("caller2"), withDetail("caller2"))),
269 oFromRanges(Source.range("Caller2")))));
270
271 auto OutgoingLevel2 = outgoingCalls(Item: OugoingLevel1[1].to, Index: Index.get());
272 ASSERT_THAT(
273 OutgoingLevel2,
274 ElementsAre(AllOf(
275 to(AllOf(withName("caller1"), withDetail("ns::Foo::caller1"))),
276 oFromRanges(Source.range("Caller1A"), Source.range("Caller1B")))));
277
278 auto OutgoingLevel3 = outgoingCalls(Item: OutgoingLevel2[0].to, Index: Index.get());
279 ASSERT_THAT(
280 OutgoingLevel3,
281 ElementsAre(AllOf(to(AllOf(withName("callee"), withDetail("callee"))),
282 oFromRanges(Source.range("Callee")))));
283
284 auto OutgoingLevel4 = outgoingCalls(Item: OutgoingLevel3[0].to, Index: Index.get());
285 EXPECT_THAT(OutgoingLevel4, IsEmpty());
286}
287
288TEST(CallHierarchy, MultiFileCpp) {
289 // The test uses a .hh suffix for header files to get clang
290 // to parse them in C++ mode. .h files are parsed in C mode
291 // by default, which causes problems because e.g. symbol
292 // USRs are different in C mode (do not include function signatures).
293
294 Annotations CalleeH(R"cpp(
295 void calle^e(int);
296 )cpp");
297 Annotations CalleeC(R"cpp(
298 #include "callee.hh"
299 void calle^e(int) {}
300 )cpp");
301 Annotations Caller1H(R"cpp(
302 namespace nsa {
303 void caller1();
304 }
305 )cpp");
306 Annotations Caller1C(R"cpp(
307 #include "callee.hh"
308 #include "caller1.hh"
309 namespace nsa {
310 void caller1() {
311 [[calle^e]](42);
312 }
313 }
314 )cpp");
315 Annotations Caller2H(R"cpp(
316 namespace nsb {
317 void caller2();
318 }
319 )cpp");
320 Annotations Caller2C(R"cpp(
321 #include "caller1.hh"
322 #include "caller2.hh"
323 namespace nsb {
324 void caller2() {
325 nsa::$A[[caller1]]();
326 nsa::$B[[caller1]]();
327 }
328 }
329 )cpp");
330 Annotations Caller3H(R"cpp(
331 namespace nsa {
332 void call^er3();
333 }
334 )cpp");
335 Annotations Caller3C(R"cpp(
336 #include "caller1.hh"
337 #include "caller2.hh"
338 namespace nsa {
339 void call^er3() {
340 $Caller1[[caller1]]();
341 nsb::$Caller2[[caller2]]();
342 }
343 }
344 )cpp");
345
346 TestWorkspace Workspace;
347 Workspace.addSource(Filename: "callee.hh", Code: CalleeH.code());
348 Workspace.addSource(Filename: "caller1.hh", Code: Caller1H.code());
349 Workspace.addSource(Filename: "caller2.hh", Code: Caller2H.code());
350 Workspace.addSource(Filename: "caller3.hh", Code: Caller3H.code());
351 Workspace.addMainFile(Filename: "callee.cc", Code: CalleeC.code());
352 Workspace.addMainFile(Filename: "caller1.cc", Code: Caller1C.code());
353 Workspace.addMainFile(Filename: "caller2.cc", Code: Caller2C.code());
354 Workspace.addMainFile(Filename: "caller3.cc", Code: Caller3C.code());
355
356 auto Index = Workspace.index();
357
358 auto CheckIncomingCalls = [&](ParsedAST &AST, Position Pos, PathRef TUPath) {
359 std::vector<CallHierarchyItem> Items =
360 prepareCallHierarchy(AST, Pos, TUPath);
361 ASSERT_THAT(Items, ElementsAre(withName("callee")));
362 auto IncomingLevel1 = incomingCalls(Item: Items[0], Index: Index.get());
363 ASSERT_THAT(IncomingLevel1,
364 ElementsAre(AllOf(from(AllOf(withName("caller1"),
365 withDetail("nsa::caller1"))),
366 iFromRanges(Caller1C.range()))));
367
368 auto IncomingLevel2 = incomingCalls(Item: IncomingLevel1[0].from, Index: Index.get());
369 ASSERT_THAT(
370 IncomingLevel2,
371 ElementsAre(
372 AllOf(from(AllOf(withName("caller2"), withDetail("nsb::caller2"))),
373 iFromRanges(Caller2C.range("A"), Caller2C.range("B"))),
374 AllOf(from(AllOf(withName("caller3"), withDetail("nsa::caller3"))),
375 iFromRanges(Caller3C.range("Caller1")))));
376
377 auto IncomingLevel3 = incomingCalls(Item: IncomingLevel2[0].from, Index: Index.get());
378 ASSERT_THAT(IncomingLevel3,
379 ElementsAre(AllOf(from(AllOf(withName("caller3"),
380 withDetail("nsa::caller3"))),
381 iFromRanges(Caller3C.range("Caller2")))));
382
383 auto IncomingLevel4 = incomingCalls(Item: IncomingLevel3[0].from, Index: Index.get());
384 EXPECT_THAT(IncomingLevel4, IsEmpty());
385 };
386
387 auto CheckOutgoingCalls = [&](ParsedAST &AST, Position Pos, PathRef TUPath,
388 bool IsDeclaration) {
389 std::vector<CallHierarchyItem> Items =
390 prepareCallHierarchy(AST, Pos, TUPath);
391 ASSERT_THAT(
392 Items,
393 ElementsAre(AllOf(
394 withName("caller3"),
395 withFile(testPath(IsDeclaration ? "caller3.hh" : "caller3.cc")))));
396 auto OutgoingLevel1 = outgoingCalls(Item: Items[0], Index: Index.get());
397 ASSERT_THAT(
398 OutgoingLevel1,
399 // fromRanges are interpreted in the context of Items[0]'s file.
400 // If that's the header, we can't get ranges from the implementation
401 // file!
402 ElementsAre(
403 AllOf(to(AllOf(withName("caller1"), withDetail("nsa::caller1"))),
404 IsDeclaration ? oFromRanges()
405 : oFromRanges(Caller3C.range("Caller1"))),
406 AllOf(to(AllOf(withName("caller2"), withDetail("nsb::caller2"))),
407 IsDeclaration ? oFromRanges()
408 : oFromRanges(Caller3C.range("Caller2")))));
409
410 auto OutgoingLevel2 = outgoingCalls(Item: OutgoingLevel1[1].to, Index: Index.get());
411 ASSERT_THAT(OutgoingLevel2,
412 ElementsAre(AllOf(
413 to(AllOf(withName("caller1"), withDetail("nsa::caller1"))),
414 oFromRanges(Caller2C.range("A"), Caller2C.range("B")))));
415
416 auto OutgoingLevel3 = outgoingCalls(Item: OutgoingLevel2[0].to, Index: Index.get());
417 ASSERT_THAT(
418 OutgoingLevel3,
419 ElementsAre(AllOf(to(AllOf(withName("callee"), withDetail("callee"))),
420 oFromRanges(Caller1C.range()))));
421
422 auto OutgoingLevel4 = outgoingCalls(Item: OutgoingLevel3[0].to, Index: Index.get());
423 EXPECT_THAT(OutgoingLevel4, IsEmpty());
424 };
425
426 // Check that invoking from a call site works.
427 auto AST = Workspace.openFile(Filename: "caller1.cc");
428 ASSERT_TRUE(bool(AST));
429 CheckIncomingCalls(*AST, Caller1C.point(), testPath(File: "caller1.cc"));
430
431 // Check that invoking from the declaration site works.
432 AST = Workspace.openFile(Filename: "callee.hh");
433 ASSERT_TRUE(bool(AST));
434 CheckIncomingCalls(*AST, CalleeH.point(), testPath(File: "callee.hh"));
435 AST = Workspace.openFile(Filename: "caller3.hh");
436 ASSERT_TRUE(bool(AST));
437 CheckOutgoingCalls(*AST, Caller3H.point(), testPath(File: "caller3.hh"), true);
438
439 // Check that invoking from the definition site works.
440 AST = Workspace.openFile(Filename: "callee.cc");
441 ASSERT_TRUE(bool(AST));
442 CheckIncomingCalls(*AST, CalleeC.point(), testPath(File: "callee.cc"));
443 AST = Workspace.openFile(Filename: "caller3.cc");
444 ASSERT_TRUE(bool(AST));
445 CheckOutgoingCalls(*AST, Caller3C.point(), testPath(File: "caller3.cc"), false);
446}
447
448TEST(CallHierarchy, IncomingMultiFileObjC) {
449 // The test uses a .mi suffix for header files to get clang
450 // to parse them in ObjC mode. .h files are parsed in C mode
451 // by default, which causes problems because e.g. symbol
452 // USRs are different in C mode (do not include function signatures).
453
454 Annotations CalleeH(R"objc(
455 @interface CalleeClass
456 +(void)call^ee;
457 @end
458 )objc");
459 Annotations CalleeC(R"objc(
460 #import "callee.mi"
461 @implementation CalleeClass {}
462 +(void)call^ee {}
463 @end
464 )objc");
465 Annotations Caller1H(R"objc(
466 @interface Caller1Class
467 +(void)caller1;
468 @end
469 )objc");
470 Annotations Caller1C(R"objc(
471 #import "callee.mi"
472 #import "caller1.mi"
473 @implementation Caller1Class {}
474 +(void)caller1 {
475 [CalleeClass [[calle^e]]];
476 }
477 @end
478 )objc");
479 Annotations Caller2H(R"objc(
480 @interface Caller2Class
481 +(void)caller2;
482 @end
483 )objc");
484 Annotations Caller2C(R"objc(
485 #import "caller1.mi"
486 #import "caller2.mi"
487 @implementation Caller2Class {}
488 +(void)caller2 {
489 [Caller1Class $A[[caller1]]];
490 [Caller1Class $B[[caller1]]];
491 }
492 @end
493 )objc");
494 Annotations Caller3C(R"objc(
495 #import "caller1.mi"
496 #import "caller2.mi"
497 @implementation Caller3Class {}
498 +(void)caller3 {
499 [Caller1Class $Caller1[[caller1]]];
500 [Caller2Class $Caller2[[caller2]]];
501 }
502 @end
503 )objc");
504
505 TestWorkspace Workspace;
506 Workspace.addSource(Filename: "callee.mi", Code: CalleeH.code());
507 Workspace.addSource(Filename: "caller1.mi", Code: Caller1H.code());
508 Workspace.addSource(Filename: "caller2.mi", Code: Caller2H.code());
509 Workspace.addMainFile(Filename: "callee.m", Code: CalleeC.code());
510 Workspace.addMainFile(Filename: "caller1.m", Code: Caller1C.code());
511 Workspace.addMainFile(Filename: "caller2.m", Code: Caller2C.code());
512 Workspace.addMainFile(Filename: "caller3.m", Code: Caller3C.code());
513 auto Index = Workspace.index();
514
515 auto CheckCallHierarchy = [&](ParsedAST &AST, Position Pos, PathRef TUPath) {
516 std::vector<CallHierarchyItem> Items =
517 prepareCallHierarchy(AST, Pos, TUPath);
518 ASSERT_THAT(Items, ElementsAre(withName("callee")));
519 auto IncomingLevel1 = incomingCalls(Item: Items[0], Index: Index.get());
520 ASSERT_THAT(IncomingLevel1,
521 ElementsAre(AllOf(from(withName("caller1")),
522 iFromRanges(Caller1C.range()))));
523
524 auto IncomingLevel2 = incomingCalls(Item: IncomingLevel1[0].from, Index: Index.get());
525 ASSERT_THAT(IncomingLevel2,
526 ElementsAre(AllOf(from(withName("caller2")),
527 iFromRanges(Caller2C.range("A"),
528 Caller2C.range("B"))),
529 AllOf(from(withName("caller3")),
530 iFromRanges(Caller3C.range("Caller1")))));
531
532 auto IncomingLevel3 = incomingCalls(Item: IncomingLevel2[0].from, Index: Index.get());
533 ASSERT_THAT(IncomingLevel3,
534 ElementsAre(AllOf(from(withName("caller3")),
535 iFromRanges(Caller3C.range("Caller2")))));
536
537 auto IncomingLevel4 = incomingCalls(Item: IncomingLevel3[0].from, Index: Index.get());
538 EXPECT_THAT(IncomingLevel4, IsEmpty());
539 };
540
541 // Check that invoking from a call site works.
542 auto AST = Workspace.openFile(Filename: "caller1.m");
543 ASSERT_TRUE(bool(AST));
544 CheckCallHierarchy(*AST, Caller1C.point(), testPath(File: "caller1.m"));
545
546 // Check that invoking from the declaration site works.
547 AST = Workspace.openFile(Filename: "callee.mi");
548 ASSERT_TRUE(bool(AST));
549 CheckCallHierarchy(*AST, CalleeH.point(), testPath(File: "callee.mi"));
550
551 // Check that invoking from the definition site works.
552 AST = Workspace.openFile(Filename: "callee.m");
553 ASSERT_TRUE(bool(AST));
554 CheckCallHierarchy(*AST, CalleeC.point(), testPath(File: "callee.m"));
555}
556
557TEST(CallHierarchy, CallInLocalVarDecl) {
558 // Tests that local variable declarations are not treated as callers
559 // (they're not indexed, so they can't be represented as call hierarchy
560 // items); instead, the caller should be the containing function.
561 // However, namespace-scope variable declarations should be treated as
562 // callers because those are indexed and there is no enclosing entity
563 // that would be a useful caller.
564 Annotations Source(R"cpp(
565 int call^ee();
566 void caller1() {
567 $call1[[callee]]();
568 }
569 void caller2() {
570 int localVar = $call2[[callee]]();
571 }
572 int caller3 = $call3[[callee]]();
573 )cpp");
574 TestTU TU = TestTU::withCode(Code: Source.code());
575 auto AST = TU.build();
576 auto Index = TU.index();
577
578 std::vector<CallHierarchyItem> Items =
579 prepareCallHierarchy(AST, Pos: Source.point(), TUPath: testPath(File: TU.Filename));
580 ASSERT_THAT(Items, ElementsAre(withName("callee")));
581
582 auto Incoming = incomingCalls(Item: Items[0], Index: Index.get());
583 ASSERT_THAT(Incoming, ElementsAre(AllOf(from(withName("caller1")),
584 iFromRanges(Source.range("call1"))),
585 AllOf(from(withName("caller2")),
586 iFromRanges(Source.range("call2"))),
587 AllOf(from(withName("caller3")),
588 iFromRanges(Source.range("call3")))));
589}
590
591TEST(CallHierarchy, HierarchyOnField) {
592 // Tests that the call hierarchy works on fields.
593 Annotations Source(R"cpp(
594 struct Vars {
595 int v^ar1 = 1;
596 };
597 void caller() {
598 Vars values;
599 values.$Callee[[var1]];
600 }
601 )cpp");
602 TestTU TU = TestTU::withCode(Code: Source.code());
603 auto AST = TU.build();
604 auto Index = TU.index();
605
606 std::vector<CallHierarchyItem> Items =
607 prepareCallHierarchy(AST, Pos: Source.point(), TUPath: testPath(File: TU.Filename));
608 ASSERT_THAT(Items, ElementsAre(withName("var1")));
609 auto IncomingLevel1 = incomingCalls(Item: Items[0], Index: Index.get());
610 ASSERT_THAT(IncomingLevel1,
611 ElementsAre(AllOf(from(withName("caller")),
612 iFromRanges(Source.range("Callee")))));
613}
614
615TEST(CallHierarchy, HierarchyOnVar) {
616 // Tests that the call hierarchy works on non-local variables.
617 Annotations Source(R"cpp(
618 int v^ar = 1;
619 void caller() {
620 $Callee[[var]];
621 }
622 )cpp");
623 TestTU TU = TestTU::withCode(Code: Source.code());
624 auto AST = TU.build();
625 auto Index = TU.index();
626
627 std::vector<CallHierarchyItem> Items =
628 prepareCallHierarchy(AST, Pos: Source.point(), TUPath: testPath(File: TU.Filename));
629 ASSERT_THAT(Items, ElementsAre(withName("var")));
630 auto IncomingLevel1 = incomingCalls(Item: Items[0], Index: Index.get());
631 ASSERT_THAT(IncomingLevel1,
632 ElementsAre(AllOf(from(withName("caller")),
633 iFromRanges(Source.range("Callee")))));
634}
635
636TEST(CallHierarchy, CallInDifferentFileThanCaller) {
637 Annotations Header(R"cpp(
638 #define WALDO void caller() {
639 )cpp");
640 Annotations Source(R"cpp(
641 void call^ee();
642 WALDO
643 callee();
644 }
645 )cpp");
646 auto TU = TestTU::withCode(Code: Source.code());
647 TU.HeaderCode = Header.code();
648 auto AST = TU.build();
649 auto Index = TU.index();
650
651 std::vector<CallHierarchyItem> Items =
652 prepareCallHierarchy(AST, Pos: Source.point(), TUPath: testPath(File: TU.Filename));
653 ASSERT_THAT(Items, ElementsAre(withName("callee")));
654
655 auto Incoming = incomingCalls(Item: Items[0], Index: Index.get());
656
657 // The only call site is in the source file, which is a different file from
658 // the declaration of the function containing the call, which is in the
659 // header. The protocol does not allow us to represent such calls, so we drop
660 // them. (The call hierarchy item itself is kept.)
661 EXPECT_THAT(Incoming,
662 ElementsAre(AllOf(from(withName("caller")), iFromRanges())));
663}
664
665} // namespace
666} // namespace clangd
667} // namespace clang
668

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp