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