[clang] Match edit refactoring rule (PR #105717)

Сергеев Игнатий via cfe-commits cfe-commits at lists.llvm.org
Thu Aug 22 11:59:56 PDT 2024


https://github.com/IgnatSergeev created https://github.com/llvm/llvm-project/pull/105717

None

>From d631dae8cf543b8d02b7d2702d69b143a2874cd7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=98=D0=B3=D0=BD=D0=B0=D1=82=20=D0=A1=D0=B5=D1=80=D0=B3?=
 =?UTF-8?q?=D0=B5=D0=B5=D0=B2?= <ignat.sergeev at softcom.su>
Date: Mon, 27 May 2024 19:43:11 +0000
Subject: [PATCH 1/3] Add source location requirement

Added source location requirement for refactoring engine
---
 .../clang/Basic/DiagnosticRefactoringKinds.td        |  2 ++
 .../Tooling/Refactoring/RefactoringActionRule.h      |  4 ++++
 .../Refactoring/RefactoringActionRuleRequirements.h  | 12 ++++++++++++
 .../Refactoring/RefactoringActionRulesInternal.h     |  5 +++++
 .../Tooling/Refactoring/RefactoringRuleContext.h     | 12 ++++++++++++
 5 files changed, 35 insertions(+)

diff --git a/clang/include/clang/Basic/DiagnosticRefactoringKinds.td b/clang/include/clang/Basic/DiagnosticRefactoringKinds.td
index 5446b32efbdd47..da35eae93df9a9 100644
--- a/clang/include/clang/Basic/DiagnosticRefactoringKinds.td
+++ b/clang/include/clang/Basic/DiagnosticRefactoringKinds.td
@@ -28,6 +28,8 @@ def err_refactor_extract_simple_expression : Error<"the selected expression "
 def err_refactor_extract_prohibited_expression : Error<"the selected "
   "expression can't be extracted">;
 
+def err_refactor_no_location : Error<"refactoring action can't be initiated "
+  "without a location">;
 }
 
 } // end of Refactoring diagnostics
