r313244 - [refactor] add clang-refactor tool with initial testing support and

Alex Lorenz via cfe-commits cfe-commits at lists.llvm.org
Thu Sep 14 03:06:52 PDT 2017


Author: arphaman
Date: Thu Sep 14 03:06:52 2017
New Revision: 313244

URL: http://llvm.org/viewvc/llvm-project?rev=313244&view=rev
Log:
[refactor] add clang-refactor tool with initial testing support and
local-rename action

This commit introduces the clang-refactor tool alongside the local-rename action
which uses the existing renaming engine used by clang-rename. The tool
doesn't actually perform the source transformations yet, it just provides
testing support. This commit also moves only one test from clang-rename over to
test/Refactor. I will continue to move the other tests throughout
development of clang-refactor.

The following options are supported by clang-refactor:

-v: use verbose output
-selection: The source range that corresponds to the portion of the source
 that's selected (currently only special command test:<file> is supported).

Please note that a follow-up commit will migrate clang-refactor to
libTooling's common option parser, so clang-refactor will be able to use
the common interface with compilation database and options like -p, -extra-arg,
etc.

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 in TestSupport.h.

Differential Revision: https://reviews.llvm.org/D36574

Added:
    cfe/trunk/include/clang/Tooling/Refactoring/RefactoringAction.h
    cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRegistry.def
    cfe/trunk/lib/Tooling/Refactoring/RefactoringActions.cpp
    cfe/trunk/test/Refactor/
    cfe/trunk/test/Refactor/LocalRename/
    cfe/trunk/test/Refactor/LocalRename/Field.cpp
    cfe/trunk/test/Refactor/tool-common-options.c
    cfe/trunk/test/Refactor/tool-test-support.c
    cfe/trunk/tools/clang-refactor/
    cfe/trunk/tools/clang-refactor/CMakeLists.txt
    cfe/trunk/tools/clang-refactor/ClangRefactor.cpp
    cfe/trunk/tools/clang-refactor/TestSupport.cpp
    cfe/trunk/tools/clang-refactor/TestSupport.h
Removed:
    cfe/trunk/test/clang-rename/Field.cpp
Modified:
    cfe/trunk/include/clang/Tooling/Refactoring/AtomicChange.h
    cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRule.h
    cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirementsInternal.h
    cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRules.h
    cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h
    cfe/trunk/include/clang/Tooling/Refactoring/RefactoringRuleContext.h
    cfe/trunk/include/clang/Tooling/Refactoring/Rename/USRFindingAction.h
    cfe/trunk/include/clang/Tooling/Refactoring/SourceSelectionConstraints.h
    cfe/trunk/include/clang/module.modulemap
    cfe/trunk/lib/Tooling/Refactoring/AtomicChange.cpp
    cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt
    cfe/trunk/lib/Tooling/Refactoring/Rename/RenamingAction.cpp
    cfe/trunk/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp
    cfe/trunk/test/CMakeLists.txt
    cfe/trunk/tools/CMakeLists.txt
    cfe/trunk/unittests/Tooling/RefactoringActionRulesTest.cpp

Modified: cfe/trunk/include/clang/Tooling/Refactoring/AtomicChange.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/Refactoring/AtomicChange.h?rev=313244&r1=313243&r2=313244&view=diff
==============================================================================
--- cfe/trunk/include/clang/Tooling/Refactoring/AtomicChange.h (original)
+++ cfe/trunk/include/clang/Tooling/Refactoring/AtomicChange.h Thu Sep 14 03:06:52 2017
@@ -52,6 +52,8 @@ public:
   AtomicChange &operator=(AtomicChange &&) = default;
   AtomicChange &operator=(const AtomicChange &) = default;
 
+  bool operator==(const AtomicChange &Other) const;
+
   /// \brief Returns the atomic change as a YAML string.
   std::string toYAMLString();
 

Added: cfe/trunk/include/clang/Tooling/Refactoring/RefactoringAction.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/Refactoring/RefactoringAction.h?rev=313244&view=auto
==============================================================================
--- cfe/trunk/include/clang/Tooling/Refactoring/RefactoringAction.h (added)
+++ cfe/trunk/include/clang/Tooling/Refactoring/RefactoringAction.h Thu Sep 14 03:06:52 2017
@@ -0,0 +1,64 @@
+//===--- 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 {
+
+/// A refactoring action is a class that defines a set of related refactoring
+/// action rules. These rules get grouped under a common umbrella - a single
+/// clang-refactor subcommand.
+///
+/// A subclass of \c RefactoringAction is responsible for creating the set of
+/// grouped refactoring action rules that represent one refactoring operation.
+/// Although the rules in one action may have a number of different
+/// implementations, they should strive to produce a similar result. It should
+/// be easy for users to identify which refactoring action produced the result
+/// regardless of which refactoring action rule was used.
+///
+/// The distinction between actions and rules enables the creation of action
+/// that uses very different rules, for example:
+///   - local vs global: a refactoring operation like
+///     "add missing switch cases" can be applied to one switch when it's
+///     selected in an editor, or to all switches in a project when an enum
+///     constant is added to an enum.
+///   - tool vs editor: some refactoring operation can be initiated in the
+///     editor when a declaration is selected, or in a tool when the name of
+///     the declaration is passed using a command-line argument.
+class RefactoringAction {
+public:
+  virtual ~RefactoringAction() {}
+
+  /// 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;
+
+  RefactoringActionRules createActiveActionRules();
+
+protected:
+  /// Returns a set of refactoring actions rules that are defined by this
+  /// action.
+  virtual RefactoringActionRules createActionRules() const = 0;
+};
+
+/// Returns the list of all the available refactoring actions.
+std::vector<std::unique_ptr<RefactoringAction>> createRefactoringActions();
+
+} // end namespace tooling
+} // end namespace clang
+
+#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_H

Added: cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRegistry.def
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRegistry.def?rev=313244&view=auto
==============================================================================
--- cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRegistry.def (added)
+++ cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRegistry.def Thu Sep 14 03:06:52 2017
@@ -0,0 +1,7 @@
+#ifndef REFACTORING_ACTION
+#define REFACTORING_ACTION(Name)
+#endif
+
+REFACTORING_ACTION(LocalRename)
+
+#undef REFACTORING_ACTION

