| //===- unittest/Tooling/ASTSelectionTest.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 "TestVisitor.h" |
| #include "clang/Basic/SourceManager.h" |
| #include "clang/Tooling/Refactoring/ASTSelection.h" |
| |
| using namespace clang; |
| using namespace tooling; |
| |
| namespace { |
| |
| struct FileLocation { |
| unsigned Line, Column; |
| |
| SourceLocation translate(const SourceManager &SM) { |
| return SM.translateLineCol(SM.getMainFileID(), Line, Column); |
| } |
| }; |
| |
| using FileRange = std::pair<FileLocation, FileLocation>; |
| |
| class SelectionFinderVisitor : public TestVisitor<SelectionFinderVisitor> { |
| FileLocation Location; |
| Optional<FileRange> SelectionRange; |
| llvm::function_ref<void(SourceRange SelectionRange, |
| Optional<SelectedASTNode>)> |
| Consumer; |
| |
| public: |
| SelectionFinderVisitor(FileLocation Location, |
| Optional<FileRange> SelectionRange, |
| llvm::function_ref<void(SourceRange SelectionRange, |
| Optional<SelectedASTNode>)> |
| Consumer) |
| : Location(Location), SelectionRange(SelectionRange), Consumer(Consumer) { |
| } |
| |
| bool VisitTranslationUnitDecl(const TranslationUnitDecl *TU) { |
| const ASTContext &Context = TU->getASTContext(); |
| const SourceManager &SM = Context.getSourceManager(); |
| |
| SourceRange SelRange; |
| if (SelectionRange) { |
| SelRange = SourceRange(SelectionRange->first.translate(SM), |
| SelectionRange->second.translate(SM)); |
| } else { |
| SourceLocation Loc = Location.translate(SM); |
| SelRange = SourceRange(Loc, Loc); |
| } |
| Consumer(SelRange, findSelectedASTNodes(Context, SelRange)); |
| return false; |
| } |
| }; |
| |
| /// This is a test utility function that computes the AST selection at the |
| /// given location with an optional selection range. |
| /// |
| /// A location roughly corresponds to a cursor location in an editor, while |
| /// the optional range corresponds to the selection range in an editor. |
| void findSelectedASTNodesWithRange( |
| StringRef Source, FileLocation Location, Optional<FileRange> SelectionRange, |
| llvm::function_ref<void(SourceRange SelectionRange, |
| Optional<SelectedASTNode>)> |
| Consumer, |
| SelectionFinderVisitor::Language Language = |
| SelectionFinderVisitor::Lang_CXX11) { |
| SelectionFinderVisitor Visitor(Location, SelectionRange, Consumer); |
| EXPECT_TRUE(Visitor.runOver(Source, Language)); |
| } |
| |
| void findSelectedASTNodes( |
| StringRef Source, FileLocation Location, Optional<FileRange> SelectionRange, |
| llvm::function_ref<void(Optional<SelectedASTNode>)> Consumer, |
| SelectionFinderVisitor::Language Language = |
| SelectionFinderVisitor::Lang_CXX11) { |
| findSelectedASTNodesWithRange( |
| Source, Location, SelectionRange, |
| [&](SourceRange, Optional<SelectedASTNode> Selection) { |
| Consumer(std::move(Selection)); |
| }, |
| Language); |
| } |
| |
| void checkNodeImpl(bool IsTypeMatched, const SelectedASTNode &Node, |
| SourceSelectionKind SelectionKind, unsigned NumChildren) { |
| ASSERT_TRUE(IsTypeMatched); |
| EXPECT_EQ(Node.Children.size(), NumChildren); |
| ASSERT_EQ(Node.SelectionKind, SelectionKind); |
| } |
| |
| void checkDeclName(const SelectedASTNode &Node, StringRef Name) { |
| const auto *ND = Node.Node.get<NamedDecl>(); |
| EXPECT_TRUE(!!ND); |
| ASSERT_EQ(ND->getName(), Name); |
| } |
| |
| template <typename T> |
| const SelectedASTNode &checkNode( |
| const SelectedASTNode &StmtNode, SourceSelectionKind SelectionKind, |
| unsigned NumChildren = 0, |
| std::enable_if_t<std::is_base_of<Stmt, T>::value, T> *StmtOverloadChecker = |
| nullptr) { |
| checkNodeImpl(isa<T>(StmtNode.Node.get<Stmt>()), StmtNode, SelectionKind, |
| NumChildren); |
| return StmtNode; |
| } |
| |
| template <typename T> |
| const SelectedASTNode &checkNode( |
| const SelectedASTNode &DeclNode, SourceSelectionKind SelectionKind, |
| unsigned NumChildren = 0, StringRef Name = "", |
| std::enable_if_t<std::is_base_of<Decl, T>::value, T> *DeclOverloadChecker = |
| nullptr) { |
| checkNodeImpl(isa<T>(DeclNode.Node.get<Decl>()), DeclNode, SelectionKind, |
| NumChildren); |
| if (!Name.empty()) |
| checkDeclName(DeclNode, Name); |
| return DeclNode; |
| } |
| |
| struct ForAllChildrenOf { |
| const SelectedASTNode &Node; |
| |
| static void childKindVerifier(const SelectedASTNode &Node, |
| SourceSelectionKind SelectionKind) { |
| for (const SelectedASTNode &Child : Node.Children) { |
| ASSERT_EQ(Node.SelectionKind, SelectionKind); |
| childKindVerifier(Child, SelectionKind); |
| } |
| } |
| |
| public: |
| ForAllChildrenOf(const SelectedASTNode &Node) : Node(Node) {} |
| |
| void shouldHaveSelectionKind(SourceSelectionKind Kind) { |
| childKindVerifier(Node, Kind); |
| } |
| }; |
| |
| ForAllChildrenOf allChildrenOf(const SelectedASTNode &Node) { |
| return ForAllChildrenOf(Node); |
| } |
| |
| TEST(ASTSelectionFinder, CursorNoSelection) { |
| findSelectedASTNodes( |
| " void f() { }", {1, 1}, None, |
| [](Optional<SelectedASTNode> Node) { EXPECT_FALSE(Node); }); |
| } |
| |
| TEST(ASTSelectionFinder, CursorAtStartOfFunction) { |
| findSelectedASTNodes( |
| "void f() { }", {1, 1}, None, [](Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| checkNode<TranslationUnitDecl>(*Node, SourceSelectionKind::None, |
| /*NumChildren=*/1); |
| checkNode<FunctionDecl>(Node->Children[0], |
| SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/0, /*Name=*/"f"); |
| |
| // Check that the dumping works. |
| std::string DumpValue; |
| llvm::raw_string_ostream OS(DumpValue); |
| Node->Children[0].dump(OS); |
| ASSERT_EQ(OS.str(), "FunctionDecl \"f\" contains-selection\n"); |
| }); |
| } |
| |
| TEST(ASTSelectionFinder, RangeNoSelection) { |
| findSelectedASTNodes( |
| " void f() { }", {1, 1}, FileRange{{1, 1}, {1, 1}}, |
| [](Optional<SelectedASTNode> Node) { EXPECT_FALSE(Node); }); |
| findSelectedASTNodes( |
| " void f() { }", {1, 1}, FileRange{{1, 1}, {1, 2}}, |
| [](Optional<SelectedASTNode> Node) { EXPECT_FALSE(Node); }); |
| } |
| |
| TEST(ASTSelectionFinder, EmptyRangeFallbackToCursor) { |
| findSelectedASTNodes("void f() { }", {1, 1}, FileRange{{1, 1}, {1, 1}}, |
| [](Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| checkNode<FunctionDecl>( |
| Node->Children[0], |
| SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/0, /*Name=*/"f"); |
| }); |
| } |
| |
| TEST(ASTSelectionFinder, WholeFunctionSelection) { |
| StringRef Source = "int f(int x) { return x;\n}\nvoid f2() { }"; |
| // From 'int' until just after '}': |
| |
| findSelectedASTNodes( |
| Source, {1, 1}, FileRange{{1, 1}, {2, 2}}, |
| [](Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| EXPECT_EQ(Node->Children.size(), 1u); |
| const auto &Fn = checkNode<FunctionDecl>( |
| Node->Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/2, /*Name=*/"f"); |
| checkNode<ParmVarDecl>(Fn.Children[0], |
| SourceSelectionKind::InsideSelection); |
| const auto &Body = checkNode<CompoundStmt>( |
| Fn.Children[1], SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1); |
| const auto &Return = checkNode<ReturnStmt>( |
| Body.Children[0], SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1); |
| checkNode<ImplicitCastExpr>(Return.Children[0], |
| SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1); |
| checkNode<DeclRefExpr>(Return.Children[0].Children[0], |
| SourceSelectionKind::InsideSelection); |
| }); |
| |
| // From 'int' until just before '}': |
| findSelectedASTNodes( |
| Source, {2, 1}, FileRange{{1, 1}, {2, 1}}, |
| [](Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| EXPECT_EQ(Node->Children.size(), 1u); |
| const auto &Fn = checkNode<FunctionDecl>( |
| Node->Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/2, /*Name=*/"f"); |
| const auto &Body = checkNode<CompoundStmt>( |
| Fn.Children[1], SourceSelectionKind::ContainsSelectionEnd, |
| /*NumChildren=*/1); |
| checkNode<ReturnStmt>(Body.Children[0], |
| SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1); |
| }); |
| // From '{' until just after '}': |
| findSelectedASTNodes( |
| Source, {1, 14}, FileRange{{1, 14}, {2, 2}}, |
| [](Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| EXPECT_EQ(Node->Children.size(), 1u); |
| const auto &Fn = checkNode<FunctionDecl>( |
| Node->Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1, /*Name=*/"f"); |
| const auto &Body = checkNode<CompoundStmt>( |
| Fn.Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1); |
| checkNode<ReturnStmt>(Body.Children[0], |
| SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1); |
| }); |
| // From 'x' until just after '}': |
| findSelectedASTNodes( |
| Source, {2, 2}, FileRange{{1, 11}, {2, 2}}, |
| [](Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| EXPECT_EQ(Node->Children.size(), 1u); |
| const auto &Fn = checkNode<FunctionDecl>( |
| Node->Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/2, /*Name=*/"f"); |
| checkNode<ParmVarDecl>(Fn.Children[0], |
| SourceSelectionKind::ContainsSelectionStart); |
| const auto &Body = checkNode<CompoundStmt>( |
| Fn.Children[1], SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1); |
| checkNode<ReturnStmt>(Body.Children[0], |
| SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1); |
| }); |
| } |
| |
| TEST(ASTSelectionFinder, MultipleFunctionSelection) { |
| StringRef Source = R"(void f0() { |
| } |
| void f1() { } |
| void f2() { } |
| void f3() { } |
| )"; |
| auto SelectedF1F2 = [](Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| EXPECT_EQ(Node->Children.size(), 2u); |
| checkNode<FunctionDecl>(Node->Children[0], |
| SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1, /*Name=*/"f1"); |
| checkNode<FunctionDecl>(Node->Children[1], |
| SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1, /*Name=*/"f2"); |
| }; |
| // Just after '}' of f0 and just before 'void' of f3: |
| findSelectedASTNodes(Source, {2, 2}, FileRange{{2, 2}, {5, 1}}, SelectedF1F2); |
| // Just before 'void' of f1 and just after '}' of f2: |
| findSelectedASTNodes(Source, {3, 1}, FileRange{{3, 1}, {4, 14}}, |
| SelectedF1F2); |
| } |
| |
| TEST(ASTSelectionFinder, MultipleStatementSelection) { |
| StringRef Source = R"(void f(int x, int y) { |
| int z = x; |
| f(2, 3); |
| if (x == 0) { |
| return; |
| } |
| x = 1; |
| return; |
| })"; |
| // From 'f(2,3)' until just before 'x = 1;': |
| findSelectedASTNodes( |
| Source, {3, 2}, FileRange{{3, 2}, {7, 1}}, |
| [](Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| EXPECT_EQ(Node->Children.size(), 1u); |
| const auto &Fn = checkNode<FunctionDecl>( |
| Node->Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1, /*Name=*/"f"); |
| const auto &Body = checkNode<CompoundStmt>( |
| Fn.Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/2); |
| allChildrenOf(checkNode<CallExpr>(Body.Children[0], |
| SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/3)) |
| .shouldHaveSelectionKind(SourceSelectionKind::InsideSelection); |
| allChildrenOf(checkNode<IfStmt>(Body.Children[1], |
| SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/2)) |
| .shouldHaveSelectionKind(SourceSelectionKind::InsideSelection); |
| }); |
| // From 'f(2,3)' until just before ';' in 'x = 1;': |
| findSelectedASTNodes( |
| Source, {3, 2}, FileRange{{3, 2}, {7, 8}}, |
| [](Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| EXPECT_EQ(Node->Children.size(), 1u); |
| const auto &Fn = checkNode<FunctionDecl>( |
| Node->Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1, /*Name=*/"f"); |
| const auto &Body = checkNode<CompoundStmt>( |
| Fn.Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/3); |
| checkNode<CallExpr>(Body.Children[0], |
| SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/3); |
| checkNode<IfStmt>(Body.Children[1], |
| SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/2); |
| checkNode<BinaryOperator>(Body.Children[2], |
| SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/2); |
| }); |
| // From the middle of 'int z = 3' until the middle of 'x = 1;': |
| findSelectedASTNodes( |
| Source, {2, 10}, FileRange{{2, 10}, {7, 5}}, |
| [](Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| EXPECT_EQ(Node->Children.size(), 1u); |
| const auto &Fn = checkNode<FunctionDecl>( |
| Node->Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1, /*Name=*/"f"); |
| const auto &Body = checkNode<CompoundStmt>( |
| Fn.Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/4); |
| checkNode<DeclStmt>(Body.Children[0], |
| SourceSelectionKind::ContainsSelectionStart, |
| /*NumChildren=*/1); |
| checkNode<CallExpr>(Body.Children[1], |
| SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/3); |
| checkNode<IfStmt>(Body.Children[2], |
| SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/2); |
| checkNode<BinaryOperator>(Body.Children[3], |
| SourceSelectionKind::ContainsSelectionEnd, |
| /*NumChildren=*/1); |
| }); |
| } |
| |
| TEST(ASTSelectionFinder, SelectionInFunctionInObjCImplementation) { |
| StringRef Source = R"( |
| @interface I |
| @end |
| @implementation I |
| |
| int notSelected() { } |
| |
| int selected(int x) { |
| return x; |
| } |
| |
| @end |
| @implementation I(Cat) |
| |
| void catF() { } |
| |
| @end |
| |
| void outerFunction() { } |
| )"; |
| // Just the 'x' expression in 'selected': |
| findSelectedASTNodes( |
| Source, {9, 10}, FileRange{{9, 10}, {9, 11}}, |
| [](Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| EXPECT_EQ(Node->Children.size(), 1u); |
| const auto &Impl = checkNode<ObjCImplementationDecl>( |
| Node->Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1, /*Name=*/"I"); |
| const auto &Fn = checkNode<FunctionDecl>( |
| Impl.Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1, /*Name=*/"selected"); |
| allChildrenOf(Fn).shouldHaveSelectionKind( |
| SourceSelectionKind::ContainsSelection); |
| }, |
| SelectionFinderVisitor::Lang_OBJC); |
| // The entire 'catF': |
| findSelectedASTNodes( |
| Source, {15, 1}, FileRange{{15, 1}, {15, 16}}, |
| [](Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| EXPECT_EQ(Node->Children.size(), 1u); |
| const auto &Impl = checkNode<ObjCCategoryImplDecl>( |
| Node->Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1, /*Name=*/"Cat"); |
| const auto &Fn = checkNode<FunctionDecl>( |
| Impl.Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1, /*Name=*/"catF"); |
| allChildrenOf(Fn).shouldHaveSelectionKind( |
| SourceSelectionKind::ContainsSelection); |
| }, |
| SelectionFinderVisitor::Lang_OBJC); |
| // From the line before 'selected' to the line after 'catF': |
| findSelectedASTNodes( |
| Source, {16, 1}, FileRange{{7, 1}, {16, 1}}, |
| [](Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| EXPECT_EQ(Node->Children.size(), 2u); |
| const auto &Impl = checkNode<ObjCImplementationDecl>( |
| Node->Children[0], SourceSelectionKind::ContainsSelectionStart, |
| /*NumChildren=*/1, /*Name=*/"I"); |
| const auto &Selected = checkNode<FunctionDecl>( |
| Impl.Children[0], SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/2, /*Name=*/"selected"); |
| allChildrenOf(Selected).shouldHaveSelectionKind( |
| SourceSelectionKind::InsideSelection); |
| const auto &Cat = checkNode<ObjCCategoryImplDecl>( |
| Node->Children[1], SourceSelectionKind::ContainsSelectionEnd, |
| /*NumChildren=*/1, /*Name=*/"Cat"); |
| const auto &CatF = checkNode<FunctionDecl>( |
| Cat.Children[0], SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1, /*Name=*/"catF"); |
| allChildrenOf(CatF).shouldHaveSelectionKind( |
| SourceSelectionKind::InsideSelection); |
| }, |
| SelectionFinderVisitor::Lang_OBJC); |
| // Just the 'outer' function: |
| findSelectedASTNodes(Source, {19, 1}, FileRange{{19, 1}, {19, 25}}, |
| [](Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| EXPECT_EQ(Node->Children.size(), 1u); |
| checkNode<FunctionDecl>( |
| Node->Children[0], |
| SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1, /*Name=*/"outerFunction"); |
| }, |
| SelectionFinderVisitor::Lang_OBJC); |
| } |
| |
| TEST(ASTSelectionFinder, FunctionInObjCImplementationCarefulWithEarlyExit) { |
| StringRef Source = R"( |
| @interface I |
| @end |
| @implementation I |
| |
| void selected() { |
| } |
| |
| - (void) method { } |
| |
| @end |
| )"; |
| // Just 'selected' |
| findSelectedASTNodes( |
| Source, {6, 1}, FileRange{{6, 1}, {7, 2}}, |
| [](Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| EXPECT_EQ(Node->Children.size(), 1u); |
| const auto &Impl = checkNode<ObjCImplementationDecl>( |
| Node->Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1, /*Name=*/"I"); |
| checkNode<FunctionDecl>(Impl.Children[0], |
| SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1, /*Name=*/"selected"); |
| }, |
| SelectionFinderVisitor::Lang_OBJC); |
| } |
| |
| TEST(ASTSelectionFinder, AvoidImplicitDeclarations) { |
| StringRef Source = R"( |
| struct Copy { |
| int x; |
| }; |
| void foo() { |
| Copy x; |
| Copy y = x; |
| } |
| )"; |
| // The entire struct 'Copy': |
| findSelectedASTNodes( |
| Source, {2, 1}, FileRange{{2, 1}, {4, 3}}, |
| [](Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| EXPECT_EQ(Node->Children.size(), 1u); |
| const auto &Record = checkNode<CXXRecordDecl>( |
| Node->Children[0], SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1, /*Name=*/"Copy"); |
| checkNode<FieldDecl>(Record.Children[0], |
| SourceSelectionKind::InsideSelection); |
| }); |
| } |
| |
| TEST(ASTSelectionFinder, CorrectEndForObjectiveCImplementation) { |
| StringRef Source = R"( |
| @interface I |
| @end |
| @implementation I |
| @ end |
| )"; |
| // Just after '@ end' |
| findSelectedASTNodes(Source, {5, 6}, None, |
| [](Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| EXPECT_EQ(Node->Children.size(), 1u); |
| checkNode<ObjCImplementationDecl>( |
| Node->Children[0], |
| SourceSelectionKind::ContainsSelection); |
| }, |
| SelectionFinderVisitor::Lang_OBJC); |
| } |
| |
| const SelectedASTNode &checkFnBody(const Optional<SelectedASTNode> &Node, |
| StringRef Name) { |
| EXPECT_TRUE(Node); |
| EXPECT_EQ(Node->Children.size(), 1u); |
| const auto &Fn = checkNode<FunctionDecl>( |
| Node->Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1, Name); |
| return checkNode<CompoundStmt>(Fn.Children[0], |
| SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1); |
| } |
| |
| TEST(ASTSelectionFinder, SelectObjectiveCPseudoObjectExprs) { |
| StringRef Source = R"( |
| @interface I |
| @property(readwrite) int prop; |
| @end |
| void selectProp(I *i) { |
| (void)i.prop; |
| i.prop = 21; |
| } |
| |
| |
| @interface NSMutableArray |
| - (id)objectAtIndexedSubscript:(unsigned int)index; |
| - (void)setObject:(id)object atIndexedSubscript:(unsigned int)index; |
| @end |
| |
| void selectSubscript(NSMutableArray *array, I *i) { |
| (void)array[10]; |
| array[i.prop] = i; |
| } |
| )"; |
| // Just 'i.prop'. |
| findSelectedASTNodes( |
| Source, {6, 7}, FileRange{{6, 7}, {6, 13}}, |
| [](Optional<SelectedASTNode> Node) { |
| const auto &CS = checkFnBody(Node, /*Name=*/"selectProp"); |
| const auto &CCast = checkNode<CStyleCastExpr>( |
| CS.Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1); |
| const auto &POE = checkNode<PseudoObjectExpr>( |
| CCast.Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1); |
| const auto &PRE = checkNode<ObjCPropertyRefExpr>( |
| POE.Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1); |
| const auto &Cast = checkNode<ImplicitCastExpr>( |
| PRE.Children[0], SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1); |
| checkNode<DeclRefExpr>(Cast.Children[0], |
| SourceSelectionKind::InsideSelection); |
| }, |
| SelectionFinderVisitor::Lang_OBJC); |
| // Just 'i.prop = 21' |
| findSelectedASTNodes( |
| Source, {7, 1}, FileRange{{7, 1}, {7, 12}}, |
| [](Optional<SelectedASTNode> Node) { |
| const auto &CS = checkFnBody(Node, /*Name=*/"selectProp"); |
| const auto &POE = checkNode<PseudoObjectExpr>( |
| CS.Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1); |
| const auto &BinOp = checkNode<BinaryOperator>( |
| POE.Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/2); |
| const auto &PRE = checkNode<ObjCPropertyRefExpr>( |
| BinOp.Children[0], SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1); |
| const auto &Cast = checkNode<ImplicitCastExpr>( |
| PRE.Children[0], SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1); |
| checkNode<DeclRefExpr>(Cast.Children[0], |
| SourceSelectionKind::InsideSelection); |
| checkNode<IntegerLiteral>(BinOp.Children[1], |
| SourceSelectionKind::InsideSelection); |
| }, |
| SelectionFinderVisitor::Lang_OBJC); |
| // Just 'array[10]' |
| findSelectedASTNodes( |
| Source, {17, 9}, FileRange{{17, 9}, {17, 18}}, |
| [](Optional<SelectedASTNode> Node) { |
| const auto &CS = checkFnBody(Node, /*Name=*/"selectSubscript"); |
| const auto &CCast = checkNode<CStyleCastExpr>( |
| CS.Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1); |
| const auto &POE = checkNode<PseudoObjectExpr>( |
| CCast.Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1); |
| const auto &SRE = checkNode<ObjCSubscriptRefExpr>( |
| POE.Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/2); |
| const auto &Cast = checkNode<ImplicitCastExpr>( |
| SRE.Children[0], SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1); |
| checkNode<DeclRefExpr>(Cast.Children[0], |
| SourceSelectionKind::InsideSelection); |
| checkNode<IntegerLiteral>(SRE.Children[1], |
| SourceSelectionKind::InsideSelection); |
| }, |
| SelectionFinderVisitor::Lang_OBJC); |
| // Just 'array[i.prop] = array' |
| findSelectedASTNodes( |
| Source, {18, 3}, FileRange{{18, 3}, {18, 20}}, |
| [](Optional<SelectedASTNode> Node) { |
| const auto &CS = checkFnBody(Node, /*Name=*/"selectSubscript"); |
| const auto &POE = checkNode<PseudoObjectExpr>( |
| CS.Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/1); |
| const auto &BinOp = checkNode<BinaryOperator>( |
| POE.Children[0], SourceSelectionKind::ContainsSelection, |
| /*NumChildren=*/2); |
| const auto &SRE = checkNode<ObjCSubscriptRefExpr>( |
| BinOp.Children[0], SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/2); |
| const auto &Cast = checkNode<ImplicitCastExpr>( |
| SRE.Children[0], SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1); |
| checkNode<DeclRefExpr>(Cast.Children[0], |
| SourceSelectionKind::InsideSelection); |
| const auto &POE2 = checkNode<PseudoObjectExpr>( |
| SRE.Children[1], SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1); |
| const auto &PRE = checkNode<ObjCPropertyRefExpr>( |
| POE2.Children[0], SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1); |
| const auto &Cast2 = checkNode<ImplicitCastExpr>( |
| PRE.Children[0], SourceSelectionKind::InsideSelection, |
| /*NumChildren=*/1); |
| checkNode<DeclRefExpr>(Cast2.Children[0], |
| SourceSelectionKind::InsideSelection); |
| checkNode<DeclRefExpr>(BinOp.Children[1], |
| SourceSelectionKind::InsideSelection); |
| }, |
| SelectionFinderVisitor::Lang_OBJC); |
| } |
| |
| TEST(ASTSelectionFinder, SimpleCodeRangeASTSelection) { |
| StringRef Source = R"(void f(int x, int y) { |
| int z = x; |
| f(2, 3); |
| if (x == 0) { |
| return; |
| } |
| x = 1; |
| return; |
| } |
| void f2() { |
| int m = 0; |
| } |
| )"; |
| // No selection range. |
| findSelectedASTNodesWithRange( |
| Source, {2, 2}, None, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_FALSE(SelectedCode); |
| }); |
| findSelectedASTNodesWithRange( |
| Source, {2, 2}, FileRange{{2, 2}, {2, 2}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_FALSE(SelectedCode); |
| }); |
| // Range that spans multiple functions is an invalid code range. |
| findSelectedASTNodesWithRange( |
| Source, {2, 2}, FileRange{{7, 2}, {12, 1}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_FALSE(SelectedCode); |
| }); |
| // Just 'z = x;': |
| findSelectedASTNodesWithRange( |
| Source, {2, 2}, FileRange{{2, 2}, {2, 13}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_TRUE(SelectedCode); |
| EXPECT_EQ(SelectedCode->size(), 1u); |
| EXPECT_TRUE(isa<DeclStmt>((*SelectedCode)[0])); |
| ArrayRef<SelectedASTNode::ReferenceType> Parents = |
| SelectedCode->getParents(); |
| EXPECT_EQ(Parents.size(), 3u); |
| EXPECT_TRUE( |
| isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>())); |
| // Function 'f' definition. |
| EXPECT_TRUE(isa<FunctionDecl>(Parents[1].get().Node.get<Decl>())); |
| // Function body of function 'F'. |
| EXPECT_TRUE(isa<CompoundStmt>(Parents[2].get().Node.get<Stmt>())); |
| }); |
| // From 'f(2,3)' until just before 'x = 1;': |
| findSelectedASTNodesWithRange( |
| Source, {3, 2}, FileRange{{3, 2}, {7, 1}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_TRUE(SelectedCode); |
| EXPECT_EQ(SelectedCode->size(), 2u); |
| EXPECT_TRUE(isa<CallExpr>((*SelectedCode)[0])); |
| EXPECT_TRUE(isa<IfStmt>((*SelectedCode)[1])); |
| ArrayRef<SelectedASTNode::ReferenceType> Parents = |
| SelectedCode->getParents(); |
| EXPECT_EQ(Parents.size(), 3u); |
| EXPECT_TRUE( |
| isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>())); |
| // Function 'f' definition. |
| EXPECT_TRUE(isa<FunctionDecl>(Parents[1].get().Node.get<Decl>())); |
| // Function body of function 'F'. |
| EXPECT_TRUE(isa<CompoundStmt>(Parents[2].get().Node.get<Stmt>())); |
| }); |
| // From 'f(2,3)' until just before ';' in 'x = 1;': |
| findSelectedASTNodesWithRange( |
| Source, {3, 2}, FileRange{{3, 2}, {7, 8}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_TRUE(SelectedCode); |
| EXPECT_EQ(SelectedCode->size(), 3u); |
| EXPECT_TRUE(isa<CallExpr>((*SelectedCode)[0])); |
| EXPECT_TRUE(isa<IfStmt>((*SelectedCode)[1])); |
| EXPECT_TRUE(isa<BinaryOperator>((*SelectedCode)[2])); |
| }); |
| // From the middle of 'int z = 3' until the middle of 'x = 1;': |
| findSelectedASTNodesWithRange( |
| Source, {2, 10}, FileRange{{2, 10}, {7, 5}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_TRUE(SelectedCode); |
| EXPECT_EQ(SelectedCode->size(), 4u); |
| EXPECT_TRUE(isa<DeclStmt>((*SelectedCode)[0])); |
| EXPECT_TRUE(isa<CallExpr>((*SelectedCode)[1])); |
| EXPECT_TRUE(isa<IfStmt>((*SelectedCode)[2])); |
| EXPECT_TRUE(isa<BinaryOperator>((*SelectedCode)[3])); |
| }); |
| } |
| |
| TEST(ASTSelectionFinder, OutOfBodyCodeRange) { |
| StringRef Source = R"( |
| int codeRange = 2 + 3; |
| )"; |
| // '2+3' expression. |
| findSelectedASTNodesWithRange( |
| Source, {2, 17}, FileRange{{2, 17}, {2, 22}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_TRUE(SelectedCode); |
| EXPECT_EQ(SelectedCode->size(), 1u); |
| EXPECT_TRUE(isa<BinaryOperator>((*SelectedCode)[0])); |
| ArrayRef<SelectedASTNode::ReferenceType> Parents = |
| SelectedCode->getParents(); |
| EXPECT_EQ(Parents.size(), 2u); |
| EXPECT_TRUE( |
| isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>())); |
| // Variable 'codeRange'. |
| EXPECT_TRUE(isa<VarDecl>(Parents[1].get().Node.get<Decl>())); |
| }); |
| } |
| |
| TEST(ASTSelectionFinder, SelectVarDeclStmt) { |
| StringRef Source = R"( |
| void f() { |
| { |
| int a; |
| } |
| } |
| )"; |
| // 'int a' |
| findSelectedASTNodesWithRange( |
| Source, {4, 8}, FileRange{{4, 8}, {4, 14}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_TRUE(SelectedCode); |
| EXPECT_EQ(SelectedCode->size(), 1u); |
| EXPECT_TRUE(isa<DeclStmt>((*SelectedCode)[0])); |
| ArrayRef<SelectedASTNode::ReferenceType> Parents = |
| SelectedCode->getParents(); |
| EXPECT_EQ(Parents.size(), 4u); |
| EXPECT_TRUE( |
| isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>())); |
| // Function 'f' definition. |
| EXPECT_TRUE(isa<FunctionDecl>(Parents[1].get().Node.get<Decl>())); |
| // Function body of function 'F'. |
| EXPECT_TRUE(isa<CompoundStmt>(Parents[2].get().Node.get<Stmt>())); |
| // Compound statement in body of 'F'. |
| EXPECT_TRUE(isa<CompoundStmt>(Parents[3].get().Node.get<Stmt>())); |
| }); |
| } |
| |
| TEST(ASTSelectionFinder, SelectEntireDeclStmtRange) { |
| StringRef Source = R"( |
| void f(int x, int y) { |
| int a = x * y; |
| } |
| )"; |
| // 'int a = x * y' |
| findSelectedASTNodesWithRange( |
| Source, {3, 4}, FileRange{{3, 4}, {3, 17}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_TRUE(SelectedCode); |
| EXPECT_EQ(SelectedCode->size(), 1u); |
| EXPECT_TRUE(isa<DeclStmt>((*SelectedCode)[0])); |
| ArrayRef<SelectedASTNode::ReferenceType> Parents = |
| SelectedCode->getParents(); |
| EXPECT_EQ(Parents.size(), 3u); |
| EXPECT_TRUE( |
| isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>())); |
| // Function 'f' definition. |
| EXPECT_TRUE(isa<FunctionDecl>(Parents[1].get().Node.get<Decl>())); |
| // Function body of function 'F'. |
| EXPECT_TRUE(isa<CompoundStmt>(Parents[2].get().Node.get<Stmt>())); |
| }); |
| } |
| |
| TEST(ASTSelectionFinder, SelectEntireDeclStmtRangeWithMultipleDecls) { |
| StringRef Source = R"( |
| void f(int x, int y) { |
| int a = x * y, b = x - y; |
| } |
| )"; |
| // 'b = x - y' |
| findSelectedASTNodesWithRange( |
| Source, {3, 19}, FileRange{{3, 19}, {3, 28}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_TRUE(SelectedCode); |
| EXPECT_EQ(SelectedCode->size(), 1u); |
| EXPECT_TRUE(isa<DeclStmt>((*SelectedCode)[0])); |
| ArrayRef<SelectedASTNode::ReferenceType> Parents = |
| SelectedCode->getParents(); |
| EXPECT_EQ(Parents.size(), 3u); |
| EXPECT_TRUE( |
| isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>())); |
| // Function 'f' definition. |
| EXPECT_TRUE(isa<FunctionDecl>(Parents[1].get().Node.get<Decl>())); |
| // Function body of function 'F'. |
| EXPECT_TRUE(isa<CompoundStmt>(Parents[2].get().Node.get<Stmt>())); |
| }); |
| } |
| |
| TEST(ASTSelectionFinder, SimpleCodeRangeASTSelectionInObjCMethod) { |
| StringRef Source = R"(@interface I @end |
| @implementation I |
| - (void) f:(int)x with:(int) y { |
| int z = x; |
| [self f: 2 with: 3]; |
| if (x == 0) { |
| return; |
| } |
| x = 1; |
| return; |
| } |
| - (void)f2 { |
| int m = 0; |
| } |
| @end |
| )"; |
| // Range that spans multiple methods is an invalid code range. |
| findSelectedASTNodesWithRange( |
| Source, {9, 2}, FileRange{{9, 2}, {13, 1}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_FALSE(SelectedCode); |
| }, |
| SelectionFinderVisitor::Lang_OBJC); |
| // Just 'z = x;': |
| findSelectedASTNodesWithRange( |
| Source, {4, 2}, FileRange{{4, 2}, {4, 13}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_TRUE(SelectedCode); |
| EXPECT_EQ(SelectedCode->size(), 1u); |
| EXPECT_TRUE(isa<DeclStmt>((*SelectedCode)[0])); |
| ArrayRef<SelectedASTNode::ReferenceType> Parents = |
| SelectedCode->getParents(); |
| EXPECT_EQ(Parents.size(), 4u); |
| EXPECT_TRUE( |
| isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>())); |
| // 'I' @implementation. |
| EXPECT_TRUE(isa<ObjCImplDecl>(Parents[1].get().Node.get<Decl>())); |
| // Function 'f' definition. |
| EXPECT_TRUE(isa<ObjCMethodDecl>(Parents[2].get().Node.get<Decl>())); |
| // Function body of function 'F'. |
| EXPECT_TRUE(isa<CompoundStmt>(Parents[3].get().Node.get<Stmt>())); |
| }, |
| SelectionFinderVisitor::Lang_OBJC); |
| // From '[self f: 2 with: 3]' until just before 'x = 1;': |
| findSelectedASTNodesWithRange( |
| Source, {5, 2}, FileRange{{5, 2}, {9, 1}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_TRUE(SelectedCode); |
| EXPECT_EQ(SelectedCode->size(), 2u); |
| EXPECT_TRUE(isa<ObjCMessageExpr>((*SelectedCode)[0])); |
| EXPECT_TRUE(isa<IfStmt>((*SelectedCode)[1])); |
| ArrayRef<SelectedASTNode::ReferenceType> Parents = |
| SelectedCode->getParents(); |
| EXPECT_EQ(Parents.size(), 4u); |
| EXPECT_TRUE( |
| isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>())); |
| // 'I' @implementation. |
| EXPECT_TRUE(isa<ObjCImplDecl>(Parents[1].get().Node.get<Decl>())); |
| // Function 'f' definition. |
| EXPECT_TRUE(isa<ObjCMethodDecl>(Parents[2].get().Node.get<Decl>())); |
| // Function body of function 'F'. |
| EXPECT_TRUE(isa<CompoundStmt>(Parents[3].get().Node.get<Stmt>())); |
| }, |
| SelectionFinderVisitor::Lang_OBJC); |
| } |
| |
| TEST(ASTSelectionFinder, CanonicalizeObjCStringLiteral) { |
| StringRef Source = R"( |
| void foo() { |
| (void)@"test"; |
| } |
| )"; |
| // Just '"test"': |
| findSelectedASTNodesWithRange( |
| Source, {3, 10}, FileRange{{3, 10}, {3, 16}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_TRUE(SelectedCode); |
| EXPECT_EQ(SelectedCode->size(), 1u); |
| EXPECT_TRUE(isa<ObjCStringLiteral>((*SelectedCode)[0])); |
| }, |
| SelectionFinderVisitor::Lang_OBJC); |
| // Just 'test': |
| findSelectedASTNodesWithRange( |
| Source, {3, 11}, FileRange{{3, 11}, {3, 15}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_TRUE(SelectedCode); |
| EXPECT_EQ(SelectedCode->size(), 1u); |
| EXPECT_TRUE(isa<ObjCStringLiteral>((*SelectedCode)[0])); |
| }, |
| SelectionFinderVisitor::Lang_OBJC); |
| } |
| |
| TEST(ASTSelectionFinder, CanonicalizeMemberCalleeToCall) { |
| StringRef Source = R"( |
| class AClass { public: |
| void method(); |
| int afield; |
| void selectWholeCallWhenJustMethodSelected(int &i) { |
| method(); |
| } |
| }; |
| void selectWholeCallWhenJustMethodSelected() { |
| AClass a; |
| a.method(); |
| } |
| void dontSelectArgument(AClass &a) { |
| a.selectWholeCallWhenJustMethodSelected(a.afield); |
| } |
| )"; |
| // Just 'method' with implicit 'this': |
| findSelectedASTNodesWithRange( |
| Source, {6, 5}, FileRange{{6, 5}, {6, 11}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_TRUE(SelectedCode); |
| EXPECT_EQ(SelectedCode->size(), 1u); |
| EXPECT_TRUE(isa<CXXMemberCallExpr>((*SelectedCode)[0])); |
| }); |
| // Just 'method': |
| findSelectedASTNodesWithRange( |
| Source, {11, 5}, FileRange{{11, 5}, {11, 11}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_TRUE(SelectedCode); |
| EXPECT_EQ(SelectedCode->size(), 1u); |
| EXPECT_TRUE(isa<CXXMemberCallExpr>((*SelectedCode)[0])); |
| }); |
| // Just 'afield', which should not select the call. |
| findSelectedASTNodesWithRange( |
| Source, {14, 5}, FileRange{{14, 45}, {14, 51}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_TRUE(SelectedCode); |
| EXPECT_EQ(SelectedCode->size(), 1u); |
| EXPECT_FALSE(isa<CXXMemberCallExpr>((*SelectedCode)[0])); |
| }); |
| } |
| |
| TEST(ASTSelectionFinder, CanonicalizeFuncCalleeToCall) { |
| StringRef Source = R"( |
| void function(); |
| |
| void test() { |
| function(); |
| } |
| )"; |
| // Just 'function': |
| findSelectedASTNodesWithRange( |
| Source, {5, 3}, FileRange{{5, 3}, {5, 11}}, |
| [](SourceRange SelectionRange, Optional<SelectedASTNode> Node) { |
| EXPECT_TRUE(Node); |
| Node->dump(); |
| Optional<CodeRangeASTSelection> SelectedCode = |
| CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); |
| EXPECT_TRUE(SelectedCode); |
| EXPECT_EQ(SelectedCode->size(), 1u); |
| EXPECT_TRUE(isa<CallExpr>((*SelectedCode)[0])); |
| EXPECT_TRUE(isa<CompoundStmt>( |
| SelectedCode->getParents()[SelectedCode->getParents().size() - 1] |
| .get() |
| .Node.get<Stmt>())); |
| }); |
| } |
| |
| } // end anonymous namespace |