[refactor] initial support for refactoring action rules

This patch implements the initial support for refactoring action rules. The
first rule that's supported is a "source change" rule that returns a set of
atomic changes. This patch is based on the ideas presented in my RFC:

http://lists.llvm.org/pipermail/cfe-dev/2017-July/054831.html

The following pieces from the RFC are added by this patch:

- `createRefactoringRule` (known as `apply` in the RFC)
- `requiredSelection` refactoring action rule requirement.
- `selection::SourceSelectionRange` selection constraint.

Differential Revision: https://reviews.llvm.org/D36075

llvm-svn: 311884
This commit is contained in:
Alex Lorenz 2017-08-28 11:12:05 +00:00
parent 0db84863d0
commit 1586fa70a6
13 changed files with 791 additions and 0 deletions

View File

@ -35,6 +35,7 @@ namespace llvm {
template<typename T, unsigned N> class SmallVector;
template<typename T> class SmallVectorImpl;
template<typename T> class Optional;
template <class T> class Expected;
template<typename T>
struct SaveAndRestore;
@ -71,6 +72,9 @@ namespace clang {
using llvm::SmallVectorImpl;
using llvm::SaveAndRestore;
// Error handling.
using llvm::Expected;
// Reference counting.
using llvm::IntrusiveRefCntPtr;
using llvm::IntrusiveRefCntPtrInfo;

View File

@ -46,6 +46,12 @@ public:
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 @@ private:
tooling::Replacements Replaces;
};
using AtomicChanges = std::vector<AtomicChange>;
// Defines specs for applying changes.
struct ApplyChangesSpec {
// If true, cleans up redundant/erroneous code around changed code with

View File

@ -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 <vector>
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<Optional<AtomicChanges>>
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<std::unique_ptr<RefactoringActionRule>>;
} // end namespace tooling
} // end namespace clang
#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULE_H

View File