Modified: cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRule.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRule.h?rev=313244&r1=313243&r2=313244&view=diff
==============================================================================
--- cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRule.h (original)
+++ cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRule.h Thu Sep 14 03:06:52 2017
@@ -30,6 +30,10 @@ public:
   /// consumer to propagate the result of the refactoring action.
   virtual void invoke(RefactoringResultConsumer &Consumer,
                       RefactoringRuleContext &Context) = 0;
+
+  /// Returns true when the rule has a source selection requirement that has
+  /// to be fullfilled before refactoring can be performed.
+  virtual bool hasSelectionRequirement() = 0;
 };
 
 /// A set of refactoring action rules that should have unique initiation

Modified: cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirementsInternal.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirementsInternal.h?rev=313244&r1=313243&r2=313244&view=diff
==============================================================================
--- cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirementsInternal.h (original)
+++ cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirementsInternal.h Thu Sep 14 03:06:52 2017
@@ -58,7 +58,7 @@ struct SourceSelectionRequirement
     Optional<InputT> Value = InputT::evaluate(Context);
     if (!Value)
       return None;
-    return std::move(Requirement.evaluateSelection(*Value));
+    return std::move(Requirement.evaluateSelection(Context, *Value));
   }
 
 private:
@@ -82,6 +82,20 @@ struct IsRequirement<T>
     : std::conditional<std::is_base_of<internal::RequirementBase, T>::value,
                        std::true_type, std::false_type>::type {};
 
+/// 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<internal::SourceSelectionRequirement<I, O, R>>
+    : std::true_type {};
+
+template <typename T> struct HasSelectionRequirement<T> : std::false_type {};
+
 } // end namespace traits
 } // end namespace refactoring_action_rules
 } // end namespace tooling

