arphaman created this revision.
Herald added a subscriber: mgorny.

This patch depends on https://reviews.llvm.org/D36075 and 
https://reviews.llvm.org/D36156.

It introduces the `clang-refactor` tool alongside the `local-rename` action 
which is uses the existing renaming engine used by `clang-rename`. The tool 
doesn't actually perform the source transformations yet, it just provides 
testing support. I've moved one test from `clang-rename` over right now, but 
will move the others if the general direction of this patch is accepted.

The following options are supported by clang-refactor:

- `-v`: use verbose output
- `-dump`: dump results instead of applying them
- `-no-dbs`: avoid searching/loading databases like compilation database and/or 
indexer stores/databases
- `-selection`: The source range that corresponds to the portion of the source 
that's selected (currently only special command `test:<file>` is supported).

The testing support provided by `clang-refactor` is described below:
When `-selection=test:<file>` is given, `clang-refactor` will parse the 
selection commands from that file. The selection commands are grouped and the 
specified refactoring action invoked by the tool. Each command in a group is 
expected to produce an identical result. The precise syntax for the selection 
commands is described in a comment for `findTestSelectionRangesIn` in 
TestSupport.h.

Thanks for taking a look!


Repository:
  rL LLVM

https://reviews.llvm.org/D36574

Files:
  include/clang/Basic/DiagnosticRefactoringKinds.td
  include/clang/Tooling/Refactoring/RefactoringAction.h
  include/clang/Tooling/Refactoring/RefactoringActionRegistry.def
  include/clang/Tooling/Refactoring/RefactoringActionRules.h
  include/clang/Tooling/Refactoring/RefactoringEngine.h
  include/clang/Tooling/Refactoring/RefactoringOperationController.h
  include/clang/Tooling/Refactoring/RefactoringResult.h
  include/clang/Tooling/Refactoring/Rename/SymbolOccurrences.h
  include/clang/Tooling/Refactoring/Rename/USRFindingAction.h
  include/clang/Tooling/Refactoring/SourceSelectionConstraints.h
  include/clang/module.modulemap
  lib/Tooling/Refactoring/CMakeLists.txt
  lib/Tooling/Refactoring/RefactoringEngine.cpp
  lib/Tooling/Refactoring/Rename/RenamingAction.cpp
  lib/Tooling/Refactoring/Rename/USRFindingAction.cpp
  test/Refactor/LocalRename/Field.cpp
  test/Refactor/tool-common-options.c
  test/Refactor/tool-test-support.c
  test/clang-rename/Field.cpp
  tools/CMakeLists.txt
  tools/clang-refactor/CMakeLists.txt
  tools/clang-refactor/ClangRefactor.cpp
  tools/clang-refactor/TestSupport.cpp
  tools/clang-refactor/TestSupport.h
  unittests/Tooling/RefactoringActionRulesTest.cpp

Index: unittests/Tooling/RefactoringActionRulesTest.cpp
===================================================================
--- unittests/Tooling/RefactoringActionRulesTest.cpp
+++ unittests/Tooling/RefactoringActionRulesTest.cpp
@@ -56,7 +56,8 @@
 
   // When the requirements are satisifed, the rule's function must be invoked.
   {
-    RefactoringOperationController Operation(Context.Sources);
+    RefactoringOperationController Operation(Context.Sources,
+                                             Context.Diagnostics);
     SourceLocation Cursor =
         Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID())
             .getLocWithOffset(10);
@@ -87,7 +88,8 @@
   // When one of the requirements is not satisfied, perform should return either
   // None or a valid diagnostic.
   {
-    RefactoringOperationController Operation(Context.Sources);
+    RefactoringOperationController Operation(Context.Sources,
+                                             Context.Diagnostics);
     DiagnosticOr<RefactoringResult> DiagOrResult = Rule->perform(Operation);
 
     // A failure to select returns the invalidSelectionError.
@@ -107,7 +109,8 @@
       apply(Func, requiredSelection(
                       selection::identity<selection::SourceSelectionRange>()));
 
-  RefactoringOperationController Operation(Context.Sources);
+  RefactoringOperationController Operation(Context.Sources,
+                                           Context.Diagnostics);
   SourceLocation Cursor =
       Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID());
   Operation.setSelectionRange({Cursor, Cursor});
