Index: include/clang/Tooling/Refactoring/ASTSelection.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/ASTSelection.h @@ -0,0 +1,79 @@ +//===--- ASTSelection.h - Clang refactoring library -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_AST_SELECTION_H +#define LLVM_CLANG_TOOLING_REFACTOR_AST_SELECTION_H + +#include "clang/AST/ASTTypeTraits.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include + +namespace clang { + +class ASTContext; + +namespace tooling { + +enum class SourceSelectionKind { + /// A node that's not selected. + None, + + /// A node that's considered to be selected because the selection point is + /// inside of its source range. + ContainsSelectionPoint, + + /// A node that's considered to be selected because the whole selection range + /// is inside of its source range. + ContainsSelection, + /// A node that's considered to be selected because the start of the selection + /// range is inside its source range. + ContainsSelectionStart, + /// A node that's considered to be selected because the end of the selection + /// range is inside its source range. + ContainsSelectionEnd, + + /// A node that's considered to be selected because the node is entirely in + /// the selection range. + InsideSelection, +}; + +/// Represents a selected AST node. +/// +/// AST selection is represented using a tree of \c SelectedASTNode. The tree +/// follows the top-down shape of the actual AST. Each selected node has +/// a selection kind. The kind might be none as the node itself might not +/// actually be selected, e.g. a statement in macro whose child is in a macro +/// argument. +struct SelectedASTNode { + ast_type_traits::DynTypedNode Node; + SourceSelectionKind SelectionKind; + std::vector Children; + + SelectedASTNode(const ast_type_traits::DynTypedNode &Node, + SourceSelectionKind SelectionKind) + : Node(Node), SelectionKind(SelectionKind) {} + SelectedASTNode(SelectedASTNode &&) = default; + SelectedASTNode &operator=(SelectedASTNode &&) = default; + + void dump(llvm::raw_ostream &OS = llvm::errs()) const; +}; + +/// Traverses the given ASTContext and creates a tree of selected AST nodes. +/// +/// \returns None if no nodes are selected in the AST, or a selected AST node +/// that corresponds to the TranslationUnitDecl otherwise. +Optional findSelectedASTNodes(const ASTContext &Context, + SourceLocation Location, + SourceRange SelectionRange); + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_AST_SELECTION_H Index: lib/Tooling/Refactoring/ASTSelection.cpp =================================================================== --- /dev/null +++ lib/Tooling/Refactoring/ASTSelection.cpp @@ -0,0 +1,236 @@ +//===--- ASTSelection.cpp - Clang refactoring library ---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/ASTSelection.h" +#include "SourceLocationUtils.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Lex/Lexer.h" + +using namespace clang; +using namespace tooling; +using ast_type_traits::DynTypedNode; + +namespace { + +/// Constructs the tree of selected AST nodes that either contain the location +/// of the cursor or overlap with the selection range. +class ASTSelectionFinder : public RecursiveASTVisitor { + const SourceLocation Location; + const SourceRange SelectionRange; + const ASTContext &Context; + std::vector SelectionStack; + + SourceSelectionKind selectionKindFor(CharSourceRange Range) { + SourceLocation End = Range.getEnd(); + const SourceManager &SM = Context.getSourceManager(); + if (Range.isTokenRange()) + End = Lexer::getLocForEndOfToken(End, 0, SM, Context.getLangOpts()); + if (!isPairOfFileLocations(Range.getBegin(), End)) + return SourceSelectionKind::None; + if (!SelectionRange.isValid()) { + if (isPointWithin(Location, Range.getBegin(), End, SM)) + return SourceSelectionKind::ContainsSelectionPoint; + return SourceSelectionKind::None; + } + bool HasStart = + isPointWithin(SelectionRange.getBegin(), Range.getBegin(), End, SM); + bool HasEnd = + isPointWithin(SelectionRange.getEnd(), Range.getBegin(), End, SM); + if (HasStart && HasEnd) + return SourceSelectionKind::ContainsSelection; + if (isPointWithin(Range.getBegin(), SelectionRange.getBegin(), + SelectionRange.getEnd(), SM) && + isPointWithin(End, SelectionRange.getBegin(), SelectionRange.getEnd(), + SM)) + return SourceSelectionKind::InsideSelection; + // Ensure there's at least some overlap with the 'start'/'end' selection + // types. + if (HasStart && SelectionRange.getBegin() != End) + return SourceSelectionKind::ContainsSelectionStart; + if (HasEnd && SelectionRange.getEnd() != Range.getBegin()) + return SourceSelectionKind::ContainsSelectionEnd; + + return SourceSelectionKind::None; + } + +public: + ASTSelectionFinder(SourceLocation Location, SourceRange SelectionRange, + const ASTContext &Context) + : Location(Location), SelectionRange(SelectionRange), Context(Context) { + // The TU decl is the root of the selected node tree. + SelectionStack.push_back( + SelectedASTNode(DynTypedNode::create(*Context.getTranslationUnitDecl()), + SourceSelectionKind::None)); + } + + unsigned getNumTopLevelMatches() const { + return SelectionStack[0].Children.size(); + } + + Optional getResult() { + assert(SelectionStack.size() == 1 && "stack was not popped"); + SelectedASTNode Result = std::move(SelectionStack.back()); + SelectionStack.pop_back(); + if (Result.Children.empty()) + return None; + return Result; + } + + bool TraverseDecl(Decl *D) { + if (!D) + return true; + if (D->isImplicit()) + return RecursiveASTVisitor::TraverseDecl(D); + // TODO (Alex): Add location adjustment for ObjCImplDecls. + SourceSelectionKind SelectionKind = + selectionKindFor(CharSourceRange::getTokenRange(D->getSourceRange())); + SelectionStack.push_back( + SelectedASTNode(DynTypedNode::create(*D), SelectionKind)); + RecursiveASTVisitor::TraverseDecl(D); + SelectedASTNode Node = std::move(SelectionStack.back()); + SelectionStack.pop_back(); + if (SelectionKind != SourceSelectionKind::None || !Node.Children.empty()) + SelectionStack.back().Children.push_back(std::move(Node)); + return true; + } + + void TraverseDeclInPrevious(Decl *D) { + assert(!SelectionStack.back().Children.empty() && + "No previous declaration"); + SelectedASTNode &Previous = SelectionStack.back().Children.back(); + SelectionStack.push_back( + SelectedASTNode(Previous.Node, Previous.SelectionKind)); + TraverseDecl(D); + std::vector Children = + std::move(SelectionStack.back().Children); + SelectionStack.pop_back(); + for (auto &&Child : Children) + Previous.Children.push_back(std::move(Child)); + } + + bool TraverseStmt(Stmt *S) { + if (!S) + return true; + // TODO (Alex): Improve handling for macro locations. + SourceSelectionKind SelectionKind = + selectionKindFor(CharSourceRange::getTokenRange(S->getSourceRange())); + SelectionStack.push_back( + SelectedASTNode(DynTypedNode::create(*S), SelectionKind)); + RecursiveASTVisitor::TraverseStmt(S); + SelectedASTNode Node = std::move(SelectionStack.back()); + SelectionStack.pop_back(); + if (SelectionKind != SourceSelectionKind::None || !Node.Children.empty()) + SelectionStack.back().Children.push_back(std::move(Node)); + return true; + } +}; + +} // end anonymous namespace + +Optional +clang::tooling::findSelectedASTNodes(const ASTContext &Context, + SourceLocation Location, + SourceRange SelectionRange) { + assert(Location.isValid() && Location.isFileID() && + "Expected a file location"); + FileID TargetFile = Context.getSourceManager().getFileID(Location); + if (SelectionRange.isValid()) { + assert(isPairOfFileLocations(SelectionRange.getBegin(), + SelectionRange.getEnd()) && + "Expected a file range"); + assert(Context.getSourceManager().getFileID(SelectionRange.getBegin()) == + TargetFile && + Context.getSourceManager().getFileID(SelectionRange.getEnd()) == + TargetFile && + "location and selection range must be in the same file"); + // Ignore empty selection ranges. + if (SelectionRange.getBegin() == SelectionRange.getEnd()) { + assert(Location == SelectionRange.getBegin() && + "invalid cursor location"); + SelectionRange = SourceRange(); + } + } + + ASTSelectionFinder Visitor(Location, SelectionRange, Context); + const SourceManager &SM = Context.getSourceManager(); + SourceLocation ObjCImplEndLoc; + unsigned NumMatches = 0; + for (Decl *D : Context.getTranslationUnitDecl()->decls()) { + if (ObjCImplEndLoc.isValid() && + !SM.isBeforeInTranslationUnit(D->getLocStart(), ObjCImplEndLoc)) + ObjCImplEndLoc = SourceLocation(); + + // Check if this declaration is written in the file of interest. + const SourceRange DeclRange = D->getSourceRange(); + SourceLocation FileLoc; + if (DeclRange.getBegin().isMacroID() && !DeclRange.getEnd().isMacroID()) + FileLoc = DeclRange.getEnd(); + else + FileLoc = SM.getSpellingLoc(DeclRange.getBegin()); + if (SM.getFileID(FileLoc) == TargetFile) { + if (ObjCImplEndLoc.isValid()) + Visitor.TraverseDeclInPrevious(D); + else + Visitor.TraverseDecl(D); + } + + unsigned PrevNumMatches = NumMatches; + NumMatches = Visitor.getNumTopLevelMatches(); + // Objective-C @implementation declarations might have trailing declarations + // that are written in the @implementation, but stored outside of it in the + // AST. Pretend that we are still traversing the ObjCImplDecl until we + // reach a declaration that's outside of the @implementation. + if (NumMatches != PrevNumMatches && isa(D)) { + ObjCImplEndLoc = DeclRange.getEnd(); + continue; + } + if (ObjCImplEndLoc.isInvalid() && PrevNumMatches && + PrevNumMatches == NumMatches) { + // Looks like all possible matches have been found. The traversal can be + // stopped. + break; + } + } + return Visitor.getResult(); +} + +static const char *selectionKindToString(SourceSelectionKind Kind) { + switch (Kind) { + case SourceSelectionKind::None: + return "none"; + case SourceSelectionKind::ContainsSelectionPoint: + return "contains-selection-point"; + case SourceSelectionKind::ContainsSelection: + return "contains-selection"; + case SourceSelectionKind::ContainsSelectionStart: + return "contains-selection-start"; + case SourceSelectionKind::ContainsSelectionEnd: + return "contains-selection-end"; + case SourceSelectionKind::InsideSelection: + return "inside"; + } + llvm_unreachable("invalid selection kind"); +} + +static void dump(const SelectedASTNode &Node, llvm::raw_ostream &OS, + unsigned Indent = 0) { + OS.indent(Indent * 2); + if (const Decl *D = Node.Node.get()) { + OS << D->getDeclKindName() << "Decl"; + if (const auto *ND = dyn_cast(D)) + OS << " \"" << ND->getNameAsString() << '"'; + } else if (const Stmt *S = Node.Node.get()) { + OS << S->getStmtClassName(); + } + OS << ' ' << selectionKindToString(Node.SelectionKind) << "\n"; + for (const auto &Child : Node.Children) + dump(Child, OS, Indent + 1); +} + +void SelectedASTNode::dump(llvm::raw_ostream &OS) const { ::dump(*this, OS); } Index: lib/Tooling/Refactoring/CMakeLists.txt =================================================================== --- lib/Tooling/Refactoring/CMakeLists.txt +++ lib/Tooling/Refactoring/CMakeLists.txt @@ -4,6 +4,7 @@ ) add_clang_library(clangToolingRefactor + ASTSelection.cpp AtomicChange.cpp Rename/RenamingAction.cpp Rename/USRFinder.cpp Index: lib/Tooling/Refactoring/SourceLocationUtils.h =================================================================== --- /dev/null +++ lib/Tooling/Refactoring/SourceLocationUtils.h @@ -0,0 +1,38 @@ +//===--- SourceLocationUtils.h - Source location helper functions ---------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_TOOLING_REFACTOR_SOURCE_LOCATION_UTILS_H +#define LLVM_CLANG_LIB_TOOLING_REFACTOR_SOURCE_LOCATION_UTILS_H + +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceManager.h" + +namespace clang { + +class Stmt; +class LangOptions; + +namespace tooling { + +inline bool isPairOfFileLocations(SourceLocation Start, SourceLocation End) { + return Start.isValid() && Start.isFileID() && End.isValid() && End.isFileID(); +} + +/// Return true if the Point is within Start and End. +inline bool isPointWithin(SourceLocation Location, SourceLocation Start, + SourceLocation End, const SourceManager &SM) { + return Location == Start || Location == End || + (SM.isBeforeInTranslationUnit(Start, Location) && + SM.isBeforeInTranslationUnit(Location, End)); +} + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_LIB_TOOLING_REFACTOR_SOURCE_LOCATION_UTILS_H Index: unittests/Tooling/ASTSelectionTest.cpp =================================================================== --- /dev/null +++ unittests/Tooling/ASTSelectionTest.cpp @@ -0,0 +1,453 @@ +//===- 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)); + } + Selection = findSelectedASTNodes(Context, Location.translate(SM), 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::ContainsSelectionPoint, + /*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-point\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::ContainsSelectionPoint, + /*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, 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 Index: unittests/Tooling/CMakeLists.txt =================================================================== --- unittests/Tooling/CMakeLists.txt +++ unittests/Tooling/CMakeLists.txt @@ -11,6 +11,7 @@ endif() add_clang_unittest(ToolingTests + ASTSelectionTest.cpp CastExprTest.cpp CommentHandlerTest.cpp CompilationDatabaseTest.cpp