diff --git a/clang/include/clang/Tooling/Refactoring/RangeSelector.h b/clang/include/clang/Tooling/Refactoring/RangeSelector.h new file mode 100644 --- /dev/null +++ b/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 + +namespace clang { +namespace tooling { +using RangeSelector = std::function( + const ast_matchers::MatchFinder::MatchResult &)>; + +inline RangeSelector charRange(CharSourceRange R) { + return [R](const ast_matchers::MatchFinder::MatchResult &) + -> Expected { 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_ diff --git a/clang/lib/Tooling/Refactoring/CMakeLists.txt b/clang/lib/Tooling/Refactoring/CMakeLists.txt --- a/clang/lib/Tooling/Refactoring/CMakeLists.txt +++ b/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 diff --git a/clang/lib/Tooling/Refactoring/RangeSelector.cpp b/clang/lib/Tooling/Refactoring/RangeSelector.cpp new file mode 100644 --- /dev/null +++ b/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 +#include +#include + +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(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 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 { + Expected Node = getNode(Result.Nodes, ID); + if (!Node) + return Node.takeError(); + return Node->get() != nullptr && Node->get() == nullptr + ? getExtendedRange(*Node, tok::TokenKind::semi, *Result.Context) + : CharSourceRange::getTokenRange(Node->getSourceRange()); + }; +} + +RangeSelector tooling::statement(StringRef ID) { + return [ID](const MatchResult &Result) -> Expected { + Expected 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 { + Expected BeginRange = Begin(Result); + if (!BeginRange) + return BeginRange.takeError(); + Expected 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 { + Expected Node = getNode(Result.Nodes, ID); + if (!Node) + return Node.takeError(); + if (auto *M = Node->get()) + return CharSourceRange::getTokenRange( + M->getMemberNameInfo().getSourceRange()); + return typeError(ID, Node->getNodeKind(), "MemberExpr"); + }; +} + +RangeSelector tooling::name(StringRef ID) { + return [ID](const MatchResult &Result) -> Expected { + Expected N = getNode(Result.Nodes, ID); + if (!N) + return N.takeError(); + auto &Node = *N; + if (const auto *D = Node.get()) { + 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` 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()) { + 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()) { + 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 +class RelativeSelector { + std::string ID; + +public: + RelativeSelector(StringRef ID) : ID(ID) {} + + Expected operator()(const MatchResult &Result) { + Expected N = getNode(Result.Nodes, ID); + if (!N) + return N.takeError(); + if (const auto *Arg = N->get()) + 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(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(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(ID); +} + +RangeSelector tooling::expansion(RangeSelector S) { + return [S](const MatchResult &Result) -> Expected { + Expected SRange = S(Result); + if (!SRange) + return SRange.takeError(); + return Result.SourceManager->getExpansionRange(*SRange); + }; +} diff --git a/clang/unittests/Tooling/CMakeLists.txt b/clang/unittests/Tooling/CMakeLists.txt --- a/clang/unittests/Tooling/CMakeLists.txt +++ b/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 diff --git a/clang/unittests/Tooling/RangeSelectorTest.cpp b/clang/unittests/Tooling/RangeSelectorTest.cpp new file mode 100644 --- /dev/null +++ b/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; + // The result to use in the test. References `ast_unit`. + MatchResult Result; +}; + +template +llvm::Optional 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 +void test(M Matcher, RangeSelector Selector, StringRef Code, + StringRef Selected) { + Optional Match = matchAny(Code, Matcher); + ASSERT_TRUE(Match); + MatchResult &Result = Match->Result; + if (Expected 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 +void testError(M Matcher, const RangeSelector &Selector, StringRef Code, + ::testing::Matcher ErrorMatcher) { + Optional 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 T foo(T t); + }; + int f(int x) { + S s; + return s.template foo(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