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]]() {} |
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]]() {} |
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 | void func(int); |
1274 | void [[o^therFunc]](double); |
1275 | )cpp" , |
1276 | .ErrorMessage: nullptr, .IsHeaderFile: !HeaderFile, .NewName: "func" }, |
1277 | {.Code: R"cpp( |
1278 | struct S { |
1279 | void func(int); |
1280 | void [[o^therFunc]](double); |
1281 | }; |
1282 | )cpp" , |
1283 | .ErrorMessage: nullptr, .IsHeaderFile: !HeaderFile, .NewName: "func" }, |
1284 | |
1285 | {.Code: R"cpp( |
1286 | int V^ar; |
1287 | )cpp" , |
1288 | .ErrorMessage: "\"const\" is a keyword" , .IsHeaderFile: !HeaderFile, .NewName: "const" }, |
1289 | |
1290 | {.Code: R"cpp(// Trying to rename into the same name, SameName == SameName. |
1291 | void func() { |
1292 | int S^ameName; |
1293 | } |
1294 | )cpp" , |
1295 | .ErrorMessage: "new name is the same" , .IsHeaderFile: !HeaderFile, .NewName: "SameName" }, |
1296 | {.Code: R"cpp(// Ensure it doesn't associate base specifier with base name. |
1297 | struct A {}; |
1298 | struct B : priv^ate A {}; |
1299 | )cpp" , |
1300 | .ErrorMessage: "Cannot rename symbol: there is no symbol at the given location" , .IsHeaderFile: false}, |
1301 | {.Code: R"cpp(// Ensure it doesn't associate base specifier with base name. |
1302 | /*error-ok*/ |
1303 | struct A { |
1304 | A() : inva^lid(0) {} |
1305 | }; |
1306 | )cpp" , |
1307 | .ErrorMessage: "no symbol" , .IsHeaderFile: false}, |
1308 | |
1309 | {.Code: R"cpp(// FIXME we probably want to rename both overloads here, |
1310 | // but renaming currently assumes there's only a |
1311 | // single canonical declaration. |
1312 | namespace ns { int foo(int); char foo(char); } |
1313 | using ns::^foo; |
1314 | )cpp" , |
1315 | .ErrorMessage: "there are multiple symbols at the given location" , .IsHeaderFile: !HeaderFile}, |
1316 | |
1317 | {.Code: R"cpp( |
1318 | void test() { |
1319 | // no crash |
1320 | using namespace std; |
1321 | int [[V^ar]]; |
1322 | } |
1323 | )cpp" , |
1324 | .ErrorMessage: nullptr, .IsHeaderFile: !HeaderFile}, |
1325 | }; |
1326 | |
1327 | for (const auto& Case : Cases) { |
1328 | SCOPED_TRACE(Case.Code); |
1329 | Annotations T(Case.Code); |
1330 | TestTU TU = TestTU::withCode(Code: T.code()); |
1331 | TU.ExtraArgs.push_back(x: "-fno-delayed-template-parsing" ); |
1332 | if (Case.IsHeaderFile) { |
1333 | // We open the .h file as the main file. |
1334 | TU.Filename = "test.h" ; |
1335 | // Parsing the .h file as C++ include. |
1336 | TU.ExtraArgs.push_back(x: "-xobjective-c++-header" ); |
1337 | } |
1338 | auto AST = TU.build(); |
1339 | llvm::StringRef NewName = Case.NewName; |
1340 | auto Results = rename(RInputs: {.Pos: T.point(), .NewName: NewName, .AST: AST, .MainFilePath: testPath(File: TU.Filename)}); |
1341 | bool WantRename = true; |
1342 | if (T.ranges().empty()) |
1343 | WantRename = false; |
1344 | if (!WantRename) { |
1345 | assert(Case.ErrorMessage && "Error message must be set!" ); |
1346 | EXPECT_FALSE(Results) |
1347 | << "expected rename returned an error: " << T.code(); |
1348 | auto ActualMessage = llvm::toString(E: Results.takeError()); |
1349 | EXPECT_THAT(ActualMessage, testing::HasSubstr(Case.ErrorMessage)); |
1350 | } else { |
1351 | EXPECT_TRUE(bool(Results)) << "rename returned an error: " |
1352 | << llvm::toString(E: Results.takeError()); |
1353 | EXPECT_EQ(Results->LocalChanges, T.ranges()); |
1354 | } |
1355 | } |
1356 | } |
1357 | |
1358 | MATCHER_P(newText, T, "" ) { return arg.newText == T; } |
1359 | |
1360 | TEST(RenameTest, IndexMergeMainFile) { |
1361 | Annotations Code("int ^x();" ); |
1362 | TestTU TU = TestTU::withCode(Code: Code.code()); |
1363 | TU.Filename = "main.cc" ; |
1364 | auto AST = TU.build(); |
1365 | |
1366 | auto Main = testPath(File: "main.cc" ); |
1367 | auto InMemFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); |
1368 | InMemFS->addFile(Path: testPath(File: "main.cc" ), ModificationTime: 0, |
1369 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: Code.code())); |
1370 | InMemFS->addFile(Path: testPath(File: "other.cc" ), ModificationTime: 0, |
1371 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: Code.code())); |
1372 | |
1373 | auto Rename = [&](const SymbolIndex *Idx) { |
1374 | RenameInputs Inputs{.Pos: Code.point(), |
1375 | .NewName: "xPrime" , |
1376 | .AST: AST, |
1377 | .MainFilePath: Main, |
1378 | .FS: Idx ? createOverlay(Base: getVFSFromAST(AST), Overlay: InMemFS) |
1379 | : nullptr, |
1380 | .Index: Idx, |
1381 | .Opts: RenameOptions()}; |
1382 | auto Results = rename(RInputs: Inputs); |
1383 | EXPECT_TRUE(bool(Results)) << llvm::toString(E: Results.takeError()); |
1384 | return std::move(*Results); |
1385 | }; |
1386 | |
1387 | // We do not expect to see duplicated edits from AST vs index. |
1388 | auto Results = Rename(TU.index().get()); |
1389 | EXPECT_THAT(Results.GlobalChanges.keys(), ElementsAre(Main)); |
1390 | EXPECT_THAT(Results.GlobalChanges[Main].asTextEdits(), |
1391 | ElementsAre(newText("xPrime" ))); |
1392 | |
1393 | // Sanity check: we do expect to see index results! |
1394 | TU.Filename = "other.cc" ; |
1395 | Results = Rename(TU.index().get()); |
1396 | EXPECT_THAT(Results.GlobalChanges.keys(), |
1397 | UnorderedElementsAre(Main, testPath("other.cc" ))); |
1398 | |
1399 | #ifdef CLANGD_PATH_CASE_INSENSITIVE |
1400 | // On case-insensitive systems, no duplicates if AST vs index case differs. |
1401 | // https://github.com/clangd/clangd/issues/665 |
1402 | TU.Filename = "MAIN.CC" ; |
1403 | Results = Rename(TU.index().get()); |
1404 | EXPECT_THAT(Results.GlobalChanges.keys(), ElementsAre(Main)); |
1405 | EXPECT_THAT(Results.GlobalChanges[Main].asTextEdits(), |
1406 | ElementsAre(newText("xPrime" ))); |
1407 | #endif |
1408 | } |
1409 | |
1410 | TEST(RenameTest, MainFileReferencesOnly) { |
1411 | // filter out references not from main file. |
1412 | llvm::StringRef Test = |
1413 | R"cpp( |
1414 | void test() { |
1415 | int [[fo^o]] = 1; |
1416 | // rename references not from main file are not included. |
1417 | #include "foo.inc" |
1418 | })cpp" ; |
1419 | |
1420 | Annotations Code(Test); |
1421 | auto TU = TestTU::withCode(Code: Code.code()); |
1422 | TU.AdditionalFiles["foo.inc" ] = R"cpp( |
1423 | #define Macro(X) X |
1424 | &Macro(foo); |
1425 | &foo; |
1426 | )cpp" ; |
1427 | auto AST = TU.build(); |
1428 | llvm::StringRef NewName = "abcde" ; |
1429 | |
1430 | auto RenameResult = |
1431 | rename(RInputs: {.Pos: Code.point(), .NewName: NewName, .AST: AST, .MainFilePath: testPath(File: TU.Filename)}); |
1432 | ASSERT_TRUE(bool(RenameResult)) << RenameResult.takeError() << Code.point(); |
1433 | ASSERT_EQ(1u, RenameResult->GlobalChanges.size()); |
1434 | EXPECT_EQ(applyEdits(std::move(RenameResult->GlobalChanges)).front().second, |
1435 | expectedResult(Code, NewName)); |
1436 | } |
1437 | |
1438 | TEST(RenameTest, NoRenameOnSymbolsFromSystemHeaders) { |
1439 | llvm::StringRef Test = |
1440 | R"cpp( |
1441 | #include <cstdlib> |
1442 | #include <system> |
1443 | |
1444 | SystemSym^bol abc; |
1445 | |
1446 | void foo() { at^oi("9000"); } |
1447 | )cpp" ; |
1448 | |
1449 | Annotations Code(Test); |
1450 | auto TU = TestTU::withCode(Code: Code.code()); |
1451 | TU.AdditionalFiles["system" ] = R"cpp( |
1452 | class SystemSymbol {}; |
1453 | )cpp" ; |
1454 | TU.AdditionalFiles["cstdlib" ] = R"cpp( |
1455 | int atoi(const char *str); |
1456 | )cpp" ; |
1457 | TU.ExtraArgs = {"-isystem" , testRoot()}; |
1458 | auto AST = TU.build(); |
1459 | llvm::StringRef NewName = "abcde" ; |
1460 | |
1461 | // Clangd will not allow renaming symbols from the system headers for |
1462 | // correctness. |
1463 | for (auto &Point : Code.points()) { |
1464 | auto Results = rename(RInputs: {.Pos: Point, .NewName: NewName, .AST: AST, .MainFilePath: testPath(File: TU.Filename)}); |
1465 | EXPECT_FALSE(Results) << "expected rename returned an error: " |
1466 | << Code.code(); |
1467 | auto ActualMessage = llvm::toString(E: Results.takeError()); |
1468 | EXPECT_THAT(ActualMessage, testing::HasSubstr("not a supported kind" )); |
1469 | } |
1470 | } |
1471 | |
1472 | TEST(RenameTest, ProtobufSymbolIsExcluded) { |
1473 | Annotations Code("Prot^obuf buf;" ); |
1474 | auto TU = TestTU::withCode(Code: Code.code()); |
1475 | TU.HeaderCode = |
1476 | R"cpp(// Generated by the protocol buffer compiler. DO NOT EDIT! |
1477 | class Protobuf {}; |
1478 | )cpp" ; |
1479 | TU.HeaderFilename = "protobuf.pb.h" ; |
1480 | auto AST = TU.build(); |
1481 | auto Results = rename(RInputs: {.Pos: Code.point(), .NewName: "newName" , .AST: AST, .MainFilePath: testPath(File: TU.Filename)}); |
1482 | EXPECT_FALSE(Results); |
1483 | EXPECT_THAT(llvm::toString(Results.takeError()), |
1484 | testing::HasSubstr("not a supported kind" )); |
1485 | } |
1486 | |
1487 | TEST(RenameTest, PrepareRename) { |
1488 | Annotations FooH("void func();" ); |
1489 | Annotations FooCC(R"cpp( |
1490 | #include "foo.h" |
1491 | void [[fu^nc]]() {} |
1492 | )cpp" ); |
1493 | std::string FooHPath = testPath(File: "foo.h" ); |
1494 | std::string FooCCPath = testPath(File: "foo.cc" ); |
1495 | MockFS FS; |
1496 | FS.Files[FooHPath] = std::string(FooH.code()); |
1497 | FS.Files[FooCCPath] = std::string(FooCC.code()); |
1498 | |
1499 | auto ServerOpts = ClangdServer::optsForTest(); |
1500 | ServerOpts.BuildDynamicSymbolIndex = true; |
1501 | |
1502 | trace::TestTracer Tracer; |
1503 | MockCompilationDatabase CDB; |
1504 | ClangdServer Server(CDB, FS, ServerOpts); |
1505 | runAddDocument(Server, File: FooHPath, Contents: FooH.code()); |
1506 | runAddDocument(Server, File: FooCCPath, Contents: FooCC.code()); |
1507 | |
1508 | auto Results = runPrepareRename(Server, File: FooCCPath, Pos: FooCC.point(), |
1509 | /*NewName=*/std::nullopt, RenameOpts: {}); |
1510 | // Verify that for multi-file rename, we only return main-file occurrences. |
1511 | ASSERT_TRUE(bool(Results)) << Results.takeError(); |
1512 | // We don't know the result is complete in prepareRename (passing a nullptr |
1513 | // index internally), so GlobalChanges should be empty. |
1514 | EXPECT_TRUE(Results->GlobalChanges.empty()); |
1515 | EXPECT_THAT(FooCC.ranges(), |
1516 | testing::UnorderedElementsAreArray(Results->LocalChanges)); |
1517 | |
1518 | // Name validation. |
1519 | Results = runPrepareRename(Server, File: FooCCPath, Pos: FooCC.point(), |
1520 | /*NewName=*/std::string("int" ), RenameOpts: {}); |
1521 | EXPECT_FALSE(Results); |
1522 | EXPECT_THAT(llvm::toString(Results.takeError()), |
1523 | testing::HasSubstr("keyword" )); |
1524 | EXPECT_THAT(Tracer.takeMetric("rename_name_invalid" , "Keywords" ), |
1525 | ElementsAre(1)); |
1526 | |
1527 | for (std::string BadIdent : {"foo!bar" , "123foo" , "😀@" }) { |
1528 | Results = runPrepareRename(Server, File: FooCCPath, Pos: FooCC.point(), |
1529 | /*NewName=*/BadIdent, RenameOpts: {}); |
1530 | EXPECT_FALSE(Results); |
1531 | EXPECT_THAT(llvm::toString(Results.takeError()), |
1532 | testing::HasSubstr("identifier" )); |
1533 | EXPECT_THAT(Tracer.takeMetric("rename_name_invalid" , "BadIdentifier" ), |
1534 | ElementsAre(1)); |
1535 | } |
1536 | for (std::string GoodIdent : {"fooBar" , "__foo$" , "😀" }) { |
1537 | Results = runPrepareRename(Server, File: FooCCPath, Pos: FooCC.point(), |
1538 | /*NewName=*/GoodIdent, RenameOpts: {}); |
1539 | EXPECT_TRUE(bool(Results)); |
1540 | } |
1541 | } |
1542 | |
1543 | TEST(CrossFileRenameTests, DirtyBuffer) { |
1544 | Annotations FooCode("class [[Foo]] {};" ); |
1545 | std::string FooPath = testPath(File: "foo.cc" ); |
1546 | Annotations FooDirtyBuffer("class [[Foo]] {};\n// this is dirty buffer" ); |
1547 | Annotations BarCode("void [[Bar]]() {}" ); |
1548 | std::string BarPath = testPath(File: "bar.cc" ); |
1549 | // Build the index, the index has "Foo" references from foo.cc and "Bar" |
1550 | // references from bar.cc. |
1551 | FileSymbols FSymbols(IndexContents::All); |
1552 | FSymbols.update(Key: FooPath, Symbols: nullptr, Refs: buildRefSlab(Code: FooCode, SymbolName: "Foo" , Path: FooPath), |
1553 | Relations: nullptr, CountReferences: false); |
1554 | FSymbols.update(Key: BarPath, Symbols: nullptr, Refs: buildRefSlab(Code: BarCode, SymbolName: "Bar" , Path: BarPath), |
1555 | Relations: nullptr, CountReferences: false); |
1556 | auto Index = FSymbols.buildIndex(IndexType::Light); |
1557 | |
1558 | Annotations MainCode("class [[Fo^o]] {};" ); |
1559 | auto MainFilePath = testPath(File: "main.cc" ); |
1560 | llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemFS = |
1561 | new llvm::vfs::InMemoryFileSystem; |
1562 | InMemFS->addFile(Path: FooPath, ModificationTime: 0, |
1563 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: FooDirtyBuffer.code())); |
1564 | |
1565 | // Run rename on Foo, there is a dirty buffer for foo.cc, rename should |
1566 | // respect the dirty buffer. |
1567 | TestTU TU = TestTU::withCode(Code: MainCode.code()); |
1568 | auto AST = TU.build(); |
1569 | llvm::StringRef NewName = "newName" ; |
1570 | auto Results = |
1571 | rename(RInputs: {.Pos: MainCode.point(), .NewName: NewName, .AST: AST, .MainFilePath: MainFilePath, |
1572 | .FS: createOverlay(Base: getVFSFromAST(AST), Overlay: InMemFS), .Index: Index.get()}); |
1573 | ASSERT_TRUE(bool(Results)) << Results.takeError(); |
1574 | EXPECT_THAT( |
1575 | applyEdits(std::move(Results->GlobalChanges)), |
1576 | UnorderedElementsAre( |
1577 | Pair(Eq(FooPath), Eq(expectedResult(FooDirtyBuffer, NewName))), |
1578 | Pair(Eq(MainFilePath), Eq(expectedResult(MainCode, NewName))))); |
1579 | |
1580 | // Run rename on Bar, there is no dirty buffer for the affected file bar.cc, |
1581 | // so we should read file content from VFS. |
1582 | MainCode = Annotations("void [[Bar]]() { [[B^ar]](); }" ); |
1583 | TU = TestTU::withCode(Code: MainCode.code()); |
1584 | // Set a file "bar.cc" on disk. |
1585 | TU.AdditionalFiles["bar.cc" ] = std::string(BarCode.code()); |
1586 | AST = TU.build(); |
1587 | Results = rename(RInputs: {.Pos: MainCode.point(), .NewName: NewName, .AST: AST, .MainFilePath: MainFilePath, |
1588 | .FS: createOverlay(Base: getVFSFromAST(AST), Overlay: InMemFS), .Index: Index.get()}); |
1589 | ASSERT_TRUE(bool(Results)) << Results.takeError(); |
1590 | EXPECT_THAT( |
1591 | applyEdits(std::move(Results->GlobalChanges)), |
1592 | UnorderedElementsAre( |
1593 | Pair(Eq(BarPath), Eq(expectedResult(BarCode, NewName))), |
1594 | Pair(Eq(MainFilePath), Eq(expectedResult(MainCode, NewName))))); |
1595 | |
1596 | // Run rename on a pagination index which couldn't return all refs in one |
1597 | // request, we reject rename on this case. |
1598 | class : public SymbolIndex { |
1599 | bool refs(const RefsRequest &Req, |
1600 | llvm::function_ref<void(const Ref &)> Callback) const override { |
1601 | return true; // has more references |
1602 | } |
1603 | |
1604 | bool fuzzyFind( |
1605 | const FuzzyFindRequest &Req, |
1606 | llvm::function_ref<void(const Symbol &)> Callback) const override { |
1607 | return false; |
1608 | } |
1609 | void |
1610 | lookup(const LookupRequest &Req, |
1611 | llvm::function_ref<void(const Symbol &)> Callback) const override {} |
1612 | |
1613 | void relations(const RelationsRequest &Req, |
1614 | llvm::function_ref<void(const SymbolID &, const Symbol &)> |
1615 | Callback) const override {} |
1616 | |
1617 | llvm::unique_function<IndexContents(llvm::StringRef) const> |
1618 | indexedFiles() const override { |
1619 | return [](llvm::StringRef) { return IndexContents::None; }; |
1620 | } |
1621 | |
1622 | size_t estimateMemoryUsage() const override { return 0; } |
1623 | } PIndex; |
1624 | Results = rename(RInputs: {.Pos: MainCode.point(), .NewName: NewName, .AST: AST, .MainFilePath: MainFilePath, |
1625 | .FS: createOverlay(Base: getVFSFromAST(AST), Overlay: InMemFS), .Index: &PIndex}); |
1626 | EXPECT_FALSE(Results); |
1627 | EXPECT_THAT(llvm::toString(Results.takeError()), |
1628 | testing::HasSubstr("too many occurrences" )); |
1629 | } |
1630 | |
1631 | TEST(CrossFileRenameTests, DeduplicateRefsFromIndex) { |
1632 | auto MainCode = Annotations("int [[^x]] = 2;" ); |
1633 | auto MainFilePath = testPath(File: "main.cc" ); |
1634 | auto BarCode = Annotations("int [[x]];" ); |
1635 | auto BarPath = testPath(File: "bar.cc" ); |
1636 | auto TU = TestTU::withCode(Code: MainCode.code()); |
1637 | // Set a file "bar.cc" on disk. |
1638 | TU.AdditionalFiles["bar.cc" ] = std::string(BarCode.code()); |
1639 | auto AST = TU.build(); |
1640 | std::string BarPathURI = URI::create(AbsolutePath: BarPath).toString(); |
1641 | Ref XRefInBarCC = refWithRange(Range: BarCode.range(), URI: BarPathURI); |
1642 | // The index will return duplicated refs, our code should be robost to handle |
1643 | // it. |
1644 | class DuplicatedXRefIndex : public SymbolIndex { |
1645 | public: |
1646 | DuplicatedXRefIndex(const Ref &ReturnedRef) : ReturnedRef(ReturnedRef) {} |
1647 | bool refs(const RefsRequest &Req, |
1648 | llvm::function_ref<void(const Ref &)> Callback) const override { |
1649 | // Return two duplicated refs. |
1650 | Callback(ReturnedRef); |
1651 | Callback(ReturnedRef); |
1652 | return false; |
1653 | } |
1654 | |
1655 | bool fuzzyFind(const FuzzyFindRequest &, |
1656 | llvm::function_ref<void(const Symbol &)>) const override { |
1657 | return false; |
1658 | } |
1659 | void lookup(const LookupRequest &, |
1660 | llvm::function_ref<void(const Symbol &)>) const override {} |
1661 | |
1662 | void relations(const RelationsRequest &, |
1663 | llvm::function_ref<void(const SymbolID &, const Symbol &)>) |
1664 | const override {} |
1665 | |
1666 | llvm::unique_function<IndexContents(llvm::StringRef) const> |
1667 | indexedFiles() const override { |
1668 | return [](llvm::StringRef) { return IndexContents::None; }; |
1669 | } |
1670 | |
1671 | size_t estimateMemoryUsage() const override { return 0; } |
1672 | Ref ReturnedRef; |
1673 | } DIndex(XRefInBarCC); |
1674 | llvm::StringRef NewName = "newName" ; |
1675 | auto Results = rename(RInputs: {.Pos: MainCode.point(), .NewName: NewName, .AST: AST, .MainFilePath: MainFilePath, |
1676 | .FS: getVFSFromAST(AST), .Index: &DIndex}); |
1677 | ASSERT_TRUE(bool(Results)) << Results.takeError(); |
1678 | EXPECT_THAT( |
1679 | applyEdits(std::move(Results->GlobalChanges)), |
1680 | UnorderedElementsAre( |
1681 | Pair(Eq(BarPath), Eq(expectedResult(BarCode, NewName))), |
1682 | Pair(Eq(MainFilePath), Eq(expectedResult(MainCode, NewName))))); |
1683 | } |
1684 | |
1685 | TEST(CrossFileRenameTests, WithUpToDateIndex) { |
1686 | MockCompilationDatabase CDB; |
1687 | CDB.ExtraClangFlags = {"-xobjective-c++" }; |
1688 | // rename is runnning on all "^" points in FooH, and "[[]]" ranges are the |
1689 | // expected rename occurrences. |
1690 | struct Case { |
1691 | llvm::StringRef FooH; |
1692 | llvm::StringRef FooCC; |
1693 | } Cases[] = { |
1694 | { |
1695 | // classes. |
1696 | .FooH: R"cpp( |
1697 | class [[Fo^o]] { |
1698 | [[Foo]](); |
1699 | ~[[Foo]](); |
1700 | }; |
1701 | )cpp" , |
1702 | .FooCC: R"cpp( |
1703 | #include "foo.h" |
1704 | [[Foo]]::[[Foo]]() {} |
1705 | [[Foo]]::~[[Foo]]() {} |
1706 | |
1707 | void func() { |
1708 | [[Foo]] foo; |
1709 | } |
1710 | )cpp" , |
1711 | }, |
1712 | { |
1713 | // class templates. |
1714 | .FooH: R"cpp( |
1715 | template <typename T> |
1716 | class [[Foo]] {}; |
1717 | // FIXME: explicit template specializations are not supported due the |
1718 | // clangd index limitations. |
1719 | template <> |
1720 | class Foo<double> {}; |
1721 | )cpp" , |
1722 | .FooCC: R"cpp( |
1723 | #include "foo.h" |
1724 | void func() { |
1725 | [[F^oo]]<int> foo; |
1726 | } |
1727 | )cpp" , |
1728 | }, |
1729 | { |
1730 | // class methods. |
1731 | .FooH: R"cpp( |
1732 | class Foo { |
1733 | void [[f^oo]](); |
1734 | }; |
1735 | )cpp" , |
1736 | .FooCC: R"cpp( |
1737 | #include "foo.h" |
1738 | void Foo::[[foo]]() {} |
1739 | |
1740 | void func(Foo* p) { |
1741 | p->[[foo]](); |
1742 | } |
1743 | )cpp" , |
1744 | }, |
1745 | { |
1746 | // virtual methods. |
1747 | .FooH: R"cpp( |
1748 | class Base { |
1749 | virtual void [[foo]](); |
1750 | }; |
1751 | class Derived1 : public Base { |
1752 | void [[f^oo]]() override; |
1753 | }; |
1754 | class NotDerived { |
1755 | void foo() {}; |
1756 | } |
1757 | )cpp" , |
1758 | .FooCC: R"cpp( |
1759 | #include "foo.h" |
1760 | void Base::[[foo]]() {} |
1761 | void Derived1::[[foo]]() {} |
1762 | |
1763 | class Derived2 : public Derived1 { |
1764 | void [[foo]]() override {}; |
1765 | }; |
1766 | |
1767 | void func(Base* b, Derived1* d1, |
1768 | Derived2* d2, NotDerived* nd) { |
1769 | b->[[foo]](); |
1770 | d1->[[foo]](); |
1771 | d2->[[foo]](); |
1772 | nd->foo(); |
1773 | } |
1774 | )cpp" , |
1775 | }, |
1776 | {// virtual templated method |
1777 | .FooH: R"cpp( |
1778 | template <typename> class Foo { virtual void [[m]](); }; |
1779 | class Bar : Foo<int> { void [[^m]]() override; }; |
1780 | )cpp" , |
1781 | .FooCC: R"cpp( |
1782 | #include "foo.h" |
1783 | |
1784 | template<typename T> void Foo<T>::[[m]]() {} |
1785 | // FIXME: not renamed as the index doesn't see this as an override of |
1786 | // the canonical Foo<T>::m(). |
1787 | // https://github.com/clangd/clangd/issues/1325 |
1788 | class Baz : Foo<float> { void m() override; }; |
1789 | )cpp" }, |
1790 | { |
1791 | // rename on constructor and destructor. |
1792 | .FooH: R"cpp( |
1793 | class [[Foo]] { |
1794 | [[^Foo]](); |
1795 | ~[[Foo^]](); |
1796 | }; |
1797 | )cpp" , |
1798 | .FooCC: R"cpp( |
1799 | #include "foo.h" |
1800 | [[Foo]]::[[Foo]]() {} |
1801 | [[Foo]]::~[[Foo]]() {} |
1802 | |
1803 | void func() { |
1804 | [[Foo]] foo; |
1805 | } |
1806 | )cpp" , |
1807 | }, |
1808 | { |
1809 | // functions. |
1810 | .FooH: R"cpp( |
1811 | void [[f^oo]](); |
1812 | )cpp" , |
1813 | .FooCC: R"cpp( |
1814 | #include "foo.h" |
1815 | void [[foo]]() {} |
1816 | |
1817 | void func() { |
1818 | [[foo]](); |
1819 | } |
1820 | )cpp" , |
1821 | }, |
1822 | { |
1823 | // typedefs. |
1824 | .FooH: R"cpp( |
1825 | typedef int [[IN^T]]; |
1826 | [[INT]] foo(); |
1827 | )cpp" , |
1828 | .FooCC: R"cpp( |
1829 | #include "foo.h" |
1830 | [[INT]] foo() {} |
1831 | )cpp" , |
1832 | }, |
1833 | { |
1834 | // usings. |
1835 | .FooH: R"cpp( |
1836 | using [[I^NT]] = int; |
1837 | [[INT]] foo(); |
1838 | )cpp" , |
1839 | .FooCC: R"cpp( |
1840 | #include "foo.h" |
1841 | [[INT]] foo() {} |
1842 | )cpp" , |
1843 | }, |
1844 | { |
1845 | // variables. |
1846 | .FooH: R"cpp( |
1847 | static const int [[VA^R]] = 123; |
1848 | )cpp" , |
1849 | .FooCC: R"cpp( |
1850 | #include "foo.h" |
1851 | int s = [[VAR]]; |
1852 | )cpp" , |
1853 | }, |
1854 | { |
1855 | // scope enums. |
1856 | .FooH: R"cpp( |
1857 | enum class [[K^ind]] { ABC }; |
1858 | )cpp" , |
1859 | .FooCC: R"cpp( |
1860 | #include "foo.h" |
1861 | [[Kind]] ff() { |
1862 | return [[Kind]]::ABC; |
1863 | } |
1864 | )cpp" , |
1865 | }, |
1866 | { |
1867 | // enum constants. |
1868 | .FooH: R"cpp( |
1869 | enum class Kind { [[A^BC]] }; |
1870 | )cpp" , |
1871 | .FooCC: R"cpp( |
1872 | #include "foo.h" |
1873 | Kind ff() { |
1874 | return Kind::[[ABC]]; |
1875 | } |
1876 | )cpp" , |
1877 | }, |
1878 | { |
1879 | // Implicit references in macro expansions. |
1880 | .FooH: R"cpp( |
1881 | class [[Fo^o]] {}; |
1882 | #define FooFoo Foo |
1883 | #define FOO Foo |
1884 | )cpp" , |
1885 | .FooCC: R"cpp( |
1886 | #include "foo.h" |
1887 | void bar() { |
1888 | [[Foo]] x; |
1889 | FOO y; |
1890 | FooFoo z; |
1891 | } |
1892 | )cpp" , |
1893 | }, |
1894 | { |
1895 | // Objective-C classes. |
1896 | .FooH: R"cpp( |
1897 | @interface [[Fo^o]] |
1898 | @end |
1899 | )cpp" , |
1900 | .FooCC: R"cpp( |
1901 | #include "foo.h" |
1902 | @implementation [[Foo]] |
1903 | @end |
1904 | |
1905 | void func([[Foo]] *f) {} |
1906 | )cpp" , |
1907 | }, |
1908 | }; |
1909 | |
1910 | trace::TestTracer Tracer; |
1911 | for (const auto &T : Cases) { |
1912 | SCOPED_TRACE(T.FooH); |
1913 | Annotations FooH(T.FooH); |
1914 | Annotations FooCC(T.FooCC); |
1915 | std::string FooHPath = testPath(File: "foo.h" ); |
1916 | std::string FooCCPath = testPath(File: "foo.cc" ); |
1917 | |
1918 | MockFS FS; |
1919 | FS.Files[FooHPath] = std::string(FooH.code()); |
1920 | FS.Files[FooCCPath] = std::string(FooCC.code()); |
1921 | |
1922 | auto ServerOpts = ClangdServer::optsForTest(); |
1923 | ServerOpts.BuildDynamicSymbolIndex = true; |
1924 | ClangdServer Server(CDB, FS, ServerOpts); |
1925 | |
1926 | // Add all files to clangd server to make sure the dynamic index has been |
1927 | // built. |
1928 | runAddDocument(Server, File: FooHPath, Contents: FooH.code()); |
1929 | runAddDocument(Server, File: FooCCPath, Contents: FooCC.code()); |
1930 | |
1931 | llvm::StringRef NewName = "NewName" ; |
1932 | for (const auto &RenamePos : FooH.points()) { |
1933 | EXPECT_THAT(Tracer.takeMetric("rename_files" ), SizeIs(0)); |
1934 | auto FileEditsList = |
1935 | llvm::cantFail(ValOrErr: runRename(Server, File: FooHPath, Pos: RenamePos, NewName, RenameOpts: {})); |
1936 | EXPECT_THAT(Tracer.takeMetric("rename_files" ), ElementsAre(2)); |
1937 | EXPECT_THAT( |
1938 | applyEdits(std::move(FileEditsList.GlobalChanges)), |
1939 | UnorderedElementsAre( |
1940 | Pair(Eq(FooHPath), Eq(expectedResult(T.FooH, NewName))), |
1941 | Pair(Eq(FooCCPath), Eq(expectedResult(T.FooCC, NewName))))); |
1942 | } |
1943 | } |
1944 | } |
1945 | |
1946 | TEST(CrossFileRenameTests, ObjC) { |
1947 | MockCompilationDatabase CDB; |
1948 | CDB.ExtraClangFlags = {"-xobjective-c" }; |
1949 | // rename is runnning on all "^" points in FooH. |
1950 | struct Case { |
1951 | llvm::StringRef FooH; |
1952 | llvm::StringRef FooM; |
1953 | llvm::StringRef NewName; |
1954 | llvm::StringRef ExpectedFooH; |
1955 | llvm::StringRef ExpectedFooM; |
1956 | }; |
1957 | Case Cases[] = {// --- Zero arg selector |
1958 | { |
1959 | // Input |
1960 | .FooH: R"cpp( |
1961 | @interface Foo |
1962 | - (int)performA^ction; |
1963 | @end |
1964 | )cpp" , |
1965 | .FooM: R"cpp( |
1966 | @implementation Foo |
1967 | - (int)performAction { |
1968 | [self performAction]; |
1969 | } |
1970 | @end |
1971 | )cpp" , |
1972 | // New name |
1973 | .NewName: "performNewAction" , |
1974 | // Expected |
1975 | .ExpectedFooH: R"cpp( |
1976 | @interface Foo |
1977 | - (int)performNewAction; |
1978 | @end |
1979 | )cpp" , |
1980 | .ExpectedFooM: R"cpp( |
1981 | @implementation Foo |
1982 | - (int)performNewAction { |
1983 | [self performNewAction]; |
1984 | } |
1985 | @end |
1986 | )cpp" , |
1987 | }, |
1988 | // --- Single arg selector |
1989 | { |
1990 | // Input |
1991 | .FooH: R"cpp( |
1992 | @interface Foo |
1993 | - (int)performA^ction:(int)action; |
1994 | @end |
1995 | )cpp" , |
1996 | .FooM: R"cpp( |
1997 | @implementation Foo |
1998 | - (int)performAction:(int)action { |
1999 | [self performAction:action]; |
2000 | } |
2001 | @end |
2002 | )cpp" , |
2003 | // New name |
2004 | .NewName: "performNewAction:" , |
2005 | // Expected |
2006 | .ExpectedFooH: R"cpp( |
2007 | @interface Foo |
2008 | - (int)performNewAction:(int)action; |
2009 | @end |
2010 | )cpp" , |
2011 | .ExpectedFooM: R"cpp( |
2012 | @implementation Foo |
2013 | - (int)performNewAction:(int)action { |
2014 | [self performNewAction:action]; |
2015 | } |
2016 | @end |
2017 | )cpp" , |
2018 | }, |
2019 | // --- Multi arg selector |
2020 | { |
2021 | // Input |
2022 | .FooH: R"cpp( |
2023 | @interface Foo |
2024 | - (int)performA^ction:(int)action with:(int)value; |
2025 | @end |
2026 | )cpp" , |
2027 | .FooM: R"cpp( |
2028 | @implementation Foo |
2029 | - (int)performAction:(int)action with:(int)value { |
2030 | [self performAction:action with:value]; |
2031 | } |
2032 | @end |
2033 | )cpp" , |
2034 | // New name |
2035 | .NewName: "performNewAction:by:" , |
2036 | // Expected |
2037 | .ExpectedFooH: R"cpp( |
2038 | @interface Foo |
2039 | - (int)performNewAction:(int)action by:(int)value; |
2040 | @end |
2041 | )cpp" , |
2042 | .ExpectedFooM: R"cpp( |
2043 | @implementation Foo |
2044 | - (int)performNewAction:(int)action by:(int)value { |
2045 | [self performNewAction:action by:value]; |
2046 | } |
2047 | @end |
2048 | )cpp" , |
2049 | }}; |
2050 | |
2051 | trace::TestTracer Tracer; |
2052 | for (const auto &T : Cases) { |
2053 | SCOPED_TRACE(T.FooH); |
2054 | Annotations FooH(T.FooH); |
2055 | Annotations FooM(T.FooM); |
2056 | std::string FooHPath = testPath(File: "foo.h" ); |
2057 | std::string FooMPath = testPath(File: "foo.m" ); |
2058 | |
2059 | MockFS FS; |
2060 | FS.Files[FooHPath] = std::string(FooH.code()); |
2061 | FS.Files[FooMPath] = std::string(FooM.code()); |
2062 | |
2063 | auto ServerOpts = ClangdServer::optsForTest(); |
2064 | ServerOpts.BuildDynamicSymbolIndex = true; |
2065 | ClangdServer Server(CDB, FS, ServerOpts); |
2066 | |
2067 | // Add all files to clangd server to make sure the dynamic index has been |
2068 | // built. |
2069 | runAddDocument(Server, File: FooHPath, Contents: FooH.code()); |
2070 | runAddDocument(Server, File: FooMPath, Contents: FooM.code()); |
2071 | |
2072 | for (const auto &RenamePos : FooH.points()) { |
2073 | EXPECT_THAT(Tracer.takeMetric("rename_files" ), SizeIs(0)); |
2074 | auto FileEditsList = |
2075 | llvm::cantFail(ValOrErr: runRename(Server, File: FooHPath, Pos: RenamePos, NewName: T.NewName, RenameOpts: {})); |
2076 | EXPECT_THAT(Tracer.takeMetric("rename_files" ), ElementsAre(2)); |
2077 | EXPECT_THAT(applyEdits(std::move(FileEditsList.GlobalChanges)), |
2078 | UnorderedElementsAre(Pair(Eq(FooHPath), Eq(T.ExpectedFooH)), |
2079 | Pair(Eq(FooMPath), Eq(T.ExpectedFooM)))); |
2080 | } |
2081 | } |
2082 | } |
2083 | |
2084 | TEST(CrossFileRenameTests, CrossFileOnLocalSymbol) { |
2085 | // cross-file rename should work for function-local symbols, even there is no |
2086 | // index provided. |
2087 | Annotations Code("void f(int [[abc]]) { [[a^bc]] = 3; }" ); |
2088 | auto TU = TestTU::withCode(Code: Code.code()); |
2089 | auto Path = testPath(File: TU.Filename); |
2090 | auto AST = TU.build(); |
2091 | llvm::StringRef NewName = "newName" ; |
2092 | auto Results = rename(RInputs: {.Pos: Code.point(), .NewName: NewName, .AST: AST, .MainFilePath: Path}); |
2093 | ASSERT_TRUE(bool(Results)) << Results.takeError(); |
2094 | EXPECT_THAT( |
2095 | applyEdits(std::move(Results->GlobalChanges)), |
2096 | UnorderedElementsAre(Pair(Eq(Path), Eq(expectedResult(Code, NewName))))); |
2097 | } |
2098 | |
2099 | TEST(CrossFileRenameTests, BuildRenameEdits) { |
2100 | Annotations Code("[[😂]]" ); |
2101 | auto LSPRange = Code.range(); |
2102 | llvm::StringRef FilePath = "/test/TestTU.cpp" ; |
2103 | llvm::SmallVector<llvm::StringRef, 2> NewNames = {"abc" }; |
2104 | auto Edit = buildRenameEdit(AbsFilePath: FilePath, InitialCode: Code.code(), Occurrences: {LSPRange}, NewNames); |
2105 | ASSERT_TRUE(bool(Edit)) << Edit.takeError(); |
2106 | ASSERT_EQ(1UL, Edit->Replacements.size()); |
2107 | EXPECT_EQ(FilePath, Edit->Replacements.begin()->getFilePath()); |
2108 | EXPECT_EQ(4UL, Edit->Replacements.begin()->getLength()); |
2109 | |
2110 | // Test invalid range. |
2111 | LSPRange.end = {.line: 10, .character: 0}; // out of range |
2112 | Edit = buildRenameEdit(AbsFilePath: FilePath, InitialCode: Code.code(), Occurrences: {LSPRange}, NewNames); |
2113 | EXPECT_FALSE(Edit); |
2114 | EXPECT_THAT(llvm::toString(Edit.takeError()), |
2115 | testing::HasSubstr("fail to convert" )); |
2116 | |
2117 | // Normal ascii characters. |
2118 | Annotations T(R"cpp( |
2119 | [[range]] |
2120 | [[range]] |
2121 | [[range]] |
2122 | )cpp" ); |
2123 | Edit = |
2124 | buildRenameEdit(AbsFilePath: FilePath, InitialCode: T.code(), Occurrences: symbolRanges(Ranges: T.ranges()), NewNames); |
2125 | ASSERT_TRUE(bool(Edit)) << Edit.takeError(); |
2126 | EXPECT_EQ(applyEdits(FileEdits{{T.code(), std::move(*Edit)}}).front().second, |
2127 | expectedResult(T, NewNames[0])); |
2128 | } |
2129 | |
2130 | TEST(CrossFileRenameTests, adjustRenameRanges) { |
2131 | // Ranges in IndexedCode indicate the indexed occurrences; |
2132 | // ranges in DraftCode indicate the expected mapped result, empty indicates |
2133 | // we expect no matched result found. |
2134 | struct { |
2135 | llvm::StringRef IndexedCode; |
2136 | llvm::StringRef DraftCode; |
2137 | } Tests[] = { |
2138 | { |
2139 | // both line and column are changed, not a near miss. |
2140 | .IndexedCode: R"cpp( |
2141 | int [[x]] = 0; |
2142 | )cpp" , |
2143 | .DraftCode: R"cpp( |
2144 | // insert a line. |
2145 | double x = 0; |
2146 | )cpp" , |
2147 | }, |
2148 | { |
2149 | // subset. |
2150 | .IndexedCode: R"cpp( |
2151 | int [[x]] = 0; |
2152 | )cpp" , |
2153 | .DraftCode: R"cpp( |
2154 | int [[x]] = 0; |
2155 | {int x = 0; } |
2156 | )cpp" , |
2157 | }, |
2158 | { |
2159 | // shift columns. |
2160 | .IndexedCode: R"cpp(int [[x]] = 0; void foo(int x);)cpp" , |
2161 | .DraftCode: R"cpp(double [[x]] = 0; void foo(double x);)cpp" , |
2162 | }, |
2163 | { |
2164 | // shift lines. |
2165 | .IndexedCode: R"cpp( |
2166 | int [[x]] = 0; |
2167 | void foo(int x); |
2168 | )cpp" , |
2169 | .DraftCode: R"cpp( |
2170 | // insert a line. |
2171 | int [[x]] = 0; |
2172 | void foo(int x); |
2173 | )cpp" , |
2174 | }, |
2175 | }; |
2176 | LangOptions LangOpts; |
2177 | LangOpts.CPlusPlus = true; |
2178 | for (const auto &T : Tests) { |
2179 | SCOPED_TRACE(T.DraftCode); |
2180 | Annotations Draft(T.DraftCode); |
2181 | auto ActualRanges = adjustRenameRanges(DraftCode: Draft.code(), Identifier: "x" , |
2182 | Indexed: Annotations(T.IndexedCode).ranges(), |
2183 | LangOpts, Selector: std::nullopt); |
2184 | if (!ActualRanges) |
2185 | EXPECT_THAT(Draft.ranges(), testing::IsEmpty()); |
2186 | else |
2187 | EXPECT_THAT(Draft.ranges(), |
2188 | testing::UnorderedElementsAreArray(*ActualRanges)); |
2189 | } |
2190 | } |
2191 | |
2192 | TEST(RangePatchingHeuristic, GetMappedRanges) { |
2193 | // ^ in LexedCode marks the ranges we expect to be mapped; no ^ indicates |
2194 | // there are no mapped ranges. |
2195 | struct { |
2196 | llvm::StringRef IndexedCode; |
2197 | llvm::StringRef LexedCode; |
2198 | } Tests[] = { |
2199 | { |
2200 | // no lexed ranges. |
2201 | .IndexedCode: "[[]]" , |
2202 | .LexedCode: "" , |
2203 | }, |
2204 | { |
2205 | // both line and column are changed, not a near miss. |
2206 | .IndexedCode: R"([[]])" , |
2207 | .LexedCode: R"( |
2208 | [[]] |
2209 | )" , |
2210 | }, |
2211 | { |
2212 | // subset. |
2213 | .IndexedCode: "[[]]" , |
2214 | .LexedCode: "^[[]] [[]]" |
2215 | }, |
2216 | { |
2217 | // shift columns. |
2218 | .IndexedCode: "[[]] [[]]" , |
2219 | .LexedCode: " ^[[]] ^[[]] [[]]" |
2220 | }, |
2221 | { |
2222 | .IndexedCode: R"( |
2223 | [[]] |
2224 | |
2225 | [[]] [[]] |
2226 | )" , |
2227 | .LexedCode: R"( |
2228 | // insert a line |
2229 | ^[[]] |
2230 | |
2231 | ^[[]] ^[[]] |
2232 | )" , |
2233 | }, |
2234 | { |
2235 | .IndexedCode: R"( |
2236 | [[]] |
2237 | |
2238 | [[]] [[]] |
2239 | )" , |
2240 | .LexedCode: R"( |
2241 | // insert a line |
2242 | ^[[]] |
2243 | ^[[]] ^[[]] // column is shifted. |
2244 | )" , |
2245 | }, |
2246 | { |
2247 | .IndexedCode: R"( |
2248 | [[]] |
2249 | |
2250 | [[]] [[]] |
2251 | )" , |
2252 | .LexedCode: R"( |
2253 | // insert a line |
2254 | [[]] |
2255 | |
2256 | [[]] [[]] // not mapped (both line and column are changed). |
2257 | )" , |
2258 | }, |
2259 | { |
2260 | .IndexedCode: R"( |
2261 | [[]] |
2262 | [[]] |
2263 | |
2264 | [[]] |
2265 | [[]] |
2266 | |
2267 | } |
2268 | )" , |
2269 | .LexedCode: R"( |
2270 | // insert a new line |
2271 | ^[[]] |
2272 | ^[[]] |
2273 | [[]] // additional range |
2274 | ^[[]] |
2275 | ^[[]] |
2276 | [[]] // additional range |
2277 | )" , |
2278 | }, |
2279 | { |
2280 | // non-distinct result (two best results), not a near miss |
2281 | .IndexedCode: R"( |
2282 | [[]] |
2283 | [[]] |
2284 | [[]] |
2285 | )" , |
2286 | .LexedCode: R"( |
2287 | [[]] |
2288 | [[]] |
2289 | [[]] |
2290 | [[]] |
2291 | )" , |
2292 | } |
2293 | }; |
2294 | for (const auto &T : Tests) { |
2295 | SCOPED_TRACE(T.IndexedCode); |
2296 | auto Lexed = Annotations(T.LexedCode); |
2297 | auto LexedRanges = symbolRanges(Ranges: Lexed.ranges()); |
2298 | std::vector<SymbolRange> ExpectedMatches; |
2299 | for (auto P : Lexed.points()) { |
2300 | auto Match = llvm::find_if(Range&: LexedRanges, P: [&P](const SymbolRange &R) { |
2301 | return R.range().start == P; |
2302 | }); |
2303 | ASSERT_NE(Match, LexedRanges.end()); |
2304 | ExpectedMatches.push_back(x: *Match); |
2305 | } |
2306 | |
2307 | auto Mapped = |
2308 | getMappedRanges(Indexed: Annotations(T.IndexedCode).ranges(), Lexed: LexedRanges); |
2309 | if (!Mapped) |
2310 | EXPECT_THAT(ExpectedMatches, IsEmpty()); |
2311 | else |
2312 | EXPECT_THAT(ExpectedMatches, UnorderedElementsAreArray(*Mapped)); |
2313 | } |
2314 | } |
2315 | |
2316 | TEST(CrossFileRenameTests, adjustmentCost) { |
2317 | struct { |
2318 | llvm::StringRef RangeCode; |
2319 | size_t ExpectedCost; |
2320 | } Tests[] = { |
2321 | { |
2322 | .RangeCode: R"( |
2323 | $idx[[]]$lex[[]] // diff: 0 |
2324 | )" , |
2325 | .ExpectedCost: 0, |
2326 | }, |
2327 | { |
2328 | .RangeCode: R"( |
2329 | $idx[[]] |
2330 | $lex[[]] // line diff: +1 |
2331 | $idx[[]] |
2332 | $lex[[]] // line diff: +1 |
2333 | $idx[[]] |
2334 | $lex[[]] // line diff: +1 |
2335 | |
2336 | $idx[[]] |
2337 | |
2338 | $lex[[]] // line diff: +2 |
2339 | )" , |
2340 | .ExpectedCost: 1 + 1 |
2341 | }, |
2342 | { |
2343 | .RangeCode: R"( |
2344 | $idx[[]] |
2345 | $lex[[]] // line diff: +1 |
2346 | $idx[[]] |
2347 | |
2348 | $lex[[]] // line diff: +2 |
2349 | $idx[[]] |
2350 | |
2351 | |
2352 | $lex[[]] // line diff: +3 |
2353 | )" , |
2354 | .ExpectedCost: 1 + 1 + 1 |
2355 | }, |
2356 | { |
2357 | .RangeCode: R"( |
2358 | $idx[[]] |
2359 | |
2360 | |
2361 | $lex[[]] // line diff: +3 |
2362 | $idx[[]] |
2363 | |
2364 | $lex[[]] // line diff: +2 |
2365 | $idx[[]] |
2366 | $lex[[]] // line diff: +1 |
2367 | )" , |
2368 | .ExpectedCost: 3 + 1 + 1 |
2369 | }, |
2370 | { |
2371 | .RangeCode: R"( |
2372 | $idx[[]] |
2373 | $lex[[]] // line diff: +1 |
2374 | $lex[[]] // line diff: -2 |
2375 | |
2376 | $idx[[]] |
2377 | $idx[[]] |
2378 | |
2379 | |
2380 | $lex[[]] // line diff: +3 |
2381 | )" , |
2382 | .ExpectedCost: 1 + 3 + 5 |
2383 | }, |
2384 | { |
2385 | .RangeCode: R"( |
2386 | $idx[[]] $lex[[]] // column diff: +1 |
2387 | $idx[[]]$lex[[]] // diff: 0 |
2388 | )" , |
2389 | .ExpectedCost: 1 |
2390 | }, |
2391 | { |
2392 | .RangeCode: R"( |
2393 | $idx[[]] |
2394 | $lex[[]] // diff: +1 |
2395 | $idx[[]] $lex[[]] // column diff: +1 |
2396 | $idx[[]]$lex[[]] // diff: 0 |
2397 | )" , |
2398 | .ExpectedCost: 1 + 1 + 1 |
2399 | }, |
2400 | { |
2401 | .RangeCode: R"( |
2402 | $idx[[]] $lex[[]] // column diff: +1 |
2403 | )" , |
2404 | .ExpectedCost: 1 |
2405 | }, |
2406 | { |
2407 | .RangeCode: R"( |
2408 | // column diffs: +1, +2, +3 |
2409 | $idx[[]] $lex[[]] $idx[[]] $lex[[]] $idx[[]] $lex[[]] |
2410 | )" , |
2411 | .ExpectedCost: 1 + 1 + 1, |
2412 | }, |
2413 | }; |
2414 | for (const auto &T : Tests) { |
2415 | SCOPED_TRACE(T.RangeCode); |
2416 | Annotations C(T.RangeCode); |
2417 | std::vector<size_t> MappedIndex; |
2418 | for (size_t I = 0; I < C.ranges(Name: "lex" ).size(); ++I) |
2419 | MappedIndex.push_back(x: I); |
2420 | EXPECT_EQ(renameRangeAdjustmentCost( |
2421 | C.ranges("idx" ), symbolRanges(C.ranges("lex" )), MappedIndex), |
2422 | T.ExpectedCost); |
2423 | } |
2424 | } |
2425 | |
2426 | } // namespace |
2427 | } // namespace clangd |
2428 | } // namespace clang |
2429 | |