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> 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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/USRFindingAction.cpp
|
||||
Rename/USRLocFinder.cpp
|
||||
SourceSelectionConstraints.cpp
|
||||
|
||||
LINK_LIBS
|
||||
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
|
||||
RecursiveASTVisitorTestExprVisitor.cpp
|
||||
RecursiveASTVisitorTestTypeLocVisitor.cpp
|
||||
RefactoringActionRulesTest.cpp
|
||||
RefactoringCallbacksTest.cpp
|
||||
RefactoringTest.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