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