Modified: cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRules.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRules.h?rev=313244&r1=313243&r2=313244&view=diff
==============================================================================
--- cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRules.h (original)
+++ cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRules.h Thu Sep 14 03:06:52 2017
@@ -15,6 +15,9 @@
 
 namespace clang {
 namespace tooling {
+
+class RefactoringRuleContext;
+
 namespace refactoring_action_rules {
 
 /// Creates a new refactoring action rule that invokes the given function once
@@ -40,6 +43,7 @@ namespace refactoring_action_rules {
 template <typename ResultType, typename... RequirementTypes>
 std::unique_ptr<RefactoringActionRule>
 createRefactoringRule(Expected<ResultType> (*RefactoringFunction)(
+                          const RefactoringRuleContext &,
                           typename RequirementTypes::OutputType...),
                       const RequirementTypes &... Requirements) {
   static_assert(tooling::traits::IsValidRefactoringResult<ResultType>::value,
@@ -56,13 +60,12 @@ template <
     typename Fn = decltype(&Callable::operator()),
     typename ResultType = typename internal::LambdaDeducer<Fn>::ReturnType,
     bool IsNonCapturingLambda = std::is_convertible<
-        Callable,
-        ResultType (*)(typename RequirementTypes::OutputType...)>::value,
+        Callable, typename internal::LambdaDeducer<Fn>::FunctionType>::value,
     typename = typename std::enable_if<IsNonCapturingLambda>::type>
 std::unique_ptr<RefactoringActionRule>
 createRefactoringRule(const Callable &C,
                       const RequirementTypes &... Requirements) {
-  ResultType (*Func)(typename RequirementTypes::OutputType...) = C;
+  typename internal::LambdaDeducer<Fn>::FunctionType Func = C;
   return createRefactoringRule(Func, Requirements...);
 }
 

Modified: cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h?rev=313244&r1=313243&r2=313244&view=diff
==============================================================================
--- cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h (original)
+++ cfe/trunk/include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h Thu Sep 14 03:06:52 2017
@@ -39,6 +39,10 @@ public:
                       llvm::index_sequence_for<RequirementTypes...>());
   }
 
+  bool hasSelectionRequirement() override {
+    return traits::HasSelectionRequirement<RequirementTypes...>::value;
+  }
+
 private:
   /// Returns \c T when given \c Expected<Optional<T>>, or \c T otherwise.
   template <typename T>
@@ -79,7 +83,7 @@ private:
   template <size_t... Is>
   void invokeImpl(RefactoringResultConsumer &Consumer,
                   RefactoringRuleContext &Context,
-                  llvm::index_sequence<Is...>) {
+                  llvm::index_sequence<Is...> Seq) {
     // Initiate the operation.
     auto Values =
         std::make_tuple(std::get<Is>(Requirements).evaluate(Context)...);
@@ -96,8 +100,8 @@ private:
       return Consumer.handleError(std::move(Error));
     }
     // Perform the operation.
-    auto Result =
-        Function(unwrapRequirementResult(std::move(std::get<Is>(Values)))...);
+    auto Result = Function(
+        Context, unwrapRequirementResult(std::move(std::get<Is>(Values)))...);
     if (!Result)
       return Consumer.handleError(Result.takeError());
     Consumer.handle(std::move(*Result));
@@ -111,8 +115,9 @@ private:
 /// createRefactoringRule.
 template <typename T> struct LambdaDeducer;
 template <typename T, typename R, typename... Args>
-struct LambdaDeducer<R (T::*)(Args...) const> {
+struct LambdaDeducer<R (T::*)(const RefactoringRuleContext &, Args...) const> {
   using ReturnType = R;
+  using FunctionType = R (*)(const RefactoringRuleContext &, Args...);
 };
 
 } // end namespace internal

Modified: cfe/trunk/include/clang/Tooling/Refactoring/RefactoringRuleContext.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/Refactoring/RefactoringRuleContext.h?rev=313244&r1=313243&r2=313244&view=diff
==============================================================================
--- cfe/trunk/include/clang/Tooling/Refactoring/RefactoringRuleContext.h (original)
+++ cfe/trunk/include/clang/Tooling/Refactoring/RefactoringRuleContext.h Thu Sep 14 03:06:52 2017
@@ -13,6 +13,9 @@
 #include "clang/Basic/SourceManager.h"
 
 namespace clang {
+
+class ASTContext;
+
 namespace tooling {
 
 /// The refactoring rule context stores all of the inputs that might be needed
@@ -38,6 +41,15 @@ public:
 
   void setSelectionRange(SourceRange R) { SelectionRange = R; }
 
+  bool hasASTContext() const { return AST; }
+
+  ASTContext &getASTContext() const {
+    assert(AST && "no AST!");
+    return *AST;
+  }
+
+  void setASTContext(ASTContext &Context) { AST = &Context; }
+
 private:
   /// The source manager for the translation unit / file on which a refactoring
   /// action might operate on.
@@ -45,6 +57,9 @@ private:
   /// An optional source selection range that's commonly used to represent
   /// a selection in an editor.
   SourceRange SelectionRange;
+  /// An optional AST for the translation unit on which a refactoring action
+  /// might operate on.
+  ASTContext *AST = nullptr;
 };
 
 } // end namespace tooling

Modified: cfe/trunk/include/clang/Tooling/Refactoring/Rename/USRFindingAction.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/Refactoring/Rename/USRFindingAction.h?rev=313244&r1=313243&r2=313244&view=diff
==============================================================================
--- cfe/trunk/include/clang/Tooling/Refactoring/Rename/USRFindingAction.h (original)
+++ cfe/trunk/include/clang/Tooling/Refactoring/Rename/USRFindingAction.h Thu Sep 14 03:06:52 2017
@@ -23,6 +23,7 @@
 
 namespace clang {
 class ASTConsumer;
+class ASTContext;
 class CompilerInstance;
 class NamedDecl;
 
@@ -37,6 +38,10 @@ namespace tooling {
 /// - 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)

Modified: cfe/trunk/include/clang/Tooling/Refactoring/SourceSelectionConstraints.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/Refactoring/SourceSelectionConstraints.h?rev=313244&r1=313243&r2=313244&view=diff
==============================================================================
--- cfe/trunk/include/clang/Tooling/Refactoring/SourceSelectionConstraints.h (original)
+++ cfe/trunk/include/clang/Tooling/Refactoring/SourceSelectionConstraints.h Thu Sep 14 03:06:52 2017
@@ -12,6 +12,7 @@
 
 #include "clang/Basic/LLVM.h"
 #include "clang/Basic/SourceLocation.h"
+#include "clang/Tooling/Refactoring/RefactoringRuleContext.h"
 #include <type_traits>
 
 namespace clang {
@@ -40,9 +41,10 @@ private:
 
 /// A custom selection requirement.
 class Requirement {
-  /// Subclasses must implement 'T evaluateSelection(SelectionConstraint) const'
-  /// member function. \c T is used to determine the return type that is
-  /// passed to the refactoring rule's function.
+  /// Subclasses must implement
+  /// 'T evaluateSelection(const RefactoringRuleContext &,
+  /// SelectionConstraint) const' member function. \c T is used to determine
+  /// the return type that is passed to the refactoring rule's function.
   /// If T is \c DiagnosticOr<S> , then \c S is passed to the rule's function
   /// using move semantics.
   /// Otherwise, T is passed to the function directly using move semantics.
@@ -64,14 +66,17 @@ namespace internal {
 template <typename T> struct EvaluateSelectionChecker : std::false_type {};
 
 template <typename T, typename R, typename A>
-struct EvaluateSelectionChecker<R (T::*)(A) const> : std::true_type {
+struct EvaluateSelectionChecker<R (T::*)(const RefactoringRuleContext &, A)
+                                    const> : std::true_type {
   using ReturnType = R;
   using ArgType = A;
 };
 
 template <typename T> class Identity : public Requirement {
 public:
-  T evaluateSelection(T Value) const { return std::move(Value); }
+  T evaluateSelection(const RefactoringRuleContext &, T Value) const {
+    return std::move(Value);
+  }
 };
 
 } // end namespace internal

Modified: cfe/trunk/include/clang/module.modulemap
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/module.modulemap?rev=313244&r1=313243&r2=313244&view=diff
==============================================================================
--- cfe/trunk/include/clang/module.modulemap (original)
+++ cfe/trunk/include/clang/module.modulemap Thu Sep 14 03:06:52 2017
@@ -138,6 +138,8 @@ module Clang_Tooling {
   // 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 {

Modified: cfe/trunk/lib/Tooling/Refactoring/AtomicChange.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/Refactoring/AtomicChange.cpp?rev=313244&r1=313243&r2=313244&view=diff
==============================================================================
--- cfe/trunk/lib/Tooling/Refactoring/AtomicChange.cpp (original)
+++ cfe/trunk/lib/Tooling/Refactoring/AtomicChange.cpp Thu Sep 14 03:06:52 2017
@@ -215,6 +215,15 @@ AtomicChange::AtomicChange(std::string K
       RemovedHeaders(std::move(RemovedHeaders)), Replaces(std::move(Replaces)) {
 }
 
+bool AtomicChange::operator==(const AtomicChange &Other) const {
+  if (Key != Other.Key || FilePath != Other.FilePath || Error != Other.Error)
+    return false;
+  if (!(Replaces == Other.Replaces))
+    return false;
+  // FXIME: Compare header insertions/removals.
+  return true;
+}
+
 std::string AtomicChange::toYAMLString() {
   std::string YamlContent;
   llvm::raw_string_ostream YamlContentStream(YamlContent);

Modified: cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt?rev=313244&r1=313243&r2=313244&view=diff
==============================================================================
--- cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt (original)
+++ cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt Thu Sep 14 03:06:52 2017
@@ -3,6 +3,7 @@ set(LLVM_LINK_COMPONENTS Support)
 add_clang_library(clangToolingRefactor
   ASTSelection.cpp
   AtomicChange.cpp
+  RefactoringActions.cpp
   Rename/RenamingAction.cpp
   Rename/SymbolOccurrences.cpp
   Rename/USRFinder.cpp

Added: cfe/trunk/lib/Tooling/Refactoring/RefactoringActions.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/Refactoring/RefactoringActions.cpp?rev=313244&view=auto
==============================================================================
--- cfe/trunk/lib/Tooling/Refactoring/RefactoringActions.cpp (added)
+++ cfe/trunk/lib/Tooling/Refactoring/RefactoringActions.cpp Thu Sep 14 03:06:52 2017
@@ -0,0 +1,35 @@
+//===--- RefactoringActions.cpp - Constructs refactoring actions ----------===//
+//
+//                     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/RefactoringAction.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"
+
+std::vector<std::unique_ptr<RefactoringAction>> createRefactoringActions() {
+  std::vector<std::unique_ptr<RefactoringAction>> Actions;
+
+#define REFACTORING_ACTION(Name) Actions.push_back(create##Name##Action());
+#include "clang/Tooling/Refactoring/RefactoringActionRegistry.def"
+
+  return Actions;
+}
+
+RefactoringActionRules RefactoringAction::createActiveActionRules() {
+  // FIXME: Filter out rules that are not supported by a particular client.
+  return createActionRules();
+}
+
+} // end namespace tooling
+} // end namespace clang

Modified: cfe/trunk/lib/Tooling/Refactoring/Rename/RenamingAction.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/Refactoring/Rename/RenamingAction.cpp?rev=313244&r1=313243&r2=313244&view=diff
==============================================================================
--- cfe/trunk/lib/Tooling/Refactoring/Rename/RenamingAction.cpp (original)
+++ cfe/trunk/lib/Tooling/Refactoring/Rename/RenamingAction.cpp Thu Sep 14 03:06:52 2017
@@ -22,6 +22,10 @@
 #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"
@@ -33,6 +37,63 @@ using namespace llvm;
 namespace clang {
 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";
+  }
+
+  /// 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(createRefactoringRule(
+        renameOccurrences, requiredSelection(SymbolSelectionRequirement())));
+    return Rules;
+  }
+
+private:
+  static Expected<AtomicChanges>
+  renameOccurrences(const RefactoringRuleContext &Context,
+                    const NamedDecl *ND) {
+    std::vector<std::string> USRs =
+        getUSRsForDeclaration(ND, Context.getASTContext());
+    std::string PrevName = ND->getNameAsString();
+    auto Occurrences = getOccurrencesOfUSRs(
+        USRs, PrevName, Context.getASTContext().getTranslationUnitDecl());
+
+    // FIXME: This is a temporary workaround that's needed until the refactoring
+    // options are implemented.
+    StringRef NewName = "Bar";
+    return createRenameReplacements(
+        Occurrences, Context.getASTContext().getSourceManager(), NewName);
+  }
+
+  class SymbolSelectionRequirement : public selection::Requirement {
+  public:
+    Expected<Optional<const NamedDecl *>>
+    evaluateSelection(const RefactoringRuleContext &Context,
+                      selection::SourceSelectionRange Selection) const {
+      const NamedDecl *ND = getNamedDeclAt(Context.getASTContext(),
+                                           Selection.getRange().getBegin());
+      if (!ND)
+        return None;
+      return getCanonicalSymbolDeclaration(ND);
+    }
+  };
+};
+
+} // end anonymous namespace
+
+std::unique_ptr<RefactoringAction> createLocalRenameAction() {
+  return llvm::make_unique<LocalRename>();
+}
+
 Expected<std::vector<AtomicChange>>
 createRenameReplacements(const SymbolOccurrences &Occurrences,
                          const SourceManager &SM,

Modified: cfe/trunk/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp?rev=313244&r1=313243&r2=313244&view=diff
==============================================================================
--- cfe/trunk/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp (original)
+++ cfe/trunk/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp Thu Sep 14 03:06:52 2017
@@ -154,6 +154,12 @@ private:
 };
 } // 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,

Modified: cfe/trunk/test/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/CMakeLists.txt?rev=313244&r1=313243&r2=313244&view=diff
==============================================================================
--- cfe/trunk/test/CMakeLists.txt (original)
+++ cfe/trunk/test/CMakeLists.txt Thu Sep 14 03:06:52 2017
@@ -48,6 +48,7 @@ list(APPEND CLANG_TEST_DEPS
   clang-offload-bundler
   clang-import-test
   clang-rename
+  clang-refactor
   clang-diff
   )
   

Added: cfe/trunk/test/Refactor/LocalRename/Field.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Refactor/LocalRename/Field.cpp?rev=313244&view=auto
==============================================================================
--- cfe/trunk/test/Refactor/LocalRename/Field.cpp (added)
+++ cfe/trunk/test/Refactor/LocalRename/Field.cpp Thu Sep 14 03:06:52 2017
@@ -0,0 +1,9 @@
+// RUN: clang-refactor local-rename -selection=test:%s -no-dbs %s | FileCheck %s
+
+class Baz {
+  int /*range=*/Foo; // CHECK: int /*range=*/Bar;
+public:
+  Baz();
+};
+
+Baz::Baz() : /*range=*/Foo(0) {}  // CHECK: Baz::Baz() : /*range=*/Bar(0) {};

Added: cfe/trunk/test/Refactor/tool-common-options.c
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Refactor/tool-common-options.c?rev=313244&view=auto
==============================================================================
--- cfe/trunk/test/Refactor/tool-common-options.c (added)
+++ cfe/trunk/test/Refactor/tool-common-options.c Thu Sep 14 03:06:52 2017
@@ -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

Added: cfe/trunk/test/Refactor/tool-test-support.c
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Refactor/tool-test-support.c?rev=313244&view=auto
==============================================================================
--- cfe/trunk/test/Refactor/tool-test-support.c (added)
+++ cfe/trunk/test/Refactor/tool-test-support.c Thu Sep 14 03:06:52 2017
@@ -0,0 +1,41 @@
+// RUN: clang-refactor local-rename -selection=test:%s -no-dbs -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:   100-100
+// CHECK-NEXT:   153-153
+// CHECK-NEXT:   192-192
+// CHECK-NEXT: Test selection group 'named':
+// CHECK-NEXT:   127-127
+// CHECK-NEXT:   213-213
+
+// The following invocations are in the default group:
+
+// 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
+
+
+// The following invocations are in the 'named' group, and they follow
+// the default invocation even if some of their ranges occur prior to the
+// ranges from the default group because the groups are tested one-by-one:
+
+// 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

Removed: cfe/trunk/test/clang-rename/Field.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/clang-rename/Field.cpp?rev=313243&view=auto
==============================================================================
--- cfe/trunk/test/clang-rename/Field.cpp (original)
+++ cfe/trunk/test/clang-rename/Field.cpp (removed)
@@ -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>

Modified: cfe/trunk/tools/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/tools/CMakeLists.txt?rev=313244&r1=313243&r2=313244&view=diff
==============================================================================
--- cfe/trunk/tools/CMakeLists.txt (original)
+++ cfe/trunk/tools/CMakeLists.txt Thu Sep 14 03:06:52 2017
@@ -12,6 +12,7 @@ add_clang_subdirectory(clang-offload-bun
 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)

Added: cfe/trunk/tools/clang-refactor/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/tools/clang-refactor/CMakeLists.txt?rev=313244&view=auto
==============================================================================
--- cfe/trunk/tools/clang-refactor/CMakeLists.txt (added)
+++ cfe/trunk/tools/clang-refactor/CMakeLists.txt Thu Sep 14 03:06:52 2017
@@ -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)

Added: cfe/trunk/tools/clang-refactor/ClangRefactor.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/tools/clang-refactor/ClangRefactor.cpp?rev=313244&view=auto
==============================================================================
--- cfe/trunk/tools/clang-refactor/ClangRefactor.cpp (added)
+++ cfe/trunk/tools/clang-refactor/ClangRefactor.cpp Thu Sep 14 03:06:52 2017
@@ -0,0 +1,391 @@
+//===--- 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/RefactoringAction.h"
+#include "clang/Tooling/Refactoring/Rename/RenamingAction.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 refactor;
+namespace cl = llvm::cl;
+
+namespace opts {
+
+static cl::OptionCategory CommonRefactorOptions("Common refactoring options");
+
+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 {
+
+/// Stores the parsed `-selection` argument.
+class SourceSelectionArgument {
+public:
+  virtual ~SourceSelectionArgument() {}
+
+  /// Parse the `-selection` argument.
+  ///
+  /// \returns A valid argument when the parse succedeed, null otherwise.
+  static std::unique_ptr<SourceSelectionArgument> fromString(StringRef Value);
+
+  /// Prints any additional state associated with the selection argument to
+  /// the given output stream.
+  virtual void print(raw_ostream &OS) = 0;
+
+  /// Returns a replacement refactoring result consumer (if any) that should
+  /// consume the results of a refactoring operation.
+  ///
+  /// The replacement refactoring result consumer is used by \c
+  /// TestSourceSelectionArgument to inject a test-specific result handling
+  /// logic into the refactoring operation. The test-specific consumer
+  /// ensures that the individual results in a particular test group are
+  /// identical.
+  virtual std::unique_ptr<RefactoringResultConsumer> createCustomConsumer() {
+    return nullptr;
+  }
+
+  /// Runs the give refactoring function for each specified selection.
+  ///
+  /// \returns true if an error occurred, false otherwise.
+  virtual bool
+  forAllRanges(const SourceManager &SM,
+               llvm::function_ref<void(SourceRange R)> Callback) = 0;
+};
+
+/// Stores the parsed -selection=test:<filename> option.
+class TestSourceSelectionArgument final : public SourceSelectionArgument {
+public:
+  TestSourceSelectionArgument(TestSelectionRangesInFile TestSelections)
+      : TestSelections(std::move(TestSelections)) {}
+
+  void print(raw_ostream &OS) override { TestSelections.dump(OS); }
+
+  std::unique_ptr<RefactoringResultConsumer> createCustomConsumer() override {
+    return TestSelections.createConsumer();
+  }
+
+  /// Testing support: invokes the selection action for each selection range in
+  /// the test file.
+  bool forAllRanges(const SourceManager &SM,
+                    llvm::function_ref<void(SourceRange R)> Callback) override {
+    return TestSelections.foreachRange(SM, Callback);
+  }
+
+private:
+  TestSelectionRangesInFile TestSelections;
+};
+
+std::unique_ptr<SourceSelectionArgument>
+SourceSelectionArgument::fromString(StringRef Value) {
+  if (Value.startswith("test:")) {
+    StringRef Filename = Value.drop_front(strlen("test:"));
+    Optional<TestSelectionRangesInFile> ParsedTestSelection =
+        findTestSelectionRanges(Filename);
+    if (!ParsedTestSelection)
+      return nullptr; // A parsing error was already reported.
+    return llvm::make_unique<TestSourceSelectionArgument>(
+        std::move(*ParsedTestSelection));
+  }
+  // FIXME: Support true selection ranges.
+  llvm::errs() << "error: '-selection' option must be specified using "
+                  "<file>:<line>:<column> or "
+                  "<file>:<line>:<column>-<line>:<column> format";
+  return nullptr;
+}
+
+/// A subcommand that corresponds to individual refactoring action.
+class RefactoringActionSubcommand : public cl::SubCommand {
+public:
+  RefactoringActionSubcommand(std::unique_ptr<RefactoringAction> Action,
+                              RefactoringActionRules ActionRules,
+                              cl::OptionCategory &Category)
+      : SubCommand(Action->getCommand(), Action->getDescription()),
+        Action(std::move(Action)), ActionRules(std::move(ActionRules)) {
+    Sources = llvm::make_unique<cl::list<std::string>>(
+        cl::Positional, cl::ZeroOrMore, cl::desc("<source0> [... <sourceN>]"),
+        cl::cat(Category), cl::sub(*this));
+
+    // Check if the selection option is supported.
+    bool HasSelection = false;
+    for (const auto &Rule : this->ActionRules) {
+      if ((HasSelection = Rule->hasSelectionRequirement()))
+        break;
+    }
+    if (HasSelection) {
+      Selection = llvm::make_unique<cl::opt<std::string>>(
+          "selection",
+          cl::desc("The selected source range in which the refactoring should "
+                   "be initiated (<file>:<line>:<column>-<line>:<column> or "
+                   "<file>:<line>:<column>)"),
+          cl::cat(Category), cl::sub(*this));
+    }
+  }
+
+  ~RefactoringActionSubcommand() { unregisterSubCommand(); }
+
+  const RefactoringActionRules &getActionRules() const { return ActionRules; }
+
+  /// Parses the command-line arguments that are specific to this rule.
+  ///
+  /// \returns true on error, false otherwise.
+  bool parseArguments() {
+    if (Selection) {
+      ParsedSelection = SourceSelectionArgument::fromString(*Selection);
+      if (!ParsedSelection)
+        return true;
+    }
+    return false;
+  }
+
+  SourceSelectionArgument *getSelection() const {
+    assert(Selection && "selection not supported!");
+    return ParsedSelection.get();
+  }
+
+  ArrayRef<std::string> getSources() const { return *Sources; }
+
+private:
+  std::unique_ptr<RefactoringAction> Action;
+  RefactoringActionRules ActionRules;
+  std::unique_ptr<cl::list<std::string>> Sources;
+  std::unique_ptr<cl::opt<std::string>> Selection;
+  std::unique_ptr<SourceSelectionArgument> ParsedSelection;
+};
+
+class ClangRefactorConsumer : public RefactoringResultConsumer {
+public:
+  void handleError(llvm::Error Err) {
+    llvm::errs() << llvm::toString(std::move(Err)) << "\n";
+  }
+
+  // FIXME: Consume atomic changes and apply them to files.
+};
+
+class ClangRefactorTool {
+public:
+  std::vector<std::unique_ptr<RefactoringActionSubcommand>> SubCommands;
+
+  ClangRefactorTool() {
+    std::vector<std::unique_ptr<RefactoringAction>> Actions =
+        createRefactoringActions();
+
+    // Actions must have unique command names so that we can map them to one
+    // subcommand.
+    llvm::StringSet<> CommandNames;
+    for (const auto &Action : Actions) {
+      if (!CommandNames.insert(Action->getCommand()).second) {
+        llvm::errs() << "duplicate refactoring action command '"
+                     << Action->getCommand() << "'!";
+        exit(1);
+      }
+    }
+
+    // Create subcommands and command-line options.
+    for (auto &Action : Actions) {
+      SubCommands.push_back(llvm::make_unique<RefactoringActionSubcommand>(
+          std::move(Action), Action->createActiveActionRules(),
+          opts::CommonRefactorOptions));
+    }
+  }
+
+  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 RefactoringRuleContext &Context) {
+    if (!opts::Verbose)
+      return;
+    llvm::outs() << "invoking action '" << Subcommand.getName() << "':\n";
+    if (Context.getSelectionRange().isValid()) {
+      SourceRange R = Context.getSelectionRange();
+      llvm::outs() << "  -selection=";
+      R.getBegin().print(llvm::outs(), Context.getSources());
+      llvm::outs() << " -> ";
+      R.getEnd().print(llvm::outs(), Context.getSources());
+      llvm::outs() << "\n";
+    }
+  }
+
+  bool invokeAction(RefactoringActionSubcommand &Subcommand) {
+    // Find a set of matching rules.
+    SmallVector<RefactoringActionRule *, 4> MatchingRules;
+    llvm::StringSet<> MissingOptions;
+
+    bool HasSelection = false;
+    for (const auto &Rule : Subcommand.getActionRules()) {
+      if (Rule->hasSelectionRequirement()) {
+        HasSelection = true;
+        if (Subcommand.getSelection())
+          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;
+    ClangRefactorConsumer Consumer;
+    if (foreachTranslationUnit(Subcommand, [&](ASTContext &AST) {
+          RefactoringRuleContext Context(AST.getSourceManager());
+          Context.setASTContext(AST);
+
+          auto InvokeRule = [&](RefactoringResultConsumer &Consumer) {
+            logInvocation(Subcommand, Context);
+            for (RefactoringActionRule *Rule : MatchingRules) {
+              if (!Rule->hasSelectionRequirement())
+                continue;
+              Rule->invoke(Consumer, Context);
+              return;
+            }
+            // FIXME (Alex L): If more than one initiation succeeded, then the
+            // rules are ambiguous.
+            llvm_unreachable(
+                "The action must have at least one selection rule");
+          };
+
+          if (HasSelection) {
+            assert(Subcommand.getSelection() && "Missing selection argument?");
+            if (opts::Verbose)
+              Subcommand.getSelection()->print(llvm::outs());
+            auto CustomConsumer =
+                Subcommand.getSelection()->createCustomConsumer();
+            if (Subcommand.getSelection()->forAllRanges(
+                    Context.getSources(), [&](SourceRange R) {
+                      Context.setSelectionRange(R);
+                      InvokeRule(CustomConsumer ? *CustomConsumer : Consumer);
+                    }))
+              HasFailed = true;
+            return;
+          }
+          // FIXME (Alex L): Implement non-selection based invocation path.
+        }))
+      return true;
+    return HasFailed;
+  }
+};
+
+} // end anonymous namespace
+
+int main(int argc, const char **argv) {
+  ClangRefactorTool Tool;
+
+  // FIXME: Use LibTooling's CommonOptions parser when subcommands are supported
+  // by it.
+  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 specified by the user. The user must specify
+  // the action using a command-line subcommand, e.g. the invocation
+  // `clang-refactor local-rename` corresponds to the `LocalRename` refactoring
+  // action. All subcommands must have a unique names. This allows us to figure
+  // out which refactoring action should be invoked by looking at the first
+  // subcommand that's enabled by LLVM's command-line parser.
+  auto It = llvm::find_if(
+      Tool.SubCommands,
+      [](const std::unique_ptr<RefactoringActionSubcommand> &SubCommand) {
+        return !!(*SubCommand);
+      });
+  if (It == Tool.SubCommands.end()) {
+    llvm::errs() << "error: no refactoring action given\n";
+    llvm::errs() << "note: the following actions are supported:\n";
+    for (const auto &Subcommand : Tool.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 (ActionCommand.parseArguments())
+    return 1;
+  if (Tool.invokeAction(ActionCommand))
+    return 1;
+
+  return 0;
+}

Added: cfe/trunk/tools/clang-refactor/TestSupport.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/tools/clang-refactor/TestSupport.cpp?rev=313244&view=auto
==============================================================================
--- cfe/trunk/tools/clang-refactor/TestSupport.cpp (added)
+++ cfe/trunk/tools/clang-refactor/TestSupport.cpp Thu Sep 14 03:06:52 2017
@@ -0,0 +1,343 @@
+//===--- 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/MemoryBuffer.h"
+#include "llvm/Support/Regex.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace llvm;
+
+namespace clang {
+namespace refactor {
+
+void TestSelectionRangesInFile::dump(raw_ostream &OS) const {
+  for (const auto &Group : GroupedRanges) {
+    OS << "Test selection group '" << Group.Name << "':\n";
+    for (const auto &Range : Group.Ranges) {
+      OS << "  " << Range.Begin << "-" << Range.End << "\n";
+    }
+  }
+}
+
+bool TestSelectionRangesInFile::foreachRange(
+    const SourceManager &SM,
+    llvm::function_ref<void(SourceRange)> Callback) 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);
+  for (const auto &Group : GroupedRanges) {
+    for (const TestSelectionRange &Range : Group.Ranges) {
+      // 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");
+      Callback(SourceRange(Start, End));
+    }
+  }
+  return false;
+}
+
+namespace {
+
+void dumpChanges(const tooling::AtomicChanges &Changes, raw_ostream &OS) {
+  for (const auto &Change : Changes)
+    OS << const_cast<tooling::AtomicChange &>(Change).toYAMLString() << "\n";
+}
+
+bool areChangesSame(const tooling::AtomicChanges &LHS,
+                    const tooling::AtomicChanges &RHS) {
+  if (LHS.size() != RHS.size())
+    return false;
+  for (const auto &I : llvm::zip(LHS, RHS)) {
+    if (!(std::get<0>(I) == std::get<1>(I)))
+      return false;
+  }
+  return true;
+}
+
+bool printRewrittenSources(const tooling::AtomicChanges &Changes,
+                           raw_ostream &OS) {
+  std::set<std::string> Files;
+  for (const auto &Change : Changes)
+    Files.insert(Change.getFilePath());
+  tooling::ApplyChangesSpec Spec;
+  Spec.Cleanup = false;
+  for (const auto &File : Files) {
+    llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> BufferErr =
+        llvm::MemoryBuffer::getFile(File);
+    if (!BufferErr) {
+      llvm::errs() << "failed to open" << File << "\n";
+      return true;
+    }
+    auto Result = tooling::applyAtomicChanges(File, (*BufferErr)->getBuffer(),
+                                              Changes, Spec);
+    if (!Result) {
+      llvm::errs() << toString(Result.takeError());
+      return true;
+    }
+    OS << *Result;
+  }
+  return false;
+}
+
+class TestRefactoringResultConsumer final
+    : public tooling::RefactoringResultConsumer {
+public:
+  TestRefactoringResultConsumer(const TestSelectionRangesInFile &TestRanges)
+      : TestRanges(TestRanges) {
+    Results.push_back({});
+  }
+
+  ~TestRefactoringResultConsumer() {
+    // Ensure all results are checked.
+    for (auto &Group : Results) {
+      for (auto &Result : Group) {
+        if (!Result) {
+          (void)llvm::toString(Result.takeError());
+        }
+      }
+    }
+  }
+
+  void handleError(llvm::Error Err) override { handleResult(std::move(Err)); }
+
+  void handle(tooling::AtomicChanges Changes) override {
+    handleResult(std::move(Changes));
+  }
+
+  void handle(tooling::SymbolOccurrences Occurrences) override {
+    tooling::RefactoringResultConsumer::handle(std::move(Occurrences));
+  }
+
+private:
+  bool handleAllResults();
+
+  void handleResult(Expected<tooling::AtomicChanges> Result) {
+    Results.back().push_back(std::move(Result));
+    size_t GroupIndex = Results.size() - 1;
+    if (Results.back().size() >=
+        TestRanges.GroupedRanges[GroupIndex].Ranges.size()) {
+      ++GroupIndex;
+      if (GroupIndex >= TestRanges.GroupedRanges.size()) {
+        if (handleAllResults())
+          exit(1); // error has occurred.
+        return;
+      }
+      Results.push_back({});
+    }
+  }
+
+  const TestSelectionRangesInFile &TestRanges;
+  std::vector<std::vector<Expected<tooling::AtomicChanges>>> Results;
+};
+
+std::pair<unsigned, unsigned> getLineColumn(StringRef Filename,
+                                            unsigned Offset) {
+  ErrorOr<std::unique_ptr<MemoryBuffer>> ErrOrFile =
+      MemoryBuffer::getFile(Filename);
+  if (!ErrOrFile)
+    return {0, 0};
+  StringRef Source = ErrOrFile.get()->getBuffer();
+  Source = Source.take_front(Offset);
+  size_t LastLine = Source.find_last_of("\r\n");
+  return {Source.count('\n') + 1,
+          (LastLine == StringRef::npos ? Offset : Offset - LastLine) + 1};
+}
+
+} // end anonymous namespace
+
+bool TestRefactoringResultConsumer::handleAllResults() {
+  bool Failed = false;
+  for (auto &Group : llvm::enumerate(Results)) {
+    // All ranges in the group must produce the same result.
+    Optional<tooling::AtomicChanges> CanonicalResult;
+    Optional<std::string> CanonicalErrorMessage;
+    for (auto &I : llvm::enumerate(Group.value())) {
+      Expected<tooling::AtomicChanges> &Result = I.value();
+      std::string ErrorMessage;
+      bool HasResult = !!Result;
+      if (!HasResult) {
+        // FIXME: Handle diagnostic error as well.
+        handleAllErrors(Result.takeError(), [&](StringError &Err) {
+          ErrorMessage = Err.getMessage();
+        });
+      }
+      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 && areChangesSame(*Result, *CanonicalResult))
+          continue;
+      }
+      Failed = true;
+      // Report the mismatch.
+      std::pair<unsigned, unsigned> LineColumn = getLineColumn(
+          TestRanges.Filename,
+          TestRanges.GroupedRanges[Group.index()].Ranges[I.index()].Begin);
+      llvm::errs()
+          << "error: unexpected refactoring result for range starting at "
+          << LineColumn.first << ':' << LineColumn.second << " in group '"
+          << TestRanges.GroupedRanges[Group.index()].Name << "':\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";
+        dumpChanges(*CanonicalResult, llvm::errs());
+        llvm::errs() << "  Produced:\n";
+        dumpChanges(*Result, llvm::errs());
+      }
+    }
+
+    // Dump the results:
+    const auto &TestGroup = TestRanges.GroupedRanges[Group.index()];
+    if (!CanonicalResult) {
+      llvm::errs() << TestGroup.Ranges.size() << " '" << TestGroup.Name
+                   << "' results:\n";
+      llvm::errs() << *CanonicalErrorMessage << "\n";
+    } else {
+      llvm::outs() << TestGroup.Ranges.size() << " '" << TestGroup.Name
+                   << "' results:\n";
+      if (printRewrittenSources(*CanonicalResult, llvm::outs()))
+        return true;
+    }
+  }
+  return Failed;
+}
+
+std::unique_ptr<tooling::RefactoringResultConsumer>
+TestSelectionRangesInFile::createConsumer() const {
+  return llvm::make_unique<TestRefactoringResultConsumer>(*this);
+}
+
+/// 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>
+findTestSelectionRanges(StringRef Filename) {
+  ErrorOr<std::unique_ptr<MemoryBuffer>> ErrOrFile =
+      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:]]+)?");
+
+  std::map<std::string, SmallVector<TestSelectionRange, 8>> GroupedRanges;
+
+  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());
+    SmallVector<StringRef, 4> Matches;
+    // Allow CHECK: comments to contain range= commands.
+    if (!RangeRegex.match(Comment, &Matches) || Comment.contains("CHECK")) {
+      // Try to detect mistyped 'range:' comments to ensure tests don't miss
+      // anything.
+      if (Comment.contains_lower("range") && Comment.contains("=") &&
+          !Comment.contains_lower("run") && !Comment.contains("CHECK")) {
+        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 = GroupedRanges.insert(std::make_pair(
+        Matches[1].str(), SmallVector<TestSelectionRange, 8>{Range}));
+    if (!It.second)
+      It.first->second.push_back(Range);
+  }
+  if (GroupedRanges.empty()) {
+    llvm::errs() << "error: -selection=test:" << Filename
+                 << ": no 'range' commands";
+    return None;
+  }
+
+  TestSelectionRangesInFile TestRanges = {Filename.str(), {}};
+  for (auto &Group : GroupedRanges)
+    TestRanges.GroupedRanges.push_back({Group.first, std::move(Group.second)});
+  return std::move(TestRanges);
+}
+
+} // end namespace refactor
+} // end namespace clang

