1085 lines
		
	
	
		
			42 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			1085 lines
		
	
	
		
			42 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===- 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
 |