Index: include/clang/Basic/LLVM.h =================================================================== --- include/clang/Basic/LLVM.h +++ include/clang/Basic/LLVM.h @@ -35,6 +35,7 @@ template class SmallVector; template class SmallVectorImpl; template class Optional; + template class Expected; template struct SaveAndRestore; @@ -71,6 +72,9 @@ using llvm::SmallVectorImpl; using llvm::SaveAndRestore; + // Error handling. + using llvm::Expected; + // Reference counting. using llvm::IntrusiveRefCntPtr; using llvm::IntrusiveRefCntPtrInfo; Index: include/clang/Tooling/Refactoring/AtomicChange.h =================================================================== --- include/clang/Tooling/Refactoring/AtomicChange.h +++ include/clang/Tooling/Refactoring/AtomicChange.h @@ -46,6 +46,12 @@ AtomicChange(llvm::StringRef FilePath, llvm::StringRef Key) : Key(Key), FilePath(FilePath) {} + AtomicChange(AtomicChange &&) = default; + AtomicChange(const AtomicChange &) = default; + + AtomicChange &operator=(AtomicChange &&) = default; + AtomicChange &operator=(const AtomicChange &) = default; + /// \brief Returns the atomic change as a YAML string. std::string toYAMLString(); @@ -130,6 +136,8 @@ tooling::Replacements Replaces; }; +using AtomicChanges = std::vector; + // Defines specs for applying changes. struct ApplyChangesSpec { // If true, cleans up redundant/erroneous code around changed code with Index: include/clang/Tooling/Refactoring/RefactoringActionRule.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/RefactoringActionRule.h @@ -0,0 +1,70 @@ +//===--- RefactoringActionRule.h - Clang refactoring library -------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULE_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULE_H + +#include "clang/Basic/LLVM.h" +#include "clang/Tooling/Refactoring/AtomicChange.h" +#include "llvm/Support/Error.h" +#include + +namespace clang { +namespace tooling { + +class RefactoringRuleContext; + +/// A common refactoring action rule interface. +class RefactoringActionRule { +public: + enum RuleKind { SourceChangeRefactoringRuleKind }; + + RuleKind getRuleKind() const { return Kind; } + + virtual ~RefactoringActionRule() {} + +protected: + RefactoringActionRule(RuleKind Kind) : Kind(Kind) {} + +private: + RuleKind Kind; +}; + +/// A type of refactoring action rule that produces source replacements in the +/// form of atomic changes. +/// +/// This action rule is typically used for local refactorings that replace +/// source in a single AST unit. +class SourceChangeRefactoringRule : public RefactoringActionRule { +public: + SourceChangeRefactoringRule() + : RefactoringActionRule(SourceChangeRefactoringRuleKind) {} + + /// Initiates and performs a refactoring action that modifies the sources. + /// + /// The specific rule must return an llvm::Error with a DiagnosticError + /// payload or None when the refactoring action couldn't be initiated/ + /// performed, or \c AtomicChanges when the action was performed successfully. + virtual Expected> + createSourceReplacements(RefactoringRuleContext &Context) = 0; + + static bool classof(const RefactoringActionRule *Rule) { + return Rule->getRuleKind() == SourceChangeRefactoringRuleKind; + } +}; + +/// A set of refactoring action rules that should have unique initiation +/// requirements. +using RefactoringActionRules = + std::vector>; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULE_H Index: include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h @@ -0,0 +1,58 @@ +//===--- RefactoringActionRuleRequirements.h - Clang refactoring library --===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULE_REQUIREMENTS_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULE_REQUIREMENTS_H + +#include "clang/Tooling/Refactoring/RefactoringActionRuleRequirementsInternal.h" +#include "llvm/Support/Error.h" +#include + +namespace clang { +namespace tooling { +namespace refactoring_action_rules { + +/// Creates a selection requirement from the given requirement. +/// +/// Requirements must subclass \c selection::Requirement and implement +/// evaluateSelection member function. +template +internal::SourceSelectionRequirement< + typename selection::internal::EvaluateSelectionChecker< + decltype(&T::evaluateSelection)>::ArgType, + typename selection::internal::EvaluateSelectionChecker< + decltype(&T::evaluateSelection)>::ReturnType, + T> +requiredSelection( + const T &Requirement, + typename std::enable_if::value>::type + * = nullptr) { + return internal::SourceSelectionRequirement< + typename selection::internal::EvaluateSelectionChecker::ArgType, + typename selection::internal::EvaluateSelectionChecker::ReturnType, + T>(Requirement); +} + +template +void requiredSelection( + const T &, + typename std::enable_if< + !std::is_base_of::value>::type * = nullptr) { + static_assert( + sizeof(T) && false, + "selection requirement must be a class derived from Requirement"); +} + +} // end namespace refactoring_action_rules +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULE_REQUIREMENTS_H Index: include/clang/Tooling/Refactoring/RefactoringActionRuleRequirementsInternal.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/RefactoringActionRuleRequirementsInternal.h @@ -0,0 +1,90 @@ +//===--- RefactoringActionRuleRequirementsInternal.h - --------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_REQUIREMENTS_INTERNAL_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_REQUIREMENTS_INTERNAL_H + +#include "clang/Basic/LLVM.h" +#include "clang/Tooling/Refactoring/RefactoringRuleContext.h" +#include "clang/Tooling/Refactoring/SourceSelectionConstraints.h" +#include + +namespace clang { +namespace tooling { +namespace refactoring_action_rules { +namespace internal { + +/// A base class for any requirement. Used by the \c IsRequirement trait to +/// determine if a class is a valid requirement. +struct RequirementBase {}; + +/// Defines a type alias of type \T when given \c Expected>, or +/// \c T otherwise. +template struct DropExpectedOptional { using Type = T; }; + +template struct DropExpectedOptional>> { + using Type = T; +}; + +/// The \c requiredSelection refactoring action requirement is represented +/// using this type. +template +struct SourceSelectionRequirement + : std::enable_if::value && + selection::traits::IsRequirement::value, + RequirementBase>::type { + using OutputType = typename DropExpectedOptional::Type; + + SourceSelectionRequirement(const RequirementT &Requirement) + : Requirement(Requirement) {} + + /// Evaluates the action rule requirement by ensuring that both the selection + /// constraint and the selection requirement can be evaluated with the given + /// context. + /// + /// \returns None if the selection constraint is not evaluated successfully, + /// Error if the selection requirement is not evaluated successfully or + /// an OutputT if the selection requirement was successfully. The OutpuT + /// value is wrapped in Expected> which is then unwrapped by the + /// refactoring action rule before passing the value to the refactoring + /// function. + Expected> evaluate(RefactoringRuleContext &Context) { + Optional Value = InputT::evaluate(Context); + if (!Value) + return None; + return std::move(Requirement.evaluateSelection(*Value)); + } + +private: + const RequirementT Requirement; +}; + +} // end namespace internal + +namespace traits { + +/// A type trait that returns true iff the given type is a valid rule +/// requirement. +template +struct IsRequirement : std::conditional::value && + IsRequirement::value, + std::true_type, std::false_type>::type { +}; + +template +struct IsRequirement + : std::conditional::value, + std::true_type, std::false_type>::type {}; + +} // end namespace traits +} // end namespace refactoring_action_rules +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_REQUIREMENTS_INTERNAL_H Index: include/clang/Tooling/Refactoring/RefactoringActionRules.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/RefactoringActionRules.h @@ -0,0 +1,76 @@ +//===--- RefactoringActionRules.h - Clang refactoring library -------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULES_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULES_H + +#include "clang/Tooling/Refactoring/RefactoringActionRule.h" +#include "clang/Tooling/Refactoring/RefactoringActionRulesInternal.h" + +namespace clang { +namespace tooling { +namespace refactoring_action_rules { + +/// Creates a new refactoring action rule that invokes the given function once +/// all of the requirements are satisfied. The values produced during the +/// evaluation of requirements are passed to the given function (in the order of +/// requirements). +/// +/// \param RefactoringFunction the function that will perform the refactoring +/// once the requirements are satisfied. The function must return a valid +/// refactoring result type wrapped in an \c Expected type. The following result +/// types are currently supported: +/// +/// - AtomicChanges: the refactoring function will be used to create source +/// replacements. +/// +/// \param Requirements a set of rule requirements that have to be satisfied. +/// Each requirement must be a valid requirement, i.e. the value of +/// \c traits::IsRequirement must be true. The following requirements are +/// currently supported: +/// +/// - requiredSelection: The refactoring function won't be invoked unless the +/// given selection requirement is satisfied. +template +std::unique_ptr +createRefactoringRule(Expected (*RefactoringFunction)( + typename RequirementTypes::OutputType...), + const RequirementTypes &... Requirements) { + static_assert( + std::is_base_of< + RefactoringActionRule, + internal::SpecificRefactoringRuleAdapter>::value, + "invalid refactoring result type"); + static_assert(traits::IsRequirement::value, + "invalid refactoring action rule requirement"); + return llvm::make_unique>( + RefactoringFunction, std::make_tuple(Requirements...)); +} + +template < + typename Callable, typename... RequirementTypes, + typename Fn = decltype(&Callable::operator()), + typename ResultType = typename internal::LambdaDeducer::ReturnType, + bool IsNonCapturingLambda = std::is_convertible< + Callable, + ResultType (*)(typename RequirementTypes::OutputType...)>::value, + typename = typename std::enable_if::type> +std::unique_ptr +createRefactoringRule(const Callable &C, + const RequirementTypes &... Requirements) { + ResultType (*Func)(typename RequirementTypes::OutputType...) = C; + return createRefactoringRule(Func, Requirements...); +} + +} // end namespace refactoring_action_rules +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULES_H Index: include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h @@ -0,0 +1,133 @@ +//===--- RefactoringActionRulesInternal.h - Clang refactoring library -----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULES_INTERNAL_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULES_INTERNAL_H + +#include "clang/Basic/LLVM.h" +#include "clang/Tooling/Refactoring/RefactoringActionRule.h" +#include "clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h" +#include "clang/Tooling/Refactoring/RefactoringRuleContext.h" +#include "llvm/Support/Error.h" +#include + +namespace clang { +namespace tooling { +namespace refactoring_action_rules { +namespace internal { + +/// A wrapper around a specific refactoring action rule that calls a generic +/// 'perform' method from the specific refactoring method. +template struct SpecificRefactoringRuleAdapter {}; + +template <> +class SpecificRefactoringRuleAdapter + : public SourceChangeRefactoringRule { +public: + virtual Expected> + perform(RefactoringRuleContext &Context) = 0; + + Expected> + createSourceReplacements(RefactoringRuleContext &Context) final override { + return perform(Context); + } +}; + +/// A specialized refactoring action rule that calls the stored function once +/// all the of the requirements are fullfilled. The values produced during the +/// evaluation of requirements are passed to the stored function. +template +class PlainFunctionRule final + : public SpecificRefactoringRuleAdapter { +public: + PlainFunctionRule(FunctionType Function, + std::tuple &&Requirements) + : Function(Function), Requirements(std::move(Requirements)) {} + + Expected> + perform(RefactoringRuleContext &Context) override { + return performImpl(Context, + llvm::index_sequence_for()); + } + +private: + /// Returns \c T when given \c Expected>, or \c T otherwise. + template + static T &&unwrapRequirementResult(Expected> &&X) { + assert(X && "unexpected diagnostic!"); + return std::move(**X); + } + template static T &&unwrapRequirementResult(T &&X) { + return std::move(X); + } + + /// Scans the tuple and returns a \c PartialDiagnosticAt + /// from the first invalid \c DiagnosticOr value. Returns \c None if all + /// values are valid. + template + static Optional findErrorNone(FirstT &First, RestT &... Rest) { + Optional Result = takeErrorNone(First); + if (Result) + return Result; + return findErrorNone(Rest...); + } + + static Optional findErrorNone() { return None; } + + template static Optional takeErrorNone(T &) { + return None; + } + + template + static Optional takeErrorNone(Expected> &Diag) { + if (!Diag) + return std::move(Diag.takeError()); + if (!*Diag) + return llvm::Error::success(); // Initiation failed without a diagnostic. + return None; + } + + template + Expected> performImpl(RefactoringRuleContext &Context, + llvm::index_sequence) { + // Initiate the operation. + auto Values = + std::make_tuple(std::get(Requirements).evaluate(Context)...); + Optional InitiationFailure = + findErrorNone(std::get(Values)...); + if (InitiationFailure) { + llvm::Error Error = std::move(*InitiationFailure); + if (!Error) + return None; + return std::move(Error); + } + // Perform the operation. + return Function( + unwrapRequirementResult(std::move(std::get(Values)))...); + } + + FunctionType Function; + std::tuple Requirements; +}; + +/// Used to deduce the refactoring result type for the lambda that passed into +/// createRefactoringRule. +template struct LambdaDeducer; +template +struct LambdaDeducer { + using ReturnType = R; +}; + +} // end namespace internal +} // end namespace refactoring_action_rules +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULES_INTERNAL_H Index: include/clang/Tooling/Refactoring/RefactoringRuleContext.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/RefactoringRuleContext.h @@ -0,0 +1,53 @@ +//===--- RefactoringRuleContext.h - Clang refactoring library -------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_RULE_CONTEXT_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_RULE_CONTEXT_H + +#include "clang/Basic/SourceManager.h" + +namespace clang { +namespace tooling { + +/// The refactoring rule context stores all of the inputs that might be needed +/// by a refactoring action rule. It can create the specialized +/// \c ASTRefactoringOperation or \c PreprocessorRefactoringOperation values +/// that can be used by the refactoring action rules. +/// +/// The following inputs are stored by the operation: +/// +/// - SourceManager: a reference to a valid source manager. +/// +/// - SelectionRange: an optional source selection ranges that can be used +/// to represent a selection in an editor. +class RefactoringRuleContext { +public: + RefactoringRuleContext(const SourceManager &SM) : SM(SM) {} + + const SourceManager &getSources() const { return SM; } + + /// Returns the current source selection range as set by the + /// refactoring engine. Can be invalid. + SourceRange getSelectionRange() const { return SelectionRange; } + + void setSelectionRange(SourceRange R) { SelectionRange = R; } + +private: + /// The source manager for the translation unit / file on which a refactoring + /// action might operate on. + const SourceManager &SM; + /// An optional source selection range that's commonly used to represent + /// a selection in an editor. + SourceRange SelectionRange; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_RULE_CONTEXT_H Index: include/clang/Tooling/Refactoring/SourceSelectionConstraints.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/SourceSelectionConstraints.h @@ -0,0 +1,109 @@ +//===--- SourceSelectionConstraints.h - Clang refactoring library ---------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_SOURCE_SELECTION_CONSTRAINTS_H +#define LLVM_CLANG_TOOLING_REFACTOR_SOURCE_SELECTION_CONSTRAINTS_H + +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include + +namespace clang { +namespace tooling { + +class RefactoringRuleContext; + +namespace selection { + +/// This constraint is satisfied when any portion of the source text is +/// selected. It can be used to obtain the raw source selection range. +struct SourceSelectionRange { + SourceSelectionRange(const SourceManager &SM, SourceRange Range) + : SM(SM), Range(Range) {} + + const SourceManager &getSources() const { return SM; } + SourceRange getRange() const { return Range; } + + static Optional + evaluate(RefactoringRuleContext &Context); + +private: + const SourceManager &SM; + SourceRange Range; +}; + +/// A custom selection requirement. +class Requirement { + /// Subclasses must implement 'T evaluateSelection(SelectionConstraint) const' + /// member function. \c T is used to determine the return type that is + /// passed to the refactoring rule's function. + /// If T is \c DiagnosticOr , then \c S is passed to the rule's function + /// using move semantics. + /// Otherwise, T is passed to the function directly using move semantics. + /// + /// The different return type rules allow refactoring actions to fail + /// initiation when the relevant portions of AST aren't selected. +}; + +namespace traits { + +/// A type trait that returns true iff the given type is a valid selection +/// constraint. +template struct IsConstraint : public std::false_type {}; + +} // end namespace traits + +namespace internal { + +template struct EvaluateSelectionChecker : std::false_type {}; + +template +struct EvaluateSelectionChecker : std::true_type { + using ReturnType = R; + using ArgType = A; +}; + +template class Identity : public Requirement { +public: + T evaluateSelection(T Value) const { return std::move(Value); } +}; + +} // end namespace internal + +/// A identity function that returns the given selection constraint is provided +/// for convenience, as it can be passed to \c requiredSelection directly. +template internal::Identity identity() { + static_assert( + traits::IsConstraint::value, + "selection::identity can be used with selection constraints only"); + return internal::Identity(); +} + +namespace traits { + +template <> +struct IsConstraint : public std::true_type {}; + +/// A type trait that returns true iff \c T is a valid selection requirement. +template +struct IsRequirement + : std::conditional< + std::is_base_of::value && + internal::EvaluateSelectionChecker::value && + IsConstraint::ArgType>::value, + std::true_type, std::false_type>::type {}; + +} // end namespace traits +} // end namespace selection +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_SOURCE_SELECTION_CONSTRAINTS_H Index: lib/Tooling/Refactoring/CMakeLists.txt =================================================================== --- lib/Tooling/Refactoring/CMakeLists.txt +++ lib/Tooling/Refactoring/CMakeLists.txt @@ -8,6 +8,7 @@ Rename/USRFinder.cpp Rename/USRFindingAction.cpp Rename/USRLocFinder.cpp + SourceSelectionConstraints.cpp LINK_LIBS clangAST Index: lib/Tooling/Refactoring/SourceSelectionConstraints.cpp =================================================================== --- /dev/null +++ lib/Tooling/Refactoring/SourceSelectionConstraints.cpp @@ -0,0 +1,23 @@ +//===--- SourceSelectionConstraints.cpp - Evaluate selection constraints --===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/SourceSelectionConstraints.h" +#include "clang/Tooling/Refactoring/RefactoringRuleContext.h" + +using namespace clang; +using namespace tooling; +using namespace selection; + +Optional +SourceSelectionRange::evaluate(RefactoringRuleContext &Context) { + SourceRange R = Context.getSelectionRange(); + if (R.isInvalid()) + return None; + return SourceSelectionRange(Context.getSources(), R); +} Index: unittests/Tooling/CMakeLists.txt =================================================================== --- unittests/Tooling/CMakeLists.txt +++ unittests/Tooling/CMakeLists.txt @@ -25,6 +25,7 @@ RecursiveASTVisitorTestDeclVisitor.cpp RecursiveASTVisitorTestExprVisitor.cpp RecursiveASTVisitorTestTypeLocVisitor.cpp + RefactoringActionRulesTest.cpp RefactoringCallbacksTest.cpp RefactoringTest.cpp ReplacementsYamlTest.cpp Index: unittests/Tooling/RefactoringActionRulesTest.cpp =================================================================== --- /dev/null +++ unittests/Tooling/RefactoringActionRulesTest.cpp @@ -0,0 +1,165 @@ +//===- unittest/Tooling/RefactoringTestActionRulesTest.cpp ----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ReplacementTest.h" +#include "RewriterTestContext.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Refactoring/RefactoringActionRules.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/Errc.h" +#include "gtest/gtest.h" + +using namespace clang; +using namespace tooling; +using namespace refactoring_action_rules; + +namespace { + +class RefactoringActionRulesTest : public ::testing::Test { +protected: + void SetUp() override { + Context.Sources.setMainFileID( + Context.createInMemoryFile("input.cpp", DefaultCode)); + } + + RewriterTestContext Context; + std::string DefaultCode = std::string(100, 'a'); +}; + +Expected> +createReplacements(const std::unique_ptr &Rule, + RefactoringRuleContext &Context) { + return cast(*Rule).createSourceReplacements( + Context); +} + +TEST_F(RefactoringActionRulesTest, MyFirstRefactoringRule) { + auto ReplaceAWithB = + [](std::pair Selection) + -> Expected { + const SourceManager &SM = Selection.first.getSources(); + SourceLocation Loc = Selection.first.getRange().getBegin().getLocWithOffset( + Selection.second); + AtomicChange Change(SM, Loc); + llvm::Error E = Change.replace(SM, Loc, 1, "b"); + if (E) + return std::move(E); + return AtomicChanges{Change}; + }; + class SelectionRequirement : public selection::Requirement { + public: + std::pair + evaluateSelection(selection::SourceSelectionRange Selection) const { + return std::make_pair(Selection, 20); + } + }; + auto Rule = createRefactoringRule(ReplaceAWithB, + requiredSelection(SelectionRequirement())); + + // When the requirements are satisifed, the rule's function must be invoked. + { + RefactoringRuleContext Operation(Context.Sources); + SourceLocation Cursor = + Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID()) + .getLocWithOffset(10); + Operation.setSelectionRange({Cursor, Cursor}); + + Expected> ErrorOrResult = + createReplacements(Rule, Operation); + ASSERT_FALSE(!ErrorOrResult); + ASSERT_FALSE(!*ErrorOrResult); + AtomicChanges Result = std::move(**ErrorOrResult); + ASSERT_EQ(Result.size(), 1u); + std::string YAMLString = + const_cast(Result[0]).toYAMLString(); + + ASSERT_STREQ("---\n" + "Key: 'input.cpp:30'\n" + "FilePath: input.cpp\n" + "Error: ''\n" + "InsertedHeaders: \n" + "RemovedHeaders: \n" + "Replacements: \n" // Extra whitespace here! + " - FilePath: input.cpp\n" + " Offset: 30\n" + " Length: 1\n" + " ReplacementText: b\n" + "...\n", + YAMLString.c_str()); + } + + // When one of the requirements is not satisfied, perform should return either + // None or a valid diagnostic. + { + RefactoringRuleContext Operation(Context.Sources); + Expected> ErrorOrResult = + createReplacements(Rule, Operation); + + ASSERT_FALSE(!ErrorOrResult); + Optional Value = std::move(*ErrorOrResult); + EXPECT_TRUE(!Value); + } +} + +TEST_F(RefactoringActionRulesTest, ReturnError) { + Expected (*Func)(selection::SourceSelectionRange) = + [](selection::SourceSelectionRange) -> Expected { + return llvm::make_error( + "Error", std::make_error_code(std::errc::bad_message)); + }; + auto Rule = createRefactoringRule( + Func, requiredSelection( + selection::identity())); + + RefactoringRuleContext Operation(Context.Sources); + SourceLocation Cursor = + Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID()); + Operation.setSelectionRange({Cursor, Cursor}); + Expected> Result = + createReplacements(Rule, Operation); + + ASSERT_TRUE(!Result); + std::string Message; + llvm::handleAllErrors(Result.takeError(), [&](llvm::StringError &Error) { + Message = Error.getMessage(); + }); + EXPECT_EQ(Message, "Error"); +} + +TEST_F(RefactoringActionRulesTest, ReturnInitiationDiagnostic) { + RefactoringRuleContext Operation(Context.Sources); + class SelectionRequirement : public selection::Requirement { + public: + Expected> + evaluateSelection(selection::SourceSelectionRange Selection) const { + return llvm::make_error( + "bad selection", std::make_error_code(std::errc::bad_message)); + } + }; + auto Rule = createRefactoringRule( + [](int) -> Expected { + llvm::report_fatal_error("Should not run!"); + }, + requiredSelection(SelectionRequirement())); + + SourceLocation Cursor = + Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID()); + Operation.setSelectionRange({Cursor, Cursor}); + Expected> Result = + createReplacements(Rule, Operation); + + ASSERT_TRUE(!Result); + std::string Message; + llvm::handleAllErrors(Result.takeError(), [&](llvm::StringError &Error) { + Message = Error.getMessage(); + }); + EXPECT_EQ(Message, "bad selection"); +} + +} // end anonymous namespace