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