[clang-tools-extra] 1844054 - [libTooling] Generalize string explanation as templated metadata

Yitzhak Mandelbaum via cfe-commits cfe-commits at lists.llvm.org
Mon Mar 21 11:46:09 PDT 2022


Author: Eric Li
Date: 2022-03-21T18:45:39Z
New Revision: 18440547d3520b78c9ab929685309419fc1fbe95

URL: https://github.com/llvm/llvm-project/commit/18440547d3520b78c9ab929685309419fc1fbe95
DIFF: https://github.com/llvm/llvm-project/commit/18440547d3520b78c9ab929685309419fc1fbe95.diff

LOG: [libTooling] Generalize string explanation as templated metadata

Change RewriteRule from holding an `Explanation` to being able to generate
arbitrary metadata. Where TransformerClangTidyCheck was interested in a string
description for the diagnostic, other tools may be interested in richer metadata
at a higher level of abstraction than at the edit level (which is currently
available as ASTEdit::Metadata).

Reviewed By: ymandel

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

Added: 
    

Modified: 
    clang-tools-extra/clang-tidy/abseil/CleanupCtadCheck.cpp
    clang-tools-extra/clang-tidy/abseil/StringFindStrContainsCheck.cpp
    clang-tools-extra/clang-tidy/bugprone/StringviewNullptrCheck.cpp
    clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp
    clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.h
    clang-tools-extra/unittests/clang-tidy/TransformerClangTidyCheckTest.cpp
    clang/include/clang/Tooling/Transformer/RewriteRule.h
    clang/include/clang/Tooling/Transformer/Transformer.h
    clang/lib/Tooling/Transformer/RewriteRule.cpp
    clang/lib/Tooling/Transformer/Transformer.cpp
    clang/unittests/Tooling/TransformerTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clang-tidy/abseil/CleanupCtadCheck.cpp b/clang-tools-extra/clang-tidy/abseil/CleanupCtadCheck.cpp
