ymandel updated this revision to Diff 193584.
ymandel added a subscriber: ABataev.
ymandel added a comment.

Sever dependency of clangToolingRefactor on clangTooling via FixIt.

Transformer used FixIt, which causes a problematic dependency.  This diff copies
the (minimal) code from FixIt to Transformer.cpp to break the dependency. For
the future, though, we should consider whether the FixIt library should live
somewhere accessible to components in Refactoring (perhaps Core?).


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D59376/new/

https://reviews.llvm.org/D59376

Files:
  clang/include/clang/Tooling/Refactoring/Transformer.h
  clang/lib/Tooling/Refactoring/CMakeLists.txt
  clang/lib/Tooling/Refactoring/Transformer.cpp
  clang/unittests/Tooling/CMakeLists.txt
  clang/unittests/Tooling/TransformerTest.cpp

Index: clang/unittests/Tooling/TransformerTest.cpp
===================================================================
--- /dev/null
+++ clang/unittests/Tooling/TransformerTest.cpp
@@ -0,0 +1,389 @@
+//===- unittest/Tooling/TransformerTest.cpp -------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Tooling/Refactoring/Transformer.h"
+
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Tooling/Tooling.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace tooling {
+namespace {
+using ast_matchers::anyOf;
+using ast_matchers::argumentCountIs;
+using ast_matchers::callee;
+using ast_matchers::callExpr;
+using ast_matchers::cxxMemberCallExpr;
+using ast_matchers::cxxMethodDecl;
+using ast_matchers::cxxRecordDecl;
+using ast_matchers::declRefExpr;
+using ast_matchers::expr;
+using ast_matchers::functionDecl;
+using ast_matchers::hasAnyName;
+using ast_matchers::hasArgument;
+using ast_matchers::hasDeclaration;
+using ast_matchers::hasElse;
+using ast_matchers::hasName;
+using ast_matchers::hasType;
+using ast_matchers::ifStmt;
+using ast_matchers::member;
+using ast_matchers::memberExpr;
+using ast_matchers::namedDecl;
+using ast_matchers::on;
+using ast_matchers::pointsTo;
+using ast_matchers::to;
+using ast_matchers::unless;
+
+using llvm::StringRef;
+
+constexpr char KHeaderContents[] = R"cc(
+  struct string {
+    string(const char*);
+    char* c_str();
+    int size();
+  };
+  int strlen(const char*);
+
+  namespace proto {
+  struct PCFProto {
+    int foo();
+  };
+  struct ProtoCommandLineFlag : PCFProto {
+    PCFProto& GetProto();
+  };
+  }  // namespace proto
+)cc";
+
+static ast_matchers::internal::Matcher<clang::QualType>
+isOrPointsTo(const clang::ast_matchers::DeclarationMatcher &TypeMatcher) {
+  return anyOf(hasDeclaration(TypeMatcher), pointsTo(TypeMatcher));
+}
+
+static std::string format(StringRef Code) {
+  const std::vector<Range> Ranges(1, Range(0, Code.size()));
+  auto Style = format::getLLVMStyle();
+  const auto Replacements = format::reformat(Style, Code, Ranges);
+  auto Formatted = applyAllReplacements(Code, Replacements);
+  if (!Formatted) {
+    ADD_FAILURE() << "Could not format code: "
+                  << llvm::toString(Formatted.takeError());
+    return std::string();
+  }
+  return *Formatted;
+}
+
+static void compareSnippets(StringRef Expected,
+                     const llvm::Optional<std::string> &MaybeActual) {
+  ASSERT_TRUE(MaybeActual) << "Rewrite failed. Expecting: " << Expected;
+  auto Actual = *MaybeActual;
+  std::string HL = "#include \"header.h\"\n";
+  auto I = Actual.find(HL);
+  if (I != std::string::npos)
+    Actual.erase(I, HL.size());
+  EXPECT_EQ(format(Expected), format(Actual));
+}
+
+// FIXME: consider separating this class into its own file(s).
+class ClangRefactoringTestBase : public testing::Test {
+protected:
+  void appendToHeader(StringRef S) { FileContents[0].second += S; }
+
+  void addFile(StringRef Filename, StringRef Content) {
+    FileContents.emplace_back(Filename, Content);
+  }
+
+  llvm::Optional<std::string> rewrite(StringRef Input) {
+    std::string Code = ("#include \"header.h\"\n" + Input).str();
+    auto Factory = newFrontendActionFactory(&MatchFinder);
+    if (!runToolOnCodeWithArgs(
+            Factory->create(), Code, std::vector<std::string>(), "input.cc",
+            "clang-tool", std::make_shared<PCHContainerOperations>(),
+            FileContents)) {
+      return None;
+    }
+    auto ChangedCodeOrErr =
+        applyAtomicChanges("input.cc", Code, Changes, ApplyChangesSpec());
+    if (auto Err = ChangedCodeOrErr.takeError()) {
+      llvm::errs() << "Change failed: " << llvm::toString(std::move(Err))
+                   << "\n";
+      return None;
+    }
+    return *ChangedCodeOrErr;
+  }
+
+  void testRule(RewriteRule Rule, StringRef Input, StringRef Expected) {
+    Transformer T(std::move(Rule),
+                  [this](const AtomicChange &C) { Changes.push_back(C); });
+    T.registerMatchers(&MatchFinder);
+    compareSnippets(Expected, rewrite(Input));
+  }
+
+  clang::ast_matchers::MatchFinder MatchFinder;
+  AtomicChanges Changes;
+
+private:
+  FileContentMappings FileContents = {{"header.h", ""}};
+};
+
+class TransformerTest : public ClangRefactoringTestBase {
+protected:
+  TransformerTest() { appendToHeader(KHeaderContents); }
+};
+
+// Given string s, change strlen($s.c_str()) to $s.size().
+static RewriteRule ruleStrlenSize() {
+  StringRef StringExpr = "strexpr";
+  auto StringType = namedDecl(hasAnyName("::basic_string", "::string"));
+  return buildRule(
+             callExpr(
+                 callee(functionDecl(hasName("strlen"))),
+                 hasArgument(0, cxxMemberCallExpr(
+                                    on(expr(hasType(isOrPointsTo(StringType)))
+                                           .bind(StringExpr)),
+                                    callee(cxxMethodDecl(hasName("c_str")))))))
+      // Specify the intended type explicitly, because the matcher "type" of
+      // `callExpr()` is `Stmt`, not `Expr`.
+      .as<clang::Expr>()
+      .replaceWith("REPLACED")
+      .because("Use size() method directly on string.");
+}
+
+TEST_F(TransformerTest, StrlenSize) {
+  std::string Input = "int f(string s) { return strlen(s.c_str()); }";
+  std::string Expected = "int f(string s) { return REPLACED; }";
+  testRule(ruleStrlenSize(), Input, Expected);
+}
+
+// Tests that no change is applied when a match is not expected.
+TEST_F(TransformerTest, NoMatch) {
+  std::string Input = "int f(string s) { return s.size(); }";
+  testRule(ruleStrlenSize(), Input, Input);
+}
+
+// Tests that expressions in macro arguments are rewritten (when applicable).
+TEST_F(TransformerTest, StrlenSizeMacro) {
+  std::string Input = R"cc(
+#define ID(e) e
+    int f(string s) { return ID(strlen(s.c_str())); })cc";
+  std::string Expected = R"cc(
+#define ID(e) e
+    int f(string s) { return ID(REPLACED); })cc";
+  testRule(ruleStrlenSize(), Input, Expected);
+}
+
+// Tests replacing an expression.
+TEST_F(TransformerTest, Flag) {
+  StringRef Flag = "flag";
+  RewriteRule Rule =
+      buildRule(
+          cxxMemberCallExpr(on(expr(hasType(cxxRecordDecl(hasName(
+                                        "proto::ProtoCommandLineFlag"))))
+                                   .bind(Flag)),
+                            unless(callee(cxxMethodDecl(hasName("GetProto"))))))
+          .change<clang::Expr>(Flag)
+          .replaceWith("EXPR")
+          .because("Use GetProto() to access proto fields.");
+
+  std::string Input = R"cc(
+    proto::ProtoCommandLineFlag flag;
+    int x = flag.foo();
+    int y = flag.GetProto().foo();
+  )cc";
+  std::string Expected = R"cc(
+    proto::ProtoCommandLineFlag flag;
+    int x = EXPR.foo();
+    int y = flag.GetProto().foo();
+  )cc";
+
+  testRule(std::move(Rule), Input, Expected);
+}
+
+TEST_F(TransformerTest, NodePartNameNamedDecl) {
+  StringRef Fun = "fun";
+  RewriteRule Rule = buildRule(functionDecl(hasName("bad")).bind(Fun))
+                         .change<clang::FunctionDecl>(Fun, NodePart::Name)
+                         .replaceWith("good");
+
+  std::string Input = R"cc(
+    int bad(int x);
+    int bad(int x) { return x * x; }
+  )cc";
+  std::string Expected = R"cc(
+    int good(int x);
+    int good(int x) { return x * x; }
+  )cc";
+
+  testRule(Rule, Input, Expected);
+}
+
+TEST_F(TransformerTest, NodePartNameDeclRef) {
+  std::string Input = R"cc(
+    template <typename T>
+    T bad(T x) {
+      return x;
+    }
+    int neutral(int x) { return bad<int>(x) * x; }
+  )cc";
+  std::string Expected = R"cc(
+    template <typename T>
+    T bad(T x) {
+      return x;
+    }
+    int neutral(int x) { return good<int>(x) * x; }
+  )cc";
+
+  StringRef Ref = "ref";
+  testRule(buildRule(declRefExpr(to(functionDecl(hasName("bad")))).bind(Ref))
+               .change<clang::Expr>(Ref, NodePart::Name)
+               .replaceWith("good"),
+           Input, Expected);
+}
+
+TEST_F(TransformerTest, NodePartNameDeclRefFailure) {
+  std::string Input = R"cc(
+    struct Y {
+      int operator*();
+    };
+    int neutral(int x) {
+      Y y;
+      int (Y::*ptr)() = &Y::operator*;
+      return *y + x;
+    }
+  )cc";
+
+  StringRef Ref = "ref";
+  testRule(buildRule(declRefExpr(to(functionDecl())).bind(Ref))
+               .change<clang::Expr>(Ref, NodePart::Name)
+               .replaceWith("good"),
+           Input, Input);
+}
+
+TEST_F(TransformerTest, NodePartMember) {
+  StringRef E = "expr";
+  RewriteRule Rule = buildRule(memberExpr(member(hasName("bad"))).bind(E))
+                         .change<clang::Expr>(E, NodePart::Member)
+                         .replaceWith("good");
+
+  std::string Input = R"cc(
+    struct S {
+      int bad;
+    };
+    int g() {
+      S s;
+      return s.bad;
+    }
+  )cc";
+  std::string Expected = R"cc(
+    struct S {
+      int bad;
+    };
+    int g() {
+      S s;
+      return s.good;
+    }
+  )cc";
+
+  testRule(Rule, Input, Expected);
+}
+
+TEST_F(TransformerTest, NodePartMemberQualified) {
+  std::string Input = R"cc(
+    struct S {
+      int bad;
+      int good;
+    };
+    struct T : public S {
+      int bad;
+    };
+    int g() {
+      T t;
+      return t.S::bad;
+    }
+  )cc";
+  std::string Expected = R"cc(
+    struct S {
+      int bad;
+      int good;
+    };
+    struct T : public S {
+      int bad;
+    };
+    int g() {
+      T t;
+      return t.S::good;
+    }
+  )cc";
+
+  StringRef E = "expr";
+  testRule(buildRule(memberExpr().bind(E))
+               .change<clang::Expr>(E, NodePart::Member)
+               .replaceWith("good"),
+           Input, Expected);
+}
+
+TEST_F(TransformerTest, NodePartMemberMultiToken) {
+  std::string Input = R"cc(
+    struct Y {
+      int operator*();
+      int good();
+      template <typename T> void foo(T t);
+    };
+    int neutral(int x) {
+      Y y;
+      y.template foo<int>(3);
+      return y.operator *();
+    }
+  )cc";
+  std::string Expected = R"cc(
+    struct Y {
+      int operator*();
+      int good();
+      template <typename T> void foo(T t);
+    };
+    int neutral(int x) {
+      Y y;
+      y.template good<int>(3);
+      return y.good();
+    }
+  )cc";
+
+  StringRef MemExpr = "member";
+  testRule(buildRule(memberExpr().bind(MemExpr))
+               .change<clang::Expr>(MemExpr, NodePart::Member)
+               .replaceWith("good"),
+           Input, Expected);
+}
+
+//
+// Negative tests (where we expect no transformation to occur).
+//
+
+TEST_F(TransformerTest, NoTransformationInMacro) {
+  std::string Input = R"cc(
+#define MACRO(str) strlen((str).c_str())
+    int f(string s) { return MACRO(s); })cc";
+  testRule(ruleStrlenSize(), Input, Input);
+}
+
+// This test handles the corner case where a macro called within another macro
+// expands to matching code, but the matched code is an argument to the nested
+// macro.  A simple check of isMacroArgExpansion() vs. isMacroBodyExpansion()
+// will get this wrong, and transform the code. This test verifies that no such
+// transformation occurs.
+TEST_F(TransformerTest, NoTransformationInNestedMacro) {
+  std::string Input = R"cc(
+#define NESTED(e) e
+#define MACRO(str) NESTED(strlen((str).c_str()))
+    int f(string s) { return MACRO(s); })cc";
+  testRule(ruleStrlenSize(), Input, Input);
+}
+} // namespace
+} // namespace tooling
+} // namespace clang
Index: clang/unittests/Tooling/CMakeLists.txt
===================================================================
--- clang/unittests/Tooling/CMakeLists.txt
+++ clang/unittests/Tooling/CMakeLists.txt
@@ -50,6 +50,7 @@
   ReplacementsYamlTest.cpp
   RewriterTest.cpp
   ToolingTest.cpp
