aboutsummaryrefslogtreecommitdiff
path: root/unittests/Tooling
diff options
context:
space:
mode:
authorDimitry Andric <dim@FreeBSD.org>2017-12-18 20:11:37 +0000
committerDimitry Andric <dim@FreeBSD.org>2017-12-18 20:11:37 +0000
commit461a67fa15370a9ec88f8f8a240bf7c123bb2029 (patch)
tree6942083d7d56bba40ec790a453ca58ad3baf6832 /unittests/Tooling
parent75c3240472ba6ac2669ee72ca67eb72d4e2851fc (diff)
Vendor import of clang trunk r321017:vendor/clang/clang-trunk-r321017
Notes
Notes: svn path=/vendor/clang/dist/; revision=326941 svn path=/vendor/clang/clang-trunk-r321017/; revision=326942; tag=vendor/clang/clang-trunk-r321017
Diffstat (limited to 'unittests/Tooling')
-rw-r--r--unittests/Tooling/ASTSelectionTest.cpp1085
-rw-r--r--unittests/Tooling/CMakeLists.txt5
-rw-r--r--unittests/Tooling/ExecutionTest.cpp221
-rw-r--r--unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp227
-rw-r--r--unittests/Tooling/QualTypeNamesTest.cpp2
-rw-r--r--unittests/Tooling/RefactoringActionRulesTest.cpp248
-rw-r--r--unittests/Tooling/RefactoringTest.cpp422
-rw-r--r--unittests/Tooling/TestVisitor.h5
-rw-r--r--unittests/Tooling/ToolingTest.cpp57
9 files changed, 2270 insertions, 2 deletions
diff --git a/unittests/Tooling/ASTSelectionTest.cpp b/unittests/Tooling/ASTSelectionTest.cpp
new file mode 100644
index 000000000000..2f5df8f43009
--- /dev/null
+++ b/unittests/Tooling/ASTSelectionTest.cpp
@@ -0,0 +1,1085 @@
+//===- 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;
+ 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,
+ 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) {
+ 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
diff --git a/unittests/Tooling/CMakeLists.txt b/unittests/Tooling/CMakeLists.txt
index 1359c7cabd1e..557d1007ae2c 100644
--- a/unittests/Tooling/CMakeLists.txt
+++ b/unittests/Tooling/CMakeLists.txt
@@ -11,11 +11,14 @@ if (MSVC)
endif()
add_clang_unittest(ToolingTests
+ ASTSelectionTest.cpp
CastExprTest.cpp
CommentHandlerTest.cpp
CompilationDatabaseTest.cpp
DiagnosticsYamlTest.cpp
+ ExecutionTest.cpp
FixItTest.cpp
+ LexicallyOrderedRecursiveASTVisitorTest.cpp
LookupTest.cpp
QualTypeNamesTest.cpp
RecursiveASTVisitorTest.cpp
@@ -23,6 +26,7 @@ add_clang_unittest(ToolingTests
RecursiveASTVisitorTestDeclVisitor.cpp
RecursiveASTVisitorTestExprVisitor.cpp
RecursiveASTVisitorTestTypeLocVisitor.cpp
+ RefactoringActionRulesTest.cpp
RefactoringCallbacksTest.cpp
RefactoringTest.cpp
ReplacementsYamlTest.cpp
@@ -31,6 +35,7 @@ add_clang_unittest(ToolingTests
)
target_link_libraries(ToolingTests
+ PRIVATE
clangAST
clangASTMatchers
clangBasic
diff --git a/unittests/Tooling/ExecutionTest.cpp b/unittests/Tooling/ExecutionTest.cpp
new file mode 100644
index 000000000000..b0c16d6cc5d2
--- /dev/null
+++ b/unittests/Tooling/ExecutionTest.cpp
@@ -0,0 +1,221 @@
+//===- unittest/Tooling/ExecutionTest.cpp - Tool execution tests. --------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/ASTConsumer.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/Frontend/ASTUnit.h"
+#include "clang/Frontend/FrontendAction.h"
+#include "clang/Frontend/FrontendActions.h"
+#include "clang/Tooling/CompilationDatabase.h"
+#include "clang/Tooling/Execution.h"
+#include "clang/Tooling/StandaloneExecution.h"
+#include "clang/Tooling/ToolExecutorPluginRegistry.h"
+#include "clang/Tooling/Tooling.h"
+#include "gtest/gtest.h"
+#include <algorithm>
+#include <string>
+
+namespace clang {
+namespace tooling {
+
+namespace {
+
+// This traverses the AST and outputs function name as key and "1" as value for
+// each function declaration.
+class ASTConsumerWithResult
+ : public ASTConsumer,
+ public RecursiveASTVisitor<ASTConsumerWithResult> {
+public:
+ using ASTVisitor = RecursiveASTVisitor<ASTConsumerWithResult>;
+
+ explicit ASTConsumerWithResult(ExecutionContext *Context) : Context(Context) {
+ assert(Context != nullptr);
+ }
+
+ void HandleTranslationUnit(clang::ASTContext &Context) override {
+ TraverseDecl(Context.getTranslationUnitDecl());
+ }
+
+ bool TraverseFunctionDecl(clang::FunctionDecl *Decl) {
+ Context->reportResult(Decl->getNameAsString(), "1");
+ return ASTVisitor::TraverseFunctionDecl(Decl);
+ }
+
+private:
+ ExecutionContext *const Context;
+};
+
+class ReportResultAction : public ASTFrontendAction {
+public:
+ explicit ReportResultAction(ExecutionContext *Context) : Context(Context) {
+ assert(Context != nullptr);
+ }
+
+protected:
+ std::unique_ptr<clang::ASTConsumer>
+ CreateASTConsumer(clang::CompilerInstance &compiler,
+ StringRef /* dummy */) override {
+ std::unique_ptr<clang::ASTConsumer> ast_consumer{
+ new ASTConsumerWithResult(Context)};
+ return ast_consumer;
+ }
+
+private:
+ ExecutionContext *const Context;
+};
+
+class ReportResultActionFactory : public FrontendActionFactory {
+public:
+ ReportResultActionFactory(ExecutionContext *Context) : Context(Context) {}
+ FrontendAction *create() override { return new ReportResultAction(Context); }
+
+private:
+ ExecutionContext *const Context;
+};
+
+} // namespace
+
+class TestToolExecutor : public ToolExecutor {
+public:
+ static const char *ExecutorName;
+
+ TestToolExecutor(CommonOptionsParser Options)
+ : OptionsParser(std::move(Options)) {}
+
+ StringRef getExecutorName() const override { return ExecutorName; }
+
+ llvm::Error
+ execute(llvm::ArrayRef<std::pair<std::unique_ptr<FrontendActionFactory>,
+ ArgumentsAdjuster>>) override {
+ return llvm::Error::success();
+ }
+
+ ExecutionContext *getExecutionContext() override { return nullptr; };
+
+ ToolResults *getToolResults() override { return nullptr; }
+
+ llvm::ArrayRef<std::string> getSourcePaths() const {
+ return OptionsParser.getSourcePathList();
+ }
+
+ void mapVirtualFile(StringRef FilePath, StringRef Content) override {
+ VFS[FilePath] = Content;
+ }
+
+private:
+ CommonOptionsParser OptionsParser;
+ std::string SourcePaths;
+ std::map<std::string, std::string> VFS;
+};
+
+const char *TestToolExecutor::ExecutorName = "test-executor";
+
+class TestToolExecutorPlugin : public ToolExecutorPlugin {
+public:
+ llvm::Expected<std::unique_ptr<ToolExecutor>>
+ create(CommonOptionsParser &OptionsParser) override {
+ return llvm::make_unique<TestToolExecutor>(std::move(OptionsParser));
+ }
+};
+
+static ToolExecutorPluginRegistry::Add<TestToolExecutorPlugin>
+ X("test-executor", "Plugin for TestToolExecutor.");
+
+llvm::cl::OptionCategory TestCategory("execution-test options");
+
+TEST(CreateToolExecutorTest, FailedCreateExecutorUndefinedFlag) {
+ std::vector<const char *> argv = {"prog", "--fake_flag_no_no_no", "f"};
+ int argc = argv.size();
+ auto Executor = internal::createExecutorFromCommandLineArgsImpl(
+ argc, &argv[0], TestCategory);
+ ASSERT_FALSE((bool)Executor);
+ llvm::consumeError(Executor.takeError());
+}
+
+TEST(CreateToolExecutorTest, RegisterFlagsBeforeReset) {
+ llvm::cl::opt<std::string> BeforeReset(
+ "before_reset", llvm::cl::desc("Defined before reset."),
+ llvm::cl::init(""));
+
+ llvm::cl::ResetAllOptionOccurrences();
+
+ std::vector<const char *> argv = {"prog", "--before_reset=set", "f"};
+ int argc = argv.size();
+ auto Executor = internal::createExecutorFromCommandLineArgsImpl(
+ argc, &argv[0], TestCategory);
+ ASSERT_TRUE((bool)Executor);
+ EXPECT_EQ(BeforeReset, "set");
+ BeforeReset.removeArgument();
+}
+
+TEST(CreateToolExecutorTest, CreateStandaloneToolExecutor) {
+ std::vector<const char *> argv = {"prog", "standalone.cpp"};
+ int argc = argv.size();
+ auto Executor = internal::createExecutorFromCommandLineArgsImpl(
+ argc, &argv[0], TestCategory);
+ ASSERT_TRUE((bool)Executor);
+ EXPECT_EQ(Executor->get()->getExecutorName(),
+ StandaloneToolExecutor::ExecutorName);
+}
+
+TEST(CreateToolExecutorTest, CreateTestToolExecutor) {
+ std::vector<const char *> argv = {"prog", "test.cpp",
+ "--executor=test-executor"};
+ int argc = argv.size();
+ auto Executor = internal::createExecutorFromCommandLineArgsImpl(
+ argc, &argv[0], TestCategory);
+ ASSERT_TRUE((bool)Executor);
+ EXPECT_EQ(Executor->get()->getExecutorName(), TestToolExecutor::ExecutorName);
+}
+
+TEST(StandaloneToolTest, SynctaxOnlyActionOnSimpleCode) {
+ FixedCompilationDatabase Compilations(".", std::vector<std::string>());
+ StandaloneToolExecutor Executor(Compilations,
+ std::vector<std::string>(1, "a.cc"));
+ Executor.mapVirtualFile("a.cc", "int x = 0;");
+
+ auto Err = Executor.execute(newFrontendActionFactory<SyntaxOnlyAction>(),
+ getClangSyntaxOnlyAdjuster());
+ ASSERT_TRUE(!Err);
+}
+
+TEST(StandaloneToolTest, SimpleAction) {
+ FixedCompilationDatabase Compilations(".", std::vector<std::string>());
+ StandaloneToolExecutor Executor(Compilations,
+ std::vector<std::string>(1, "a.cc"));
+ Executor.mapVirtualFile("a.cc", "int x = 0;");
+
+ auto Err = Executor.execute(std::unique_ptr<FrontendActionFactory>(
+ new ReportResultActionFactory(Executor.getExecutionContext())));
+ ASSERT_TRUE(!Err);
+ auto KVs = Executor.getToolResults()->AllKVResults();
+ ASSERT_EQ(KVs.size(), 0u);
+}
+
+TEST(StandaloneToolTest, SimpleActionWithResult) {
+ FixedCompilationDatabase Compilations(".", std::vector<std::string>());
+ StandaloneToolExecutor Executor(Compilations,
+ std::vector<std::string>(1, "a.cc"));
+ Executor.mapVirtualFile("a.cc", "int x = 0; void f() {}");
+
+ auto Err = Executor.execute(std::unique_ptr<FrontendActionFactory>(
+ new ReportResultActionFactory(Executor.getExecutionContext())));
+ ASSERT_TRUE(!Err);
+ auto KVs = Executor.getToolResults()->AllKVResults();
+ ASSERT_EQ(KVs.size(), 1u);
+ EXPECT_EQ("f", KVs[0].first);
+ EXPECT_EQ("1", KVs[0].second);
+
+ Executor.getToolResults()->forEachResult(
+ [](StringRef, StringRef Value) { EXPECT_EQ("1", Value); });
+}
+
+} // end namespace tooling
+} // end namespace clang
diff --git a/unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp b/unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp
new file mode 100644
index 000000000000..50727a55fc71
--- /dev/null
+++ b/unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp
@@ -0,0 +1,227 @@
+//===- unittest/Tooling/LexicallyOrderedRecursiveASTVisitorTest.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/AST/LexicallyOrderedRecursiveASTVisitor.h"
+#include <stack>
+
+using namespace clang;
+
+namespace {
+
+class DummyMatchVisitor;
+
+class LexicallyOrderedDeclVisitor
+ : public LexicallyOrderedRecursiveASTVisitor<LexicallyOrderedDeclVisitor> {
+public:
+ LexicallyOrderedDeclVisitor(DummyMatchVisitor &Matcher,
+ const SourceManager &SM, bool EmitDeclIndices,
+ bool EmitStmtIndices)
+ : LexicallyOrderedRecursiveASTVisitor(SM), Matcher(Matcher),
+ EmitDeclIndices(EmitDeclIndices), EmitStmtIndices(EmitStmtIndices) {}
+
+ bool TraverseDecl(Decl *D) {
+ TraversalStack.push_back(D);
+ LexicallyOrderedRecursiveASTVisitor::TraverseDecl(D);
+ TraversalStack.pop_back();
+ return true;
+ }
+
+ bool TraverseStmt(Stmt *S);
+
+ bool VisitNamedDecl(const NamedDecl *D);
+ bool VisitDeclRefExpr(const DeclRefExpr *D);
+
+private:
+ DummyMatchVisitor &Matcher;
+ bool EmitDeclIndices, EmitStmtIndices;
+ unsigned Index = 0;
+ llvm::SmallVector<Decl *, 8> TraversalStack;
+};
+
+class DummyMatchVisitor : public ExpectedLocationVisitor<DummyMatchVisitor> {
+ bool EmitDeclIndices, EmitStmtIndices;
+
+public:
+ DummyMatchVisitor(bool EmitDeclIndices = false, bool EmitStmtIndices = false)
+ : EmitDeclIndices(EmitDeclIndices), EmitStmtIndices(EmitStmtIndices) {}
+ bool VisitTranslationUnitDecl(TranslationUnitDecl *TU) {
+ const ASTContext &Context = TU->getASTContext();
+ const SourceManager &SM = Context.getSourceManager();
+ LexicallyOrderedDeclVisitor SubVisitor(*this, SM, EmitDeclIndices,
+ EmitStmtIndices);
+ SubVisitor.TraverseDecl(TU);
+ return false;
+ }
+
+ template <class T> void match(StringRef Path, const T *D) {
+ Match(Path, D->getLocStart());
+ }
+};
+
+bool LexicallyOrderedDeclVisitor::TraverseStmt(Stmt *S) {
+ Matcher.match("overridden TraverseStmt", S);
+ return LexicallyOrderedRecursiveASTVisitor::TraverseStmt(S);
+}
+
+bool LexicallyOrderedDeclVisitor::VisitNamedDecl(const NamedDecl *D) {
+ std::string Path;
+ llvm::raw_string_ostream OS(Path);
+ assert(TraversalStack.back() == D);
+ for (const Decl *D : TraversalStack) {
+ if (isa<TranslationUnitDecl>(D)) {
+ OS << "/";
+ continue;
+ }
+ if (const auto *ND = dyn_cast<NamedDecl>(D))
+ OS << ND->getNameAsString();
+ else
+ OS << "???";
+ if (isa<DeclContext>(D) || isa<TemplateDecl>(D))
+ OS << "/";
+ }
+ if (EmitDeclIndices)
+ OS << "@" << Index++;
+ Matcher.match(OS.str(), D);
+ return true;
+}
+
+bool LexicallyOrderedDeclVisitor::VisitDeclRefExpr(const DeclRefExpr *D) {
+ std::string Name = D->getFoundDecl()->getNameAsString();
+ llvm::raw_string_ostream OS(Name);
+ if (EmitStmtIndices)
+ OS << "@" << Index++;
+ Matcher.match(OS.str(), D);
+ return true;
+}
+
+TEST(LexicallyOrderedRecursiveASTVisitor, VisitDeclsInImplementation) {
+ StringRef Source = R"(
+@interface I
+@end
+@implementation I
+
+int nestedFunction() { }
+
+- (void) method{ }
+
+int anotherNestedFunction(int x) {
+ return x;
+}
+
+int innerVariable = 0;
+
+@end
+
+int outerVariable = 0;
+
+@implementation I(Cat)
+
+void catF() { }
+
+@end
+
+void outerFunction() { }
+)";
+ DummyMatchVisitor Visitor;
+ Visitor.DisallowMatch("/nestedFunction/", 6, 1);
+ Visitor.ExpectMatch("/I/nestedFunction/", 6, 1);
+ Visitor.ExpectMatch("/I/method/", 8, 1);
+ Visitor.DisallowMatch("/anotherNestedFunction/", 10, 1);
+ Visitor.ExpectMatch("/I/anotherNestedFunction/", 10, 1);
+ Visitor.DisallowMatch("/innerVariable", 14, 1);
+ Visitor.ExpectMatch("/I/innerVariable", 14, 1);
+ Visitor.ExpectMatch("/outerVariable", 18, 1);
+ Visitor.DisallowMatch("/catF/", 22, 1);
+ Visitor.ExpectMatch("/Cat/catF/", 22, 1);
+ Visitor.ExpectMatch("/outerFunction/", 26, 1);
+ EXPECT_TRUE(Visitor.runOver(Source, DummyMatchVisitor::Lang_OBJC));
+}
+
+TEST(LexicallyOrderedRecursiveASTVisitor, VisitMacroDeclsInImplementation) {
+ StringRef Source = R"(
+@interface I
+@end
+
+void outerFunction() { }
+
+#define MACRO_F(x) void nestedFunction##x() { }
+
+@implementation I
+
+MACRO_F(1)
+
+@end
+
+MACRO_F(2)
+)";
+ DummyMatchVisitor Visitor;
+ Visitor.ExpectMatch("/outerFunction/", 5, 1);
+ Visitor.ExpectMatch("/I/nestedFunction1/", 7, 20);
+ Visitor.ExpectMatch("/nestedFunction2/", 7, 20);
+ EXPECT_TRUE(Visitor.runOver(Source, DummyMatchVisitor::Lang_OBJC));
+}
+
+TEST(LexicallyOrderedRecursiveASTVisitor, VisitTemplateDecl) {
+ StringRef Source = R"(
+template <class T> T f();
+template <class U, class = void> class Class {};
+)";
+ DummyMatchVisitor Visitor(/*EmitIndices=*/true);
+ Visitor.ExpectMatch("/f/T@1", 2, 11);
+ Visitor.ExpectMatch("/f/f/@2", 2, 20);
+ Visitor.ExpectMatch("/Class/U@4", 3, 11);
+ Visitor.ExpectMatch("/Class/@5", 3, 20);
+ Visitor.ExpectMatch("/Class/Class/@6", 3, 34);
+ EXPECT_TRUE(Visitor.runOver(Source));
+}
+
+TEST(LexicallyOrderedRecursiveASTVisitor, VisitCXXOperatorCallExpr) {
+ StringRef Source = R"(
+struct S {
+ S &operator+(S&);
+ S *operator->();
+ S &operator++();
+ S operator++(int);
+ void operator()(int, int);
+ void operator[](int);
+ void f();
+};
+S a, b, c;
+
+void test() {
+ a = b + c;
+ a->f();
+ a(1, 2);
+ b[0];
+ ++a;
+ b++;
+}
+)";
+ DummyMatchVisitor Visitor(/*EmitDeclIndices=*/false,
+ /*EmitStmtIndices=*/true);
+ // There are two overloaded operators that start at this point
+ // This makes sure they are both traversed using the overridden
+ // TraverseStmt, as the traversal is implemented by us for
+ // CXXOperatorCallExpr.
+ Visitor.ExpectMatch("overridden TraverseStmt", 14, 3, 2);
+ Visitor.ExpectMatch("a@0", 14, 3);
+ Visitor.ExpectMatch("operator=@1", 14, 5);
+ Visitor.ExpectMatch("b@2", 14, 7);
+ Visitor.ExpectMatch("operator+@3", 14, 9);
+ Visitor.ExpectMatch("c@4", 14, 11);
+ Visitor.ExpectMatch("operator->@6", 15, 4);
+ Visitor.ExpectMatch("operator()@8", 16, 4);
+ Visitor.ExpectMatch("operator[]@10", 17, 4);
+ Visitor.ExpectMatch("operator++@11", 18, 3);
+ Visitor.ExpectMatch("operator++@14", 19, 4);
+ EXPECT_TRUE(Visitor.runOver(Source));
+}
+
+} // end anonymous namespace
diff --git a/unittests/Tooling/QualTypeNamesTest.cpp b/unittests/Tooling/QualTypeNamesTest.cpp
index edd5060ba0e5..dd48f3bf4595 100644
--- a/unittests/Tooling/QualTypeNamesTest.cpp
+++ b/unittests/Tooling/QualTypeNamesTest.cpp
@@ -7,7 +7,7 @@
//
//===----------------------------------------------------------------------===//
-#include "clang/Tooling/Core/QualTypeNames.h"
+#include "clang/AST/QualTypeNames.h"
#include "TestVisitor.h"
using namespace clang;
diff --git a/unittests/Tooling/RefactoringActionRulesTest.cpp b/unittests/Tooling/RefactoringActionRulesTest.cpp
new file mode 100644
index 000000000000..f0b6466fec46
--- /dev/null
+++ b/unittests/Tooling/RefactoringActionRulesTest.cpp
@@ -0,0 +1,248 @@
+//===- unittest/Tooling/RefactoringTestActionRulesTest.cpp ----------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ReplacementTest.h"
+#include "RewriterTestContext.h"
+#include "clang/Tooling/Refactoring.h"
+#include "clang/Tooling/Refactoring/Extract/Extract.h"
+#include "clang/Tooling/Refactoring/RefactoringAction.h"
+#include "clang/Tooling/Refactoring/RefactoringDiagnostic.h"
+#include "clang/Tooling/Refactoring/Rename/SymbolName.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/Support/Errc.h"
+#include "gtest/gtest.h"
+
+using namespace clang;
+using namespace tooling;
+
+namespace {
+
+class RefactoringActionRulesTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ Context.Sources.setMainFileID(
+ Context.createInMemoryFile("input.cpp", DefaultCode));
+ }
+
+ RewriterTestContext Context;
+ std::string DefaultCode = std::string(100, 'a');
+};
+
+Expected<AtomicChanges>
+createReplacements(const std::unique_ptr<RefactoringActionRule> &Rule,
+ RefactoringRuleContext &Context) {
+ class Consumer final : public RefactoringResultConsumer {
+ void handleError(llvm::Error Err) override { Result = std::move(Err); }
+
+ void handle(AtomicChanges SourceReplacements) override {
+ Result = std::move(SourceReplacements);
+ }
+ void handle(SymbolOccurrences Occurrences) override {
+ RefactoringResultConsumer::handle(std::move(Occurrences));
+ }
+
+ public:
+ Optional<Expected<AtomicChanges>> Result;
+ };
+
+ Consumer C;
+ Rule->invoke(C, Context);
+ return std::move(*C.Result);
+}
+
+TEST_F(RefactoringActionRulesTest, MyFirstRefactoringRule) {
+ class ReplaceAWithB : public SourceChangeRefactoringRule {
+ std::pair<SourceRange, int> Selection;
+
+ public:
+ ReplaceAWithB(std::pair<SourceRange, int> Selection)
+ : Selection(Selection) {}
+
+ static Expected<ReplaceAWithB>
+ initiate(RefactoringRuleContext &Cotnext,
+ std::pair<SourceRange, int> Selection) {
+ return ReplaceAWithB(Selection);
+ }
+
+ Expected<AtomicChanges>
+ createSourceReplacements(RefactoringRuleContext &Context) {
+ const SourceManager &SM = Context.getSources();
+ SourceLocation Loc =
+ Selection.first.getBegin().getLocWithOffset(Selection.second);
+ AtomicChange Change(SM, Loc);
+ llvm::Error E = Change.replace(SM, Loc, 1, "b");
+ if (E)
+ return std::move(E);
+ return AtomicChanges{Change};
+ }
+ };
+
+ class SelectionRequirement : public SourceRangeSelectionRequirement {
+ public:
+ Expected<std::pair<SourceRange, int>>
+ evaluate(RefactoringRuleContext &Context) const {
+ Expected<SourceRange> R =
+ SourceRangeSelectionRequirement::evaluate(Context);
+ if (!R)
+ return R.takeError();
+ return std::make_pair(*R, 20);
+ }
+ };
+ auto Rule =
+ createRefactoringActionRule<ReplaceAWithB>(SelectionRequirement());
+
+ // When the requirements are satisifed, the rule's function must be invoked.
+ {
+ RefactoringRuleContext RefContext(Context.Sources);
+ SourceLocation Cursor =
+ Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID())
+ .getLocWithOffset(10);
+ RefContext.setSelectionRange({Cursor, Cursor});
+
+ Expected<AtomicChanges> ErrorOrResult =
+ createReplacements(Rule, RefContext);
+ ASSERT_FALSE(!ErrorOrResult);
+ AtomicChanges Result = std::move(*ErrorOrResult);
+ ASSERT_EQ(Result.size(), 1u);
+ std::string YAMLString =
+ const_cast<AtomicChange &>(Result[0]).toYAMLString();
+
+ ASSERT_STREQ("---\n"
+ "Key: 'input.cpp:30'\n"
+ "FilePath: input.cpp\n"
+ "Error: ''\n"
+ "InsertedHeaders: \n"
+ "RemovedHeaders: \n"
+ "Replacements: \n" // Extra whitespace here!
+ " - FilePath: input.cpp\n"
+ " Offset: 30\n"
+ " Length: 1\n"
+ " ReplacementText: b\n"
+ "...\n",
+ YAMLString.c_str());
+ }
+
+ // When one of the requirements is not satisfied, invoke should return a
+ // valid error.
+ {
+ RefactoringRuleContext RefContext(Context.Sources);
+ Expected<AtomicChanges> ErrorOrResult =
+ createReplacements(Rule, RefContext);
+
+ ASSERT_TRUE(!ErrorOrResult);
+ unsigned DiagID;
+ llvm::handleAllErrors(ErrorOrResult.takeError(),
+ [&](DiagnosticError &Error) {
+ DiagID = Error.getDiagnostic().second.getDiagID();
+ });
+ EXPECT_EQ(DiagID, diag::err_refactor_no_selection);
+ }
+}
+
+TEST_F(RefactoringActionRulesTest, ReturnError) {
+ class ErrorRule : public SourceChangeRefactoringRule {
+ public:
+ static Expected<ErrorRule> initiate(RefactoringRuleContext &,
+ SourceRange R) {
+ return ErrorRule(R);
+ }
+
+ ErrorRule(SourceRange R) {}
+ Expected<AtomicChanges> createSourceReplacements(RefactoringRuleContext &) {
+ return llvm::make_error<llvm::StringError>(
+ "Error", llvm::make_error_code(llvm::errc::invalid_argument));
+ }
+ };
+
+ auto Rule =
+ createRefactoringActionRule<ErrorRule>(SourceRangeSelectionRequirement());
+ RefactoringRuleContext RefContext(Context.Sources);
+ SourceLocation Cursor =
+ Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID());
+ RefContext.setSelectionRange({Cursor, Cursor});
+ Expected<AtomicChanges> Result = createReplacements(Rule, RefContext);
+
+ ASSERT_TRUE(!Result);
+ std::string Message;
+ llvm::handleAllErrors(Result.takeError(), [&](llvm::StringError &Error) {
+ Message = Error.getMessage();
+ });
+ EXPECT_EQ(Message, "Error");
+}
+
+Optional<SymbolOccurrences> findOccurrences(RefactoringActionRule &Rule,
+ RefactoringRuleContext &Context) {
+ class Consumer final : public RefactoringResultConsumer {
+ void handleError(llvm::Error) override {}
+ void handle(SymbolOccurrences Occurrences) override {
+ Result = std::move(Occurrences);
+ }
+ void handle(AtomicChanges Changes) override {
+ RefactoringResultConsumer::handle(std::move(Changes));
+ }
+
+ public:
+ Optional<SymbolOccurrences> Result;
+ };
+
+ Consumer C;
+ Rule.invoke(C, Context);
+ return std::move(C.Result);
+}
+
+TEST_F(RefactoringActionRulesTest, ReturnSymbolOccurrences) {
+ class FindOccurrences : public FindSymbolOccurrencesRefactoringRule {
+ SourceRange Selection;
+
+ public:
+ FindOccurrences(SourceRange Selection) : Selection(Selection) {}
+
+ static Expected<FindOccurrences> initiate(RefactoringRuleContext &,
+ SourceRange Selection) {
+ return FindOccurrences(Selection);
+ }
+
+ Expected<SymbolOccurrences>
+ findSymbolOccurrences(RefactoringRuleContext &) override {
+ SymbolOccurrences Occurrences;
+ Occurrences.push_back(SymbolOccurrence(SymbolName("test"),
+ SymbolOccurrence::MatchingSymbol,
+ Selection.getBegin()));
+ return std::move(Occurrences);
+ }
+ };
+
+ auto Rule = createRefactoringActionRule<FindOccurrences>(
+ SourceRangeSelectionRequirement());
+
+ RefactoringRuleContext RefContext(Context.Sources);
+ SourceLocation Cursor =
+ Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID());
+ RefContext.setSelectionRange({Cursor, Cursor});
+ Optional<SymbolOccurrences> Result = findOccurrences(*Rule, RefContext);
+
+ ASSERT_FALSE(!Result);
+ SymbolOccurrences Occurrences = std::move(*Result);
+ EXPECT_EQ(Occurrences.size(), 1u);
+ EXPECT_EQ(Occurrences[0].getKind(), SymbolOccurrence::MatchingSymbol);
+ EXPECT_EQ(Occurrences[0].getNameRanges().size(), 1u);
+ EXPECT_EQ(Occurrences[0].getNameRanges()[0],
+ SourceRange(Cursor, Cursor.getLocWithOffset(strlen("test"))));
+}
+
+TEST_F(RefactoringActionRulesTest, EditorCommandBinding) {
+ const RefactoringDescriptor &Descriptor = ExtractFunction::describe();
+ EXPECT_EQ(Descriptor.Name, "extract-function");
+ EXPECT_EQ(
+ Descriptor.Description,
+ "(WIP action; use with caution!) Extracts code into a new function");
+ EXPECT_EQ(Descriptor.Title, "Extract Function");
+}
+
+} // end anonymous namespace
diff --git a/unittests/Tooling/RefactoringTest.cpp b/unittests/Tooling/RefactoringTest.cpp
index 15900940c887..41836f11ee29 100644
--- a/unittests/Tooling/RefactoringTest.cpp
+++ b/unittests/Tooling/RefactoringTest.cpp
@@ -1295,5 +1295,427 @@ TEST_F(AtomicChangeTest, InsertAfterWithInvalidLocation) {
Replacement(Context.Sources, SourceLocation(), 0, "b")));
}
+class ApplyAtomicChangesTest : public ::testing::Test {
+protected:
+ ApplyAtomicChangesTest() : FilePath("file.cc") {
+ Spec.Cleanup = true;
+ Spec.Format = ApplyChangesSpec::kAll;
+ Spec.Style = format::getLLVMStyle();
+ }
+
+ ~ApplyAtomicChangesTest() override {}
+
+ void setInput(llvm::StringRef Input) {
+ Code = Input;
+ FID = Context.createInMemoryFile(FilePath, Code);
+ }
+
+ SourceLocation getLoc(unsigned Offset) const {
+ return Context.Sources.getLocForStartOfFile(FID).getLocWithOffset(Offset);
+ }
+
+ AtomicChange replacementToAtomicChange(llvm::StringRef Key, unsigned Offset,
+ unsigned Length,
+ llvm::StringRef Text) {
+ AtomicChange Change(FilePath, Key);
+ llvm::Error Err =
+ Change.replace(Context.Sources, getLoc(Offset), Length, Text);
+ EXPECT_FALSE(Err);
+ return Change;
+ }
+
+ std::string rewrite(bool FailureExpected = false) {
+ llvm::Expected<std::string> ChangedCode =
+ applyAtomicChanges(FilePath, Code, Changes, Spec);
+ EXPECT_EQ(FailureExpected, !ChangedCode);
+ if (!ChangedCode) {
+ llvm::errs() << "Failed to apply changes: "
+ << llvm::toString(ChangedCode.takeError()) << "\n";
+ return "";
+ }
+ return *ChangedCode;
+ }
+
+ RewriterTestContext Context;
+ FileID FID;
+ ApplyChangesSpec Spec;
+ std::string Code;
+ std::string FilePath;
+ llvm::SmallVector<AtomicChange, 8> Changes;
+};
+
+TEST_F(ApplyAtomicChangesTest, BasicRefactoring) {
+ setInput("int a;");
+ AtomicChange Change(FilePath, "key1");
+ Changes.push_back(replacementToAtomicChange("key1", 4, 1, "b"));
+ EXPECT_EQ("int b;", rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, SeveralRefactorings) {
+ setInput("int a;\n"
+ "int b;");
+ Changes.push_back(replacementToAtomicChange("key1", 0, 3, "float"));
+ Changes.push_back(replacementToAtomicChange("key2", 4, 1, "f"));
+ Changes.push_back(replacementToAtomicChange("key3", 11, 1, "g"));
+ Changes.push_back(replacementToAtomicChange("key4", 7, 3, "float"));
+ EXPECT_EQ("float f;\n"
+ "float g;",
+ rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, IgnorePathsInRefactorings) {
+ setInput("int a;\n"
+ "int b;");
+ Changes.push_back(replacementToAtomicChange("key1", 4, 1, "aa"));
+
+ FileID ID = Context.createInMemoryFile("AnotherFile", "12345678912345");
+ Changes.emplace_back("AnotherFile", "key2");
+ auto Err = Changes.back().replace(
+ Context.Sources,
+ Context.Sources.getLocForStartOfFile(ID).getLocWithOffset(11), 1, "bb");
+ ASSERT_TRUE(!Err);
+ EXPECT_EQ("int aa;\n"
+ "int bb;",
+ rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, AppliesDuplicateInsertions) {
+ setInput("int a;");
+ Changes.push_back(replacementToAtomicChange("key1", 5, 0, "b"));
+ Changes.push_back(replacementToAtomicChange("key2", 5, 0, "b"));
+ EXPECT_EQ("int abb;", rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, BailsOnOverlappingRefactorings) {
+ setInput("int a;");
+ Changes.push_back(replacementToAtomicChange("key1", 0, 5, "float f"));
+ Changes.push_back(replacementToAtomicChange("key2", 4, 1, "b"));
+ EXPECT_EQ("", rewrite(/*FailureExpected=*/true));
+}
+
+TEST_F(ApplyAtomicChangesTest, BasicReformatting) {
+ setInput("int a;");
+ Changes.push_back(replacementToAtomicChange("key1", 5, 1, "b"));
+ EXPECT_EQ("int b;", rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, OnlyFormatWhenViolateColumnLimits) {
+ Spec.Format = ApplyChangesSpec::kViolations;
+ Spec.Style.ColumnLimit = 8;
+ setInput("int a;\n"
+ "int a;\n"
+ "int aaaaaaaa;\n");
+ Changes.push_back(replacementToAtomicChange("key1", 5, 1, "x"));
+ Changes.push_back(replacementToAtomicChange("key2", 15, 1, "x"));
+ Changes.push_back(replacementToAtomicChange("key3", 23, 8, "xx"));
+ EXPECT_EQ("int x;\n"
+ "int x;\n"
+ "int xx;\n",
+ rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, LastLineViolateColumnLimits) {
+ Spec.Format = ApplyChangesSpec::kViolations;
+ Spec.Style.ColumnLimit = 8;
+ setInput("int a;\n"
+ "int a;");
+ Changes.push_back(replacementToAtomicChange("key1", 0, 1, "i"));
+ Changes.push_back(replacementToAtomicChange("key2", 15, 2, "y;"));
+ EXPECT_EQ("int a;\n"
+ "int y;",
+ rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, LastLineWithNewlineViolateColumnLimits) {
+ Spec.Format = ApplyChangesSpec::kViolations;
+ Spec.Style.ColumnLimit = 8;
+ setInput("int a;\n"
+ "int a;\n");
+ Changes.push_back(replacementToAtomicChange("key1", 0, 1, "i"));
+ Changes.push_back(replacementToAtomicChange("key2", 14, 3, "y;\n"));
+ EXPECT_EQ("int a;\n"
+ "int y;\n",
+ rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, Longer) {
+ setInput("int a;");
+ Changes.push_back(replacementToAtomicChange("key1", 5, 1, "bbb"));
+ EXPECT_EQ("int bbb;", rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, Shorter) {
+ setInput("int aaa;");
+ Changes.push_back(replacementToAtomicChange("key1", 5, 3, "b"));
+ EXPECT_EQ("int b;", rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, OnlyFormatChangedLines) {
+ setInput("int aaa;\n"
+ "int a = b;\n"
+ "int bbb;");
+ Changes.push_back(replacementToAtomicChange("key1", 14, 1, "b"));
+ EXPECT_EQ("int aaa;\n"
+ "int b = b;\n"
+ "int bbb;",
+ rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, DisableFormatting) {
+ Spec.Format = ApplyChangesSpec::kNone;
+ setInput("int aaa;\n"
+ "int a = b;\n"
+ "int bbb;");
+ Changes.push_back(replacementToAtomicChange("key1", 14, 1, "b"));
+ EXPECT_EQ("int aaa;\n"
+ "int b = b;\n"
+ "int bbb;",
+ rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, AdaptsToLocalPointerStyle) {
+ setInput("int *aaa;\n"
+ "int *bbb;");
+ Changes.push_back(replacementToAtomicChange("key1", 0, 0, "int* ccc;\n"));
+ EXPECT_EQ("int *ccc;\n"
+ "int *aaa;\n"
+ "int *bbb;",
+ rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, AcceptsSurroundingFormatting) {
+ setInput(" int aaa;\n"
+ " int a = b;\n"
+ " int bbb;");
+ Changes.push_back(replacementToAtomicChange("key1", 20, 1, "b"));
+ EXPECT_EQ(" int aaa;\n"
+ " int b = b;\n"
+ " int bbb;",
+ rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, BailsOutOnConflictingChanges) {
+ setInput("int c;\n"
+ "int f;");
+ // Insertions at the same offset are only allowed in the same AtomicChange.
+ Changes.push_back(replacementToAtomicChange("key1", 0, 0, "int a;\n"));
+ Changes.push_back(replacementToAtomicChange("key2", 0, 0, "int b;\n"));
+ EXPECT_EQ("", rewrite(/*FailureExpected=*/true));
+}
+
+TEST_F(ApplyAtomicChangesTest, InsertsNewIncludesInRightOrder) {
+ setInput("int a;");
+ Changes.emplace_back(FilePath, "key1");
+ Changes.back().addHeader("b");
+ Changes.back().addHeader("c");
+ Changes.emplace_back(FilePath, "key2");
+ Changes.back().addHeader("a");
+ EXPECT_EQ("#include \"a\"\n"
+ "#include \"b\"\n"
+ "#include \"c\"\n"
+ "int a;",
+ rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, RemoveAndSortIncludes) {
+ setInput("#include \"a\"\n"
+ "#include \"b\"\n"
+ "#include \"c\"\n"
+ "\n"
+ "int a;");
+ Changes.emplace_back(FilePath, "key1");
+ Changes.back().removeHeader("b");
+ EXPECT_EQ("#include \"a\"\n"
+ "#include \"c\"\n"
+ "\n"
+ "int a;",
+ rewrite());
+}
+TEST_F(ApplyAtomicChangesTest, InsertsSystemIncludes) {
+ setInput("#include <asys>\n"
+ "#include <csys>\n"
+ "\n"
+ "#include \"a\"\n"
+ "#include \"c\"\n");
+ Changes.emplace_back(FilePath, "key1");
+ Changes.back().addHeader("<asys>"); // Already exists.
+ Changes.back().addHeader("<b>");
+ Changes.back().addHeader("<d>");
+ Changes.back().addHeader("\"b-already-escaped\"");
+ EXPECT_EQ("#include <asys>\n"
+ "#include <b>\n"
+ "#include <csys>\n"
+ "#include <d>\n"
+ "\n"
+ "#include \"a\"\n"
+ "#include \"b-already-escaped\"\n"
+ "#include \"c\"\n",
+ rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, RemoveSystemIncludes) {
+ setInput("#include <a>\n"
+ "#include <b>\n"
+ "\n"
+ "#include \"c\""
+ "\n"
+ "int a;");
+ Changes.emplace_back(FilePath, "key1");
+ Changes.back().removeHeader("<a>");
+ EXPECT_EQ("#include <b>\n"
+ "\n"
+ "#include \"c\""
+ "\n"
+ "int a;",
+ rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest,
+ DoNotFormatFollowingLinesIfSeparatedWithNewline) {
+ setInput("#ifndef __H__\n"
+ "#define __H__\n"
+ "#include \"b\"\n"
+ "\n"
+ "int a;\n"
+ "int a;\n"
+ "int a;\n"
+ "#endif // __H__\n");
+ Changes.push_back(replacementToAtomicChange("key1",
+ llvm::StringRef("#ifndef __H__\n"
+ "#define __H__\n"
+ "\n"
+ "#include \"b\"\n"
+ "int a;\n"
+ "int ")
+ .size(),
+ 1, "b"));
+ Changes.back().addHeader("a");
+ EXPECT_EQ("#ifndef __H__\n"
+ "#define __H__\n"
+ "#include \"a\"\n"
+ "#include \"b\"\n"
+ "\n"
+ "int a;\n"
+ "int b;\n"
+ "int a;\n"
+ "#endif // __H__\n",
+ rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, FormatsCorrectLineWhenHeaderIsRemoved) {
+ setInput("#include \"a\"\n"
+ "\n"
+ "int a;\n"
+ "int a;\n"
+ "int a;");
+ Changes.push_back(replacementToAtomicChange("key1", 27, 1, "b"));
+ Changes.back().removeHeader("a");
+ EXPECT_EQ("\n"
+ "int a;\n"
+ "int b;\n"
+ "int a;",
+ rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, CleansUpCtorInitializers) {
+ setInput("A::A() : a(), b() {}\n"
+ "A::A() : a(), b() {}\n"
+ "A::A() : a(), b() {}\n"
+ "A::A() : a()/**/, b() {}\n"
+ "A::A() : a() ,// \n"
+ " /**/ b() {}");
+ Changes.emplace_back(FilePath, "key1");
+ auto Err = Changes.back().replace(Context.Sources, getLoc(9), 3, "");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(35), 3, "");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(51), 3, "");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(56), 3, "");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(72), 3, "");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(97), 3, "");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(118), 3, "");
+ ASSERT_TRUE(!Err);
+ EXPECT_EQ("A::A() : b() {}\n"
+ "A::A() : a() {}\n"
+ "A::A() {}\n"
+ "A::A() : b() {}\n"
+ "A::A() {}",
+ rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, CleansUpParameterLists) {
+ setInput("void f(int i, float f, string s);\n"
+ "f(1, 2.0f, \"a\");\n"
+ "g(1, 1);");
+ Changes.emplace_back(FilePath, "key1");
+ auto Err = Changes.back().replace(Context.Sources, getLoc(7), 5, "");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(23), 8, "");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(36), 1, "");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(45), 3, "");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(53), 1, "");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(56), 1, "");
+ ASSERT_TRUE(!Err);
+ EXPECT_EQ("void f(float f);\n"
+ "f(2.0f);\n"
+ "g();",
+ rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, DisableCleanup) {
+ Spec.Cleanup = false;
+ setInput("void f(int i, float f, string s);\n"
+ "f(1, 2.0f, \"a\");\n"
+ "g(1, 1);");
+ Changes.emplace_back(FilePath, "key1");
+ auto Err = Changes.back().replace(Context.Sources, getLoc(7), 5, "");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(23), 8, "");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(36), 1, "");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(45), 3, "");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(53), 1, "");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(56), 1, "");
+ ASSERT_TRUE(!Err);
+ EXPECT_EQ("void f(, float f, );\n"
+ "f(, 2.0f, );\n"
+ "g(, );",
+ rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, EverythingDeleted) {
+ setInput("int a;");
+ Changes.push_back(replacementToAtomicChange("key1", 0, 6, ""));
+ EXPECT_EQ("", rewrite());
+}
+
+TEST_F(ApplyAtomicChangesTest, DoesNotDeleteInserts) {
+ setInput("int a;\n"
+ "int b;");
+ Changes.emplace_back(FilePath, "key1");
+ auto Err = Changes.back().replace(Context.Sources, getLoc(4), 1, "");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(4), 0, "b");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(11), 0, "a");
+ ASSERT_TRUE(!Err);
+ Err = Changes.back().replace(Context.Sources, getLoc(11), 1, "");
+ ASSERT_TRUE(!Err);
+ EXPECT_EQ("int b;\n"
+ "int a;",
+ rewrite());
+}
+
} // end namespace tooling
} // end namespace clang
diff --git a/unittests/Tooling/TestVisitor.h b/unittests/Tooling/TestVisitor.h
index adfd3ef60f50..fb6a76ccadd0 100644
--- a/unittests/Tooling/TestVisitor.h
+++ b/unittests/Tooling/TestVisitor.h
@@ -60,7 +60,10 @@ public:
case Lang_CXX98: Args.push_back("-std=c++98"); break;
case Lang_CXX11: Args.push_back("-std=c++11"); break;
case Lang_CXX14: Args.push_back("-std=c++14"); break;
- case Lang_OBJC: Args.push_back("-ObjC"); break;
+ case Lang_OBJC:
+ Args.push_back("-ObjC");
+ Args.push_back("-fobjc-runtime=macosx-10.12.0");
+ break;
case Lang_OBJCXX11:
Args.push_back("-ObjC++");
Args.push_back("-std=c++11");
diff --git a/unittests/Tooling/ToolingTest.cpp b/unittests/Tooling/ToolingTest.cpp
index b179f033d357..891907a4d081 100644
--- a/unittests/Tooling/ToolingTest.cpp
+++ b/unittests/Tooling/ToolingTest.cpp
@@ -402,6 +402,37 @@ TEST(ClangToolTest, ArgumentAdjusters) {
EXPECT_FALSE(Found);
}
+// Check getClangStripDependencyFileAdjuster doesn't strip args after -MD/-MMD.
+TEST(ClangToolTest, StripDependencyFileAdjuster) {
+ FixedCompilationDatabase Compilations("/", {"-MD", "-c", "-MMD", "-w"});
+
+ ClangTool Tool(Compilations, std::vector<std::string>(1, "/a.cc"));
+ Tool.mapVirtualFile("/a.cc", "void a() {}");
+
+ std::unique_ptr<FrontendActionFactory> Action(
+ newFrontendActionFactory<SyntaxOnlyAction>());
+
+ CommandLineArguments FinalArgs;
+ ArgumentsAdjuster CheckFlagsAdjuster =
+ [&FinalArgs](const CommandLineArguments &Args, StringRef /*unused*/) {
+ FinalArgs = Args;
+ return Args;
+ };
+ Tool.clearArgumentsAdjusters();
+ Tool.appendArgumentsAdjuster(getClangStripDependencyFileAdjuster());
+ Tool.appendArgumentsAdjuster(CheckFlagsAdjuster);
+ Tool.run(Action.get());
+
+ auto HasFlag = [&FinalArgs](const std::string &Flag) {
+ return std::find(FinalArgs.begin(), FinalArgs.end(), Flag) !=
+ FinalArgs.end();
+ };
+ EXPECT_FALSE(HasFlag("-MD"));
+ EXPECT_FALSE(HasFlag("-MMD"));
+ EXPECT_TRUE(HasFlag("-c"));
+ EXPECT_TRUE(HasFlag("-w"));
+}
+
namespace {
/// Find a target name such that looking for it in TargetRegistry by that name
/// returns the same target. We expect that there is at least one target
@@ -533,5 +564,31 @@ TEST(ClangToolTest, InjectDiagnosticConsumerInBuildASTs) {
}
#endif
+TEST(runToolOnCode, TestResetDiagnostics) {
+ // This is a tool that resets the diagnostic during the compilation.
+ struct ResetDiagnosticAction : public clang::ASTFrontendAction {
+ std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,
+ StringRef) override {
+ struct Consumer : public clang::ASTConsumer {
+ bool HandleTopLevelDecl(clang::DeclGroupRef D) override {
+ auto &Diags = (*D.begin())->getASTContext().getDiagnostics();
+ // Ignore any error
+ Diags.Reset();
+ // Disable warnings because computing the CFG might crash.
+ Diags.setIgnoreAllWarnings(true);
+ return true;
+ }
+ };
+ return llvm::make_unique<Consumer>();
+ }
+ };
+
+ // Should not crash
+ EXPECT_FALSE(
+ runToolOnCode(new ResetDiagnosticAction,
+ "struct Foo { Foo(int); ~Foo(); struct Fwd _fwd; };"
+ "void func() { long x; Foo f(x); }"));
+}
+
} // end namespace tooling
} // end namespace clang