index bbf860342fee6..e6617fb7dd89e 100644
--- a/clang-tools-extra/clang-tidy/abseil/CleanupCtadCheck.cpp
+++ b/clang-tools-extra/clang-tidy/abseil/CleanupCtadCheck.cpp
@@ -23,7 +23,7 @@ namespace clang {
 namespace tidy {
 namespace abseil {
 
-RewriteRule CleanupCtadCheckImpl() {
+RewriteRuleWith<std::string> CleanupCtadCheckImpl() {
   auto warning_message = cat("prefer absl::Cleanup's class template argument "
                              "deduction pattern in C++17 and higher");
 

diff  --git a/clang-tools-extra/clang-tidy/abseil/StringFindStrContainsCheck.cpp b/clang-tools-extra/clang-tidy/abseil/StringFindStrContainsCheck.cpp
index 601b987d332b6..964826a3bd615 100644
--- a/clang-tools-extra/clang-tidy/abseil/StringFindStrContainsCheck.cpp
+++ b/clang-tools-extra/clang-tidy/abseil/StringFindStrContainsCheck.cpp
@@ -30,7 +30,7 @@ using ::clang::transformer::cat;
 using ::clang::transformer::changeTo;
 using ::clang::transformer::makeRule;
 using ::clang::transformer::node;
-using ::clang::transformer::RewriteRule;
+using ::clang::transformer::RewriteRuleWith;
 
 AST_MATCHER(Type, isCharType) { return Node.isCharType(); }
 
@@ -39,7 +39,7 @@ static const char DefaultStringLikeClasses[] = "::std::basic_string;"
                                                "::absl::string_view";
 static const char DefaultAbseilStringsMatchHeader[] = "absl/strings/match.h";
 
-static transformer::RewriteRule
+static transformer::RewriteRuleWith<std::string>
 makeRewriteRule(const std::vector<std::string> &StringLikeClassNames,
                 StringRef AbseilStringsMatchHeader) {
   auto StringLikeClass = cxxRecordDecl(hasAnyName(SmallVector<StringRef, 4>(
@@ -62,7 +62,7 @@ makeRewriteRule(const std::vector<std::string> &StringLikeClassNames,
             hasArgument(1, cxxDefaultArgExpr())),
       onImplicitObjectArgument(expr().bind("string_being_searched")));
 
-  RewriteRule Rule = applyFirst(
+  RewriteRuleWith<std::string> Rule = applyFirst(
       {makeRule(
            binaryOperator(hasOperatorName("=="),
                           hasOperands(ignoringParenImpCasts(StringNpos),

diff  --git a/clang-tools-extra/clang-tidy/bugprone/StringviewNullptrCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/StringviewNullptrCheck.cpp
index b45aa93533b08..205f0c4ff9c6f 100644
--- a/clang-tools-extra/clang-tidy/bugprone/StringviewNullptrCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/StringviewNullptrCheck.cpp
@@ -37,7 +37,7 @@ AST_MATCHER(clang::VarDecl, isDirectInitialization) {
 
 } // namespace
 
-RewriteRule StringviewNullptrCheckImpl() {
+RewriteRuleWith<std::string> StringviewNullptrCheckImpl() {
   auto construction_warning =
       cat("constructing basic_string_view from null is undefined; replace with "
           "the default constructor");

diff  --git a/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp b/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp
index 9f64c562600bc..c18838fb0f992 100644
--- a/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp
+++ b/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp
@@ -13,16 +13,16 @@
 namespace clang {
 namespace tidy {
 namespace utils {
-using transformer::RewriteRule;
+using transformer::RewriteRuleWith;
 
 #ifndef NDEBUG
-static bool hasExplanation(const RewriteRule::Case &C) {
-  return C.Explanation != nullptr;
+static bool hasGenerator(const transformer::Generator<std::string> &G) {
+  return G != nullptr;
 }
 #endif
 
-static void verifyRule(const RewriteRule &Rule) {
-  assert(llvm::all_of(Rule.Cases, hasExplanation) &&
+static void verifyRule(const RewriteRuleWith<std::string> &Rule) {
+  assert(llvm::all_of(Rule.Metadata, hasGenerator) &&
          "clang-tidy checks must have an explanation by default;"
          " explicitly provide an empty explanation if none is desired");
 }
@@ -39,23 +39,24 @@ TransformerClangTidyCheck::TransformerClangTidyCheck(StringRef Name,
 // we would be accessing `getLangOpts` and `Options` before the underlying
 // `ClangTidyCheck` instance was properly initialized.
 TransformerClangTidyCheck::TransformerClangTidyCheck(
-    std::function<Optional<RewriteRule>(const LangOptions &,
-                                        const OptionsView &)>
+    std::function<Optional<RewriteRuleWith<std::string>>(const LangOptions &,
+                                                         const OptionsView &)>
         MakeRule,
     StringRef Name, ClangTidyContext *Context)
     : TransformerClangTidyCheck(Name, Context) {
-  if (Optional<RewriteRule> R = MakeRule(getLangOpts(), Options))
+  if (Optional<RewriteRuleWith<std::string>> R =
+          MakeRule(getLangOpts(), Options))
     setRule(std::move(*R));
 }
 
-TransformerClangTidyCheck::TransformerClangTidyCheck(RewriteRule R,
-                                                     StringRef Name,
-                                                     ClangTidyContext *Context)
+TransformerClangTidyCheck::TransformerClangTidyCheck(
+    RewriteRuleWith<std::string> R, StringRef Name, ClangTidyContext *Context)
     : TransformerClangTidyCheck(Name, Context) {
   setRule(std::move(R));
 }
 
-void TransformerClangTidyCheck::setRule(transformer::RewriteRule R) {
+void TransformerClangTidyCheck::setRule(
+    transformer::RewriteRuleWith<std::string> R) {
   verifyRule(R);
   Rule = std::move(R);
 }
@@ -77,8 +78,9 @@ void TransformerClangTidyCheck::check(
   if (Result.Context->getDiagnostics().hasErrorOccurred())
     return;
 
-  RewriteRule::Case Case = transformer::detail::findSelectedCase(Result, Rule);
-  Expected<SmallVector<transformer::Edit, 1>> Edits = Case.Edits(Result);
+  size_t I = transformer::detail::findSelectedCase(Result, Rule);
+  Expected<SmallVector<transformer::Edit, 1>> Edits =
+      Rule.Cases[I].Edits(Result);
   if (!Edits) {
     llvm::errs() << "Rewrite failed: " << llvm::toString(Edits.takeError())
                  << "\n";
@@ -89,7 +91,7 @@ void TransformerClangTidyCheck::check(
   if (Edits->empty())
     return;
 
-  Expected<std::string> Explanation = Case.Explanation->eval(Result);
+  Expected<std::string> Explanation = Rule.Metadata[I]->eval(Result);
   if (!Explanation) {
     llvm::errs() << "Error in explanation: "
                  << llvm::toString(Explanation.takeError()) << "\n";

diff  --git a/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.h b/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.h
index d26737935b1aa..38ca2011fba14 100644
--- a/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.h
+++ b/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.h
@@ -48,15 +48,16 @@ class TransformerClangTidyCheck : public ClangTidyCheck {
   /// cases where the options disable the check.
   ///
   /// See \c setRule for constraints on the rule.
-  TransformerClangTidyCheck(std::function<Optional<transformer::RewriteRule>(
-                                const LangOptions &, const OptionsView &)>
-                                MakeRule,
-                            StringRef Name, ClangTidyContext *Context);
+  TransformerClangTidyCheck(
+      std::function<Optional<transformer::RewriteRuleWith<std::string>>(
+          const LangOptions &, const OptionsView &)>
+          MakeRule,
+      StringRef Name, ClangTidyContext *Context);
 
   /// Convenience overload of the constructor when the rule doesn't have any
   /// dependencies.
-  TransformerClangTidyCheck(transformer::RewriteRule R, StringRef Name,
-                            ClangTidyContext *Context);
+  TransformerClangTidyCheck(transformer::RewriteRuleWith<std::string> R,
+                            StringRef Name, ClangTidyContext *Context);
 
   void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
                            Preprocessor *ModuleExpanderPP) override;
@@ -74,10 +75,10 @@ class TransformerClangTidyCheck : public ClangTidyCheck {
   /// is a bug.  If no explanation is desired, indicate that explicitly (for
   /// example, by passing `text("no explanation")` to `makeRule` as the
   /// `Explanation` argument).
-  void setRule(transformer::RewriteRule R);
+  void setRule(transformer::RewriteRuleWith<std::string> R);
 
 private:
-  transformer::RewriteRule Rule;
+  transformer::RewriteRuleWith<std::string> Rule;
   IncludeInserter Inserter;
 };
 

diff  --git a/clang-tools-extra/unittests/clang-tidy/TransformerClangTidyCheckTest.cpp b/clang-tools-extra/unittests/clang-tidy/TransformerClangTidyCheckTest.cpp
index 53ea4f5977587..832b8b86e1266 100644
--- a/clang-tools-extra/unittests/clang-tidy/TransformerClangTidyCheckTest.cpp
+++ b/clang-tools-extra/unittests/clang-tidy/TransformerClangTidyCheckTest.cpp
@@ -28,14 +28,14 @@ using transformer::IncludeFormat;
 using transformer::makeRule;
 using transformer::node;
 using transformer::noopEdit;
-using transformer::RewriteRule;
+using transformer::RewriteRuleWith;
 using transformer::RootID;
 using transformer::statement;
 
 // Invert the code of an if-statement, while maintaining its semantics.
-RewriteRule invertIf() {
+RewriteRuleWith<std::string> invertIf() {
   StringRef C = "C", T = "T", E = "E";
-  RewriteRule Rule = makeRule(
+  RewriteRuleWith<std::string> Rule = makeRule(
       ifStmt(hasCondition(expr().bind(C)), hasThen(stmt().bind(T)),
              hasElse(stmt().bind(E))),
       change(statement(RootID), cat("if(!(", node(std::string(C)), ")) ",
@@ -140,8 +140,9 @@ TEST(TransformerClangTidyCheckTest, TwoMatchesInMacroExpansion) {
 }
 
 // A trivial rewrite-rule generator that requires Objective-C code.
-Optional<RewriteRule> needsObjC(const LangOptions &LangOpts,
-                                const ClangTidyCheck::OptionsView &Options) {
+Optional<RewriteRuleWith<std::string>>
+needsObjC(const LangOptions &LangOpts,
+          const ClangTidyCheck::OptionsView &Options) {
   if (!LangOpts.ObjC)
     return None;
   return makeRule(clang::ast_matchers::functionDecl(),
@@ -165,8 +166,9 @@ TEST(TransformerClangTidyCheckTest, DisableByLang) {
 }
 
 // A trivial rewrite rule generator that checks config options.
-Optional<RewriteRule> noSkip(const LangOptions &LangOpts,
-                             const ClangTidyCheck::OptionsView &Options) {
+Optional<RewriteRuleWith<std::string>>
+noSkip(const LangOptions &LangOpts,
+       const ClangTidyCheck::OptionsView &Options) {
   if (Options.get("Skip", "false") == "true")
     return None;
   return makeRule(clang::ast_matchers::functionDecl(),
@@ -194,10 +196,11 @@ TEST(TransformerClangTidyCheckTest, DisableByConfig) {
                           Input, nullptr, "input.cc", None, Options));
 }
 
-RewriteRule replaceCall(IncludeFormat Format) {
+RewriteRuleWith<std::string> replaceCall(IncludeFormat Format) {
   using namespace ::clang::ast_matchers;
-  RewriteRule Rule = makeRule(callExpr(callee(functionDecl(hasName("f")))),
-                              change(cat("other()")), cat("no message"));
+  RewriteRuleWith<std::string> Rule =
+      makeRule(callExpr(callee(functionDecl(hasName("f")))),
+               change(cat("other()")), cat("no message"));
   addInclude(Rule, "clang/OtherLib.h", Format);
   return Rule;
 }
@@ -243,10 +246,10 @@ TEST(TransformerClangTidyCheckTest, AddIncludeAngled) {
 }
 
 class IncludeOrderCheck : public TransformerClangTidyCheck {
-  static RewriteRule rule() {
+  static RewriteRuleWith<std::string> rule() {
     using namespace ::clang::ast_matchers;
-    RewriteRule Rule = transformer::makeRule(integerLiteral(), change(cat("5")),
-                                             cat("no message"));
+    RewriteRuleWith<std::string> Rule = transformer::makeRule(
+        integerLiteral(), change(cat("5")), cat("no message"));
     addInclude(Rule, "bar.h", IncludeFormat::Quoted);
     return Rule;
   }

diff  --git a/clang/include/clang/Tooling/Transformer/RewriteRule.h b/clang/include/clang/Tooling/Transformer/RewriteRule.h
index 6b14861e92d76..b4609099bf804 100644
--- a/clang/include/clang/Tooling/Transformer/RewriteRule.h
+++ b/clang/include/clang/Tooling/Transformer/RewriteRule.h
@@ -61,7 +61,9 @@ enum class IncludeFormat {
 /// of `EditGenerator`.
 using EditGenerator = MatchConsumer<llvm::SmallVector<Edit, 1>>;
 
-using TextGenerator = std::shared_ptr<MatchComputation<std::string>>;
+template <typename T> using Generator = std::shared_ptr<MatchComputation<T>>;
+
+using TextGenerator = Generator<std::string>;
 
 using AnyGenerator = MatchConsumer<llvm::Any>;
 
@@ -262,12 +264,9 @@ inline EditGenerator shrinkTo(RangeSelector outer, RangeSelector inner) {
 //
 // * Edits: a set of Edits to the source code, described with ASTEdits.
 //
-// * Explanation: explanation of the rewrite.  This will be displayed to the
-//   user, where possible; for example, in clang-tidy diagnostics.
-//
 // However, rules can also consist of (sub)rules, where the first that matches
-// is applied and the rest are ignored.  So, the above components are gathered
-// as a `Case` and a rule is a list of cases.
+// is applied and the rest are ignored.  So, the above components together form
+// a logical "case" and a rule is a sequence of cases.
 //
 // Rule cases have an additional, implicit, component: the parameters. These are
 // portions of the pattern which are left unspecified, yet bound in the pattern
@@ -275,37 +274,82 @@ inline EditGenerator shrinkTo(RangeSelector outer, RangeSelector inner) {
 //
 // The \c Transformer class can be used to apply the rewrite rule and obtain the
 // corresponding replacements.
-struct RewriteRule {
+struct RewriteRuleBase {
   struct Case {
     ast_matchers::internal::DynTypedMatcher Matcher;
     EditGenerator Edits;
-    TextGenerator Explanation;
   };
   // We expect RewriteRules will most commonly include only one case.
   SmallVector<Case, 1> Cases;
+};
 
-  /// DEPRECATED: use `::clang::transformer::RootID` instead.
-  static const llvm::StringRef RootID;
+/// A source-code transformation with accompanying metadata.
+///
+/// When a case of the rule matches, the \c Transformer invokes the
+/// corresponding metadata generator and provides it alongside the edits.
+template <typename MetadataT> struct RewriteRuleWith : RewriteRuleBase {
+  SmallVector<Generator<MetadataT>, 1> Metadata;
 };
 
-/// Constructs a simple \c RewriteRule.
+template <> struct RewriteRuleWith<void> : RewriteRuleBase {};
+
+using RewriteRule = RewriteRuleWith<void>;
+
+namespace detail {
+
+RewriteRule makeRule(ast_matchers::internal::DynTypedMatcher M,
+                     EditGenerator Edits);
+
+template <typename MetadataT>
+RewriteRuleWith<MetadataT> makeRule(ast_matchers::internal::DynTypedMatcher M,
+                                    EditGenerator Edits,
+                                    Generator<MetadataT> Metadata) {
+  RewriteRuleWith<MetadataT> R;
+  R.Cases = {{std::move(M), std::move(Edits)}};
+  R.Metadata = {std::move(Metadata)};
+  return R;
+}
+
+inline EditGenerator makeEditGenerator(EditGenerator Edits) { return Edits; }
+EditGenerator makeEditGenerator(llvm::SmallVector<ASTEdit, 1> Edits);
+EditGenerator makeEditGenerator(ASTEdit Edit);
+
+} // namespace detail
+
+/// Constructs a simple \c RewriteRule. \c Edits can be an \c EditGenerator,
+/// multiple \c ASTEdits, or a single \c ASTEdit.
+/// @{
+template <int &..., typename EditsT>
+RewriteRule makeRule(ast_matchers::internal::DynTypedMatcher M,
+                     EditsT &&Edits) {
+  return detail::makeRule(
+      std::move(M), detail::makeEditGenerator(std::forward<EditsT>(Edits)));
+}
+
 RewriteRule makeRule(ast_matchers::internal::DynTypedMatcher M,
-                     EditGenerator Edits, TextGenerator Explanation = nullptr);
-
-/// Constructs a \c RewriteRule from multiple `ASTEdit`s.
-inline RewriteRule makeRule(ast_matchers::internal::DynTypedMatcher M,
-                            llvm::SmallVector<ASTEdit, 1> Edits,
-                            TextGenerator Explanation = nullptr) {
-  return makeRule(std::move(M), editList(std::move(Edits)),
-                  std::move(Explanation));
+                     std::initializer_list<ASTEdit> Edits);
+/// @}
+
+/// Overloads of \c makeRule that also generate metadata when matching.
+/// @{
+template <typename MetadataT, int &..., typename EditsT>
+RewriteRuleWith<MetadataT> makeRule(ast_matchers::internal::DynTypedMatcher M,
+                                    EditsT &&Edits,
+                                    Generator<MetadataT> Metadata) {
+  return detail::makeRule(
+      std::move(M), detail::makeEditGenerator(std::forward<EditsT>(Edits)),
+      std::move(Metadata));
 }
 
-/// Overload of \c makeRule for common case of only one edit.
-inline RewriteRule makeRule(ast_matchers::internal::DynTypedMatcher M,
-                            ASTEdit Edit,
-                            TextGenerator Explanation = nullptr) {
-  return makeRule(std::move(M), edit(std::move(Edit)), std::move(Explanation));
+template <typename MetadataT>
+RewriteRuleWith<MetadataT> makeRule(ast_matchers::internal::DynTypedMatcher M,
+                                    std::initializer_list<ASTEdit> Edits,
+                                    Generator<MetadataT> Metadata) {
+  return detail::makeRule(std::move(M),
+                          detail::makeEditGenerator(std::move(Edits)),
+                          std::move(Metadata));
 }
+/// @}
 
 /// For every case in Rule, adds an include directive for the given header. The
 /// common use is assumed to be a rule with only one case. For example, to
@@ -317,7 +361,7 @@ inline RewriteRule makeRule(ast_matchers::internal::DynTypedMatcher M,
 ///   addInclude(R, "path/to/bar_header.h");
 ///   addInclude(R, "vector", IncludeFormat::Angled);
 /// \endcode
-void addInclude(RewriteRule &Rule, llvm::StringRef Header,
+void addInclude(RewriteRuleBase &Rule, llvm::StringRef Header,
                 IncludeFormat Format = IncludeFormat::Quoted);
 
 /// Applies the first rule whose pattern matches; other rules are ignored.  If
@@ -359,7 +403,45 @@ void addInclude(RewriteRule &Rule, llvm::StringRef Header,
 //                             makeRule(left_call, left_call_action),
 //                             makeRule(right_call, right_call_action)});
 // ```
-RewriteRule applyFirst(ArrayRef<RewriteRule> Rules);
+/// @{
+template <typename MetadataT>
+RewriteRuleWith<MetadataT>
+applyFirst(ArrayRef<RewriteRuleWith<MetadataT>> Rules) {
+  RewriteRuleWith<MetadataT> R;
+  for (auto &Rule : Rules) {
+    assert(Rule.Cases.size() == Rule.Metadata.size() &&
+           "mis-match in case and metadata array size");
+    R.Cases.append(Rule.Cases.begin(), Rule.Cases.end());
+    R.Metadata.append(Rule.Metadata.begin(), Rule.Metadata.end());
+  }
+  return R;
+}
+
+template <>
+RewriteRuleWith<void> applyFirst(ArrayRef<RewriteRuleWith<void>> Rules);
+
+template <typename MetadataT>
+RewriteRuleWith<MetadataT>
+applyFirst(const std::vector<RewriteRuleWith<MetadataT>> &Rules) {
+  return applyFirst(llvm::makeArrayRef(Rules));
+}
+
+template <typename MetadataT>
+RewriteRuleWith<MetadataT>
+applyFirst(std::initializer_list<RewriteRuleWith<MetadataT>> Rules) {
+  return applyFirst(llvm::makeArrayRef(Rules.begin(), Rules.end()));
+}
+/// @}
+
+/// Converts a \c RewriteRuleWith<T> to a \c RewriteRule by stripping off the
+/// metadata generators.
+template <int &..., typename MetadataT>
+std::enable_if_t<!std::is_same<MetadataT, void>::value, RewriteRule>
+stripMetadata(RewriteRuleWith<MetadataT> Rule) {
+  RewriteRule R;
+  R.Cases = std::move(Rule.Cases);
+  return R;
+}
 
 /// Applies `Rule` to all descendants of the node bound to `NodeId`. `Rule` can
 /// refer to nodes bound by the calling rule. `Rule` is not applied to the node
@@ -423,7 +505,8 @@ rewriteDescendants(const DynTypedNode &Node, RewriteRule Rule,
 /// Only supports Rules whose cases' matchers share the same base "kind"
 /// (`Stmt`, `Decl`, etc.)  Deprecated: use `buildMatchers` instead, which
 /// supports mixing matchers of 
diff erent kinds.
-ast_matchers::internal::DynTypedMatcher buildMatcher(const RewriteRule &Rule);
+ast_matchers::internal::DynTypedMatcher
+buildMatcher(const RewriteRuleBase &Rule);
 
 /// Builds a set of matchers that cover the rule.
 ///
@@ -433,7 +516,7 @@ ast_matchers::internal::DynTypedMatcher buildMatcher(const RewriteRule &Rule);
 /// for rewriting. If any such matchers are included, will return an empty
 /// vector.
 std::vector<ast_matchers::internal::DynTypedMatcher>
-buildMatchers(const RewriteRule &Rule);
+buildMatchers(const RewriteRuleBase &Rule);
 
 /// Gets the beginning location of the source matched by a rewrite rule. If the
 /// match occurs within a macro expansion, returns the beginning of the
@@ -441,11 +524,10 @@ buildMatchers(const RewriteRule &Rule);
 SourceLocation
 getRuleMatchLoc(const ast_matchers::MatchFinder::MatchResult &Result);
 
-/// Returns the \c Case of \c Rule that was selected in the match result.
-/// Assumes a matcher built with \c buildMatcher.
-const RewriteRule::Case &
-findSelectedCase(const ast_matchers::MatchFinder::MatchResult &Result,
-                 const RewriteRule &Rule);
+/// Returns the index of the \c Case of \c Rule that was selected in the match
+/// result. Assumes a matcher built with \c buildMatcher.
+size_t findSelectedCase(const ast_matchers::MatchFinder::MatchResult &Result,
+                        const RewriteRuleBase &Rule);
 } // namespace detail
 } // namespace transformer
 } // namespace clang

diff  --git a/clang/include/clang/Tooling/Transformer/Transformer.h b/clang/include/clang/Tooling/Transformer/Transformer.h
index 3e0a026a7f2aa..53dd61f35ef5e 100644
--- a/clang/include/clang/Tooling/Transformer/Transformer.h
+++ b/clang/include/clang/Tooling/Transformer/Transformer.h
@@ -18,6 +18,62 @@
 
 namespace clang {
 namespace tooling {
+
+namespace detail {
+/// Implementation details of \c Transformer with type erasure around
+/// \c RewriteRule and \c RewriteRule<T> as well as the corresponding consumers.
+class TransformerImpl {
+public:
+  virtual ~TransformerImpl() = default;
+
+  void onMatch(const ast_matchers::MatchFinder::MatchResult &Result);
+
+  virtual std::vector<ast_matchers::internal::DynTypedMatcher>
+  buildMatchers() const = 0;
+
+protected:
+  /// Converts a set of \c Edit into a \c AtomicChange per file modified.
+  /// Returns an error if the edits fail to compose, e.g. overlapping edits.
+  static llvm::Expected<llvm::SmallVector<AtomicChange, 1>>
+  convertToAtomicChanges(const llvm::SmallVectorImpl<transformer::Edit> &Edits,
+                         const ast_matchers::MatchFinder::MatchResult &Result);
+
+private:
+  virtual void
+  onMatchImpl(const ast_matchers::MatchFinder::MatchResult &Result) = 0;
+};
+
+/// Implementation for when no metadata is generated as a part of the
+/// \c RewriteRule.
+class NoMetadataImpl final : public TransformerImpl {
+  transformer::RewriteRule Rule;
+  std::function<void(Expected<llvm::MutableArrayRef<AtomicChange>>)> Consumer;
+
+public:
+  explicit NoMetadataImpl(
+      transformer::RewriteRule R,
+      std::function<void(Expected<llvm::MutableArrayRef<AtomicChange>>)>
+          Consumer)
+      : Rule(std::move(R)), Consumer(std::move(Consumer)) {
+    assert(llvm::all_of(Rule.Cases,
+                        [](const transformer::RewriteRule::Case &Case) {
+                          return Case.Edits;
+                        }) &&
+           "edit generator must be provided for each rule");
+  }
+
+private:
+  void onMatchImpl(const ast_matchers::MatchFinder::MatchResult &Result) final;
+  std::vector<ast_matchers::internal::DynTypedMatcher>
+  buildMatchers() const final {
+    return transformer::detail::buildMatchers(Rule);
+  }
+};
+
+// FIXME: Use std::type_identity or backport when available.
+template <class T> struct type_identity { using type = T; };
+} // namespace detail
+
 /// Handles the matcher and callback registration for a single `RewriteRule`, as
 /// defined by the arguments of the constructor.
 class Transformer : public ast_matchers::MatchFinder::MatchCallback {
@@ -31,16 +87,38 @@ class Transformer : public ast_matchers::MatchFinder::MatchCallback {
   using ChangeSetConsumer = std::function<void(
       Expected<llvm::MutableArrayRef<AtomicChange>> Changes)>;
 
-  /// \param Consumer Receives all rewrites for a single match, or an error.
+  template <typename T> struct Result {
+    llvm::MutableArrayRef<AtomicChange> Changes;
+    T Metadata;
+  };
+
+  // Specialization provided only to avoid SFINAE on the Transformer
+  // constructor; not intended for use.
+  template <> struct Result<void> {
+    llvm::MutableArrayRef<AtomicChange> Changes;
+  };
+
+  /// \param Consumer receives all rewrites for a single match, or an error.
   /// Will not necessarily be called for each match; for example, if the rule
   /// generates no edits but does not fail.  Note that clients are responsible
   /// for handling the case that independent \c AtomicChanges conflict with each
   /// other.
-  explicit Transformer(transformer::RewriteRule Rule,
+  explicit Transformer(transformer::RewriteRuleWith<void> Rule,
                        ChangeSetConsumer Consumer)
-      : Rule(std::move(Rule)), Consumer(std::move(Consumer)) {
-    assert(this->Consumer && "Consumer is empty");
-  }
+      : Impl(std::make_unique<detail::NoMetadataImpl>(std::move(Rule),
+                                                      std::move(Consumer))) {}
+
+  /// \param Consumer receives all rewrites and the associated metadata for a
+  /// single match, or an error. Will always be called for each match, even if
+  /// the rule generates no edits.  Note that clients are responsible for
+  /// handling the case that independent \c AtomicChanges conflict with each
+  /// other.
+  template <typename MetadataT>
+  explicit Transformer(
+      transformer::RewriteRuleWith<MetadataT> Rule,
+      std::function<void(llvm::Expected<Transformer::Result<
+                             typename detail::type_identity<MetadataT>::type>>)>
+          Consumer);
 
   /// N.B. Passes `this` pointer to `MatchFinder`.  So, this object should not
   /// be moved after this call.
@@ -51,11 +129,77 @@ class Transformer : public ast_matchers::MatchFinder::MatchCallback {
   void run(const ast_matchers::MatchFinder::MatchResult &Result) override;
 
 private:
-  transformer::RewriteRule Rule;
-  /// Receives sets of successful rewrites as an
-  /// \c llvm::ArrayRef<AtomicChange>.
-  ChangeSetConsumer Consumer;
+  std::unique_ptr<detail::TransformerImpl> Impl;
+};
+
+namespace detail {
+/// Implementation when metadata is generated as a part of the rewrite. This
+/// happens when we have a \c RewriteRuleWith<T>.
+template <typename T> class WithMetadataImpl final : public TransformerImpl {
+  transformer::RewriteRuleWith<T> Rule;
+  std::function<void(llvm::Expected<Transformer::Result<T>>)> Consumer;
+
+public:
+  explicit WithMetadataImpl(
+      transformer::RewriteRuleWith<T> R,
+      std::function<void(llvm::Expected<Transformer::Result<T>>)> Consumer)
+      : Rule(std::move(R)), Consumer(std::move(Consumer)) {
+    assert(llvm::all_of(Rule.Cases,
+                        [](const transformer::RewriteRuleBase::Case &Case)
+                            -> bool { return !!Case.Edits; }) &&
+           "edit generator must be provided for each rule");
+    assert(llvm::all_of(Rule.Metadata,
+                        [](const typename transformer::Generator<T> &Metadata)
+                            -> bool { return !!Metadata; }) &&
+           "metadata generator must be provided for each rule");
+  }
+
+private:
+  void onMatchImpl(const ast_matchers::MatchFinder::MatchResult &Result) final {
+    size_t I = transformer::detail::findSelectedCase(Result, Rule);
+    auto Transformations = Rule.Cases[I].Edits(Result);
+    if (!Transformations) {
+      Consumer(Transformations.takeError());
+      return;
+    }
+
+    llvm::SmallVector<AtomicChange, 1> Changes;
+    if (!Transformations->empty()) {
+      auto C = convertToAtomicChanges(*Transformations, Result);
+      if (C) {
+        Changes = std::move(*C);
+      } else {
+        Consumer(C.takeError());
+        return;
+      }
+    }
+
+    auto Metadata = Rule.Metadata[I]->eval(Result);
+    if (!Metadata) {
+      Consumer(Metadata.takeError());
+      return;
+    }
+
+    Consumer(Transformer::Result<T>{
+        llvm::MutableArrayRef<AtomicChange>(Changes), std::move(*Metadata)});
+  }
+
+  std::vector<ast_matchers::internal::DynTypedMatcher>
+  buildMatchers() const final {
+    return transformer::detail::buildMatchers(Rule);
+  }
 };
+} // namespace detail
+
+template <typename MetadataT>
+Transformer::Transformer(
+    transformer::RewriteRuleWith<MetadataT> Rule,
+    std::function<void(llvm::Expected<Transformer::Result<
+                           typename detail::type_identity<MetadataT>::type>>)>
+        Consumer)
+    : Impl(std::make_unique<detail::WithMetadataImpl<MetadataT>>(
+          std::move(Rule), std::move(Consumer))) {}
+
 } // namespace tooling
 } // namespace clang
 

diff  --git a/clang/lib/Tooling/Transformer/RewriteRule.cpp b/clang/lib/Tooling/Transformer/RewriteRule.cpp
index 93bd7e91dba7c..3e76489782f34 100644
--- a/clang/lib/Tooling/Transformer/RewriteRule.cpp
+++ b/clang/lib/Tooling/Transformer/RewriteRule.cpp
@@ -166,10 +166,26 @@ ASTEdit transformer::addInclude(RangeSelector Target, StringRef Header,
   return E;
 }
 
-RewriteRule transformer::makeRule(DynTypedMatcher M, EditGenerator Edits,
-                                  TextGenerator Explanation) {
-  return RewriteRule{{RewriteRule::Case{std::move(M), std::move(Edits),
-                                        std::move(Explanation)}}};
+EditGenerator
+transformer::detail::makeEditGenerator(llvm::SmallVector<ASTEdit, 1> Edits) {
+  return editList(std::move(Edits));
+}
+
+EditGenerator transformer::detail::makeEditGenerator(ASTEdit Edit) {
+  return edit(std::move(Edit));
+}
+
+RewriteRule transformer::detail::makeRule(DynTypedMatcher M,
+                                          EditGenerator Edits) {
+  RewriteRule R;
+  R.Cases = {{std::move(M), std::move(Edits)}};
+  return R;
+}
+
+RewriteRule transformer::makeRule(ast_matchers::internal::DynTypedMatcher M,
+                                  std::initializer_list<ASTEdit> Edits) {
+  return detail::makeRule(std::move(M),
+                          detail::makeEditGenerator(std::move(Edits)));
 }
 
 namespace {
@@ -247,9 +263,8 @@ class ApplyRuleCallback : public MatchFinder::MatchCallback {
   void run(const MatchFinder::MatchResult &Result) override {
     if (!Edits)
       return;
-    transformer::RewriteRule::Case Case =
-        transformer::detail::findSelectedCase(Result, Rule);
-    auto Transformations = Case.Edits(Result);
+    size_t I = transformer::detail::findSelectedCase(Result, Rule);
+    auto Transformations = Rule.Cases[I].Edits(Result);
     if (!Transformations) {
       Edits = Transformations.takeError();
       return;
@@ -325,7 +340,7 @@ EditGenerator transformer::rewriteDescendants(std::string NodeId,
   };
 }
 
-void transformer::addInclude(RewriteRule &Rule, StringRef Header,
+void transformer::addInclude(RewriteRuleBase &Rule, StringRef Header,
                              IncludeFormat Format) {
   for (auto &Case : Rule.Cases)
     Case.Edits = flatten(std::move(Case.Edits), addInclude(Header, Format));
@@ -366,7 +381,9 @@ static std::vector<DynTypedMatcher> taggedMatchers(
 // Simply gathers the contents of the various rules into a single rule. The
 // actual work to combine these into an ordered choice is deferred to matcher
 // registration.
-RewriteRule transformer::applyFirst(ArrayRef<RewriteRule> Rules) {
+template <>
+RewriteRuleWith<void>
+transformer::applyFirst(ArrayRef<RewriteRuleWith<void>> Rules) {
   RewriteRule R;
   for (auto &Rule : Rules)
     R.Cases.append(Rule.Cases.begin(), Rule.Cases.end());
@@ -374,12 +391,13 @@ RewriteRule transformer::applyFirst(ArrayRef<RewriteRule> Rules) {
 }
 
 std::vector<DynTypedMatcher>
-transformer::detail::buildMatchers(const RewriteRule &Rule) {
+transformer::detail::buildMatchers(const RewriteRuleBase &Rule) {
   // Map the cases into buckets of matchers -- one for each "root" AST kind,
   // which guarantees that they can be combined in a single anyOf matcher. Each
   // case is paired with an identifying number that is converted to a string id
   // in `taggedMatchers`.
-  std::map<ASTNodeKind, SmallVector<std::pair<size_t, RewriteRule::Case>, 1>>
+  std::map<ASTNodeKind,
+           SmallVector<std::pair<size_t, RewriteRuleBase::Case>, 1>>
       Buckets;
   const SmallVectorImpl<RewriteRule::Case> &Cases = Rule.Cases;
   for (int I = 0, N = Cases.size(); I < N; ++I) {
@@ -405,7 +423,7 @@ transformer::detail::buildMatchers(const RewriteRule &Rule) {
   return Matchers;
 }
 
-DynTypedMatcher transformer::detail::buildMatcher(const RewriteRule &Rule) {
+DynTypedMatcher transformer::detail::buildMatcher(const RewriteRuleBase &Rule) {
   std::vector<DynTypedMatcher> Ms = buildMatchers(Rule);
   assert(Ms.size() == 1 && "Cases must have compatible matchers.");
   return Ms[0];
@@ -428,19 +446,16 @@ SourceLocation transformer::detail::getRuleMatchLoc(const MatchResult &Result) {
 
 // Finds the case that was "selected" -- that is, whose matcher triggered the
 // `MatchResult`.
-const RewriteRule::Case &
-transformer::detail::findSelectedCase(const MatchResult &Result,
-                                  const RewriteRule &Rule) {
+size_t transformer::detail::findSelectedCase(const MatchResult &Result,
+                                             const RewriteRuleBase &Rule) {
   if (Rule.Cases.size() == 1)
-    return Rule.Cases[0];
+    return 0;
 
   auto &NodesMap = Result.Nodes.getMap();
   for (size_t i = 0, N = Rule.Cases.size(); i < N; ++i) {
     std::string Tag = ("Tag" + Twine(i)).str();
     if (NodesMap.find(Tag) != NodesMap.end())
-      return Rule.Cases[i];
+      return i;
   }
   llvm_unreachable("No tag found for this rule.");
 }
-
-const llvm::StringRef RewriteRule::RootID = ::clang::transformer::RootID;

diff  --git a/clang/lib/Tooling/Transformer/Transformer.cpp b/clang/lib/Tooling/Transformer/Transformer.cpp
index 1219afc551868..1cf824d2cbe49 100644
--- a/clang/lib/Tooling/Transformer/Transformer.cpp
+++ b/clang/lib/Tooling/Transformer/Transformer.cpp
@@ -16,35 +16,29 @@
 #include <utility>
 #include <vector>
 
-using namespace clang;
-using namespace tooling;
+namespace clang {
+namespace tooling {
 
-using ast_matchers::MatchFinder;
+using ::clang::ast_matchers::MatchFinder;
 
-void Transformer::registerMatchers(MatchFinder *MatchFinder) {
-  for (auto &Matcher : transformer::detail::buildMatchers(Rule))
-    MatchFinder->addDynamicMatcher(Matcher, this);
-}
+namespace detail {
 
-void Transformer::run(const MatchFinder::MatchResult &Result) {
+void TransformerImpl::onMatch(
+    const ast_matchers::MatchFinder::MatchResult &Result) {
   if (Result.Context->getDiagnostics().hasErrorOccurred())
     return;
 
-  transformer::RewriteRule::Case Case =
-      transformer::detail::findSelectedCase(Result, Rule);
-  auto Transformations = Case.Edits(Result);
-  if (!Transformations) {
-    Consumer(Transformations.takeError());
-    return;
-  }
-
-  if (Transformations->empty())
-    return;
+  onMatchImpl(Result);
+}
 
+llvm::Expected<llvm::SmallVector<AtomicChange, 1>>
+TransformerImpl::convertToAtomicChanges(
+    const llvm::SmallVectorImpl<transformer::Edit> &Edits,
+    const MatchFinder::MatchResult &Result) {
   // Group the transformations, by file, into AtomicChanges, each anchored by
   // the location of the first change in that file.
   std::map<FileID, AtomicChange> ChangesByFileID;
-  for (const auto &T : *Transformations) {
+  for (const auto &T : Edits) {
     auto ID = Result.SourceManager->getFileID(T.Range.getBegin());
     auto Iter = ChangesByFileID
                     .emplace(ID, AtomicChange(*Result.SourceManager,
@@ -55,8 +49,7 @@ void Transformer::run(const MatchFinder::MatchResult &Result) {
     case transformer::EditKind::Range:
       if (auto Err =
               AC.replace(*Result.SourceManager, T.Range, T.Replacement)) {
-        Consumer(std::move(Err));
-        return;
+        return std::move(Err);
       }
       break;
     case transformer::EditKind::AddInclude:
@@ -69,5 +62,43 @@ void Transformer::run(const MatchFinder::MatchResult &Result) {
   Changes.reserve(ChangesByFileID.size());
   for (auto &IDChangePair : ChangesByFileID)
     Changes.push_back(std::move(IDChangePair.second));
-  Consumer(llvm::MutableArrayRef<AtomicChange>(Changes));
+
+  return Changes;
+}
+
+void NoMetadataImpl::onMatchImpl(const MatchFinder::MatchResult &Result) {
+  size_t I = transformer::detail::findSelectedCase(Result, Rule);
+  auto Transformations = Rule.Cases[I].Edits(Result);
+  if (!Transformations) {
+    Consumer(Transformations.takeError());
+    return;
+  }
+
+  if (Transformations->empty())
+    return;
+
+  auto Changes = convertToAtomicChanges(*Transformations, Result);
+  if (!Changes) {
+    Consumer(Changes.takeError());
+    return;
+  }
+
+  Consumer(llvm::MutableArrayRef<AtomicChange>(*Changes));
 }
+
+} // namespace detail
+
+void Transformer::registerMatchers(MatchFinder *MatchFinder) {
+  for (auto &Matcher : Impl->buildMatchers())
+    MatchFinder->addDynamicMatcher(Matcher, this);
+}
+
+void Transformer::run(const MatchFinder::MatchResult &Result) {
+  if (Result.Context->getDiagnostics().hasErrorOccurred())
+    return;
+
+  Impl->onMatch(Result);
+}
+
+} // namespace tooling
+} // namespace clang

diff  --git a/clang/unittests/Tooling/TransformerTest.cpp b/clang/unittests/Tooling/TransformerTest.cpp
index 4ab39843de935..9ca1daa0e1e74 100644
--- a/clang/unittests/Tooling/TransformerTest.cpp
+++ b/clang/unittests/Tooling/TransformerTest.cpp
@@ -31,9 +31,11 @@ using ::clang::transformer::makeRule;
 using ::clang::transformer::member;
 using ::clang::transformer::name;
 using ::clang::transformer::node;
+using ::clang::transformer::noEdits;
 using ::clang::transformer::remove;
 using ::clang::transformer::rewriteDescendants;
 using ::clang::transformer::RewriteRule;
+using ::clang::transformer::RewriteRuleWith;
 using ::clang::transformer::statement;
 using ::testing::ElementsAre;
 using ::testing::IsEmpty;
@@ -129,7 +131,7 @@ class ClangRefactoringTestBase : public testing::Test {
         Changes.insert(Changes.end(), std::make_move_iterator(C->begin()),
                        std::make_move_iterator(C->end()));
       } else {
-        // FIXME: stash this error rather then printing.
+        // FIXME: stash this error rather than printing.
         llvm::errs() << "Error generating changes: "
                      << llvm::toString(C.takeError()) << "\n";
         ++ErrorCount;
@@ -137,27 +139,58 @@ class ClangRefactoringTestBase : public testing::Test {
     };
   }
 
-  template <typename R>
-  void testRule(R Rule, StringRef Input, StringRef Expected) {
+  auto consumerWithStringMetadata() {
+    return [this](Expected<Transformer::Result<std::string>> C) {
+      if (C) {
+        Changes.insert(Changes.end(),
+                       std::make_move_iterator(C->Changes.begin()),
+                       std::make_move_iterator(C->Changes.end()));
+        StringMetadata.push_back(std::move(C->Metadata));
+      } else {
+        // FIXME: stash this error rather than printing.
+        llvm::errs() << "Error generating changes: "
+                     << llvm::toString(C.takeError()) << "\n";
+        ++ErrorCount;
+      }
+    };
+  }
+
+  void testRule(RewriteRule Rule, StringRef Input, StringRef Expected) {
     Transformers.push_back(
         std::make_unique<Transformer>(std::move(Rule), consumer()));
     Transformers.back()->registerMatchers(&MatchFinder);
     compareSnippets(Expected, rewrite(Input));
   }
 
-  template <typename R> void testRuleFailure(R Rule, StringRef Input) {
+  void testRule(RewriteRuleWith<std::string> Rule, StringRef Input,
+                StringRef Expected) {
+    Transformers.push_back(std::make_unique<Transformer>(
+        std::move(Rule), consumerWithStringMetadata()));
+    Transformers.back()->registerMatchers(&MatchFinder);
+    compareSnippets(Expected, rewrite(Input));
+  }
+
+  void testRuleFailure(RewriteRule Rule, StringRef Input) {
     Transformers.push_back(
         std::make_unique<Transformer>(std::move(Rule), consumer()));
     Transformers.back()->registerMatchers(&MatchFinder);
     ASSERT_FALSE(rewrite(Input)) << "Expected failure to rewrite code";
   }
 
+  void testRuleFailure(RewriteRuleWith<std::string> Rule, StringRef Input) {
+    Transformers.push_back(std::make_unique<Transformer>(
+        std::move(Rule), consumerWithStringMetadata()));
+    Transformers.back()->registerMatchers(&MatchFinder);
+    ASSERT_FALSE(rewrite(Input)) << "Expected failure to rewrite code";
+  }
+
   // Transformers are referenced by MatchFinder.
   std::vector<std::unique_ptr<Transformer>> Transformers;
   clang::ast_matchers::MatchFinder MatchFinder;
   // Records whether any errors occurred in individual changes.
   int ErrorCount = 0;
   AtomicChanges Changes;
+  std::vector<std::string> StringMetadata;
 
 private:
   FileContentMappings FileContents = {{"header.h", ""}};
@@ -169,7 +202,7 @@ class TransformerTest : public ClangRefactoringTestBase {
 };
 
 // Given string s, change strlen($s.c_str()) to REPLACED.
-static RewriteRule ruleStrlenSize() {
+static RewriteRuleWith<std::string> ruleStrlenSize() {
   StringRef StringExpr = "strexpr";
   auto StringType = namedDecl(hasAnyName("::basic_string", "::string"));
   auto R = makeRule(
@@ -886,12 +919,12 @@ TEST_F(TransformerTest, FlattenWithMixedArgs) {
 
 TEST_F(TransformerTest, OrderedRuleUnrelated) {
   StringRef Flag = "flag";
-  RewriteRule FlagRule = makeRule(
+  RewriteRuleWith<std::string> FlagRule = makeRule(
       cxxMemberCallExpr(on(expr(hasType(cxxRecordDecl(
                                     hasName("proto::ProtoCommandLineFlag"))))
                                .bind(Flag)),
                         unless(callee(cxxMethodDecl(hasName("GetProto"))))),
-      changeTo(node(std::string(Flag)), cat("PROTO")));
+      changeTo(node(std::string(Flag)), cat("PROTO")), cat(""));
 
   std::string Input = R"cc(
     proto::ProtoCommandLineFlag flag;
@@ -1657,8 +1690,8 @@ TEST_F(TransformerTest, MultiFileEdit) {
       makeRule(callExpr(callee(functionDecl(hasName("Func"))),
                         forEachArgumentWithParam(expr().bind("arg"),
                                                  parmVarDecl().bind("param"))),
-               editList({changeTo(node("arg"), cat("ARG")),
-                         changeTo(node("param"), cat("PARAM"))})),
+               {changeTo(node("arg"), cat("ARG")),
+                changeTo(node("param"), cat("PARAM"))}),
       [&](Expected<MutableArrayRef<AtomicChange>> Changes) {
         if (Changes)
           ChangeSets.push_back(AtomicChanges(Changes->begin(), Changes->end()));
@@ -1682,4 +1715,39 @@ TEST_F(TransformerTest, MultiFileEdit) {
                    "./input.h"))));
 }
 
+TEST_F(TransformerTest, GeneratesMetadata) {
+  std::string Input = R"cc(int target = 0;)cc";
+  std::string Expected = R"cc(REPLACE)cc";
+  RewriteRuleWith<std::string> Rule = makeRule(
+      varDecl(hasName("target")), changeTo(cat("REPLACE")), cat("METADATA"));
+  testRule(std::move(Rule), Input, Expected);
+  EXPECT_EQ(ErrorCount, 0);
+  EXPECT_THAT(StringMetadata, UnorderedElementsAre("METADATA"));
+}
+
+TEST_F(TransformerTest, GeneratesMetadataWithNoEdits) {
+  std::string Input = R"cc(int target = 0;)cc";
+  RewriteRuleWith<std::string> Rule = makeRule(
+      varDecl(hasName("target")).bind("var"), noEdits(), cat("METADATA"));
+  testRule(std::move(Rule), Input, Input);
+  EXPECT_EQ(ErrorCount, 0);
+  EXPECT_THAT(StringMetadata, UnorderedElementsAre("METADATA"));
+}
+
+TEST_F(TransformerTest, PropagateMetadataErrors) {
+  class AlwaysFail : public transformer::MatchComputation<std::string> {
+    llvm::Error eval(const ast_matchers::MatchFinder::MatchResult &,
+                     std::string *) const override {
+      return llvm::createStringError(llvm::errc::invalid_argument, "ERROR");
+    }
+    std::string toString() const override { return "AlwaysFail"; }
+  };
+  std::string Input = R"cc(int target = 0;)cc";
+  RewriteRuleWith<std::string> Rule = makeRule<std::string>(
+      varDecl(hasName("target")).bind("var"), changeTo(cat("REPLACE")),
+      std::make_shared<AlwaysFail>());
+  testRuleFailure(std::move(Rule), Input);
+  EXPECT_EQ(ErrorCount, 1);
+}
+
 } // namespace


        


More information about the cfe-commits mailing list