ymandel updated this revision to Diff 199896.
ymandel marked 9 inline comments as done.
ymandel added a comment.

comment tweak


Repository:
  rG LLVM Github Monorepo

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

https://reviews.llvm.org/D61774

Files:
  clang/include/clang/Tooling/Refactoring/RangeSelector.h
  clang/lib/Tooling/Refactoring/CMakeLists.txt
  clang/lib/Tooling/Refactoring/RangeSelector.cpp
  clang/unittests/Tooling/CMakeLists.txt
  clang/unittests/Tooling/RangeSelectorTest.cpp

Index: clang/unittests/Tooling/RangeSelectorTest.cpp
===================================================================
--- /dev/null
+++ clang/unittests/Tooling/RangeSelectorTest.cpp
@@ -0,0 +1,466 @@
+//===- unittest/Tooling/RangeSelectorTest.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/RangeSelector.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Tooling/FixIt.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/Support/Error.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using namespace clang;
+using namespace tooling;
+using namespace ast_matchers;
+
+namespace {
+using ::testing::AllOf;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using MatchResult = MatchFinder::MatchResult;
+using ::llvm::Expected;
+using ::llvm::Optional;
+
+struct TestMatch {
+  // The AST unit from which `result` is built. We bundle it because it backs
+  // the result. Users are not expected to access it.
+  std::unique_ptr<ASTUnit> AstUnit;
+  // The result to use in the test. References `ast_unit`.
+  MatchResult Result;
+};
+
+template <typename M>
+llvm::Optional<TestMatch> matchAny(StringRef Code, M Matcher) {
+  auto AstUnit = buildASTFromCode(Code);
+  if (AstUnit == nullptr) {
+    ADD_FAILURE() << "AST construction failed";
+    return llvm::None;
+  }
+  ASTContext &Context = AstUnit->getASTContext();
+  if (Context.getDiagnostics().hasErrorOccurred()) {
+    ADD_FAILURE() << "Compilation error";
+    return llvm::None;
+  }
+  auto Matches = ast_matchers::match(Matcher, Context);
+  // We expect a single, exact match.
+  if (Matches.size() != 1) {
+    ADD_FAILURE() << "Wrong number of matches: " << Matches.size();
+    return llvm::None;
+  }
+  return TestMatch{std::move(AstUnit), MatchResult(Matches[0], &Context)};
+}
+
+template <typename M>
+void test(M Matcher, RangeSelector Selector, StringRef Code,
+          StringRef Selected) {
+  Optional<TestMatch> Match = matchAny(Code, Matcher);
+  ASSERT_TRUE(Match);
+  MatchResult &Result = Match->Result;
+  if (Expected<CharSourceRange> Range = Selector(Result))
+    EXPECT_EQ(fixit::internal::getText(*Range, *Result.Context), Selected);
+  else
+    ADD_FAILURE() << llvm::toString(Range.takeError());
+}
+
+// Verifies that \c Selector fails when evaluated on \c Code.
+template <typename M>
+void testError(M Matcher, const RangeSelector &Selector, StringRef Code,
+               ::testing::Matcher<std::string> ErrorMatcher) {
+  Optional<TestMatch> Match = matchAny(Code, Matcher);
+  ASSERT_TRUE(Match);
+  auto Range = Selector(Match->Result);
+  if (Range) {
+    ADD_FAILURE() << "Expected failure but succeeded";
+    return;
+  }
+  auto Err = llvm::handleErrors(Range.takeError(),
+                                [&ErrorMatcher](const llvm::StringError &Err) {
+                                  EXPECT_THAT(Err.getMessage(), ErrorMatcher);
+                                });
+  if (Err)
+    ADD_FAILURE() << "Unhandled error: " << llvm::toString(std::move(Err));
+}
+
+// Tests failures caused by references to unbound nodes. `UnboundID` is the ID
+// that will cause the failure.
+void testUnboundNodeError(const RangeSelector &S, llvm::StringRef UnboundID) {
+  // We need to bind the result to something, or the match will fail. Create an
+  // ID from UnboundID to ensure they are different.
+  std::string BoundID = (UnboundID + "_").str();
+  testError(varDecl().bind(BoundID), S, "static int x = 0;",
+            AllOf(HasSubstr(UnboundID), HasSubstr("not bound")));
+}
+
+// Tests failures caused by operations applied to nodes of the wrong type. For
+// convenience, binds a statement to "stmt", a (non-member) ctor-initializer to
+// "init", an expression to "expr" and a (nameless) declaration to "decl".
+void testTypeError(const RangeSelector &Selector, llvm::StringRef NodeID) {
+  StringRef Code = R"cc(
+      struct A {};
+      class F : public A {
+       public:
+        F(int) {}
+      };
+      void g() { F f(1); }
+    )cc";
+
+  auto Matcher =
+      compoundStmt(
+          hasDescendant(
+              cxxConstructExpr(
+                  hasDeclaration(
+                      decl(hasDescendant(cxxCtorInitializer(isBaseInitializer())
+                                             .bind("init")))
+                          .bind("decl")))
+                  .bind("expr")))
+          .bind("stmt");
+
+  testError(Matcher, Selector, Code,
+            AllOf(HasSubstr(NodeID), HasSubstr("mismatched type")));
+}
+
+TEST(RangeSelectorTest, UnboundNode) {
+  testUnboundNodeError(node("unbound"), "unbound");
+}
+
+TEST(RangeSelectorTest, RangeOp) {
+  StringRef Code = R"cc(
+    int f(int x, int y, int z) { return 3; }
+    int g() { return f(/* comment */ 3, 7 /* comment */, 9); }
+  )cc";
+  StringRef Arg0 = "a0";
+  StringRef Arg1 = "a1";
+  StringRef Call = "call";
+  auto Matcher = callExpr(hasArgument(0, expr().bind(Arg0)),
+                          hasArgument(1, expr().bind(Arg1)))
+                     .bind(Call);
+  // Node-id specific version:
+  test(Matcher, range(Arg0, Arg1), Code, "3, 7");
+  // General version:
+  test(Matcher, range(node(Arg0), node(Arg1)), Code, "3, 7");
+}
+
+TEST(RangeSelectorTest, NodeOpStatement) {
+  StringRef Code = "int f() { return 3; }";
+  StringRef ID = "id";
+  test(returnStmt().bind(ID), node(ID), Code, "return 3;");
+}
+
+TEST(RangeSelectorTest, NodeOpExpression) {
+  StringRef Code = "int f() { return 3; }";
+  StringRef ID = "id";
+  test(expr().bind(ID), node(ID), Code, "3");
+}
+
+TEST(RangeSelectorTest, StatementOp) {
+  StringRef Code = "int f() { return 3; }";
+  StringRef ID = "id";
+  test(expr().bind(ID), statement(ID), Code, "3;");
+}
+
+TEST(RangeSelectorTest, MemberOp) {
+  StringRef Code = R"cc(
+    struct S {
+      int member;
+    };
+    int g() {
+      S s;
+      return s.member;
+    }
+  )cc";
+  StringRef ID = "id";
+  test(memberExpr().bind(ID), member(ID), Code, "member");
+}
+
+// Tests that member does not select any qualifiers on the member name.
+TEST(RangeSelectorTest, MemberOpQualified) {
+  StringRef Code = R"cc(
+    struct S {
+      int member;
+    };
+    struct T : public S {
+      int field;
+    };
+    int g() {
+      T t;
+      return t.S::member;
+    }
+  )cc";
+  StringRef ID = "id";
+  test(memberExpr().bind(ID), member(ID), Code, "member");
+}
+
+TEST(RangeSelectorTest, MemberOpTemplate) {
+  StringRef Code = R"cc(
+    struct S {
+      template <typename T> T foo(T t);
+    };
+    int f(int x) {
+      S s;
+      return s.template foo<int>(3);
+    }
+  )cc";
+
+  StringRef ID = "id";
+  test(memberExpr().bind(ID), member(ID), Code, "foo");
+}
+
+TEST(RangeSelectorTest, MemberOpOperator) {
+  StringRef Code = R"cc(
+    struct S {
+      int operator*();
+    };
+    int f(int x) {
+      S s;
+      return s.operator *();
+    }
+  )cc";
+
+  StringRef ID = "id";
+  test(memberExpr().bind(ID), member(ID), Code, "operator *");
+}
+
+TEST(RangeSelectorTest, NameOpNamedDecl) {
+  StringRef Code = R"cc(
+    int myfun() {
+      return 3;
+    }
+  )cc";
+  StringRef ID = "id";
+  test(functionDecl().bind(ID), name(ID), Code, "myfun");
+}
+
+TEST(RangeSelectorTest, NameOpDeclRef) {
+  StringRef Code = R"cc(
+    int foo(int x) {
+      return x;
+    }
+    int g(int x) { return foo(x) * x; }
+  )cc";
+  StringRef Ref = "ref";
+  test(declRefExpr(to(functionDecl())).bind(Ref), name(Ref), Code, "foo");
+}
+
+TEST(RangeSelectorTest, NameOpCtorInitializer) {
+  StringRef Code = R"cc(
+    class C {
+     public:
+      C() : field(3) {}
+      int field;
+    };
+  )cc";
+  StringRef Init = "init";
+  test(cxxCtorInitializer().bind(Init), name(Init), Code, "field");
+}
+
+TEST(RangeSelectorTest, NameOpErrors) {
+  testUnboundNodeError(name("unbound"), "unbound");
+  testTypeError(name("stmt"), "stmt");
+}
+
+TEST(RangeSelectorTest, NameOpDeclRefError) {
+  StringRef Code = R"cc(
+    struct S {
+      int operator*();
+    };
+    int f(int x) {
+      S s;
+      return *s + x;
+    }
+  )cc";
+  StringRef Ref = "ref";
+  testError(declRefExpr(to(functionDecl())).bind(Ref), name(Ref), Code,
+            AllOf(HasSubstr(Ref), HasSubstr("requires property 'identifier'")));
+}
+
+TEST(RangeSelectorTest, CallArgsOp) {
+  const StringRef Code = R"cc(
+    struct C {
+      int bar(int, int);
+    };
+    int f() {
+      C x;
+      return x.bar(3, 4);
+    }
+  )cc";
+  StringRef ID = "id";
+  test(callExpr().bind(ID), callArgs(ID), Code, "3, 4");
+}
+
+TEST(RangeSelectorTest, CallArgsOpNoArgs) {
+  const StringRef Code = R"cc(
+    struct C {
+      int bar();
+    };
+    int f() {
+      C x;
+      return x.bar();
+    }
+  )cc";
+  StringRef ID = "id";
+  test(callExpr().bind(ID), callArgs(ID), Code, "");
+}
+
+TEST(RangeSelectorTest, CallArgsOpNoArgsWithComments) {
+  const StringRef Code = R"cc(
+    struct C {
+      int bar();
+    };
+    int f() {
+      C x;
+      return x.bar(/*empty*/);
+    }
+  )cc";
+  StringRef ID = "id";
+  test(callExpr().bind(ID), callArgs(ID), Code, "/*empty*/");
+}
+
+// Tests that arguments are extracted correctly when a temporary (with parens)
+// is used.
+TEST(RangeSelectorTest, CallArgsOpWithParens) {
+  const StringRef Code = R"cc(
+    struct C {
+      int bar(int, int) { return 3; }
+    };
+    int f() {
+      C x;
+      return C().bar(3, 4);
+    }
+  )cc";
+  StringRef ID = "id";
+  test(callExpr(callee(functionDecl(hasName("bar")))).bind(ID), callArgs(ID),
+       Code, "3, 4");
+}
+
+TEST(RangeSelectorTest, CallArgsOpLeadingComments) {
+  const StringRef Code = R"cc(
+    struct C {
+      int bar(int, int) { return 3; }
+    };
+    int f() {
+      C x;
+      return x.bar(/*leading*/ 3, 4);
+    }
+  )cc";
+  StringRef ID = "id";
+  test(callExpr().bind(ID), callArgs(ID), Code, "/*leading*/ 3, 4");
+}
+
+TEST(RangeSelectorTest, CallArgsOpTrailingComments) {
+  const StringRef Code = R"cc(
+    struct C {
+      int bar(int, int) { return 3; }
+    };
+    int f() {
+      C x;
+      return x.bar(3 /*trailing*/, 4);
+    }
+  )cc";
+  StringRef ID = "id";
+  test(callExpr().bind(ID), callArgs(ID), Code, "3 /*trailing*/, 4");
+}
+
+TEST(RangeSelectorTest, CallArgsOpEolComments) {
+  const StringRef Code = R"cc(
+    struct C {
+      int bar(int, int) { return 3; }
+    };
+    int f() {
+      C x;
+      return x.bar(  // Header
+          1,           // foo
+          2            // bar
+      );
+    }
+  )cc";
+  StringRef ID = "id";
+  test(callExpr().bind(ID), callArgs(ID), Code, R"(  // Header
+          1,           // foo
+          2            // bar
+      )");
+}
+
+TEST(RangeSelectorTest, CallArgsErrors) {
+  testUnboundNodeError(callArgs("unbound"), "unbound");
+  testTypeError(callArgs("stmt"), "stmt");
+}
+
+
+TEST(RangeSelectorTest, StatementsOp) {
+  StringRef Code = R"cc(
+    void g();
+    void f() { /* comment */ g(); /* comment*/ g(); /* comment */ }
+  )cc";
+  StringRef ID = "id";
+  test(compoundStmt().bind(ID), statements(ID), Code,
+       " /* comment */ g(); /* comment*/ g(); /* comment */ ");
+}
+
+TEST(RangeSelectorTest, StatementsOpEmptyList) {
+  StringRef Code = "void f() {}";
+  StringRef ID = "id";
+  test(compoundStmt().bind(ID), statements(ID), Code, "");
+}
+
+TEST(RangeSelectorTest, StatementsOpErrors) {
+  testUnboundNodeError(statements("unbound"), "unbound");
+  testTypeError(statements("decl"), "decl");
+}
+
+TEST(RangeSelectorTest, ElementsOp) {
+  StringRef Code = R"cc(
+    void f() {
+      int v[] = {/* comment */ 3, /* comment*/ 4 /* comment */};
+      (void)v;
+    }
+  )cc";
+  StringRef ID = "id";
+  test(initListExpr().bind(ID), initListElements(ID), Code,
+       "/* comment */ 3, /* comment*/ 4 /* comment */");
+}
+
+TEST(RangeSelectorTest, ElementsOpEmptyList) {
+  StringRef Code = R"cc(
+    void f() {
+      int v[] = {};
+      (void)v;
+    }
+  )cc";
+  StringRef ID = "id";
+  test(initListExpr().bind(ID), initListElements(ID), Code, "");
+}
+
+TEST(RangeSelectorTest, ElementsOpErrors) {
+  testUnboundNodeError(initListElements("unbound"), "unbound");
+  testTypeError(initListElements("stmt"), "stmt");
+}
+
+// Tests case where the matched node is the complete expanded text.
+TEST(RangeSelectorTest, ExpansionOp) {
+  StringRef Code = R"cc(
+#define BADDECL(E) int bad(int x) { return E; }
+    BADDECL(x * x)
+  )cc";
+
+  StringRef Fun = "Fun";
+  test(functionDecl(hasName("bad")).bind(Fun), expansion(node(Fun)), Code,
+       "BADDECL(x * x)");
+}
+
+// Tests case where the matched node is (only) part of the expanded text.
+TEST(RangeSelectorTest, ExpansionOpPartial) {
+  StringRef Code = R"cc(
+#define BADDECL(E) int bad(int x) { return E; }
+    BADDECL(x * x)
+  )cc";
+
+  StringRef Ret = "Ret";
+  test(returnStmt().bind(Ret), expansion(node(Ret)), Code, "BADDECL(x * x)");
+}
+
+} // namespace
Index: clang/unittests/Tooling/CMakeLists.txt
===================================================================
--- clang/unittests/Tooling/CMakeLists.txt
+++ clang/unittests/Tooling/CMakeLists.txt
@@ -22,6 +22,7 @@
   LexicallyOrderedRecursiveASTVisitorTest.cpp
   LookupTest.cpp
   QualTypeNamesTest.cpp
