r361152 - [LibTooling] Add RangeSelector library for defining source ranges based on bound AST nodes.

Yitzhak Mandelbaum via cfe-commits cfe-commits at lists.llvm.org
Mon May 20 06:15:14 PDT 2019


Author: ymandel
Date: Mon May 20 06:15:14 2019
New Revision: 361152

URL: http://llvm.org/viewvc/llvm-project?rev=361152&view=rev
Log:
[LibTooling] Add RangeSelector library for defining source ranges based on bound AST nodes.

Summary:

The RangeSelector library defines a combinator language for specifying source
ranges based on bound ids for AST nodes.  The combinator approach follows the
design of the AST matchers.  The RangeSelectors defined here will be used in
both RewriteRule, for specifying source affected by edit, and in Stencil for
specifying source to use constructively in a replacement.

Reviewers: ilya-biryukov

Subscribers: mgorny, cfe-commits

Tags: #clang

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

Added:
    cfe/trunk/include/clang/Tooling/Refactoring/RangeSelector.h
    cfe/trunk/lib/Tooling/Refactoring/RangeSelector.cpp
    cfe/trunk/unittests/Tooling/RangeSelectorTest.cpp
Modified:
    cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt
    cfe/trunk/unittests/Tooling/CMakeLists.txt

Added: cfe/trunk/include/clang/Tooling/Refactoring/RangeSelector.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/Refactoring/RangeSelector.h?rev=361152&view=auto
==============================================================================
--- cfe/trunk/include/clang/Tooling/Refactoring/RangeSelector.h (added)
+++ cfe/trunk/include/clang/Tooling/Refactoring/RangeSelector.h Mon May 20 06:15:14 2019
@@ -0,0 +1,80 @@
+//===--- 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.  Only selects the final
+/// identifier of a qualified name, but not any qualifiers or template
+/// arguments.  For example, for `::foo::bar::baz` and `::foo::bar::baz<int>`,
+/// it selects only `baz`.
+///
+/// \param 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_

Modified: cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt?rev=361152&r1=361151&r2=361152&view=diff
==============================================================================
--- cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt (original)
+++ cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt Mon May 20 06:15:14 2019
@@ -6,6 +6,7 @@ add_clang_library(clangToolingRefactor
   AtomicChange.cpp
   Extract/Extract.cpp
   Extract/SourceExtraction.cpp
+  RangeSelector.cpp
   RefactoringActions.cpp
   Rename/RenamingAction.cpp
   Rename/SymbolOccurrences.cpp

Added: cfe/trunk/lib/Tooling/Refactoring/RangeSelector.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/Refactoring/RangeSelector.cpp?rev=361152&view=auto
==============================================================================
--- cfe/trunk/lib/Tooling/Refactoring/RangeSelector.cpp (added)
+++ cfe/trunk/lib/Tooling/Refactoring/RangeSelector.cpp Mon May 20 06:15:14 2019
@@ -0,0 +1,264 @@
+//===--- RangeSelector.cpp - RangeSelector implementations ------*- 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;
+}
+
+// FIXME: handling of macros should be configurable.
+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);
+}
+
+// Finds the start location of the previous token of kind \p TK.
+// FIXME: handling of macros should be configurable.
+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 \p Func, which selects a
+// range that is relative to a bound node id.  \c T is the node type expected by
+// \p 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);
+  };
+}

