1 //===- unittest/Tooling/RefactoringTestActionRulesTest.cpp ----------------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "ReplacementTest.h"
11 #include "RewriterTestContext.h"
12 #include "clang/Tooling/Refactoring.h"
13 #include "clang/Tooling/Refactoring/RefactoringActionRules.h"
14 #include "clang/Tooling/Refactoring/Rename/SymbolName.h"
15 #include "clang/Tooling/Tooling.h"
16 #include "llvm/Support/Errc.h"
17 #include "gtest/gtest.h"
18 
19 using namespace clang;
20 using namespace tooling;
21 
22 namespace {
23 
24 class RefactoringActionRulesTest : public ::testing::Test {
25 protected:
26   void SetUp() override {
27     Context.Sources.setMainFileID(
28         Context.createInMemoryFile("input.cpp", DefaultCode));
29   }
30 
31   RewriterTestContext Context;
32   std::string DefaultCode = std::string(100, 'a');
33 };
34 
35 Expected<AtomicChanges>
36 createReplacements(const std::unique_ptr<RefactoringActionRule> &Rule,
37                    RefactoringRuleContext &Context) {
38   class Consumer final : public RefactoringResultConsumer {
39     void handleError(llvm::Error Err) override { Result = std::move(Err); }
40 
41     void handle(AtomicChanges SourceReplacements) override {
42       Result = std::move(SourceReplacements);
43     }
44     void handle(SymbolOccurrences Occurrences) override {
45       RefactoringResultConsumer::handle(std::move(Occurrences));
46     }
47 
48   public:
49     Optional<Expected<AtomicChanges>> Result;
50   };
51 
52   Consumer C;
53   Rule->invoke(C, Context);
54   return std::move(*C.Result);
55 }
56 
57 TEST_F(RefactoringActionRulesTest, MyFirstRefactoringRule) {
58   class ReplaceAWithB : public SourceChangeRefactoringRule {
59     std::pair<SourceRange, int> Selection;
60 
61   public:
62     ReplaceAWithB(std::pair<SourceRange, int> Selection)
63         : Selection(Selection) {}
64 
65     Expected<AtomicChanges>
66     createSourceReplacements(RefactoringRuleContext &Context) {
67       const SourceManager &SM = Context.getSources();
68       SourceLocation Loc =
69           Selection.first.getBegin().getLocWithOffset(Selection.second);
70       AtomicChange Change(SM, Loc);
71       llvm::Error E = Change.replace(SM, Loc, 1, "b");
72       if (E)
73         return std::move(E);
74       return AtomicChanges{Change};
75     }
76   };
77 
78   class SelectionRequirement : public SourceRangeSelectionRequirement {
79   public:
80     Expected<std::pair<SourceRange, int>>
81     evaluate(RefactoringRuleContext &Context) const {
82       Expected<SourceRange> R =
83           SourceRangeSelectionRequirement::evaluate(Context);
84       if (!R)
85         return R.takeError();
86       return std::make_pair(*R, 20);
87     }
88   };
89   auto Rule =
90       createRefactoringActionRule<ReplaceAWithB>(SelectionRequirement());
91 
92   // When the requirements are satisifed, the rule's function must be invoked.
93   {
94     RefactoringRuleContext RefContext(Context.Sources);
95     SourceLocation Cursor =
96         Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID())
97             .getLocWithOffset(10);
98     RefContext.setSelectionRange({Cursor, Cursor});
99 
100     Expected<AtomicChanges> ErrorOrResult =
101         createReplacements(Rule, RefContext);
102     ASSERT_FALSE(!ErrorOrResult);
103     AtomicChanges Result = std::move(*ErrorOrResult);
104     ASSERT_EQ(Result.size(), 1u);
105     std::string YAMLString =
106         const_cast<AtomicChange &>(Result[0]).toYAMLString();
107 
108     ASSERT_STREQ("---\n"
109                  "Key:             'input.cpp:30'\n"
110                  "FilePath:        input.cpp\n"
111                  "Error:           ''\n"
112                  "InsertedHeaders: \n"
113                  "RemovedHeaders:  \n"
114                  "Replacements:    \n" // Extra whitespace here!
115                  "  - FilePath:        input.cpp\n"
116                  "    Offset:          30\n"
117                  "    Length:          1\n"
118                  "    ReplacementText: b\n"
119                  "...\n",
120                  YAMLString.c_str());
121   }
122 
123   // When one of the requirements is not satisfied, invoke should return a
124   // valid error.
125   {
126     RefactoringRuleContext RefContext(Context.Sources);
127     Expected<AtomicChanges> ErrorOrResult =
128         createReplacements(Rule, RefContext);
129 
130     ASSERT_TRUE(!ErrorOrResult);
131     std::string Message;
132     llvm::handleAllErrors(
133         ErrorOrResult.takeError(),
134         [&](llvm::StringError &Error) { Message = Error.getMessage(); });
135     EXPECT_EQ(Message,
136               "refactoring action can't be initiated without a selection");
137   }
138 }
139 
140 TEST_F(RefactoringActionRulesTest, ReturnError) {
141   class ErrorRule : public SourceChangeRefactoringRule {
142   public:
143     ErrorRule(SourceRange R) {}
144     Expected<AtomicChanges> createSourceReplacements(RefactoringRuleContext &) {
145       return llvm::make_error<llvm::StringError>(
146           "Error", llvm::make_error_code(llvm::errc::invalid_argument));
147     }
148   };
149 
150   auto Rule =
151       createRefactoringActionRule<ErrorRule>(SourceRangeSelectionRequirement());
152   RefactoringRuleContext RefContext(Context.Sources);
153   SourceLocation Cursor =
154       Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID());
155   RefContext.setSelectionRange({Cursor, Cursor});
156   Expected<AtomicChanges> Result = createReplacements(Rule, RefContext);
157 
158   ASSERT_TRUE(!Result);
159   std::string Message;
160   llvm::handleAllErrors(Result.takeError(), [&](llvm::StringError &Error) {
161     Message = Error.getMessage();
162   });
163   EXPECT_EQ(Message, "Error");
164 }
165 
166 Optional<SymbolOccurrences> findOccurrences(RefactoringActionRule &Rule,
167                                             RefactoringRuleContext &Context) {
168   class Consumer final : public RefactoringResultConsumer {
169     void handleError(llvm::Error) override {}
170     void handle(SymbolOccurrences Occurrences) override {
171       Result = std::move(Occurrences);
172     }
173     void handle(AtomicChanges Changes) override {
174       RefactoringResultConsumer::handle(std::move(Changes));
175     }
176 
177   public:
178     Optional<SymbolOccurrences> Result;
179   };
180 
181   Consumer C;
182   Rule.invoke(C, Context);
183   return std::move(C.Result);
184 }
185 
186 TEST_F(RefactoringActionRulesTest, ReturnSymbolOccurrences) {
187   class FindOccurrences : public FindSymbolOccurrencesRefactoringRule {
188     SourceRange Selection;
189 
190   public:
191     FindOccurrences(SourceRange Selection) : Selection(Selection) {}
192 
193     Expected<SymbolOccurrences>
194     findSymbolOccurrences(RefactoringRuleContext &) override {
195       SymbolOccurrences Occurrences;
196       Occurrences.push_back(SymbolOccurrence(SymbolName("test"),
197                                              SymbolOccurrence::MatchingSymbol,
198                                              Selection.getBegin()));
199       return std::move(Occurrences);
200     }
201   };
202 
203   auto Rule = createRefactoringActionRule<FindOccurrences>(
204       SourceRangeSelectionRequirement());
205 
206   RefactoringRuleContext RefContext(Context.Sources);
207   SourceLocation Cursor =
208       Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID());
209   RefContext.setSelectionRange({Cursor, Cursor});
210   Optional<SymbolOccurrences> Result = findOccurrences(*Rule, RefContext);
211 
212   ASSERT_FALSE(!Result);
213   SymbolOccurrences Occurrences = std::move(*Result);
214   EXPECT_EQ(Occurrences.size(), 1u);
215   EXPECT_EQ(Occurrences[0].getKind(), SymbolOccurrence::MatchingSymbol);
216   EXPECT_EQ(Occurrences[0].getNameRanges().size(), 1u);
217   EXPECT_EQ(Occurrences[0].getNameRanges()[0],
218             SourceRange(Cursor, Cursor.getLocWithOffset(strlen("test"))));
219 }
220 
221 } // end anonymous namespace
222