+  RangeSelectorTest.cpp
   RecursiveASTVisitorTests/Attr.cpp
   RecursiveASTVisitorTests/Class.cpp
   RecursiveASTVisitorTests/ConstructExpr.cpp
Index: clang/lib/Tooling/Refactoring/RangeSelector.cpp
===================================================================
--- /dev/null
+++ clang/lib/Tooling/Refactoring/RangeSelector.cpp
@@ -0,0 +1,261 @@
+//===--- 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/RangeSelector.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Tooling/Refactoring/SourceCode.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Errc.h"
+#include "llvm/Support/Error.h"
+#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::StringError;
+
+using MatchResult = MatchFinder::MatchResult;
+
+static Error invalidArgumentError(Twine Message) {
+  return llvm::make_error<StringError>(llvm::errc::invalid_argument, Message);
+}
+
+static Error typeError(StringRef ID, const ASTNodeKind &Kind) {
+  return invalidArgumentError("mismatched type (node id=" + ID +
+                              " kind=" + Kind.asStringRef() + ")");
+}
+
+static Error typeError(StringRef ID, const ASTNodeKind &Kind,
+                       Twine ExpectedType) {
+  return invalidArgumentError("mismatched type: expected one of " +
+                              ExpectedType + " (node id=" + ID +
+                              " kind=" + Kind.asStringRef() + ")");
+}
+
+static Error missingPropertyError(StringRef ID, Twine Description,
+                                  StringRef Property) {
+  return invalidArgumentError(Description + " requires property '" + Property +
+                              "' (node id=" + ID + ")");
+}
+
+static Expected<DynTypedNode> getNode(const ast_matchers::BoundNodes &Nodes,
+                                      StringRef ID) {
+  auto &NodesMap = Nodes.getMap();
+  auto It = NodesMap.find(ID);
+  if (It == NodesMap.end())
+    return invalidArgumentError("ID not bound: " + ID);
+  return It->second;
+}
+
+static SourceLocation findPreviousTokenStart(SourceLocation Start,
+                                             const SourceManager &SM,
+                                             const LangOptions &LangOpts) {
+  if (Start.isInvalid() || Start.isMacroID())
+    return SourceLocation();
+
+  SourceLocation BeforeStart = Start.getLocWithOffset(-1);
+  if (BeforeStart.isInvalid() || BeforeStart.isMacroID())
+    return SourceLocation();
+
+  return Lexer::GetBeginningOfToken(BeforeStart, SM, LangOpts);
+}
+
+static SourceLocation findPreviousTokenKind(SourceLocation Start,
+                                            const SourceManager &SM,
+                                            const LangOptions &LangOpts,
+                                            tok::TokenKind TK) {
+  while (true) {
+    SourceLocation L = findPreviousTokenStart(Start, SM, LangOpts);
+    if (L.isInvalid() || L.isMacroID())
+      return SourceLocation();
+
+    Token T;
+    if (Lexer::getRawToken(L, T, SM, LangOpts, /*IgnoreWhiteSpace=*/true))
+      return SourceLocation();
+
+    if (T.is(TK))
+      return T.getLocation();
+
+    Start = L;
+  }
+}
+
+static SourceLocation findOpenParen(const CallExpr &E, const SourceManager &SM,
+                                    const LangOptions &LangOpts) {
+  SourceLocation EndLoc =
+      E.getNumArgs() == 0 ? E.getRParenLoc() : E.getArg(0)->getBeginLoc();
+  return findPreviousTokenKind(EndLoc, SM, LangOpts, tok::TokenKind::l_paren);
+}
+
+RangeSelector tooling::node(StringRef ID) {
+  return [ID](const MatchResult &Result) -> Expected<CharSourceRange> {
+    Expected<DynTypedNode> Node = getNode(Result.Nodes, ID);
+    if (!Node)
+      return Node.takeError();
+    return Node->get<Stmt>() != nullptr && Node->get<Expr>() == nullptr
+               ? getExtendedRange(*Node, tok::TokenKind::semi, *Result.Context)
+               : CharSourceRange::getTokenRange(Node->getSourceRange());
+  };
+}
+
+RangeSelector tooling::statement(StringRef ID) {
+  return [ID](const MatchResult &Result) -> Expected<CharSourceRange> {
+    Expected<DynTypedNode> Node = getNode(Result.Nodes, ID);
+    if (!Node)
+      return Node.takeError();
+    return getExtendedRange(*Node, tok::TokenKind::semi, *Result.Context);
+  };
+}
+
+RangeSelector tooling::range(RangeSelector Begin, RangeSelector End) {
+  return [Begin, End](const MatchResult &Result) -> Expected<CharSourceRange> {
+    Expected<CharSourceRange> BeginRange = Begin(Result);
+    if (!BeginRange)
+      return BeginRange.takeError();
+    Expected<CharSourceRange> EndRange = End(Result);
+    if (!EndRange)
+      return EndRange.takeError();
+    SourceLocation B = BeginRange->getBegin();
+    SourceLocation E = EndRange->getEnd();
+    // Note: we are precluding the possibility of sub-token ranges in the case
+    // that EndRange is a token range.
+    if (Result.SourceManager->isBeforeInTranslationUnit(E, B)) {
+      return invalidArgumentError("Bad range: out of order");
+    }
+    return CharSourceRange(SourceRange(B, E), EndRange->isTokenRange());
+  };
+}
+
+RangeSelector tooling::range(StringRef BeginID, StringRef EndID) {
+  return tooling::range(node(BeginID), node(EndID));
+}
+
+RangeSelector tooling::member(StringRef ID) {
+  return [ID](const MatchResult &Result) -> Expected<CharSourceRange> {
+    Expected<DynTypedNode> Node = getNode(Result.Nodes, ID);
+    if (!Node)
+      return Node.takeError();
+    if (auto *M = Node->get<clang::MemberExpr>())
+      return CharSourceRange::getTokenRange(
+          M->getMemberNameInfo().getSourceRange());
+    return typeError(ID, Node->getNodeKind(), "MemberExpr");
+  };
+}
+
+RangeSelector tooling::name(StringRef ID) {
+  return [ID](const MatchResult &Result) -> Expected<CharSourceRange> {
+    Expected<DynTypedNode> N = getNode(Result.Nodes, ID);
+    if (!N)
+      return N.takeError();
+    auto &Node = *N;
+    if (const auto *D = Node.get<NamedDecl>()) {
+      if (!D->getDeclName().isIdentifier())
+        return missingPropertyError(ID, "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, *Result.Context) != D->getName())
+        return CharSourceRange();
+      return R;
+    }
+    if (const auto *E = Node.get<DeclRefExpr>()) {
+      if (!E->getNameInfo().getName().isIdentifier())
+        return missingPropertyError(ID, "name", "identifier");
+      SourceLocation L = E->getLocation();
+      return CharSourceRange::getTokenRange(L, L);
+    }
+    if (const auto *I = Node.get<CXXCtorInitializer>()) {
+      if (!I->isMemberInitializer() && I->isWritten())
+        return missingPropertyError(ID, "name", "explicit member initializer");
+      SourceLocation L = I->getMemberLocation();
+      return CharSourceRange::getTokenRange(L, L);
+    }
+    return typeError(ID, Node.getNodeKind(),
+                     "DeclRefExpr, NamedDecl, CXXCtorInitializer");
+  };
+}
+
+namespace {
+// Creates a selector from a range-selection function `Func`, which selects a
+// range that is relative to a bound node id.  `ArgT` is the node type expected
+// by `Func`.
+template <typename T, CharSourceRange (*Func)(const MatchResult &, const T &)>
+class RelativeSelector {
+  std::string ID;
+
+public:
+  RelativeSelector(StringRef ID) : ID(ID) {}
+
+  Expected<CharSourceRange> operator()(const MatchResult &Result) {
+    Expected<DynTypedNode> N = getNode(Result.Nodes, ID);
+    if (!N)
+      return N.takeError();
+    if (const auto *Arg = N->get<T>())
+      return Func(Result, *Arg);
+    return typeError(ID, N->getNodeKind());
+  }
+};
+} // namespace
+
+// Returns the range of the statements (all source between the braces).
+static CharSourceRange getStatementsRange(const MatchResult &,
+                                          const CompoundStmt &CS) {
+  return CharSourceRange::getCharRange(CS.getLBracLoc().getLocWithOffset(1),
+                                       CS.getRBracLoc());
+}
+
+RangeSelector tooling::statements(StringRef ID) {
+  return RelativeSelector<CompoundStmt, getStatementsRange>(ID);
+}
+
+// Returns the range of the source between the call's parentheses.
+static CharSourceRange getCallArgumentsRange(const MatchResult &Result,
+                                             const CallExpr &CE) {
+  return CharSourceRange::getCharRange(
+      findOpenParen(CE, *Result.SourceManager, Result.Context->getLangOpts())
+          .getLocWithOffset(1),
+      CE.getRParenLoc());
+}
+
+RangeSelector tooling::callArgs(StringRef ID) {
+  return RelativeSelector<CallExpr, getCallArgumentsRange>(ID);
+}
+
+// Returns the range of the elements of the initializer list. Includes all
+// source between the braces.
+static CharSourceRange getElementsRange(const MatchResult &,
+                                        const InitListExpr &E) {
+  return CharSourceRange::getCharRange(E.getLBraceLoc().getLocWithOffset(1),
+                                       E.getRBraceLoc());
+}
+
+RangeSelector tooling::initListElements(StringRef ID) {
+  return RelativeSelector<InitListExpr, getElementsRange>(ID);
+}
+
+RangeSelector tooling::expansion(RangeSelector S) {
+  return [S](const MatchResult &Result) -> Expected<CharSourceRange> {
+    Expected<CharSourceRange> SRange = S(Result);
+    if (!SRange)
+      return SRange.takeError();
+    return Result.SourceManager->getExpansionRange(*SRange);
+  };
+}
Index: clang/lib/Tooling/Refactoring/CMakeLists.txt
===================================================================
--- clang/lib/Tooling/Refactoring/CMakeLists.txt
+++ clang/lib/Tooling/Refactoring/CMakeLists.txt
@@ -6,6 +6,7 @@
   AtomicChange.cpp
   Extract/Extract.cpp
   Extract/SourceExtraction.cpp
+  RangeSelector.cpp
   RefactoringActions.cpp
   Rename/RenamingAction.cpp
   Rename/SymbolOccurrences.cpp
Index: clang/include/clang/Tooling/Refactoring/RangeSelector.h
===================================================================
--- /dev/null
+++ clang/include/clang/Tooling/Refactoring/RangeSelector.h
@@ -0,0 +1,76 @@
+//===--- RangeSelector.h - Source-selection 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 combinator library supporting the definition of _selectors_,
+///  which select source ranges based on (bound) AST nodes.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLING_REFACTOR_RANGE_SELECTOR_H_
+#define LLVM_CLANG_TOOLING_REFACTOR_RANGE_SELECTOR_H_
+
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Basic/SourceLocation.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include <functional>
+
+namespace clang {
+namespace tooling {
+using RangeSelector = std::function<Expected<CharSourceRange>(
+    const ast_matchers::MatchFinder::MatchResult &)>;
+
+inline RangeSelector charRange(CharSourceRange R) {
+  return [R](const ast_matchers::MatchFinder::MatchResult &)
+             -> Expected<CharSourceRange> { return R; };
+}
+
+/// Selects from the start of \p Begin and to the end of \p End.
+RangeSelector range(RangeSelector Begin, RangeSelector End);
+
+/// Convenience version of \c range where end-points are bound nodes.
+RangeSelector range(StringRef BeginID, StringRef EndID);
+
+/// Selects a node, including trailing semicolon (for non-expression
+/// statements). \p ID is the node's binding in the match result.
+RangeSelector node(StringRef ID);
+
+/// Selects a node, including trailing semicolon (always). Useful for selecting
+/// expression statements. \p ID is the node's binding in the match result.
+RangeSelector statement(StringRef ID);
+
+/// Given a \c MemberExpr, selects the member token. \p ID is the node's
+/// binding in the match result.
+RangeSelector member(StringRef ID);
+
+/// Given a node with a "name", (like \c NamedDecl, \c DeclRefExpr or \c
+/// CxxCtorInitializer) selects the name's token. \p ID is the node's binding in
+/// the match result.
+RangeSelector name(StringRef ID);
+
+// Given a \c CallExpr (bound to \p ID), selects the arguments' source text (all
+// source between the call's parentheses).
+RangeSelector callArgs(StringRef ID);
+
+// Given a \c CompoundStmt (bound to \p ID), selects the source of the
+// statements (all source between the braces).
+RangeSelector statements(StringRef ID);
+
+// Given a \c InitListExpr (bound to \p ID), selects the range of the elements
+// (all source between the braces).
+RangeSelector initListElements(StringRef ID);
+
+/// Selects the range from which `S` was expanded (possibly along with other
+/// source), if `S` is an expansion, and `S` itself, otherwise.  Corresponds to
+/// `SourceManager::getExpansionRange`.
+RangeSelector expansion(RangeSelector S);
+} // namespace tooling
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLING_REFACTOR_RANGE_SELECTOR_H_
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to