//===- 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; class SelectionFinderVisitor : public TestVisitor { FileLocation Location; Optional SelectionRange; llvm::function_ref)> Consumer; public: SelectionFinderVisitor(FileLocation Location, Optional SelectionRange, llvm::function_ref)> 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 SelectionRange, llvm::function_ref)> 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 SelectionRange, llvm::function_ref)> Consumer, SelectionFinderVisitor::Language Language = SelectionFinderVisitor::Lang_CXX11) { findSelectedASTNodesWithRange( Source, Location, SelectionRange, [&](SourceRange, Optional 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(); EXPECT_TRUE(!!ND); ASSERT_EQ(ND->getName(), Name); } template const SelectedASTNode &checkNode( const SelectedASTNode &StmtNode, SourceSelectionKind SelectionKind, unsigned NumChildren = 0, std::enable_if_t::value, T> *StmtOverloadChecker = nullptr) { checkNodeImpl(isa(StmtNode.Node.get()), StmtNode, SelectionKind, NumChildren); return StmtNode; } template const SelectedASTNode &checkNode( const SelectedASTNode &DeclNode, SourceSelectionKind SelectionKind, unsigned NumChildren = 0, StringRef Name = "", std::enable_if_t::value, T> *DeclOverloadChecker = nullptr) { checkNodeImpl(isa(DeclNode.Node.get()), 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 Node) { EXPECT_FALSE(Node); }); } TEST(ASTSelectionFinder, CursorAtStartOfFunction) { findSelectedASTNodes( "void f() { }", {1, 1}, None, [](Optional Node) { EXPECT_TRUE(Node); checkNode(*Node, SourceSelectionKind::None, /*NumChildren=*/1); checkNode(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 Node) { EXPECT_FALSE(Node); }); findSelectedASTNodes( " void f() { }", {1, 1}, FileRange{{1, 1}, {1, 2}}, [](Optional Node) { EXPECT_FALSE(Node); }); } TEST(ASTSelectionFinder, EmptyRangeFallbackToCursor) { findSelectedASTNodes("void f() { }", {1, 1}, FileRange{{1, 1}, {1, 1}}, [](Optional Node) { EXPECT_TRUE(Node); checkNode( 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 Node) { EXPECT_TRUE(Node); EXPECT_EQ(Node->Children.size(), 1u); const auto &Fn = checkNode( Node->Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/2, /*Name=*/"f"); checkNode(Fn.Children[0], SourceSelectionKind::InsideSelection); const auto &Body = checkNode( Fn.Children[1], SourceSelectionKind::InsideSelection, /*NumChildren=*/1); const auto &Return = checkNode( Body.Children[0], SourceSelectionKind::InsideSelection, /*NumChildren=*/1); checkNode(Return.Children[0], SourceSelectionKind::InsideSelection, /*NumChildren=*/1); checkNode(Return.Children[0].Children[0], SourceSelectionKind::InsideSelection); }); // From 'int' until just before '}': findSelectedASTNodes( Source, {2, 1}, FileRange{{1, 1}, {2, 1}}, [](Optional Node) { EXPECT_TRUE(Node); EXPECT_EQ(Node->Children.size(), 1u); const auto &Fn = checkNode( Node->Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/2, /*Name=*/"f"); const auto &Body = checkNode( Fn.Children[1], SourceSelectionKind::ContainsSelectionEnd, /*NumChildren=*/1); checkNode(Body.Children[0], SourceSelectionKind::InsideSelection, /*NumChildren=*/1); }); // From '{' until just after '}': findSelectedASTNodes( Source, {1, 14}, FileRange{{1, 14}, {2, 2}}, [](Optional Node) { EXPECT_TRUE(Node); EXPECT_EQ(Node->Children.size(), 1u); const auto &Fn = checkNode( Node->Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/1, /*Name=*/"f"); const auto &Body = checkNode( Fn.Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/1); checkNode(Body.Children[0], SourceSelectionKind::InsideSelection, /*NumChildren=*/1); }); // From 'x' until just after '}': findSelectedASTNodes( Source, {2, 2}, FileRange{{1, 11}, {2, 2}}, [](Optional Node) { EXPECT_TRUE(Node); EXPECT_EQ(Node->Children.size(), 1u); const auto &Fn = checkNode( Node->Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/2, /*Name=*/"f"); checkNode(Fn.Children[0], SourceSelectionKind::ContainsSelectionStart); const auto &Body = checkNode( Fn.Children[1], SourceSelectionKind::InsideSelection, /*NumChildren=*/1); checkNode(Body.Children[0], SourceSelectionKind::InsideSelection, /*NumChildren=*/1); }); } TEST(ASTSelectionFinder, MultipleFunctionSelection) { StringRef Source = R"(void f0() { } void f1() { } void f2() { } void f3() { } )"; auto SelectedF1F2 = [](Optional Node) { EXPECT_TRUE(Node); EXPECT_EQ(Node->Children.size(), 2u); checkNode(Node->Children[0], SourceSelectionKind::InsideSelection, /*NumChildren=*/1, /*Name=*/"f1"); checkNode(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 Node) { EXPECT_TRUE(Node); EXPECT_EQ(Node->Children.size(), 1u); const auto &Fn = checkNode( Node->Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/1, /*Name=*/"f"); const auto &Body = checkNode( Fn.Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/2); allChildrenOf(checkNode(Body.Children[0], SourceSelectionKind::InsideSelection, /*NumChildren=*/3)) .shouldHaveSelectionKind(SourceSelectionKind::InsideSelection); allChildrenOf(checkNode(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 Node) { EXPECT_TRUE(Node); EXPECT_EQ(Node->Children.size(), 1u); const auto &Fn = checkNode( Node->Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/1, /*Name=*/"f"); const auto &Body = checkNode( Fn.Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/3); checkNode(Body.Children[0], SourceSelectionKind::InsideSelection, /*NumChildren=*/3); checkNode(Body.Children[1], SourceSelectionKind::InsideSelection, /*NumChildren=*/2); checkNode(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 Node) { EXPECT_TRUE(Node); EXPECT_EQ(Node->Children.size(), 1u); const auto &Fn = checkNode( Node->Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/1, /*Name=*/"f"); const auto &Body = checkNode( Fn.Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/4); checkNode(Body.Children[0], SourceSelectionKind::ContainsSelectionStart, /*NumChildren=*/1); checkNode(Body.Children[1], SourceSelectionKind::InsideSelection, /*NumChildren=*/3); checkNode(Body.Children[2], SourceSelectionKind::InsideSelection, /*NumChildren=*/2); checkNode(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 Node) { EXPECT_TRUE(Node); EXPECT_EQ(Node->Children.size(), 1u); const auto &Impl = checkNode( Node->Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/1, /*Name=*/"I"); const auto &Fn = checkNode( 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 Node) { EXPECT_TRUE(Node); EXPECT_EQ(Node->Children.size(), 1u); const auto &Impl = checkNode( Node->Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/1, /*Name=*/"Cat"); const auto &Fn = checkNode( 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 Node) { EXPECT_TRUE(Node); EXPECT_EQ(Node->Children.size(), 2u); const auto &Impl = checkNode( Node->Children[0], SourceSelectionKind::ContainsSelectionStart, /*NumChildren=*/1, /*Name=*/"I"); const auto &Selected = checkNode( Impl.Children[0], SourceSelectionKind::InsideSelection, /*NumChildren=*/2, /*Name=*/"selected"); allChildrenOf(Selected).shouldHaveSelectionKind( SourceSelectionKind::InsideSelection); const auto &Cat = checkNode( Node->Children[1], SourceSelectionKind::ContainsSelectionEnd, /*NumChildren=*/1, /*Name=*/"Cat"); const auto &CatF = checkNode( 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 Node) { EXPECT_TRUE(Node); EXPECT_EQ(Node->Children.size(), 1u); checkNode( 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 Node) { EXPECT_TRUE(Node); EXPECT_EQ(Node->Children.size(), 1u); const auto &Impl = checkNode( Node->Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/1, /*Name=*/"I"); checkNode(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 Node) { EXPECT_TRUE(Node); EXPECT_EQ(Node->Children.size(), 1u); const auto &Record = checkNode( Node->Children[0], SourceSelectionKind::InsideSelection, /*NumChildren=*/1, /*Name=*/"Copy"); checkNode(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 Node) { EXPECT_TRUE(Node); EXPECT_EQ(Node->Children.size(), 1u); checkNode( Node->Children[0], SourceSelectionKind::ContainsSelection); }, SelectionFinderVisitor::Lang_OBJC); } const SelectedASTNode &checkFnBody(const Optional &Node, StringRef Name) { EXPECT_TRUE(Node); EXPECT_EQ(Node->Children.size(), 1u); const auto &Fn = checkNode( Node->Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/1, Name); return checkNode(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 Node) { const auto &CS = checkFnBody(Node, /*Name=*/"selectProp"); const auto &CCast = checkNode( CS.Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/1); const auto &POE = checkNode( CCast.Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/1); const auto &PRE = checkNode( POE.Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/1); const auto &Cast = checkNode( PRE.Children[0], SourceSelectionKind::InsideSelection, /*NumChildren=*/1); checkNode(Cast.Children[0], SourceSelectionKind::InsideSelection); }, SelectionFinderVisitor::Lang_OBJC); // Just 'i.prop = 21' findSelectedASTNodes( Source, {7, 1}, FileRange{{7, 1}, {7, 12}}, [](Optional Node) { const auto &CS = checkFnBody(Node, /*Name=*/"selectProp"); const auto &POE = checkNode( CS.Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/1); const auto &BinOp = checkNode( POE.Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/2); const auto &PRE = checkNode( BinOp.Children[0], SourceSelectionKind::InsideSelection, /*NumChildren=*/1); const auto &Cast = checkNode( PRE.Children[0], SourceSelectionKind::InsideSelection, /*NumChildren=*/1); checkNode(Cast.Children[0], SourceSelectionKind::InsideSelection); checkNode(BinOp.Children[1], SourceSelectionKind::InsideSelection); }, SelectionFinderVisitor::Lang_OBJC); // Just 'array[10]' findSelectedASTNodes( Source, {17, 9}, FileRange{{17, 9}, {17, 18}}, [](Optional Node) { const auto &CS = checkFnBody(Node, /*Name=*/"selectSubscript"); const auto &CCast = checkNode( CS.Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/1); const auto &POE = checkNode( CCast.Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/1); const auto &SRE = checkNode( POE.Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/2); const auto &Cast = checkNode( SRE.Children[0], SourceSelectionKind::InsideSelection, /*NumChildren=*/1); checkNode(Cast.Children[0], SourceSelectionKind::InsideSelection); checkNode(SRE.Children[1], SourceSelectionKind::InsideSelection); }, SelectionFinderVisitor::Lang_OBJC); // Just 'array[i.prop] = array' findSelectedASTNodes( Source, {18, 3}, FileRange{{18, 3}, {18, 20}}, [](Optional Node) { const auto &CS = checkFnBody(Node, /*Name=*/"selectSubscript"); const auto &POE = checkNode( CS.Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/1); const auto &BinOp = checkNode( POE.Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/2); const auto &SRE = checkNode( BinOp.Children[0], SourceSelectionKind::InsideSelection, /*NumChildren=*/2); const auto &Cast = checkNode( SRE.Children[0], SourceSelectionKind::InsideSelection, /*NumChildren=*/1); checkNode(Cast.Children[0], SourceSelectionKind::InsideSelection); const auto &POE2 = checkNode( SRE.Children[1], SourceSelectionKind::InsideSelection, /*NumChildren=*/1); const auto &PRE = checkNode( POE2.Children[0], SourceSelectionKind::InsideSelection, /*NumChildren=*/1); const auto &Cast2 = checkNode( PRE.Children[0], SourceSelectionKind::InsideSelection, /*NumChildren=*/1); checkNode(Cast2.Children[0], SourceSelectionKind::InsideSelection); checkNode(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 Node) { EXPECT_TRUE(Node); Optional SelectedCode = CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); EXPECT_FALSE(SelectedCode); }); findSelectedASTNodesWithRange( Source, {2, 2}, FileRange{{2, 2}, {2, 2}}, [](SourceRange SelectionRange, Optional Node) { EXPECT_TRUE(Node); Optional 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 Node) { EXPECT_TRUE(Node); Optional 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 Node) { EXPECT_TRUE(Node); Optional SelectedCode = CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); EXPECT_TRUE(SelectedCode); EXPECT_EQ(SelectedCode->size(), 1u); EXPECT_TRUE(isa((*SelectedCode)[0])); ArrayRef Parents = SelectedCode->getParents(); EXPECT_EQ(Parents.size(), 3u); EXPECT_TRUE( isa(Parents[0].get().Node.get())); // Function 'f' definition. EXPECT_TRUE(isa(Parents[1].get().Node.get())); // Function body of function 'F'. EXPECT_TRUE(isa(Parents[2].get().Node.get())); }); // From 'f(2,3)' until just before 'x = 1;': findSelectedASTNodesWithRange( Source, {3, 2}, FileRange{{3, 2}, {7, 1}}, [](SourceRange SelectionRange, Optional Node) { EXPECT_TRUE(Node); Optional SelectedCode = CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); EXPECT_TRUE(SelectedCode); EXPECT_EQ(SelectedCode->size(), 2u); EXPECT_TRUE(isa((*SelectedCode)[0])); EXPECT_TRUE(isa((*SelectedCode)[1])); ArrayRef Parents = SelectedCode->getParents(); EXPECT_EQ(Parents.size(), 3u); EXPECT_TRUE( isa(Parents[0].get().Node.get())); // Function 'f' definition. EXPECT_TRUE(isa(Parents[1].get().Node.get())); // Function body of function 'F'. EXPECT_TRUE(isa(Parents[2].get().Node.get())); }); // From 'f(2,3)' until just before ';' in 'x = 1;': findSelectedASTNodesWithRange( Source, {3, 2}, FileRange{{3, 2}, {7, 8}}, [](SourceRange SelectionRange, Optional Node) { EXPECT_TRUE(Node); Optional SelectedCode = CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); EXPECT_TRUE(SelectedCode); EXPECT_EQ(SelectedCode->size(), 3u); EXPECT_TRUE(isa((*SelectedCode)[0])); EXPECT_TRUE(isa((*SelectedCode)[1])); EXPECT_TRUE(isa((*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 Node) { EXPECT_TRUE(Node); EXPECT_TRUE(Node); Optional SelectedCode = CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); EXPECT_TRUE(SelectedCode); EXPECT_EQ(SelectedCode->size(), 4u); EXPECT_TRUE(isa((*SelectedCode)[0])); EXPECT_TRUE(isa((*SelectedCode)[1])); EXPECT_TRUE(isa((*SelectedCode)[2])); EXPECT_TRUE(isa((*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 Node) { EXPECT_TRUE(Node); Optional SelectedCode = CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); EXPECT_TRUE(SelectedCode); EXPECT_EQ(SelectedCode->size(), 1u); EXPECT_TRUE(isa((*SelectedCode)[0])); ArrayRef Parents = SelectedCode->getParents(); EXPECT_EQ(Parents.size(), 2u); EXPECT_TRUE( isa(Parents[0].get().Node.get())); // Variable 'codeRange'. EXPECT_TRUE(isa(Parents[1].get().Node.get())); }); } TEST(ASTSelectionFinder, SelectVarDeclStmt) { StringRef Source = R"( void f() { { int a; } } )"; // 'int a' findSelectedASTNodesWithRange( Source, {4, 8}, FileRange{{4, 8}, {4, 14}}, [](SourceRange SelectionRange, Optional Node) { EXPECT_TRUE(Node); Optional SelectedCode = CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); EXPECT_TRUE(SelectedCode); EXPECT_EQ(SelectedCode->size(), 1u); EXPECT_TRUE(isa((*SelectedCode)[0])); ArrayRef Parents = SelectedCode->getParents(); EXPECT_EQ(Parents.size(), 4u); EXPECT_TRUE( isa(Parents[0].get().Node.get())); // Function 'f' definition. EXPECT_TRUE(isa(Parents[1].get().Node.get())); // Function body of function 'F'. EXPECT_TRUE(isa(Parents[2].get().Node.get())); // Compound statement in body of 'F'. EXPECT_TRUE(isa(Parents[3].get().Node.get())); }); } 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 Node) { EXPECT_TRUE(Node); Optional SelectedCode = CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); EXPECT_TRUE(SelectedCode); EXPECT_EQ(SelectedCode->size(), 1u); EXPECT_TRUE(isa((*SelectedCode)[0])); ArrayRef Parents = SelectedCode->getParents(); EXPECT_EQ(Parents.size(), 3u); EXPECT_TRUE( isa(Parents[0].get().Node.get())); // Function 'f' definition. EXPECT_TRUE(isa(Parents[1].get().Node.get())); // Function body of function 'F'. EXPECT_TRUE(isa(Parents[2].get().Node.get())); }); } 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 Node) { EXPECT_TRUE(Node); Optional SelectedCode = CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); EXPECT_TRUE(SelectedCode); EXPECT_EQ(SelectedCode->size(), 1u); EXPECT_TRUE(isa((*SelectedCode)[0])); ArrayRef Parents = SelectedCode->getParents(); EXPECT_EQ(Parents.size(), 3u); EXPECT_TRUE( isa(Parents[0].get().Node.get())); // Function 'f' definition. EXPECT_TRUE(isa(Parents[1].get().Node.get())); // Function body of function 'F'. EXPECT_TRUE(isa(Parents[2].get().Node.get())); }); } 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 Node) { EXPECT_TRUE(Node); Optional 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 Node) { EXPECT_TRUE(Node); Optional SelectedCode = CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); EXPECT_TRUE(SelectedCode); EXPECT_EQ(SelectedCode->size(), 1u); EXPECT_TRUE(isa((*SelectedCode)[0])); ArrayRef Parents = SelectedCode->getParents(); EXPECT_EQ(Parents.size(), 4u); EXPECT_TRUE( isa(Parents[0].get().Node.get())); // 'I' @implementation. EXPECT_TRUE(isa(Parents[1].get().Node.get())); // Function 'f' definition. EXPECT_TRUE(isa(Parents[2].get().Node.get())); // Function body of function 'F'. EXPECT_TRUE(isa(Parents[3].get().Node.get())); }, 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 Node) { EXPECT_TRUE(Node); Optional SelectedCode = CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); EXPECT_TRUE(SelectedCode); EXPECT_EQ(SelectedCode->size(), 2u); EXPECT_TRUE(isa((*SelectedCode)[0])); EXPECT_TRUE(isa((*SelectedCode)[1])); ArrayRef Parents = SelectedCode->getParents(); EXPECT_EQ(Parents.size(), 4u); EXPECT_TRUE( isa(Parents[0].get().Node.get())); // 'I' @implementation. EXPECT_TRUE(isa(Parents[1].get().Node.get())); // Function 'f' definition. EXPECT_TRUE(isa(Parents[2].get().Node.get())); // Function body of function 'F'. EXPECT_TRUE(isa(Parents[3].get().Node.get())); }, 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 Node) { EXPECT_TRUE(Node); Optional SelectedCode = CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); EXPECT_TRUE(SelectedCode); EXPECT_EQ(SelectedCode->size(), 1u); EXPECT_TRUE(isa((*SelectedCode)[0])); }, SelectionFinderVisitor::Lang_OBJC); // Just 'test': findSelectedASTNodesWithRange( Source, {3, 11}, FileRange{{3, 11}, {3, 15}}, [](SourceRange SelectionRange, Optional Node) { EXPECT_TRUE(Node); Optional SelectedCode = CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); EXPECT_TRUE(SelectedCode); EXPECT_EQ(SelectedCode->size(), 1u); EXPECT_TRUE(isa((*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 Node) { EXPECT_TRUE(Node); Optional SelectedCode = CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); EXPECT_TRUE(SelectedCode); EXPECT_EQ(SelectedCode->size(), 1u); EXPECT_TRUE(isa((*SelectedCode)[0])); }); // Just 'method': findSelectedASTNodesWithRange( Source, {11, 5}, FileRange{{11, 5}, {11, 11}}, [](SourceRange SelectionRange, Optional Node) { EXPECT_TRUE(Node); Optional SelectedCode = CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); EXPECT_TRUE(SelectedCode); EXPECT_EQ(SelectedCode->size(), 1u); EXPECT_TRUE(isa((*SelectedCode)[0])); }); // Just 'afield', which should not select the call. findSelectedASTNodesWithRange( Source, {14, 5}, FileRange{{14, 45}, {14, 51}}, [](SourceRange SelectionRange, Optional Node) { EXPECT_TRUE(Node); Optional SelectedCode = CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); EXPECT_TRUE(SelectedCode); EXPECT_EQ(SelectedCode->size(), 1u); EXPECT_FALSE(isa((*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 Node) { EXPECT_TRUE(Node); Node->dump(); Optional SelectedCode = CodeRangeASTSelection::create(SelectionRange, std::move(*Node)); EXPECT_TRUE(SelectedCode); EXPECT_EQ(SelectedCode->size(), 1u); EXPECT_TRUE(isa((*SelectedCode)[0])); EXPECT_TRUE(isa( SelectedCode->getParents()[SelectedCode->getParents().size() - 1] .get() .Node.get())); }); } } // end anonymous namespace