Added: cfe/trunk/tools/clang-refactor/TestSupport.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/tools/clang-refactor/TestSupport.h?rev=313244&view=auto
==============================================================================
--- cfe/trunk/tools/clang-refactor/TestSupport.h (added)
+++ cfe/trunk/tools/clang-refactor/TestSupport.h Thu Sep 14 03:06:52 2017
@@ -0,0 +1,107 @@
+//===--- 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/LLVM.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Tooling/Refactoring/RefactoringResultConsumer.h"
+#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include <map>
+#include <string>
+
+namespace clang {
+
+class SourceManager;
+
+namespace 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;
+  struct RangeGroup {
+    std::string Name;
+    SmallVector<TestSelectionRange, 8> Ranges;
+  };
+  std::vector<RangeGroup> GroupedRanges;
+
+  TestSelectionRangesInFile(TestSelectionRangesInFile &&) = default;
+  TestSelectionRangesInFile &operator=(TestSelectionRangesInFile &&) = default;
+
+  bool foreachRange(const SourceManager &SM,
+                    llvm::function_ref<void(SourceRange)> Callback) const;
+
+  std::unique_ptr<tooling::RefactoringResultConsumer> createConsumer() 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 print 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> findTestSelectionRanges(StringRef Filename);
+
+} // end namespace refactor
+} // end namespace clang
+
+#endif // LLVM_CLANG_TOOLS_CLANG_REFACTOR_TEST_SUPPORT_H