@@ -122,7 +125,8 @@
 TEST_F(RefactoringActionRulesTest, ReturnInitiationDiagnostic) {
   unsigned DiagID = Context.Diagnostics.getCustomDiagID(
       DiagnosticsEngine::Error, "Diagnostic: %0");
-  RefactoringOperationController Operation(Context.Sources);
+  RefactoringOperationController Operation(Context.Sources,
+                                           Context.Diagnostics);
   PartialDiagnostic Diag(DiagID, Operation.getDiagnosticStorage());
 
   class SelectionRequirement : public selection::Requirement {
Index: tools/clang-refactor/TestSupport.h
===================================================================
--- /dev/null
+++ tools/clang-refactor/TestSupport.h
@@ -0,0 +1,104 @@
+//===--- TestSupport.h - Clang-based refactoring tool -----------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief Declares datatypes and routines that are used by test-specific code
+/// in clang-refactor.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_CLANG_REFACTOR_TEST_SUPPORT_H
+#define LLVM_CLANG_TOOLS_CLANG_REFACTOR_TEST_SUPPORT_H
+
+#include "clang/Basic/DiagnosticOr.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Tooling/Refactoring/RefactoringResult.h"
+#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include <map>
+#include <string>
+
+namespace clang {
+
+class SourceManager;
+
+namespace clang_refactor {
+
+/// A source selection range that's specified in a test file using an inline
+/// command in the comment. These commands can take the following forms:
+///
+/// - /*range=*/ will create an empty selection range in the default group
+///   right after the comment.
+/// - /*range a=*/ will create an empty selection range in the 'a' group right
+///   after the comment.
+/// - /*range = +1*/ will create an empty selection range at a location that's
+///   right after the comment with one offset to the column.
+/// - /*range= -> +2:3*/ will create a selection range that starts at the
+///   location right after the comment, and ends at column 3 of the 2nd line
+///   after the line of the starting location.
+///
+/// Clang-refactor will expected all ranges in one test group to produce
+/// identical results.
+struct TestSelectionRange {
+  unsigned Begin, End;
+};
+
+/// A set of test selection ranges specified in one file.
+struct TestSelectionRangesInFile {
+  std::string Filename;
+  std::map<std::string, SmallVector<TestSelectionRange, 8>> GroupedRanges;
+
+  TestSelectionRangesInFile(TestSelectionRangesInFile &&) = default;
+  TestSelectionRangesInFile &operator=(TestSelectionRangesInFile &&) = default;
+
+  bool dispatch(
+      const SourceManager &SM, DiagnosticsEngine &Diags,
+      llvm::function_ref<DiagnosticOr<tooling::RefactoringResult>(SourceRange)>
+          Producer) const;
+
+  void dump(llvm::raw_ostream &OS) const;
+};
+
+/// Extracts the grouped selection ranges from the file that's specified in
+/// the -selection=test:<filename> option.
+///
+/// The grouped ranges are specified in comments using the following syntax:
+/// "range" [ group-name ] "=" [ "+" starting-column-offset ] [ "->"
+///                              "+" ending-line-offset ":"
+///                                  ending-column-position ]
+///
+/// The selection range is then computed from this command by taking the ending
+/// location of the comment, and adding 'starting-column-offset' to the column
+/// for that location. That location in turns becomes the whole selection range,
+/// unless 'ending-line-offset' and 'ending-column-position' are specified. If
+/// they are specified, then the ending location of the selection range is
+/// the starting location's line + 'ending-line-offset' and the
+/// 'ending-column-position' column.
+///
+/// All selection ranges in one group are expected to produce the same
+/// refactoring result.
+///
+/// When testing, zero is returned from clang-refactor even when a group
+/// produces an initiation error, which is different from normal invocation
+/// that returns a non-zero value. This is done on purpose, to ensure that group
+/// consistency checks can return non-zero, but still dump the output of
+/// the group. So even if a test matches the output of group, it will still fail
+/// because clang-refactor should return zero on exit when the group results are
+/// consistent.
+///
+/// \returns None on failure (errors are emitted to stderr), or a set of
+/// grouped source ranges in the given file otherwise.
+Optional<TestSelectionRangesInFile>
+findTestSelectionRangesIn(StringRef Filename);
+
+} // end namespace clang_refactor
+} // end namespace clang
+
+#endif // LLVM_CLANG_TOOLS_CLANG_REFACTOR_TEST_SUPPORT_H
Index: tools/clang-refactor/TestSupport.cpp
===================================================================
--- /dev/null
+++ tools/clang-refactor/TestSupport.cpp
@@ -0,0 +1,209 @@
+//===--- TestSupport.cpp - Clang-based refactoring tool -------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief This file implements routines that provide refactoring testing
+/// utilities.
+///
+//===----------------------------------------------------------------------===//
+
+#include "TestSupport.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Lex/Lexer.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/ErrorOr.h"
+#include "llvm/Support/Regex.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace llvm;
+using namespace clang;
+using namespace tooling;
+using namespace clang_refactor;
+
+void TestSelectionRangesInFile::dump(raw_ostream &OS) const {
+  for (const auto &Group : GroupedRanges) {
+    OS << "Test selection group '" << Group.first << "':\n";
+    for (const auto &Range : Group.second) {
+      OS << "  " << Range.Begin << "-" << Range.End << "\n";
+    }
+  }
+}
+
+bool TestSelectionRangesInFile::dispatch(
+    const SourceManager &SM, DiagnosticsEngine &Diags,
+    llvm::function_ref<DiagnosticOr<RefactoringResult>(SourceRange)> Producer)
+    const {
+  const FileEntry *FE = SM.getFileManager().getFile(Filename);
+  FileID FID = FE ? SM.translateFile(FE) : FileID();
+  if (!FE || FID.isInvalid()) {
+    llvm::errs() << "error: -selection=test:" << Filename
+                 << " : given file is not in the target TU";
+    return true;
+  }
+  SourceLocation FileLoc = SM.getLocForStartOfFile(FID);
+  bool Failed = false;
+  for (const auto &Group : GroupedRanges) {
+    // All ranges in the group must produce the same result.
+    Optional<RefactoringResult> CanonicalResult;
+    Optional<std::string> CanonicalErrorMessage;
+    for (const TestSelectionRange &Range : Group.second) {
+      // Translate the offset pair to a true source range.
+      SourceLocation Start =
+          SM.getMacroArgExpandedLocation(FileLoc.getLocWithOffset(Range.Begin));
+      SourceLocation End =
+          SM.getMacroArgExpandedLocation(FileLoc.getLocWithOffset(Range.End));
+      assert(Start.isValid() && End.isValid() && "unexpected invalid range");
+
+      DiagnosticOr<RefactoringResult> Result =
+          Producer(SourceRange(Start, End));
+      std::string ErrorMessage;
+      bool HasResult = !!Result;
+      if (!HasResult) {
+        SmallString<100> String;
+        // It's better to test location agnostic errors, so ignore the location.
+        Result.getDiagnostic().second.EmitToString(Diags, String);
+        ErrorMessage = String.c_str();
+      }
+      if (!CanonicalResult && !CanonicalErrorMessage) {
+        if (HasResult)
+          CanonicalResult = std::move(*Result);
+        else
+          CanonicalErrorMessage = std::move(ErrorMessage);
+        continue;
+      }
+
+      // Verify that this result corresponds to the canonical result.
+      if (CanonicalErrorMessage) {
+        // The error messages must match.
+        if (!HasResult && ErrorMessage == *CanonicalErrorMessage)
+          continue;
+      } else {
+        assert(CanonicalResult && "missing canonical result");
+        // The results must match.
+        if (HasResult && *Result == *CanonicalResult)
+          continue;
+      }
+      Failed = true;
+      // Report the mismatch.
+      llvm::errs()
+          << "error: unexpected refactoring result for range starting at "
+          << SM.getLineNumber(FID, Range.Begin) << ':'
+          << SM.getColumnNumber(FID, Range.Begin) << " in group '"
+          << Group.first << "':\n  ";
+      if (HasResult)
+        llvm::errs() << "valid result";
+      else
+        llvm::errs() << "error '" << ErrorMessage << "'";
+      llvm::errs() << " does not match initial ";
+      if (CanonicalErrorMessage)
+        llvm::errs() << "error '" << *CanonicalErrorMessage << "'\n";
+      else
+        llvm::errs() << "valid result\n";
+      if (HasResult && !CanonicalErrorMessage) {
+        llvm::errs() << "  Expected to Produce:\n";
+        CanonicalResult->dump(llvm::errs(), SM, /*Indent=*/4);
+        llvm::errs() << "  Produced:\n";
+        Result->dump(llvm::errs(), SM, /*Indent=*/4);
+      }
+    }
+
+    // FIXME: Integrate with -dump.
+    if (!CanonicalResult) {
+      llvm::errs() << Group.second.size() << " '" << Group.first
+                   << "' results:\n";
+      llvm::errs() << *CanonicalErrorMessage << "\n";
+    } else {
+      llvm::outs() << Group.second.size() << " '" << Group.first
+                   << "' results:\n";
+      CanonicalResult->dump(llvm::outs(), SM);
+    }
+  }
+  return Failed;
+}
+
+/// Adds the \p ColumnOffset to file offset \p Offset, without going past a
+/// newline.
+static unsigned addColumnOffset(StringRef Source, unsigned Offset,
+                                unsigned ColumnOffset) {
+  if (!ColumnOffset)
+    return Offset;
+  StringRef Substr = Source.drop_front(Offset).take_front(ColumnOffset);
+  size_t NewlinePos = Substr.find_first_of("\r\n");
+  return Offset +
+         (NewlinePos == StringRef::npos ? ColumnOffset : (unsigned)NewlinePos);
+}
+
+Optional<TestSelectionRangesInFile>
+clang_refactor::findTestSelectionRangesIn(StringRef Filename) {
+  ErrorOr<std::unique_ptr<MemoryBuffer>> ErrOrFile =
+      llvm::MemoryBuffer::getFile(Filename);
+  if (!ErrOrFile) {
+    llvm::errs() << "error: -selection=test:" << Filename
+                 << " : could not open the given file";
+    return None;
+  }
+  StringRef Source = ErrOrFile.get()->getBuffer();
+
+  // FIXME (Alex L): 3rd capture groups for +line:column.
+  // See the doc comment for this function for the explanation of this
+  // syntax.
+  static Regex RangeRegex("range[[:blank:]]*([[:alpha:]_]*)?[[:blank:]]*=[[:"
+                          "blank:]]*(\\+[[:digit:]]+)?");
+
+  TestSelectionRangesInFile TestRanges = {Filename.str(), {}};
+
+  LangOptions LangOpts;
+  LangOpts.CPlusPlus = 1;
+  LangOpts.CPlusPlus11 = 1;
+  Lexer Lex(SourceLocation::getFromRawEncoding(0), LangOpts, Source.begin(),
+            Source.begin(), Source.end());
+  Lex.SetCommentRetentionState(true);
+  Token Tok;
+  for (Lex.LexFromRawLexer(Tok); Tok.isNot(tok::eof);
+       Lex.LexFromRawLexer(Tok)) {
+    if (Tok.isNot(tok::comment))
+      continue;
+    StringRef Comment =
+        Source.substr(Tok.getLocation().getRawEncoding(), Tok.getLength());
+    llvm::SmallVector<StringRef, 4> Matches;
+    if (!RangeRegex.match(Comment, &Matches)) {
+      // Try to detect mistyped 'range:' comments to ensure tests don't miss
+      // anything.
+      if (Comment.contains_lower("range") && !Comment.contains_lower("run")) {
+        llvm::errs() << "error: suspicious comment '" << Comment
+                     << "' that "
+                        "resembles the range command found\n";
+        llvm::errs() << "note: please reword if this isn't a range command\n";
+        return None;
+      }
+      continue;
+    }
+    unsigned Offset = Tok.getEndLoc().getRawEncoding();
+    unsigned ColumnOffset = 0;
+    if (!Matches[2].empty()) {
+      // Don't forget to drop the '+'!
+      if (Matches[2].drop_front().getAsInteger(10, ColumnOffset))
+        assert(false && "regex should have produced a number");
+    }
+    // FIXME (Alex L): Support true ranges.
+    Offset = addColumnOffset(Source, Offset, ColumnOffset);
+    TestSelectionRange Range = {Offset, Offset};
+    auto It = TestRanges.GroupedRanges.insert(std::make_pair(
+        Matches[1].str(), SmallVector<TestSelectionRange, 8>{Range}));
+    if (!It.second)
+      It.first->second.push_back(Range);
+  }
+  if (TestRanges.GroupedRanges.empty()) {
+    llvm::errs() << "error: -selection=test:" << Filename
+                 << ": no 'range' commands";
+    return None;
+  }
+  return std::move(TestRanges);
+}
Index: tools/clang-refactor/ClangRefactor.cpp
===================================================================
--- /dev/null
+++ tools/clang-refactor/ClangRefactor.cpp
@@ -0,0 +1,336 @@
+//===--- ClangRefactor.cpp - Clang-based refactoring tool -----------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief This file implements a clang-refactor tool that performs various
+/// source transformations.
+///
+//===----------------------------------------------------------------------===//
+
+#include "TestSupport.h"
+#include "clang/Rewrite/Core/Rewriter.h"
+#include "clang/Tooling/Refactoring.h"
+#include "clang/Tooling/Refactoring/RefactoringEngine.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/raw_ostream.h"
+#include <string>
+
+using namespace clang;
+using namespace tooling;
+using namespace clang_refactor;
+namespace cl = llvm::cl;
+
+namespace opts {
+
+static cl::OptionCategory CommonRefactorOptions("Common refactoring options");
+
+static cl::opt<bool> DumpResults(
+    "dump",
+    cl::desc("Dump the produced source replacements instead of applying them"),
+    cl::cat(CommonRefactorOptions), cl::sub(*cl::AllSubCommands));
+
+static cl::opt<bool>
+    NoDatabases("no-dbs",
+                cl::desc("Ignore external databases including Clang's "
+                         "compilation database and indexer stores"),
+                cl::cat(CommonRefactorOptions), cl::sub(*cl::AllSubCommands));
+
+static cl::opt<bool> Verbose("v", cl::desc("Use verbose output"),
+                             cl::cat(CommonRefactorOptions),
+                             cl::sub(*cl::AllSubCommands));
+} // end namespace opts
+
+namespace {
+
+/// A subcommand that corresponds to individual refactoring action.
+class RefactoringActionSubcommand : public cl::SubCommand {
+public:
+  RefactoringActionSubcommand(StringRef Name, StringRef Description,
+                              RefactoringAction &Action,
+                              cl::OptionCategory &Category, bool HasSelection)
+      : SubCommand(Name, Description), Action(&Action) {
+    Sources = llvm::make_unique<cl::list<std::string>>(
+        cl::Positional, cl::ZeroOrMore, cl::desc("<source0> [... <sourceN>]"),
+        cl::cat(Category), cl::sub(*this));
+    if (HasSelection) {
+      Selection = llvm::make_unique<cl::opt<std::string>>(
+          "selection", cl::desc("Source selection range"), cl::cat(Category),
+          cl::sub(*this));
+    }
+  }
+
+  ~RefactoringActionSubcommand() { unregisterSubCommand(); }
+
+  RefactoringAction &getAction() const { return *Action; }
+
+  StringRef getSelection() const {
+    assert(Selection && "selection not supported!");
+    return *Selection;
+  }
+
+  /// Returns a pointer to the parsed -selection=find(<filename>) option, or
+  /// null if the selection was given in another format.
+  const TestSelectionRangesInFile *getTestSelection() {
+    parseSelection();
+    return ParsedTestSelection ? ParsedTestSelection.getPointer() : nullptr;
+  }
+
+  ArrayRef<std::string> getSources() const { return *Sources; }
+
+private:
+  void parseSelection() {
+    assert(Selection && "selection not supported!");
+    if (IsSelectionParsed)
+      return;
+    IsSelectionParsed = true;
+    // FIXME: Support true selection ranges.
+    StringRef Value = *Selection;
+    if (Value.startswith("test:")) {
+      StringRef Filename = Value.drop_front(strlen("test:"));
+      ParsedTestSelection = findTestSelectionRangesIn(Filename);
+      if (!ParsedTestSelection) {
+        // A parsing error was already reported.
+        exit(1);
+      }
+    } else {
+      llvm::errs() << "error: '-selection' option must be specified using "
+                      "<file>:<line>:<column> or "
+                      "<file>:<line>:<column>-<line>:<column> format";
+      exit(1);
+    }
+  }
+
+  RefactoringAction *Action;
+  std::unique_ptr<cl::list<std::string>> Sources;
+  std::unique_ptr<cl::opt<std::string>> Selection;
+
+  bool IsSelectionParsed;
+  /// The parsed -selection=test:<filename> option.
+  Optional<TestSelectionRangesInFile> ParsedTestSelection;
+};
+
+class ToolRefactoringEngine final : public RefactoringEngine {
+public:
+  std::vector<std::unique_ptr<RefactoringActionSubcommand>> SubCommands;
+  PartialDiagnostic::StorageAllocator DiagnosticStorage;
+
+  void initialize() override {
+    RefactoringEngine::initialize();
+
+    // Create subcommands and command-line options.
+    for (const auto &Action : Actions) {
+      const ActionState &ActionInfo = State[Action.get()];
+      // Check if the selection option is supported.
+      bool HasSelection = false;
+      for (const auto &Rule : ActionInfo.Rules) {
+        if ((HasSelection = Rule->hasSelectionRequirement()))
+          break;
+      }
+
+      SubCommands.push_back(llvm::make_unique<RefactoringActionSubcommand>(
+          Action->getCommand(), Action->getDescription(), *Action,
+          opts::CommonRefactorOptions, HasSelection));
+    }
+  }
+
+  using TUCallbackType = llvm::function_ref<void(ASTContext &)>;
+
+  /// Parses the translation units that were given to the subcommand using
+  /// the 'sources' option and invokes the callback for each parsed
+  /// translation unit.
+  bool foreachTranslationUnit(RefactoringActionSubcommand &Subcommand,
+                              TUCallbackType Callback) {
+    std::unique_ptr<CompilationDatabase> Compilations;
+    if (opts::NoDatabases) {
+      // FIXME (Alex L): Support compilation options.
+      Compilations =
+          llvm::make_unique<clang::tooling::FixedCompilationDatabase>(
+              ".", std::vector<std::string>());
+    } else {
+      // FIXME (Alex L): Support compilation database.
+      llvm::errs() << "compilation databases are not supported yet!\n";
+      return true;
+    }
+
+    class ToolASTConsumer : public ASTConsumer {
+    public:
+      TUCallbackType Callback;
+      ToolASTConsumer(TUCallbackType Callback) : Callback(Callback) {}
+
+      void HandleTranslationUnit(ASTContext &Context) override {
+        Callback(Context);
+      }
+    };
+    class ActionWrapper {
+    public:
+      TUCallbackType Callback;
+      ActionWrapper(TUCallbackType Callback) : Callback(Callback) {}
+
+      std::unique_ptr<ASTConsumer> newASTConsumer() {
+        return llvm::make_unique<ToolASTConsumer>(std::move(Callback));
+      }
+    };
+
+    ClangTool Tool(*Compilations, Subcommand.getSources());
+    ActionWrapper ToolAction(std::move(Callback));
+    std::unique_ptr<tooling::FrontendActionFactory> Factory =
+        tooling::newFrontendActionFactory(&ToolAction);
+    return Tool.run(Factory.get());
+  }
+
+  /// Logs an individual refactoring action invocation to STDOUT.
+  void logInvocation(RefactoringActionSubcommand &Subcommand,
+                     const RefactoringOperationController &Controller) {
+    if (!opts::Verbose)
+      return;
+    llvm::outs() << "invoking action '" << Subcommand.getName() << "':\n";
+    if (Controller.getSelectionRange().isValid()) {
+      SourceRange R = Controller.getSelectionRange();
+      llvm::outs() << "  -selection=";
+      R.getBegin().print(llvm::outs(), Controller.getSources());
+      llvm::outs() << " -> ";
+      R.getEnd().print(llvm::outs(), Controller.getSources());
+      llvm::outs() << "\n";
+    }
+  }
+
+  static PartialDiagnosticAt
+  reportInitiationDiag(const RefactoringActionSubcommand &Subcommand,
+                       RefactoringOperationController &Controller) {
+    return PartialDiagnosticAt(
+        Controller.getSelectionRange().getBegin(),
+        PartialDiagnostic(diag::err_refactor_selection_initiation_failed,
+                          Controller.getDiagnosticStorage())
+            << Subcommand.getName());
+  }
+
+  /// Testing support: invokes the selection action for each selection range in
+  /// the test file.
+  bool
+  runSelectionTestInvocations(RefactoringActionSubcommand &Subcommand,
+                              RefactoringOperationController &Controller,
+                              const TestSelectionRangesInFile &TestSelections,
+                              ArrayRef<RefactoringActionRule *> MatchingRules) {
+    if (opts::Verbose)
+      TestSelections.dump(llvm::outs());
+    if (!opts::DumpResults) {
+      llvm::errs() << "error: test refactoring results can't be applied."
+                      " Did you forget -dump?\n";
+      return true;
+    }
+    return TestSelections.dispatch(
+        Controller.getSources(), Controller.getDiagnostics(),
+        [&](SourceRange R) -> DiagnosticOr<RefactoringResult> {
+          Controller.setSelectionRange(R);
+          logInvocation(Subcommand, Controller);
+          for (RefactoringActionRule *Rule : MatchingRules) {
+            if (!Rule->hasSelectionRequirement())
+              continue;
+            // FIXME (Alex L): Smarter handle for multiple matching rules.
+            DiagnosticOr<RefactoringResult> Result = Rule->perform(Controller);
+            if (!Result && !Result.getDiagnostic().second.getDiagID())
+              return reportInitiationDiag(Subcommand, Controller);
+            return Result;
+          }
+          llvm_unreachable("The action must have at least one selection rule");
+        });
+  }
+
+  bool invokeAction(RefactoringActionSubcommand &Subcommand) {
+    const RefactoringAction &Action = Subcommand.getAction();
+    const ActionState &ActionInfo = State[&Action];
+
+    // Find a set of matching rules.
+    SmallVector<RefactoringActionRule *, 4> MatchingRules;
+    llvm::StringSet<> MissingOptions;
+
+    bool HasSelection = false;
+    for (const auto &Rule : ActionInfo.Rules) {
+      if (Rule->hasSelectionRequirement()) {
+        HasSelection = true;
+        if (!Subcommand.getSelection().empty())
+          MatchingRules.push_back(Rule.get());
+        else
+          MissingOptions.insert("selection");
+      }
+      // FIXME (Alex L): Support custom options.
+    }
+    if (MatchingRules.empty()) {
+      llvm::errs() << "error: '" << Subcommand.getName()
+                   << "' can't be invoked with the given arguments:\n";
+      for (const auto &Opt : MissingOptions)
+        llvm::errs() << "  missing '-" << Opt.getKey() << "' option\n";
+      return true;
+    }
+
+    bool HasFailed = false;
+    if (foreachTranslationUnit(Subcommand, [&](ASTContext &Context) {
+          RefactoringOperationController Controller(Context.getSourceManager(),
+                                                    Context.getDiagnostics());
+          ASTRefactoringOperation Op(Context);
+          Controller.setASTRefactoringOperation(&Op);
+
+          if (HasSelection && Subcommand.getTestSelection()) {
+            // Use a slightly different invocation path for tests.
+            HasFailed = runSelectionTestInvocations(
+                Subcommand, Controller, *Subcommand.getTestSelection(),
+                MatchingRules);
+            return;
+          }
+          // FIXME (Alex L): Implement non-test invocation path.
+          // FIXME (Alex L): If more than one initiation succeeded, then the
+          // rules are ambiguous.
+        }))
+      return true;
+    return HasFailed;
+  }
+};
+
+} // end anonymous namespace
+
+int main(int argc, const char **argv) {
+  ToolRefactoringEngine Engine;
+  Engine.initialize();
+
+  cl::HideUnrelatedOptions(opts::CommonRefactorOptions);
+  cl::ParseCommandLineOptions(
+      argc, argv, "Clang-based refactoring tool for C, C++ and Objective-C");
+  cl::PrintOptionValues();
+
+  // Figure out which action is given by the user.
+  auto It = llvm::find_if(
+      Engine.SubCommands,
+      [](const std::unique_ptr<RefactoringActionSubcommand> &SubCommand) {
+        return !!(*SubCommand);
+      });
+  if (It == Engine.SubCommands.end()) {
+    llvm::errs() << "error: no refactoring action given\n";
+    llvm::errs() << "note: the following actions are supported:\n";
+    for (const auto &Subcommand : Engine.SubCommands)
+      llvm::errs().indent(2) << Subcommand->getName() << "\n";
+    return 1;
+  }
+  RefactoringActionSubcommand &ActionCommand = **It;
+
+  ArrayRef<std::string> Sources = ActionCommand.getSources();
+  // When -no-dbs is used, at least one file (TU) must be given to any
+  // subcommand.
+  if (opts::NoDatabases && Sources.empty()) {
+    llvm::errs() << "error: must provide paths to the source files when "
+                    "'-no-dbs' is used\n";
+    return 1;
+  }
+  if (Engine.invokeAction(ActionCommand))
+    return 1;
+
+  // FIXME (Alex L): Apply refactoring results to the source files.
+  return 0;
+}
Index: tools/clang-refactor/CMakeLists.txt
===================================================================
--- /dev/null
+++ tools/clang-refactor/CMakeLists.txt
@@ -0,0 +1,20 @@
+set(LLVM_LINK_COMPONENTS
+  Option
+  Support
+  )
+
+add_clang_executable(clang-refactor
+  ClangRefactor.cpp
+  TestSupport.cpp
+  )
+
+target_link_libraries(clang-refactor
+  clangBasic
+  clangFrontend
+  clangRewrite
+  clangTooling
+  clangToolingCore
+  clangToolingRefactor
+  )
+
+install(TARGETS clang-refactor RUNTIME DESTINATION bin)
Index: tools/CMakeLists.txt
===================================================================
--- tools/CMakeLists.txt
+++ tools/CMakeLists.txt
@@ -12,6 +12,7 @@
 add_clang_subdirectory(c-index-test)
 
 add_clang_subdirectory(clang-rename)
+add_clang_subdirectory(clang-refactor)
 
 if(CLANG_ENABLE_ARCMT)
   add_clang_subdirectory(arcmt-test)
Index: test/clang-rename/Field.cpp
===================================================================
--- test/clang-rename/Field.cpp
+++ /dev/null
@@ -1,15 +0,0 @@
-class Baz {
-  int Foo; /* Test 1 */ // CHECK: int Bar;
-public:
-  Baz();
-};
-
-Baz::Baz() : Foo(0) /* Test 2 */ {}  // CHECK: Baz::Baz() : Bar(0)
-
-// Test 1.
-// RUN: clang-rename -offset=18 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s
-// Test 2.
-// RUN: clang-rename -offset=89 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s
-
-// To find offsets after modifying the file, use:
-//   grep -Ubo 'Foo.*' <file>
Index: test/Refactor/tool-test-support.c
===================================================================
--- /dev/null
+++ test/Refactor/tool-test-support.c
@@ -0,0 +1,34 @@
+// RUN: clang-refactor local-rename -selection=test:%s -no-dbs -dump -v %s 2>&1 | FileCheck %s
+
+/*range=*/int test;
+
+/*range named=*/int test2;
+
+/*range= +1*/int test3;
+
+/* range = +100 */int test4;
+
+/*range named =+0*/int test5;
+
+// CHECK: Test selection group '':
+// CHECK-NEXT:   106-106
+// CHECK-NEXT:   159-159
+// CHECK-NEXT:   198-198
+// CHECK-NEXT: Test selection group 'named':
+// CHECK-NEXT:   133-133
+// CHECK-NEXT:   219-219
+
+// CHECK: invoking action 'local-rename':
+// CHECK-NEXT: -selection={{.*}}tool-test-support.c:3:11
+
+// CHECK: invoking action 'local-rename':
+// CHECK-NEXT: -selection={{.*}}tool-test-support.c:7:15
+
+// CHECK: invoking action 'local-rename':
+// CHECK-NEXT: -selection={{.*}}tool-test-support.c:9:29
+
+// CHECK: invoking action 'local-rename':
+// CHECK-NEXT: -selection={{.*}}tool-test-support.c:5:17
+
+// CHECK: invoking action 'local-rename':
+// CHECK-NEXT: -selection={{.*}}tool-test-support.c:11:20
Index: test/Refactor/tool-common-options.c
===================================================================
--- /dev/null
+++ test/Refactor/tool-common-options.c
@@ -0,0 +1,6 @@
+// RUN: not clang-refactor 2>&1 | FileCheck --check-prefix=MISSING_ACTION %s
+// MISSING_ACTION: error: no refactoring action given
+// MISSING_ACTION-NEXT: note: the following actions are supported:
+
+// RUN: not clang-refactor local-rename -no-dbs 2>&1 | FileCheck --check-prefix=MISSING_SOURCES %s
+// MISSING_SOURCES: error: must provide paths to the source files when '-no-dbs' is used
Index: test/Refactor/LocalRename/Field.cpp
===================================================================
--- /dev/null
+++ test/Refactor/LocalRename/Field.cpp
@@ -0,0 +1,9 @@
+// RUN: clang-refactor local-rename -selection=test:%s -no-dbs -dump %s | FileCheck %s
+
+class Baz {
+  int /*range=*/Foo; // CHECK: symbol [[@LINE]]:17 -> [[@LINE]]:20
+public:
+  Baz();
+};
+
+Baz::Baz() : /*range=*/Foo(0) {}  // CHECK: symbol [[@LINE]]:24 -> [[@LINE]]:27
Index: lib/Tooling/Refactoring/Rename/USRFindingAction.cpp
===================================================================
--- lib/Tooling/Refactoring/Rename/USRFindingAction.cpp
+++ lib/Tooling/Refactoring/Rename/USRFindingAction.cpp
@@ -154,6 +154,12 @@
 };
 } // namespace
 
