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 "TestFS.h"
11 #include "TestTU.h"
12 #include "refactor/Rename.h"
13 #include "clang/Tooling/Core/Replacement.h"
14 #include "gmock/gmock.h"
15 #include "gtest/gtest.h"
16
17 namespace clang {
18 namespace clangd {
19 namespace {
20
21 MATCHER_P2(RenameRange, Code, Range, "") {
22 return replacementToEdit(Code, arg).range == Range;
23 }
24
TEST(RenameTest,SingleFile)25 TEST(RenameTest, SingleFile) {
26 struct Test {
27 const char* Before;
28 const char* After;
29 } Tests[] = {
30 // Rename function.
31 {
32 R"cpp(
33 void foo() {
34 fo^o();
35 }
36 )cpp",
37 R"cpp(
38 void abcde() {
39 abcde();
40 }
41 )cpp",
42 },
43 // Rename type.
44 {
45 R"cpp(
46 struct foo{};
47 foo test() {
48 f^oo x;
49 return x;
50 }
51 )cpp",
52 R"cpp(
53 struct abcde{};
54 abcde test() {
55 abcde x;
56 return x;
57 }
58 )cpp",
59 },
60 // Rename variable.
61 {
62 R"cpp(
63 void bar() {
64 if (auto ^foo = 5) {
65 foo = 3;
66 }
67 }
68 )cpp",
69 R"cpp(
70 void bar() {
71 if (auto abcde = 5) {
72 abcde = 3;
73 }
74 }
75 )cpp",
76 },
77 };
78 for (const Test &T : Tests) {
79 Annotations Code(T.Before);
80 auto TU = TestTU::withCode(Code.code());
81 auto AST = TU.build();
82 auto RenameResult =
83 renameWithinFile(AST, testPath(TU.Filename), Code.point(), "abcde");
84 ASSERT_TRUE(bool(RenameResult)) << RenameResult.takeError();
85 auto ApplyResult =
86 tooling::applyAllReplacements(Code.code(), *RenameResult);
87 ASSERT_TRUE(bool(ApplyResult)) << ApplyResult.takeError();
88
89 EXPECT_EQ(T.After, *ApplyResult) << T.Before;
90 }
91 }
92
TEST(RenameTest,Renameable)93 TEST(RenameTest, Renameable) {
94 struct Case {
95 const char *Code;
96 const char* ErrorMessage; // null if no error
97 bool IsHeaderFile;
98 };
99 const bool HeaderFile = true;
100 Case Cases[] = {
101 {R"cpp(// allow -- function-local
102 void f(int [[Lo^cal]]) {
103 [[Local]] = 2;
104 }
105 )cpp",
106 nullptr, HeaderFile},
107
108 {R"cpp(// allow -- symbol is indexable and has no refs in index.
109 void [[On^lyInThisFile]]();
110 )cpp",
111 nullptr, HeaderFile},
112
113 {R"cpp(// disallow -- symbol is indexable and has other refs in index.
114 void f() {
115 Out^side s;
116 }
117 )cpp",
118 "used outside main file", HeaderFile},
119
120 {R"cpp(// disallow -- symbol is not indexable.
121 namespace {
122 class Unin^dexable {};
123 }
124 )cpp",
125 "not eligible for indexing", HeaderFile},
126
127 {R"cpp(// disallow -- namespace symbol isn't supported
128 namespace fo^o {}
129 )cpp",
130 "not a supported kind", HeaderFile},
131
132 {
133 R"cpp(
134 #define MACRO 1
135 int s = MAC^RO;
136 )cpp",
137 "not a supported kind", HeaderFile},
138
139 {R"cpp(// foo is declared outside the file.
140 void fo^o() {}
141 )cpp", "used outside main file", !HeaderFile/*cc file*/},
142 };
143 const char *CommonHeader = R"cpp(
144 class Outside {};
145 void foo();
146 )cpp";
147 TestTU OtherFile = TestTU::withCode("Outside s; auto ss = &foo;");
148 OtherFile.HeaderCode = CommonHeader;
149 OtherFile.Filename = "other.cc";
150 // The index has a "Outside" reference and a "foo" reference.
151 auto OtherFileIndex = OtherFile.index();
152
153 for (const auto& Case : Cases) {
154 Annotations T(Case.Code);
155 TestTU TU = TestTU::withCode(T.code());
156 TU.HeaderCode = CommonHeader;
157 if (Case.IsHeaderFile) {
158 // We open the .h file as the main file.
159 TU.Filename = "test.h";
160 // Parsing the .h file as C++ include.
161 TU.ExtraArgs.push_back("-xobjective-c++-header");
162 }
163 auto AST = TU.build();
164
165 auto Results = renameWithinFile(AST, testPath(TU.Filename), T.point(),
166 "dummyNewName", OtherFileIndex.get());
167 bool WantRename = true;
168 if (T.ranges().empty())
169 WantRename = false;
170 if (!WantRename) {
171 assert(Case.ErrorMessage && "Error message must be set!");
172 EXPECT_FALSE(Results) << "expected renameWithinFile returned an error: "
173 << T.code();
174 auto ActualMessage = llvm::toString(Results.takeError());
175 EXPECT_THAT(ActualMessage, testing::HasSubstr(Case.ErrorMessage));
176 } else {
177 EXPECT_TRUE(bool(Results)) << "renameWithinFile returned an error: "
178 << llvm::toString(Results.takeError());
179 std::vector<testing::Matcher<tooling::Replacement>> Expected;
180 for (const auto &R : T.ranges())
181 Expected.push_back(RenameRange(TU.Code, R));
182 EXPECT_THAT(*Results, UnorderedElementsAreArray(Expected));
183 }
184 }
185 }
186
187 } // namespace
188 } // namespace clangd
189 } // namespace clang
190