diff options
Diffstat (limited to 'clang/unittests/Tooling/ASTSelectionTest.cpp')
-rw-r--r-- | clang/unittests/Tooling/ASTSelectionTest.cpp | 484 |
1 files changed, 484 insertions, 0 deletions
diff --git a/clang/unittests/Tooling/ASTSelectionTest.cpp b/clang/unittests/Tooling/ASTSelectionTest.cpp new file mode 100644 index 00000000000..b4e8bc042b8 --- /dev/null +++ b/clang/unittests/Tooling/ASTSelectionTest.cpp @@ -0,0 +1,484 @@ +//===- 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<FileLocation, FileLocation>; + +class SelectionFinderVisitor : public TestVisitor<SelectionFinderVisitor> { + FileLocation Location; + Optional<FileRange> SelectionRange; + +public: + Optional<SelectedASTNode> Selection; + + SelectionFinderVisitor(FileLocation Location, + Optional<FileRange> 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<SelectedASTNode> +findSelectedASTNodes(StringRef Source, FileLocation Location, + Optional<FileRange> 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<NamedDecl>(); + EXPECT_TRUE(!!ND); + ASSERT_EQ(ND->getName(), Name); +} + +template <typename T> +const SelectedASTNode & +checkNode(const SelectedASTNode &StmtNode, SourceSelectionKind SelectionKind, + unsigned NumChildren = 0, + typename std::enable_if<std::is_base_of<Stmt, T>::value, T>::type + *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 = "", + typename std::enable_if<std::is_base_of<Decl, T>::value, T>::type + *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) { + Optional<SelectedASTNode> Node = + findSelectedASTNodes(" void f() { }", {1, 1}, None); + EXPECT_FALSE(Node); +} + +TEST(ASTSelectionFinder, CursorAtStartOfFunction) { + Optional<SelectedASTNode> Node = + findSelectedASTNodes("void f() { }", {1, 1}, None); + 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) { + { + Optional<SelectedASTNode> Node = findSelectedASTNodes( + " void f() { }", {1, 1}, FileRange{{1, 1}, {1, 1}}); + EXPECT_FALSE(Node); + } + { + Optional<SelectedASTNode> Node = findSelectedASTNodes( + " void f() { }", {1, 1}, FileRange{{1, 1}, {1, 2}}); + EXPECT_FALSE(Node); + } +} + +TEST(ASTSelectionFinder, EmptyRangeFallbackToCursor) { + Optional<SelectedASTNode> Node = + findSelectedASTNodes("void f() { }", {1, 1}, FileRange{{1, 1}, {1, 1}}); + 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 '}': + { + auto Node = findSelectedASTNodes(Source, {1, 1}, FileRange{{1, 1}, {2, 2}}); + 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 '}': + { + auto Node = findSelectedASTNodes(Source, {2, 1}, FileRange{{1, 1}, {2, 1}}); + 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 '}': + { + auto Node = + findSelectedASTNodes(Source, {1, 14}, FileRange{{1, 14}, {2, 2}}); + 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 '}': + { + auto Node = + findSelectedASTNodes(Source, {2, 2}, FileRange{{1, 11}, {2, 2}}); + 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: + 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<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;': + { + auto Node = findSelectedASTNodes(Source, {3, 2}, FileRange{{3, 2}, {7, 8}}); + 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;': + { + auto Node = + findSelectedASTNodes(Source, {2, 10}, FileRange{{2, 10}, {7, 5}}); + 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': + { + 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<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); + } + // 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<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); + } + // 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<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); + } + // 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<FunctionDecl>(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<ObjCImplementationDecl>( + Node->Children[0], SourceSelectionKind::ContainsSelection, + /*NumChildren=*/1, /*Name=*/"I"); + checkNode<FunctionDecl>(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<CXXRecordDecl>( + Node->Children[0], SourceSelectionKind::InsideSelection, + /*NumChildren=*/1, /*Name=*/"Copy"); + checkNode<FieldDecl>(Record.Children[0], + SourceSelectionKind::InsideSelection); +} + +} // end anonymous namespace |