namespace clang {
namespace tooling {
+class RefactoringOptionVisitor;
class RefactoringResultConsumer;
class RefactoringRuleContext;
/// Returns true when the rule has a source selection requirement that has
/// to be fullfilled before refactoring can be performed.
virtual bool hasSelectionRequirement() = 0;
+
+ /// Traverses each refactoring option used by the rule and invokes the
+ /// \c visit callback in the consumer for each option.
+ ///
+ /// Options are visited in the order of use, e.g. if a rule has two
+ /// requirements that use options, the options from the first requirement
+ /// are visited before the options in the second requirement.
+ virtual void visitRefactoringOptions(RefactoringOptionVisitor &Visitor) = 0;
};
} // end namespace tooling
#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULE_REQUIREMENTS_H
#include "clang/Basic/LLVM.h"
+#include "clang/Tooling/Refactoring/RefactoringOption.h"
#include "clang/Tooling/Refactoring/RefactoringRuleContext.h"
#include "llvm/Support/Error.h"
#include <type_traits>
}
};
+/// A base class for any requirement that requires some refactoring options.
+class RefactoringOptionsRequirement : public RefactoringActionRuleRequirement {
+public:
+ virtual ~RefactoringOptionsRequirement() {}
+
+ /// Returns the set of refactoring options that are used when evaluating this
+ /// requirement.
+ virtual ArrayRef<std::shared_ptr<RefactoringOption>>
+ getRefactoringOptions() const = 0;
+};
+
+/// A requirement that evaluates to the value of the given \c OptionType when
+/// the \c OptionType is a required option. When the \c OptionType is an
+/// optional option, the requirement will evaluate to \c None if the option is
+/// not specified or to an appropriate value otherwise.
+template <typename OptionType>
+class OptionRequirement : public RefactoringOptionsRequirement {
+public:
+ OptionRequirement() : Opt(createRefactoringOption<OptionType>()) {}
+
+ ArrayRef<std::shared_ptr<RefactoringOption>>
+ getRefactoringOptions() const final override {
+ return static_cast<const std::shared_ptr<RefactoringOption> &>(Opt);
+ }
+
+ Expected<typename OptionType::ValueType>
+ evaluate(RefactoringRuleContext &) const {
+ return Opt->getValue();
+ }
+
+private:
+ /// The partially-owned option.
+ ///
+ /// The ownership of the option is shared among the different requirements
+ /// because the same option can be used by multiple rules in one refactoring
+ /// action.
+ std::shared_ptr<OptionType> Opt;
+};
+
} // end namespace tooling
} // end namespace clang
inline llvm::Error findError() { return llvm::Error::success(); }
+inline void ignoreError() {}
+
+template <typename FirstT, typename... RestT>
+void ignoreError(Expected<FirstT> &First, Expected<RestT> &... Rest) {
+ if (!First)
+ llvm::consumeError(First.takeError());
+ ignoreError(Rest...);
+}
+
/// Scans the tuple and returns a valid \c Error if any of the values are
/// invalid.
template <typename FirstT, typename... RestT>
llvm::Error findError(Expected<FirstT> &First, Expected<RestT> &... Rest) {
- if (!First)
+ if (!First) {
+ ignoreError(Rest...);
return First.takeError();
+ }
return findError(Rest...);
}
RuleType((*std::get<Is>(Values))...).invoke(Consumer, Context);
}
+inline void visitRefactoringOptionsImpl(RefactoringOptionVisitor &) {}
+
+/// Scans the list of requirements in a rule and visits all the refactoring
+/// options that are used by all the requirements.
+template <typename FirstT, typename... RestT>
+void visitRefactoringOptionsImpl(RefactoringOptionVisitor &Visitor,
+ const FirstT &First, const RestT &... Rest) {
+ struct OptionGatherer {
+ RefactoringOptionVisitor &Visitor;
+
+ void operator()(const RefactoringOptionsRequirement &Requirement) {
+ for (const auto &Option : Requirement.getRefactoringOptions())
+ Option->passToVisitor(Visitor);
+ }
+ void operator()(const RefactoringActionRuleRequirement &) {}
+ };
+ (OptionGatherer{Visitor})(First);
+ return visitRefactoringOptionsImpl(Visitor, Rest...);
+}
+
+template <typename... RequirementTypes, size_t... Is>
+void visitRefactoringOptions(
+ RefactoringOptionVisitor &Visitor,
+ const std::tuple<RequirementTypes...> &Requirements,
+ llvm::index_sequence<Is...>) {
+ visitRefactoringOptionsImpl(Visitor, std::get<Is>(Requirements)...);
+}
+
/// A type trait that returns true when the given type list has at least one
/// type whose base is the given base type.
template <typename Base, typename First, typename... Rest>
RequirementTypes...>::value;
}
+ void visitRefactoringOptions(RefactoringOptionVisitor &Visitor) override {
+ internal::visitRefactoringOptions(
+ Visitor, Requirements,
+ llvm::index_sequence_for<RequirementTypes...>());
+ }
+
private:
std::tuple<RequirementTypes...> Requirements;
};
--- /dev/null
+//===--- RefactoringOption.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_OPTION_H
+#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPTION_H
+
+#include "clang/Basic/LLVM.h"
+#include <memory>
+#include <type_traits>
+
+namespace clang {
+namespace tooling {
+
+class RefactoringOptionVisitor;
+
+/// A refactoring option is an interface that describes a value that
+/// has an impact on the outcome of a refactoring.
+///
+/// Refactoring options can be specified using command-line arguments when
+/// the clang-refactor tool is used.
+class RefactoringOption {
+public:
+ virtual ~RefactoringOption() {}
+
+ /// Returns the name of the refactoring option.
+ ///
+ /// Each refactoring option must have a unique name.
+ virtual StringRef getName() const = 0;
+
+ virtual StringRef getDescription() const = 0;
+
+ /// True when this option must be specified before invoking the refactoring
+ /// action.
+ virtual bool isRequired() const = 0;
+
+ /// Invokes the \c visit method in the option consumer that's appropriate
+ /// for the option's value type.
+ ///
+ /// For example, if the option stores a string value, this method will
+ /// invoke the \c visit method with a reference to an std::string value.
+ virtual void passToVisitor(RefactoringOptionVisitor &Visitor) = 0;
+};
+
+/// Constructs a refactoring option of the given type.
+///
+/// The ownership of options is shared among requirements that use it because
+/// one option can be used by multiple rules in a refactoring action.
+template <typename OptionType>
+std::shared_ptr<OptionType> createRefactoringOption() {
+ static_assert(std::is_base_of<RefactoringOption, OptionType>::value,
+ "invalid option type");
+ return std::make_shared<OptionType>();
+}
+
+} // end namespace tooling
+} // end namespace clang
+
+#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPTION_H
--- /dev/null
+//===--- RefactoringOptionVisitor.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_OPTION_VISITOR_H
+#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPTION_VISITOR_H
+
+#include "clang/Basic/LLVM.h"
+#include <type_traits>
+
+namespace clang {
+namespace tooling {
+
+class RefactoringOption;
+
+/// An interface that declares functions that handle different refactoring
+/// option types.
+///
+/// A valid refactoring option type must have a corresponding \c visit
+/// declaration in this interface.
+class RefactoringOptionVisitor {
+public:
+ virtual ~RefactoringOptionVisitor() {}
+
+ virtual void visit(const RefactoringOption &Opt,
+ Optional<std::string> &Value) = 0;
+};
+
+namespace traits {
+namespace internal {
+
+template <typename T> struct HasHandle {
+private:
+ template <typename ClassT>
+ static auto check(ClassT *) -> typename std::is_same<
+ decltype(std::declval<RefactoringOptionVisitor>().visit(
+ std::declval<RefactoringOption>(), *std::declval<Optional<T> *>())),
+ void>::type;
+
+ template <typename> static std::false_type check(...);
+
+public:
+ using Type = decltype(check<RefactoringOptionVisitor>(nullptr));
+};
+
+} // end namespace internal
+
+/// A type trait that returns true iff the given type is a type that can be
+/// stored in a refactoring option.
+template <typename T>
+struct IsValidOptionType : internal::HasHandle<T>::Type {};
+
+} // end namespace traits
+} // end namespace tooling
+} // end namespace clang
+
+#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPTION_VISITOR_H
--- /dev/null
+//===--- RefactoringOptions.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_OPTIONS_H
+#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPTIONS_H
+
+#include "clang/Basic/LLVM.h"
+#include "clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h"
+#include "clang/Tooling/Refactoring/RefactoringOption.h"
+#include "clang/Tooling/Refactoring/RefactoringOptionVisitor.h"
+#include "llvm/Support/Error.h"
+#include <type_traits>
+
+namespace clang {
+namespace tooling {
+
+/// A refactoring option that stores a value of type \c T.
+template <typename T, typename = typename std::enable_if<
+ traits::IsValidOptionType<T>::value>::type>
+class OptionalRefactoringOption : public RefactoringOption {
+public:
+ void passToVisitor(RefactoringOptionVisitor &Visitor) final override {
+ Visitor.visit(*this, Value);
+ }
+
+ bool isRequired() const override { return false; }
+
+ using ValueType = Optional<T>;
+
+ const ValueType &getValue() const { return Value; }
+
+protected:
+ Optional<T> Value;
+};
+
+/// A required refactoring option that stores a value of type \c T.
+template <typename T, typename = typename std::enable_if<
+ traits::IsValidOptionType<T>::value>::type>
+class RequiredRefactoringOption : public OptionalRefactoringOption<T> {
+public:
+ using ValueType = T;
+
+ const ValueType &getValue() const {
+ return *OptionalRefactoringOption<T>::Value;
+ }
+ bool isRequired() const final override { return true; }
+};
+
+} // end namespace tooling
+} // end namespace clang
+
+#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPTIONS_H
#include "clang/Tooling/Refactoring.h"
#include "clang/Tooling/Refactoring/AtomicChange.h"
+#include "clang/Tooling/Refactoring/RefactoringOptions.h"
#include "clang/Tooling/Refactoring/Rename/SymbolOccurrences.h"
#include "llvm/Support/Error.h"
bool PrintLocations;
};
+class NewNameOption : public RequiredRefactoringOption<std::string> {
+public:
+ StringRef getName() const override { return "new-name"; }
+ StringRef getDescription() const override {
+ return "The new name to change the symbol to";
+ }
+};
+
/// Returns source replacements that correspond to the rename of the given
/// symbol occurrences.
llvm::Expected<std::vector<AtomicChange>>
createRenameReplacements(const SymbolOccurrences &Occurrences,
- const SourceManager &SM,
- ArrayRef<StringRef> NewNameStrings);
+ const SourceManager &SM, const SymbolName &NewName);
/// Rename all symbols identified by the given USRs.
class QualifiedRenamingAction {
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Refactoring.h"
#include "clang/Tooling/Refactoring/RefactoringAction.h"
+#include "clang/Tooling/Refactoring/RefactoringOptions.h"
+#include "clang/Tooling/Refactoring/Rename/SymbolName.h"
#include "clang/Tooling/Refactoring/Rename/USRFinder.h"
#include "clang/Tooling/Refactoring/Rename/USRFindingAction.h"
#include "clang/Tooling/Refactoring/Rename/USRLocFinder.h"
class RenameOccurrences final : public SourceChangeRefactoringRule {
public:
- RenameOccurrences(const NamedDecl *ND) : Finder(ND) {}
+ RenameOccurrences(const NamedDecl *ND, std::string NewName)
+ : Finder(ND), NewName(NewName) {}
Expected<AtomicChanges>
createSourceReplacements(RefactoringRuleContext &Context) {
Finder.findSymbolOccurrences(Context);
if (!Occurrences)
return Occurrences.takeError();
- // FIXME: This is a temporary workaround that's needed until the refactoring
- // options are implemented.
- StringRef NewName = "Bar";
+ // FIXME: Verify that the new name is valid.
+ SymbolName Name(NewName);
return createRenameReplacements(
- *Occurrences, Context.getASTContext().getSourceManager(), NewName);
+ *Occurrences, Context.getASTContext().getSourceManager(), Name);
}
private:
OccurrenceFinder Finder;
+ std::string NewName;
};
class LocalRename final : public RefactoringAction {
RefactoringActionRules createActionRules() const override {
RefactoringActionRules Rules;
Rules.push_back(createRefactoringActionRule<RenameOccurrences>(
- SymbolSelectionRequirement()));
+ SymbolSelectionRequirement(), OptionRequirement<NewNameOption>()));
return Rules;
}
};
Expected<std::vector<AtomicChange>>
createRenameReplacements(const SymbolOccurrences &Occurrences,
- const SourceManager &SM,
- ArrayRef<StringRef> NewNameStrings) {
+ const SourceManager &SM, const SymbolName &NewName) {
// FIXME: A true local rename can use just one AtomicChange.
std::vector<AtomicChange> Changes;
for (const auto &Occurrence : Occurrences) {
ArrayRef<SourceRange> Ranges = Occurrence.getNameRanges();
- assert(NewNameStrings.size() == Ranges.size() &&
+ assert(NewName.getNamePieces().size() == Ranges.size() &&
"Mismatching number of ranges and name pieces");
AtomicChange Change(SM, Ranges[0].getBegin());
for (const auto &Range : llvm::enumerate(Ranges)) {
auto Error =
Change.replace(SM, CharSourceRange::getCharRange(Range.value()),
- NewNameStrings[Range.index()]);
+ NewName.getNamePieces()[Range.index()]);
if (Error)
return std::move(Error);
}
}
// FIXME: Support multi-piece names.
// FIXME: better error handling (propagate error out).
- StringRef NewNameRef = NewName;
+ SymbolName NewNameRef(NewName);
Expected<std::vector<AtomicChange>> Change =
createRenameReplacements(Occurrences, SourceMgr, NewNameRef);
if (!Change) {
-// RUN: clang-refactor local-rename -selection=test:%s %s -- | FileCheck %s
+// RUN: clang-refactor local-rename -selection=test:%s -new-name=Bar %s -- | FileCheck %s
class Baz {
int /*range=*/Foo; // CHECK: int /*range=*/Bar;
-// RUN: clang-refactor local-rename -selection=test:%s -v %s -- | FileCheck %s
+// RUN: clang-refactor local-rename -selection=test:%s -new-name=test -v %s -- | FileCheck %s
/*range=*/int test;
/*range named =+0*/int test5;
// CHECK: Test selection group '':
-// CHECK-NEXT: 90-90
-// CHECK-NEXT: 143-143
-// CHECK-NEXT: 182-182
+// CHECK-NEXT: 105-105
+// CHECK-NEXT: 158-158
+// CHECK-NEXT: 197-197
// CHECK-NEXT: Test selection group 'named':
-// CHECK-NEXT: 117-117
-// CHECK-NEXT: 203-203
+// CHECK-NEXT: 132-132
+// CHECK-NEXT: 218-218
// The following invocations are in the default group:
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Refactoring.h"
#include "clang/Tooling/Refactoring/RefactoringAction.h"
+#include "clang/Tooling/Refactoring/RefactoringOptions.h"
#include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/CommandLine.h"
namespace opts {
-static cl::OptionCategory CommonRefactorOptions("Common refactoring options");
+static cl::OptionCategory CommonRefactorOptions("Refactoring options");
static cl::opt<bool> Verbose("v", cl::desc("Use verbose output"),
- cl::cat(CommonRefactorOptions),
+ cl::cat(cl::GeneralCategory),
cl::sub(*cl::AllSubCommands));
} // end namespace opts
return nullptr;
}
+/// A container that stores the command-line options used by a single
+/// refactoring option.
+class RefactoringActionCommandLineOptions {
+public:
+ void addStringOption(const RefactoringOption &Option,
+ std::unique_ptr<cl::opt<std::string>> CLOption) {
+ StringOptions[&Option] = std::move(CLOption);
+ }
+
+ const cl::opt<std::string> &
+ getStringOption(const RefactoringOption &Opt) const {
+ auto It = StringOptions.find(&Opt);
+ return *It->second;
+ }
+
+private:
+ llvm::DenseMap<const RefactoringOption *,
+ std::unique_ptr<cl::opt<std::string>>>
+ StringOptions;
+};
+
+/// Passes the command-line option values to the options used by a single
+/// refactoring action rule.
+class CommandLineRefactoringOptionVisitor final
+ : public RefactoringOptionVisitor {
+public:
+ CommandLineRefactoringOptionVisitor(
+ const RefactoringActionCommandLineOptions &Options)
+ : Options(Options) {}
+
+ void visit(const RefactoringOption &Opt,
+ Optional<std::string> &Value) override {
+ const cl::opt<std::string> &CLOpt = Options.getStringOption(Opt);
+ if (!CLOpt.getValue().empty()) {
+ Value = CLOpt.getValue();
+ return;
+ }
+ Value = None;
+ if (Opt.isRequired())
+ MissingRequiredOptions.push_back(&Opt);
+ }
+
+ ArrayRef<const RefactoringOption *> getMissingRequiredOptions() const {
+ return MissingRequiredOptions;
+ }
+
+private:
+ llvm::SmallVector<const RefactoringOption *, 4> MissingRequiredOptions;
+ const RefactoringActionCommandLineOptions &Options;
+};
+
+/// Creates the refactoring options used by all the rules in a single
+/// refactoring action.
+class CommandLineRefactoringOptionCreator final
+ : public RefactoringOptionVisitor {
+public:
+ CommandLineRefactoringOptionCreator(
+ cl::OptionCategory &Category, cl::SubCommand &Subcommand,
+ RefactoringActionCommandLineOptions &Options)
+ : Category(Category), Subcommand(Subcommand), Options(Options) {}
+
+ void visit(const RefactoringOption &Opt, Optional<std::string> &) override {
+ if (Visited.insert(&Opt).second)
+ Options.addStringOption(Opt, create<std::string>(Opt));
+ }
+
+private:
+ template <typename T>
+ std::unique_ptr<cl::opt<T>> create(const RefactoringOption &Opt) {
+ if (!OptionNames.insert(Opt.getName()).second)
+ llvm::report_fatal_error("Multiple identical refactoring options "
+ "specified for one refactoring action");
+ // FIXME: cl::Required can be specified when this option is present
+ // in all rules in an action.
+ return llvm::make_unique<cl::opt<T>>(
+ Opt.getName(), cl::desc(Opt.getDescription()), cl::Optional,
+ cl::cat(Category), cl::sub(Subcommand));
+ }
+
+ llvm::SmallPtrSet<const RefactoringOption *, 8> Visited;
+ llvm::StringSet<> OptionNames;
+ cl::OptionCategory &Category;
+ cl::SubCommand &Subcommand;
+ RefactoringActionCommandLineOptions &Options;
+};
+
/// A subcommand that corresponds to individual refactoring action.
class RefactoringActionSubcommand : public cl::SubCommand {
public:
"<file>:<line>:<column>)"),
cl::cat(Category), cl::sub(*this));
}
+ // Create the refactoring options.
+ for (const auto &Rule : this->ActionRules) {
+ CommandLineRefactoringOptionCreator OptionCreator(Category, *this,
+ Options);
+ Rule->visitRefactoringOptions(OptionCreator);
+ }
}
~RefactoringActionSubcommand() { unregisterSubCommand(); }
assert(Selection && "selection not supported!");
return ParsedSelection.get();
}
+
+ const RefactoringActionCommandLineOptions &getOptions() const {
+ return Options;
+ }
+
private:
std::unique_ptr<RefactoringAction> Action;
RefactoringActionRules ActionRules;
std::unique_ptr<cl::opt<std::string>> Selection;
std::unique_ptr<SourceSelectionArgument> ParsedSelection;
+ RefactoringActionCommandLineOptions Options;
};
class ClangRefactorConsumer : public RefactoringResultConsumer {
bool HasSelection = false;
for (const auto &Rule : Subcommand.getActionRules()) {
+ bool SelectionMatches = true;
if (Rule->hasSelectionRequirement()) {
HasSelection = true;
- if (Subcommand.getSelection())
- MatchingRules.push_back(Rule.get());
- else
+ if (!Subcommand.getSelection()) {
MissingOptions.insert("selection");
+ SelectionMatches = false;
+ }
+ }
+ CommandLineRefactoringOptionVisitor Visitor(Subcommand.getOptions());
+ Rule->visitRefactoringOptions(Visitor);
+ if (SelectionMatches && Visitor.getMissingRequiredOptions().empty()) {
+ MatchingRules.push_back(Rule.get());
+ continue;
}
- // FIXME (Alex L): Support custom options.
+ for (const RefactoringOption *Opt : Visitor.getMissingRequiredOptions())
+ MissingOptions.insert(Opt->getName());
}
if (MatchingRules.empty()) {
llvm::errs() << "error: '" << Subcommand.getName()
ClangRefactorTool Tool;
CommonOptionsParser Options(
- argc, argv, opts::CommonRefactorOptions, cl::ZeroOrMore,
+ argc, argv, cl::GeneralCategory, cl::ZeroOrMore,
"Clang-based refactoring tool for C, C++ and Objective-C");
// Figure out which action is specified by the user. The user must specify