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