1 //===- unittest/Tooling/RefactoringTestActionRulesTest.cpp ----------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "ReplacementTest.h"
10 #include "RewriterTestContext.h"
11 #include "clang/Tooling/Refactoring.h"
12 #include "clang/Tooling/Refactoring/Extract/Extract.h"
13 #include "clang/Tooling/Refactoring/RefactoringAction.h"
14 #include "clang/Tooling/Refactoring/RefactoringDiagnostic.h"
15 #include "clang/Tooling/Refactoring/Rename/SymbolName.h"
16 #include "clang/Tooling/Tooling.h"
17 #include "llvm/Support/Errc.h"
18 #include "gtest/gtest.h"
21 using namespace clang
;
22 using namespace tooling
;
26 class RefactoringActionRulesTest
: public ::testing::Test
{
28 void SetUp() override
{
29 Context
.Sources
.setMainFileID(
30 Context
.createInMemoryFile("input.cpp", DefaultCode
));
33 RewriterTestContext Context
;
34 std::string DefaultCode
= std::string(100, 'a');
37 Expected
<AtomicChanges
>
38 createReplacements(const std::unique_ptr
<RefactoringActionRule
> &Rule
,
39 RefactoringRuleContext
&Context
) {
40 class Consumer final
: public RefactoringResultConsumer
{
41 void handleError(llvm::Error Err
) override
{ Result
= std::move(Err
); }
43 void handle(AtomicChanges SourceReplacements
) override
{
44 Result
= std::move(SourceReplacements
);
46 void handle(SymbolOccurrences Occurrences
) override
{
47 RefactoringResultConsumer::handle(std::move(Occurrences
));
51 std::optional
<Expected
<AtomicChanges
>> Result
;
55 Rule
->invoke(C
, Context
);
56 return std::move(*C
.Result
);
59 TEST_F(RefactoringActionRulesTest
, MyFirstRefactoringRule
) {
60 class ReplaceAWithB
: public SourceChangeRefactoringRule
{
61 std::pair
<SourceRange
, int> Selection
;
64 ReplaceAWithB(std::pair
<SourceRange
, int> Selection
)
65 : Selection(Selection
) {}
67 static Expected
<ReplaceAWithB
>
68 initiate(RefactoringRuleContext
&Cotnext
,
69 std::pair
<SourceRange
, int> Selection
) {
70 return ReplaceAWithB(Selection
);
73 Expected
<AtomicChanges
>
74 createSourceReplacements(RefactoringRuleContext
&Context
) {
75 const SourceManager
&SM
= Context
.getSources();
77 Selection
.first
.getBegin().getLocWithOffset(Selection
.second
);
78 AtomicChange
Change(SM
, Loc
);
79 llvm::Error E
= Change
.replace(SM
, Loc
, 1, "b");
82 return AtomicChanges
{Change
};
86 class SelectionRequirement
: public SourceRangeSelectionRequirement
{
88 Expected
<std::pair
<SourceRange
, int>>
89 evaluate(RefactoringRuleContext
&Context
) const {
90 Expected
<SourceRange
> R
=
91 SourceRangeSelectionRequirement::evaluate(Context
);
94 return std::make_pair(*R
, 20);
98 createRefactoringActionRule
<ReplaceAWithB
>(SelectionRequirement());
100 // When the requirements are satisfied, the rule's function must be invoked.
102 RefactoringRuleContext
RefContext(Context
.Sources
);
103 SourceLocation Cursor
=
104 Context
.Sources
.getLocForStartOfFile(Context
.Sources
.getMainFileID())
105 .getLocWithOffset(10);
106 RefContext
.setSelectionRange({Cursor
, Cursor
});
108 Expected
<AtomicChanges
> ErrorOrResult
=
109 createReplacements(Rule
, RefContext
);
110 ASSERT_FALSE(!ErrorOrResult
);
111 AtomicChanges Result
= std::move(*ErrorOrResult
);
112 ASSERT_EQ(Result
.size(), 1u);
113 std::string YAMLString
=
114 const_cast<AtomicChange
&>(Result
[0]).toYAMLString();
117 "Key: 'input.cpp:30'\n"
118 "FilePath: input.cpp\n"
120 "InsertedHeaders: []\n"
121 "RemovedHeaders: []\n"
123 " - FilePath: input.cpp\n"
126 " ReplacementText: b\n"
131 // When one of the requirements is not satisfied, invoke should return a
134 RefactoringRuleContext
RefContext(Context
.Sources
);
135 Expected
<AtomicChanges
> ErrorOrResult
=
136 createReplacements(Rule
, RefContext
);
138 ASSERT_TRUE(!ErrorOrResult
);
140 llvm::handleAllErrors(ErrorOrResult
.takeError(),
141 [&](DiagnosticError
&Error
) {
142 DiagID
= Error
.getDiagnostic().second
.getDiagID();
144 EXPECT_EQ(DiagID
, diag::err_refactor_no_selection
);
148 TEST_F(RefactoringActionRulesTest
, ReturnError
) {
149 class ErrorRule
: public SourceChangeRefactoringRule
{
151 static Expected
<ErrorRule
> initiate(RefactoringRuleContext
&,
156 ErrorRule(SourceRange R
) {}
157 Expected
<AtomicChanges
> createSourceReplacements(RefactoringRuleContext
&) {
158 return llvm::make_error
<llvm::StringError
>(
159 "Error", llvm::make_error_code(llvm::errc::invalid_argument
));
164 createRefactoringActionRule
<ErrorRule
>(SourceRangeSelectionRequirement());
165 RefactoringRuleContext
RefContext(Context
.Sources
);
166 SourceLocation Cursor
=
167 Context
.Sources
.getLocForStartOfFile(Context
.Sources
.getMainFileID());
168 RefContext
.setSelectionRange({Cursor
, Cursor
});
169 Expected
<AtomicChanges
> Result
= createReplacements(Rule
, RefContext
);
171 ASSERT_TRUE(!Result
);
173 llvm::handleAllErrors(Result
.takeError(), [&](llvm::StringError
&Error
) {
174 Message
= Error
.getMessage();
176 EXPECT_EQ(Message
, "Error");
179 std::optional
<SymbolOccurrences
>
180 findOccurrences(RefactoringActionRule
&Rule
, RefactoringRuleContext
&Context
) {
181 class Consumer final
: public RefactoringResultConsumer
{
182 void handleError(llvm::Error
) override
{}
183 void handle(SymbolOccurrences Occurrences
) override
{
184 Result
= std::move(Occurrences
);
186 void handle(AtomicChanges Changes
) override
{
187 RefactoringResultConsumer::handle(std::move(Changes
));
191 std::optional
<SymbolOccurrences
> Result
;
195 Rule
.invoke(C
, Context
);
196 return std::move(C
.Result
);
199 TEST_F(RefactoringActionRulesTest
, ReturnSymbolOccurrences
) {
200 class FindOccurrences
: public FindSymbolOccurrencesRefactoringRule
{
201 SourceRange Selection
;
204 FindOccurrences(SourceRange Selection
) : Selection(Selection
) {}
206 static Expected
<FindOccurrences
> initiate(RefactoringRuleContext
&,
207 SourceRange Selection
) {
208 return FindOccurrences(Selection
);
211 Expected
<SymbolOccurrences
>
212 findSymbolOccurrences(RefactoringRuleContext
&) override
{
213 SymbolOccurrences Occurrences
;
214 Occurrences
.push_back(SymbolOccurrence(SymbolName("test"),
215 SymbolOccurrence::MatchingSymbol
,
216 Selection
.getBegin()));
217 return std::move(Occurrences
);
221 auto Rule
= createRefactoringActionRule
<FindOccurrences
>(
222 SourceRangeSelectionRequirement());
224 RefactoringRuleContext
RefContext(Context
.Sources
);
225 SourceLocation Cursor
=
226 Context
.Sources
.getLocForStartOfFile(Context
.Sources
.getMainFileID());
227 RefContext
.setSelectionRange({Cursor
, Cursor
});
228 std::optional
<SymbolOccurrences
> Result
= findOccurrences(*Rule
, RefContext
);
230 ASSERT_FALSE(!Result
);
231 SymbolOccurrences Occurrences
= std::move(*Result
);
232 EXPECT_EQ(Occurrences
.size(), 1u);
233 EXPECT_EQ(Occurrences
[0].getKind(), SymbolOccurrence::MatchingSymbol
);
234 EXPECT_EQ(Occurrences
[0].getNameRanges().size(), 1u);
235 EXPECT_EQ(Occurrences
[0].getNameRanges()[0],
236 SourceRange(Cursor
, Cursor
.getLocWithOffset(strlen("test"))));
239 TEST_F(RefactoringActionRulesTest
, EditorCommandBinding
) {
240 const RefactoringDescriptor
&Descriptor
= ExtractFunction::describe();
241 EXPECT_EQ(Descriptor
.Name
, "extract-function");
243 Descriptor
.Description
,
244 "(WIP action; use with caution!) Extracts code into a new function");
245 EXPECT_EQ(Descriptor
.Title
, "Extract Function");
248 } // end anonymous namespace