+std::vector<std::string> getUSRsForDeclaration(const NamedDecl *ND,
+                                               ASTContext &Context) {
+  AdditionalUSRFinder Finder(ND, Context);
+  return Finder.Find();
+}
+
 class NamedDeclFindingConsumer : public ASTConsumer {
 public:
   NamedDeclFindingConsumer(ArrayRef<unsigned> SymbolOffsets,
Index: lib/Tooling/Refactoring/Rename/RenamingAction.cpp
===================================================================
--- lib/Tooling/Refactoring/Rename/RenamingAction.cpp
+++ lib/Tooling/Refactoring/Rename/RenamingAction.cpp
@@ -22,17 +22,73 @@
 #include "clang/Lex/Preprocessor.h"
 #include "clang/Tooling/CommonOptionsParser.h"
 #include "clang/Tooling/Refactoring.h"
+#include "clang/Tooling/Refactoring/RefactoringAction.h"
+#include "clang/Tooling/Refactoring/RefactoringActionRules.h"
+#include "clang/Tooling/Refactoring/Rename/USRFinder.h"
+#include "clang/Tooling/Refactoring/Rename/USRFindingAction.h"
 #include "clang/Tooling/Refactoring/Rename/USRLocFinder.h"
 #include "clang/Tooling/Tooling.h"
 #include "llvm/ADT/STLExtras.h"
 #include <string>
 #include <vector>
 
 using namespace llvm;
+using namespace clang;
+using namespace tooling;
+
+namespace {
+
+class LocalRename : public RefactoringAction {
+public:
+  StringRef getCommand() const override { return "local-rename"; }
+
+  StringRef getDescription() const override {
+    return "Finds and renames symbols in code with no indexer support";
+  }
+
+  static Expected<RefactoringResult>
+  findOccurrences(const ASTRefactoringOperation &Op, const NamedDecl *ND) {
+    std::vector<std::string> USRs =
+        getUSRsForDeclaration(ND, Op.getASTContext());
+    std::string PrevName = ND->getNameAsString();
+    return getOccurrencesOfUSRs(USRs, PrevName,
+                                Op.getASTContext().getTranslationUnitDecl());
+    // FIXME: Create the atomic changes when -new-name is given.
+  }
+
+  class SymbolSelectionRequirement : public selection::Requirement {
+  public:
+    DiagnosticOr<const NamedDecl *>
+    evaluateSelection(const ASTRefactoringOperation &Op,
+                      selection::SourceSelectionRange Selection) const {
+      const NamedDecl *ND =
+          getNamedDeclAt(Op.getASTContext(), Selection.getRange().getBegin());
+      if (!ND)
+        return invalidSelectionError();
+      return getCanonicalSymbolDeclaration(ND);
+    }
+  };
+
+  /// Returns a set of refactoring actions rules that are defined by this
+  /// action.
+  RefactoringActionRules createActionRules() const override {
+    using namespace refactoring_action_rules;
+    RefactoringActionRules Rules;
+    Rules.push_back(apply(findOccurrences,
+                          requiredSelection(SymbolSelectionRequirement())));
+    return Rules;
+  }
+};
+
+} // end anonymous namespace
 
 namespace clang {
 namespace tooling {
 
+std::unique_ptr<RefactoringAction> createLocalRenameAction() {
+  return llvm::make_unique<LocalRename>();
+}
+
 Expected<std::vector<AtomicChange>>
 createRenameReplacements(const SymbolOccurrences &Occurrences,
                          const SourceManager &SM,
Index: lib/Tooling/Refactoring/RefactoringEngine.cpp
===================================================================
--- /dev/null
+++ lib/Tooling/Refactoring/RefactoringEngine.cpp
@@ -0,0 +1,97 @@
+//===--- RefactoringEngine.cpp - Common refactoring engine code -----------===//
+//
+//                     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/RefactoringEngine.h"
+#include "clang/Basic/SourceManager.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/Errc.h"
+
+namespace clang {
+namespace tooling {
+
+// Forward declare the individual create*Action functions.
+#define REFACTORING_ACTION(Name)                                               \
+  std::unique_ptr<RefactoringAction> create##Name##Action();
+#include "clang/Tooling/Refactoring/RefactoringActionRegistry.def"
+
+} // end namespace tooling
+} // end namespace clang
+
+using namespace clang;
+using namespace tooling;
+
+void RefactoringEngine::initialize() {
+// Create the refactoring action.
+#define REFACTORING_ACTION(Name) Actions.push_back(create##Name##Action());
+#include "clang/Tooling/Refactoring/RefactoringActionRegistry.def"
+
+  // Create the refactoring action rules.
+  for (const auto &Action : Actions) {
+    RefactoringActionRules Rules = Action->createActionRules();
+    // FIXME (Alex L): Drop unsupported rules.
+    State[Action.get()].Rules = std::move(Rules);
+  }
+}
+
+void RefactoringResult::dump(llvm::raw_ostream &OS, const SourceManager &SM,
+                             int Indent) const {
+  switch (getKind()) {
+  case AtomicChanges:
+    // FIXME (Alex L): Dump atomic changes.
+    break;
+  case SymbolOccurrencesKind:
+    for (const SymbolOccurrence &Occurrence : getSymbolOccurrences()) {
+      OS.indent(Indent);
+      switch (Occurrence.getKind()) {
+      case SymbolOccurrence::MatchingSymbol:
+        OS << "symbol ";
+        break;
+      }
+      for (const auto &I : llvm::enumerate(Occurrence.getNameRanges())) {
+        if (I.index())
+          OS << ", ";
+        SourceRange Range = I.value();
+        auto DumpLoc = [&](SourceLocation L) {
+          std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(L);
+          if (LocInfo.first.isInvalid()) {
+            OS << "<invalid>";
+            return;
+          }
+          OS << SM.getLineNumber(LocInfo.first, LocInfo.second) << ':'
+             << SM.getColumnNumber(LocInfo.first, LocInfo.second);
+        };
+        DumpLoc(Range.getBegin());
+        OS << " -> ";
+        DumpLoc(Range.getEnd());
+        OS << "\n";
+      }
+    }
+    break;
+  }
+}
+
+bool RefactoringResult::operator==(const RefactoringResult &Other) const {
+  if (getKind() != Other.getKind())
+    return false;
+  switch (getKind()) {
+  case AtomicChanges:
+    // FIXME (Alex L): Compare atomic changes.
+    return false;
+  case SymbolOccurrencesKind:
+    if (getSymbolOccurrences().size() != Other.getSymbolOccurrences().size())
+      return false;
+    for (const auto &I :
+         llvm::zip(getSymbolOccurrences(), Other.getSymbolOccurrences())) {
+      if (std::get<0>(I) != std::get<1>(I))
+        return false;
+    }
+    break;
+  }
+  return true;
+}
Index: lib/Tooling/Refactoring/CMakeLists.txt
===================================================================
--- lib/Tooling/Refactoring/CMakeLists.txt
+++ lib/Tooling/Refactoring/CMakeLists.txt
@@ -2,6 +2,7 @@
 
 add_clang_library(clangToolingRefactor
   AtomicChange.cpp
+  RefactoringEngine.cpp
   Rename/RenamingAction.cpp
   Rename/SymbolOccurrences.cpp
   Rename/USRFinder.cpp
Index: include/clang/module.modulemap
===================================================================
--- include/clang/module.modulemap
+++ include/clang/module.modulemap
@@ -138,6 +138,8 @@
   // importing the AST matchers library gives a link dependency on the AST
   // matchers (and thus the AST), which clang-format should not have.
   exclude header "Tooling/RefactoringCallbacks.h"
+
+  textual header "Tooling/Refactoring/RefactoringActionRegistry.def"
 }
 
 module Clang_ToolingCore {
Index: include/clang/Tooling/Refactoring/SourceSelectionConstraints.h
===================================================================
--- include/clang/Tooling/Refactoring/SourceSelectionConstraints.h
+++ include/clang/Tooling/Refactoring/SourceSelectionConstraints.h
@@ -15,6 +15,9 @@
 
 namespace clang {
 namespace tooling {
+
+class ASTRefactoringOperation;
+
 namespace selection {
 
 /// This constraint is satisfied when any portion of the source text is
@@ -33,7 +36,11 @@
 
 /// A custom selection requirement.
 class Requirement {
-  /// Subclasses must implement 'T evaluateSelection(SelectionConstraint) const'
+  /// Subclasses must implement
+  /// 'T evaluateSelection(const ASTRefactoringOperation &,
+  ///                      SelectionConstraint) const'
+  /// or
+  /// '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
@@ -57,9 +64,18 @@
 template <typename T> struct EvaluateSelectionChecker : std::false_type {};
 
 template <typename T, typename R, typename A>
+struct EvaluateSelectionChecker<R (T::*)(const ASTRefactoringOperation &, A)
+                                    const> : std::true_type {
+  using ReturnType = R;
+  using ArgType = A;
+  using OperationType = ASTRefactoringOperation;
+};
+
+template <typename T, typename R, typename A>
 struct EvaluateSelectionChecker<R (T::*)(A) const> : std::true_type {
   using ReturnType = R;
   using ArgType = A;
+  using OperationType = void;
 };
 
 template <typename T> class Identity : public Requirement {
@@ -94,6 +110,16 @@
                   &T::evaluateSelection)>::ArgType>::value,
           std::true_type, std::false_type>::type {};
 
+/// A type trait that returns the type of the operation on which the selection
+/// operates.
+///
+/// Operation can be either \c ASTRefactoringOperation which requires parsed
+/// AST, or \c void which doesn't require the parsed AST.
+template <typename T> struct RequirementOperationType {
+  using Type = typename detail::EvaluateSelectionChecker<decltype(
+      &T::evaluateSelection)>::OperationType;
+};
+
 } // end namespace traits
 } // end namespace selection
 } // end namespace tooling
Index: include/clang/Tooling/Refactoring/Rename/USRFindingAction.h
===================================================================
--- include/clang/Tooling/Refactoring/Rename/USRFindingAction.h
+++ include/clang/Tooling/Refactoring/Rename/USRFindingAction.h
@@ -23,6 +23,7 @@
 
 namespace clang {
 class ASTConsumer;
+class ASTContext;
 class CompilerInstance;
 class NamedDecl;
 
@@ -37,6 +38,10 @@
 /// - A destructor is canonicalized to its class.
 const NamedDecl *getCanonicalSymbolDeclaration(const NamedDecl *FoundDecl);
 
+/// Returns the set of USRs that correspond to the given declaration.
+std::vector<std::string> getUSRsForDeclaration(const NamedDecl *ND,
+                                               ASTContext &Context);
+
 struct USRFindingAction {
   USRFindingAction(ArrayRef<unsigned> SymbolOffsets,
                    ArrayRef<std::string> QualifiedNames, bool Force)
Index: include/clang/Tooling/Refactoring/Rename/SymbolOccurrences.h
===================================================================
--- include/clang/Tooling/Refactoring/Rename/SymbolOccurrences.h
+++ include/clang/Tooling/Refactoring/Rename/SymbolOccurrences.h
@@ -77,6 +77,22 @@
     return RangeOrNumRanges;
   }
 
+  bool operator==(const SymbolOccurrence &Other) const {
+    if (Kind != Other.Kind)
+      return false;
+    if (getNameRanges().size() != Other.getNameRanges().size())
+      return false;
+    for (size_t I = 0, E = getNameRanges().size(); I != E; ++I) {
+      if (getNameRanges()[I] != Other.getNameRanges()[I])
+        return false;
+    }
+    return true;
+  }
+
+  bool operator!=(const SymbolOccurrence &Other) const {
+    return !(*this == Other);
+  }
+
 private:
   OccurrenceKind Kind;
   std::unique_ptr<SourceRange[]> MultipleRanges;
Index: include/clang/Tooling/Refactoring/RefactoringResult.h
===================================================================
--- include/clang/Tooling/Refactoring/RefactoringResult.h
+++ include/clang/Tooling/Refactoring/RefactoringResult.h
@@ -11,6 +11,7 @@
 #define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_RESULT_H
 
 #include "clang/Tooling/Refactoring/AtomicChange.h"
+#include "clang/Tooling/Refactoring/Rename/SymbolOccurrences.h"
 
 namespace clang {
 namespace tooling {
@@ -21,12 +22,16 @@
   enum ResultKind {
     /// A set of source replacements represented using a vector of
     /// \c AtomicChanges.
-    AtomicChanges
+    AtomicChanges,
+
+    SymbolOccurrencesKind
   };
 
   RefactoringResult(AtomicChange Change) : Kind(AtomicChanges) {
     Changes.push_back(std::move(Change));
   }
+  RefactoringResult(SymbolOccurrences Occurrences)
+      : Kind(SymbolOccurrencesKind), Occurrences(std::move(Occurrences)) {}
   RefactoringResult(RefactoringResult &&Other) = default;
   RefactoringResult &operator=(RefactoringResult &&Other) = default;
 
@@ -38,9 +43,24 @@
     return Changes;
   }
 
+  ArrayRef<SymbolOccurrence> getSymbolOccurrences() const {
+    assert(getKind() == SymbolOccurrencesKind &&
+           "Refactoring didn't produce occurrences");
+    return Occurrences;
+  }
+
+  void dump(llvm::raw_ostream &OS, const SourceManager &SM,
+            int Indent = 0) const;
+
+  bool operator==(const RefactoringResult &Other) const;
+  bool operator!=(const RefactoringResult &Other) const {
+    return !(*this == Other);
+  }
+
 private:
   ResultKind Kind;
   std::vector<AtomicChange> Changes;
+  SymbolOccurrences Occurrences;
 };
 
 } // end namespace tooling
Index: include/clang/Tooling/Refactoring/RefactoringOperationController.h
===================================================================
--- include/clang/Tooling/Refactoring/RefactoringOperationController.h
+++ include/clang/Tooling/Refactoring/RefactoringOperationController.h
@@ -14,32 +14,58 @@
 #include "clang/Basic/SourceManager.h"
 
 namespace clang {
+
+class ASTContext;
+
 namespace tooling {
 
+/// A refactoring operation that requires the parsed AST.
+class ASTRefactoringOperation {
+public:
+  ASTRefactoringOperation(ASTContext &Context) : Context(Context) {}
+
+  ASTContext &getASTContext() const { return Context; }
+
+private:
+  ASTContext &Context;
+};
+
 /// Encapsulates all of the possible state that an individual refactoring
 /// operation might have. Controls the process of initiation of refactoring
 /// operations, by feeding the right information to the functions that
 /// evaluate the refactoring action rule requirements.
 class RefactoringOperationController {
 public:
-  RefactoringOperationController(const SourceManager &SM) : SM(SM) {}
+  RefactoringOperationController(const SourceManager &SM,
+                                 DiagnosticsEngine &Diags)
+      : SM(SM), Diags(Diags) {}
 
   PartialDiagnostic::StorageAllocator &getDiagnosticStorage() {
     return DiagnosticStorage;
   }
 
   const SourceManager &getSources() const { return SM; }
 
+  DiagnosticsEngine &getDiagnostics() const { return Diags; }
+
   /// 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; }
 
+  void setASTRefactoringOperation(ASTRefactoringOperation *Op) {
+    ASTOperation = Op;
+  }
+
+  ASTRefactoringOperation *getASTOperation() const { return ASTOperation; }
+
 private:
   const SourceManager &SM;
+  DiagnosticsEngine &Diags;
   SourceRange SelectionRange;
   PartialDiagnostic::StorageAllocator DiagnosticStorage;
+  ASTRefactoringOperation *ASTOperation = nullptr;
 };
 
 } // end namespace tooling
Index: include/clang/Tooling/Refactoring/RefactoringEngine.h
===================================================================
--- /dev/null
+++ include/clang/Tooling/Refactoring/RefactoringEngine.h
@@ -0,0 +1,42 @@
+//===--- RefactoringEngine.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_ENGINE_H
+#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ENGINE_H
+
+#include "clang/Tooling/Refactoring/RefactoringAction.h"
+#include "clang/Tooling/Refactoring/RefactoringActionRules.h"
+#include "llvm/ADT/DenseMap.h"
+#include <vector>
+
+namespace clang {
+namespace tooling {
+
+/// A base class for an editor or a tool refactoring engine.
+///
+/// Contains the list of supported actions and refactoring action rules.
+class RefactoringEngine {
+public:
+  virtual ~RefactoringEngine() {}
+
+  virtual void initialize();
+
+protected:
+  struct ActionState {
+    RefactoringActionRules Rules;
+  };
+
+  std::vector<std::unique_ptr<RefactoringAction>> Actions;
+  llvm::DenseMap<const RefactoringAction *, ActionState> State;
+};
+
+} // end namespace tooling
+} // end namespace clang
+
+#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULES_H
Index: include/clang/Tooling/Refactoring/RefactoringActionRules.h
===================================================================
--- include/clang/Tooling/Refactoring/RefactoringActionRules.h
+++ include/clang/Tooling/Refactoring/RefactoringActionRules.h
@@ -35,6 +35,8 @@
   /// rule's function runs successfully.
   virtual DiagnosticOr<RefactoringResult>
   perform(RefactoringOperationController &Controller) = 0;
+
+  virtual bool hasSelectionRequirement() = 0;
 };
 
 /// A set of refactoring action rules that should have unique initiation
@@ -94,16 +96,52 @@
   friend class BaseSpecializedRule;
 };
 
+/// A type trait that returns true when the given type has at least one source
+/// selection requirement.
+template <typename First, typename... Rest>
+struct HasSelectionRequirement
+    : std::conditional<HasSelectionRequirement<First>::value ||
+                           HasSelectionRequirement<Rest...>::value,
+                       std::true_type, std::false_type>::type {};
+
+template <typename I, typename O, typename R>
+struct HasSelectionRequirement<SourceSelectionRequirement<I, O, R>>
+    : std::true_type {};
+
+template <typename T> struct HasSelectionRequirement<T> : std::false_type {};
+
 /// A wrapper class around \c RefactoringActionRule that defines some helper
 /// methods that are used by the subclasses.
 class BaseSpecializedRule : public RefactoringActionRule {
 protected:
   /// Evaluates a source selection action rule requirement.
   template <typename InputT, typename OutputT, typename RequirementT>
-  static DiagnosticOr<typename DropDiagnosticOr<OutputT>::Type>
-  evaluate(RefactoringOperationController &Controller,
-           const SourceSelectionRequirement<InputT, OutputT, RequirementT>
-               &SelectionRequirement) {
+  static DiagnosticOr<typename DropDiagnosticOr<OutputT>::Type> evaluate(
+      RefactoringOperationController &Controller,
+      const SourceSelectionRequirement<InputT, OutputT, RequirementT>
+          &SelectionRequirement,
+      typename std::enable_if<
+          std::is_same<typename selection::traits::RequirementOperationType<
+                           RequirementT>::Type,
+                       ASTRefactoringOperation>::value>::type * = nullptr) {
+    Optional<InputT> Value = evalSelection<InputT>(Controller);
+    if (!Value)
+      return invalidSelectionError();
+    const ASTRefactoringOperation *Op = Controller.getASTOperation();
+    assert(Op && "not an AST operation!");
+    return std::move(
+        SelectionRequirement.Requirement.evaluateSelection(*Op, *Value));
+  }
+
+  template <typename InputT, typename OutputT, typename RequirementT>
+  static DiagnosticOr<typename DropDiagnosticOr<OutputT>::Type> evaluate(
+      RefactoringOperationController &Controller,
+      const SourceSelectionRequirement<InputT, OutputT, RequirementT>
+          &SelectionRequirement,
+      typename std::enable_if<
+          std::is_same<typename selection::traits::RequirementOperationType<
+                           RequirementT>::Type,
+                       void>::value>::type * = nullptr) {
     Optional<InputT> Value = evalSelection<InputT>(Controller);
     if (!Value)
       return invalidSelectionError();
@@ -164,7 +202,8 @@
 /// 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 FunctionType, typename... RequirementTypes>
+template <typename FunctionType, typename OperationType,
+          typename... RequirementTypes>
 class PlainFunctionRule final : public BaseSpecializedRule {
 public:
   PlainFunctionRule(FunctionType Function,
@@ -177,20 +216,46 @@
                        llvm::index_sequence_for<RequirementTypes...>());
   }
 
+  bool hasSelectionRequirement() override {
+    return HasSelectionRequirement<RequirementTypes...>::value;
+  }
+
 private:
+  template <typename OpType, typename ValueType, size_t... Is>
+  Expected<RefactoringResult> performImplDispatch(
+      RefactoringOperationController &Controller, llvm::index_sequence<Is...>,
+      ValueType &Values,
+      typename std::enable_if<
+          std::is_same<OpType, ASTRefactoringOperation>::value>::type * =
+          nullptr) {
+    assert(Controller.getASTOperation() && "Not an AST-based operation!");
+    return Function(*Controller.getASTOperation(),
+                    removeDiagOr(std::move(std::get<Is>(Values)))...);
+  }
+
+  template <typename OpType, typename ValueType, size_t... Is>
+  Expected<RefactoringResult> performImplDispatch(
+      RefactoringOperationController &, llvm::index_sequence<Is...>,
+      ValueType &Values,
+      typename std::enable_if<std::is_same<OpType, void>::value>::type * =
+          nullptr) {
+    return Function(removeDiagOr(std::move(std::get<Is>(Values)))...);
+  }
+
   template <size_t... Is>
   DiagnosticOr<RefactoringResult>
   performImpl(RefactoringOperationController &Controller,
-              llvm::index_sequence<Is...>) {
+              llvm::index_sequence<Is...> Seq) {
     // Initiate the operation.
     auto Values =
         std::make_tuple(evaluate(Controller, std::get<Is>(Requirements))...);
     OptionalDiag InitiationFailure = findDiag(std::get<Is>(Values)...);
     if (InitiationFailure)
       return std::move(*InitiationFailure);
     // Perform the operation.
     Expected<RefactoringResult> Result =
-        Function(removeDiagOr(std::move(std::get<Is>(Values)))...);
+        performImplDispatch<OperationType, decltype(Values), Is...>(
+            Controller, Seq, Values);
     if (Result)
       return std::move(*Result);
     std::string Error = llvm::toString(Result.takeError());
@@ -225,12 +290,27 @@
 template <typename... RequirementTypes>
 std::unique_ptr<RefactoringActionRule>
 apply(Expected<RefactoringResult> (*RefactoringFunction)(
+          const ASTRefactoringOperation &Op,
+          typename RequirementTypes::OutputType...),
+      const RequirementTypes &... Requirements) {
+  static_assert(traits::IsRequirement<RequirementTypes...>::value,
+                "invalid refactoring action rule requirement");
+  return llvm::make_unique<
+      detail::PlainFunctionRule<decltype(RefactoringFunction),
+                                ASTRefactoringOperation, RequirementTypes...>>(
+      RefactoringFunction, std::make_tuple(Requirements...));
+}
+
+template <typename... RequirementTypes>
+std::unique_ptr<RefactoringActionRule>
+apply(Expected<RefactoringResult> (*RefactoringFunction)(
           typename RequirementTypes::OutputType...),
       const RequirementTypes &... Requirements) {
   static_assert(traits::IsRequirement<RequirementTypes...>::value,
                 "invalid refactoring action rule requirement");
-  return llvm::make_unique<detail::PlainFunctionRule<
-      decltype(RefactoringFunction), RequirementTypes...>>(
+  return llvm::make_unique<
+      detail::PlainFunctionRule<decltype(RefactoringFunction),
+                                /*OperationType=*/void, RequirementTypes...>>(
       RefactoringFunction, std::make_tuple(Requirements...));
 }
 
Index: include/clang/Tooling/Refactoring/RefactoringActionRegistry.def
===================================================================
--- /dev/null
+++ include/clang/Tooling/Refactoring/RefactoringActionRegistry.def
@@ -0,0 +1,7 @@
+#ifndef REFACTORING_ACTION
+#define REFACTORING_ACTION(Name)
+#endif
+
+REFACTORING_ACTION(LocalRename)
+
+#undef REFACTORING_ACTION
Index: include/clang/Tooling/Refactoring/RefactoringAction.h
===================================================================
--- /dev/null
+++ include/clang/Tooling/Refactoring/RefactoringAction.h
@@ -0,0 +1,38 @@
+//===--- RefactoringAction.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_H
+#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_H
+
+#include "clang/Basic/LLVM.h"
+#include "clang/Tooling/Refactoring/RefactoringActionRules.h"
+#include <vector>
+
+namespace clang {
+namespace tooling {
+
+class RefactoringAction {
+public:
+  /// Returns the name of the subcommand that's used by clang-refactor for this
+  /// action.
+  virtual StringRef getCommand() const = 0;
+
+  virtual StringRef getDescription() const = 0;
+
+  /// Returns a set of refactoring actions rules that are defined by this
+  /// action.
+  virtual RefactoringActionRules createActionRules() const = 0;
+
+  virtual ~RefactoringAction() {}
+};
+
+} // end namespace tooling
+} // end namespace clang
+
+#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_H
Index: include/clang/Basic/DiagnosticRefactoringKinds.td
===================================================================
--- include/clang/Basic/DiagnosticRefactoringKinds.td
+++ include/clang/Basic/DiagnosticRefactoringKinds.td
@@ -19,4 +19,11 @@
 
 }
 
+let CategoryName = "clang-refactor Issue" in {
+
+def err_refactor_selection_initiation_failed : Error<
+  "action '%0' can't be initiated with the specified selection range">;
+
+}
+
 } // end of Refactoring diagnostics
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to