//===- unittest/Tooling/ASTSelectionTest.cpp ------------------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #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; public: Optional Selection; SelectionFinderVisitor(FileLocation Location, Optional SelectionRange) : Location(Location), SelectionRange(SelectionRange) {} 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); } Selection = findSelectedASTNodes(Context, SelRange); return false; } }; Optional findSelectedASTNodes(StringRef Source, FileLocation Location, Optional SelectionRange, SelectionFinderVisitor::Language Language = SelectionFinderVisitor::Lang_CXX11) { SelectionFinderVisitor Visitor(Location, SelectionRange); EXPECT_TRUE(Visitor.runOver(Source, Language)); return std::move(Visitor.Selection); } 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, typename std::enable_if::value, T>::type *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 = "", typename std::enable_if::value, T>::type *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) { Optional Node = findSelectedASTNodes(" void f() { }", {1, 1}, None); EXPECT_FALSE(Node); } TEST(ASTSelectionFinder, CursorAtStartOfFunction) { Optional Node = findSelectedASTNodes("void f() { }", {1, 1}, None); 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) { { Optional Node = findSelectedASTNodes( " void f() { }", {1, 1}, FileRange{{1, 1}, {1, 1}}); EXPECT_FALSE(Node); } { Optional Node = findSelectedASTNodes( " void f() { }", {1, 1}, FileRange{{1, 1}, {1, 2}}); EXPECT_FALSE(Node); } } TEST(ASTSelectionFinder, EmptyRangeFallbackToCursor) { Optional Node = findSelectedASTNodes("void f() { }", {1, 1}, FileRange{{1, 1}, {1, 1}}); 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 '}': { auto Node = findSelectedASTNodes(Source, {1, 1}, FileRange{{1, 1}, {2, 2}}); 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 '}': { auto Node = findSelectedASTNodes(Source, {2, 1}, FileRange{{1, 1}, {2, 1}}); 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 '}': { auto Node = findSelectedASTNodes(Source, {1, 14}, FileRange{{1, 14}, {2, 2}}); 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 '}': { auto Node = findSelectedASTNodes(Source, {2, 2}, FileRange{{1, 11}, {2, 2}}); 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: SelectedF1F2(findSelectedASTNodes(Source, {2, 2}, FileRange{{2, 2}, {5, 1}})); // Just before 'void' of f1 and just after '}' of f2: SelectedF1F2( findSelectedASTNodes(Source, {3, 1}, FileRange{{3, 1}, {4, 14}})); } 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;': { auto Node = findSelectedASTNodes(Source, {3, 2}, FileRange{{3, 2}, {7, 1}}); 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;': { auto Node = findSelectedASTNodes(Source, {3, 2}, FileRange{{3, 2}, {7, 8}}); 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;': { auto Node = findSelectedASTNodes(Source, {2, 10}, FileRange{{2, 10}, {7, 5}}); 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': { auto Node = findSelectedASTNodes(Source, {9, 10}, FileRange{{9, 10}, {9, 11}}, SelectionFinderVisitor::Lang_OBJC); 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); } // The entire 'catF': { auto Node = findSelectedASTNodes(Source, {15, 1}, FileRange{{15, 1}, {15, 16}}, SelectionFinderVisitor::Lang_OBJC); 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); } // From the line before 'selected' to the line after 'catF': { auto Node = findSelectedASTNodes(Source, {16, 1}, FileRange{{7, 1}, {16, 1}}, SelectionFinderVisitor::Lang_OBJC); 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); } // Just the 'outer' function: { auto Node = findSelectedASTNodes(Source, {19, 1}, FileRange{{19, 1}, {19, 25}}, SelectionFinderVisitor::Lang_OBJC); EXPECT_TRUE(Node); EXPECT_EQ(Node->Children.size(), 1u); checkNode(Node->Children[0], SourceSelectionKind::ContainsSelection, /*NumChildren=*/1, /*Name=*/"outerFunction"); } } TEST(ASTSelectionFinder, FunctionInObjCImplementationCarefulWithEarlyExit) { StringRef Source = R"( @interface I @end @implementation I void selected() { } - (void) method { } @end )"; // Just 'selected' { auto Node = findSelectedASTNodes(Source, {6, 1}, FileRange{{6, 1}, {7, 2}}, SelectionFinderVisitor::Lang_OBJC); 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"); } } TEST(ASTSelectionFinder, AvoidImplicitDeclarations) { StringRef Source = R"( struct Copy { int x; }; void foo() { Copy x; Copy y = x; } )"; // The entire struct 'Copy': auto Node = findSelectedASTNodes(Source, {2, 1}, FileRange{{2, 1}, {4, 3}}); 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); } } // end anonymous namespace