forked from OSchip/llvm-project
[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:
parent
0db84863d0
commit
1586fa70a6
|
@ -35,6 +35,7 @@ namespace llvm {
|
||||||
template<typename T, unsigned N> class SmallVector;
|
template<typename T, unsigned N> class SmallVector;
|
||||||
template<typename T> class SmallVectorImpl;
|
template<typename T> class SmallVectorImpl;
|
||||||
template<typename T> class Optional;
|
template<typename T> class Optional;
|
||||||
|
template <class T> class Expected;
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
struct SaveAndRestore;
|
struct SaveAndRestore;
|
||||||
|
@ -71,6 +72,9 @@ namespace clang {
|
||||||
using llvm::SmallVectorImpl;
|
using llvm::SmallVectorImpl;
|
||||||
using llvm::SaveAndRestore;
|
using llvm::SaveAndRestore;
|
||||||
|
|
||||||
|
// Error handling.
|
||||||
|
using llvm::Expected;
|
||||||
|
|
||||||
// Reference counting.
|
// Reference counting.
|
||||||
using llvm::IntrusiveRefCntPtr;
|
using llvm::IntrusiveRefCntPtr;
|
||||||
using llvm::IntrusiveRefCntPtrInfo;
|
using llvm::IntrusiveRefCntPtrInfo;
|
||||||
|
|
|
@ -46,6 +46,12 @@ public:
|
||||||
AtomicChange(llvm::StringRef FilePath, llvm::StringRef Key)
|
AtomicChange(llvm::StringRef FilePath, llvm::StringRef Key)
|
||||||
: Key(Key), FilePath(FilePath) {}
|
: 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.
|
/// \brief Returns the atomic change as a YAML string.
|
||||||
std::string toYAMLString();
|
std::string toYAMLString();
|
||||||
|
|
||||||
|
@ -130,6 +136,8 @@ private:
|
||||||
tooling::Replacements Replaces;
|
tooling::Replacements Replaces;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using AtomicChanges = std::vector<AtomicChange>;
|
||||||
|
|
||||||
// Defines specs for applying changes.
|
// Defines specs for applying changes.
|
||||||
struct ApplyChangesSpec {
|
struct ApplyChangesSpec {
|
||||||
// If true, cleans up redundant/erroneous code around changed code with
|
// If true, cleans up redundant/erroneous code around changed code with
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -8,6 +8,7 @@ add_clang_library(clangToolingRefactor
|
||||||
Rename/USRFinder.cpp
|
Rename/USRFinder.cpp
|
||||||
Rename/USRFindingAction.cpp
|
Rename/USRFindingAction.cpp
|
||||||
Rename/USRLocFinder.cpp
|
Rename/USRLocFinder.cpp
|
||||||
|
SourceSelectionConstraints.cpp
|
||||||
|
|
||||||
LINK_LIBS
|
LINK_LIBS
|
||||||
clangAST
|
clangAST
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ add_clang_unittest(ToolingTests
|
||||||
RecursiveASTVisitorTestDeclVisitor.cpp
|
RecursiveASTVisitorTestDeclVisitor.cpp
|
||||||
RecursiveASTVisitorTestExprVisitor.cpp
|
RecursiveASTVisitorTestExprVisitor.cpp
|
||||||
RecursiveASTVisitorTestTypeLocVisitor.cpp
|
RecursiveASTVisitorTestTypeLocVisitor.cpp
|
||||||
|
RefactoringActionRulesTest.cpp
|
||||||
RefactoringCallbacksTest.cpp
|
RefactoringCallbacksTest.cpp
|
||||||
RefactoringTest.cpp
|
RefactoringTest.cpp
|
||||||
ReplacementsYamlTest.cpp
|
ReplacementsYamlTest.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<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
|
Loading…
Reference in New Issue