Modified: cfe/trunk/unittests/Tooling/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/Tooling/CMakeLists.txt?rev=361152&r1=361151&r2=361152&view=diff
==============================================================================
--- cfe/trunk/unittests/Tooling/CMakeLists.txt (original)
+++ cfe/trunk/unittests/Tooling/CMakeLists.txt Mon May 20 06:15:14 2019
@@ -22,6 +22,7 @@ add_clang_unittest(ToolingTests
   LexicallyOrderedRecursiveASTVisitorTest.cpp
   LookupTest.cpp
   QualTypeNamesTest.cpp
+  RangeSelectorTest.cpp
   RecursiveASTVisitorTests/Attr.cpp
   RecursiveASTVisitorTests/Class.cpp
   RecursiveASTVisitorTests/ConstructExpr.cpp
@@ -69,6 +70,8 @@ target_link_libraries(ToolingTests
   clangToolingCore
   clangToolingInclusions
   clangToolingRefactor
+  LLVMSupport
+  LLVMTestingSupport
   )
 
 

Added: cfe/trunk/unittests/Tooling/RangeSelectorTest.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/Tooling/RangeSelectorTest.cpp?rev=361152&view=auto
==============================================================================
--- cfe/trunk/unittests/Tooling/RangeSelectorTest.cpp (added)
+++ cfe/trunk/unittests/Tooling/RangeSelectorTest.cpp Mon May 20 06:15:14 2019
@@ -0,0 +1,496 @@
+//===- 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 "llvm/Testing/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::HasSubstr;
+using MatchResult = MatchFinder::MatchResult;
+using ::llvm::Expected;
+using ::llvm::Failed;
+using ::llvm::HasValue;
+using ::llvm::StringError;
+
+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> TestMatch matchCode(StringRef Code, M Matcher) {
+  auto ASTUnit = buildASTFromCode(Code);
+  assert(ASTUnit != nullptr && "AST construction failed");
+
+  ASTContext &Context = ASTUnit->getASTContext();
+  assert(!Context.getDiagnostics().hasErrorOccurred() && "Compilation error");
+
+  auto Matches = ast_matchers::match(Matcher, Context);
+  // We expect a single, exact match.
+  assert(Matches.size() != 0 && "no matches found");
+  assert(Matches.size() == 1 && "too many matches");
+
+  return TestMatch{std::move(ASTUnit), MatchResult(Matches[0], &Context)};
+}
+
+// Applies \p Selector to \p Match and, on success, returns the selected source.
+Expected<StringRef> apply(RangeSelector Selector, const TestMatch &Match) {
+  Expected<CharSourceRange> Range = Selector(Match.Result);
+  if (!Range)
+    return Range.takeError();
+  return fixit::internal::getText(*Range, *Match.Result.Context);
+}
+
+// Applies \p Selector to a trivial match with only a single bound node with id
+// "bound_node_id".  For use in testing unbound-node errors.
+Expected<CharSourceRange> applyToTrivial(const RangeSelector &Selector) {
+  // We need to bind the result to something, or the match will fail. Use a
+  // binding that is not used in the unbound node tests.
+  TestMatch Match =
+      matchCode("static int x = 0;", varDecl().bind("bound_node_id"));
+  return Selector(Match.Result);
+}
+
+// Matches the message expected for unbound-node failures.
+testing::Matcher<StringError> withUnboundNodeMessage() {
+  return testing::Property(
+      &StringError::getMessage,
+      AllOf(HasSubstr("unbound_id"), HasSubstr("not bound")));
+}
+
+// Applies \p Selector to code containing assorted node types, where the match
+// binds each one: a statement ("stmt"), a (non-member) ctor-initializer
+// ("init"), an expression ("expr") and a (nameless) declaration ("decl").  Used
+// to test failures caused by applying selectors to nodes of the wrong type.
+Expected<CharSourceRange> applyToAssorted(RangeSelector Selector) {
+  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");
+
+  return Selector(matchCode(Code, Matcher).Result);
+}
+
+// Matches the message expected for type-error failures.
+testing::Matcher<StringError> withTypeErrorMessage(StringRef NodeID) {
+  return testing::Property(
+      &StringError::getMessage,
+      AllOf(HasSubstr(NodeID), HasSubstr("mismatched type")));
+}
+
+TEST(RangeSelectorTest, UnboundNode) {
+  EXPECT_THAT_EXPECTED(applyToTrivial(node("unbound_id")),
+                       Failed<StringError>(withUnboundNodeMessage()));
+}
+
+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);
+  TestMatch Match = matchCode(Code, Matcher);
+
+  // Node-id specific version:
+  EXPECT_THAT_EXPECTED(apply(range(Arg0, Arg1), Match), HasValue("3, 7"));
+  // General version:
+  EXPECT_THAT_EXPECTED(apply(range(node(Arg0), node(Arg1)), Match),
+                       HasValue("3, 7"));
+}
+
+TEST(RangeSelectorTest, NodeOpStatement) {
+  StringRef Code = "int f() { return 3; }";
+  StringRef ID = "id";
+  TestMatch Match = matchCode(Code, returnStmt().bind(ID));
+  EXPECT_THAT_EXPECTED(apply(node(ID), Match), HasValue("return 3;"));
+}
+
+TEST(RangeSelectorTest, NodeOpExpression) {
+  StringRef Code = "int f() { return 3; }";
+  StringRef ID = "id";
+  TestMatch Match = matchCode(Code, expr().bind(ID));
+  EXPECT_THAT_EXPECTED(apply(node(ID), Match), HasValue("3"));
+}
+
+TEST(RangeSelectorTest, StatementOp) {
+  StringRef Code = "int f() { return 3; }";
+  StringRef ID = "id";
+  TestMatch Match = matchCode(Code, expr().bind(ID));
+  EXPECT_THAT_EXPECTED(apply(statement(ID), Match), HasValue("3;"));
+}
+
+TEST(RangeSelectorTest, MemberOp) {
+  StringRef Code = R"cc(
+    struct S {
+      int member;
+    };
+    int g() {
+      S s;
+      return s.member;
+    }
+  )cc";
+  StringRef ID = "id";
+  TestMatch Match = matchCode(Code, memberExpr().bind(ID));
+  EXPECT_THAT_EXPECTED(apply(member(ID), Match), HasValue("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";
+  TestMatch Match = matchCode(Code, memberExpr().bind(ID));
+  EXPECT_THAT_EXPECTED(apply(member(ID), Match), HasValue("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";
+  TestMatch Match = matchCode(Code, memberExpr().bind(ID));
+  EXPECT_THAT_EXPECTED(apply(member(ID), Match), HasValue("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";
+  TestMatch Match = matchCode(Code, memberExpr().bind(ID));
+  EXPECT_THAT_EXPECTED(apply(member(ID), Match), HasValue("operator *"));
+}
+
+TEST(RangeSelectorTest, NameOpNamedDecl) {
+  StringRef Code = R"cc(
+    int myfun() {
+      return 3;
+    }
+  )cc";
+  StringRef ID = "id";
+  TestMatch Match = matchCode(Code, functionDecl().bind(ID));
+  EXPECT_THAT_EXPECTED(apply(name(ID), Match), HasValue("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";
+  TestMatch Match = matchCode(Code, declRefExpr(to(functionDecl())).bind(Ref));
+  EXPECT_THAT_EXPECTED(apply(name(Ref), Match), HasValue("foo"));
+}
+
+TEST(RangeSelectorTest, NameOpCtorInitializer) {
+  StringRef Code = R"cc(
+    class C {
+     public:
+      C() : field(3) {}
+      int field;
+    };
+  )cc";
+  StringRef Init = "init";
+  TestMatch Match = matchCode(Code, cxxCtorInitializer().bind(Init));
+  EXPECT_THAT_EXPECTED(apply(name(Init), Match), HasValue("field"));
+}
+
+TEST(RangeSelectorTest, NameOpErrors) {
+  EXPECT_THAT_EXPECTED(applyToTrivial(name("unbound_id")),
+                       Failed<StringError>(withUnboundNodeMessage()));
+  EXPECT_THAT_EXPECTED(applyToAssorted(name("stmt")),
+                       Failed<StringError>(withTypeErrorMessage("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";
+  TestMatch Match = matchCode(Code, declRefExpr(to(functionDecl())).bind(Ref));
+  EXPECT_THAT_EXPECTED(
+      name(Ref)(Match.Result),
+      Failed<StringError>(testing::Property(
+          &StringError::getMessage,
+          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";
+  TestMatch Match = matchCode(Code, callExpr().bind(ID));
+  EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match), HasValue("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";
+  TestMatch Match = matchCode(Code, callExpr().bind(ID));
+  EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match), HasValue(""));
+}
+
+TEST(RangeSelectorTest, CallArgsOpNoArgsWithComments) {
+  const StringRef Code = R"cc(
+    struct C {
+      int bar();
+    };
+    int f() {
+      C x;
+      return x.bar(/*empty*/);
+    }
+  )cc";
+  StringRef ID = "id";
+  TestMatch Match = matchCode(Code, callExpr().bind(ID));
+  EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match), HasValue("/*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";
+  TestMatch Match =
+      matchCode(Code, callExpr(callee(functionDecl(hasName("bar")))).bind(ID));
+  EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match), HasValue("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";
+  TestMatch Match = matchCode(Code, callExpr().bind(ID));
+  EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match),
+                       HasValue("/*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";
+  TestMatch Match = matchCode(Code, callExpr().bind(ID));
+  EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match),
+                       HasValue("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";
+  TestMatch Match = matchCode(Code, callExpr().bind(ID));
+  EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match), HasValue(R"(  // Header
+          1,           // foo
+          2            // bar
+      )"));
+}
+
+TEST(RangeSelectorTest, CallArgsErrors) {
+  EXPECT_THAT_EXPECTED(applyToTrivial(callArgs("unbound_id")),
+                       Failed<StringError>(withUnboundNodeMessage()));
+  EXPECT_THAT_EXPECTED(applyToAssorted(callArgs("stmt")),
+                       Failed<StringError>(withTypeErrorMessage("stmt")));
+}
+
+TEST(RangeSelectorTest, StatementsOp) {
+  StringRef Code = R"cc(
+    void g();
+    void f() { /* comment */ g(); /* comment */ g(); /* comment */ }
+  )cc";
+  StringRef ID = "id";
+  TestMatch Match = matchCode(Code, compoundStmt().bind(ID));
+  EXPECT_THAT_EXPECTED(
+      apply(statements(ID), Match),
+      HasValue(" /* comment */ g(); /* comment */ g(); /* comment */ "));
+}
+
+TEST(RangeSelectorTest, StatementsOpEmptyList) {
+  StringRef Code = "void f() {}";
+  StringRef ID = "id";
+  TestMatch Match = matchCode(Code, compoundStmt().bind(ID));
+  EXPECT_THAT_EXPECTED(apply(statements(ID), Match), HasValue(""));
+}
+
+TEST(RangeSelectorTest, StatementsOpErrors) {
+  EXPECT_THAT_EXPECTED(applyToTrivial(statements("unbound_id")),
+                       Failed<StringError>(withUnboundNodeMessage()));
+  EXPECT_THAT_EXPECTED(applyToAssorted(statements("decl")),
+                       Failed<StringError>(withTypeErrorMessage("decl")));
+}
+
+TEST(RangeSelectorTest, ElementsOp) {
+  StringRef Code = R"cc(
+    void f() {
+      int v[] = {/* comment */ 3, /* comment*/ 4 /* comment */};
+      (void)v;
+    }
+  )cc";
+  StringRef ID = "id";
+  TestMatch Match = matchCode(Code, initListExpr().bind(ID));
+  EXPECT_THAT_EXPECTED(
+      apply(initListElements(ID), Match),
+      HasValue("/* comment */ 3, /* comment*/ 4 /* comment */"));
+}
+
+TEST(RangeSelectorTest, ElementsOpEmptyList) {
+  StringRef Code = R"cc(
+    void f() {
+      int v[] = {};
+      (void)v;
+    }
+  )cc";
+  StringRef ID = "id";
+  TestMatch Match = matchCode(Code, initListExpr().bind(ID));
+  EXPECT_THAT_EXPECTED(apply(initListElements(ID), Match), HasValue(""));
+}
+
+TEST(RangeSelectorTest, ElementsOpErrors) {
+  EXPECT_THAT_EXPECTED(applyToTrivial(initListElements("unbound_id")),
+                       Failed<StringError>(withUnboundNodeMessage()));
+  EXPECT_THAT_EXPECTED(applyToAssorted(initListElements("stmt")),
+                       Failed<StringError>(withTypeErrorMessage("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";
+  TestMatch Match = matchCode(Code, functionDecl(hasName("bad")).bind(Fun));
+  EXPECT_THAT_EXPECTED(apply(expansion(node(Fun)), Match),
+                       HasValue("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";
+  TestMatch Match = matchCode(Code, returnStmt().bind(Ret));
+  EXPECT_THAT_EXPECTED(apply(expansion(node(Ret)), Match),
+                       HasValue("BADDECL(x * x)"));
+}
+
+} // namespace




More information about the cfe-commits mailing list