+  TransformerTest.cpp
   )
 
 target_link_libraries(ToolingTests
Index: clang/lib/Tooling/Refactoring/Transformer.cpp
===================================================================
--- /dev/null
+++ clang/lib/Tooling/Refactoring/Transformer.cpp
@@ -0,0 +1,221 @@
+//===--- Transformer.cpp - Transformer library implementation ---*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Tooling/Refactoring/Transformer.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Rewrite/Core/Rewriter.h"
+#include "clang/Tooling/Refactoring/AtomicChange.h"
+#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Errc.h"
+#include "llvm/Support/Error.h"
+#include <deque>
+#include <string>
+#include <utility>
+#include <vector>
+
+using namespace clang;
+using namespace tooling;
+
+using ast_matchers::MatchFinder;
+using ast_type_traits::ASTNodeKind;
+using ast_type_traits::DynTypedNode;
+using llvm::Error;
+using llvm::Expected;
+using llvm::Optional;
+using llvm::StringError;
+using llvm::StringRef;
+using llvm::Twine;
+
+using MatchResult = MatchFinder::MatchResult;
+
+// Did the text at this location originate in a macro definition (aka. body)?
+// For example,
+//
+//   #define NESTED(x) x
+//   #define MACRO(y) { int y  = NESTED(3); }
+//   if (true) MACRO(foo)
+//
+// The if statement expands to
+//
+//   if (true) { int foo = 3; }
+//                   ^     ^
+//                   Loc1  Loc2
+//
+// For SourceManager SM, SM.isMacroArgExpansion(Loc1) and
+// SM.isMacroArgExpansion(Loc2) are both true, but isOriginMacroBody(sm, Loc1)
+// is false, because "foo" originated in the source file (as an argument to a
+// macro), whereas isOriginMacroBody(SM, Loc2) is true, because "3" originated
+// in the definition of MACRO.
+static bool isOriginMacroBody(const clang::SourceManager &SM,
+                              clang::SourceLocation Loc) {
+  while (Loc.isMacroID()) {
+    if (SM.isMacroBodyExpansion(Loc))
+      return true;
+    // Otherwise, it must be in an argument, so we continue searching up the
+    // invocation stack. getImmediateMacroCallerLoc() gives the location of the
+    // argument text, inside the call text.
+    Loc = SM.getImmediateMacroCallerLoc(Loc);
+  }
+  return false;
+}
+
+static llvm::Error invalidArgumentError(Twine Message) {
+  return llvm::make_error<StringError>(llvm::errc::invalid_argument, Message);
+}
+
+static llvm::Error typeError(StringRef Id, const ASTNodeKind &Kind,
+                             Twine Message) {
+  return invalidArgumentError(
+      Message + " (node id=" + Id + " kind=" + Kind.asStringRef() + ")");
+}
+
+static llvm::Error missingPropertyError(StringRef Id, Twine Description,
+                                        StringRef Property) {
+  return invalidArgumentError(Description + " requires property '" + Property +
+                              "' (node id=" + Id + ")");
+}
+
+static StringRef getText(CharSourceRange Range, const ASTContext &Context) {
+  return Lexer::getSourceText(Range, Context.getSourceManager(),
+                              Context.getLangOpts());
+}
+
+/// Returns the source range spanning the node, extended to include \p Next, if
+/// it immediately follows \p Node. Otherwise, returns the normal range of \p
+/// Node.  See comments on `getExtendedText()` for examples.
+static CharSourceRange getExtendedRange(const DynTypedNode &Node, tok::TokenKind Next,
+                                 ASTContext &Context) {
+  auto Range = CharSourceRange::getTokenRange(Node.getSourceRange());
+  Optional<Token> Tok = Lexer::findNextToken(
+      Range.getEnd(), Context.getSourceManager(), Context.getLangOpts());
+  if (!Tok || !Tok->is(Next))
+    return Range;
+  return CharSourceRange::getTokenRange(Range.getBegin(), Tok->getLocation());
+}
+
+static Expected<CharSourceRange>
+getTargetRange(StringRef Target, const DynTypedNode &Node, ASTNodeKind Kind,
+               NodePart TargetPart, ASTContext &Context) {
+  switch (TargetPart) {
+  case NodePart::Node: {
+    // For non-expression statements, associate any trailing semicolon with the
+    // statement text.  However, if the target was intended as an expression (as
+    // indicated by its kind) then we do not associate any trailing semicolon
+    // with it.  We only associate the exact expression text.
+    if (Node.get<Stmt>() != nullptr) {
+      auto ExprKind = ASTNodeKind::getFromNodeKind<clang::Expr>();
+      if (!ExprKind.isBaseOf(Kind))
+        return getExtendedRange(Node, tok::TokenKind::semi, Context);
+    }
+    return CharSourceRange::getTokenRange(Node.getSourceRange());
+  }
+  case NodePart::Member:
+    if (auto *M = Node.get<clang::MemberExpr>())
+      return CharSourceRange::getTokenRange(
+          M->getMemberNameInfo().getSourceRange());
+    return typeError(Target, Node.getNodeKind(),
+                     "NodePart::Member applied to non-MemberExpr");
+  case NodePart::Name:
+    if (const auto *D = Node.get<clang::NamedDecl>()) {
+      if (!D->getDeclName().isIdentifier())
+        return missingPropertyError(Target, "NodePart::Name", "identifier");
+      SourceLocation L = D->getLocation();
+      auto R = CharSourceRange::getTokenRange(L, L);
+      // Verify that the range covers exactly the name.
+      // FIXME: extend this code to support cases like `operator +` or
+      // `foo<int>` for which this range will be too short.  Doing so will
+      // require subcasing `NamedDecl`, because it doesn't provide virtual
+      // access to the \c DeclarationNameInfo.
+      if (getText(R, Context) != D->getName())
+        return CharSourceRange();
+      return R;
+    }
+    if (const auto *E = Node.get<clang::DeclRefExpr>()) {
+      if (!E->getNameInfo().getName().isIdentifier())
+        return missingPropertyError(Target, "NodePart::Name", "identifier");
+      SourceLocation L = E->getLocation();
+      return CharSourceRange::getTokenRange(L, L);
+    }
+    if (const auto *I = Node.get<clang::CXXCtorInitializer>()) {
+      if (!I->isMemberInitializer() && I->isWritten())
+        return missingPropertyError(Target, "NodePart::Name",
+                                    "explicit member initializer");
+      SourceLocation L = I->getMemberLocation();
+      return CharSourceRange::getTokenRange(L, L);
+    }
+    return typeError(
+        Target, Node.getNodeKind(),
+        "NodePart::Name applied to neither DeclRefExpr, NamedDecl nor "
+        "CXXCtorInitializer");
+  }
+  llvm_unreachable("Unexpected case in NodePart type.");
+}
+
+Expected<Transformation>
+tooling::applyRewriteRule(const RewriteRule &Rule,
+                          const ast_matchers::MatchFinder::MatchResult &Match) {
+  if (Match.Context->getDiagnostics().hasErrorOccurred())
+    return Transformation();
+
+  auto &NodesMap = Match.Nodes.getMap();
+  auto It = NodesMap.find(Rule.Target);
+  assert (It != NodesMap.end() && "Rule.Target must be bound in the match.");
+
+  Expected<CharSourceRange> TargetOrErr =
+      getTargetRange(Rule.Target, It->second, Rule.TargetKind, Rule.TargetPart,
+                     *Match.Context);
+  if (auto Err = TargetOrErr.takeError())
+    return std::move(Err);
+  auto &Target = *TargetOrErr;
+  if (Target.isInvalid() ||
+      isOriginMacroBody(*Match.SourceManager, Target.getBegin()))
+    return Transformation();
+
+  return Transformation{Target, Rule.Replacement(Match)};
+}
+
+constexpr llvm::StringLiteral RewriteRule::RootId;
+
+RewriteRuleBuilder RewriteRuleBuilder::replaceWith(TextGenerator T) {
+  Rule.Replacement = std::move(T);
+  return *this;
+}
+
+RewriteRuleBuilder RewriteRuleBuilder::because(TextGenerator T) {
+  Rule.Explanation = std::move(T);
+  return *this;
+}
+
+void Transformer::registerMatchers(MatchFinder *MatchFinder) {
+  MatchFinder->addDynamicMatcher(Rule.Matcher, this);
+}
+
+void Transformer::run(const MatchResult &Result) {
+  auto ChangeOrErr = applyRewriteRule(Rule, Result);
+  if (auto Err = ChangeOrErr.takeError()) {
+    llvm::errs() << "Rewrite failed: " << llvm::toString(std::move(Err))
+                 << "\n";
+    return;
+  }
+  auto &Change = *ChangeOrErr;
+  auto &Range = Change.Range;
+  if (Range.isInvalid()) {
+    // No rewrite applied (but no error encountered either).
+    return;
+  }
+  AtomicChange AC(*Result.SourceManager, Range.getBegin());
+  if (auto Err = AC.replace(*Result.SourceManager, Range, Change.Replacement))
+    AC.setError(llvm::toString(std::move(Err)));
+  Consumer(AC);
+}
Index: clang/lib/Tooling/Refactoring/CMakeLists.txt
===================================================================
--- clang/lib/Tooling/Refactoring/CMakeLists.txt
+++ clang/lib/Tooling/Refactoring/CMakeLists.txt
@@ -12,6 +12,7 @@
   Rename/USRFinder.cpp
   Rename/USRFindingAction.cpp
   Rename/USRLocFinder.cpp
+  Transformer.cpp
 
   LINK_LIBS
   clangAST
Index: clang/include/clang/Tooling/Refactoring/Transformer.h
===================================================================
--- /dev/null
+++ clang/include/clang/Tooling/Refactoring/Transformer.h
@@ -0,0 +1,210 @@
+//===--- Transformer.h - Clang source-rewriting library ---------*- C++ -*-===//
+//
+// 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
+///  Defines a library supporting the concise specification of clang-based
+///  source-to-source transformations.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLING_REFACTOR_TRANSFORMER_H_
+#define LLVM_CLANG_TOOLING_REFACTOR_TRANSFORMER_H_
+
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/ASTMatchers/ASTMatchersInternal.h"
+#include "clang/Tooling/Refactoring/AtomicChange.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/Error.h"
+#include <deque>
+#include <functional>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+namespace clang {
+namespace tooling {
+/// Determines the part of the AST node to replace.  We support this to work
+/// around the fact that the AST does not differentiate various syntactic
+/// elements into their own nodes, so users can specify them relative to a node,
+/// instead.
+enum class NodePart {
+  /// The node itself.
+  Node,
+  /// Given a \c MemberExpr, selects the member's token.
+  Member,
+  /// Given a \c NamedDecl or \c CxxCtorInitializer, selects that token of the
+  /// relevant name, not including qualifiers.
+  Name,
+};
+
+using TextGenerator =
+    std::function<std::string(const ast_matchers::MatchFinder::MatchResult &)>;
+
+/// Description of a source-code transformation.
+//
+// A *rewrite rule* describes a transformation of source code. It has the
+// following components:
+//
+// * Matcher: the pattern term, expressed as clang matchers (with Transformer
+//   extensions).
+//
+// * Target: the source code impacted by the rule. This identifies an AST node,
+//   or part thereof (\c TargetPart), whose source range indicates the extent of
+//   the replacement applied by the replacement term.  By default, the extent is
+//   the node matched by the pattern term (\c NodePart::Node). Target's are
+//   typed (\c TargetKind), which guides the determination of the node extent
+//   and might, in the future, statically constrain the set of eligible
+//   NodeParts for a given node.
+//
+// * Replacement: a function that produces a replacement string for the target,
+//   based on the match result.
+//
+// * Explanation: explanation of the rewrite.  This will be displayed to the
+//   user, where possible (for example, in clang-tidy fix descriptions).
+//
+// Rules have an additional, implicit, component: the parameters. These are
+// portions of the pattern which are left unspecified, yet named so that we can
+// reference them in the replacement term.  The structure of parameters can be
+// partially or even fully specified, in which case they serve just to identify
+// matched nodes for later reference rather than abstract over portions of the
+// AST.  However, in all cases, we refer to named portions of the pattern as
+// parameters.
+//
+// RewriteRule is constructed in a "fluent" style, by creating a builder and
+// chaining setters of individual components.
+// \code
+//   RewriteRule MyRule = buildRule(functionDecl(...)).replaceWith(...);
+// \endcode
+//
+// The \c Transformer class should then be used to apply the rewrite rule and
+// obtain the corresponding replacements.
+struct RewriteRule {
+  // `Matcher` describes the context of this rule. It should always be bound to
+  // at least `RootId`.  The builder class below takes care of this
+  // binding. Here, we bind it to a trivial Matcher to enable the default
+  // constructor, since DynTypedMatcher has no default constructor.
+  ast_matchers::internal::DynTypedMatcher Matcher = ast_matchers::stmt();
+  // The (bound) id of the node whose source will be replaced.  This id should
+  // never be the empty string.
+  std::string Target;
+  ast_type_traits::ASTNodeKind TargetKind;
+  NodePart TargetPart;
+  TextGenerator Replacement;
+  TextGenerator Explanation;
+
+  // Id used as the default target of each match. The node described by the
+  // matcher is guaranteed to be bound to this id, for all rewrite rules
+  // constructed with the builder class.
+  static constexpr llvm::StringLiteral RootId = "___root___";
+};
+
+/// A fluent builder class for \c RewriteRule.  See comments on \c RewriteRule.
+class RewriteRuleBuilder {
+  RewriteRule Rule;
+
+public:
+  RewriteRuleBuilder(ast_matchers::internal::DynTypedMatcher M) {
+    M.setAllowBind(true);
+    // `tryBind` is guaranteed to succeed, because `AllowBind` was set to true.
+    Rule.Matcher = *M.tryBind(RewriteRule::RootId);
+    Rule.Target = RewriteRule::RootId;
+    Rule.TargetKind = M.getSupportedKind();
+    Rule.TargetPart = NodePart::Node;
+  }
+
+  /// (Implicit) "build" operator to build a RewriteRule from this builder.
+  operator RewriteRule() && { return std::move(Rule); }
+
+  // Sets the target kind based on a clang AST node type.
+  template <typename T> RewriteRuleBuilder as();
+
+  template <typename T>
+  RewriteRuleBuilder change(llvm::StringRef Target,
+                            NodePart Part = NodePart::Node);
+
+  RewriteRuleBuilder replaceWith(TextGenerator Replacement);
+  RewriteRuleBuilder replaceWith(std::string Replacement) {
+    return replaceWith(text(std::move(Replacement)));
+  }
+
+  RewriteRuleBuilder because(TextGenerator Explanation);
+  RewriteRuleBuilder because(std::string Explanation) {
+    return because(text(std::move(Explanation)));
+  }
+
+private:
+  // Wraps a string as a TextGenerator.
+  static TextGenerator text(std::string M) {
+    return [M](const ast_matchers::MatchFinder::MatchResult &) { return M; };
+   }
+};
+
+/// Convenience factory functions for starting construction of a \c RewriteRule.
+inline RewriteRuleBuilder buildRule(ast_matchers::internal::DynTypedMatcher M) {
+  return RewriteRuleBuilder(std::move(M));
+}
+
+template <typename T> RewriteRuleBuilder RewriteRuleBuilder::as() {
+  Rule.TargetKind = ast_type_traits::ASTNodeKind::getFromNodeKind<T>();
+  return *this;
+}
+
+template <typename T>
+RewriteRuleBuilder RewriteRuleBuilder::change(llvm::StringRef TargetId,
+                                              NodePart Part) {
+  Rule.Target = TargetId;
+  Rule.TargetKind = ast_type_traits::ASTNodeKind::getFromNodeKind<T>();
+  Rule.TargetPart = Part;
+  return *this;
+}
+
+/// A source "transformation," represented by a character range in the source to
+/// be replaced and a corresponding replacement string.
+struct Transformation {
+  CharSourceRange Range;
+  std::string Replacement;
+};
+
+/// Attempts to apply a rule to a match.  Returns an empty transformation if the
+/// match is not eligible for rewriting (certain interactions with macros, for
+/// example).  Fails if any invariants are violated relating to bound nodes in
+/// the match.
+Expected<Transformation>
+applyRewriteRule(const RewriteRule &Rule,
+                 const ast_matchers::MatchFinder::MatchResult &Match);
+
+/// Handles the matcher and callback registration for a single rewrite rule, as
+/// defined by the arguments of the constructor.
+class Transformer : public ast_matchers::MatchFinder::MatchCallback {
+public:
+  using ChangeConsumer =
+      std::function<void(const clang::tooling::AtomicChange &Change)>;
+
+  /// \param Consumer Receives each successful rewrites as an \c AtomicChange.
+  Transformer(RewriteRule Rule, ChangeConsumer Consumer)
+      : Rule(std::move(Rule)), Consumer(std::move(Consumer)) {}
+
+  /// N.B. Passes `this` pointer to `MatchFinder`.  So, this object should not
+  /// be moved after this call.
+  void registerMatchers(ast_matchers::MatchFinder *MatchFinder);
+
+  /// Not called directly by users -- called by the framework, via base class
+  /// pointer.
+  void run(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+  RewriteRule Rule;
+  /// Receives each successful rewrites as an \c AtomicChange.
+  ChangeConsumer Consumer;
+};
+} // namespace tooling
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLING_REFACTOR_TRANSFORMER_H_
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to