arphaman created this revision. Herald added subscribers: mgorny, klimek. This patch adds an editor client that can do things like:
- find a list of available refactoring editor commands in a particular range. - perform a particular refactoring editor command in a particular range. This editor client is used in clangd's support for refactoring commands (See dependent revision). (I've ran out of time to add a unit test before devmeeting, so for now this is tested in clangd, but I'll add a unit test when I update the patch) Repository: rL LLVM https://reviews.llvm.org/D39055 Files: include/clang/Tooling/Refactoring/EditorClient.h include/clang/Tooling/Refactoring/RefactoringActionRule.h include/clang/Tooling/Refactoring/RefactoringActionRules.h include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h lib/Tooling/Refactoring/CMakeLists.txt lib/Tooling/Refactoring/EditorClient.cpp lib/Tooling/Refactoring/EditorCommand.cpp
Index: lib/Tooling/Refactoring/EditorCommand.cpp =================================================================== --- lib/Tooling/Refactoring/EditorCommand.cpp +++ lib/Tooling/Refactoring/EditorCommand.cpp @@ -28,8 +28,9 @@ : Rule(std::move(Rule)), Command(Command) {} void invoke(RefactoringResultConsumer &Consumer, - RefactoringRuleContext &Context) override { - Rule->invoke(Consumer, Context); + RefactoringRuleContext &Context, + RefactoringStage StopAfter) override { + Rule->invoke(Consumer, Context, StopAfter); } bool hasSelectionRequirement() override { Index: lib/Tooling/Refactoring/EditorClient.cpp =================================================================== --- /dev/null +++ lib/Tooling/Refactoring/EditorClient.cpp @@ -0,0 +1,117 @@ +//===--- EditorClient.cpp - refactoring editor client ---------------------===// +// +// 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/EditorClient.h" +#include "clang/AST/ASTContext.h" +#include "clang/Tooling/Refactoring/EditorCommands.h" +#include "clang/Tooling/Refactoring/RefactoringAction.h" +#include "clang/Tooling/Refactoring/RefactoringActionRule.h" +#include "llvm/ADT/STLExtras.h" + +namespace clang { +namespace tooling { + +RefactoringEditorClient::Refactoring::~Refactoring() {} + +RefactoringEditorClient::RefactoringEditorClient() { + std::vector<std::unique_ptr<RefactoringAction>> Actions = + createRefactoringActions(); + + // Create subcommands and command-line options. + for (auto &Action : Actions) { + RefactoringActionRules Rules = Action->createActiveActionRules(); + // Filter out refactoring rules without an source selection requirement and + // without an editor command. + Rules.resize( + llvm::remove_if(Rules, + [](const std::unique_ptr<RefactoringActionRule> &Rule) { + return !Rule->hasSelectionRequirement() || + !Rule->getEditorCommand(); + }) - + Rules.begin()); + // FIXME (Alex L): Filter out rules with required options that can't be + // fulfilled by editor clients (Also potentially simplify when selection + // gets treated just like another option). + if (Rules.empty()) + continue; + for (const auto &Rule : Rules) + EditorCommandsToRule.insert( + std::make_pair(Rule->getEditorCommand()->getName(), Rule.get())); + RefactoringActions.push_back({std::move(Action), std::move(Rules)}); + } +} + +std::vector<const EditorCommand *> +RefactoringEditorClient::getAvailableRefactorings(ASTContext &Context, + SourceRange SelectionRange) { + std::vector<const EditorCommand *> Commands; + RefactoringRuleContext RuleContext(Context.getSourceManager()); + RuleContext.setSelectionRange(SelectionRange); + RuleContext.setASTContext(Context); + + class ErrorChecker final : public RefactoringResultConsumer { + public: + bool InitiationSucceeded = true; + + void handleError(llvm::Error Err) override { + llvm::consumeError(std::move(Err)); + InitiationSucceeded = false; + } + }; + + // Figure out which refactorings are available by running the initiation + // stage only. + for (const auto &Action : RefactoringActions) { + for (const auto &Rule : Action.ActionRules) { + ErrorChecker Consumer; + Rule->invoke(Consumer, RuleContext, + /*StopAfter=*/RefactoringStage::Initiation); + if (Consumer.InitiationSucceeded) + Commands.push_back(Rule->getEditorCommand()); + } + } + return Commands; +} + +Expected<AtomicChanges> RefactoringEditorClient::performRefactoring( + ASTContext &Context, StringRef CommandName, SourceRange SelectionRange) { + auto It = EditorCommandsToRule.find(CommandName); + if (It == EditorCommandsToRule.end()) + return llvm::make_error<llvm::StringError>("invalid command name", + llvm::inconvertibleErrorCode()); + + RefactoringActionRule *Rule = It->second; + RefactoringRuleContext RuleContext(Context.getSourceManager()); + RuleContext.setSelectionRange(SelectionRange); + RuleContext.setASTContext(Context); + + class EditorConsumer final : public RefactoringResultConsumer { + public: + Expected<AtomicChanges> SourceChanges = std::vector<AtomicChange>(); + + void handleError(llvm::Error Err) override { + SourceChanges = std::move(Err); + } + + void handle(AtomicChanges Changes) override { + if (SourceChanges) + SourceChanges = std::move(Changes); + } + + void handle(SymbolOccurrences) override { + llvm_unreachable("symbol occurrence results are not handled yet"); + } + }; + EditorConsumer Consumer; + Rule->invoke(Consumer, RuleContext); + return std::move(Consumer.SourceChanges); +} + +} // end namespace tooling +} // end namespace clang Index: lib/Tooling/Refactoring/CMakeLists.txt =================================================================== --- lib/Tooling/Refactoring/CMakeLists.txt +++ lib/Tooling/Refactoring/CMakeLists.txt @@ -4,6 +4,7 @@ ASTSelection.cpp ASTSelectionRequirements.cpp AtomicChange.cpp + EditorClient.cpp EditorCommand.cpp Extract.cpp RefactoringActions.cpp Index: include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h =================================================================== --- include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h +++ include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h @@ -47,17 +47,22 @@ template <typename RuleType, typename... RequirementTypes, size_t... Is> void invokeRuleAfterValidatingRequirements( RefactoringResultConsumer &Consumer, RefactoringRuleContext &Context, + RefactoringStage StopAfter, const std::tuple<RequirementTypes...> &Requirements, llvm::index_sequence<Is...>) { // Check if the requirements we're interested in can be evaluated. auto Values = std::make_tuple(std::get<Is>(Requirements).evaluate(Context)...); auto Err = findError(std::get<Is>(Values)...); if (Err) return Consumer.handleError(std::move(Err)); + // Don't perform the refactoring when we're initiating only. + if (StopAfter == RefactoringStage::Initiation) + return; // Construct the target action rule by extracting the evaluated // requirements from Expected<> wrappers and then run it. - RuleType(std::move((*std::get<Is>(Values)))...).invoke(Consumer, Context); + RuleType(std::move((*std::get<Is>(Values)))...) + .invoke(Consumer, Context, StopAfter); } inline void visitRefactoringOptionsImpl(RefactoringOptionVisitor &) {} @@ -125,9 +130,10 @@ : Requirements(Requirements) {} void invoke(RefactoringResultConsumer &Consumer, - RefactoringRuleContext &Context) override { + RefactoringRuleContext &Context, + RefactoringStage StopAfter) override { internal::invokeRuleAfterValidatingRequirements<RuleType>( - Consumer, Context, Requirements, + Consumer, Context, StopAfter, Requirements, llvm::index_sequence_for<RequirementTypes...>()); } Index: include/clang/Tooling/Refactoring/RefactoringActionRules.h =================================================================== --- include/clang/Tooling/Refactoring/RefactoringActionRules.h +++ include/clang/Tooling/Refactoring/RefactoringActionRules.h @@ -53,7 +53,10 @@ class SourceChangeRefactoringRule : public RefactoringActionRuleBase { public: void invoke(RefactoringResultConsumer &Consumer, - RefactoringRuleContext &Context) final override { + RefactoringRuleContext &Context, + RefactoringStage StopAfter) final override { + assert(StopAfter >= RefactoringStage::SourceTransformation && + "not stopped after initiation"); Expected<AtomicChanges> Changes = createSourceReplacements(Context); if (!Changes) Consumer.handleError(Changes.takeError()); @@ -75,7 +78,10 @@ class FindSymbolOccurrencesRefactoringRule : public RefactoringActionRuleBase { public: void invoke(RefactoringResultConsumer &Consumer, - RefactoringRuleContext &Context) final override { + RefactoringRuleContext &Context, + RefactoringStage StopAfter) final override { + assert(StopAfter >= RefactoringStage::SourceTransformation && + "not stopped after initiation"); Expected<SymbolOccurrences> Occurrences = findSymbolOccurrences(Context); if (!Occurrences) Consumer.handleError(Occurrences.takeError()); Index: include/clang/Tooling/Refactoring/RefactoringActionRule.h =================================================================== --- include/clang/Tooling/Refactoring/RefactoringActionRule.h +++ include/clang/Tooling/Refactoring/RefactoringActionRule.h @@ -21,6 +21,15 @@ class RefactoringResultConsumer; class RefactoringRuleContext; +/// A refactoring operation is split into different stages of operations. +enum class RefactoringStage { + /// The initiation stage verifies options and handles AST selection and + /// AST matching. + Initiation, + /// The source transformation change produces the source changes. + SourceTransformation +}; + /// A common refactoring action rule interface that defines the 'invoke' /// function that performs the refactoring operation (either fully or /// partially). @@ -32,8 +41,9 @@ /// /// The specific rule will invoke an appropriate \c handle method on a /// consumer to propagate the result of the refactoring action. - virtual void invoke(RefactoringResultConsumer &Consumer, - RefactoringRuleContext &Context) = 0; + virtual void invoke( + RefactoringResultConsumer &Consumer, RefactoringRuleContext &Context, + RefactoringStage StopAfter = RefactoringStage::SourceTransformation) = 0; }; /// A refactoring action rule is a wrapper class around a specific refactoring Index: include/clang/Tooling/Refactoring/EditorClient.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/EditorClient.h @@ -0,0 +1,66 @@ +//===--- EditorClient.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_EDITOR_CLIENT_H +#define LLVM_CLANG_TOOLING_REFACTOR_EDITOR_CLIENT_H + +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Refactoring/AtomicChange.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include <memory> + +namespace clang { + +class ASTContext; + +namespace tooling { + +class RefactoringAction; +class RefactoringActionRule; +class EditorCommand; + +/// A refactoring editor client simplifies integration of the refactoring +/// library with the editor services like libclang and Clangd. +class RefactoringEditorClient { +public: + RefactoringEditorClient(); + + /// Returns the list of editor commands that represent a refactoring + /// operation that can be performed for the specified selection range. + std::vector<const EditorCommand *> + getAvailableRefactorings(ASTContext &Context, SourceRange SelectionRange); + + /// Performs the refactoring that's associated with an editor commands with + /// the given name with the provided source selection range. + /// + /// Returns a set of source changes if the refactoring succeeds, or an + /// error otherwise. + Expected<AtomicChanges> performRefactoring(ASTContext &Context, + StringRef CommandName, + SourceRange SelectionRange); + +private: + struct Refactoring { + std::unique_ptr<RefactoringAction> Action; + std::vector<std::unique_ptr<RefactoringActionRule>> ActionRules; + ~Refactoring(); + + Refactoring(Refactoring &&) = default; + Refactoring &operator=(Refactoring &&) = default; + }; + std::vector<Refactoring> RefactoringActions; + llvm::StringMap<RefactoringActionRule *> EditorCommandsToRule; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_EDITOR_CLIENT_H
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits