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"
28 using testing::ElementsAre;
30 using testing::IsEmpty;
32 using testing::SizeIs;
33 using testing::UnorderedElementsAre;
34 using testing::UnorderedElementsAreArray;
40 Result.Location.Start.setLine(
Range.start.line);
41 Result.Location.Start.setColumn(
Range.start.character);
42 Result.Location.End.setLine(
Range.end.line);
43 Result.Location.End.setColumn(
Range.end.character);
44 Result.Location.FileURI = URI.c_str();
50 std::unique_ptr<RefSlab> buildRefSlab(
const Annotations &
Code,
51 llvm::StringRef SymbolName,
52 llvm::StringRef
Path) {
55 TU.HeaderCode = std::string(
Code.code());
56 auto Symbols = TU.headerSymbols();
62 return std::make_unique<RefSlab>(std::move(
Builder).build());
66 std::pair< std::string, std::string>>
68 std::vector<std::pair<std::string, std::string>>
Results;
72 llvm::cantFail(tooling::applyAllReplacements(
73 It.getValue().InitialCode, It.getValue().Replacements)));
79 std::string expectedResult(Annotations Test, llvm::StringRef NewName) {
81 unsigned NextChar = 0;
82 llvm::StringRef
Code = Test.code();
83 for (
const auto &R : Test.llvm::Annotations::ranges()) {
84 assert(R.Begin <= R.End && NextChar <= R.Begin);
85 Result +=
Code.substr(NextChar, R.Begin - NextChar);
89 Result +=
Code.substr(NextChar);
93 TEST(RenameTest, WithinFileRename) {
96 llvm::StringRef Tests[] = {
116 if (auto [[^foo]] = 5) {
129 [[Foo]]::[[Fo^o]]() {}
130 void [[Foo]]::foo(int x) {}
135 template <typename T>
157 template <typename T> void func();
158 template <typename T> class Baz {};
175 virtual void [[f^oo]]() {}
178 void [[f^oo]]() override {}
181 void [[f^oo]]() override {}
193 template <typename T>
197 class [[F^oo]]<bool> {};
198 template <typename T>
199 class [[F^oo]]<T*> {};
210 template <typename T>
212 void func([[Foo]]<int>);
217 template <typename T>
220 T foo(T arg, T& ref, T* ptr) {
224 value = static_cast<T>(number);
227 static void foo(T value) {}
231 template <typename T>
241 [[F^oo]]<int>::foo(0);
245 [[Foo]]<bool>::foo(false);
251 template <typename T>
259 A<double>().[[f^oo]]();
260 A<float>().[[f^oo]]();
266 // Forward declaration.
269 virtual int getValue() const = 0;
272 class [[F^oo]] : public Baz {
274 [[Foo]](int value = 0) : x(value) {}
276 [[Foo]] &operator++(int);
278 bool operator<([[Foo]] const &rhs);
279 int getValue() const;
285 [[Foo]] *Pointer = 0;
286 [[Foo]] Variable = [[Foo]](10);
287 for ([[Foo]] it; it < Variable; it++);
288 const [[Foo]] *C = new [[Foo]]();
289 const_cast<[[Foo]] *>(C)->getValue();
291 const Baz &BazReference = foo;
292 const Baz *BazPointer = &foo;
293 reinterpret_cast<const [[^Foo]] *>(BazPointer)->getValue();
294 static_cast<const [[^Foo]] &>(BazReference).getValue();
295 static_cast<const [[^Foo]] *>(BazPointer)->getValue();
306 [[Foo^]]::~[[^Foo]]() {}
310 f.~/*something*/[[^Foo]]();
318 class Derived : public [[Bas^e]] {};
321 [[Bas^e]] *foo = new Derived();
322 foo->[[^Base]]::~[[^Base]]();
334 Qux::Qux() : [[F^oo]]() {}
345 #define MACRO(a) foo(a)
356 // no rename inside macro body.
368 M2(M1()); // foo is inside the nested macro body.
379 #define MACRO(a) qux(a)
391 template <typename [[^T]]>
393 [[T]] foo([[T]] arg, [[T]]& ref, [[^T]]* ptr) {
396 value = ([[T]])number;
397 value = static_cast<[[^T]]>(number);
400 static void foo([[T]] value) {}
408 class basic_string {};
409 typedef basic_string [[s^tring]];
412 ns::[[s^tring]] foo();
422 int Baz = A::[[^Foo]];
432 Foo = A::[[F^oo]] + Baz;
441 namespace a { namespace b { void foo(); } }
442 namespace [[^x]] = a::b;
450 enum class [[K^ind]] { ABC };
461 template <template<typename> class Z> struct Bar { };
462 template <> struct Bar<[[Foo]]> {};
470 Bar bar { .[[^Foo]] = 42 };
481 // FIXME: v selecting here results in renaming Field.
482 Bar bar { .[[Foo]].Field = 42 };
491 Bar bar { .Foo.[[^Field]] = 42 };
494 for (llvm::StringRef T : Tests) {
498 TU.
ExtraArgs.push_back(
"-fno-delayed-template-parsing");
499 auto AST = TU.
build();
500 llvm::StringRef NewName =
"abcde";
501 for (
const auto &RenamePos :
Code.points()) {
504 ASSERT_TRUE(
bool(RenameResult)) << RenameResult.takeError();
505 ASSERT_EQ(1u, RenameResult->size());
506 EXPECT_EQ(applyEdits(std::move(*RenameResult)).front().second,
507 expectedResult(
Code, NewName));
512 TEST(RenameTest, Renameable) {
515 const char* ErrorMessage;
517 const SymbolIndex *
Index;
520 const char *CommonHeader = R
"cpp(
524 OtherFile.HeaderCode = CommonHeader;
525 OtherFile.Filename = "other.cc";
527 auto OtherFileIndex = OtherFile.index();
528 const SymbolIndex *
Index = OtherFileIndex.get();
530 const bool HeaderFile =
true;
532 {R
"cpp(// allow -- function-local
533 void f(int [[Lo^cal]]) {
537 nullptr, HeaderFile,
Index},
539 {R
"cpp(// allow -- symbol is indexable and has no refs in index.
540 void [[On^lyInThisFile]]();
542 nullptr, HeaderFile,
Index},
544 {R
"cpp(// disallow -- symbol is indexable and has other refs in index.
549 "used outside main file", HeaderFile,
Index},
551 {R
"cpp(// disallow -- symbol in anonymous namespace in header is not indexable.
553 class Unin^dexable {};
556 "not eligible for indexing", HeaderFile,
Index},
558 {R
"cpp(// allow -- symbol in anonymous namespace in non-header file is indexable.
563 nullptr, !HeaderFile,
Index},
565 {R
"cpp(// disallow -- namespace symbol isn't supported
568 "not a supported kind", HeaderFile,
Index},
575 "not a supported kind", HeaderFile,
Index},
579 struct X { X operator++(int); };
580 void f(X x) {x+^+;})cpp",
581 "no symbol", HeaderFile,
Index},
583 {R
"cpp(// foo is declared outside the file.
586 "used outside main file", !HeaderFile ,
Index},
589 // We should detect the symbol is used outside the file from the AST.
591 "used outside main file", !HeaderFile,
nullptr },
593 {R
"cpp(// disallow rename on excluded symbols (e.g. std symbols)
598 "not a supported kind", !HeaderFile,
Index},
599 {R
"cpp(// disallow rename on excluded symbols (e.g. std symbols)
601 inline namespace __u {
606 "not a supported kind", !HeaderFile,
Index},
611 template <typename T> void f(T t) {
614 "multiple symbols", !HeaderFile,
nullptr },
616 {R
"cpp(// disallow rename on unrelated token.
619 "no symbol", !HeaderFile,
nullptr},
621 {R
"cpp(// disallow rename on unrelated token.
622 temp^late<typename T>
625 "no symbol", !HeaderFile,
nullptr},
628 for (
const auto& Case : Cases) {
629 SCOPED_TRACE(Case.Code);
630 Annotations T(Case.Code);
633 TU.ExtraArgs.push_back(
"-fno-delayed-template-parsing");
634 if (Case.IsHeaderFile) {
636 TU.Filename =
"test.h";
638 TU.ExtraArgs.push_back(
"-xobjective-c++-header");
640 auto AST = TU.
build();
641 llvm::StringRef NewName =
"dummyNewName";
643 rename({T.point(), NewName, AST,
testPath(TU.Filename), Case.Index});
644 bool WantRename =
true;
645 if (T.ranges().empty())
648 assert(Case.ErrorMessage &&
"Error message must be set!");
650 <<
"expected rename returned an error: " << T.code();
652 EXPECT_THAT(ActualMessage, testing::HasSubstr(Case.ErrorMessage));
654 EXPECT_TRUE(
bool(
Results)) <<
"rename returned an error: "
656 ASSERT_EQ(1u,
Results->size());
657 EXPECT_EQ(applyEdits(std::move(*
Results)).front().second,
658 expectedResult(T, NewName));
663 TEST(RenameTest, MainFileReferencesOnly) {
665 llvm::StringRef Test =
669 // rename references not from main file are not included.
673 Annotations Code(Test);
680 auto AST = TU.
build();
681 llvm::StringRef NewName =
"abcde";
685 ASSERT_TRUE(
bool(RenameResult)) << RenameResult.takeError() <<
Code.point();
686 ASSERT_EQ(1u, RenameResult->size());
687 EXPECT_EQ(applyEdits(std::move(*RenameResult)).front().second,
688 expectedResult(
Code, NewName));
691 TEST(RenameTest, ProtobufSymbolIsExcluded) {
692 Annotations
Code(
"Prot^obuf buf;");
695 R
"cpp(// Generated by the protocol buffer compiler. DO NOT EDIT!
698 TU.HeaderFilename = "protobuf.pb.h";
699 auto AST = TU.
build();
703 testing::HasSubstr(
"not a supported kind"));
706 TEST(CrossFileRenameTests, DirtyBuffer) {
707 Annotations FooCode(
"class [[Foo]] {};");
708 std::string FooPath =
testPath(
"foo.cc");
709 Annotations FooDirtyBuffer(
"class [[Foo]] {};\n// this is dirty buffer");
710 Annotations BarCode(
"void [[Bar]]() {}");
711 std::string BarPath =
testPath(
"bar.cc");
714 FileSymbols FSymbols;
715 FSymbols.update(FooPath,
nullptr, buildRefSlab(FooCode,
"Foo", FooPath),
717 FSymbols.update(BarPath,
nullptr, buildRefSlab(BarCode,
"Bar", BarPath),
721 Annotations MainCode(
"class [[Fo^o]] {};");
722 auto MainFilePath =
testPath(
"main.cc");
724 auto GetDirtyBuffer = [&](
PathRef Path) -> llvm::Optional<std::string> {
726 return FooDirtyBuffer.code().str();
733 auto AST = TU.
build();
734 llvm::StringRef NewName =
"newName";
744 applyEdits(std::move(*
Results)),
745 UnorderedElementsAre(
746 Pair(Eq(FooPath), Eq(expectedResult(FooDirtyBuffer, NewName))),
747 Pair(Eq(MainFilePath), Eq(expectedResult(MainCode, NewName)))));
751 MainCode = Annotations(
"void [[Bar]]() { [[B^ar]](); }");
765 applyEdits(std::move(*
Results)),
766 UnorderedElementsAre(
767 Pair(Eq(BarPath), Eq(expectedResult(BarCode, NewName))),
768 Pair(Eq(MainFilePath), Eq(expectedResult(MainCode, NewName)))));
772 class PaginationIndex :
public SymbolIndex {
773 bool refs(
const RefsRequest &Req,
774 llvm::function_ref<
void(
const Ref &)>
Callback)
const override {
779 const FuzzyFindRequest &Req,
780 llvm::function_ref<
void(
const Symbol &)>
Callback)
const override {
784 lookup(
const LookupRequest &Req,
785 llvm::function_ref<
void(
const Symbol &)>
Callback)
const override {}
787 void relations(
const RelationsRequest &Req,
788 llvm::function_ref<
void(
const SymbolID &,
const Symbol &)>
790 size_t estimateMemoryUsage()
const override {
return 0; }
801 testing::HasSubstr(
"too many occurrences"));
804 TEST(CrossFileRenameTests, DeduplicateRefsFromIndex) {
805 auto MainCode = Annotations(
"int [[^x]] = 2;");
806 auto MainFilePath =
testPath(
"main.cc");
807 auto BarCode = Annotations(
"int [[x]];");
812 auto AST = TU.
build();
813 std::string BarPathURI =
URI::create(BarPath).toString();
814 Ref XRefInBarCC = refWithRange(BarCode.range(), BarPathURI);
817 class DuplicatedXRefIndex :
public SymbolIndex {
819 DuplicatedXRefIndex(
const Ref &ReturnedRef) : ReturnedRef(ReturnedRef) {}
820 bool refs(
const RefsRequest &Req,
821 llvm::function_ref<
void(
const Ref &)>
Callback)
const override {
828 bool fuzzyFind(
const FuzzyFindRequest &,
829 llvm::function_ref<
void(
const Symbol &)>)
const override {
832 void lookup(
const LookupRequest &,
833 llvm::function_ref<
void(
const Symbol &)>)
const override {}
835 void relations(
const RelationsRequest &,
836 llvm::function_ref<
void(
const SymbolID &,
const Symbol &)>)
838 size_t estimateMemoryUsage()
const override {
return 0; }
840 } DIndex(XRefInBarCC);
841 llvm::StringRef NewName =
"newName";
850 applyEdits(std::move(*
Results)),
851 UnorderedElementsAre(
852 Pair(Eq(BarPath), Eq(expectedResult(BarCode, NewName))),
853 Pair(Eq(MainFilePath), Eq(expectedResult(MainCode, NewName)))));
856 TEST(CrossFileRenameTests, WithUpToDateIndex) {
857 MockCompilationDatabase CDB;
858 CDB.ExtraClangFlags = {
"-xc++"};
862 llvm::StringRef FooH;
863 llvm::StringRef FooCC;
875 [[Foo]]::[[Foo]]() {}
876 [[Foo]]::~[[Foo]]() {}
886 template <typename T>
888 // FIXME: explicit template specializations are not supported due the
889 // clangd index limitations.
891 class Foo<double> {};
909 void Foo::[[foo]]() {}
926 [[Foo]]::[[Foo]]() {}
927 [[Foo]]::~[[Foo]]() {}
951 typedef int [[IN^T]];
962 using [[I^NT]] = int;
973 static const int [[VA^R]] = 123;
983 enum class [[K^ind]] { ABC };
988 return [[Kind]]::ABC;
995 enum class Kind { [[A^BC]] };
1000 return Kind::[[ABC]];
1022 trace::TestTracer Tracer;
1023 for (
const auto &T : Cases) {
1024 SCOPED_TRACE(T.FooH);
1025 Annotations FooH(T.FooH);
1026 Annotations FooCC(T.FooCC);
1027 std::string FooHPath =
testPath(
"foo.h");
1028 std::string FooCCPath =
testPath(
"foo.cc");
1031 FS.
Files[FooHPath] = std::string(FooH.code());
1032 FS.
Files[FooCCPath] = std::string(FooCC.code());
1035 ServerOpts.BuildDynamicSymbolIndex =
true;
1036 ClangdServer Server(CDB,
FS, ServerOpts);
1043 llvm::StringRef NewName =
"NewName";
1044 for (
const auto &RenamePos : FooH.points()) {
1045 EXPECT_THAT(
Tracer.takeMetric(
"rename_files"), SizeIs(0));
1046 auto FileEditsList = llvm::cantFail(
runRename(
1047 Server, FooHPath, RenamePos, NewName, {
true}));
1048 EXPECT_THAT(
Tracer.takeMetric(
"rename_files"), ElementsAre(2));
1050 applyEdits(std::move(FileEditsList)),
1051 UnorderedElementsAre(
1052 Pair(Eq(FooHPath), Eq(expectedResult(T.FooH, NewName))),
1053 Pair(Eq(FooCCPath), Eq(expectedResult(T.FooCC, NewName)))));
1058 TEST(CrossFileRenameTests, CrossFileOnLocalSymbol) {
1061 Annotations
Code(
"void f(int [[abc]]) { [[a^bc]] = 3; }");
1064 auto AST = TU.
build();
1065 llvm::StringRef NewName =
"newName";
1069 applyEdits(std::move(*
Results)),
1070 UnorderedElementsAre(Pair(Eq(
Path), Eq(expectedResult(
Code, NewName)))));
1073 TEST(CrossFileRenameTests, BuildRenameEdits) {
1074 Annotations
Code(
"[[😂]]");
1075 auto LSPRange =
Code.range();
1076 llvm::StringRef FilePath =
"/test/TestTU.cpp";
1077 llvm::StringRef NewName =
"abc";
1079 ASSERT_TRUE(
bool(Edit)) << Edit.takeError();
1080 ASSERT_EQ(1UL, Edit->Replacements.size());
1081 EXPECT_EQ(FilePath, Edit->Replacements.begin()->getFilePath());
1082 EXPECT_EQ(4UL, Edit->Replacements.begin()->getLength());
1085 LSPRange.end = {10, 0};
1089 testing::HasSubstr(
"fail to convert"));
1092 Annotations T(R
"cpp(
1098 ASSERT_TRUE(bool(Edit)) << Edit.takeError();
1099 EXPECT_EQ(applyEdits(
FileEdits{{T.code(), std::move(*Edit)}}).front().second,
1100 expectedResult(T, NewName));
1108 llvm::StringRef IndexedCode;
1109 llvm::StringRef DraftCode;
1133 R
"cpp(int [[x]] = 0; void foo(int x);)cpp",
1134 R"cpp(double [[x]] = 0; void foo(double x);)cpp",
1149 LangOptions LangOpts;
1150 LangOpts.CPlusPlus = true;
1151 for (
const auto &T : Tests) {
1152 SCOPED_TRACE(T.DraftCode);
1153 Annotations Draft(T.DraftCode);
1155 Draft.code(),
"x", Annotations(T.IndexedCode).ranges(), LangOpts);
1157 EXPECT_THAT(Draft.ranges(), testing::IsEmpty());
1159 EXPECT_THAT(Draft.ranges(),
1160 testing::UnorderedElementsAreArray(*ActualRanges));
1164 TEST(RangePatchingHeuristic, GetMappedRanges) {
1168 llvm::StringRef IndexedCode;
1169 llvm::StringRef LexedCode;
1215 ^[[]] ^[[]] // column is shifted.
1228 [[]] [[]] // not mapped (both line and column are changed).
1242 // insert a new line
1245 [[]] // additional range
1248 [[]] // additional range
1266 for (
const auto &T : Tests) {
1267 SCOPED_TRACE(T.IndexedCode);
1268 auto Lexed = Annotations(T.LexedCode);
1269 auto LexedRanges = Lexed.ranges();
1270 std::vector<Range> ExpectedMatches;
1271 for (
auto P : Lexed.points()) {
1272 auto Match = llvm::find_if(LexedRanges, [&P](
const Range& R) {
1273 return R.start == P;
1275 ASSERT_NE(Match, LexedRanges.end());
1276 ExpectedMatches.push_back(*Match);
1282 EXPECT_THAT(ExpectedMatches, IsEmpty());
1284 EXPECT_THAT(ExpectedMatches, UnorderedElementsAreArray(*Mapped));
1288 TEST(CrossFileRenameTests, adjustmentCost) {
1290 llvm::StringRef RangeCode;
1291 size_t ExpectedCost;
1295 $idx[[]]$lex[[]] // diff: 0
1302 $lex[[]] // line diff: +1
1304 $lex[[]] // line diff: +1
1306 $lex[[]] // line diff: +1
1310 $lex[[]] // line diff: +2
1317 $lex[[]] // line diff: +1
1320 $lex[[]] // line diff: +2
1324 $lex[[]] // line diff: +3
1333 $lex[[]] // line diff: +3
1336 $lex[[]] // line diff: +2
1338 $lex[[]] // line diff: +1
1345 $lex[[]] // line diff: +1
1346 $lex[[]] // line diff: -2
1352 $lex[[]] // line diff: +3
1358 $idx[[]] $lex[[]] // column diff: +1
1359 $idx[[]]$lex[[]] // diff: 0
1366 $lex[[]] // diff: +1
1367 $idx[[]] $lex[[]] // column diff: +1
1368 $idx[[]]$lex[[]] // diff: 0
1374 $idx[[]] $lex[[]] // column diff: +1
1380 // column diffs: +1, +2, +3
1381 $idx[[]] $lex[[]] $idx[[]] $lex[[]] $idx[[]] $lex[[]]
1386 for (
const auto &T : Tests) {
1387 SCOPED_TRACE(T.RangeCode);
1388 Annotations C(T.RangeCode);
1389 std::vector<size_t> MappedIndex;
1390 for (
size_t I = 0; I < C.ranges(
"lex").size(); ++I)
1391 MappedIndex.push_back(I);