1 | //===-- RenameTests.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 | |
9 | #include "Annotations.h" |
10 | #include "ClangdServer.h" |
11 | #include "SyncAPI.h" |
12 | #include "TestFS.h" |
13 | #include "TestTU.h" |
14 | #include "index/Ref.h" |
15 | #include "refactor/Rename.h" |
16 | #include "support/TestTracer.h" |
17 | #include "clang/Tooling/Core/Replacement.h" |
18 | #include "llvm/ADT/STLExtras.h" |
19 | #include "llvm/Support/MemoryBuffer.h" |
20 | #include <algorithm> |
21 | #include "gmock/gmock.h" |
22 | #include "gtest/gtest.h" |
23 | |
24 | namespace clang { |
25 | namespace clangd { |
26 | namespace { |
27 | |
28 | using testing::ElementsAre; |
29 | using testing::Eq; |
30 | using testing::IsEmpty; |
31 | using testing::Pair; |
32 | using testing::SizeIs; |
33 | using testing::UnorderedElementsAre; |
34 | using testing::UnorderedElementsAreArray; |
35 | |
36 | llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> |
37 | createOverlay(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> Base, |
38 | llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> Overlay) { |
39 | auto OFS = |
40 | llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(A: std::move(Base)); |
41 | OFS->pushOverlay(FS: std::move(Overlay)); |
42 | return OFS; |
43 | } |
44 | |
45 | llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> getVFSFromAST(ParsedAST &AST) { |
46 | return &AST.getSourceManager().getFileManager().getVirtualFileSystem(); |
47 | } |
48 | |
49 | // Convert a Range to a Ref. |
50 | Ref refWithRange(const clangd::Range &Range, const std::string &URI) { |
51 | Ref Result; |
52 | Result.Kind = RefKind::Reference | RefKind::Spelled; |
53 | Result.Location.Start.setLine(Range.start.line); |
54 | Result.Location.Start.setColumn(Range.start.character); |
55 | Result.Location.End.setLine(Range.end.line); |
56 | Result.Location.End.setColumn(Range.end.character); |
57 | Result.Location.FileURI = URI.c_str(); |
58 | return Result; |
59 | } |
60 | |
61 | // Build a RefSlab from all marked ranges in the annotation. The ranges are |
62 | // assumed to associate with the given SymbolName. |
63 | std::unique_ptr<RefSlab> buildRefSlab(const Annotations &Code, |
64 | llvm::StringRef SymbolName, |
65 | llvm::StringRef Path) { |
66 | RefSlab::Builder Builder; |
67 | TestTU TU; |
68 | TU.HeaderCode = std::string(Code.code()); |
69 | auto Symbols = TU.headerSymbols(); |
70 | const auto &SymbolID = findSymbol(Symbols, QName: SymbolName).ID; |
71 | std::string PathURI = URI::create(AbsolutePath: Path).toString(); |
72 | for (const auto &Range : Code.ranges()) |
73 | Builder.insert(ID: SymbolID, S: refWithRange(Range, URI: PathURI)); |
74 | |
75 | return std::make_unique<RefSlab>(args: std::move(Builder).build()); |
76 | } |
77 | |
78 | std::vector< |
79 | std::pair</*FilePath*/ std::string, /*CodeAfterRename*/ std::string>> |
80 | applyEdits(FileEdits FE) { |
81 | std::vector<std::pair<std::string, std::string>> Results; |
82 | for (auto &It : FE) |
83 | Results.emplace_back( |
84 | args: It.first().str(), |
85 | args: llvm::cantFail(ValOrErr: tooling::applyAllReplacements( |
86 | Code: It.getValue().InitialCode, Replaces: It.getValue().Replacements))); |
87 | return Results; |
88 | } |
89 | |
90 | // Generates an expected rename result by replacing all ranges in the given |
91 | // annotation with the NewName. |
92 | std::string expectedResult(Annotations Test, llvm::StringRef NewName) { |
93 | std::string Result; |
94 | unsigned NextChar = 0; |
95 | llvm::StringRef Code = Test.code(); |
96 | for (const auto &R : Test.llvm::Annotations::ranges()) { |
97 | assert(R.Begin <= R.End && NextChar <= R.Begin); |
98 | Result += Code.substr(Start: NextChar, N: R.Begin - NextChar); |
99 | Result += NewName; |
100 | NextChar = R.End; |
101 | } |
102 | Result += Code.substr(Start: NextChar); |
103 | return Result; |
104 | } |
105 | |
106 | std::vector<SymbolRange> symbolRanges(llvm::ArrayRef<Range> Ranges) { |
107 | std::vector<SymbolRange> Result; |
108 | for (const auto &R : Ranges) |
109 | Result.emplace_back(args: R); |
110 | return Result; |
111 | } |
112 | |
113 | TEST(RenameTest, WithinFileRename) { |
114 | // For each "^" this test moves cursor to its location and applies renaming |
115 | // while checking that all identifiers in [[]] ranges are also renamed. |
116 | llvm::StringRef Tests[] = { |
117 | // Function. |
118 | R"cpp( |
119 | void [[foo^]]() { |
120 | [[fo^o]](); |
121 | } |
122 | )cpp" , |
123 | |
124 | // Type. |
125 | R"cpp( |
126 | struct [[foo^]] {}; |
127 | [[foo]] test() { |
128 | [[f^oo]] x; |
129 | return x; |
130 | } |
131 | )cpp" , |
132 | |
133 | // Local variable. |
134 | R"cpp( |
135 | void bar() { |
136 | if (auto [[^foo]] = 5) { |
137 | [[foo]] = 3; |
138 | } |
139 | } |
140 | )cpp" , |
141 | |
142 | // Class, its constructor and destructor. |
143 | R"cpp( |
144 | class [[F^oo]] { |
145 | [[F^oo]](); |
146 | ~[[F^oo]](); |
147 | [[F^oo]] *foo(int x); |
148 | |
149 | [[F^oo]] *Ptr; |
150 | }; |
151 | [[F^oo]]::[[Fo^o]]() {} |
152 | [[F^oo]]::~[[Fo^o]]() {} |
153 | [[F^oo]] *[[F^oo]]::foo(int x) { return Ptr; } |
154 | )cpp" , |
155 | |
156 | // Template class, its constructor and destructor. |
157 | R"cpp( |
158 | template <typename T> |
159 | class [[F^oo]] { |
160 | [[F^oo]](); |
161 | ~[[F^oo]](); |
162 | void f([[F^oo]] x); |
163 | }; |
164 | |
165 | template<typename T> |
166 | [[F^oo]]<T>::[[Fo^o]]() {} |
167 | |
168 | template<typename T> |
169 | [[F^oo]]<T>::~[[Fo^o]]() {} |
170 | )cpp" , |
171 | |
172 | // Template class constructor. |
173 | R"cpp( |
174 | class [[F^oo]] { |
175 | template<typename T> |
176 | [[Fo^o]](); |
177 | |
178 | template<typename T> |
179 | [[F^oo]](T t); |
180 | }; |
181 | |
182 | template<typename T> |
183 | [[F^oo]]::[[Fo^o]]() {} |
184 | )cpp" , |
185 | |
186 | // Class in template argument. |
187 | R"cpp( |
188 | class [[F^oo]] {}; |
189 | template <typename T> void func(); |
190 | template <typename T> class Baz {}; |
191 | int main() { |
192 | func<[[F^oo]]>(); |
193 | Baz<[[F^oo]]> obj; |
194 | return 0; |
195 | } |
196 | )cpp" , |
197 | |
198 | // Forward class declaration without definition. |
199 | R"cpp( |
200 | class [[F^oo]]; |
201 | [[F^oo]] *f(); |
202 | )cpp" , |
203 | |
204 | // Member function. |
205 | R"cpp( |
206 | struct X { |
207 | void [[F^oo]]() {} |
208 | void Baz() { [[F^oo]](); } |
209 | }; |
210 | )cpp" , |
211 | |
212 | // Templated method instantiation. |
213 | R"cpp( |
214 | template<typename T> |
215 | class Foo { |
216 | public: |
217 | static T [[f^oo]]() { return T(); } |
218 | }; |
219 | |
220 | void bar() { |
221 | Foo<int>::[[f^oo]](); |
222 | } |
223 | )cpp" , |
224 | R"cpp( |
225 | template<typename T> |
226 | class Foo { |
227 | public: |
228 | T [[f^oo]]() { return T(); } |
229 | }; |
230 | |
231 | void bar() { |
232 | Foo<int>().[[f^oo]](); |
233 | } |
234 | )cpp" , |
235 | |
236 | // Template class (partial) specializations. |
237 | R"cpp( |
238 | template <typename T> |
239 | class [[F^oo]] {}; |
240 | |
241 | template<> |
242 | class [[F^oo]]<bool> {}; |
243 | template <typename T> |
244 | class [[F^oo]]<T*> {}; |
245 | |
246 | void test() { |
247 | [[F^oo]]<int> x; |
248 | [[F^oo]]<bool> y; |
249 | [[F^oo]]<int*> z; |
250 | } |
251 | )cpp" , |
252 | |
253 | // Incomplete class specializations |
254 | R"cpp( |
255 | template <typename T> |
256 | class [[Fo^o]] {}; |
257 | void func([[F^oo]]<int>); |
258 | )cpp" , |
259 | |
260 | // Template class instantiations. |
261 | R"cpp( |
262 | template <typename T> |
263 | class [[F^oo]] { |
264 | public: |
265 | T foo(T arg, T& ref, T* ptr) { |
266 | T value; |
267 | int number = 42; |
268 | value = (T)number; |
269 | value = static_cast<T>(number); |
270 | return value; |
271 | } |
272 | static void foo(T value) {} |
273 | T member; |
274 | }; |
275 | |
276 | template <typename T> |
277 | void func() { |
278 | [[F^oo]]<T> obj; |
279 | obj.member = T(); |
280 | [[Foo]]<T>::foo(); |
281 | } |
282 | |
283 | void test() { |
284 | [[F^oo]]<int> i; |
285 | i.member = 0; |
286 | [[F^oo]]<int>::foo(0); |
287 | |
288 | [[F^oo]]<bool> b; |
289 | b.member = false; |
290 | [[F^oo]]<bool>::foo(false); |
291 | } |
292 | )cpp" , |
293 | |
294 | // Template class methods. |
295 | R"cpp( |
296 | template <typename T> |
297 | class A { |
298 | public: |
299 | void [[f^oo]]() {} |
300 | }; |
301 | |
302 | void func() { |
303 | A<int>().[[f^oo]](); |
304 | A<double>().[[f^oo]](); |
305 | A<float>().[[f^oo]](); |
306 | } |
307 | )cpp" , |
308 | |
309 | // Templated class specialization. |
310 | R"cpp( |
311 | template<typename T, typename U=bool> |
312 | class [[Foo^]]; |
313 | |
314 | template<typename T, typename U> |
315 | class [[Foo^]] {}; |
316 | |
317 | template<typename T=int, typename U> |
318 | class [[Foo^]]; |
319 | )cpp" , |
320 | R"cpp( |
321 | template<typename T=float, typename U=int> |
322 | class [[Foo^]]; |
323 | |
324 | template<typename T, typename U> |
325 | class [[Foo^]] {}; |
326 | )cpp" , |
327 | |
328 | // Function template specialization. |
329 | R"cpp( |
330 | template<typename T=int, typename U=bool> |
331 | U [[foo^]](); |
332 | |
333 | template<typename T, typename U> |
334 | U [[foo^]]() {}; |
335 | )cpp" , |
336 | R"cpp( |
337 | template<typename T, typename U> |
338 | U [[foo^]]() {}; |
339 | |
340 | template<typename T=int, typename U=bool> |
341 | U [[foo^]](); |
342 | )cpp" , |
343 | R"cpp( |
344 | template<typename T=int, typename U=bool> |
345 | U [[foo^]](); |
346 | |
347 | template<typename T, typename U> |
348 | U [[foo^]](); |
349 | )cpp" , |
350 | R"cpp( |
351 | template <typename T> |
352 | void [[f^oo]](T t); |
353 | |
354 | template <> |
355 | void [[f^oo]](int a); |
356 | |
357 | void test() { |
358 | [[f^oo]]<double>(1); |
359 | } |
360 | )cpp" , |
361 | |
362 | // Variable template. |
363 | R"cpp( |
364 | template <typename T, int U> |
365 | bool [[F^oo]] = true; |
366 | |
367 | // Explicit template specialization |
368 | template <> |
369 | bool [[F^oo]]<int, 0> = false; |
370 | |
371 | // Partial template specialization |
372 | template <typename T> |
373 | bool [[F^oo]]<T, 1> = false; |
374 | |
375 | void foo() { |
376 | // Ref to the explicit template specialization |
377 | [[F^oo]]<int, 0>; |
378 | // Ref to the primary template. |
379 | [[F^oo]]<double, 2>; |
380 | } |
381 | )cpp" , |
382 | |
383 | // Complicated class type. |
384 | R"cpp( |
385 | // Forward declaration. |
386 | class [[Fo^o]]; |
387 | class Baz { |
388 | virtual int getValue() const = 0; |
389 | }; |
390 | |
391 | class [[F^oo]] : public Baz { |
392 | public: |
393 | [[F^oo]](int value = 0) : x(value) {} |
394 | |
395 | [[F^oo]] &operator++(int); |
396 | |
397 | bool operator<([[Foo]] const &rhs); |
398 | int getValue() const; |
399 | private: |
400 | int x; |
401 | }; |
402 | |
403 | void func() { |
404 | [[F^oo]] *Pointer = 0; |
405 | [[F^oo]] Variable = [[Foo]](10); |
406 | for ([[F^oo]] it; it < Variable; it++); |
407 | const [[F^oo]] *C = new [[Foo]](); |
408 | const_cast<[[F^oo]] *>(C)->getValue(); |
409 | [[F^oo]] foo; |
410 | const Baz &BazReference = foo; |
411 | const Baz *BazPointer = &foo; |
412 | reinterpret_cast<const [[^Foo]] *>(BazPointer)->getValue(); |
413 | static_cast<const [[^Foo]] &>(BazReference).getValue(); |
414 | static_cast<const [[^Foo]] *>(BazPointer)->getValue(); |
415 | } |
416 | )cpp" , |
417 | |
418 | // Static class member. |
419 | R"cpp( |
420 | struct Foo { |
421 | static Foo *[[Static^Member]]; |
422 | }; |
423 | |
424 | Foo* Foo::[[Static^Member]] = nullptr; |
425 | |
426 | void foo() { |
427 | Foo* Pointer = Foo::[[Static^Member]]; |
428 | } |
429 | )cpp" , |
430 | |
431 | // Reference in lambda parameters. |
432 | R"cpp( |
433 | template <class T> |
434 | class function; |
435 | template <class R, class... ArgTypes> |
436 | class function<R(ArgTypes...)> { |
437 | public: |
438 | template <typename Functor> |
439 | function(Functor f) {} |
440 | |
441 | function() {} |
442 | |
443 | R operator()(ArgTypes...) const {} |
444 | }; |
445 | |
446 | namespace ns { |
447 | class [[Old]] {}; |
448 | void f() { |
449 | function<void([[Old]])> func; |
450 | } |
451 | } // namespace ns |
452 | )cpp" , |
453 | |
454 | // Destructor explicit call. |
455 | R"cpp( |
456 | class [[F^oo]] { |
457 | public: |
458 | ~[[^Foo]](); |
459 | }; |
460 | |
461 | [[Foo^]]::~[[^Foo]]() {} |
462 | |
463 | int main() { |
464 | [[Fo^o]] f; |
465 | f.~/*something*/[[^Foo]](); |
466 | f.~[[^Foo]](); |
467 | } |
468 | )cpp" , |
469 | |
470 | // Derived destructor explicit call. |
471 | R"cpp( |
472 | class [[Bas^e]] {}; |
473 | class Derived : public [[Bas^e]] {}; |
474 | |
475 | int main() { |
476 | [[Bas^e]] *foo = new Derived(); |
477 | foo->[[^Base]]::~[[^Base]](); |
478 | } |
479 | )cpp" , |
480 | |
481 | // CXXConstructor initializer list. |
482 | R"cpp( |
483 | class Baz {}; |
484 | class Qux { |
485 | Baz [[F^oo]]; |
486 | public: |
487 | Qux(); |
488 | }; |
489 | Qux::Qux() : [[F^oo]]() {} |
490 | )cpp" , |
491 | |
492 | // DeclRefExpr. |
493 | R"cpp( |
494 | class C { |
495 | public: |
496 | static int [[F^oo]]; |
497 | }; |
498 | |
499 | int foo(int x); |
500 | #define MACRO(a) foo(a) |
501 | |
502 | void func() { |
503 | C::[[F^oo]] = 1; |
504 | MACRO(C::[[Foo]]); |
505 | int y = C::[[F^oo]]; |
506 | } |
507 | )cpp" , |
508 | |
509 | // Macros. |
510 | R"cpp( |
511 | // no rename inside macro body. |
512 | #define M1 foo |
513 | #define M2(x) x |
514 | int [[fo^o]](); |
515 | void boo(int); |
516 | |
517 | void qoo() { |
518 | [[f^oo]](); |
519 | boo([[f^oo]]()); |
520 | M1(); |
521 | boo(M1()); |
522 | M2([[f^oo]]()); |
523 | M2(M1()); // foo is inside the nested macro body. |
524 | } |
525 | )cpp" , |
526 | |
527 | // MemberExpr in macros |
528 | R"cpp( |
529 | class Baz { |
530 | public: |
531 | int [[F^oo]]; |
532 | }; |
533 | int qux(int x); |
534 | #define MACRO(a) qux(a) |
535 | |
536 | int main() { |
537 | Baz baz; |
538 | baz.[[F^oo]] = 1; |
539 | MACRO(baz.[[F^oo]]); |
540 | int y = baz.[[F^oo]]; |
541 | } |
542 | )cpp" , |
543 | |
544 | // Fields in classes & partial and full specialiations. |
545 | R"cpp( |
546 | template<typename T> |
547 | struct Foo { |
548 | T [[Vari^able]] = 42; |
549 | }; |
550 | |
551 | void foo() { |
552 | Foo<int> f; |
553 | f.[[Varia^ble]] = 9000; |
554 | } |
555 | )cpp" , |
556 | R"cpp( |
557 | template<typename T, typename U> |
558 | struct Foo { |
559 | T Variable[42]; |
560 | U Another; |
561 | |
562 | void bar() {} |
563 | }; |
564 | |
565 | template<typename T> |
566 | struct Foo<T, bool> { |
567 | T [[Var^iable]]; |
568 | void bar() { ++[[Var^iable]]; } |
569 | }; |
570 | |
571 | void foo() { |
572 | Foo<unsigned, bool> f; |
573 | f.[[Var^iable]] = 9000; |
574 | } |
575 | )cpp" , |
576 | R"cpp( |
577 | template<typename T, typename U> |
578 | struct Foo { |
579 | T Variable[42]; |
580 | U Another; |
581 | |
582 | void bar() {} |
583 | }; |
584 | |
585 | template<typename T> |
586 | struct Foo<T, bool> { |
587 | T Variable; |
588 | void bar() { ++Variable; } |
589 | }; |
590 | |
591 | template<> |
592 | struct Foo<unsigned, bool> { |
593 | unsigned [[Var^iable]]; |
594 | void bar() { ++[[Var^iable]]; } |
595 | }; |
596 | |
597 | void foo() { |
598 | Foo<unsigned, bool> f; |
599 | f.[[Var^iable]] = 9000; |
600 | } |
601 | )cpp" , |
602 | // Static fields. |
603 | R"cpp( |
604 | struct Foo { |
605 | static int [[Var^iable]]; |
606 | }; |
607 | |
608 | int Foo::[[Var^iable]] = 42; |
609 | |
610 | void foo() { |
611 | int LocalInt = Foo::[[Var^iable]]; |
612 | } |
613 | )cpp" , |
614 | R"cpp( |
615 | template<typename T> |
616 | struct Foo { |
617 | static T [[Var^iable]]; |
618 | }; |
619 | |
620 | template <> |
621 | int Foo<int>::[[Var^iable]] = 42; |
622 | |
623 | template <> |
624 | bool Foo<bool>::[[Var^iable]] = true; |
625 | |
626 | void foo() { |
627 | int LocalInt = Foo<int>::[[Var^iable]]; |
628 | bool LocalBool = Foo<bool>::[[Var^iable]]; |
629 | } |
630 | )cpp" , |
631 | |
632 | // Template parameters. |
633 | R"cpp( |
634 | template <typename [[^T]]> |
635 | class Foo { |
636 | [[T^]] foo([[T^]] arg, [[T^]]& ref, [[^T]]* ptr) { |
637 | [[T]] value; |
638 | int number = 42; |
639 | value = ([[T^]])number; |
640 | value = static_cast<[[^T]]>(number); |
641 | return value; |
642 | } |
643 | static void foo([[T^]] value) {} |
644 | [[T^]] member; |
645 | }; |
646 | )cpp" , |
647 | |
648 | // Typedef. |
649 | R"cpp( |
650 | namespace ns { |
651 | class basic_string {}; |
652 | typedef basic_string [[s^tring]]; |
653 | } // namespace ns |
654 | |
655 | ns::[[s^tring]] foo(); |
656 | )cpp" , |
657 | |
658 | // Variable. |
659 | R"cpp( |
660 | namespace A { |
661 | int [[F^oo]]; |
662 | } |
663 | int Foo; |
664 | int Qux = Foo; |
665 | int Baz = A::[[^Foo]]; |
666 | void fun() { |
667 | struct { |
668 | int Foo; |
669 | } b = {100}; |
670 | int Foo = 100; |
671 | Baz = Foo; |
672 | { |
673 | extern int Foo; |
674 | Baz = Foo; |
675 | Foo = A::[[F^oo]] + Baz; |
676 | A::[[Fo^o]] = b.Foo; |
677 | } |
678 | Foo = b.Foo; |
679 | } |
680 | )cpp" , |
681 | |
682 | // Namespace alias. |
683 | R"cpp( |
684 | namespace a { namespace b { void foo(); } } |
685 | namespace [[^x]] = a::b; |
686 | void bar() { |
687 | [[x^]]::foo(); |
688 | } |
689 | )cpp" , |
690 | |
691 | // Enum. |
692 | R"cpp( |
693 | enum [[C^olor]] { Red, Green, Blue }; |
694 | void foo() { |
695 | [[C^olor]] c; |
696 | c = [[C^olor]]::Blue; |
697 | } |
698 | )cpp" , |
699 | |
700 | // Scoped enum. |
701 | R"cpp( |
702 | enum class [[K^ind]] { ABC }; |
703 | void ff() { |
704 | [[K^ind]] s; |
705 | s = [[K^ind]]::ABC; |
706 | } |
707 | )cpp" , |
708 | |
709 | // Template class in template argument list. |
710 | R"cpp( |
711 | template<typename T> |
712 | class [[Fo^o]] {}; |
713 | template <template<typename> class Z> struct Bar { }; |
714 | template <> struct Bar<[[F^oo]]> {}; |
715 | )cpp" , |
716 | |
717 | // Designated initializer. |
718 | R"cpp( |
719 | struct Bar { |
720 | int [[Fo^o]]; |
721 | }; |
722 | Bar bar { .[[^Foo]] = 42 }; |
723 | )cpp" , |
724 | |
725 | // Nested designated initializer. |
726 | R"cpp( |
727 | struct Baz { |
728 | int Field; |
729 | }; |
730 | struct Bar { |
731 | Baz [[Fo^o]]; |
732 | }; |
733 | // FIXME: v selecting here results in renaming Field. |
734 | Bar bar { .[[Foo]].Field = 42 }; |
735 | )cpp" , |
736 | R"cpp( |
737 | struct Baz { |
738 | int [[Fiel^d]]; |
739 | }; |
740 | struct Bar { |
741 | Baz Foo; |
742 | }; |
743 | Bar bar { .Foo.[[^Field]] = 42 }; |
744 | )cpp" , |
745 | |
746 | // Templated alias. |
747 | R"cpp( |
748 | template <typename T> |
749 | class X { T t; }; |
750 | |
751 | template <typename T> |
752 | using [[Fo^o]] = X<T>; |
753 | |
754 | void bar() { |
755 | [[Fo^o]]<int> Bar; |
756 | } |
757 | )cpp" , |
758 | |
759 | // Alias. |
760 | R"cpp( |
761 | class X {}; |
762 | using [[F^oo]] = X; |
763 | |
764 | void bar() { |
765 | [[Fo^o]] Bar; |
766 | } |
767 | )cpp" , |
768 | |
769 | // Alias within a namespace. |
770 | R"cpp( |
771 | namespace x { class X {}; } |
772 | namespace ns { |
773 | using [[Fo^o]] = x::X; |
774 | } |
775 | |
776 | void bar() { |
777 | ns::[[Fo^o]] Bar; |
778 | } |
779 | )cpp" , |
780 | |
781 | // Alias within macros. |
782 | R"cpp( |
783 | namespace x { class Old {}; } |
784 | namespace ns { |
785 | #define REF(alias) alias alias_var; |
786 | |
787 | #define ALIAS(old) \ |
788 | using old##Alias = x::old; \ |
789 | REF(old##Alias); |
790 | |
791 | ALIAS(Old); |
792 | |
793 | [[Old^Alias]] old_alias; |
794 | } |
795 | |
796 | void bar() { |
797 | ns::[[Old^Alias]] Bar; |
798 | } |
799 | )cpp" , |
800 | |
801 | // User defined conversion. |
802 | R"cpp( |
803 | class [[F^oo]] { |
804 | public: |
805 | [[F^oo]]() {} |
806 | }; |
807 | |
808 | class Baz { |
809 | public: |
810 | operator [[F^oo]]() { |
811 | return [[F^oo]](); |
812 | } |
813 | }; |
814 | |
815 | int main() { |
816 | Baz boo; |
817 | [[F^oo]] foo = static_cast<[[F^oo]]>(boo); |
818 | } |
819 | )cpp" , |
820 | |
821 | // ObjC, should not crash. |
822 | R"cpp( |
823 | @interface ObjC { |
824 | char [[da^ta]]; |
825 | } @end |
826 | )cpp" , |
827 | |
828 | // Issue 170: Rename symbol introduced by UsingDecl |
829 | R"cpp( |
830 | namespace ns { void [[f^oo]](); } |
831 | |
832 | using ns::[[f^oo]]; |
833 | |
834 | void f() { |
835 | [[f^oo]](); |
836 | auto p = &[[f^oo]]; |
837 | } |
838 | )cpp" , |
839 | |
840 | // Issue 170: using decl that imports multiple overloads |
841 | // -> Only the overload under the cursor is renamed |
842 | R"cpp( |
843 | namespace ns { int [[^foo]](int); char foo(char); } |
844 | using ns::[[foo]]; |
845 | void f() { |
846 | [[^foo]](42); |
847 | foo('x'); |
848 | } |
849 | )cpp" , |
850 | |
851 | // ObjC class with a category. |
852 | R"cpp( |
853 | @interface [[Fo^o]] |
854 | @end |
855 | @implementation [[F^oo]] |
856 | @end |
857 | @interface [[Fo^o]] (Category) |
858 | @end |
859 | @implementation [[F^oo]] (Category) |
860 | @end |
861 | |
862 | void func([[Fo^o]] *f) {} |
863 | )cpp" , |
864 | }; |
865 | llvm::StringRef NewName = "NewName" ; |
866 | for (llvm::StringRef T : Tests) { |
867 | SCOPED_TRACE(T); |
868 | Annotations Code(T); |
869 | auto TU = TestTU::withCode(Code: Code.code()); |
870 | TU.ExtraArgs.push_back(x: "-xobjective-c++" ); |
871 | auto AST = TU.build(); |
872 | auto Index = TU.index(); |
873 | for (const auto &RenamePos : Code.points()) { |
874 | auto RenameResult = |
875 | rename(RInputs: {.Pos: RenamePos, .NewName: NewName, .AST: AST, .MainFilePath: testPath(File: TU.Filename), |
876 | .FS: getVFSFromAST(AST), .Index: Index.get()}); |
877 | ASSERT_TRUE(bool(RenameResult)) << RenameResult.takeError(); |
878 | ASSERT_EQ(1u, RenameResult->GlobalChanges.size()); |
879 | EXPECT_EQ( |
880 | applyEdits(std::move(RenameResult->GlobalChanges)).front().second, |
881 | expectedResult(Code, NewName)); |
882 | } |
883 | } |
884 | } |
885 | |
886 | TEST(RenameTest, ObjCWithinFileRename) { |
887 | struct TestCase { |
888 | /// Annotated source code that should be renamed. Every point (indicated by |
889 | /// `^`) will be used as a rename location. |
890 | llvm::StringRef Input; |
891 | /// The new name that should be given to the rename locaitons. |
892 | llvm::StringRef NewName; |
893 | /// The expected rename source code or `nullopt` if we expect rename to |
894 | /// fail. |
895 | std::optional<llvm::StringRef> Expected; |
896 | }; |
897 | TestCase Tests[] = {// Simple rename |
898 | { |
899 | // Input |
900 | .Input: R"cpp( |
901 | @interface Foo |
902 | - (int)performA^ction:(int)action w^ith:(int)value; |
903 | @end |
904 | @implementation Foo |
905 | - (int)performAc^tion:(int)action w^ith:(int)value { |
906 | return [self performAction:action with:value]; |
907 | } |
908 | @end |
909 | )cpp" , |
910 | // New name |
911 | .NewName: "performNewAction:by:" , |
912 | // Expected |
913 | .Expected: R"cpp( |
914 | @interface Foo |
915 | - (int)performNewAction:(int)action by:(int)value; |
916 | @end |
917 | @implementation Foo |
918 | - (int)performNewAction:(int)action by:(int)value { |
919 | return [self performNewAction:action by:value]; |
920 | } |
921 | @end |
922 | )cpp" , |
923 | }, |
924 | // Rename selector with macro |
925 | { |
926 | // Input |
927 | .Input: R"cpp( |
928 | #define mySelector - (int)performAction:(int)action with:(int)value |
929 | @interface Foo |
930 | ^mySelector; |
931 | @end |
932 | @implementation Foo |
933 | mySelector { |
934 | return [self performAction:action with:value]; |
935 | } |
936 | @end |
937 | )cpp" , |
938 | // New name |
939 | .NewName: "performNewAction:by:" , |
940 | // Expected error |
941 | .Expected: std::nullopt, |
942 | }, |
943 | // Rename selector in macro definition |
944 | { |
945 | // Input |
946 | .Input: R"cpp( |
947 | #define mySelector - (int)perform^Action:(int)action with:(int)value |
948 | @interface Foo |
949 | mySelector; |
950 | @end |
951 | @implementation Foo |
952 | mySelector { |
953 | return [self performAction:action with:value]; |
954 | } |
955 | @end |
956 | )cpp" , |
957 | // New name |
958 | .NewName: "performNewAction:by:" , |
959 | // Expected error |
960 | .Expected: std::nullopt, |
961 | }, |
962 | // Don't rename `@selector` |
963 | // `@selector` is not tied to a single selector. Eg. there |
964 | // might be multiple |
965 | // classes in the codebase that implement that selector. |
966 | // It's thus more like |
967 | // a string literal and we shouldn't rename it. |
968 | { |
969 | // Input |
970 | .Input: R"cpp( |
971 | @interface Foo |
972 | - (void)performA^ction:(int)action with:(int)value; |
973 | @end |
974 | @implementation Foo |
975 | - (void)performAction:(int)action with:(int)value { |
976 | SEL mySelector = @selector(performAction:with:); |
977 | } |
978 | @end |
979 | )cpp" , |
980 | // New name |
981 | .NewName: "performNewAction:by:" , |
982 | // Expected |
983 | .Expected: R"cpp( |
984 | @interface Foo |
985 | - (void)performNewAction:(int)action by:(int)value; |
986 | @end |
987 | @implementation Foo |
988 | - (void)performNewAction:(int)action by:(int)value { |
989 | SEL mySelector = @selector(performAction:with:); |
990 | } |
991 | @end |
992 | )cpp" , |
993 | }, |
994 | // Fail if rename initiated inside @selector |
995 | { |
996 | // Input |
997 | .Input: R"cpp( |
998 | @interface Foo |
999 | - (void)performAction:(int)action with:(int)value; |
1000 | @end |
1001 | @implementation Foo |
1002 | - (void)performAction:(int)action with:(int)value { |
1003 | SEL mySelector = @selector(perfo^rmAction:with:); |
1004 | } |
1005 | @end |
1006 | )cpp" , |
1007 | // New name |
1008 | .NewName: "performNewAction:by:" , |
1009 | // Expected |
1010 | .Expected: std::nullopt, |
1011 | }}; |
1012 | for (TestCase T : Tests) { |
1013 | SCOPED_TRACE(T.Input); |
1014 | Annotations Code(T.Input); |
1015 | auto TU = TestTU::withCode(Code: Code.code()); |
1016 | TU.ExtraArgs.push_back(x: "-xobjective-c" ); |
1017 | auto AST = TU.build(); |
1018 | auto Index = TU.index(); |
1019 | for (const auto &RenamePos : Code.points()) { |
1020 | auto RenameResult = |
1021 | rename(RInputs: {.Pos: RenamePos, .NewName: T.NewName, .AST: AST, .MainFilePath: testPath(File: TU.Filename), |
1022 | .FS: getVFSFromAST(AST), .Index: Index.get()}); |
1023 | if (std::optional<StringRef> Expected = T.Expected) { |
1024 | ASSERT_TRUE(bool(RenameResult)) << RenameResult.takeError(); |
1025 | ASSERT_EQ(1u, RenameResult->GlobalChanges.size()); |
1026 | EXPECT_EQ( |
1027 | applyEdits(std::move(RenameResult->GlobalChanges)).front().second, |
1028 | *Expected); |
1029 | } else { |
1030 | ASSERT_FALSE(bool(RenameResult)); |
1031 | consumeError(Err: RenameResult.takeError()); |
1032 | } |
1033 | } |
1034 | } |
1035 | } |
1036 | |
1037 | TEST(RenameTest, Renameable) { |
1038 | struct Case { |
1039 | const char *Code; |
1040 | const char* ErrorMessage; // null if no error |
1041 | bool ; |
1042 | llvm::StringRef NewName = "MockName" ; |
1043 | }; |
1044 | const bool = true; |
1045 | Case Cases[] = { |
1046 | {.Code: R"cpp(// allow -- function-local |
1047 | void f(int [[Lo^cal]]) { |
1048 | [[Local]] = 2; |
1049 | } |
1050 | )cpp" , |
1051 | .ErrorMessage: nullptr, .IsHeaderFile: HeaderFile}, |
1052 | |
1053 | {.Code: R"cpp(// disallow -- symbol in anonymous namespace in header is not indexable. |
1054 | namespace { |
1055 | class Unin^dexable {}; |
1056 | } |
1057 | )cpp" , |
1058 | .ErrorMessage: "not eligible for indexing" , .IsHeaderFile: HeaderFile}, |
1059 | |
1060 | {.Code: R"cpp(// disallow -- namespace symbol isn't supported |
1061 | namespace n^s {} |
1062 | )cpp" , |
1063 | .ErrorMessage: "not a supported kind" , .IsHeaderFile: HeaderFile}, |
1064 | |
1065 | {.Code: R"cpp(// disallow - category rename. |
1066 | @interface Foo |
1067 | @end |
1068 | @interface Foo (Cate^gory) |
1069 | @end |
1070 | )cpp" , |
1071 | .ErrorMessage: "Cannot rename symbol: there is no symbol at the given location" , |
1072 | .IsHeaderFile: HeaderFile}, |
1073 | |
1074 | { |
1075 | .Code: R"cpp( |
1076 | #define MACRO 1 |
1077 | int s = MAC^RO; |
1078 | )cpp" , |
1079 | .ErrorMessage: "not a supported kind" , .IsHeaderFile: HeaderFile}, |
1080 | |
1081 | { |
1082 | .Code: R"cpp( |
1083 | struct X { X operator++(int); }; |
1084 | void f(X x) {x+^+;})cpp" , |
1085 | .ErrorMessage: "no symbol" , .IsHeaderFile: HeaderFile}, |
1086 | |
1087 | {.Code: R"cpp( |
1088 | @interface Foo {} |
1089 | - (int)[[fo^o]]:(int)x; |
1090 | @end |
1091 | )cpp" , |
1092 | .ErrorMessage: nullptr, .IsHeaderFile: HeaderFile, .NewName: "newName:" }, |
1093 | {.Code: R"cpp(//disallow as : count must match |
1094 | @interface Foo {} |
1095 | - (int)fo^o:(int)x; |
1096 | @end |
1097 | )cpp" , |
1098 | .ErrorMessage: "invalid name: the chosen name \"MockName\" is not a valid identifier" , |
1099 | .IsHeaderFile: HeaderFile}, |
1100 | {.Code: R"cpp( |
1101 | @interface Foo {} |
1102 | - (int)[[o^ne]]:(int)one two:(int)two; |
1103 | @end |
1104 | )cpp" , |
1105 | .ErrorMessage: nullptr, .IsHeaderFile: HeaderFile, .NewName: "a:two:" }, |
1106 | {.Code: R"cpp( |
1107 | @interface Foo {} |
1108 | - (int)[[o^ne]]:(int)one [[two]]:(int)two; |
1109 | @end |
1110 | )cpp" , |
1111 | .ErrorMessage: nullptr, .IsHeaderFile: HeaderFile, .NewName: "a:b:" }, |
1112 | {.Code: R"cpp( |
1113 | @interface Foo {} |
1114 | - (int)o^ne:(int)one [[two]]:(int)two; |
1115 | @end |
1116 | )cpp" , |
1117 | .ErrorMessage: nullptr, .IsHeaderFile: HeaderFile, .NewName: "one:three:" }, |
1118 | |
1119 | {.Code: R"cpp( |
1120 | void foo(int); |
1121 | void foo(char); |
1122 | template <typename T> void f(T t) { |
1123 | fo^o(t); |
1124 | })cpp" , |
1125 | .ErrorMessage: "multiple symbols" , .IsHeaderFile: !HeaderFile}, |
1126 | |
1127 | {.Code: R"cpp(// disallow rename on unrelated token. |
1128 | cl^ass Foo {}; |
1129 | )cpp" , |
1130 | .ErrorMessage: "no symbol" , .IsHeaderFile: !HeaderFile}, |
1131 | |
1132 | {.Code: R"cpp(// disallow rename on unrelated token. |
1133 | temp^late<typename T> |
1134 | class Foo {}; |
1135 | )cpp" , |
1136 | .ErrorMessage: "no symbol" , .IsHeaderFile: !HeaderFile}, |
1137 | |
1138 | {.Code: R"cpp( |
1139 | namespace { |
1140 | int Conflict; |
1141 | int Va^r; |
1142 | } |
1143 | )cpp" , |
1144 | .ErrorMessage: "conflict" , .IsHeaderFile: !HeaderFile, .NewName: "Conflict" }, |
1145 | |
1146 | {.Code: R"cpp( |
1147 | int Conflict; |
1148 | int Va^r; |
1149 | )cpp" , |
1150 | .ErrorMessage: "conflict" , .IsHeaderFile: !HeaderFile, .NewName: "Conflict" }, |
1151 | |
1152 | {.Code: R"cpp( |
1153 | class Foo { |
1154 | int Conflict; |
1155 | int Va^r; |
1156 | }; |
1157 | )cpp" , |
1158 | .ErrorMessage: "conflict" , .IsHeaderFile: !HeaderFile, .NewName: "Conflict" }, |
1159 | |
1160 | {.Code: R"cpp( |
1161 | enum E { |
1162 | Conflict, |
1163 | Fo^o, |
1164 | }; |
1165 | )cpp" , |
1166 | .ErrorMessage: "conflict" , .IsHeaderFile: !HeaderFile, .NewName: "Conflict" }, |
1167 | |
1168 | {.Code: R"cpp( |
1169 | int Conflict; |
1170 | enum E { // transparent context. |
1171 | F^oo, |
1172 | }; |
1173 | )cpp" , |
1174 | .ErrorMessage: "conflict" , .IsHeaderFile: !HeaderFile, .NewName: "Conflict" }, |
1175 | |
1176 | {.Code: R"cpp( |
1177 | void func() { |
1178 | bool Whatever; |
1179 | int V^ar; |
1180 | char Conflict; |
1181 | } |
1182 | )cpp" , |
1183 | .ErrorMessage: "conflict" , .IsHeaderFile: !HeaderFile, .NewName: "Conflict" }, |
1184 | |
1185 | {.Code: R"cpp( |
1186 | void func() { |
1187 | if (int Conflict = 42) { |
1188 | int V^ar; |
1189 | } |
1190 | } |
1191 | )cpp" , |
1192 | .ErrorMessage: "conflict" , .IsHeaderFile: !HeaderFile, .NewName: "Conflict" }, |
1193 | |
1194 | {.Code: R"cpp( |
1195 | void func() { |
1196 | if (int Conflict = 42) { |
1197 | } else { |
1198 | bool V^ar; |
1199 | } |
1200 | } |
1201 | )cpp" , |
1202 | .ErrorMessage: "conflict" , .IsHeaderFile: !HeaderFile, .NewName: "Conflict" }, |
1203 | |
1204 | {.Code: R"cpp( |
1205 | void func() { |
1206 | if (int V^ar = 42) { |
1207 | } else { |
1208 | bool Conflict; |
1209 | } |
1210 | } |
1211 | )cpp" , |
1212 | .ErrorMessage: "conflict" , .IsHeaderFile: !HeaderFile, .NewName: "Conflict" }, |
1213 | |
1214 | {.Code: R"cpp( |
1215 | void func() { |
1216 | while (int V^ar = 10) { |
1217 | bool Conflict = true; |
1218 | } |
1219 | } |
1220 | )cpp" , |
1221 | .ErrorMessage: "conflict" , .IsHeaderFile: !HeaderFile, .NewName: "Conflict" }, |
1222 | |
1223 | {.Code: R"cpp( |
1224 | void func() { |
1225 | for (int Something = 9000, Anything = 14, Conflict = 42; Anything > 9; |
1226 | ++Something) { |
1227 | int V^ar; |
1228 | } |
1229 | } |
1230 | )cpp" , |
1231 | .ErrorMessage: "conflict" , .IsHeaderFile: !HeaderFile, .NewName: "Conflict" }, |
1232 | |
1233 | {.Code: R"cpp( |
1234 | void func() { |
1235 | for (int V^ar = 14, Conflict = 42;;) { |
1236 | } |
1237 | } |
1238 | )cpp" , |
1239 | .ErrorMessage: "conflict" , .IsHeaderFile: !HeaderFile, .NewName: "Conflict" }, |
1240 | |
1241 | {.Code: R"cpp( |
1242 | void func(int Conflict) { |
1243 | bool V^ar; |
1244 | } |
1245 | )cpp" , |
1246 | .ErrorMessage: "conflict" , .IsHeaderFile: !HeaderFile, .NewName: "Conflict" }, |
1247 | |
1248 | {.Code: R"cpp( |
1249 | void func(int Var); |
1250 | |
1251 | void func(int V^ar) { |
1252 | bool Conflict; |
1253 | } |
1254 | )cpp" , |
1255 | .ErrorMessage: "conflict" , .IsHeaderFile: !HeaderFile, .NewName: "Conflict" }, |
1256 | |
1257 | {.Code: R"cpp(// No conflict: only forward declaration's argument is renamed. |
1258 | void func(int [[V^ar]]); |
1259 | |
1260 | void func(int Var) { |
1261 | bool Conflict; |
1262 | } |
1263 | )cpp" , |
1264 | .ErrorMessage: nullptr, .IsHeaderFile: !HeaderFile, .NewName: "Conflict" }, |
1265 | |
1266 | {.Code: R"cpp( |
1267 | void func(int V^ar, int Conflict) { |
1268 | } |
1269 | )cpp" , |
1270 | .ErrorMessage: "conflict" , .IsHeaderFile: !HeaderFile, .NewName: "Conflict" }, |
1271 | |
1272 | {.Code: R"cpp( |
1273 | struct conflict {}; |
1274 | enum v^ar {}; |
1275 | )cpp" , |
1276 | .ErrorMessage: "conflict" , .IsHeaderFile: !HeaderFile, .NewName: "conflict" }, |
1277 | |
1278 | {.Code: R"cpp( |
1279 | struct conflict {}; |
1280 | int [[v^ar]]; |
1281 | )cpp" , |
1282 | .ErrorMessage: nullptr, .IsHeaderFile: !HeaderFile, .NewName: "conflict" }, |
1283 | |
1284 | {.Code: R"cpp( |
1285 | enum conflict {}; |
1286 | int [[v^ar]]; |
1287 | )cpp" , |
1288 | .ErrorMessage: nullptr, .IsHeaderFile: !HeaderFile, .NewName: "conflict" }, |
1289 | |
1290 | {.Code: R"cpp( |
1291 | void func(int conflict) { |
1292 | struct [[t^ag]] {}; |
1293 | } |
1294 | )cpp" , |
1295 | .ErrorMessage: nullptr, .IsHeaderFile: !HeaderFile, .NewName: "conflict" }, |
1296 | |
1297 | {.Code: R"cpp( |
1298 | void func(void) { |
1299 | struct conflict {}; |
1300 | int [[v^ar]]; |
1301 | } |
1302 | )cpp" , |
1303 | .ErrorMessage: nullptr, .IsHeaderFile: !HeaderFile, .NewName: "conflict" }, |
1304 | |
1305 | {.Code: R"cpp( |
1306 | void func(int); |
1307 | void [[o^therFunc]](double); |
1308 | )cpp" , |
1309 | .ErrorMessage: nullptr, .IsHeaderFile: !HeaderFile, .NewName: "func" }, |
1310 | {.Code: R"cpp( |
1311 | struct S { |
1312 | void func(int); |
1313 | void [[o^therFunc]](double); |
1314 | }; |
1315 | )cpp" , |
1316 | .ErrorMessage: nullptr, .IsHeaderFile: !HeaderFile, .NewName: "func" }, |
1317 | |
1318 | {.Code: R"cpp( |
1319 | int V^ar; |
1320 | )cpp" , |
1321 | .ErrorMessage: "\"const\" is a keyword" , .IsHeaderFile: !HeaderFile, .NewName: "const" }, |
1322 | |
1323 | {.Code: R"cpp(// Trying to rename into the same name, SameName == SameName. |
1324 | void func() { |
1325 | int S^ameName; |
1326 | } |
1327 | )cpp" , |
1328 | .ErrorMessage: "new name is the same" , .IsHeaderFile: !HeaderFile, .NewName: "SameName" }, |
1329 | {.Code: R"cpp(// Ensure it doesn't associate base specifier with base name. |
1330 | struct A {}; |
1331 | struct B : priv^ate A {}; |
1332 | )cpp" , |
1333 | .ErrorMessage: "Cannot rename symbol: there is no symbol at the given location" , .IsHeaderFile: false}, |
1334 | {.Code: R"cpp(// Ensure it doesn't associate base specifier with base name. |
1335 | /*error-ok*/ |
1336 | struct A { |
1337 | A() : inva^lid(0) {} |
1338 | }; |
1339 | )cpp" , |
1340 | .ErrorMessage: "no symbol" , .IsHeaderFile: false}, |
1341 | |
1342 | {.Code: R"cpp(// FIXME we probably want to rename both overloads here, |
1343 | // but renaming currently assumes there's only a |
1344 | // single canonical declaration. |
1345 | namespace ns { int foo(int); char foo(char); } |
1346 | using ns::^foo; |
1347 | )cpp" , |
1348 | .ErrorMessage: "there are multiple symbols at the given location" , .IsHeaderFile: !HeaderFile}, |
1349 | |
1350 | {.Code: R"cpp( |
1351 | void test() { |
1352 | // no crash |
1353 | using namespace std; |
1354 | int [[V^ar]]; |
1355 | } |
1356 | )cpp" , |
1357 | .ErrorMessage: nullptr, .IsHeaderFile: !HeaderFile}, |
1358 | }; |
1359 | |
1360 | for (const auto& Case : Cases) { |
1361 | SCOPED_TRACE(Case.Code); |
1362 | Annotations T(Case.Code); |
1363 | TestTU TU = TestTU::withCode(Code: T.code()); |
1364 | TU.ExtraArgs.push_back(x: "-fno-delayed-template-parsing" ); |
1365 | if (Case.IsHeaderFile) { |
1366 | // We open the .h file as the main file. |
1367 | TU.Filename = "test.h" ; |
1368 | // Parsing the .h file as C++ include. |
1369 | TU.ExtraArgs.push_back(x: "-xobjective-c++-header" ); |
1370 | } |
1371 | auto AST = TU.build(); |
1372 | llvm::StringRef NewName = Case.NewName; |
1373 | auto Results = rename(RInputs: {.Pos: T.point(), .NewName: NewName, .AST: AST, .MainFilePath: testPath(File: TU.Filename)}); |
1374 | bool WantRename = true; |
1375 | if (T.ranges().empty()) |
1376 | WantRename = false; |
1377 | if (!WantRename) { |
1378 | assert(Case.ErrorMessage && "Error message must be set!" ); |
1379 | EXPECT_FALSE(Results) |
1380 | << "expected rename returned an error: " << T.code(); |
1381 | auto ActualMessage = llvm::toString(E: Results.takeError()); |
1382 | EXPECT_THAT(ActualMessage, testing::HasSubstr(Case.ErrorMessage)); |
1383 | } else { |
1384 | EXPECT_TRUE(bool(Results)) << "rename returned an error: " |
1385 | << llvm::toString(E: Results.takeError()); |
1386 | EXPECT_EQ(Results->LocalChanges, T.ranges()); |
1387 | } |
1388 | } |
1389 | } |
1390 | |
1391 | MATCHER_P(newText, T, "" ) { return arg.newText == T; } |
1392 | |
1393 | TEST(RenameTest, IndexMergeMainFile) { |
1394 | Annotations Code("int ^x();" ); |
1395 | TestTU TU = TestTU::withCode(Code: Code.code()); |
1396 | TU.Filename = "main.cc" ; |
1397 | auto AST = TU.build(); |
1398 | |
1399 | auto Main = testPath(File: "main.cc" ); |
1400 | auto InMemFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); |
1401 | InMemFS->addFile(Path: testPath(File: "main.cc" ), ModificationTime: 0, |
1402 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: Code.code())); |
1403 | InMemFS->addFile(Path: testPath(File: "other.cc" ), ModificationTime: 0, |
1404 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: Code.code())); |
1405 | |
1406 | auto Rename = [&](const SymbolIndex *Idx) { |
1407 | RenameInputs Inputs{.Pos: Code.point(), |
1408 | .NewName: "xPrime" , |
1409 | .AST: AST, |
1410 | .MainFilePath: Main, |
1411 | .FS: Idx ? createOverlay(Base: getVFSFromAST(AST), Overlay: InMemFS) |
1412 | : nullptr, |
1413 | .Index: Idx, |
1414 | .Opts: RenameOptions()}; |
1415 | auto Results = rename(RInputs: Inputs); |
1416 | EXPECT_TRUE(bool(Results)) << llvm::toString(E: Results.takeError()); |
1417 | return std::move(*Results); |
1418 | }; |
1419 | |
1420 | // We do not expect to see duplicated edits from AST vs index. |
1421 | auto Results = Rename(TU.index().get()); |
1422 | EXPECT_THAT(Results.GlobalChanges.keys(), ElementsAre(Main)); |
1423 | EXPECT_THAT(Results.GlobalChanges[Main].asTextEdits(), |
1424 | ElementsAre(newText("xPrime" ))); |
1425 | |
1426 | // Sanity check: we do expect to see index results! |
1427 | TU.Filename = "other.cc" ; |
1428 | Results = Rename(TU.index().get()); |
1429 | EXPECT_THAT(Results.GlobalChanges.keys(), |
1430 | UnorderedElementsAre(Main, testPath("other.cc" ))); |
1431 | |
1432 | #ifdef CLANGD_PATH_CASE_INSENSITIVE |
1433 | // On case-insensitive systems, no duplicates if AST vs index case differs. |
1434 | // https://github.com/clangd/clangd/issues/665 |
1435 | TU.Filename = "MAIN.CC" ; |
1436 | Results = Rename(TU.index().get()); |
1437 | EXPECT_THAT(Results.GlobalChanges.keys(), ElementsAre(Main)); |
1438 | EXPECT_THAT(Results.GlobalChanges[Main].asTextEdits(), |
1439 | ElementsAre(newText("xPrime" ))); |
1440 | #endif |
1441 | } |
1442 | |
1443 | TEST(RenameTest, MainFileReferencesOnly) { |
1444 | // filter out references not from main file. |
1445 | llvm::StringRef Test = |
1446 | R"cpp( |
1447 | void test() { |
1448 | int [[fo^o]] = 1; |
1449 | // rename references not from main file are not included. |
1450 | #include "foo.inc" |
1451 | })cpp" ; |
1452 | |
1453 | Annotations Code(Test); |
1454 | auto TU = TestTU::withCode(Code: Code.code()); |
1455 | TU.AdditionalFiles["foo.inc" ] = R"cpp( |
1456 | #define Macro(X) X |
1457 | &Macro(foo); |
1458 | &foo; |
1459 | )cpp" ; |
1460 | auto AST = TU.build(); |
1461 | llvm::StringRef NewName = "abcde" ; |
1462 | |
1463 | auto RenameResult = |
1464 | rename(RInputs: {.Pos: Code.point(), .NewName: NewName, .AST: AST, .MainFilePath: testPath(File: TU.Filename)}); |
1465 | ASSERT_TRUE(bool(RenameResult)) << RenameResult.takeError() << Code.point(); |
1466 | ASSERT_EQ(1u, RenameResult->GlobalChanges.size()); |
1467 | EXPECT_EQ(applyEdits(std::move(RenameResult->GlobalChanges)).front().second, |
1468 | expectedResult(Code, NewName)); |
1469 | } |
1470 | |
1471 | TEST(RenameTest, NoRenameOnSymbolsFromSystemHeaders) { |
1472 | llvm::StringRef Test = |
1473 | R"cpp( |
1474 | #include <cstdlib> |
1475 | #include <system> |
1476 | |
1477 | SystemSym^bol abc; |
1478 | |
1479 | void foo() { at^oi("9000"); } |
1480 | )cpp" ; |
1481 | |
1482 | Annotations Code(Test); |
1483 | auto TU = TestTU::withCode(Code: Code.code()); |
1484 | TU.AdditionalFiles["system" ] = R"cpp( |
1485 | class SystemSymbol {}; |
1486 | )cpp" ; |
1487 | TU.AdditionalFiles["cstdlib" ] = R"cpp( |
1488 | int atoi(const char *str); |
1489 | )cpp" ; |
1490 | TU.ExtraArgs = {"-isystem" , testRoot()}; |
1491 | auto AST = TU.build(); |
1492 | llvm::StringRef NewName = "abcde" ; |
1493 | |
1494 | // Clangd will not allow renaming symbols from the system headers for |
1495 | // correctness. |
1496 | for (auto &Point : Code.points()) { |
1497 | auto Results = rename(RInputs: {.Pos: Point, .NewName: NewName, .AST: AST, .MainFilePath: testPath(File: TU.Filename)}); |
1498 | EXPECT_FALSE(Results) << "expected rename returned an error: " |
1499 | << Code.code(); |
1500 | auto ActualMessage = llvm::toString(E: Results.takeError()); |
1501 | EXPECT_THAT(ActualMessage, testing::HasSubstr("not a supported kind" )); |
1502 | } |
1503 | } |
1504 | |
1505 | TEST(RenameTest, ProtobufSymbolIsExcluded) { |
1506 | Annotations Code("Prot^obuf buf;" ); |
1507 | auto TU = TestTU::withCode(Code: Code.code()); |
1508 | TU.HeaderCode = |
1509 | R"cpp(// Generated by the protocol buffer compiler. DO NOT EDIT! |
1510 | class Protobuf {}; |
1511 | )cpp" ; |
1512 | TU.HeaderFilename = "protobuf.pb.h" ; |
1513 | auto AST = TU.build(); |
1514 | auto Results = rename(RInputs: {.Pos: Code.point(), .NewName: "newName" , .AST: AST, .MainFilePath: testPath(File: TU.Filename)}); |
1515 | EXPECT_FALSE(Results); |
1516 | EXPECT_THAT(llvm::toString(Results.takeError()), |
1517 | testing::HasSubstr("not a supported kind" )); |
1518 | } |
1519 | |
1520 | TEST(RenameTest, PrepareRename) { |
1521 | Annotations FooH("void func();" ); |
1522 | Annotations FooCC(R"cpp( |
1523 | #include "foo.h" |
1524 | void [[fu^nc]]() {} |
1525 | )cpp" ); |
1526 | std::string FooHPath = testPath(File: "foo.h" ); |
1527 | std::string FooCCPath = testPath(File: "foo.cc" ); |
1528 | MockFS FS; |
1529 | FS.Files[FooHPath] = std::string(FooH.code()); |
1530 | FS.Files[FooCCPath] = std::string(FooCC.code()); |
1531 | |
1532 | auto ServerOpts = ClangdServer::optsForTest(); |
1533 | ServerOpts.BuildDynamicSymbolIndex = true; |
1534 | |
1535 | trace::TestTracer Tracer; |
1536 | MockCompilationDatabase CDB; |
1537 | ClangdServer Server(CDB, FS, ServerOpts); |
1538 | runAddDocument(Server, File: FooHPath, Contents: FooH.code()); |
1539 | runAddDocument(Server, File: FooCCPath, Contents: FooCC.code()); |
1540 | |
1541 | auto Results = runPrepareRename(Server, File: FooCCPath, Pos: FooCC.point(), |
1542 | /*NewName=*/std::nullopt, RenameOpts: {}); |
1543 | // Verify that for multi-file rename, we only return main-file occurrences. |
1544 | ASSERT_TRUE(bool(Results)) << Results.takeError(); |
1545 | // We don't know the result is complete in prepareRename (passing a nullptr |
1546 | // index internally), so GlobalChanges should be empty. |
1547 | EXPECT_TRUE(Results->GlobalChanges.empty()); |
1548 | EXPECT_THAT(FooCC.ranges(), |
1549 | testing::UnorderedElementsAreArray(Results->LocalChanges)); |
1550 | |
1551 | // Name validation. |
1552 | Results = runPrepareRename(Server, File: FooCCPath, Pos: FooCC.point(), |
1553 | /*NewName=*/std::string("int" ), RenameOpts: {}); |
1554 | EXPECT_FALSE(Results); |
1555 | EXPECT_THAT(llvm::toString(Results.takeError()), |
1556 | testing::HasSubstr("keyword" )); |
1557 | EXPECT_THAT(Tracer.takeMetric("rename_name_invalid" , "Keywords" ), |
1558 | ElementsAre(1)); |
1559 | |
1560 | for (std::string BadIdent : {"foo!bar" , "123foo" , "😀@" }) { |
1561 | Results = runPrepareRename(Server, File: FooCCPath, Pos: FooCC.point(), |
1562 | /*NewName=*/BadIdent, RenameOpts: {}); |
1563 | EXPECT_FALSE(Results); |
1564 | EXPECT_THAT(llvm::toString(Results.takeError()), |
1565 | testing::HasSubstr("identifier" )); |
1566 | EXPECT_THAT(Tracer.takeMetric("rename_name_invalid" , "BadIdentifier" ), |
1567 | ElementsAre(1)); |
1568 | } |
1569 | for (std::string GoodIdent : {"fooBar" , "__foo$" , "😀" }) { |
1570 | Results = runPrepareRename(Server, File: FooCCPath, Pos: FooCC.point(), |
1571 | /*NewName=*/GoodIdent, RenameOpts: {}); |
1572 | EXPECT_TRUE(bool(Results)); |
1573 | } |
1574 | } |
1575 | |
1576 | TEST(CrossFileRenameTests, DirtyBuffer) { |
1577 | Annotations FooCode("class [[Foo]] {};" ); |
1578 | std::string FooPath = testPath(File: "foo.cc" ); |
1579 | Annotations FooDirtyBuffer("class [[Foo]] {};\n// this is dirty buffer" ); |
1580 | Annotations BarCode("void [[Bar]]() {}" ); |
1581 | std::string BarPath = testPath(File: "bar.cc" ); |
1582 | // Build the index, the index has "Foo" references from foo.cc and "Bar" |
1583 | // references from bar.cc. |
1584 | FileSymbols FSymbols(IndexContents::All, true); |
1585 | FSymbols.update(Key: FooPath, Symbols: nullptr, Refs: buildRefSlab(Code: FooCode, SymbolName: "Foo" , Path: FooPath), |
1586 | Relations: nullptr, CountReferences: false); |
1587 | FSymbols.update(Key: BarPath, Symbols: nullptr, Refs: buildRefSlab(Code: BarCode, SymbolName: "Bar" , Path: BarPath), |
1588 | Relations: nullptr, CountReferences: false); |
1589 | auto Index = FSymbols.buildIndex(IndexType::Light); |
1590 | |
1591 | Annotations MainCode("class [[Fo^o]] {};" ); |
1592 | auto MainFilePath = testPath(File: "main.cc" ); |
1593 | llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemFS = |
1594 | new llvm::vfs::InMemoryFileSystem; |
1595 | InMemFS->addFile(Path: FooPath, ModificationTime: 0, |
1596 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: FooDirtyBuffer.code())); |
1597 | |
1598 | // Run rename on Foo, there is a dirty buffer for foo.cc, rename should |
1599 | // respect the dirty buffer. |
1600 | TestTU TU = TestTU::withCode(Code: MainCode.code()); |
1601 | auto AST = TU.build(); |
1602 | llvm::StringRef NewName = "newName" ; |
1603 | auto Results = |
1604 | rename(RInputs: {.Pos: MainCode.point(), .NewName: NewName, .AST: AST, .MainFilePath: MainFilePath, |
1605 | .FS: createOverlay(Base: getVFSFromAST(AST), Overlay: InMemFS), .Index: Index.get()}); |
1606 | ASSERT_TRUE(bool(Results)) << Results.takeError(); |
1607 | EXPECT_THAT( |
1608 | applyEdits(std::move(Results->GlobalChanges)), |
1609 | UnorderedElementsAre( |
1610 | Pair(Eq(FooPath), Eq(expectedResult(FooDirtyBuffer, NewName))), |
1611 | Pair(Eq(MainFilePath), Eq(expectedResult(MainCode, NewName))))); |
1612 | |
1613 | // Run rename on Bar, there is no dirty buffer for the affected file bar.cc, |
1614 | // so we should read file content from VFS. |
1615 | MainCode = Annotations("void [[Bar]]() { [[B^ar]](); }" ); |
1616 | TU = TestTU::withCode(Code: MainCode.code()); |
1617 | // Set a file "bar.cc" on disk. |
1618 | TU.AdditionalFiles["bar.cc" ] = std::string(BarCode.code()); |
1619 | AST = TU.build(); |
1620 | Results = rename(RInputs: {.Pos: MainCode.point(), .NewName: NewName, .AST: AST, .MainFilePath: MainFilePath, |
1621 | .FS: createOverlay(Base: getVFSFromAST(AST), Overlay: InMemFS), .Index: Index.get()}); |
1622 | ASSERT_TRUE(bool(Results)) << Results.takeError(); |
1623 | EXPECT_THAT( |
1624 | applyEdits(std::move(Results->GlobalChanges)), |
1625 | UnorderedElementsAre( |
1626 | Pair(Eq(BarPath), Eq(expectedResult(BarCode, NewName))), |
1627 | Pair(Eq(MainFilePath), Eq(expectedResult(MainCode, NewName))))); |
1628 | |
1629 | // Run rename on a pagination index which couldn't return all refs in one |
1630 | // request, we reject rename on this case. |
1631 | class : public SymbolIndex { |
1632 | bool refs(const RefsRequest &Req, |
1633 | llvm::function_ref<void(const Ref &)> Callback) const override { |
1634 | return true; // has more references |
1635 | } |
1636 | |
1637 | bool containedRefs(const ContainedRefsRequest &Req, |
1638 | llvm::function_ref<void(const ContainedRefsResult &)> |
1639 | Callback) const override { |
1640 | return false; |
1641 | } |
1642 | |
1643 | bool fuzzyFind( |
1644 | const FuzzyFindRequest &Req, |
1645 | llvm::function_ref<void(const Symbol &)> Callback) const override { |
1646 | return false; |
1647 | } |
1648 | void |
1649 | lookup(const LookupRequest &Req, |
1650 | llvm::function_ref<void(const Symbol &)> Callback) const override {} |
1651 | |
1652 | void relations(const RelationsRequest &Req, |
1653 | llvm::function_ref<void(const SymbolID &, const Symbol &)> |
1654 | Callback) const override {} |
1655 | |
1656 | llvm::unique_function<IndexContents(llvm::StringRef) const> |
1657 | indexedFiles() const override { |
1658 | return [](llvm::StringRef) { return IndexContents::None; }; |
1659 | } |
1660 | |
1661 | size_t estimateMemoryUsage() const override { return 0; } |
1662 | } PIndex; |
1663 | Results = rename(RInputs: {.Pos: MainCode.point(), .NewName: NewName, .AST: AST, .MainFilePath: MainFilePath, |
1664 | .FS: createOverlay(Base: getVFSFromAST(AST), Overlay: InMemFS), .Index: &PIndex}); |
1665 | EXPECT_FALSE(Results); |
1666 | EXPECT_THAT(llvm::toString(Results.takeError()), |
1667 | testing::HasSubstr("too many occurrences" )); |
1668 | } |
1669 | |
1670 | TEST(CrossFileRenameTests, DeduplicateRefsFromIndex) { |
1671 | auto MainCode = Annotations("int [[^x]] = 2;" ); |
1672 | auto MainFilePath = testPath(File: "main.cc" ); |
1673 | auto BarCode = Annotations("int [[x]];" ); |
1674 | auto BarPath = testPath(File: "bar.cc" ); |
1675 | auto TU = TestTU::withCode(Code: MainCode.code()); |
1676 | // Set a file "bar.cc" on disk. |
1677 | TU.AdditionalFiles["bar.cc" ] = std::string(BarCode.code()); |
1678 | auto AST = TU.build(); |
1679 | std::string BarPathURI = URI::create(AbsolutePath: BarPath).toString(); |
1680 | Ref XRefInBarCC = refWithRange(Range: BarCode.range(), URI: BarPathURI); |
1681 | // The index will return duplicated refs, our code should be robost to handle |
1682 | // it. |
1683 | class DuplicatedXRefIndex : public SymbolIndex { |
1684 | public: |
1685 | DuplicatedXRefIndex(const Ref &ReturnedRef) : ReturnedRef(ReturnedRef) {} |
1686 | bool refs(const RefsRequest &Req, |
1687 | llvm::function_ref<void(const Ref &)> Callback) const override { |
1688 | // Return two duplicated refs. |
1689 | Callback(ReturnedRef); |
1690 | Callback(ReturnedRef); |
1691 | return false; |
1692 | } |
1693 | |
1694 | bool containedRefs(const ContainedRefsRequest &Req, |
1695 | llvm::function_ref<void(const ContainedRefsResult &)> |
1696 | Callback) const override { |
1697 | return false; |
1698 | } |
1699 | |
1700 | bool fuzzyFind(const FuzzyFindRequest &, |
1701 | llvm::function_ref<void(const Symbol &)>) const override { |
1702 | return false; |
1703 | } |
1704 | void lookup(const LookupRequest &, |
1705 | llvm::function_ref<void(const Symbol &)>) const override {} |
1706 | |
1707 | void relations(const RelationsRequest &, |
1708 | llvm::function_ref<void(const SymbolID &, const Symbol &)>) |
1709 | const override {} |
1710 | |
1711 | llvm::unique_function<IndexContents(llvm::StringRef) const> |
1712 | indexedFiles() const override { |
1713 | return [](llvm::StringRef) { return IndexContents::None; }; |
1714 | } |
1715 | |
1716 | size_t estimateMemoryUsage() const override { return 0; } |
1717 | Ref ReturnedRef; |
1718 | } DIndex(XRefInBarCC); |
1719 | llvm::StringRef NewName = "newName" ; |
1720 | auto Results = rename(RInputs: {.Pos: MainCode.point(), .NewName: NewName, .AST: AST, .MainFilePath: MainFilePath, |
1721 | .FS: getVFSFromAST(AST), .Index: &DIndex}); |
1722 | ASSERT_TRUE(bool(Results)) << Results.takeError(); |
1723 | EXPECT_THAT( |
1724 | applyEdits(std::move(Results->GlobalChanges)), |
1725 | UnorderedElementsAre( |
1726 | Pair(Eq(BarPath), Eq(expectedResult(BarCode, NewName))), |
1727 | Pair(Eq(MainFilePath), Eq(expectedResult(MainCode, NewName))))); |
1728 | } |
1729 | |
1730 | TEST(CrossFileRenameTests, WithUpToDateIndex) { |
1731 | MockCompilationDatabase CDB; |
1732 | CDB.ExtraClangFlags = {"-xobjective-c++" }; |
1733 | // rename is runnning on all "^" points in FooH, and "[[]]" ranges are the |
1734 | // expected rename occurrences. |
1735 | struct Case { |
1736 | llvm::StringRef FooH; |
1737 | llvm::StringRef FooCC; |
1738 | } Cases[] = { |
1739 | { |
1740 | // classes. |
1741 | .FooH: R"cpp( |
1742 | class [[Fo^o]] { |
1743 | [[Foo]](); |
1744 | ~[[Foo]](); |
1745 | }; |
1746 | )cpp" , |
1747 | .FooCC: R"cpp( |
1748 | #include "foo.h" |
1749 | [[Foo]]::[[Foo]]() {} |
1750 | [[Foo]]::~[[Foo]]() {} |
1751 | |
1752 | void func() { |
1753 | [[Foo]] foo; |
1754 | } |
1755 | )cpp" , |
1756 | }, |
1757 | { |
1758 | // class templates. |
1759 | .FooH: R"cpp( |
1760 | template <typename T> |
1761 | class [[Foo]] {}; |
1762 | // FIXME: explicit template specializations are not supported due the |
1763 | // clangd index limitations. |
1764 | template <> |
1765 | class Foo<double> {}; |
1766 | )cpp" , |
1767 | .FooCC: R"cpp( |
1768 | #include "foo.h" |
1769 | void func() { |
1770 | [[F^oo]]<int> foo; |
1771 | } |
1772 | )cpp" , |
1773 | }, |
1774 | { |
1775 | // class methods. |
1776 | .FooH: R"cpp( |
1777 | class Foo { |
1778 | void [[f^oo]](); |
1779 | }; |
1780 | )cpp" , |
1781 | .FooCC: R"cpp( |
1782 | #include "foo.h" |
1783 | void Foo::[[foo]]() {} |
1784 | |
1785 | void func(Foo* p) { |
1786 | p->[[foo]](); |
1787 | } |
1788 | )cpp" , |
1789 | }, |
1790 | { |
1791 | // virtual methods. |
1792 | .FooH: R"cpp( |
1793 | class Base { |
1794 | virtual void [[foo]](); |
1795 | }; |
1796 | class Derived1 : public Base { |
1797 | void [[f^oo]]() override; |
1798 | }; |
1799 | class NotDerived { |
1800 | void foo() {}; |
1801 | } |
1802 | )cpp" , |
1803 | .FooCC: R"cpp( |
1804 | #include "foo.h" |
1805 | void Base::[[foo]]() {} |
1806 | void Derived1::[[foo]]() {} |
1807 | |
1808 | class Derived2 : public Derived1 { |
1809 | void [[foo]]() override {}; |
1810 | }; |
1811 | |
1812 | void func(Base* b, Derived1* d1, |
1813 | Derived2* d2, NotDerived* nd) { |
1814 | b->[[foo]](); |
1815 | d1->[[foo]](); |
1816 | d2->[[foo]](); |
1817 | nd->foo(); |
1818 | } |
1819 | )cpp" , |
1820 | }, |
1821 | {// virtual templated method |
1822 | .FooH: R"cpp( |
1823 | template <typename> class Foo { virtual void [[m]](); }; |
1824 | class Bar : Foo<int> { void [[^m]]() override; }; |
1825 | )cpp" , |
1826 | .FooCC: R"cpp( |
1827 | #include "foo.h" |
1828 | |
1829 | template<typename T> void Foo<T>::[[m]]() {} |
1830 | // FIXME: not renamed as the index doesn't see this as an override of |
1831 | // the canonical Foo<T>::m(). |
1832 | // https://github.com/clangd/clangd/issues/1325 |
1833 | class Baz : Foo<float> { void m() override; }; |
1834 | )cpp" }, |
1835 | { |
1836 | // rename on constructor and destructor. |
1837 | .FooH: R"cpp( |
1838 | class [[Foo]] { |
1839 | [[^Foo]](); |
1840 | ~[[Foo^]](); |
1841 | }; |
1842 | )cpp" , |
1843 | .FooCC: R"cpp( |
1844 | #include "foo.h" |
1845 | [[Foo]]::[[Foo]]() {} |
1846 | [[Foo]]::~[[Foo]]() {} |
1847 | |
1848 | void func() { |
1849 | [[Foo]] foo; |
1850 | } |
1851 | )cpp" , |
1852 | }, |
1853 | { |
1854 | // functions. |
1855 | .FooH: R"cpp( |
1856 | void [[f^oo]](); |
1857 | )cpp" , |
1858 | .FooCC: R"cpp( |
1859 | #include "foo.h" |
1860 | void [[foo]]() {} |
1861 | |
1862 | void func() { |
1863 | [[foo]](); |
1864 | } |
1865 | )cpp" , |
1866 | }, |
1867 | { |
1868 | // typedefs. |
1869 | .FooH: R"cpp( |
1870 | typedef int [[IN^T]]; |
1871 | [[INT]] foo(); |
1872 | )cpp" , |
1873 | .FooCC: R"cpp( |
1874 | #include "foo.h" |
1875 | [[INT]] foo() {} |
1876 | )cpp" , |
1877 | }, |
1878 | { |
1879 | // usings. |
1880 | .FooH: R"cpp( |
1881 | using [[I^NT]] = int; |
1882 | [[INT]] foo(); |
1883 | )cpp" , |
1884 | .FooCC: R"cpp( |
1885 | #include "foo.h" |
1886 | [[INT]] foo() {} |
1887 | )cpp" , |
1888 | }, |
1889 | { |
1890 | // variables. |
1891 | .FooH: R"cpp( |
1892 | static const int [[VA^R]] = 123; |
1893 | )cpp" , |
1894 | .FooCC: R"cpp( |
1895 | #include "foo.h" |
1896 | int s = [[VAR]]; |
1897 | )cpp" , |
1898 | }, |
1899 | { |
1900 | // scope enums. |
1901 | .FooH: R"cpp( |
1902 | enum class [[K^ind]] { ABC }; |
1903 | )cpp" , |
1904 | .FooCC: R"cpp( |
1905 | #include "foo.h" |
1906 | [[Kind]] ff() { |
1907 | return [[Kind]]::ABC; |
1908 | } |
1909 | )cpp" , |
1910 | }, |
1911 | { |
1912 | // enum constants. |
1913 | .FooH: R"cpp( |
1914 | enum class Kind { [[A^BC]] }; |
1915 | )cpp" , |
1916 | .FooCC: R"cpp( |
1917 | #include "foo.h" |
1918 | Kind ff() { |
1919 | return Kind::[[ABC]]; |
1920 | } |
1921 | )cpp" , |
1922 | }, |
1923 | { |
1924 | // Implicit references in macro expansions. |
1925 | .FooH: R"cpp( |
1926 | class [[Fo^o]] {}; |
1927 | #define FooFoo Foo |
1928 | #define FOO Foo |
1929 | )cpp" , |
1930 | .FooCC: R"cpp( |
1931 | #include "foo.h" |
1932 | void bar() { |
1933 | [[Foo]] x; |
1934 | FOO y; |
1935 | FooFoo z; |
1936 | } |
1937 | )cpp" , |
1938 | }, |
1939 | { |
1940 | // Objective-C classes. |
1941 | .FooH: R"cpp( |
1942 | @interface [[Fo^o]] |
1943 | @end |
1944 | )cpp" , |
1945 | .FooCC: R"cpp( |
1946 | #include "foo.h" |
1947 | @implementation [[Foo]] |
1948 | @end |
1949 | |
1950 | void func([[Foo]] *f) {} |
1951 | )cpp" , |
1952 | }, |
1953 | }; |
1954 | |
1955 | trace::TestTracer Tracer; |
1956 | for (const auto &T : Cases) { |
1957 | SCOPED_TRACE(T.FooH); |
1958 | Annotations FooH(T.FooH); |
1959 | Annotations FooCC(T.FooCC); |
1960 | std::string FooHPath = testPath(File: "foo.h" ); |
1961 | std::string FooCCPath = testPath(File: "foo.cc" ); |
1962 | |
1963 | MockFS FS; |
1964 | FS.Files[FooHPath] = std::string(FooH.code()); |
1965 | FS.Files[FooCCPath] = std::string(FooCC.code()); |
1966 | |
1967 | auto ServerOpts = ClangdServer::optsForTest(); |
1968 | ServerOpts.BuildDynamicSymbolIndex = true; |
1969 | ClangdServer Server(CDB, FS, ServerOpts); |
1970 | |
1971 | // Add all files to clangd server to make sure the dynamic index has been |
1972 | // built. |
1973 | runAddDocument(Server, File: FooHPath, Contents: FooH.code()); |
1974 | runAddDocument(Server, File: FooCCPath, Contents: FooCC.code()); |
1975 | |
1976 | llvm::StringRef NewName = "NewName" ; |
1977 | for (const auto &RenamePos : FooH.points()) { |
1978 | EXPECT_THAT(Tracer.takeMetric("rename_files" ), SizeIs(0)); |
1979 | auto FileEditsList = |
1980 | llvm::cantFail(ValOrErr: runRename(Server, File: FooHPath, Pos: RenamePos, NewName, RenameOpts: {})); |
1981 | EXPECT_THAT(Tracer.takeMetric("rename_files" ), ElementsAre(2)); |
1982 | EXPECT_THAT( |
1983 | applyEdits(std::move(FileEditsList.GlobalChanges)), |
1984 | UnorderedElementsAre( |
1985 | Pair(Eq(FooHPath), Eq(expectedResult(T.FooH, NewName))), |
1986 | Pair(Eq(FooCCPath), Eq(expectedResult(T.FooCC, NewName))))); |
1987 | } |
1988 | } |
1989 | } |
1990 | |
1991 | TEST(CrossFileRenameTests, ObjC) { |
1992 | MockCompilationDatabase CDB; |
1993 | CDB.ExtraClangFlags = {"-xobjective-c" }; |
1994 | // rename is runnning on all "^" points in FooH. |
1995 | struct Case { |
1996 | llvm::StringRef FooH; |
1997 | llvm::StringRef FooM; |
1998 | llvm::StringRef NewName; |
1999 | llvm::StringRef ExpectedFooH; |
2000 | llvm::StringRef ExpectedFooM; |
2001 | }; |
2002 | Case Cases[] = {// --- Zero arg selector |
2003 | { |
2004 | // Input |
2005 | .FooH: R"cpp( |
2006 | @interface Foo |
2007 | - (int)performA^ction; |
2008 | @end |
2009 | )cpp" , |
2010 | .FooM: R"cpp( |
2011 | @implementation Foo |
2012 | - (int)performAction { |
2013 | [self performAction]; |
2014 | } |
2015 | @end |
2016 | )cpp" , |
2017 | // New name |
2018 | .NewName: "performNewAction" , |
2019 | // Expected |
2020 | .ExpectedFooH: R"cpp( |
2021 | @interface Foo |
2022 | - (int)performNewAction; |
2023 | @end |
2024 | )cpp" , |
2025 | .ExpectedFooM: R"cpp( |
2026 | @implementation Foo |
2027 | - (int)performNewAction { |
2028 | [self performNewAction]; |
2029 | } |
2030 | @end |
2031 | )cpp" , |
2032 | }, |
2033 | // --- Single arg selector |
2034 | { |
2035 | // Input |
2036 | .FooH: R"cpp( |
2037 | @interface Foo |
2038 | - (int)performA^ction:(int)action; |
2039 | @end |
2040 | )cpp" , |
2041 | .FooM: R"cpp( |
2042 | @implementation Foo |
2043 | - (int)performAction:(int)action { |
2044 | [self performAction:action]; |
2045 | } |
2046 | @end |
2047 | )cpp" , |
2048 | // New name |
2049 | .NewName: "performNewAction:" , |
2050 | // Expected |
2051 | .ExpectedFooH: R"cpp( |
2052 | @interface Foo |
2053 | - (int)performNewAction:(int)action; |
2054 | @end |
2055 | )cpp" , |
2056 | .ExpectedFooM: R"cpp( |
2057 | @implementation Foo |
2058 | - (int)performNewAction:(int)action { |
2059 | [self performNewAction:action]; |
2060 | } |
2061 | @end |
2062 | )cpp" , |
2063 | }, |
2064 | // --- Multi arg selector |
2065 | { |
2066 | // Input |
2067 | .FooH: R"cpp( |
2068 | @interface Foo |
2069 | - (int)performA^ction:(int)action with:(int)value; |
2070 | @end |
2071 | )cpp" , |
2072 | .FooM: R"cpp( |
2073 | @implementation Foo |
2074 | - (int)performAction:(int)action with:(int)value { |
2075 | [self performAction:action with:value]; |
2076 | } |
2077 | @end |
2078 | )cpp" , |
2079 | // New name |
2080 | .NewName: "performNewAction:by:" , |
2081 | // Expected |
2082 | .ExpectedFooH: R"cpp( |
2083 | @interface Foo |
2084 | - (int)performNewAction:(int)action by:(int)value; |
2085 | @end |
2086 | )cpp" , |
2087 | .ExpectedFooM: R"cpp( |
2088 | @implementation Foo |
2089 | - (int)performNewAction:(int)action by:(int)value { |
2090 | [self performNewAction:action by:value]; |
2091 | } |
2092 | @end |
2093 | )cpp" , |
2094 | }}; |
2095 | |
2096 | trace::TestTracer Tracer; |
2097 | for (const auto &T : Cases) { |
2098 | SCOPED_TRACE(T.FooH); |
2099 | Annotations FooH(T.FooH); |
2100 | Annotations FooM(T.FooM); |
2101 | std::string FooHPath = testPath(File: "foo.h" ); |
2102 | std::string FooMPath = testPath(File: "foo.m" ); |
2103 | |
2104 | MockFS FS; |
2105 | FS.Files[FooHPath] = std::string(FooH.code()); |
2106 | FS.Files[FooMPath] = std::string(FooM.code()); |
2107 | |
2108 | auto ServerOpts = ClangdServer::optsForTest(); |
2109 | ServerOpts.BuildDynamicSymbolIndex = true; |
2110 | ClangdServer Server(CDB, FS, ServerOpts); |
2111 | |
2112 | // Add all files to clangd server to make sure the dynamic index has been |
2113 | // built. |
2114 | runAddDocument(Server, File: FooHPath, Contents: FooH.code()); |
2115 | runAddDocument(Server, File: FooMPath, Contents: FooM.code()); |
2116 | |
2117 | for (const auto &RenamePos : FooH.points()) { |
2118 | EXPECT_THAT(Tracer.takeMetric("rename_files" ), SizeIs(0)); |
2119 | auto FileEditsList = |
2120 | llvm::cantFail(ValOrErr: runRename(Server, File: FooHPath, Pos: RenamePos, NewName: T.NewName, RenameOpts: {})); |
2121 | EXPECT_THAT(Tracer.takeMetric("rename_files" ), ElementsAre(2)); |
2122 | EXPECT_THAT(applyEdits(std::move(FileEditsList.GlobalChanges)), |
2123 | UnorderedElementsAre(Pair(Eq(FooHPath), Eq(T.ExpectedFooH)), |
2124 | Pair(Eq(FooMPath), Eq(T.ExpectedFooM)))); |
2125 | } |
2126 | } |
2127 | } |
2128 | |
2129 | TEST(CrossFileRenameTests, CrossFileOnLocalSymbol) { |
2130 | // cross-file rename should work for function-local symbols, even there is no |
2131 | // index provided. |
2132 | Annotations Code("void f(int [[abc]]) { [[a^bc]] = 3; }" ); |
2133 | auto TU = TestTU::withCode(Code: Code.code()); |
2134 | auto Path = testPath(File: TU.Filename); |
2135 | auto AST = TU.build(); |
2136 | llvm::StringRef NewName = "newName" ; |
2137 | auto Results = rename(RInputs: {.Pos: Code.point(), .NewName: NewName, .AST: AST, .MainFilePath: Path}); |
2138 | ASSERT_TRUE(bool(Results)) << Results.takeError(); |
2139 | EXPECT_THAT( |
2140 | applyEdits(std::move(Results->GlobalChanges)), |
2141 | UnorderedElementsAre(Pair(Eq(Path), Eq(expectedResult(Code, NewName))))); |
2142 | } |
2143 | |
2144 | TEST(CrossFileRenameTests, BuildRenameEdits) { |
2145 | Annotations Code("[[😂]]" ); |
2146 | auto LSPRange = Code.range(); |
2147 | llvm::StringRef FilePath = "/test/TestTU.cpp" ; |
2148 | llvm::SmallVector<llvm::StringRef, 2> NewNames = {"abc" }; |
2149 | auto Edit = buildRenameEdit(AbsFilePath: FilePath, InitialCode: Code.code(), Occurrences: {LSPRange}, NewNames); |
2150 | ASSERT_TRUE(bool(Edit)) << Edit.takeError(); |
2151 | ASSERT_EQ(1UL, Edit->Replacements.size()); |
2152 | EXPECT_EQ(FilePath, Edit->Replacements.begin()->getFilePath()); |
2153 | EXPECT_EQ(4UL, Edit->Replacements.begin()->getLength()); |
2154 | |
2155 | // Test invalid range. |
2156 | LSPRange.end = {.line: 10, .character: 0}; // out of range |
2157 | Edit = buildRenameEdit(AbsFilePath: FilePath, InitialCode: Code.code(), Occurrences: {LSPRange}, NewNames); |
2158 | EXPECT_FALSE(Edit); |
2159 | EXPECT_THAT(llvm::toString(Edit.takeError()), |
2160 | testing::HasSubstr("fail to convert" )); |
2161 | |
2162 | // Normal ascii characters. |
2163 | Annotations T(R"cpp( |
2164 | [[range]] |
2165 | [[range]] |
2166 | [[range]] |
2167 | )cpp" ); |
2168 | Edit = |
2169 | buildRenameEdit(AbsFilePath: FilePath, InitialCode: T.code(), Occurrences: symbolRanges(Ranges: T.ranges()), NewNames); |
2170 | ASSERT_TRUE(bool(Edit)) << Edit.takeError(); |
2171 | EXPECT_EQ(applyEdits(FileEdits{{T.code(), std::move(*Edit)}}).front().second, |
2172 | expectedResult(T, NewNames[0])); |
2173 | } |
2174 | |
2175 | TEST(CrossFileRenameTests, adjustRenameRanges) { |
2176 | // Ranges in IndexedCode indicate the indexed occurrences; |
2177 | // ranges in DraftCode indicate the expected mapped result, empty indicates |
2178 | // we expect no matched result found. |
2179 | struct { |
2180 | llvm::StringRef IndexedCode; |
2181 | llvm::StringRef DraftCode; |
2182 | } Tests[] = { |
2183 | { |
2184 | // both line and column are changed, not a near miss. |
2185 | .IndexedCode: R"cpp( |
2186 | int [[x]] = 0; |
2187 | )cpp" , |
2188 | .DraftCode: R"cpp( |
2189 | // insert a line. |
2190 | double x = 0; |
2191 | )cpp" , |
2192 | }, |
2193 | { |
2194 | // subset. |
2195 | .IndexedCode: R"cpp( |
2196 | int [[x]] = 0; |
2197 | )cpp" , |
2198 | .DraftCode: R"cpp( |
2199 | int [[x]] = 0; |
2200 | {int x = 0; } |
2201 | )cpp" , |
2202 | }, |
2203 | { |
2204 | // shift columns. |
2205 | .IndexedCode: R"cpp(int [[x]] = 0; void foo(int x);)cpp" , |
2206 | .DraftCode: R"cpp(double [[x]] = 0; void foo(double x);)cpp" , |
2207 | }, |
2208 | { |
2209 | // shift lines. |
2210 | .IndexedCode: R"cpp( |
2211 | int [[x]] = 0; |
2212 | void foo(int x); |
2213 | )cpp" , |
2214 | .DraftCode: R"cpp( |
2215 | // insert a line. |
2216 | int [[x]] = 0; |
2217 | void foo(int x); |
2218 | )cpp" , |
2219 | }, |
2220 | }; |
2221 | LangOptions LangOpts; |
2222 | LangOpts.CPlusPlus = true; |
2223 | for (const auto &T : Tests) { |
2224 | SCOPED_TRACE(T.DraftCode); |
2225 | Annotations Draft(T.DraftCode); |
2226 | auto ActualRanges = adjustRenameRanges( |
2227 | DraftCode: Draft.code(), Name: RenameSymbolName(ArrayRef<std::string>{"x" }), |
2228 | Indexed: Annotations(T.IndexedCode).ranges(), LangOpts); |
2229 | if (!ActualRanges) |
2230 | EXPECT_THAT(Draft.ranges(), testing::IsEmpty()); |
2231 | else |
2232 | EXPECT_THAT(Draft.ranges(), |
2233 | testing::UnorderedElementsAreArray(*ActualRanges)); |
2234 | } |
2235 | } |
2236 | |
2237 | TEST(RangePatchingHeuristic, GetMappedRanges) { |
2238 | // ^ in LexedCode marks the ranges we expect to be mapped; no ^ indicates |
2239 | // there are no mapped ranges. |
2240 | struct { |
2241 | llvm::StringRef IndexedCode; |
2242 | llvm::StringRef LexedCode; |
2243 | } Tests[] = { |
2244 | { |
2245 | // no lexed ranges. |
2246 | .IndexedCode: "[[]]" , |
2247 | .LexedCode: "" , |
2248 | }, |
2249 | { |
2250 | // both line and column are changed, not a near miss. |
2251 | .IndexedCode: R"([[]])" , |
2252 | .LexedCode: R"( |
2253 | [[]] |
2254 | )" , |
2255 | }, |
2256 | { |
2257 | // subset. |
2258 | .IndexedCode: "[[]]" , |
2259 | .LexedCode: "^[[]] [[]]" |
2260 | }, |
2261 | { |
2262 | // shift columns. |
2263 | .IndexedCode: "[[]] [[]]" , |
2264 | .LexedCode: " ^[[]] ^[[]] [[]]" |
2265 | }, |
2266 | { |
2267 | .IndexedCode: R"( |
2268 | [[]] |
2269 | |
2270 | [[]] [[]] |
2271 | )" , |
2272 | .LexedCode: R"( |
2273 | // insert a line |
2274 | ^[[]] |
2275 | |
2276 | ^[[]] ^[[]] |
2277 | )" , |
2278 | }, |
2279 | { |
2280 | .IndexedCode: R"( |
2281 | [[]] |
2282 | |
2283 | [[]] [[]] |
2284 | )" , |
2285 | .LexedCode: R"( |
2286 | // insert a line |
2287 | ^[[]] |
2288 | ^[[]] ^[[]] // column is shifted. |
2289 | )" , |
2290 | }, |
2291 | { |
2292 | .IndexedCode: R"( |
2293 | [[]] |
2294 | |
2295 | [[]] [[]] |
2296 | )" , |
2297 | .LexedCode: R"( |
2298 | // insert a line |
2299 | [[]] |
2300 | |
2301 | [[]] [[]] // not mapped (both line and column are changed). |
2302 | )" , |
2303 | }, |
2304 | { |
2305 | .IndexedCode: R"( |
2306 | [[]] |
2307 | [[]] |
2308 | |
2309 | [[]] |
2310 | [[]] |
2311 | |
2312 | } |
2313 | )" , |
2314 | .LexedCode: R"( |
2315 | // insert a new line |
2316 | ^[[]] |
2317 | ^[[]] |
2318 | [[]] // additional range |
2319 | ^[[]] |
2320 | ^[[]] |
2321 | [[]] // additional range |
2322 | )" , |
2323 | }, |
2324 | { |
2325 | // non-distinct result (two best results), not a near miss |
2326 | .IndexedCode: R"( |
2327 | [[]] |
2328 | [[]] |
2329 | [[]] |
2330 | )" , |
2331 | .LexedCode: R"( |
2332 | [[]] |
2333 | [[]] |
2334 | [[]] |
2335 | [[]] |
2336 | )" , |
2337 | } |
2338 | }; |
2339 | for (const auto &T : Tests) { |
2340 | SCOPED_TRACE(T.IndexedCode); |
2341 | auto Lexed = Annotations(T.LexedCode); |
2342 | auto LexedRanges = symbolRanges(Ranges: Lexed.ranges()); |
2343 | std::vector<SymbolRange> ExpectedMatches; |
2344 | for (auto P : Lexed.points()) { |
2345 | auto Match = llvm::find_if(Range&: LexedRanges, P: [&P](const SymbolRange &R) { |
2346 | return R.range().start == P; |
2347 | }); |
2348 | ASSERT_NE(Match, LexedRanges.end()); |
2349 | ExpectedMatches.push_back(x: *Match); |
2350 | } |
2351 | |
2352 | auto Mapped = |
2353 | getMappedRanges(Indexed: Annotations(T.IndexedCode).ranges(), Lexed: LexedRanges); |
2354 | if (!Mapped) |
2355 | EXPECT_THAT(ExpectedMatches, IsEmpty()); |
2356 | else |
2357 | EXPECT_THAT(ExpectedMatches, UnorderedElementsAreArray(*Mapped)); |
2358 | } |
2359 | } |
2360 | |
2361 | TEST(CrossFileRenameTests, adjustmentCost) { |
2362 | struct { |
2363 | llvm::StringRef RangeCode; |
2364 | size_t ExpectedCost; |
2365 | } Tests[] = { |
2366 | { |
2367 | .RangeCode: R"( |
2368 | $idx[[]]$lex[[]] // diff: 0 |
2369 | )" , |
2370 | .ExpectedCost: 0, |
2371 | }, |
2372 | { |
2373 | .RangeCode: R"( |
2374 | $idx[[]] |
2375 | $lex[[]] // line diff: +1 |
2376 | $idx[[]] |
2377 | $lex[[]] // line diff: +1 |
2378 | $idx[[]] |
2379 | $lex[[]] // line diff: +1 |
2380 | |
2381 | $idx[[]] |
2382 | |
2383 | $lex[[]] // line diff: +2 |
2384 | )" , |
2385 | .ExpectedCost: 1 + 1 |
2386 | }, |
2387 | { |
2388 | .RangeCode: R"( |
2389 | $idx[[]] |
2390 | $lex[[]] // line diff: +1 |
2391 | $idx[[]] |
2392 | |
2393 | $lex[[]] // line diff: +2 |
2394 | $idx[[]] |
2395 | |
2396 | |
2397 | $lex[[]] // line diff: +3 |
2398 | )" , |
2399 | .ExpectedCost: 1 + 1 + 1 |
2400 | }, |
2401 | { |
2402 | .RangeCode: R"( |
2403 | $idx[[]] |
2404 | |
2405 | |
2406 | $lex[[]] // line diff: +3 |
2407 | $idx[[]] |
2408 | |
2409 | $lex[[]] // line diff: +2 |
2410 | $idx[[]] |
2411 | $lex[[]] // line diff: +1 |
2412 | )" , |
2413 | .ExpectedCost: 3 + 1 + 1 |
2414 | }, |
2415 | { |
2416 | .RangeCode: R"( |
2417 | $idx[[]] |
2418 | $lex[[]] // line diff: +1 |
2419 | $lex[[]] // line diff: -2 |
2420 | |
2421 | $idx[[]] |
2422 | $idx[[]] |
2423 | |
2424 | |
2425 | $lex[[]] // line diff: +3 |
2426 | )" , |
2427 | .ExpectedCost: 1 + 3 + 5 |
2428 | }, |
2429 | { |
2430 | .RangeCode: R"( |
2431 | $idx[[]] $lex[[]] // column diff: +1 |
2432 | $idx[[]]$lex[[]] // diff: 0 |
2433 | )" , |
2434 | .ExpectedCost: 1 |
2435 | }, |
2436 | { |
2437 | .RangeCode: R"( |
2438 | $idx[[]] |
2439 | $lex[[]] // diff: +1 |
2440 | $idx[[]] $lex[[]] // column diff: +1 |
2441 | $idx[[]]$lex[[]] // diff: 0 |
2442 | )" , |
2443 | .ExpectedCost: 1 + 1 + 1 |
2444 | }, |
2445 | { |
2446 | .RangeCode: R"( |
2447 | $idx[[]] $lex[[]] // column diff: +1 |
2448 | )" , |
2449 | .ExpectedCost: 1 |
2450 | }, |
2451 | { |
2452 | .RangeCode: R"( |
2453 | // column diffs: +1, +2, +3 |
2454 | $idx[[]] $lex[[]] $idx[[]] $lex[[]] $idx[[]] $lex[[]] |
2455 | )" , |
2456 | .ExpectedCost: 1 + 1 + 1, |
2457 | }, |
2458 | }; |
2459 | for (const auto &T : Tests) { |
2460 | SCOPED_TRACE(T.RangeCode); |
2461 | Annotations C(T.RangeCode); |
2462 | std::vector<size_t> MappedIndex; |
2463 | for (size_t I = 0; I < C.ranges(Name: "lex" ).size(); ++I) |
2464 | MappedIndex.push_back(x: I); |
2465 | EXPECT_EQ(renameRangeAdjustmentCost( |
2466 | C.ranges("idx" ), symbolRanges(C.ranges("lex" )), MappedIndex), |
2467 | T.ExpectedCost); |
2468 | } |
2469 | } |
2470 | |
2471 | } // namespace |
2472 | } // namespace clangd |
2473 | } // namespace clang |
2474 | |