xref: /llvm-project/clang/unittests/Tooling/RefactoringActionRulesTest.cpp (revision 6ad0788c332bb2043142954d300c49ac3e537f34)
11586fa70SAlex Lorenz //===- unittest/Tooling/RefactoringTestActionRulesTest.cpp ----------------===//
21586fa70SAlex Lorenz //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
61586fa70SAlex Lorenz //
71586fa70SAlex Lorenz //===----------------------------------------------------------------------===//
81586fa70SAlex Lorenz 
91586fa70SAlex Lorenz #include "ReplacementTest.h"
101586fa70SAlex Lorenz #include "RewriterTestContext.h"
111586fa70SAlex Lorenz #include "clang/Tooling/Refactoring.h"
120beca4d1SAlex Lorenz #include "clang/Tooling/Refactoring/Extract/Extract.h"
130beca4d1SAlex Lorenz #include "clang/Tooling/Refactoring/RefactoringAction.h"
14f5ca27ccSAlex Lorenz #include "clang/Tooling/Refactoring/RefactoringDiagnostic.h"
153529a94bSAlex Lorenz #include "clang/Tooling/Refactoring/Rename/SymbolName.h"
161586fa70SAlex Lorenz #include "clang/Tooling/Tooling.h"
171586fa70SAlex Lorenz #include "llvm/Support/Errc.h"
181586fa70SAlex Lorenz #include "gtest/gtest.h"
19a1580d7bSKazu Hirata #include <optional>
201586fa70SAlex Lorenz 
211586fa70SAlex Lorenz using namespace clang;
221586fa70SAlex Lorenz using namespace tooling;
231586fa70SAlex Lorenz 
241586fa70SAlex Lorenz namespace {
251586fa70SAlex Lorenz 
261586fa70SAlex Lorenz class RefactoringActionRulesTest : public ::testing::Test {
271586fa70SAlex Lorenz protected:
SetUp()281586fa70SAlex Lorenz   void SetUp() override {
291586fa70SAlex Lorenz     Context.Sources.setMainFileID(
301586fa70SAlex Lorenz         Context.createInMemoryFile("input.cpp", DefaultCode));
311586fa70SAlex Lorenz   }
321586fa70SAlex Lorenz 
331586fa70SAlex Lorenz   RewriterTestContext Context;
341586fa70SAlex Lorenz   std::string DefaultCode = std::string(100, 'a');
351586fa70SAlex Lorenz };
361586fa70SAlex Lorenz 
37f6959ed1SAlex Lorenz Expected<AtomicChanges>
createReplacements(const std::unique_ptr<RefactoringActionRule> & Rule,RefactoringRuleContext & Context)381586fa70SAlex Lorenz createReplacements(const std::unique_ptr<RefactoringActionRule> &Rule,
391586fa70SAlex Lorenz                    RefactoringRuleContext &Context) {
40f6959ed1SAlex Lorenz   class Consumer final : public RefactoringResultConsumer {
41f6959ed1SAlex Lorenz     void handleError(llvm::Error Err) override { Result = std::move(Err); }
42f6959ed1SAlex Lorenz 
43f6959ed1SAlex Lorenz     void handle(AtomicChanges SourceReplacements) override {
44f6959ed1SAlex Lorenz       Result = std::move(SourceReplacements);
45f6959ed1SAlex Lorenz     }
462420122fSAlex Lorenz     void handle(SymbolOccurrences Occurrences) override {
472420122fSAlex Lorenz       RefactoringResultConsumer::handle(std::move(Occurrences));
482420122fSAlex Lorenz     }
49f6959ed1SAlex Lorenz 
50f6959ed1SAlex Lorenz   public:
51*6ad0788cSKazu Hirata     std::optional<Expected<AtomicChanges>> Result;
52f6959ed1SAlex Lorenz   };
53f6959ed1SAlex Lorenz 
54f6959ed1SAlex Lorenz   Consumer C;
55f6959ed1SAlex Lorenz   Rule->invoke(C, Context);
56f6959ed1SAlex Lorenz   return std::move(*C.Result);
571586fa70SAlex Lorenz }
581586fa70SAlex Lorenz 
TEST_F(RefactoringActionRulesTest,MyFirstRefactoringRule)591586fa70SAlex Lorenz TEST_F(RefactoringActionRulesTest, MyFirstRefactoringRule) {
601c0a26bdSAlex Lorenz   class ReplaceAWithB : public SourceChangeRefactoringRule {
611c0a26bdSAlex Lorenz     std::pair<SourceRange, int> Selection;
621c0a26bdSAlex Lorenz 
631c0a26bdSAlex Lorenz   public:
641c0a26bdSAlex Lorenz     ReplaceAWithB(std::pair<SourceRange, int> Selection)
651c0a26bdSAlex Lorenz         : Selection(Selection) {}
661c0a26bdSAlex Lorenz 
670beca4d1SAlex Lorenz     static Expected<ReplaceAWithB>
680beca4d1SAlex Lorenz     initiate(RefactoringRuleContext &Cotnext,
690beca4d1SAlex Lorenz              std::pair<SourceRange, int> Selection) {
700beca4d1SAlex Lorenz       return ReplaceAWithB(Selection);
710beca4d1SAlex Lorenz     }
720beca4d1SAlex Lorenz 
731c0a26bdSAlex Lorenz     Expected<AtomicChanges>
741c0a26bdSAlex Lorenz     createSourceReplacements(RefactoringRuleContext &Context) {
751c0a26bdSAlex Lorenz       const SourceManager &SM = Context.getSources();
761c0a26bdSAlex Lorenz       SourceLocation Loc =
771c0a26bdSAlex Lorenz           Selection.first.getBegin().getLocWithOffset(Selection.second);
781586fa70SAlex Lorenz       AtomicChange Change(SM, Loc);
791586fa70SAlex Lorenz       llvm::Error E = Change.replace(SM, Loc, 1, "b");
801586fa70SAlex Lorenz       if (E)
811586fa70SAlex Lorenz         return std::move(E);
821586fa70SAlex Lorenz       return AtomicChanges{Change};
831586fa70SAlex Lorenz     }
841586fa70SAlex Lorenz   };
851c0a26bdSAlex Lorenz 
861c0a26bdSAlex Lorenz   class SelectionRequirement : public SourceRangeSelectionRequirement {
871c0a26bdSAlex Lorenz   public:
881c0a26bdSAlex Lorenz     Expected<std::pair<SourceRange, int>>
891c0a26bdSAlex Lorenz     evaluate(RefactoringRuleContext &Context) const {
901c0a26bdSAlex Lorenz       Expected<SourceRange> R =
911c0a26bdSAlex Lorenz           SourceRangeSelectionRequirement::evaluate(Context);
921c0a26bdSAlex Lorenz       if (!R)
931c0a26bdSAlex Lorenz         return R.takeError();
941c0a26bdSAlex Lorenz       return std::make_pair(*R, 20);
951c0a26bdSAlex Lorenz     }
961c0a26bdSAlex Lorenz   };
971c0a26bdSAlex Lorenz   auto Rule =
981c0a26bdSAlex Lorenz       createRefactoringActionRule<ReplaceAWithB>(SelectionRequirement());
991586fa70SAlex Lorenz 
100f8586ae8SSylvestre Ledru   // When the requirements are satisfied, the rule's function must be invoked.
1011586fa70SAlex Lorenz   {
1021586fa70SAlex Lorenz     RefactoringRuleContext RefContext(Context.Sources);
1031586fa70SAlex Lorenz     SourceLocation Cursor =
1041586fa70SAlex Lorenz         Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID())
1051586fa70SAlex Lorenz             .getLocWithOffset(10);
1061586fa70SAlex Lorenz     RefContext.setSelectionRange({Cursor, Cursor});
1071586fa70SAlex Lorenz 
108f6959ed1SAlex Lorenz     Expected<AtomicChanges> ErrorOrResult =
1091586fa70SAlex Lorenz         createReplacements(Rule, RefContext);
1101586fa70SAlex Lorenz     ASSERT_FALSE(!ErrorOrResult);
111f6959ed1SAlex Lorenz     AtomicChanges Result = std::move(*ErrorOrResult);
1121586fa70SAlex Lorenz     ASSERT_EQ(Result.size(), 1u);
1131586fa70SAlex Lorenz     std::string YAMLString =
1141586fa70SAlex Lorenz         const_cast<AtomicChange &>(Result[0]).toYAMLString();
1151586fa70SAlex Lorenz 
1161586fa70SAlex Lorenz     ASSERT_STREQ("---\n"
1171586fa70SAlex Lorenz                  "Key:             'input.cpp:30'\n"
1181586fa70SAlex Lorenz                  "FilePath:        input.cpp\n"
1191586fa70SAlex Lorenz                  "Error:           ''\n"
120c0830f55SScott Linder                  "InsertedHeaders: []\n"
121c0830f55SScott Linder                  "RemovedHeaders:  []\n"
122ff6836f4SFangrui Song                  "Replacements:\n"
1231586fa70SAlex Lorenz                  "  - FilePath:        input.cpp\n"
1241586fa70SAlex Lorenz                  "    Offset:          30\n"
1251586fa70SAlex Lorenz                  "    Length:          1\n"
1261586fa70SAlex Lorenz                  "    ReplacementText: b\n"
1271586fa70SAlex Lorenz                  "...\n",
1281586fa70SAlex Lorenz                  YAMLString.c_str());
1291586fa70SAlex Lorenz   }
1301586fa70SAlex Lorenz 
131f6959ed1SAlex Lorenz   // When one of the requirements is not satisfied, invoke should return a
132f6959ed1SAlex Lorenz   // valid error.
1331586fa70SAlex Lorenz   {
1341586fa70SAlex Lorenz     RefactoringRuleContext RefContext(Context.Sources);
135f6959ed1SAlex Lorenz     Expected<AtomicChanges> ErrorOrResult =
1361586fa70SAlex Lorenz         createReplacements(Rule, RefContext);
1371586fa70SAlex Lorenz 
138f6959ed1SAlex Lorenz     ASSERT_TRUE(!ErrorOrResult);
139f5ca27ccSAlex Lorenz     unsigned DiagID;
140f5ca27ccSAlex Lorenz     llvm::handleAllErrors(ErrorOrResult.takeError(),
141f5ca27ccSAlex Lorenz                           [&](DiagnosticError &Error) {
142f5ca27ccSAlex Lorenz                             DiagID = Error.getDiagnostic().second.getDiagID();
143f5ca27ccSAlex Lorenz                           });
144f5ca27ccSAlex Lorenz     EXPECT_EQ(DiagID, diag::err_refactor_no_selection);
1451586fa70SAlex Lorenz   }
1461586fa70SAlex Lorenz }
1471586fa70SAlex Lorenz 
TEST_F(RefactoringActionRulesTest,ReturnError)1481586fa70SAlex Lorenz TEST_F(RefactoringActionRulesTest, ReturnError) {
1491c0a26bdSAlex Lorenz   class ErrorRule : public SourceChangeRefactoringRule {
1501c0a26bdSAlex Lorenz   public:
1510beca4d1SAlex Lorenz     static Expected<ErrorRule> initiate(RefactoringRuleContext &,
1520beca4d1SAlex Lorenz                                         SourceRange R) {
1530beca4d1SAlex Lorenz       return ErrorRule(R);
1540beca4d1SAlex Lorenz     }
1550beca4d1SAlex Lorenz 
1561c0a26bdSAlex Lorenz     ErrorRule(SourceRange R) {}
1571c0a26bdSAlex Lorenz     Expected<AtomicChanges> createSourceReplacements(RefactoringRuleContext &) {
1581586fa70SAlex Lorenz       return llvm::make_error<llvm::StringError>(
159595c644fSAlex Lorenz           "Error", llvm::make_error_code(llvm::errc::invalid_argument));
1601c0a26bdSAlex Lorenz     }
1611586fa70SAlex Lorenz   };
1621586fa70SAlex Lorenz 
1631c0a26bdSAlex Lorenz   auto Rule =
1641c0a26bdSAlex Lorenz       createRefactoringActionRule<ErrorRule>(SourceRangeSelectionRequirement());
1651586fa70SAlex Lorenz   RefactoringRuleContext RefContext(Context.Sources);
1661586fa70SAlex Lorenz   SourceLocation Cursor =
1671586fa70SAlex Lorenz       Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID());
1681586fa70SAlex Lorenz   RefContext.setSelectionRange({Cursor, Cursor});
169f6959ed1SAlex Lorenz   Expected<AtomicChanges> Result = createReplacements(Rule, RefContext);
1701586fa70SAlex Lorenz 
1711586fa70SAlex Lorenz   ASSERT_TRUE(!Result);
1721586fa70SAlex Lorenz   std::string Message;
1731586fa70SAlex Lorenz   llvm::handleAllErrors(Result.takeError(), [&](llvm::StringError &Error) {
1741586fa70SAlex Lorenz     Message = Error.getMessage();
1751586fa70SAlex Lorenz   });
1761586fa70SAlex Lorenz   EXPECT_EQ(Message, "Error");
1771586fa70SAlex Lorenz }
1781586fa70SAlex Lorenz 
179*6ad0788cSKazu Hirata std::optional<SymbolOccurrences>
findOccurrences(RefactoringActionRule & Rule,RefactoringRuleContext & Context)180*6ad0788cSKazu Hirata findOccurrences(RefactoringActionRule &Rule, RefactoringRuleContext &Context) {
1813529a94bSAlex Lorenz   class Consumer final : public RefactoringResultConsumer {
1823529a94bSAlex Lorenz     void handleError(llvm::Error) override {}
1833529a94bSAlex Lorenz     void handle(SymbolOccurrences Occurrences) override {
1843529a94bSAlex Lorenz       Result = std::move(Occurrences);
1853529a94bSAlex Lorenz     }
1862420122fSAlex Lorenz     void handle(AtomicChanges Changes) override {
1872420122fSAlex Lorenz       RefactoringResultConsumer::handle(std::move(Changes));
1882420122fSAlex Lorenz     }
1893529a94bSAlex Lorenz 
1903529a94bSAlex Lorenz   public:
191*6ad0788cSKazu Hirata     std::optional<SymbolOccurrences> Result;
1923529a94bSAlex Lorenz   };
1933529a94bSAlex Lorenz 
1943529a94bSAlex Lorenz   Consumer C;
1953529a94bSAlex Lorenz   Rule.invoke(C, Context);
1963529a94bSAlex Lorenz   return std::move(C.Result);
1973529a94bSAlex Lorenz }
1983529a94bSAlex Lorenz 
TEST_F(RefactoringActionRulesTest,ReturnSymbolOccurrences)1993529a94bSAlex Lorenz TEST_F(RefactoringActionRulesTest, ReturnSymbolOccurrences) {
2001c0a26bdSAlex Lorenz   class FindOccurrences : public FindSymbolOccurrencesRefactoringRule {
2011c0a26bdSAlex Lorenz     SourceRange Selection;
2021c0a26bdSAlex Lorenz 
2031c0a26bdSAlex Lorenz   public:
2041c0a26bdSAlex Lorenz     FindOccurrences(SourceRange Selection) : Selection(Selection) {}
2051c0a26bdSAlex Lorenz 
2060beca4d1SAlex Lorenz     static Expected<FindOccurrences> initiate(RefactoringRuleContext &,
2070beca4d1SAlex Lorenz                                               SourceRange Selection) {
2080beca4d1SAlex Lorenz       return FindOccurrences(Selection);
2090beca4d1SAlex Lorenz     }
2100beca4d1SAlex Lorenz 
2111c0a26bdSAlex Lorenz     Expected<SymbolOccurrences>
2121c0a26bdSAlex Lorenz     findSymbolOccurrences(RefactoringRuleContext &) override {
2133529a94bSAlex Lorenz       SymbolOccurrences Occurrences;
2141c0a26bdSAlex Lorenz       Occurrences.push_back(SymbolOccurrence(SymbolName("test"),
2151c0a26bdSAlex Lorenz                                              SymbolOccurrence::MatchingSymbol,
2161c0a26bdSAlex Lorenz                                              Selection.getBegin()));
2174796e72bSAlex Lorenz       return std::move(Occurrences);
2181c0a26bdSAlex Lorenz     }
2191c0a26bdSAlex Lorenz   };
2201c0a26bdSAlex Lorenz 
2211c0a26bdSAlex Lorenz   auto Rule = createRefactoringActionRule<FindOccurrences>(
2221c0a26bdSAlex Lorenz       SourceRangeSelectionRequirement());
2233529a94bSAlex Lorenz 
2243529a94bSAlex Lorenz   RefactoringRuleContext RefContext(Context.Sources);
2253529a94bSAlex Lorenz   SourceLocation Cursor =
2263529a94bSAlex Lorenz       Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID());
2273529a94bSAlex Lorenz   RefContext.setSelectionRange({Cursor, Cursor});
228*6ad0788cSKazu Hirata   std::optional<SymbolOccurrences> Result = findOccurrences(*Rule, RefContext);
2293529a94bSAlex Lorenz 
2303529a94bSAlex Lorenz   ASSERT_FALSE(!Result);
2313529a94bSAlex Lorenz   SymbolOccurrences Occurrences = std::move(*Result);
2323529a94bSAlex Lorenz   EXPECT_EQ(Occurrences.size(), 1u);
2333529a94bSAlex Lorenz   EXPECT_EQ(Occurrences[0].getKind(), SymbolOccurrence::MatchingSymbol);
2343529a94bSAlex Lorenz   EXPECT_EQ(Occurrences[0].getNameRanges().size(), 1u);
2353529a94bSAlex Lorenz   EXPECT_EQ(Occurrences[0].getNameRanges()[0],
2363529a94bSAlex Lorenz             SourceRange(Cursor, Cursor.getLocWithOffset(strlen("test"))));
2373529a94bSAlex Lorenz }
2383529a94bSAlex Lorenz 
TEST_F(RefactoringActionRulesTest,EditorCommandBinding)2390beca4d1SAlex Lorenz TEST_F(RefactoringActionRulesTest, EditorCommandBinding) {
2400beca4d1SAlex Lorenz   const RefactoringDescriptor &Descriptor = ExtractFunction::describe();
2410beca4d1SAlex Lorenz   EXPECT_EQ(Descriptor.Name, "extract-function");
2420beca4d1SAlex Lorenz   EXPECT_EQ(
2430beca4d1SAlex Lorenz       Descriptor.Description,
2440beca4d1SAlex Lorenz       "(WIP action; use with caution!) Extracts code into a new function");
2450beca4d1SAlex Lorenz   EXPECT_EQ(Descriptor.Title, "Extract Function");
2460beca4d1SAlex Lorenz }
2470beca4d1SAlex Lorenz 
2481586fa70SAlex Lorenz } // end anonymous namespace
249