Modified: cfe/trunk/unittests/Tooling/RefactoringActionRulesTest.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/Tooling/RefactoringActionRulesTest.cpp?rev=313244&r1=313243&r2=313244&view=diff
==============================================================================
--- cfe/trunk/unittests/Tooling/RefactoringActionRulesTest.cpp (original)
+++ cfe/trunk/unittests/Tooling/RefactoringActionRulesTest.cpp Thu Sep 14 03:06:52 2017
@@ -57,7 +57,8 @@ createReplacements(const std::unique_ptr
 
 TEST_F(RefactoringActionRulesTest, MyFirstRefactoringRule) {
   auto ReplaceAWithB =
-      [](std::pair<selection::SourceSelectionRange, int> Selection)
+      [](const RefactoringRuleContext &,
+         std::pair<selection::SourceSelectionRange, int> Selection)
       -> Expected<AtomicChanges> {
     const SourceManager &SM = Selection.first.getSources();
     SourceLocation Loc = Selection.first.getRange().getBegin().getLocWithOffset(
@@ -71,7 +72,8 @@ TEST_F(RefactoringActionRulesTest, MyFir
   class SelectionRequirement : public selection::Requirement {
   public:
     std::pair<selection::SourceSelectionRange, int>
-    evaluateSelection(selection::SourceSelectionRange Selection) const {
+    evaluateSelection(const RefactoringRuleContext &,
+                      selection::SourceSelectionRange Selection) const {
       return std::make_pair(Selection, 20);
     }
   };
@@ -127,8 +129,10 @@ TEST_F(RefactoringActionRulesTest, MyFir
 }
 
 TEST_F(RefactoringActionRulesTest, ReturnError) {
-  Expected<AtomicChanges> (*Func)(selection::SourceSelectionRange) =
-      [](selection::SourceSelectionRange) -> Expected<AtomicChanges> {
+  Expected<AtomicChanges> (*Func)(const RefactoringRuleContext &,
+                                  selection::SourceSelectionRange) =
+      [](const RefactoringRuleContext &,
+         selection::SourceSelectionRange) -> Expected<AtomicChanges> {
     return llvm::make_error<llvm::StringError>(
         "Error", llvm::make_error_code(llvm::errc::invalid_argument));
   };
@@ -155,13 +159,14 @@ TEST_F(RefactoringActionRulesTest, Retur
   class SelectionRequirement : public selection::Requirement {
   public:
     Expected<Optional<int>>
-    evaluateSelection(selection::SourceSelectionRange Selection) const {
+    evaluateSelection(const RefactoringRuleContext &,
+                      selection::SourceSelectionRange Selection) const {
       return llvm::make_error<llvm::StringError>(
           "bad selection", llvm::make_error_code(llvm::errc::invalid_argument));
     }
   };
   auto Rule = createRefactoringRule(
-      [](int) -> Expected<AtomicChanges> {
+      [](const RefactoringRuleContext &, int) -> Expected<AtomicChanges> {
         llvm::report_fatal_error("Should not run!");
       },
       requiredSelection(SelectionRequirement()));
@@ -201,7 +206,8 @@ Optional<SymbolOccurrences> findOccurren
 
 TEST_F(RefactoringActionRulesTest, ReturnSymbolOccurrences) {
   auto Rule = createRefactoringRule(
-      [](selection::SourceSelectionRange Selection)
+      [](const RefactoringRuleContext &,
+         selection::SourceSelectionRange Selection)
           -> Expected<SymbolOccurrences> {
         SymbolOccurrences Occurrences;
         Occurrences.push_back(SymbolOccurrence(




More information about the cfe-commits mailing list