diff --git a/clang/include/clang/Tooling/Refactoring/RefactoringActionRule.h b/clang/include/clang/Tooling/Refactoring/RefactoringActionRule.h
index c6a6c4f6093a34..374f19d6d82338 100644
--- a/clang/include/clang/Tooling/Refactoring/RefactoringActionRule.h
+++ b/clang/include/clang/Tooling/Refactoring/RefactoringActionRule.h
@@ -56,6 +56,10 @@ class RefactoringActionRule : public RefactoringActionRuleBase {
   /// to be fulfilled before refactoring can be performed.
   virtual bool hasSelectionRequirement() = 0;
 
+  /// Returns true when the rule has a source location requirement that has
+  /// to be fulfilled before refactoring can be performed.
+  virtual bool hasLocationRequirement() = 0;
+
   /// Traverses each refactoring option used by the rule and invokes the
   /// \c visit callback in the consumer for each option.
   ///
diff --git a/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h b/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h
index 1a318da3acca19..bdd2d98df73eb1 100644
--- a/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h
+++ b/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h
@@ -10,6 +10,7 @@
 #define LLVM_CLANG_TOOLING_REFACTORING_REFACTORINGACTIONRULEREQUIREMENTS_H
 
 #include "clang/Basic/LLVM.h"
+#include "clang/Basic/SourceLocation.h"
 #include "clang/Tooling/Refactoring/ASTSelection.h"
 #include "clang/Tooling/Refactoring/RefactoringDiagnostic.h"
 #include "clang/Tooling/Refactoring/RefactoringOption.h"
@@ -77,6 +78,17 @@ class CodeRangeASTSelectionRequirement : public ASTSelectionRequirement {
   evaluate(RefactoringRuleContext &Context) const;
 };
 
+/// A base class for any requirement that expects source code position (or the
+/// refactoring tool with the -location option).
+class SourceLocationRequirement : public RefactoringActionRuleRequirement {
+public:
+  Expected<SourceLocation> evaluate(RefactoringRuleContext &Context) const {
+    if (Context.getLocation().isValid())
+      return Context.getLocation();
+    return Context.createDiagnosticError(diag::err_refactor_no_location);
+  }
+};
+
 /// A base class for any requirement that requires some refactoring options.
 class RefactoringOptionsRequirement : public RefactoringActionRuleRequirement {
 public:
diff --git a/clang/include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h b/clang/include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h
index 33194c401ea143..52afb012f4874c 100644
--- a/clang/include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h
+++ b/clang/include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h
@@ -139,6 +139,11 @@ createRefactoringActionRule(const RequirementTypes &... Requirements) {
                                  RequirementTypes...>::value;
     }
 
+    bool hasLocationRequirement() override {
+      return internal::HasBaseOf<SourceLocationRequirement,
+                                 RequirementTypes...>::value;
+    }
+
     void visitRefactoringOptions(RefactoringOptionVisitor &Visitor) override {
       internal::visitRefactoringOptions(
           Visitor, Requirements,
diff --git a/clang/include/clang/Tooling/Refactoring/RefactoringRuleContext.h b/clang/include/clang/Tooling/Refactoring/RefactoringRuleContext.h
index 7d97f811f024e0..85bba662afcd28 100644
--- a/clang/include/clang/Tooling/Refactoring/RefactoringRuleContext.h
+++ b/clang/include/clang/Tooling/Refactoring/RefactoringRuleContext.h
@@ -30,6 +30,9 @@ namespace tooling {
 ///
 ///   - SelectionRange: an optional source selection ranges that can be used
 ///     to represent a selection in an editor.
+///
+///   - Location: an optional source location that can be used
+///     to represent a cursor in an editor.
 class RefactoringRuleContext {
 public:
   RefactoringRuleContext(const SourceManager &SM) : SM(SM) {}
@@ -40,8 +43,14 @@ class RefactoringRuleContext {
   /// refactoring engine. Can be invalid.
   SourceRange getSelectionRange() const { return SelectionRange; }
 
+  /// Returns the current source location as set by the
+  /// refactoring engine. Can be invalid.
+  SourceLocation getLocation() const { return Location; }
+
   void setSelectionRange(SourceRange R) { SelectionRange = R; }
 
+  void setLocation(SourceLocation L) { Location = L; }
+
   bool hasASTContext() const { return AST; }
 
   ASTContext &getASTContext() const {
@@ -73,6 +82,9 @@ class RefactoringRuleContext {
   /// An optional source selection range that's commonly used to represent
   /// a selection in an editor.
   SourceRange SelectionRange;
+  /// An optional source location that's commonly used to represent
+  /// a cursor in an editor.
+  SourceLocation Location;
   /// An optional AST for the translation unit on which a refactoring action
   /// might operate on.
   ASTContext *AST = nullptr;

>From 333b349c7b335c625745f1613b7dc05cc93718cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=98=D0=B3=D0=BD=D0=B0=D1=82=20=D0=A1=D0=B5=D1=80=D0=B3?=
 =?UTF-8?q?=D0=B5=D0=B5=D0=B2?= <ignat.sergeev at softcom.su>
Date: Mon, 27 May 2024 20:26:52 +0000
Subject: [PATCH 2/3] Add source location argument

Added source location argument for clang-refactor cli
---
 clang/tools/clang-refactor/ClangRefactor.cpp | 148 ++++++++++++++++++-
 1 file changed, 142 insertions(+), 6 deletions(-)

diff --git a/clang/tools/clang-refactor/ClangRefactor.cpp b/clang/tools/clang-refactor/ClangRefactor.cpp
index 175a2b8234e9ac..1737ca9fc82a91 100644
--- a/clang/tools/clang-refactor/ClangRefactor.cpp
+++ b/clang/tools/clang-refactor/ClangRefactor.cpp
@@ -164,6 +164,84 @@ SourceSelectionArgument::fromString(StringRef Value) {
   return nullptr;
 }
 
+/// Stores the parsed `-location` argument.
+class SourceLocationArgument {
+public:
+  virtual ~SourceLocationArgument() {}
+
+  /// Parse the `-location` argument.
+  ///
+  /// \returns A valid argument when the parse succedeed, null otherwise.
+  static std::unique_ptr<SourceLocationArgument> fromString(StringRef Value);
+
+  /// Prints any additional state associated with the location argument to
+  /// the given output stream.
+  virtual void print(raw_ostream &OS) {}
+
+  /// 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
+  /// TestSourceLocationArgument 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<ClangRefactorToolConsumerInterface>
+  createCustomConsumer() {
+    return nullptr;
+  }
+
+  /// Runs the given refactoring function for each specified location.
+  ///
+  /// \returns true if an error occurred, false otherwise.
+  virtual bool
+  forAllLocations(const SourceManager &SM,
+                  llvm::function_ref<void(SourceLocation L)> Callback) = 0;
+};
+
+/// Stores the parsed -location=filename:line:column option.
+class SourceLocLocationArgument final : public SourceLocationArgument {
+public:
+  SourceLocLocationArgument(ParsedSourceLocation Location)
+      : Location(std::move(Location)) {}
+
+  bool forAllLocations(
+      const SourceManager &SM,
+      llvm::function_ref<void(SourceLocation L)> Callback) override {
+    auto FE = SM.getFileManager().getFile(Location.FileName);
+    FileID FID = FE ? SM.translateFile(*FE) : FileID();
+    if (!FE || FID.isInvalid()) {
+      llvm::errs() << "error: -location=" << Location.FileName
+                   << ":... : given file is not in the target TU\n";
+      return true;
+    }
+
+    SourceLocation Loc = SM.getMacroArgExpandedLocation(
+        SM.translateLineCol(FID, Location.Line, Location.Column));
+    if (Loc.isInvalid()) {
+      llvm::errs() << "error: -location=" << Location.FileName << ':'
+                   << Location.Line << ':' << Location.Column
+                   << " : invalid source location\n";
+      return true;
+    }
+    Callback(Loc);
+    return false;
+  }
+
+private:
+  ParsedSourceLocation Location;
+};
+
+std::unique_ptr<SourceLocationArgument>
+SourceLocationArgument::fromString(StringRef Value) {
+  ParsedSourceLocation Location = ParsedSourceLocation::FromString(Value);
+  if (Location.FileName != "")
+    return std::make_unique<SourceLocLocationArgument>(std::move(Location));
+  llvm::errs() << "error: '-location' option must be specified using "
+                  "<file>:<line>:<column>\n";
+  return nullptr;
+}
+
 /// A container that stores the command-line options used by a single
 /// refactoring option.
 class RefactoringActionCommandLineOptions {
@@ -272,6 +350,17 @@ class RefactoringActionSubcommand : public cl::SubCommand {
         break;
       }
     }
+    // Check if the location option is supported.
+    for (const auto &Rule : this->ActionRules) {
+      if (Rule->hasLocationRequirement()) {
+        Location = std::make_unique<cl::opt<std::string>>(
+            "location",
+            cl::desc("Location where refactoring should "
+                     "be initiated( <file>:<line>:<column>)"),
+            cl::cat(Category), cl::sub(*this));
+        break;
+      }
+    }
     // Create the refactoring options.
     for (const auto &Rule : this->ActionRules) {
       CommandLineRefactoringOptionCreator OptionCreator(Category, *this,
@@ -296,11 +385,28 @@ class RefactoringActionSubcommand : public cl::SubCommand {
     return false;
   }
 
+  /// Parses the "-location" command-line argument.
+  ///
+  /// \returns true on error, false otherwise.
+  bool parseLocationArgument() {
+    if (Location) {
+      ParsedLocation = SourceLocationArgument::fromString(*Location);
+      if (!ParsedLocation)
+        return true;
+    }
+    return false;
+  }
+
   SourceSelectionArgument *getSelection() const {
     assert(Selection && "selection not supported!");
     return ParsedSelection.get();
   }
 
+  SourceLocationArgument *getLocation() const {
+    assert(Location && "location not supported!");
+    return ParsedLocation.get();
+  }
+
   const RefactoringActionCommandLineOptions &getOptions() const {
     return Options;
   }
@@ -309,7 +415,9 @@ class RefactoringActionSubcommand : public cl::SubCommand {
   std::unique_ptr<RefactoringAction> Action;
   RefactoringActionRules ActionRules;
   std::unique_ptr<cl::opt<std::string>> Selection;
+  std::unique_ptr<cl::opt<std::string>> Location;
   std::unique_ptr<SourceSelectionArgument> ParsedSelection;
+  std::unique_ptr<SourceLocationArgument> ParsedLocation;
   RefactoringActionCommandLineOptions Options;
 };
 
@@ -399,6 +507,7 @@ class ClangRefactorTool {
     // consumer.
     std::unique_ptr<ClangRefactorToolConsumerInterface> TestConsumer;
     bool HasSelection = MatchingRule->hasSelectionRequirement();
+    bool HasLocation = MatchingRule->hasLocationRequirement();
     if (HasSelection)
       TestConsumer = SelectedSubcommand->getSelection()->createCustomConsumer();
     ClangRefactorToolConsumerInterface *ActiveConsumer =
@@ -424,6 +533,19 @@ class ClangRefactorTool {
       ActiveConsumer->endTU();
       return;
     }
+    if (HasLocation) {
+      assert(SelectedSubcommand->getLocation() && "Missing location argument?");
+      if (opts::Verbose)
+        SelectedSubcommand->getLocation()->print(llvm::outs());
+      if (SelectedSubcommand->getLocation()->forAllLocations(
+              Context.getSources(), [&](SourceLocation L) {
+                Context.setLocation(L);
+                InvokeRule(*ActiveConsumer);
+              }))
+        HasFailed = true;
+      ActiveConsumer->endTU();
+      return;
+    }
     InvokeRule(*ActiveConsumer);
     ActiveConsumer->endTU();
   }
@@ -528,6 +650,12 @@ class ClangRefactorTool {
       R.getEnd().print(llvm::outs(), Context.getSources());
       llvm::outs() << "\n";
     }
+    if (Context.getLocation().isValid()) {
+      SourceLocation L = Context.getLocation();
+      llvm::outs() << "  -location=";
+      L.print(llvm::outs(), Context.getSources());
+      llvm::outs() << "\n";
+    }
   }
 
   llvm::Expected<RefactoringActionRule *>
@@ -539,16 +667,24 @@ class ClangRefactorTool {
       CommandLineRefactoringOptionVisitor Visitor(Subcommand.getOptions());
       Rule->visitRefactoringOptions(Visitor);
       if (Visitor.getMissingRequiredOptions().empty()) {
-        if (!Rule->hasSelectionRequirement()) {
-          MatchingRules.push_back(Rule.get());
-        } else {
+        bool HasMissingOptions = false;
+        if (Rule->hasSelectionRequirement()) {
           Subcommand.parseSelectionArgument();
-          if (Subcommand.getSelection()) {
-            MatchingRules.push_back(Rule.get());
-          } else {
+          if (!Subcommand.getSelection()) {
             MissingOptions.insert("selection");
+            HasMissingOptions = true;
           }
         }
+        if (Rule->hasLocationRequirement()) {
+          Subcommand.parseLocationArgument();
+          if (!Subcommand.getLocation()) {
+            MissingOptions.insert("location");
+            HasMissingOptions = true;
+          }
+        }
+        if (!HasMissingOptions) {
+          MatchingRules.push_back(Rule.get());
+        }
       }
       for (const RefactoringOption *Opt : Visitor.getMissingRequiredOptions())
         MissingOptions.insert(Opt->getName());

>From 6b7a480cf4d8c75cac99ba400a030761767f8f39 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=98=D0=B3=D0=BD=D0=B0=D1=82=20=D0=A1=D0=B5=D1=80=D0=B3?=
 =?UTF-8?q?=D0=B5=D0=B5=D0=B2?= <ignat.sergeev at softcom.su>
Date: Thu, 22 Aug 2024 11:13:56 +0000
Subject: [PATCH 3/3] Add edit matcher rule

Added matcher requirement, edit requirement, and edit match rule
---
 .../clang/Basic/DiagnosticRefactoringKinds.td |  4 +
 .../Tooling/Refactoring/Edit/EditMatchRule.h  | 48 ++++++++++
 .../RefactoringActionRuleRequirements.h       | 89 ++++++++++++++++++-
 clang/lib/Tooling/Refactoring/CMakeLists.txt  |  2 +
 .../Refactoring/Edit/EditMatchRule.cpp        | 74 +++++++++++++++
 5 files changed, 216 insertions(+), 1 deletion(-)
 create mode 100644 clang/include/clang/Tooling/Refactoring/Edit/EditMatchRule.h
 create mode 100644 clang/lib/Tooling/Refactoring/Edit/EditMatchRule.cpp

diff --git a/clang/include/clang/Basic/DiagnosticRefactoringKinds.td b/clang/include/clang/Basic/DiagnosticRefactoringKinds.td
index da35eae93df9a9..bb6af31dcffc81 100644
--- a/clang/include/clang/Basic/DiagnosticRefactoringKinds.td
+++ b/clang/include/clang/Basic/DiagnosticRefactoringKinds.td
@@ -30,6 +30,10 @@ def err_refactor_extract_prohibited_expression : Error<"the selected "
 
 def err_refactor_no_location : Error<"refactoring action can't be initiated "
   "without a location">;
+def err_refactor_no_location_match : Error<"refactoring action can't be initiated "
+  "without a matching location">;
+def err_refactor_no_ast_edit : Error<"refactoring action can't be initiated "
+  "without a correct ast edit">;
 }
 
 } // end of Refactoring diagnostics
diff --git a/clang/include/clang/Tooling/Refactoring/Edit/EditMatchRule.h b/clang/include/clang/Tooling/Refactoring/Edit/EditMatchRule.h
new file mode 100644
index 00000000000000..b102f770c31759
--- /dev/null
+++ b/clang/include/clang/Tooling/Refactoring/Edit/EditMatchRule.h
@@ -0,0 +1,48 @@
+//===--- EditMatchRule.h - Clang refactoring library ----------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLING_REFACTORING_EDIT_EDITMATCHRULE_H
+#define LLVM_CLANG_TOOLING_REFACTORING_EDIT_EDITMATCHRULE_H
+
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Tooling/Refactoring/RefactoringActionRules.h"
+#include "clang/Tooling/Transformer/RewriteRule.h"
+
+namespace clang {
+namespace tooling {
+
+/// A "Edit Match" refactoring rule edits code around matches according to
+/// ASTEdit.
+class EditMatchRule final : public SourceChangeRefactoringRule {
+public:
+  /// Initiates the delete match refactoring operation.
+  ///
+  /// \param R    MatchResult  Match result to edit.
+  /// \param AE    ASTEdit  Edit to perform.
+  static Expected<EditMatchRule>
+  initiate(RefactoringRuleContext &Context,
+           ast_matchers::MatchFinder::MatchResult R, transformer::ASTEdit AE);
+
+  static const RefactoringDescriptor &describe();
+
+private:
+  EditMatchRule(ast_matchers::MatchFinder::MatchResult R,
+                transformer::ASTEdit AE)
+      : Result(std::move(R)), Edit(std::move(AE)) {}
+
+  Expected<AtomicChanges>
+  createSourceReplacements(RefactoringRuleContext &Context) override;
+
+  ast_matchers::MatchFinder::MatchResult Result;
+  transformer::ASTEdit Edit;
+};
+
+} // end namespace tooling
+} // end namespace clang
+
+#endif // LLVM_CLANG_TOOLING_REFACTORING_EDIT_EDITMATCHRULE_H
diff --git a/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h b/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h
index bdd2d98df73eb1..ddbd4fdecea0fd 100644
--- a/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h
+++ b/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h
@@ -9,14 +9,18 @@
 #ifndef LLVM_CLANG_TOOLING_REFACTORING_REFACTORINGACTIONRULEREQUIREMENTS_H
 #define LLVM_CLANG_TOOLING_REFACTORING_REFACTORINGACTIONRULEREQUIREMENTS_H
 
+#include "clang/AST/ASTTypeTraits.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/ASTMatchers/ASTMatchersMacros.h"
 #include "clang/Basic/LLVM.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Tooling/Refactoring/ASTSelection.h"
 #include "clang/Tooling/Refactoring/RefactoringDiagnostic.h"
 #include "clang/Tooling/Refactoring/RefactoringOption.h"
 #include "clang/Tooling/Refactoring/RefactoringRuleContext.h"
+#include "clang/Tooling/Transformer/RewriteRule.h"
 #include "llvm/Support/Error.h"
-#include <type_traits>
 
 namespace clang {
 namespace tooling {
@@ -89,6 +93,89 @@ class SourceLocationRequirement : public RefactoringActionRuleRequirement {
   }
 };
 
+AST_POLYMORPHIC_MATCHER_P(hasPointWithin,
+                          AST_POLYMORPHIC_SUPPORTED_TYPES(Stmt, Decl),
+                          FullSourceLoc, L) {
+  if (!L.hasManager()) {
+    return false;
+  }
+  const SourceRange &SR = Node.getSourceRange();
+  return L.getManager().isPointWithin(L, SR.getBegin(), SR.getEnd());
+}
+
+/// An AST location match is satisfied when there is match around given
+/// location. In case of several matches inner one is taken.
+///
+/// The requirement will be evaluated only once during the initiation and
+/// search of matching refactoring action rules.
+template <typename MatcherType>
+class ASTLocMatchRequirement : public SourceLocationRequirement {
+public:
+  static_assert(
+      std::is_same<ast_matchers::StatementMatcher, MatcherType>::value ||
+          std::is_same<ast_matchers::DeclarationMatcher, MatcherType>::value,
+      "Expected a Statement or Declaration matcher");
+
+  class LocMatchCallback : public ast_matchers::MatchFinder::MatchCallback {
+  public:
+    void run(const clang::ast_matchers::MatchFinder::MatchResult &R) override {
+      Result = std::make_unique<ast_matchers::MatchFinder::MatchResult>(R);
+    }
+    std::unique_ptr<ast_matchers::MatchFinder::MatchResult> Result;
+  };
+
+  Expected<ast_matchers::MatchFinder::MatchResult>
+  evaluate(RefactoringRuleContext &Context) const {
+    Expected<SourceLocation> Location =
+        SourceLocationRequirement::evaluate(Context);
+    if (!Location)
+      return Location.takeError();
+    MatcherType M = createWrapperMatcher(
+        FullSourceLoc(*Location, Context.getASTContext().getSourceManager()),
+        Matcher);
+
+    ast_matchers::MatchFinder MF;
+    LocMatchCallback Callback;
+    MF.addMatcher(M, &Callback);
+    MF.matchAST(Context.getASTContext());
+    if (!Callback.Result)
+      return Context.createDiagnosticError(
+          diag::err_refactor_no_location_match);
+    return *Callback.Result;
+  }
+
+  ASTLocMatchRequirement(MatcherType M) : Matcher(M) {}
+
+private:
+  ast_matchers::StatementMatcher
+  createWrapperMatcher(FullSourceLoc L,
+                       ast_matchers::StatementMatcher M) const {
+    return ast_matchers::stmt(M, hasPointWithin(L));
+  }
+
+  ast_matchers::DeclarationMatcher
+  createWrapperMatcher(FullSourceLoc L,
+                       ast_matchers::DeclarationMatcher M) const {
+    return ast_matchers::decl(M, hasPointWithin(L));
+  }
+
+  MatcherType Matcher;
+};
+
+/// Requirement that evaluates to the ASTEdit value given at its creation.
+class ASTEditRequirement : public RefactoringActionRuleRequirement {
+public:
+  Expected<transformer::ASTEdit>
+  evaluate(RefactoringRuleContext &Context) const {
+    return Edit;
+  }
+
+  ASTEditRequirement(transformer::ASTEdit AE) : Edit(std::move(AE)) {}
+
+private:
+  transformer::ASTEdit Edit;
+};
+
 /// A base class for any requirement that requires some refactoring options.
 class RefactoringOptionsRequirement : public RefactoringActionRuleRequirement {
 public:
diff --git a/clang/lib/Tooling/Refactoring/CMakeLists.txt b/clang/lib/Tooling/Refactoring/CMakeLists.txt
index d3077be8810aad..97023b6d6b97bb 100644
--- a/clang/lib/Tooling/Refactoring/CMakeLists.txt
+++ b/clang/lib/Tooling/Refactoring/CMakeLists.txt
@@ -13,6 +13,7 @@ add_clang_library(clangToolingRefactoring
   Rename/USRFinder.cpp
   Rename/USRFindingAction.cpp
   Rename/USRLocFinder.cpp
+  Edit/EditMatchRule.cpp
 
   LINK_LIBS
   clangAST
@@ -23,6 +24,7 @@ add_clang_library(clangToolingRefactoring
   clangLex
   clangRewrite
   clangToolingCore
+  clangTransformer
 
   DEPENDS
   omp_gen
diff --git a/clang/lib/Tooling/Refactoring/Edit/EditMatchRule.cpp b/clang/lib/Tooling/Refactoring/Edit/EditMatchRule.cpp
new file mode 100644
index 00000000000000..8ea162d6dab90b
--- /dev/null
+++ b/clang/lib/Tooling/Refactoring/Edit/EditMatchRule.cpp
@@ -0,0 +1,74 @@
+//===--- EditMatchRule.cpp - Clang refactoring library --------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implements the "edit-match" refactoring rule that can edit matcher results
+///
+//===----------------------------------------------------------------------===//
+
+#include "clang/Tooling/Refactoring/Edit/EditMatchRule.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Basic/DiagnosticRefactoring.h"
+#include "clang/Rewrite/Core/Rewriter.h"
+#include "clang/Tooling/Transformer/RewriteRule.h"
+#include "clang/Tooling/Transformer/SourceCode.h"
+
+using namespace clang;
+using namespace tooling;
+using namespace transformer;
+
+Expected<EditMatchRule>
+EditMatchRule::initiate(RefactoringRuleContext &Context,
+                        ast_matchers::MatchFinder::MatchResult R, ASTEdit AE) {
+  return EditMatchRule(std::move(R), std::move(AE));
+}
+
+const RefactoringDescriptor &EditMatchRule::describe() {
+  static const RefactoringDescriptor Descriptor = {
+      "edit-match",
+      "Edit Match",
+      "Edits match result source code",
+  };
+  return Descriptor;
+}
+
+Expected<AtomicChanges>
+EditMatchRule::createSourceReplacements(RefactoringRuleContext &Context) {
+  ASTContext &AST = Context.getASTContext();
+  SourceManager &SM = AST.getSourceManager();
+
+  Expected<CharSourceRange> Range = Edit.TargetRange(Result);
+  if (!Range)
+    return std::move(Range.takeError());
+  std::optional<CharSourceRange> EditRange =
+      getFileRangeForEdit(std::move(*Range), AST);
+  if (!EditRange)
+    return Context.createDiagnosticError(diag::err_refactor_no_ast_edit);
+
+  AtomicChange Change(SM, EditRange->getBegin());
+  {
+    auto Replacement = Edit.Replacement->eval(Result);
+    if (!Replacement)
+      return std::move(Replacement.takeError());
+
+    switch (Edit.Kind) {
+    case EditKind::Range:
+      if (auto Err = Change.replace(SM, std::move(*EditRange),
+                                    std::move(*Replacement))) {
+        return std::move(Err);
+      }
+      break;
+    case EditKind::AddInclude:
+      Change.addHeader(std::move(*Replacement));
+      break;
+    }
+  }
+
+  return AtomicChanges{std::move(Change)};
+}



More information about the cfe-commits mailing list