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
  • [PATCH] D39055: [refactor] Add... Alex Lorenz via Phabricator via cfe-commits

Reply via email to