@ -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 <type_traits>
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 <typename T>
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<selection::traits::IsRequirement<T>::value>::type
* = nullptr) {
return internal::SourceSelectionRequirement<
typename selection::internal::EvaluateSelectionChecker<decltype(
&T::evaluateSelection)>::ArgType,
typename selection::internal::EvaluateSelectionChecker<decltype(
&T::evaluateSelection)>::ReturnType,
T>(Requirement);
}
template <typename T>
void requiredSelection(
const T &,
typename std::enable_if<
!std::is_base_of<selection::Requirement, T>::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

View File

@ -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 <type_traits>
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<Optional<T>>, or
/// \c T otherwise.
template <typename T> struct DropExpectedOptional { using Type = T; };
template <typename T> struct DropExpectedOptional<Expected<Optional<T>>> {
using Type = T;
};
/// The \c requiredSelection refactoring action requirement is represented
/// using this type.
template <typename InputT, typename OutputT, typename RequirementT>
struct SourceSelectionRequirement
: std::enable_if<selection::traits::IsConstraint<InputT>::value &&
selection::traits::IsRequirement<RequirementT>::value,
RequirementBase>::type {
using OutputType = typename DropExpectedOptional<OutputT>::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<Optional<>> which is then unwrapped by the
/// refactoring action rule before passing the value to the refactoring
/// function.
Expected<Optional<OutputType>> evaluate(RefactoringRuleContext &Context) {
Optional<InputT> 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 <typename First, typename... Rest>
struct IsRequirement : std::conditional<IsRequirement<First>::value &&
IsRequirement<Rest...>::value,
std::true_type, std::false_type>::type {
};
template <typename T>
struct IsRequirement<T>
: std::conditional<std::is_base_of<internal::RequirementBase, T>::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

View File

@ -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<T> 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 <typename ResultType, typename... RequirementTypes>
std::unique_ptr<RefactoringActionRule>
createRefactoringRule(Expected<ResultType> (*RefactoringFunction)(
typename RequirementTypes::OutputType...),
const RequirementTypes &... Requirements) {
static_assert(
std::is_base_of<
RefactoringActionRule,
internal::SpecificRefactoringRuleAdapter<ResultType>>::value,
"invalid refactoring result type");
static_assert(traits::IsRequirement<RequirementTypes...>::value,
"invalid refactoring action rule requirement");
return llvm::make_unique<internal::PlainFunctionRule<
ResultType, decltype(RefactoringFunction), RequirementTypes...>>(
RefactoringFunction, std::make_tuple(Requirements...));
}
template <
typename Callable, typename... RequirementTypes,
typename Fn = decltype(&Callable::operator()),
typename ResultType = typename internal::LambdaDeducer<Fn>::ReturnType,
bool IsNonCapturingLambda = std::is_convertible<
Callable,
ResultType (*)(typename RequirementTypes::OutputType...)>::value,
typename = typename std::enable_if<IsNonCapturingLambda>::type>
std::unique_ptr<RefactoringActionRule>
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

View File

@ -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 <type_traits>
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 <typename T> struct SpecificRefactoringRuleAdapter {};
template <>
class SpecificRefactoringRuleAdapter<AtomicChanges>
: public SourceChangeRefactoringRule {
public:
virtual Expected<Optional<AtomicChanges>>
perform(RefactoringRuleContext &Context) = 0;
Expected<Optional<AtomicChanges>>
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 <typename ResultType, typename FunctionType,
typename... RequirementTypes>
class PlainFunctionRule final
: public SpecificRefactoringRuleAdapter<ResultType> {
public:
PlainFunctionRule(FunctionType Function,
std::tuple<RequirementTypes...> &&Requirements)
: Function(Function), Requirements(std::move(Requirements)) {}
Expected<Optional<ResultType>>
perform(RefactoringRuleContext &Context) override {
return performImpl(Context,
llvm::index_sequence_for<RequirementTypes...>());
}
private:
/// Returns \c T when given \c Expected<Optional<T>>, or \c T otherwise.
template <typename T>
static T &&unwrapRequirementResult(Expected<Optional<T>> &&X) {
assert(X && "unexpected diagnostic!");
return std::move(**X);
}
template <typename T> 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 <typename FirstT, typename... RestT>
static Optional<llvm::Error> findErrorNone(FirstT &First, RestT &... Rest) {
Optional<llvm::Error> Result = takeErrorNone(First);
if (Result)
return Result;
return findErrorNone(Rest...);
}
static Optional<llvm::Error> findErrorNone() { return None; }
template <typename T> static Optional<llvm::Error> takeErrorNone(T &) {
return None;
}
template <typename T>
static Optional<llvm::Error> takeErrorNone(Expected<Optional<T>> &Diag) {
if (!Diag)
return std::move(Diag.takeError());
if (!*Diag)
return llvm::Error::success(); // Initiation failed without a diagnostic.
return None;
}
template <size_t... Is>
Expected<Optional<ResultType>> performImpl(RefactoringRuleContext &Context,
llvm::index_sequence<Is...>) {
// Initiate the operation.
auto Values =
std::make_tuple(std::get<Is>(Requirements).evaluate(Context)...);
Optional<llvm::Error> InitiationFailure =
findErrorNone(std::get<Is>(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<Is>(Values)))...);
}
FunctionType Function;
std::tuple<RequirementTypes...> Requirements;
};
/// Used to deduce the refactoring result type for the lambda that passed into
/// createRefactoringRule.
template <typename T> struct LambdaDeducer;
template <typename T, typename R, typename... Args>
struct LambdaDeducer<R (T::*)(Args...) const> {
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

View File

@ -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

View File

@ -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 <type_traits>
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<SourceSelectionRange>
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<S> , 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 <typename T> struct IsConstraint : public std::false_type {};
} // end namespace traits
namespace internal {
template <typename T> struct EvaluateSelectionChecker : std::false_type {};
template <typename T, typename R, typename A>
struct EvaluateSelectionChecker<R (T::*)(A) const> : std::true_type {
using ReturnType = R;
using ArgType = A;
};
template <typename T> 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 <typename T> internal::Identity<T> identity() {
static_assert(
traits::IsConstraint<T>::value,
"selection::identity can be used with selection constraints only");
return internal::Identity<T>();
}
namespace traits {
template <>
struct IsConstraint<SourceSelectionRange> : public std::true_type {};
/// A type trait that returns true iff \c T is a valid selection requirement.
template <typename T>
struct IsRequirement
: std::conditional<
std::is_base_of<Requirement, T>::value &&
internal::EvaluateSelectionChecker<decltype(
&T::evaluateSelection)>::value &&
IsConstraint<typename internal::EvaluateSelectionChecker<decltype(
&T::evaluateSelection)>::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

View File

@ -8,6 +8,7 @@ add_clang_library(clangToolingRefactor
Rename/USRFinder.cpp
Rename/USRFindingAction.cpp
Rename/USRLocFinder.cpp
SourceSelectionConstraints.cpp
LINK_LIBS
clangAST

View File

@ -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>
SourceSelectionRange::evaluate(RefactoringRuleContext &Context) {
SourceRange R = Context.getSelectionRange();
if (R.isInvalid())
return None;
return SourceSelectionRange(Context.getSources(), R);
}

View File

@ -25,6 +25,7 @@ add_clang_unittest(ToolingTests
RecursiveASTVisitorTestDeclVisitor.cpp
RecursiveASTVisitorTestExprVisitor.cpp
RecursiveASTVisitorTestTypeLocVisitor.cpp
RefactoringActionRulesTest.cpp
RefactoringCallbacksTest.cpp
RefactoringTest.cpp
ReplacementsYamlTest.cpp

View File

@ -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<Optional<AtomicChanges>>
createReplacements(const std::unique_ptr<RefactoringActionRule> &Rule,
RefactoringRuleContext &Context) {
return cast<SourceChangeRefactoringRule>(*Rule).createSourceReplacements(
Context);
}
TEST_F(RefactoringActionRulesTest, MyFirstRefactoringRule) {
auto ReplaceAWithB =
[](std::pair<selection::SourceSelectionRange, int> Selection)
-> Expected<AtomicChanges> {
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<selection::SourceSelectionRange, int>
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 RefContext(Context.Sources);
SourceLocation Cursor =
Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID())
.getLocWithOffset(10);
RefContext.setSelectionRange({Cursor, Cursor});
Expected<Optional<AtomicChanges>> ErrorOrResult =
createReplacements(Rule, RefContext);
ASSERT_FALSE(!ErrorOrResult);
ASSERT_FALSE(!*ErrorOrResult);
AtomicChanges Result = std::move(**ErrorOrResult);
ASSERT_EQ(Result.size(), 1u);
std::string YAMLString =
const_cast<AtomicChange &>(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 RefContext(Context.Sources);
Expected<Optional<AtomicChanges>> ErrorOrResult =
createReplacements(Rule, RefContext);
ASSERT_FALSE(!ErrorOrResult);
Optional<AtomicChanges> Value = std::move(*ErrorOrResult);
EXPECT_TRUE(!Value);
}
}
TEST_F(RefactoringActionRulesTest, ReturnError) {
Expected<AtomicChanges> (*Func)(selection::SourceSelectionRange) =
[](selection::SourceSelectionRange) -> Expected<AtomicChanges> {
return llvm::make_error<llvm::StringError>(
"Error", std::make_error_code(std::errc::bad_message));
};
auto Rule = createRefactoringRule(
Func, requiredSelection(
selection::identity<selection::SourceSelectionRange>()));
RefactoringRuleContext RefContext(Context.Sources);
SourceLocation Cursor =
Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID());
RefContext.setSelectionRange({Cursor, Cursor});
Expected<Optional<AtomicChanges>> Result =
createReplacements(Rule, RefContext);
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 RefContext(Context.Sources);
class SelectionRequirement : public selection::Requirement {
public:
Expected<Optional<int>>
evaluateSelection(selection::SourceSelectionRange Selection) const {
return llvm::make_error<llvm::StringError>(
"bad selection", std::make_error_code(std::errc::bad_message));
}
};
auto Rule = createRefactoringRule(
[](int) -> Expected<AtomicChanges> {
llvm::report_fatal_error("Should not run!");
},
requiredSelection(SelectionRequirement()));
SourceLocation Cursor =
Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID());
RefContext.setSelectionRange({Cursor, Cursor});
Expected<Optional<AtomicChanges>> Result =
createReplacements(Rule, RefContext);
ASSERT_TRUE(!Result);
std::string Message;
llvm::handleAllErrors(Result.takeError(), [&](llvm::StringError &Error) {
Message = Error.getMessage();
});
EXPECT_EQ(Message, "bad selection");
}
} // end anonymous namespace