summaryrefslogtreecommitdiff
path: root/unittests
diff options
context:
space:
mode:
Diffstat (limited to 'unittests')
-rw-r--r--unittests/AST/ASTImporterTest.cpp220
-rw-r--r--unittests/AST/CMakeLists.txt2
-rw-r--r--unittests/AST/DataCollectionTest.cpp173
-rw-r--r--unittests/AST/DeclPrinterTest.cpp111
-rw-r--r--unittests/AST/StmtPrinterTest.cpp74
-rw-r--r--unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp46
-rw-r--r--unittests/ASTMatchers/ASTMatchersNodeTest.cpp38
-rw-r--r--unittests/ASTMatchers/ASTMatchersTraversalTest.cpp43
-rw-r--r--unittests/ASTMatchers/CMakeLists.txt1
-rw-r--r--unittests/ASTMatchers/Dynamic/CMakeLists.txt1
-rw-r--r--unittests/Analysis/CMakeLists.txt1
-rw-r--r--unittests/Analysis/CloneDetectionTest.cpp6
-rw-r--r--unittests/Basic/CMakeLists.txt1
-rw-r--r--unittests/Basic/DiagnosticTest.cpp22
-rw-r--r--unittests/Basic/FileManagerTest.cpp27
-rw-r--r--unittests/Basic/VirtualFileSystemTest.cpp82
-rw-r--r--unittests/CMakeLists.txt1
-rw-r--r--unittests/CodeGen/CMakeLists.txt5
-rw-r--r--unittests/CodeGen/CodeGenExternalTest.cpp302
-rw-r--r--unittests/CodeGen/IncrementalProcessingTest.cpp174
-rw-r--r--unittests/CrossTU/CMakeLists.txt17
-rw-r--r--unittests/CrossTU/CrossTranslationUnitTest.cpp158
-rw-r--r--unittests/Driver/CMakeLists.txt2
-rw-r--r--unittests/Driver/ToolChainTest.cpp96
-rw-r--r--unittests/Format/CMakeLists.txt2
-rw-r--r--unittests/Format/FormatTest.cpp766
-rw-r--r--unittests/Format/FormatTestComments.cpp523
-rw-r--r--unittests/Format/FormatTestJS.cpp356
-rw-r--r--unittests/Format/FormatTestJava.cpp15
-rw-r--r--unittests/Format/FormatTestObjC.cpp11
-rw-r--r--unittests/Format/FormatTestProto.cpp18
-rw-r--r--unittests/Format/FormatTestRawStrings.cpp733
-rw-r--r--unittests/Format/FormatTestTextProto.cpp44
-rw-r--r--unittests/Format/FormatTestUtils.h3
-rw-r--r--unittests/Format/NamespaceEndCommentsFixerTest.cpp128
-rw-r--r--unittests/Format/SortImportsTestJS.cpp8
-rw-r--r--unittests/Format/SortIncludesTest.cpp193
-rw-r--r--unittests/Format/UsingDeclarationsSorterTest.cpp153
-rw-r--r--unittests/Frontend/ASTUnitTest.cpp87
-rw-r--r--unittests/Frontend/CMakeLists.txt5
-rw-r--r--unittests/Frontend/CompilerInstanceTest.cpp74
-rw-r--r--unittests/Frontend/PCHPreambleTest.cpp200
-rw-r--r--unittests/Frontend/ParsedSourceLocationTest.cpp37
-rw-r--r--unittests/Lex/CMakeLists.txt1
-rw-r--r--unittests/Lex/LexerTest.cpp98
-rw-r--r--unittests/Rename/CMakeLists.txt5
-rw-r--r--unittests/Rename/RenameAliasTest.cpp304
-rw-r--r--unittests/Rename/RenameClassTest.cpp143
-rw-r--r--unittests/Rename/RenameEnumTest.cpp189
-rw-r--r--unittests/Rename/RenameFunctionTest.cpp574
-rw-r--r--unittests/Rename/RenameMemberTest.cpp229
-rw-r--r--unittests/Rewrite/CMakeLists.txt1
-rw-r--r--unittests/Sema/CMakeLists.txt1
-rw-r--r--unittests/StaticAnalyzer/CMakeLists.txt1
-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
-rw-r--r--unittests/libclang/CMakeLists.txt1
64 files changed, 8627 insertions, 151 deletions
diff --git a/unittests/AST/ASTImporterTest.cpp b/unittests/AST/ASTImporterTest.cpp
index 57b41f12b0bac..099d5412a7df8 100644
--- a/unittests/AST/ASTImporterTest.cpp
+++ b/unittests/AST/ASTImporterTest.cpp
@@ -97,6 +97,10 @@ testImport(const std::string &FromCode, Language FromLang,
llvm::raw_svector_ostream ToNothing(ImportChecker);
ToCtx.getTranslationUnitDecl()->print(ToNothing);
+ // This traverses the AST to catch certain bugs like poorly or not
+ // implemented subtrees.
+ Imported->dump(ToNothing);
+
return Verifier.match(Imported, AMatcher);
}
@@ -252,35 +256,28 @@ AST_MATCHER_P(TemplateDecl, hasTemplateDecl,
TEST(ImportExpr, ImportParenListExpr) {
MatchVerifier<Decl> Verifier;
+ EXPECT_TRUE(testImport(
+ "template<typename T> class dummy { void f() { dummy X(*this); } };"
+ "typedef dummy<int> declToImport;"
+ "template class dummy<int>;",
+ Lang_CXX, "", Lang_CXX, Verifier,
+ typedefDecl(hasType(templateSpecializationType(
+ hasDeclaration(classTemplateSpecializationDecl(hasSpecializedTemplate(
+ classTemplateDecl(hasTemplateDecl(cxxRecordDecl(hasMethod(allOf(
+ hasName("f"),
+ hasBody(compoundStmt(has(declStmt(hasSingleDecl(
+ varDecl(hasInitializer(parenListExpr(has(unaryOperator(
+ hasOperatorName("*"),
+ hasUnaryOperand(cxxThisExpr()))))))))))))))))))))))));
+}
+
+TEST(ImportExpr, ImportSwitch) {
+ MatchVerifier<Decl> Verifier;
EXPECT_TRUE(
- testImport(
- "template<typename T> class dummy { void f() { dummy X(*this); } };"
- "typedef dummy<int> declToImport;"
- "template class dummy<int>;",
- Lang_CXX, "", Lang_CXX, Verifier,
- typedefDecl(
- hasType(
- templateSpecializationType(
- hasDeclaration(
- classTemplateDecl(
- hasTemplateDecl(
- cxxRecordDecl(
- hasMethod(
- allOf(
- hasName("f"),
- hasBody(
- compoundStmt(
- has(
- declStmt(
- hasSingleDecl(
- varDecl(
- hasInitializer(
- parenListExpr(
- has(
- unaryOperator(
- hasOperatorName("*"),
- hasUnaryOperand(cxxThisExpr())
- )))))))))))))))))))));
+ testImport("void declToImport() { int b; switch (b) { case 1: break; } }",
+ Lang_CXX, "", Lang_CXX, Verifier,
+ functionDecl(hasBody(compoundStmt(
+ has(switchStmt(has(compoundStmt(has(caseStmt()))))))))));
}
TEST(ImportExpr, ImportStmtExpr) {
@@ -489,5 +486,172 @@ TEST(ImportType, ImportAtomicType) {
}
+TEST(ImportType, ImportTypeAliasTemplate) {
+ MatchVerifier<Decl> Verifier;
+ EXPECT_TRUE(testImport("template <int K>"
+ "struct dummy { static const int i = K; };"
+ "template <int K> using dummy2 = dummy<K>;"
+ "int declToImport() { return dummy2<3>::i; }",
+ Lang_CXX11, "", Lang_CXX11, Verifier,
+ functionDecl(
+ hasBody(
+ compoundStmt(
+ has(
+ returnStmt(
+ has(
+ implicitCastExpr(
+ has(
+ declRefExpr()))))))))));
+}
+
+TEST(ImportDecl, ImportFunctionTemplateDecl) {
+ MatchVerifier<Decl> Verifier;
+ EXPECT_TRUE(testImport("template <typename T> void declToImport() { };",
+ Lang_CXX, "", Lang_CXX, Verifier,
+ functionTemplateDecl()));
+}
+
+const internal::VariadicDynCastAllOfMatcher<Expr, CXXDependentScopeMemberExpr>
+ cxxDependentScopeMemberExpr;
+
+TEST(ImportExpr, ImportCXXDependentScopeMemberExpr) {
+ MatchVerifier<Decl> Verifier;
+ EXPECT_TRUE(testImport("template <typename T> class C { T t; };"
+ "template <typename T> void declToImport() {"
+ " C<T> d;"
+ " d.t;"
+ "}",
+ Lang_CXX, "", Lang_CXX, Verifier,
+ functionTemplateDecl(has(functionDecl(has(compoundStmt(
+ has(cxxDependentScopeMemberExpr()))))))));
+ EXPECT_TRUE(testImport("template <typename T> class C { T t; };"
+ "template <typename T> void declToImport() {"
+ " C<T> d;"
+ " (&d)->t;"
+ "}",
+ Lang_CXX, "", Lang_CXX, Verifier,
+ functionTemplateDecl(has(functionDecl(has(compoundStmt(
+ has(cxxDependentScopeMemberExpr()))))))));
+}
+
+TEST(ImportType, ImportPackExpansion) {
+ MatchVerifier<Decl> Verifier;
+ EXPECT_TRUE(testImport("template <typename... Args>"
+ "struct dummy {"
+ " dummy(Args... args) {}"
+ " static const int i = 4;"
+ "};"
+ "int declToImport() { return dummy<int>::i; }",
+ Lang_CXX11, "", Lang_CXX11, Verifier,
+ functionDecl(
+ hasBody(
+ compoundStmt(
+ has(
+ returnStmt(
+ has(
+ implicitCastExpr(
+ has(
+ declRefExpr()))))))))));
+}
+
+/// \brief Matches __builtin_types_compatible_p:
+/// GNU extension to check equivalent types
+/// Given
+/// \code
+/// __builtin_types_compatible_p(int, int)
+/// \endcode
+// will generate TypeTraitExpr <...> 'int'
+const internal::VariadicDynCastAllOfMatcher<Stmt, TypeTraitExpr> typeTraitExpr;
+
+TEST(ImportExpr, ImportTypeTraitExpr) {
+ MatchVerifier<Decl> Verifier;
+ EXPECT_TRUE(testImport("void declToImport() { "
+ " __builtin_types_compatible_p(int, int);"
+ "}",
+ Lang_C, "", Lang_C, Verifier,
+ functionDecl(
+ hasBody(
+ compoundStmt(
+ has(
+ typeTraitExpr(hasType(asString("int")))))))));
+}
+
+TEST(ImportExpr, ImportTypeTraitExprValDep) {
+ MatchVerifier<Decl> Verifier;
+ EXPECT_TRUE(testImport("template<typename T> struct declToImport {"
+ " void m() { __is_pod(T); }"
+ "};"
+ "void f() { declToImport<int>().m(); }",
+ Lang_CXX11, "", Lang_CXX11, Verifier,
+ classTemplateDecl(
+ has(
+ cxxRecordDecl(
+ has(
+ functionDecl(
+ hasBody(
+ compoundStmt(
+ has(
+ typeTraitExpr(
+ hasType(booleanType())
+ )))))))))));
+}
+
+const internal::VariadicDynCastAllOfMatcher<Expr, CXXPseudoDestructorExpr>
+ cxxPseudoDestructorExpr;
+
+TEST(ImportExpr, ImportCXXPseudoDestructorExpr) {
+ MatchVerifier<Decl> Verifier;
+ EXPECT_TRUE(
+ testImport("typedef int T;"
+ "void declToImport(int *p) {"
+ " T t;"
+ " p->T::~T();"
+ "}",
+ Lang_CXX, "", Lang_CXX, Verifier,
+ functionDecl(has(compoundStmt(has(
+ callExpr(has(cxxPseudoDestructorExpr()))))))));
+}
+
+TEST(ImportDecl, ImportUsingDecl) {
+ MatchVerifier<Decl> Verifier;
+ EXPECT_TRUE(
+ testImport(
+ "namespace foo { int bar; }"
+ "int declToImport(){ using foo::bar; }",
+ Lang_CXX, "", Lang_CXX, Verifier,
+ functionDecl(
+ has(
+ compoundStmt(
+ has(
+ declStmt(
+ has(
+ usingDecl()))))))));
+}
+
+/// \brief Matches shadow declarations introduced into a scope by a
+/// (resolved) using declaration.
+///
+/// Given
+/// \code
+/// namespace n { int f; }
+/// namespace declToImport { using n::f; }
+/// \endcode
+/// usingShadowDecl()
+/// matches \code f \endcode
+const internal::VariadicDynCastAllOfMatcher<Decl,
+ UsingShadowDecl> usingShadowDecl;
+
+TEST(ImportDecl, ImportUsingShadowDecl) {
+ MatchVerifier<Decl> Verifier;
+ EXPECT_TRUE(
+ testImport(
+ "namespace foo { int bar; }"
+ "namespace declToImport { using foo::bar; }",
+ Lang_CXX, "", Lang_CXX, Verifier,
+ namespaceDecl(
+ has(
+ usingShadowDecl()))));
+}
+
} // end namespace ast_matchers
} // end namespace clang
diff --git a/unittests/AST/CMakeLists.txt b/unittests/AST/CMakeLists.txt
index a7008f3e7e092..9839cdb1f2ec2 100644
--- a/unittests/AST/CMakeLists.txt
+++ b/unittests/AST/CMakeLists.txt
@@ -9,6 +9,7 @@ add_clang_unittest(ASTTests
ASTVectorTest.cpp
CommentLexer.cpp
CommentParser.cpp
+ DataCollectionTest.cpp
DeclPrinterTest.cpp
DeclTest.cpp
EvaluateAsRValueTest.cpp
@@ -20,6 +21,7 @@ add_clang_unittest(ASTTests
)
target_link_libraries(ASTTests
+ PRIVATE
clangAST
clangASTMatchers
clangBasic
diff --git a/unittests/AST/DataCollectionTest.cpp b/unittests/AST/DataCollectionTest.cpp
new file mode 100644
index 0000000000000..e8ebd16217f43
--- /dev/null
+++ b/unittests/AST/DataCollectionTest.cpp
@@ -0,0 +1,173 @@
+//===- unittests/AST/DataCollectionTest.cpp -------------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains tests for the DataCollection module.
+//
+// They work by hashing the collected data of two nodes and asserting that the
+// hash values are equal iff the nodes are considered equal.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/DataCollection.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/StmtVisitor.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Tooling/Tooling.h"
+#include "gtest/gtest.h"
+
+using namespace clang;
+using namespace tooling;
+using namespace ast_matchers;
+
+namespace {
+class StmtDataCollector : public ConstStmtVisitor<StmtDataCollector> {
+ ASTContext &Context;
+ llvm::MD5 &DataConsumer;
+
+ template <class T> void addData(const T &Data) {
+ data_collection::addDataToConsumer(DataConsumer, Data);
+ }
+
+public:
+ StmtDataCollector(const Stmt *S, ASTContext &Context, llvm::MD5 &DataConsumer)
+ : Context(Context), DataConsumer(DataConsumer) {
+ this->Visit(S);
+ }
+
+#define DEF_ADD_DATA(CLASS, CODE) \
+ template <class Dummy = void> Dummy Visit##CLASS(const CLASS *S) { \
+ CODE; \
+ ConstStmtVisitor<StmtDataCollector>::Visit##CLASS(S); \
+ }
+
+#include "clang/AST/StmtDataCollectors.inc"
+};
+} // end anonymous namespace
+
+namespace {
+struct StmtHashMatch : public MatchFinder::MatchCallback {
+ unsigned NumFound;
+ llvm::MD5::MD5Result &Hash;
+ StmtHashMatch(llvm::MD5::MD5Result &Hash) : NumFound(0), Hash(Hash) {}
+
+ void run(const MatchFinder::MatchResult &Result) override {
+ const Stmt *S = Result.Nodes.getNodeAs<Stmt>("id");
+ if (!S)
+ return;
+ ++NumFound;
+ if (NumFound > 1)
+ return;
+ llvm::MD5 MD5;
+ StmtDataCollector(S, *Result.Context, MD5);
+ MD5.final(Hash);
+ }
+};
+} // end anonymous namespace
+
+static testing::AssertionResult hashStmt(llvm::MD5::MD5Result &Hash,
+ const StatementMatcher &StmtMatch,
+ StringRef Code) {
+ StmtHashMatch Hasher(Hash);
+ MatchFinder Finder;
+ Finder.addMatcher(StmtMatch, &Hasher);
+ std::unique_ptr<FrontendActionFactory> Factory(
+ newFrontendActionFactory(&Finder));
+ if (!runToolOnCode(Factory->create(), Code))
+ return testing::AssertionFailure()
+ << "Parsing error in \"" << Code.str() << "\"";
+ if (Hasher.NumFound == 0)
+ return testing::AssertionFailure() << "Matcher didn't find any statements";
+ if (Hasher.NumFound > 1)
+ return testing::AssertionFailure()
+ << "Matcher should match only one statement "
+ "(found "
+ << Hasher.NumFound << ")";
+ return testing::AssertionSuccess();
+}
+
+static testing::AssertionResult
+isStmtHashEqual(const StatementMatcher &StmtMatch, StringRef Code1,
+ StringRef Code2) {
+ llvm::MD5::MD5Result Hash1, Hash2;
+ testing::AssertionResult Result = hashStmt(Hash1, StmtMatch, Code1);
+ if (!Result)
+ return Result;
+ if (!(Result = hashStmt(Hash2, StmtMatch, Code2)))
+ return Result;
+
+ return testing::AssertionResult(Hash1 == Hash2);
+}
+
+TEST(StmtDataCollector, TestDeclRefExpr) {
+ ASSERT_TRUE(isStmtHashEqual(declRefExpr().bind("id"), "int x, r = x;",
+ "int x, r = x;"));
+ ASSERT_FALSE(isStmtHashEqual(declRefExpr().bind("id"), "int x, r = x;",
+ "int y, r = y;"));
+ ASSERT_FALSE(isStmtHashEqual(declRefExpr().bind("id"), "int x, r = x;",
+ "namespace n { int x, r = x; };"));
+}
+
+TEST(StmtDataCollector, TestMemberExpr) {
+ ASSERT_TRUE(isStmtHashEqual(memberExpr().bind("id"),
+ "struct { int x; } X; int r = X.x;",
+ "struct { int x; } X; int r = (&X)->x;"));
+ ASSERT_TRUE(isStmtHashEqual(memberExpr().bind("id"),
+ "struct { int x; } X; int r = X.x;",
+ "struct { int x; } Y; int r = Y.x;"));
+ ASSERT_TRUE(isStmtHashEqual(memberExpr().bind("id"),
+ "struct { int x; } X; int r = X.x;",
+ "struct C { int x; } X; int r = X.C::x;"));
+ ASSERT_FALSE(isStmtHashEqual(memberExpr().bind("id"),
+ "struct { int x; } X; int r = X.x;",
+ "struct { int y; } X; int r = X.y;"));
+}
+
+TEST(StmtDataCollector, TestIntegerLiteral) {
+ ASSERT_TRUE(
+ isStmtHashEqual(integerLiteral().bind("id"), "int x = 0;", "int x = 0;"));
+ ASSERT_TRUE(
+ isStmtHashEqual(integerLiteral().bind("id"), "int x = 0;", "int x =00;"));
+ ASSERT_FALSE(
+ isStmtHashEqual(integerLiteral().bind("id"), "int x = 0;", "int x = 1;"));
+}
+
+TEST(StmtDataCollector, TestFloatingLiteral) {
+ ASSERT_TRUE(isStmtHashEqual(floatLiteral().bind("id"), "double x = .0;",
+ "double x = .0;"));
+ ASSERT_TRUE(isStmtHashEqual(floatLiteral().bind("id"), "double x = .10;",
+ "double x = .1;"));
+ ASSERT_TRUE(isStmtHashEqual(floatLiteral().bind("id"), "double x = .1;",
+ "double x = 1e-1;"));
+ ASSERT_FALSE(isStmtHashEqual(floatLiteral().bind("id"), "double x = .0;",
+ "double x = .1;"));
+}
+
+TEST(StmtDataCollector, TestStringLiteral) {
+ ASSERT_TRUE(isStmtHashEqual(stringLiteral().bind("id"), R"(char x[] = "0";)",
+ R"(char x[] = "0";)"));
+ ASSERT_FALSE(isStmtHashEqual(stringLiteral().bind("id"), R"(char x[] = "0";)",
+ R"(char x[] = "1";)"));
+}
+
+TEST(StmtDataCollector, TestCXXBoolLiteral) {
+ ASSERT_TRUE(isStmtHashEqual(cxxBoolLiteral().bind("id"), "bool x = false;",
+ "bool x = false;"));
+ ASSERT_FALSE(isStmtHashEqual(cxxBoolLiteral().bind("id"), "bool x = false;",
+ "bool x = true;"));
+}
+
+TEST(StmtDataCollector, TestCharacterLiteral) {
+ ASSERT_TRUE(isStmtHashEqual(characterLiteral().bind("id"), "char x = '0';",
+ "char x = '0';"));
+ ASSERT_TRUE(isStmtHashEqual(characterLiteral().bind("id"),
+ R"(char x = '\0';)",
+ R"(char x = '\x00';)"));
+ ASSERT_FALSE(isStmtHashEqual(characterLiteral().bind("id"), "char x = '0';",
+ "char x = '1';"));
+}
diff --git a/unittests/AST/DeclPrinterTest.cpp b/unittests/AST/DeclPrinterTest.cpp
index ae6d0f0dd2e23..4cf8bce20ea29 100644
--- a/unittests/AST/DeclPrinterTest.cpp
+++ b/unittests/AST/DeclPrinterTest.cpp
@@ -31,18 +31,25 @@ using namespace tooling;
namespace {
-void PrintDecl(raw_ostream &Out, const ASTContext *Context, const Decl *D) {
+using PrintingPolicyModifier = void (*)(PrintingPolicy &policy);
+
+void PrintDecl(raw_ostream &Out, const ASTContext *Context, const Decl *D,
+ PrintingPolicyModifier PolicyModifier) {
PrintingPolicy Policy = Context->getPrintingPolicy();
Policy.TerseOutput = true;
+ if (PolicyModifier)
+ PolicyModifier(Policy);
D->print(Out, Policy, /*Indentation*/ 0, /*PrintInstantiation*/ false);
}
class PrintMatch : public MatchFinder::MatchCallback {
SmallString<1024> Printed;
unsigned NumFoundDecls;
+ PrintingPolicyModifier PolicyModifier;
public:
- PrintMatch() : NumFoundDecls(0) {}
+ PrintMatch(PrintingPolicyModifier PolicyModifier)
+ : NumFoundDecls(0), PolicyModifier(PolicyModifier) {}
void run(const MatchFinder::MatchResult &Result) override {
const Decl *D = Result.Nodes.getNodeAs<Decl>("id");
@@ -53,7 +60,7 @@ public:
return;
llvm::raw_svector_ostream Out(Printed);
- PrintDecl(Out, Result.Context, D);
+ PrintDecl(Out, Result.Context, D, PolicyModifier);
}
StringRef getPrinted() const {
@@ -65,13 +72,12 @@ public:
}
};
-::testing::AssertionResult PrintedDeclMatches(
- StringRef Code,
- const std::vector<std::string> &Args,
- const DeclarationMatcher &NodeMatch,
- StringRef ExpectedPrinted,
- StringRef FileName) {
- PrintMatch Printer;
+::testing::AssertionResult
+PrintedDeclMatches(StringRef Code, const std::vector<std::string> &Args,
+ const DeclarationMatcher &NodeMatch,
+ StringRef ExpectedPrinted, StringRef FileName,
+ PrintingPolicyModifier PolicyModifier = nullptr) {
+ PrintMatch Printer(PolicyModifier);
MatchFinder Finder;
Finder.addMatcher(NodeMatch, &Printer);
std::unique_ptr<FrontendActionFactory> Factory(
@@ -98,27 +104,30 @@ public:
return ::testing::AssertionSuccess();
}
-::testing::AssertionResult PrintedDeclCXX98Matches(StringRef Code,
- StringRef DeclName,
- StringRef ExpectedPrinted) {
+::testing::AssertionResult
+PrintedDeclCXX98Matches(StringRef Code, StringRef DeclName,
+ StringRef ExpectedPrinted,
+ PrintingPolicyModifier PolicyModifier = nullptr) {
std::vector<std::string> Args(1, "-std=c++98");
return PrintedDeclMatches(Code,
Args,
namedDecl(hasName(DeclName)).bind("id"),
ExpectedPrinted,
- "input.cc");
+ "input.cc",
+ PolicyModifier);
}
-::testing::AssertionResult PrintedDeclCXX98Matches(
- StringRef Code,
- const DeclarationMatcher &NodeMatch,
- StringRef ExpectedPrinted) {
+::testing::AssertionResult
+PrintedDeclCXX98Matches(StringRef Code, const DeclarationMatcher &NodeMatch,
+ StringRef ExpectedPrinted,
+ PrintingPolicyModifier PolicyModifier = nullptr) {
std::vector<std::string> Args(1, "-std=c++98");
return PrintedDeclMatches(Code,
Args,
NodeMatch,
ExpectedPrinted,
- "input.cc");
+ "input.cc",
+ PolicyModifier);
}
::testing::AssertionResult PrintedDeclCXX11Matches(StringRef Code,
@@ -343,6 +352,47 @@ TEST(DeclPrinter, TestFunctionDecl1) {
"void A()"));
}
+TEST(DeclPrinter, TestFreeFunctionDecl_FullyQualifiedName) {
+ ASSERT_TRUE(PrintedDeclCXX98Matches(
+ "void A();",
+ "A",
+ "void A()",
+ [](PrintingPolicy &Policy){ Policy.FullyQualifiedName = true; }));
+}
+
+TEST(DeclPrinter, TestFreeFunctionDeclInNamespace_FullyQualifiedName) {
+ ASSERT_TRUE(PrintedDeclCXX98Matches(
+ "namespace X { void A(); };",
+ "A",
+ "void X::A()",
+ [](PrintingPolicy &Policy){ Policy.FullyQualifiedName = true; }));
+}
+
+TEST(DeclPrinter, TestMemberFunction_FullyQualifiedName) {
+ ASSERT_TRUE(PrintedDeclCXX98Matches(
+ "struct X { void A(); };",
+ "A",
+ "void X::A()",
+ [](PrintingPolicy &Policy){ Policy.FullyQualifiedName = true; }));
+}
+
+TEST(DeclPrinter, TestMemberFunctionInNamespace_FullyQualifiedName) {
+ ASSERT_TRUE(PrintedDeclCXX98Matches(
+ "namespace Z { struct X { void A(); }; }",
+ "A",
+ "void Z::X::A()",
+ [](PrintingPolicy &Policy){ Policy.FullyQualifiedName = true; }));
+}
+
+TEST(DeclPrinter, TestMemberFunctionOutside_FullyQualifiedName) {
+ ASSERT_TRUE(PrintedDeclCXX98Matches(
+ "struct X { void A(); };"
+ "void X::A() {}",
+ functionDecl(hasName("A"), isDefinition()).bind("id"),
+ "void X::A()",
+ [](PrintingPolicy &Policy){ Policy.FullyQualifiedName = true; }));
+}
+
TEST(DeclPrinter, TestFunctionDecl2) {
ASSERT_TRUE(PrintedDeclCXX98Matches(
"void A() {}",
@@ -478,6 +528,27 @@ TEST(DeclPrinter, TestCXXConstructorDecl4) {
"A(const A &a, int = 0)"));
}
+TEST(DeclPrinter, TestCXXConstructorDeclWithMemberInitializer) {
+ ASSERT_TRUE(PrintedDeclCXX98Matches(
+ "struct A {"
+ " int m;"
+ " A() : m(2) {}"
+ "};",
+ cxxConstructorDecl(ofClass(hasName("A"))).bind("id"),
+ "A()"));
+}
+
+TEST(DeclPrinter, TestCXXConstructorDeclWithMemberInitializer_NoTerseOutput) {
+ ASSERT_TRUE(PrintedDeclCXX98Matches(
+ "struct A {"
+ " int m;"
+ " A() : m(2) {}"
+ "};",
+ cxxConstructorDecl(ofClass(hasName("A"))).bind("id"),
+ "A() : m(2) {\n}\n",
+ [](PrintingPolicy &Policy){ Policy.TerseOutput = false; }));
+}
+
TEST(DeclPrinter, TestCXXConstructorDecl5) {
ASSERT_TRUE(PrintedDeclCXX11Matches(
"struct A {"
@@ -540,7 +611,7 @@ TEST(DeclPrinter, TestCXXConstructorDecl11) {
" A(T&&... ts) : T(ts)... {}"
"};",
cxxConstructorDecl(ofClass(hasName("A"))).bind("id"),
- "A<T...>(T &&...ts) : T(ts)... {}"));
+ "A<T...>(T &&...ts)"));
}
TEST(DeclPrinter, TestCXXDestructorDecl1) {
diff --git a/unittests/AST/StmtPrinterTest.cpp b/unittests/AST/StmtPrinterTest.cpp
index 12b203236c996..a0644401a76ab 100644
--- a/unittests/AST/StmtPrinterTest.cpp
+++ b/unittests/AST/StmtPrinterTest.cpp
@@ -31,18 +31,26 @@ using namespace tooling;
namespace {
-void PrintStmt(raw_ostream &Out, const ASTContext *Context, const Stmt *S) {
+using PolicyAdjusterType =
+ Optional<llvm::function_ref<void(PrintingPolicy &Policy)>>;
+
+void PrintStmt(raw_ostream &Out, const ASTContext *Context, const Stmt *S,
+ PolicyAdjusterType PolicyAdjuster) {
assert(S != nullptr && "Expected non-null Stmt");
PrintingPolicy Policy = Context->getPrintingPolicy();
+ if (PolicyAdjuster)
+ (*PolicyAdjuster)(Policy);
S->printPretty(Out, /*Helper*/ nullptr, Policy);
}
class PrintMatch : public MatchFinder::MatchCallback {
SmallString<1024> Printed;
unsigned NumFoundStmts;
+ PolicyAdjusterType PolicyAdjuster;
public:
- PrintMatch() : NumFoundStmts(0) {}
+ PrintMatch(PolicyAdjusterType PolicyAdjuster)
+ : NumFoundStmts(0), PolicyAdjuster(PolicyAdjuster) {}
void run(const MatchFinder::MatchResult &Result) override {
const Stmt *S = Result.Nodes.getNodeAs<Stmt>("id");
@@ -53,7 +61,7 @@ public:
return;
llvm::raw_svector_ostream Out(Printed);
- PrintStmt(Out, Result.Context, S);
+ PrintStmt(Out, Result.Context, S, PolicyAdjuster);
}
StringRef getPrinted() const {
@@ -68,9 +76,10 @@ public:
template <typename T>
::testing::AssertionResult
PrintedStmtMatches(StringRef Code, const std::vector<std::string> &Args,
- const T &NodeMatch, StringRef ExpectedPrinted) {
+ const T &NodeMatch, StringRef ExpectedPrinted,
+ PolicyAdjusterType PolicyAdjuster = None) {
- PrintMatch Printer;
+ PrintMatch Printer(PolicyAdjuster);
MatchFinder Finder;
Finder.addMatcher(NodeMatch, &Printer);
std::unique_ptr<FrontendActionFactory> Factory(
@@ -122,11 +131,13 @@ PrintedStmtCXX98Matches(StringRef Code, const StatementMatcher &NodeMatch,
::testing::AssertionResult
PrintedStmtCXX11Matches(StringRef Code, const StatementMatcher &NodeMatch,
- StringRef ExpectedPrinted) {
+ StringRef ExpectedPrinted,
+ PolicyAdjusterType PolicyAdjuster = None) {
std::vector<std::string> Args;
Args.push_back("-std=c++11");
Args.push_back("-Wno-unused-value");
- return PrintedStmtMatches(Code, Args, NodeMatch, ExpectedPrinted);
+ return PrintedStmtMatches(Code, Args, NodeMatch, ExpectedPrinted,
+ PolicyAdjuster);
}
::testing::AssertionResult PrintedStmtMSMatches(
@@ -146,6 +157,17 @@ PrintedStmtCXX11Matches(StringRef Code, const StatementMatcher &NodeMatch,
ExpectedPrinted);
}
+::testing::AssertionResult
+PrintedStmtObjCMatches(StringRef Code, const StatementMatcher &NodeMatch,
+ StringRef ExpectedPrinted,
+ PolicyAdjusterType PolicyAdjuster = None) {
+ std::vector<std::string> Args;
+ Args.push_back("-ObjC");
+ Args.push_back("-fobjc-runtime=macosx-10.12.0");
+ return PrintedStmtMatches(Code, Args, NodeMatch, ExpectedPrinted,
+ PolicyAdjuster);
+}
+
} // unnamed namespace
TEST(StmtPrinter, TestIntegerLiteral) {
@@ -214,3 +236,41 @@ TEST(StmtPrinter, TestCXXConversionDeclExplicit) {
"(a & b)"));
// WRONG; Should be: (a & b).operator void *()
}
+
+TEST(StmtPrinter, TestNoImplicitBases) {
+ const char *CPPSource = R"(
+class A {
+ int field;
+ int member() { return field; }
+};
+)";
+ // No implicit 'this'.
+ ASSERT_TRUE(PrintedStmtCXX11Matches(
+ CPPSource, memberExpr(anything()).bind("id"), "field",
+ PolicyAdjusterType(
+ [](PrintingPolicy &PP) { PP.SuppressImplicitBase = true; })));
+ // Print implicit 'this'.
+ ASSERT_TRUE(PrintedStmtCXX11Matches(
+ CPPSource, memberExpr(anything()).bind("id"), "this->field"));
+
+ const char *ObjCSource = R"(
+@interface I {
+ int ivar;
+}
+@end
+@implementation I
+- (int) method {
+ return ivar;
+}
+@end
+ )";
+ // No implicit 'self'.
+ ASSERT_TRUE(PrintedStmtObjCMatches(ObjCSource, returnStmt().bind("id"),
+ "return ivar;\n",
+ PolicyAdjusterType([](PrintingPolicy &PP) {
+ PP.SuppressImplicitBase = true;
+ })));
+ // Print implicit 'self'.
+ ASSERT_TRUE(PrintedStmtObjCMatches(ObjCSource, returnStmt().bind("id"),
+ "return self->ivar;\n"));
+}
diff --git a/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp b/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
index 7bc8421bab2ff..7e7d02707112a 100644
--- a/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
+++ b/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
@@ -1315,6 +1315,14 @@ TEST(Matcher, IsDefinition) {
cxxMethodDecl(hasName("a"), isDefinition());
EXPECT_TRUE(matches("class A { void a() {} };", DefinitionOfMethodA));
EXPECT_TRUE(notMatches("class A { void a(); };", DefinitionOfMethodA));
+
+ DeclarationMatcher DefinitionOfObjCMethodA =
+ objcMethodDecl(hasName("a"), isDefinition());
+ EXPECT_TRUE(matchesObjC("@interface A @end "
+ "@implementation A; -(void)a {} @end",
+ DefinitionOfObjCMethodA));
+ EXPECT_TRUE(notMatchesObjC("@interface A; - (void)a; @end",
+ DefinitionOfObjCMethodA));
}
TEST(Matcher, HandlesNullQualTypes) {
@@ -1983,5 +1991,43 @@ TEST(HasExternalFormalLinkage, Basic) {
namedDecl(hasExternalFormalLinkage())));
}
+TEST(HasDefaultArgument, Basic) {
+ EXPECT_TRUE(matches("void x(int val = 0) {}",
+ parmVarDecl(hasDefaultArgument())));
+ EXPECT_TRUE(notMatches("void x(int val) {}",
+ parmVarDecl(hasDefaultArgument())));
+}
+
+TEST(IsArray, Basic) {
+ EXPECT_TRUE(matches("struct MyClass {}; MyClass *p1 = new MyClass[10];",
+ cxxNewExpr(isArray())));
+}
+
+TEST(HasArraySize, Basic) {
+ EXPECT_TRUE(matches("struct MyClass {}; MyClass *p1 = new MyClass[10];",
+ cxxNewExpr(hasArraySize(integerLiteral(equals(10))))));
+}
+
+TEST(HasDefinition, MatchesStructDefinition) {
+ EXPECT_TRUE(matches("struct x {};",
+ cxxRecordDecl(hasDefinition())));
+ EXPECT_TRUE(notMatches("struct x;",
+ cxxRecordDecl(hasDefinition())));
+}
+
+TEST(HasDefinition, MatchesClassDefinition) {
+ EXPECT_TRUE(matches("class x {};",
+ cxxRecordDecl(hasDefinition())));
+ EXPECT_TRUE(notMatches("class x;",
+ cxxRecordDecl(hasDefinition())));
+}
+
+TEST(HasDefinition, MatchesUnionDefinition) {
+ EXPECT_TRUE(matches("union x {};",
+ cxxRecordDecl(hasDefinition())));
+ EXPECT_TRUE(notMatches("union x;",
+ cxxRecordDecl(hasDefinition())));
+}
+
} // namespace ast_matchers
} // namespace clang
diff --git a/unittests/ASTMatchers/ASTMatchersNodeTest.cpp b/unittests/ASTMatchers/ASTMatchersNodeTest.cpp
index 58c26eafd7e0d..59fadadbedc75 100644
--- a/unittests/ASTMatchers/ASTMatchersNodeTest.cpp
+++ b/unittests/ASTMatchers/ASTMatchersNodeTest.cpp
@@ -1184,6 +1184,10 @@ TEST(TypeMatching, MatchesAutoTypes) {
EXPECT_TRUE(matches("int v[] = { 2, 3 }; void f() { for (int i : v) {} }",
autoType()));
+ EXPECT_TRUE(matches("auto i = 2;", varDecl(hasType(isInteger()))));
+ EXPECT_TRUE(matches("struct X{}; auto x = X{};",
+ varDecl(hasType(recordDecl(hasName("X"))))));
+
// FIXME: Matching against the type-as-written can't work here, because the
// type as written was not deduced.
//EXPECT_TRUE(matches("auto a = 1;",
@@ -1586,7 +1590,7 @@ TEST(ObjCMessageExprMatcher, SimpleExprs) {
)));
}
-TEST(ObjCDeclMacher, CoreDecls) {
+TEST(ObjCDeclMatcher, CoreDecls) {
std::string ObjCString =
"@protocol Proto "
"- (void)protoDidThing; "
@@ -1601,6 +1605,9 @@ TEST(ObjCDeclMacher, CoreDecls) {
"{ id _ivar; } "
"- (void)anything {} "
"@end "
+ "@implementation Thing (ABC) "
+ "- (void)abc_doThing {} "
+ "@end "
;
EXPECT_TRUE(matchesObjC(
@@ -1608,9 +1615,15 @@ TEST(ObjCDeclMacher, CoreDecls) {
objcProtocolDecl(hasName("Proto"))));
EXPECT_TRUE(matchesObjC(
ObjCString,
+ objcImplementationDecl(hasName("Thing"))));
+ EXPECT_TRUE(matchesObjC(
+ ObjCString,
objcCategoryDecl(hasName("ABC"))));
EXPECT_TRUE(matchesObjC(
ObjCString,
+ objcCategoryImplDecl(hasName("ABC"))));
+ EXPECT_TRUE(matchesObjC(
+ ObjCString,
objcMethodDecl(hasName("protoDidThing"))));
EXPECT_TRUE(matchesObjC(
ObjCString,
@@ -1626,5 +1639,28 @@ TEST(ObjCDeclMacher, CoreDecls) {
objcPropertyDecl(hasName("enabled"))));
}
+TEST(ObjCStmtMatcher, ExceptionStmts) {
+ std::string ObjCString =
+ "void f(id obj) {"
+ " @try {"
+ " @throw obj;"
+ " } @catch (...) {"
+ " } @finally {}"
+ "}";
+
+ EXPECT_TRUE(matchesObjC(
+ ObjCString,
+ objcTryStmt()));
+ EXPECT_TRUE(matchesObjC(
+ ObjCString,
+ objcThrowStmt()));
+ EXPECT_TRUE(matchesObjC(
+ ObjCString,
+ objcCatchStmt()));
+ EXPECT_TRUE(matchesObjC(
+ ObjCString,
+ objcFinallyStmt()));
+}
+
} // namespace ast_matchers
} // namespace clang
diff --git a/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp b/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp
index 5957c7fa41dad..e699e19262e2e 100644
--- a/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp
+++ b/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp
@@ -230,6 +230,17 @@ TEST(HasDeclaration, HasGetDeclTraitTest) {
"Expected TemplateSpecializationType to *not* have a getDecl.");
}
+TEST(HasDeclaration, ElaboratedType) {
+ EXPECT_TRUE(matches(
+ "namespace n { template <typename T> struct X {}; }"
+ "void f(n::X<int>);",
+ parmVarDecl(hasType(qualType(hasDeclaration(cxxRecordDecl()))))));
+ EXPECT_TRUE(matches(
+ "namespace n { template <typename T> struct X {}; }"
+ "void f(n::X<int>);",
+ parmVarDecl(hasType(elaboratedType(hasDeclaration(cxxRecordDecl()))))));
+}
+
TEST(HasDeclaration, HasDeclarationOfTypeWithDecl) {
EXPECT_TRUE(matches("typedef int X; X a;",
varDecl(hasName("a"),
@@ -242,6 +253,13 @@ TEST(HasDeclaration, HasDeclarationOfTemplateSpecializationType) {
EXPECT_TRUE(matches("template <typename T> class A {}; A<int> a;",
varDecl(hasType(templateSpecializationType(
hasDeclaration(namedDecl(hasName("A"))))))));
+ EXPECT_TRUE(matches("template <typename T> class A {};"
+ "template <typename T> class B { A<T> a; };",
+ fieldDecl(hasType(templateSpecializationType(
+ hasDeclaration(namedDecl(hasName("A"))))))));
+ EXPECT_TRUE(matches("template <typename T> class A {}; A<int> a;",
+ varDecl(hasType(templateSpecializationType(
+ hasDeclaration(cxxRecordDecl()))))));
}
TEST(HasDeclaration, HasDeclarationOfCXXNewExpr) {
@@ -250,6 +268,12 @@ TEST(HasDeclaration, HasDeclarationOfCXXNewExpr) {
cxxNewExpr(hasDeclaration(functionDecl(parameterCountIs(1))))));
}
+TEST(HasDeclaration, HasDeclarationOfTypeAlias) {
+ EXPECT_TRUE(matches("template <typename T> using C = T; C<int> c;",
+ varDecl(hasType(templateSpecializationType(
+ hasDeclaration(typeAliasTemplateDecl()))))));
+}
+
TEST(HasUnqualifiedDesugaredType, DesugarsUsing) {
EXPECT_TRUE(
matches("struct A {}; using B = A; B b;",
@@ -785,14 +809,18 @@ TEST(HasAnyConstructorInitializer, ForField) {
static const char Code[] =
"class Baz { };"
"class Foo {"
- " Foo() : foo_() { }"
+ " Foo() : foo_(), bar_() { }"
" Baz foo_;"
- " Baz bar_;"
+ " struct {"
+ " Baz bar_;"
+ " };"
"};";
EXPECT_TRUE(matches(Code, cxxConstructorDecl(hasAnyConstructorInitializer(
forField(hasType(recordDecl(hasName("Baz"))))))));
EXPECT_TRUE(matches(Code, cxxConstructorDecl(hasAnyConstructorInitializer(
forField(hasName("foo_"))))));
+ EXPECT_TRUE(matches(Code, cxxConstructorDecl(hasAnyConstructorInitializer(
+ forField(hasName("bar_"))))));
EXPECT_TRUE(notMatches(Code, cxxConstructorDecl(hasAnyConstructorInitializer(
forField(hasType(recordDecl(hasName("Bar"))))))));
}
@@ -2207,8 +2235,7 @@ TEST(Matcher, HasAnyDeclaration) {
functionDecl(hasName("bar"))))));
}
-TEST(SubstTemplateTypeParmType, HasReplacementType)
-{
+TEST(SubstTemplateTypeParmType, HasReplacementType) {
std::string Fragment = "template<typename T>"
"double F(T t);"
"int i;"
@@ -2224,5 +2251,13 @@ TEST(SubstTemplateTypeParmType, HasReplacementType)
substTemplateTypeParmType(hasReplacementType(qualType()))));
}
+TEST(ClassTemplateSpecializationDecl, HasSpecializedTemplate) {
+ auto Matcher = classTemplateSpecializationDecl(
+ hasSpecializedTemplate(classTemplateDecl()));
+ EXPECT_TRUE(
+ matches("template<typename T> class A {}; typedef A<int> B;", Matcher));
+ EXPECT_TRUE(notMatches("template<typename T> class A {};", Matcher));
+}
+
} // namespace ast_matchers
} // namespace clang
diff --git a/unittests/ASTMatchers/CMakeLists.txt b/unittests/ASTMatchers/CMakeLists.txt
index 563303157a8ee..a876fc2d33605 100644
--- a/unittests/ASTMatchers/CMakeLists.txt
+++ b/unittests/ASTMatchers/CMakeLists.txt
@@ -18,6 +18,7 @@ add_clang_unittest(ASTMatchersTests
ASTMatchersTraversalTest.cpp)
target_link_libraries(ASTMatchersTests
+ PRIVATE
clangAST
clangASTMatchers
clangBasic
diff --git a/unittests/ASTMatchers/Dynamic/CMakeLists.txt b/unittests/ASTMatchers/Dynamic/CMakeLists.txt
index 506a65549e4f3..848a820adeadb 100644
--- a/unittests/ASTMatchers/Dynamic/CMakeLists.txt
+++ b/unittests/ASTMatchers/Dynamic/CMakeLists.txt
@@ -8,6 +8,7 @@ add_clang_unittest(DynamicASTMatchersTests
RegistryTest.cpp)
target_link_libraries(DynamicASTMatchersTests
+ PRIVATE
clangAST
clangASTMatchers
clangBasic
diff --git a/unittests/Analysis/CMakeLists.txt b/unittests/Analysis/CMakeLists.txt
index 62db8f652e111..0056f82402aae 100644
--- a/unittests/Analysis/CMakeLists.txt
+++ b/unittests/Analysis/CMakeLists.txt
@@ -8,6 +8,7 @@ add_clang_unittest(ClangAnalysisTests
)
target_link_libraries(ClangAnalysisTests
+ PRIVATE
clangAnalysis
clangAST
clangASTMatchers
diff --git a/unittests/Analysis/CloneDetectionTest.cpp b/unittests/Analysis/CloneDetectionTest.cpp
index 6d8ce3495fa8a..965a4bc308338 100644
--- a/unittests/Analysis/CloneDetectionTest.cpp
+++ b/unittests/Analysis/CloneDetectionTest.cpp
@@ -69,8 +69,9 @@ TEST(CloneDetector, FilterFunctionsByName) {
// all statements from functions which names start with "bar".
std::vector<CloneDetector::CloneGroup> CloneGroups;
Detector.findClones(CloneGroups, NoBarFunctionConstraint(),
- RecursiveCloneTypeIIConstraint(),
+ RecursiveCloneTypeIIHashConstraint(),
MinComplexityConstraint(2), MinGroupSizeConstraint(2),
+ RecursiveCloneTypeIIVerifyConstraint(),
OnlyLargestCloneConstraint());
ASSERT_EQ(CloneGroups.size(), 1u);
@@ -86,8 +87,9 @@ TEST(CloneDetector, FilterFunctionsByName) {
// Retry above's example without the filter...
CloneGroups.clear();
- Detector.findClones(CloneGroups, RecursiveCloneTypeIIConstraint(),
+ Detector.findClones(CloneGroups, RecursiveCloneTypeIIHashConstraint(),
MinComplexityConstraint(2), MinGroupSizeConstraint(2),
+ RecursiveCloneTypeIIVerifyConstraint(),
OnlyLargestCloneConstraint());
ASSERT_EQ(CloneGroups.size(), 1u);
ASSERT_EQ(CloneGroups.front().size(), 4u);
diff --git a/unittests/Basic/CMakeLists.txt b/unittests/Basic/CMakeLists.txt
index 3a9f34f3d2754..b46c067dc2efe 100644
--- a/unittests/Basic/CMakeLists.txt
+++ b/unittests/Basic/CMakeLists.txt
@@ -12,6 +12,7 @@ add_clang_unittest(BasicTests
)
target_link_libraries(BasicTests
+ PRIVATE
clangBasic
clangLex
)
diff --git a/unittests/Basic/DiagnosticTest.cpp b/unittests/Basic/DiagnosticTest.cpp
index 0111b172472b8..3068e1c34050d 100644
--- a/unittests/Basic/DiagnosticTest.cpp
+++ b/unittests/Basic/DiagnosticTest.cpp
@@ -8,6 +8,7 @@
//===----------------------------------------------------------------------===//
#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/DiagnosticError.h"
#include "clang/Basic/DiagnosticIDs.h"
#include "gtest/gtest.h"
@@ -72,4 +73,25 @@ TEST(DiagnosticTest, suppressAfterFatalError) {
}
}
+TEST(DiagnosticTest, diagnosticError) {
+ DiagnosticsEngine Diags(new DiagnosticIDs(), new DiagnosticOptions,
+ new IgnoringDiagConsumer());
+ PartialDiagnostic::StorageAllocator Alloc;
+ llvm::Expected<std::pair<int, int>> Value = DiagnosticError::create(
+ SourceLocation(), PartialDiagnostic(diag::err_cannot_open_file, Alloc)
+ << "file"
+ << "error");
+ ASSERT_TRUE(!Value);
+ llvm::Error Err = Value.takeError();
+ Optional<PartialDiagnosticAt> ErrDiag = DiagnosticError::take(Err);
+ llvm::cantFail(std::move(Err));
+ ASSERT_FALSE(!ErrDiag);
+ EXPECT_EQ(ErrDiag->first, SourceLocation());
+ EXPECT_EQ(ErrDiag->second.getDiagID(), diag::err_cannot_open_file);
+
+ Value = std::make_pair(20, 1);
+ ASSERT_FALSE(!Value);
+ EXPECT_EQ(*Value, std::make_pair(20, 1));
+ EXPECT_EQ(Value->first, 20);
+}
}
diff --git a/unittests/Basic/FileManagerTest.cpp b/unittests/Basic/FileManagerTest.cpp
index a19535e1047cc..a2a6c6aebe4b0 100644
--- a/unittests/Basic/FileManagerTest.cpp
+++ b/unittests/Basic/FileManagerTest.cpp
@@ -10,6 +10,7 @@
#include "clang/Basic/FileManager.h"
#include "clang/Basic/FileSystemOptions.h"
#include "clang/Basic/FileSystemStatCache.h"
+#include "clang/Basic/VirtualFileSystem.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/Support/Path.h"
@@ -296,4 +297,30 @@ TEST_F(FileManagerTest, getVirtualFileWithDifferentName) {
#endif // !LLVM_ON_WIN32
+TEST_F(FileManagerTest, makeAbsoluteUsesVFS) {
+ SmallString<64> CustomWorkingDir;
+#ifdef LLVM_ON_WIN32
+ CustomWorkingDir = "C:";
+#else
+ CustomWorkingDir = "/";
+#endif
+ llvm::sys::path::append(CustomWorkingDir, "some", "weird", "path");
+
+ auto FS =
+ IntrusiveRefCntPtr<vfs::InMemoryFileSystem>(new vfs::InMemoryFileSystem);
+ // setCurrentworkingdirectory must finish without error.
+ ASSERT_TRUE(!FS->setCurrentWorkingDirectory(CustomWorkingDir));
+
+ FileSystemOptions Opts;
+ FileManager Manager(Opts, FS);
+
+ SmallString<64> Path("a/foo.cpp");
+
+ SmallString<64> ExpectedResult(CustomWorkingDir);
+ llvm::sys::path::append(ExpectedResult, Path);
+
+ ASSERT_TRUE(Manager.makeAbsolutePath(Path));
+ EXPECT_EQ(Path, ExpectedResult);
+}
+
} // anonymous namespace
diff --git a/unittests/Basic/VirtualFileSystemTest.cpp b/unittests/Basic/VirtualFileSystemTest.cpp
index 40add2195b586..f9efbeaee565b 100644
--- a/unittests/Basic/VirtualFileSystemTest.cpp
+++ b/unittests/Basic/VirtualFileSystemTest.cpp
@@ -760,6 +760,88 @@ TEST_F(InMemoryFileSystemTest, WorkingDirectory) {
NormalizedFS.getCurrentWorkingDirectory().get()));
}
+TEST_F(InMemoryFileSystemTest, AddFileWithUser) {
+ FS.addFile("/a/b/c", 0, MemoryBuffer::getMemBuffer("abc"), 0xFEEDFACE);
+ auto Stat = FS.status("/a");
+ ASSERT_FALSE(Stat.getError()) << Stat.getError() << "\n" << FS.toString();
+ ASSERT_TRUE(Stat->isDirectory());
+ ASSERT_EQ(0xFEEDFACE, Stat->getUser());
+ Stat = FS.status("/a/b");
+ ASSERT_FALSE(Stat.getError()) << Stat.getError() << "\n" << FS.toString();
+ ASSERT_TRUE(Stat->isDirectory());
+ ASSERT_EQ(0xFEEDFACE, Stat->getUser());
+ Stat = FS.status("/a/b/c");
+ ASSERT_FALSE(Stat.getError()) << Stat.getError() << "\n" << FS.toString();
+ ASSERT_TRUE(Stat->isRegularFile());
+ ASSERT_EQ(sys::fs::perms::all_all, Stat->getPermissions());
+ ASSERT_EQ(0xFEEDFACE, Stat->getUser());
+}
+
+TEST_F(InMemoryFileSystemTest, AddFileWithGroup) {
+ FS.addFile("/a/b/c", 0, MemoryBuffer::getMemBuffer("abc"), None, 0xDABBAD00);
+ auto Stat = FS.status("/a");
+ ASSERT_FALSE(Stat.getError()) << Stat.getError() << "\n" << FS.toString();
+ ASSERT_TRUE(Stat->isDirectory());
+ ASSERT_EQ(0xDABBAD00, Stat->getGroup());
+ Stat = FS.status("/a/b");
+ ASSERT_TRUE(Stat->isDirectory());
+ ASSERT_FALSE(Stat.getError()) << Stat.getError() << "\n" << FS.toString();
+ ASSERT_EQ(0xDABBAD00, Stat->getGroup());
+ Stat = FS.status("/a/b/c");
+ ASSERT_FALSE(Stat.getError()) << Stat.getError() << "\n" << FS.toString();
+ ASSERT_TRUE(Stat->isRegularFile());
+ ASSERT_EQ(sys::fs::perms::all_all, Stat->getPermissions());
+ ASSERT_EQ(0xDABBAD00, Stat->getGroup());
+}
+
+TEST_F(InMemoryFileSystemTest, AddFileWithFileType) {
+ FS.addFile("/a/b/c", 0, MemoryBuffer::getMemBuffer("abc"), None, None,
+ sys::fs::file_type::socket_file);
+ auto Stat = FS.status("/a");
+ ASSERT_FALSE(Stat.getError()) << Stat.getError() << "\n" << FS.toString();
+ ASSERT_TRUE(Stat->isDirectory());
+ Stat = FS.status("/a/b");
+ ASSERT_FALSE(Stat.getError()) << Stat.getError() << "\n" << FS.toString();
+ ASSERT_TRUE(Stat->isDirectory());
+ Stat = FS.status("/a/b/c");
+ ASSERT_FALSE(Stat.getError()) << Stat.getError() << "\n" << FS.toString();
+ ASSERT_EQ(sys::fs::file_type::socket_file, Stat->getType());
+ ASSERT_EQ(sys::fs::perms::all_all, Stat->getPermissions());
+}
+
+TEST_F(InMemoryFileSystemTest, AddFileWithPerms) {
+ FS.addFile("/a/b/c", 0, MemoryBuffer::getMemBuffer("abc"), None, None,
+ None, sys::fs::perms::owner_read | sys::fs::perms::owner_write);
+ auto Stat = FS.status("/a");
+ ASSERT_FALSE(Stat.getError()) << Stat.getError() << "\n" << FS.toString();
+ ASSERT_TRUE(Stat->isDirectory());
+ ASSERT_EQ(sys::fs::perms::owner_read | sys::fs::perms::owner_write |
+ sys::fs::perms::owner_exe, Stat->getPermissions());
+ Stat = FS.status("/a/b");
+ ASSERT_FALSE(Stat.getError()) << Stat.getError() << "\n" << FS.toString();
+ ASSERT_TRUE(Stat->isDirectory());
+ ASSERT_EQ(sys::fs::perms::owner_read | sys::fs::perms::owner_write |
+ sys::fs::perms::owner_exe, Stat->getPermissions());
+ Stat = FS.status("/a/b/c");
+ ASSERT_FALSE(Stat.getError()) << Stat.getError() << "\n" << FS.toString();
+ ASSERT_TRUE(Stat->isRegularFile());
+ ASSERT_EQ(sys::fs::perms::owner_read | sys::fs::perms::owner_write,
+ Stat->getPermissions());
+}
+
+TEST_F(InMemoryFileSystemTest, AddDirectoryThenAddChild) {
+ FS.addFile("/a", 0, MemoryBuffer::getMemBuffer(""), /*User=*/None,
+ /*Group=*/None, sys::fs::file_type::directory_file);
+ FS.addFile("/a/b", 0, MemoryBuffer::getMemBuffer("abc"), /*User=*/None,
+ /*Group=*/None, sys::fs::file_type::regular_file);
+ auto Stat = FS.status("/a");
+ ASSERT_FALSE(Stat.getError()) << Stat.getError() << "\n" << FS.toString();
+ ASSERT_TRUE(Stat->isDirectory());
+ Stat = FS.status("/a/b");
+ ASSERT_FALSE(Stat.getError()) << Stat.getError() << "\n" << FS.toString();
+ ASSERT_TRUE(Stat->isRegularFile());
+}
+
// NOTE: in the tests below, we use '//root/' as our root directory, since it is
// a legal *absolute* path on Windows as well as *nix.
class VFSFromYAMLTest : public ::testing::Test {
diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt
index b622d66af4ed9..090de3b06e060 100644
--- a/unittests/CMakeLists.txt
+++ b/unittests/CMakeLists.txt
@@ -19,6 +19,7 @@ if(CLANG_ENABLE_STATIC_ANALYZER)
endif()
add_subdirectory(ASTMatchers)
add_subdirectory(AST)
+add_subdirectory(CrossTU)
add_subdirectory(Tooling)
add_subdirectory(Format)
add_subdirectory(Rewrite)
diff --git a/unittests/CodeGen/CMakeLists.txt b/unittests/CodeGen/CMakeLists.txt
index 27a513a2f9828..3fb79a03075dc 100644
--- a/unittests/CodeGen/CMakeLists.txt
+++ b/unittests/CodeGen/CMakeLists.txt
@@ -5,11 +5,16 @@ set(LLVM_LINK_COMPONENTS
add_clang_unittest(ClangCodeGenTests
BufferSourceTest.cpp
+ CodeGenExternalTest.cpp
+ IncrementalProcessingTest.cpp
)
target_link_libraries(ClangCodeGenTests
+ PRIVATE
+ clangAST
clangBasic
clangCodeGen
clangFrontend
+ clangLex
clangParse
)
diff --git a/unittests/CodeGen/CodeGenExternalTest.cpp b/unittests/CodeGen/CodeGenExternalTest.cpp
new file mode 100644
index 0000000000000..bcec3eab06fca
--- /dev/null
+++ b/unittests/CodeGen/CodeGenExternalTest.cpp
@@ -0,0 +1,302 @@
+//===- unittests/CodeGen/CodeGenExternalTest.cpp - test external CodeGen -===//
+//
+// 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/ASTContext.h"
+#include "clang/AST/GlobalDecl.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/Basic/TargetInfo.h"
+#include "clang/CodeGen/CodeGenABITypes.h"
+#include "clang/CodeGen/ModuleBuilder.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Parse/ParseAST.h"
+#include "clang/Sema/Sema.h"
+#include "llvm/ADT/Triple.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/Host.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace clang;
+
+namespace {
+
+// Mocks up a language using Clang code generation as a library and
+// tests some basic functionality there.
+// - CodeGen->GetAddrOfGlobal
+// - CodeGen::convertTypeForMemory
+// - CodeGen::getLLVMFieldNumber
+
+static const bool DebugThisTest = false;
+
+// forward declarations
+struct MyASTConsumer;
+static void test_codegen_fns(MyASTConsumer *my);
+static bool test_codegen_fns_ran;
+
+// This forwards the calls to the Clang CodeGenerator
+// so that we can test CodeGen functions while it is open.
+// It accumulates toplevel decls in HandleTopLevelDecl and
+// calls test_codegen_fns() in HandleTranslationUnit
+// before forwarding that function to the CodeGenerator.
+
+struct MyASTConsumer : public ASTConsumer {
+ std::unique_ptr<CodeGenerator> Builder;
+ std::vector<Decl*> toplevel_decls;
+
+ MyASTConsumer(std::unique_ptr<CodeGenerator> Builder_in)
+ : ASTConsumer(), Builder(std::move(Builder_in))
+ {
+ }
+
+ ~MyASTConsumer() { }
+
+ void Initialize(ASTContext &Context) override;
+ void HandleCXXStaticMemberVarInstantiation(VarDecl *VD) override;
+ bool HandleTopLevelDecl(DeclGroupRef D) override;
+ void HandleInlineFunctionDefinition(FunctionDecl *D) override;
+ void HandleInterestingDecl(DeclGroupRef D) override;
+ void HandleTranslationUnit(ASTContext &Ctx) override;
+ void HandleTagDeclDefinition(TagDecl *D) override;
+ void HandleTagDeclRequiredDefinition(const TagDecl *D) override;
+ void HandleCXXImplicitFunctionInstantiation(FunctionDecl *D) override;
+ void HandleTopLevelDeclInObjCContainer(DeclGroupRef D) override;
+ void HandleImplicitImportDecl(ImportDecl *D) override;
+ void CompleteTentativeDefinition(VarDecl *D) override;
+ void AssignInheritanceModel(CXXRecordDecl *RD) override;
+ void HandleVTable(CXXRecordDecl *RD) override;
+ ASTMutationListener *GetASTMutationListener() override;
+ ASTDeserializationListener *GetASTDeserializationListener() override;
+ void PrintStats() override;
+ bool shouldSkipFunctionBody(Decl *D) override;
+};
+
+void MyASTConsumer::Initialize(ASTContext &Context) {
+ Builder->Initialize(Context);
+}
+
+bool MyASTConsumer::HandleTopLevelDecl(DeclGroupRef DG) {
+
+ for (DeclGroupRef::iterator I = DG.begin(), E = DG.end(); I != E; ++I) {
+ toplevel_decls.push_back(*I);
+ }
+
+ return Builder->HandleTopLevelDecl(DG);
+}
+
+void MyASTConsumer::HandleInlineFunctionDefinition(FunctionDecl *D) {
+ Builder->HandleInlineFunctionDefinition(D);
+}
+
+void MyASTConsumer::HandleInterestingDecl(DeclGroupRef D) {
+ Builder->HandleInterestingDecl(D);
+}
+
+void MyASTConsumer::HandleTranslationUnit(ASTContext &Context) {
+ test_codegen_fns(this);
+ // HandleTranslationUnit can close the module
+ Builder->HandleTranslationUnit(Context);
+}
+
+void MyASTConsumer::HandleTagDeclDefinition(TagDecl *D) {
+ Builder->HandleTagDeclDefinition(D);
+}
+
+void MyASTConsumer::HandleTagDeclRequiredDefinition(const TagDecl *D) {
+ Builder->HandleTagDeclRequiredDefinition(D);
+}
+
+void MyASTConsumer::HandleCXXImplicitFunctionInstantiation(FunctionDecl *D) {
+ Builder->HandleCXXImplicitFunctionInstantiation(D);
+}
+
+void MyASTConsumer::HandleTopLevelDeclInObjCContainer(DeclGroupRef D) {
+ Builder->HandleTopLevelDeclInObjCContainer(D);
+}
+
+void MyASTConsumer::HandleImplicitImportDecl(ImportDecl *D) {
+ Builder->HandleImplicitImportDecl(D);
+}
+
+void MyASTConsumer::CompleteTentativeDefinition(VarDecl *D) {
+ Builder->CompleteTentativeDefinition(D);
+}
+
+void MyASTConsumer::AssignInheritanceModel(CXXRecordDecl *RD) {
+ Builder->AssignInheritanceModel(RD);
+}
+
+void MyASTConsumer::HandleCXXStaticMemberVarInstantiation(VarDecl *VD) {
+ Builder->HandleCXXStaticMemberVarInstantiation(VD);
+}
+
+void MyASTConsumer::HandleVTable(CXXRecordDecl *RD) {
+ Builder->HandleVTable(RD);
+ }
+
+ASTMutationListener *MyASTConsumer::GetASTMutationListener() {
+ return Builder->GetASTMutationListener();
+}
+
+ASTDeserializationListener *MyASTConsumer::GetASTDeserializationListener() {
+ return Builder->GetASTDeserializationListener();
+}
+
+void MyASTConsumer::PrintStats() {
+ Builder->PrintStats();
+}
+
+bool MyASTConsumer::shouldSkipFunctionBody(Decl *D) {
+ return Builder->shouldSkipFunctionBody(D);
+}
+
+const char TestProgram[] =
+ "struct mytest_struct { char x; short y; char p; long z; };\n"
+ "int mytest_fn(int x) { return x; }\n";
+
+// This function has the real test code here
+static void test_codegen_fns(MyASTConsumer *my) {
+
+ bool mytest_fn_ok = false;
+ bool mytest_struct_ok = false;
+
+ CodeGen::CodeGenModule &CGM = my->Builder->CGM();
+
+ for (auto decl : my->toplevel_decls ) {
+ if (FunctionDecl *fd = dyn_cast<FunctionDecl>(decl)) {
+ if (fd->getName() == "mytest_fn") {
+ Constant *c = my->Builder->GetAddrOfGlobal(GlobalDecl(fd), false);
+ // Verify that we got a function.
+ ASSERT_TRUE(c != NULL);
+ if (DebugThisTest) {
+ c->print(dbgs(), true);
+ dbgs() << "\n";
+ }
+ mytest_fn_ok = true;
+ }
+ } else if(clang::RecordDecl *rd = dyn_cast<RecordDecl>(decl)) {
+ if (rd->getName() == "mytest_struct") {
+ RecordDecl *def = rd->getDefinition();
+ ASSERT_TRUE(def != NULL);
+ const clang::Type *clangTy = rd->getCanonicalDecl()->getTypeForDecl();
+ ASSERT_TRUE(clangTy != NULL);
+ QualType qType = clangTy->getCanonicalTypeInternal();
+
+ // Check convertTypeForMemory
+ llvm::Type *llvmTy = CodeGen::convertTypeForMemory(CGM, qType);
+ ASSERT_TRUE(llvmTy != NULL);
+ if (DebugThisTest) {
+ llvmTy->print(dbgs(), true);
+ dbgs() << "\n";
+ }
+
+ llvm::CompositeType* structTy = dyn_cast<CompositeType>(llvmTy);
+ ASSERT_TRUE(structTy != NULL);
+
+ // Check getLLVMFieldNumber
+ FieldDecl *xField = NULL;
+ FieldDecl *yField = NULL;
+ FieldDecl *zField = NULL;
+
+ for (auto field : rd->fields()) {
+ if (field->getName() == "x") xField = field;
+ if (field->getName() == "y") yField = field;
+ if (field->getName() == "z") zField = field;
+ }
+
+ ASSERT_TRUE(xField != NULL);
+ ASSERT_TRUE(yField != NULL);
+ ASSERT_TRUE(zField != NULL);
+
+ unsigned x = CodeGen::getLLVMFieldNumber(CGM, rd, xField);
+ unsigned y = CodeGen::getLLVMFieldNumber(CGM, rd, yField);
+ unsigned z = CodeGen::getLLVMFieldNumber(CGM, rd, zField);
+
+ ASSERT_NE(x, y);
+ ASSERT_NE(y, z);
+
+ llvm::Type* xTy = structTy->getTypeAtIndex(x);
+ llvm::Type* yTy = structTy->getTypeAtIndex(y);
+ llvm::Type* zTy = structTy->getTypeAtIndex(z);
+
+ ASSERT_TRUE(xTy != NULL);
+ ASSERT_TRUE(yTy != NULL);
+ ASSERT_TRUE(zTy != NULL);
+
+ if (DebugThisTest) {
+ xTy->print(dbgs(), true);
+ dbgs() << "\n";
+ yTy->print(dbgs(), true);
+ dbgs() << "\n";
+ zTy->print(dbgs(), true);
+ dbgs() << "\n";
+ }
+
+ ASSERT_GE(xTy->getPrimitiveSizeInBits(), 1u);
+ ASSERT_GE(yTy->getPrimitiveSizeInBits(), 16u); // short is at least 16b
+ ASSERT_GE(zTy->getPrimitiveSizeInBits(), 32u); // long is at least 32b
+
+ mytest_struct_ok = true;
+ }
+ }
+ }
+
+ ASSERT_TRUE(mytest_fn_ok);
+ ASSERT_TRUE(mytest_struct_ok);
+
+ test_codegen_fns_ran = true;
+}
+
+TEST(CodeGenExternalTest, CodeGenExternalTest) {
+ LLVMContext Context;
+ CompilerInstance compiler;
+
+ compiler.createDiagnostics();
+ compiler.getLangOpts().CPlusPlus = 1;
+ compiler.getLangOpts().CPlusPlus11 = 1;
+
+ compiler.getTargetOpts().Triple = llvm::Triple::normalize(
+ llvm::sys::getProcessTriple());
+ compiler.setTarget(clang::TargetInfo::CreateTargetInfo(
+ compiler.getDiagnostics(),
+ std::make_shared<clang::TargetOptions>(
+ compiler.getTargetOpts())));
+
+ compiler.createFileManager();
+ compiler.createSourceManager(compiler.getFileManager());
+ compiler.createPreprocessor(clang::TU_Prefix);
+
+ compiler.createASTContext();
+
+
+ compiler.setASTConsumer(std::unique_ptr<ASTConsumer>(
+ new MyASTConsumer(std::unique_ptr<CodeGenerator>(
+ CreateLLVMCodeGen(compiler.getDiagnostics(),
+ "MemoryTypesTest",
+ compiler.getHeaderSearchOpts(),
+ compiler.getPreprocessorOpts(),
+ compiler.getCodeGenOpts(),
+ Context)))));
+
+ compiler.createSema(clang::TU_Prefix, nullptr);
+
+ clang::SourceManager &sm = compiler.getSourceManager();
+ sm.setMainFileID(sm.createFileID(
+ llvm::MemoryBuffer::getMemBuffer(TestProgram), clang::SrcMgr::C_User));
+
+ clang::ParseAST(compiler.getSema(), false, false);
+
+ ASSERT_TRUE(test_codegen_fns_ran);
+}
+
+} // end anonymous namespace
diff --git a/unittests/CodeGen/IncrementalProcessingTest.cpp b/unittests/CodeGen/IncrementalProcessingTest.cpp
new file mode 100644
index 0000000000000..40b814bf31e64
--- /dev/null
+++ b/unittests/CodeGen/IncrementalProcessingTest.cpp
@@ -0,0 +1,174 @@
+//=== unittests/CodeGen/IncrementalProcessingTest.cpp - IncrementalCodeGen ===//
+//
+// 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/ASTContext.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/Basic/TargetInfo.h"
+#include "clang/CodeGen/ModuleBuilder.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Parse/Parser.h"
+#include "clang/Sema/Sema.h"
+#include "llvm/ADT/Triple.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Module.h"
+#include "llvm/Support/Host.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "gtest/gtest.h"
+
+#include <memory>
+
+using namespace llvm;
+using namespace clang;
+
+namespace {
+
+// Incremental processing produces several modules, all using the same "main
+// file". Make sure CodeGen can cope with that, e.g. for static initializers.
+const char TestProgram1[] =
+ "extern \"C\" int funcForProg1() { return 17; }\n"
+ "struct EmitCXXGlobalInitFunc1 {\n"
+ " EmitCXXGlobalInitFunc1() {}\n"
+ "} test1;";
+
+const char TestProgram2[] =
+ "extern \"C\" int funcForProg2() { return 42; }\n"
+ "struct EmitCXXGlobalInitFunc2 {\n"
+ " EmitCXXGlobalInitFunc2() {}\n"
+ "} test2;";
+
+
+/// An incremental version of ParseAST().
+static std::unique_ptr<llvm::Module>
+IncrementalParseAST(CompilerInstance& CI, Parser& P,
+ CodeGenerator& CG, const char* code) {
+ static int counter = 0;
+ struct IncreaseCounterOnRet {
+ ~IncreaseCounterOnRet() {
+ ++counter;
+ }
+ } ICOR;
+
+ Sema& S = CI.getSema();
+ clang::SourceManager &SM = S.getSourceManager();
+ if (!code) {
+ // Main file
+ SM.setMainFileID(SM.createFileID(
+ llvm::MemoryBuffer::getMemBuffer(" "), clang::SrcMgr::C_User));
+
+ S.getPreprocessor().EnterMainSourceFile();
+ P.Initialize();
+ } else {
+ FileID FID = SM.createFileID(
+ llvm::MemoryBuffer::getMemBuffer(code), clang::SrcMgr::C_User);
+ SourceLocation MainStartLoc = SM.getLocForStartOfFile(SM.getMainFileID());
+ SourceLocation InclLoc = MainStartLoc.getLocWithOffset(counter);
+ S.getPreprocessor().EnterSourceFile(FID, 0, InclLoc);
+ }
+
+ ExternalASTSource *External = S.getASTContext().getExternalSource();
+ if (External)
+ External->StartTranslationUnit(&CG);
+
+ Parser::DeclGroupPtrTy ADecl;
+ for (bool AtEOF = P.ParseFirstTopLevelDecl(ADecl); !AtEOF;
+ AtEOF = P.ParseTopLevelDecl(ADecl)) {
+ // If we got a null return and something *was* parsed, ignore it. This
+ // is due to a top-level semicolon, an action override, or a parse error
+ // skipping something.
+ if (ADecl && !CG.HandleTopLevelDecl(ADecl.get()))
+ return nullptr;
+ }
+
+ // Process any TopLevelDecls generated by #pragma weak.
+ for (Decl *D : S.WeakTopLevelDecls())
+ CG.HandleTopLevelDecl(DeclGroupRef(D));
+
+ CG.HandleTranslationUnit(S.getASTContext());
+
+ std::unique_ptr<llvm::Module> M(CG.ReleaseModule());
+ // Switch to next module.
+ CG.StartModule("incremental-module-" + std::to_string(counter),
+ M->getContext());
+ return M;
+}
+
+const Function* getGlobalInit(llvm::Module& M) {
+ for (const auto& Func: M)
+ if (Func.hasName() && Func.getName().startswith("_GLOBAL__sub_I_"))
+ return &Func;
+
+ return nullptr;
+}
+
+TEST(IncrementalProcessing, EmitCXXGlobalInitFunc) {
+ LLVMContext Context;
+ CompilerInstance compiler;
+
+ compiler.createDiagnostics();
+ compiler.getLangOpts().CPlusPlus = 1;
+ compiler.getLangOpts().CPlusPlus11 = 1;
+
+ compiler.getTargetOpts().Triple = llvm::Triple::normalize(
+ llvm::sys::getProcessTriple());
+ compiler.setTarget(clang::TargetInfo::CreateTargetInfo(
+ compiler.getDiagnostics(),
+ std::make_shared<clang::TargetOptions>(
+ compiler.getTargetOpts())));
+
+ compiler.createFileManager();
+ compiler.createSourceManager(compiler.getFileManager());
+ compiler.createPreprocessor(clang::TU_Prefix);
+ compiler.getPreprocessor().enableIncrementalProcessing();
+
+ compiler.createASTContext();
+
+ CodeGenerator* CG =
+ CreateLLVMCodeGen(
+ compiler.getDiagnostics(),
+ "main-module",
+ compiler.getHeaderSearchOpts(),
+ compiler.getPreprocessorOpts(),
+ compiler.getCodeGenOpts(),
+ Context);
+ compiler.setASTConsumer(std::unique_ptr<ASTConsumer>(CG));
+ compiler.createSema(clang::TU_Prefix, nullptr);
+ Sema& S = compiler.getSema();
+
+ std::unique_ptr<Parser> ParseOP(new Parser(S.getPreprocessor(), S,
+ /*SkipFunctionBodies*/ false));
+ Parser &P = *ParseOP.get();
+
+ std::array<std::unique_ptr<llvm::Module>, 3> M;
+ M[0] = IncrementalParseAST(compiler, P, *CG, nullptr);
+ ASSERT_TRUE(M[0]);
+
+ M[1] = IncrementalParseAST(compiler, P, *CG, TestProgram1);
+ ASSERT_TRUE(M[1]);
+ ASSERT_TRUE(M[1]->getFunction("funcForProg1"));
+
+ M[2] = IncrementalParseAST(compiler, P, *CG, TestProgram2);
+ ASSERT_TRUE(M[2]);
+ ASSERT_TRUE(M[2]->getFunction("funcForProg2"));
+ // First code should not end up in second module:
+ ASSERT_FALSE(M[2]->getFunction("funcForProg1"));
+
+ // Make sure global inits exist and are unique:
+ const Function* GlobalInit1 = getGlobalInit(*M[1]);
+ ASSERT_TRUE(GlobalInit1);
+
+ const Function* GlobalInit2 = getGlobalInit(*M[2]);
+ ASSERT_TRUE(GlobalInit2);
+
+ ASSERT_FALSE(GlobalInit1->getName() == GlobalInit2->getName());
+
+}
+
+} // end anonymous namespace
diff --git a/unittests/CrossTU/CMakeLists.txt b/unittests/CrossTU/CMakeLists.txt
new file mode 100644
index 0000000000000..652d91612fb4d
--- /dev/null
+++ b/unittests/CrossTU/CMakeLists.txt
@@ -0,0 +1,17 @@
+set(LLVM_LINK_COMPONENTS
+ ${LLVM_TARGETS_TO_BUILD}
+ Support
+ )
+
+add_clang_unittest(CrossTUTests
+ CrossTranslationUnitTest.cpp
+ )
+
+target_link_libraries(CrossTUTests
+ PRIVATE
+ clangAST
+ clangBasic
+ clangCrossTU
+ clangFrontend
+ clangTooling
+ )
diff --git a/unittests/CrossTU/CrossTranslationUnitTest.cpp b/unittests/CrossTU/CrossTranslationUnitTest.cpp
new file mode 100644
index 0000000000000..5fbf56ed43b8d
--- /dev/null
+++ b/unittests/CrossTU/CrossTranslationUnitTest.cpp
@@ -0,0 +1,158 @@
+//===- unittest/Tooling/CrossTranslationUnitTest.cpp - Tooling unit tests -===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/CrossTU/CrossTranslationUnit.h"
+#include "clang/AST/ASTConsumer.h"
+#include "clang/Frontend/FrontendAction.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/Config/llvm-config.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/ToolOutputFile.h"
+#include "gtest/gtest.h"
+#include <cassert>
+
+namespace clang {
+namespace cross_tu {
+
+namespace {
+
+class CTUASTConsumer : public clang::ASTConsumer {
+public:
+ explicit CTUASTConsumer(clang::CompilerInstance &CI, bool *Success)
+ : CTU(CI), Success(Success) {}
+
+ void HandleTranslationUnit(ASTContext &Ctx) {
+ const TranslationUnitDecl *TU = Ctx.getTranslationUnitDecl();
+ const FunctionDecl *FD = nullptr;
+ for (const Decl *D : TU->decls()) {
+ FD = dyn_cast<FunctionDecl>(D);
+ if (FD && FD->getName() == "f")
+ break;
+ }
+ assert(FD && FD->getName() == "f");
+ bool OrigFDHasBody = FD->hasBody();
+
+ // Prepare the index file and the AST file.
+ int ASTFD;
+ llvm::SmallString<256> ASTFileName;
+ ASSERT_FALSE(
+ llvm::sys::fs::createTemporaryFile("f_ast", "ast", ASTFD, ASTFileName));
+ llvm::ToolOutputFile ASTFile(ASTFileName, ASTFD);
+
+ int IndexFD;
+ llvm::SmallString<256> IndexFileName;
+ ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD,
+ IndexFileName));
+ llvm::ToolOutputFile IndexFile(IndexFileName, IndexFD);
+ IndexFile.os() << "c:@F@f#I# " << ASTFileName << "\n";
+ IndexFile.os().flush();
+ EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName));
+
+ StringRef SourceText = "int f(int) { return 0; }\n";
+ // This file must exist since the saved ASTFile will reference it.
+ int SourceFD;
+ llvm::SmallString<256> SourceFileName;
+ ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("input", "cpp", SourceFD,
+ SourceFileName));
+ llvm::ToolOutputFile SourceFile(SourceFileName, SourceFD);
+ SourceFile.os() << SourceText;
+ SourceFile.os().flush();
+ EXPECT_TRUE(llvm::sys::fs::exists(SourceFileName));
+
+ std::unique_ptr<ASTUnit> ASTWithDefinition =
+ tooling::buildASTFromCode(SourceText, SourceFileName);
+ ASTWithDefinition->Save(ASTFileName.str());
+ EXPECT_TRUE(llvm::sys::fs::exists(ASTFileName));
+
+ // Load the definition from the AST file.
+ llvm::Expected<const FunctionDecl *> NewFDorError =
+ CTU.getCrossTUDefinition(FD, "", IndexFileName);
+ EXPECT_TRUE((bool)NewFDorError);
+ const FunctionDecl *NewFD = *NewFDorError;
+
+ *Success = NewFD && NewFD->hasBody() && !OrigFDHasBody;
+ }
+
+private:
+ CrossTranslationUnitContext CTU;
+ bool *Success;
+};
+
+class CTUAction : public clang::ASTFrontendAction {
+public:
+ CTUAction(bool *Success) : Success(Success) {}
+
+protected:
+ std::unique_ptr<clang::ASTConsumer>
+ CreateASTConsumer(clang::CompilerInstance &CI, StringRef) override {
+ return llvm::make_unique<CTUASTConsumer>(CI, Success);
+ }
+
+private:
+ bool *Success;
+};
+
+} // end namespace
+
+TEST(CrossTranslationUnit, CanLoadFunctionDefinition) {
+ bool Success = false;
+ EXPECT_TRUE(tooling::runToolOnCode(new CTUAction(&Success), "int f(int);"));
+ EXPECT_TRUE(Success);
+}
+
+TEST(CrossTranslationUnit, IndexFormatCanBeParsed) {
+ llvm::StringMap<std::string> Index;
+ Index["a"] = "/b/f1";
+ Index["c"] = "/d/f2";
+ Index["e"] = "/f/f3";
+ std::string IndexText = createCrossTUIndexString(Index);
+
+ int IndexFD;
+ llvm::SmallString<256> IndexFileName;
+ ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD,
+ IndexFileName));
+ llvm::ToolOutputFile IndexFile(IndexFileName, IndexFD);
+ IndexFile.os() << IndexText;
+ IndexFile.os().flush();
+ EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName));
+ llvm::Expected<llvm::StringMap<std::string>> IndexOrErr =
+ parseCrossTUIndex(IndexFileName, "");
+ EXPECT_TRUE((bool)IndexOrErr);
+ llvm::StringMap<std::string> ParsedIndex = IndexOrErr.get();
+ for (const auto &E : Index) {
+ EXPECT_TRUE(ParsedIndex.count(E.getKey()));
+ EXPECT_EQ(ParsedIndex[E.getKey()], E.getValue());
+ }
+ for (const auto &E : ParsedIndex)
+ EXPECT_TRUE(Index.count(E.getKey()));
+}
+
+TEST(CrossTranslationUnit, CTUDirIsHandledCorrectly) {
+ llvm::StringMap<std::string> Index;
+ Index["a"] = "/b/c/d";
+ std::string IndexText = createCrossTUIndexString(Index);
+
+ int IndexFD;
+ llvm::SmallString<256> IndexFileName;
+ ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD,
+ IndexFileName));
+ llvm::ToolOutputFile IndexFile(IndexFileName, IndexFD);
+ IndexFile.os() << IndexText;
+ IndexFile.os().flush();
+ EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName));
+ llvm::Expected<llvm::StringMap<std::string>> IndexOrErr =
+ parseCrossTUIndex(IndexFileName, "/ctudir");
+ EXPECT_TRUE((bool)IndexOrErr);
+ llvm::StringMap<std::string> ParsedIndex = IndexOrErr.get();
+ EXPECT_EQ(ParsedIndex["a"], "/ctudir/b/c/d");
+}
+
+} // end namespace cross_tu
+} // end namespace clang
diff --git a/unittests/Driver/CMakeLists.txt b/unittests/Driver/CMakeLists.txt
index a4f75d26f6603..b8c800f59eaf7 100644
--- a/unittests/Driver/CMakeLists.txt
+++ b/unittests/Driver/CMakeLists.txt
@@ -1,4 +1,5 @@
set(LLVM_LINK_COMPONENTS
+ ${LLVM_TARGETS_TO_BUILD}
Support
Option
)
@@ -10,6 +11,7 @@ add_clang_unittest(ClangDriverTests
)
target_link_libraries(ClangDriverTests
+ PRIVATE
clangDriver
clangBasic
)
diff --git a/unittests/Driver/ToolChainTest.cpp b/unittests/Driver/ToolChainTest.cpp
index ec50560b202be..93cf12b3c2be4 100644
--- a/unittests/Driver/ToolChainTest.cpp
+++ b/unittests/Driver/ToolChainTest.cpp
@@ -18,6 +18,8 @@
#include "clang/Basic/VirtualFileSystem.h"
#include "clang/Driver/Compilation.h"
#include "clang/Driver/Driver.h"
+#include "llvm/Support/TargetRegistry.h"
+#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"
using namespace clang;
@@ -164,4 +166,98 @@ TEST(ToolChainTest, InvalidArgument) {
EXPECT_TRUE(C->containsError());
}
+TEST(ToolChainTest, ParsedClangName) {
+ ParsedClangName Empty;
+ EXPECT_TRUE(Empty.TargetPrefix.empty());
+ EXPECT_TRUE(Empty.ModeSuffix.empty());
+ EXPECT_TRUE(Empty.DriverMode == nullptr);
+ EXPECT_FALSE(Empty.TargetIsValid);
+
+ ParsedClangName DriverOnly("clang", nullptr);
+ EXPECT_TRUE(DriverOnly.TargetPrefix.empty());
+ EXPECT_TRUE(DriverOnly.ModeSuffix == "clang");
+ EXPECT_TRUE(DriverOnly.DriverMode == nullptr);
+ EXPECT_FALSE(DriverOnly.TargetIsValid);
+
+ ParsedClangName DriverOnly2("clang++", "--driver-mode=g++");
+ EXPECT_TRUE(DriverOnly2.TargetPrefix.empty());
+ EXPECT_TRUE(DriverOnly2.ModeSuffix == "clang++");
+ EXPECT_STREQ(DriverOnly2.DriverMode, "--driver-mode=g++");
+ EXPECT_FALSE(DriverOnly2.TargetIsValid);
+
+ ParsedClangName TargetAndMode("i386", "clang-g++", "--driver-mode=g++", true);
+ EXPECT_TRUE(TargetAndMode.TargetPrefix == "i386");
+ EXPECT_TRUE(TargetAndMode.ModeSuffix == "clang-g++");
+ EXPECT_STREQ(TargetAndMode.DriverMode, "--driver-mode=g++");
+ EXPECT_TRUE(TargetAndMode.TargetIsValid);
+}
+
+TEST(ToolChainTest, GetTargetAndMode) {
+ llvm::InitializeAllTargets();
+ std::string IgnoredError;
+ if (!llvm::TargetRegistry::lookupTarget("x86_64", IgnoredError))
+ return;
+
+ ParsedClangName Res = ToolChain::getTargetAndModeFromProgramName("clang");
+ EXPECT_TRUE(Res.TargetPrefix.empty());
+ EXPECT_TRUE(Res.ModeSuffix == "clang");
+ EXPECT_TRUE(Res.DriverMode == nullptr);
+ EXPECT_FALSE(Res.TargetIsValid);
+
+ Res = ToolChain::getTargetAndModeFromProgramName("clang++");
+ EXPECT_TRUE(Res.TargetPrefix.empty());
+ EXPECT_TRUE(Res.ModeSuffix == "clang++");
+ EXPECT_STREQ(Res.DriverMode, "--driver-mode=g++");
+ EXPECT_FALSE(Res.TargetIsValid);
+
+ Res = ToolChain::getTargetAndModeFromProgramName("clang++6.0");
+ EXPECT_TRUE(Res.TargetPrefix.empty());
+ EXPECT_TRUE(Res.ModeSuffix == "clang++");
+ EXPECT_STREQ(Res.DriverMode, "--driver-mode=g++");
+ EXPECT_FALSE(Res.TargetIsValid);
+
+ Res = ToolChain::getTargetAndModeFromProgramName("clang++-release");
+ EXPECT_TRUE(Res.TargetPrefix.empty());
+ EXPECT_TRUE(Res.ModeSuffix == "clang++");
+ EXPECT_STREQ(Res.DriverMode, "--driver-mode=g++");
+ EXPECT_FALSE(Res.TargetIsValid);
+
+ Res = ToolChain::getTargetAndModeFromProgramName("x86_64-clang++");
+ EXPECT_TRUE(Res.TargetPrefix == "x86_64");
+ EXPECT_TRUE(Res.ModeSuffix == "clang++");
+ EXPECT_STREQ(Res.DriverMode, "--driver-mode=g++");
+ EXPECT_TRUE(Res.TargetIsValid);
+
+ Res = ToolChain::getTargetAndModeFromProgramName(
+ "x86_64-linux-gnu-clang-c++");
+ EXPECT_TRUE(Res.TargetPrefix == "x86_64-linux-gnu");
+ EXPECT_TRUE(Res.ModeSuffix == "clang-c++");
+ EXPECT_STREQ(Res.DriverMode, "--driver-mode=g++");
+ EXPECT_TRUE(Res.TargetIsValid);
+
+ Res = ToolChain::getTargetAndModeFromProgramName(
+ "x86_64-linux-gnu-clang-c++-tot");
+ EXPECT_TRUE(Res.TargetPrefix == "x86_64-linux-gnu");
+ EXPECT_TRUE(Res.ModeSuffix == "clang-c++");
+ EXPECT_STREQ(Res.DriverMode, "--driver-mode=g++");
+ EXPECT_TRUE(Res.TargetIsValid);
+
+ Res = ToolChain::getTargetAndModeFromProgramName("qqq");
+ EXPECT_TRUE(Res.TargetPrefix.empty());
+ EXPECT_TRUE(Res.ModeSuffix.empty());
+ EXPECT_TRUE(Res.DriverMode == nullptr);
+ EXPECT_FALSE(Res.TargetIsValid);
+
+ Res = ToolChain::getTargetAndModeFromProgramName("x86_64-qqq");
+ EXPECT_TRUE(Res.TargetPrefix.empty());
+ EXPECT_TRUE(Res.ModeSuffix.empty());
+ EXPECT_TRUE(Res.DriverMode == nullptr);
+ EXPECT_FALSE(Res.TargetIsValid);
+
+ Res = ToolChain::getTargetAndModeFromProgramName("qqq-clang-cl");
+ EXPECT_TRUE(Res.TargetPrefix == "qqq");
+ EXPECT_TRUE(Res.ModeSuffix == "clang-cl");
+ EXPECT_STREQ(Res.DriverMode, "--driver-mode=cl");
+ EXPECT_FALSE(Res.TargetIsValid);
+}
} // end anonymous namespace.
diff --git a/unittests/Format/CMakeLists.txt b/unittests/Format/CMakeLists.txt
index fa7e32c33d9ff..18e4432308db2 100644
--- a/unittests/Format/CMakeLists.txt
+++ b/unittests/Format/CMakeLists.txt
@@ -10,6 +10,7 @@ add_clang_unittest(FormatTests
FormatTestJava.cpp
FormatTestObjC.cpp
FormatTestProto.cpp
+ FormatTestRawStrings.cpp
FormatTestSelective.cpp
FormatTestTextProto.cpp
NamespaceEndCommentsFixerTest.cpp
@@ -19,6 +20,7 @@ add_clang_unittest(FormatTests
)
target_link_libraries(FormatTests
+ PRIVATE
clangBasic
clangFormat
clangFrontend
diff --git a/unittests/Format/FormatTest.cpp b/unittests/Format/FormatTest.cpp
index 570f3472bceff..2cae9dd0c547b 100644
--- a/unittests/Format/FormatTest.cpp
+++ b/unittests/Format/FormatTest.cpp
@@ -70,18 +70,23 @@ protected:
return getStyleWithColumns(getGoogleStyle(), ColumnLimit);
}
- void verifyFormat(llvm::StringRef Code,
+ void verifyFormat(llvm::StringRef Expected, llvm::StringRef Code,
const FormatStyle &Style = getLLVMStyle()) {
- EXPECT_EQ(Code.str(), format(test::messUp(Code), Style));
+ EXPECT_EQ(Expected.str(), format(Code, Style));
if (Style.Language == FormatStyle::LK_Cpp) {
// Objective-C++ is a superset of C++, so everything checked for C++
// needs to be checked for Objective-C++ as well.
FormatStyle ObjCStyle = Style;
ObjCStyle.Language = FormatStyle::LK_ObjC;
- EXPECT_EQ(Code.str(), format(test::messUp(Code), ObjCStyle));
+ EXPECT_EQ(Expected.str(), format(test::messUp(Code), ObjCStyle));
}
}
+ void verifyFormat(llvm::StringRef Code,
+ const FormatStyle &Style = getLLVMStyle()) {
+ verifyFormat(Code, test::messUp(Code), Style);
+ }
+
void verifyIncompleteFormat(llvm::StringRef Code,
const FormatStyle &Style = getLLVMStyle()) {
EXPECT_EQ(Code.str(),
@@ -439,11 +444,16 @@ TEST_F(FormatTest, FormatLoopsWithoutCompoundStatement) {
TEST_F(FormatTest, FormatShortBracedStatements) {
FormatStyle AllowSimpleBracedStatements = getLLVMStyle();
+ AllowSimpleBracedStatements.ColumnLimit = 40;
AllowSimpleBracedStatements.AllowShortBlocksOnASingleLine = true;
AllowSimpleBracedStatements.AllowShortIfStatementsOnASingleLine = true;
AllowSimpleBracedStatements.AllowShortLoopsOnASingleLine = true;
+ AllowSimpleBracedStatements.BreakBeforeBraces = FormatStyle::BS_Custom;
+ AllowSimpleBracedStatements.BraceWrapping.AfterFunction = true;
+ AllowSimpleBracedStatements.BraceWrapping.SplitEmptyRecord = false;
+
verifyFormat("if (true) {}", AllowSimpleBracedStatements);
verifyFormat("if constexpr (true) {}", AllowSimpleBracedStatements);
verifyFormat("while (true) {}", AllowSimpleBracedStatements);
@@ -452,6 +462,10 @@ TEST_F(FormatTest, FormatShortBracedStatements) {
verifyFormat("if constexpr (true) { f(); }", AllowSimpleBracedStatements);
verifyFormat("while (true) { f(); }", AllowSimpleBracedStatements);
verifyFormat("for (;;) { f(); }", AllowSimpleBracedStatements);
+ verifyFormat("if (true) {\n"
+ " ffffffffffffffffffffffffffffffffffffffffffffffffffffff();\n"
+ "}",
+ AllowSimpleBracedStatements);
verifyFormat("if (true) { //\n"
" f();\n"
"}",
@@ -482,6 +496,7 @@ TEST_F(FormatTest, FormatShortBracedStatements) {
AllowSimpleBracedStatements);
AllowSimpleBracedStatements.AllowShortIfStatementsOnASingleLine = false;
+ verifyFormat("if (true) {}", AllowSimpleBracedStatements);
verifyFormat("if (true) {\n"
" f();\n"
"}",
@@ -494,14 +509,83 @@ TEST_F(FormatTest, FormatShortBracedStatements) {
AllowSimpleBracedStatements);
AllowSimpleBracedStatements.AllowShortLoopsOnASingleLine = false;
+ verifyFormat("while (true) {}", AllowSimpleBracedStatements);
verifyFormat("while (true) {\n"
" f();\n"
"}",
AllowSimpleBracedStatements);
+ verifyFormat("for (;;) {}", AllowSimpleBracedStatements);
verifyFormat("for (;;) {\n"
" f();\n"
"}",
AllowSimpleBracedStatements);
+
+ AllowSimpleBracedStatements.AllowShortIfStatementsOnASingleLine = true;
+ AllowSimpleBracedStatements.AllowShortLoopsOnASingleLine = true;
+ AllowSimpleBracedStatements.BraceWrapping.AfterControlStatement = true;
+
+ verifyFormat("if (true) {}", AllowSimpleBracedStatements);
+ verifyFormat("if constexpr (true) {}", AllowSimpleBracedStatements);
+ verifyFormat("while (true) {}", AllowSimpleBracedStatements);
+ verifyFormat("for (;;) {}", AllowSimpleBracedStatements);
+ verifyFormat("if (true) { f(); }", AllowSimpleBracedStatements);
+ verifyFormat("if constexpr (true) { f(); }", AllowSimpleBracedStatements);
+ verifyFormat("while (true) { f(); }", AllowSimpleBracedStatements);
+ verifyFormat("for (;;) { f(); }", AllowSimpleBracedStatements);
+ verifyFormat("if (true)\n"
+ "{\n"
+ " ffffffffffffffffffffffffffffffffffffffffffffffffffffff();\n"
+ "}",
+ AllowSimpleBracedStatements);
+ verifyFormat("if (true)\n"
+ "{ //\n"
+ " f();\n"
+ "}",
+ AllowSimpleBracedStatements);
+ verifyFormat("if (true)\n"
+ "{\n"
+ " f();\n"
+ " f();\n"
+ "}",
+ AllowSimpleBracedStatements);
+ verifyFormat("if (true)\n"
+ "{\n"
+ " f();\n"
+ "} else\n"
+ "{\n"
+ " f();\n"
+ "}",
+ AllowSimpleBracedStatements);
+
+ AllowSimpleBracedStatements.AllowShortIfStatementsOnASingleLine = false;
+ verifyFormat("if (true) {}", AllowSimpleBracedStatements);
+ verifyFormat("if (true)\n"
+ "{\n"
+ " f();\n"
+ "}",
+ AllowSimpleBracedStatements);
+ verifyFormat("if (true)\n"
+ "{\n"
+ " f();\n"
+ "} else\n"
+ "{\n"
+ " f();\n"
+ "}",
+ AllowSimpleBracedStatements);
+
+ AllowSimpleBracedStatements.AllowShortLoopsOnASingleLine = false;
+ verifyFormat("while (true) {}", AllowSimpleBracedStatements);
+ verifyFormat("while (true)\n"
+ "{\n"
+ " f();\n"
+ "}",
+ AllowSimpleBracedStatements);
+ verifyFormat("for (;;) {}", AllowSimpleBracedStatements);
+ verifyFormat("for (;;)\n"
+ "{\n"
+ " f();\n"
+ "}",
+ AllowSimpleBracedStatements);
}
TEST_F(FormatTest, ParseIfElse) {
@@ -657,6 +741,10 @@ TEST_F(FormatTest, FormatsForLoop) {
" I != E;\n"
" ++I) {\n}",
NoBinPacking);
+
+ FormatStyle AlignLeft = getLLVMStyle();
+ AlignLeft.PointerAlignment = FormatStyle::PAS_Left;
+ verifyFormat("for (A* a = start; a < end; ++a, ++value) {\n}", AlignLeft);
}
TEST_F(FormatTest, RangeBasedForLoops) {
@@ -907,6 +995,77 @@ TEST_F(FormatTest, ShortCaseLabels) {
"}",
Style);
verifyFormat("switch (a) {\n"
+ "case 0: return; // comment\n"
+ "case 1: break; // comment\n"
+ "case 2: return;\n"
+ "// comment\n"
+ "case 3: return;\n"
+ "// comment 1\n"
+ "// comment 2\n"
+ "// comment 3\n"
+ "case 4: break; /* comment */\n"
+ "case 5:\n"
+ " // comment\n"
+ " break;\n"
+ "case 6: /* comment */ x = 1; break;\n"
+ "case 7: x = /* comment */ 1; break;\n"
+ "case 8:\n"
+ " x = 1; /* comment */\n"
+ " break;\n"
+ "case 9:\n"
+ " break; // comment line 1\n"
+ " // comment line 2\n"
+ "}",
+ Style);
+ EXPECT_EQ("switch (a) {\n"
+ "case 1:\n"
+ " x = 8;\n"
+ " // fall through\n"
+ "case 2: x = 8;\n"
+ "// comment\n"
+ "case 3:\n"
+ " return; /* comment line 1\n"
+ " * comment line 2 */\n"
+ "case 4: i = 8;\n"
+ "// something else\n"
+ "#if FOO\n"
+ "case 5: break;\n"
+ "#endif\n"
+ "}",
+ format("switch (a) {\n"
+ "case 1: x = 8;\n"
+ " // fall through\n"
+ "case 2:\n"
+ " x = 8;\n"
+ "// comment\n"
+ "case 3:\n"
+ " return; /* comment line 1\n"
+ " * comment line 2 */\n"
+ "case 4:\n"
+ " i = 8;\n"
+ "// something else\n"
+ "#if FOO\n"
+ "case 5: break;\n"
+ "#endif\n"
+ "}",
+ Style));
+ EXPECT_EQ("switch (a) {\n" "case 0:\n"
+ " return; // long long long long long long long long long long long long comment\n"
+ " // line\n" "}",
+ format("switch (a) {\n"
+ "case 0: return; // long long long long long long long long long long long long comment line\n"
+ "}",
+ Style));
+ EXPECT_EQ("switch (a) {\n"
+ "case 0:\n"
+ " return; /* long long long long long long long long long long long long comment\n"
+ " line */\n"
+ "}",
+ format("switch (a) {\n"
+ "case 0: return; /* long long long long long long long long long long long long comment line */\n"
+ "}",
+ Style));
+ verifyFormat("switch (a) {\n"
"#if FOO\n"
"case 0: return 0;\n"
"#endif\n"
@@ -1253,6 +1412,32 @@ TEST_F(FormatTest, FormatsEnumTypes) {
verifyFormat("enum X : std::uint32_t { A, B };");
}
+TEST_F(FormatTest, FormatsTypedefEnum) {
+ FormatStyle Style = getLLVMStyle();
+ Style.ColumnLimit = 40;
+ verifyFormat("typedef enum {} EmptyEnum;");
+ verifyFormat("typedef enum { A, B, C } ShortEnum;");
+ verifyFormat("typedef enum {\n"
+ " ZERO = 0,\n"
+ " ONE = 1,\n"
+ " TWO = 2,\n"
+ " THREE = 3\n"
+ "} LongEnum;",
+ Style);
+ Style.BreakBeforeBraces = FormatStyle::BS_Custom;
+ Style.BraceWrapping.AfterEnum = true;
+ verifyFormat("typedef enum {} EmptyEnum;");
+ verifyFormat("typedef enum { A, B, C } ShortEnum;");
+ verifyFormat("typedef enum\n"
+ "{\n"
+ " ZERO = 0,\n"
+ " ONE = 1,\n"
+ " TWO = 2,\n"
+ " THREE = 3\n"
+ "} LongEnum;",
+ Style);
+}
+
TEST_F(FormatTest, FormatsNSEnums) {
verifyGoogleFormat("typedef NS_ENUM(NSInteger, SomeName) { AAA, BBB }");
verifyGoogleFormat("typedef NS_ENUM(NSInteger, MyType) {\n"
@@ -1524,7 +1709,42 @@ TEST_F(FormatTest, FormatsCompactNamespaces) {
Style));
}
-TEST_F(FormatTest, FormatsExternC) { verifyFormat("extern \"C\" {\nint a;"); }
+TEST_F(FormatTest, FormatsExternC) {
+ verifyFormat("extern \"C\" {\nint a;");
+ verifyFormat("extern \"C\" {}");
+ verifyFormat("extern \"C\" {\n"
+ "int foo();\n"
+ "}");
+ verifyFormat("extern \"C\" int foo() {}");
+ verifyFormat("extern \"C\" int foo();");
+ verifyFormat("extern \"C\" int foo() {\n"
+ " int i = 42;\n"
+ " return i;\n"
+ "}");
+
+ FormatStyle Style = getLLVMStyle();
+ Style.BreakBeforeBraces = FormatStyle::BS_Custom;
+ Style.BraceWrapping.AfterFunction = true;
+ verifyFormat("extern \"C\" int foo() {}", Style);
+ verifyFormat("extern \"C\" int foo();", Style);
+ verifyFormat("extern \"C\" int foo()\n"
+ "{\n"
+ " int i = 42;\n"
+ " return i;\n"
+ "}",
+ Style);
+
+ Style.BraceWrapping.AfterExternBlock = true;
+ Style.BraceWrapping.SplitEmptyRecord = false;
+ verifyFormat("extern \"C\"\n"
+ "{}",
+ Style);
+ verifyFormat("extern \"C\"\n"
+ "{\n"
+ " int foo();\n"
+ "}",
+ Style);
+}
TEST_F(FormatTest, FormatsInlineASM) {
verifyFormat("asm(\"xyz\" : \"=a\"(a), \"=d\"(b) : \"a\"(data));");
@@ -2215,8 +2435,191 @@ TEST_F(FormatTest, LayoutMacroDefinitionsStatementsSpanningBlocks) {
getLLVMStyleWithColumns(11));
}
-TEST_F(FormatTest, IndentPreprocessorDirectivesAtZero) {
- EXPECT_EQ("{\n {\n#define A\n }\n}", format("{{\n#define A\n}}"));
+TEST_F(FormatTest, IndentPreprocessorDirectives) {
+ FormatStyle Style = getLLVMStyle();
+ Style.IndentPPDirectives = FormatStyle::PPDIS_None;
+ Style.ColumnLimit = 40;
+ verifyFormat("#ifdef _WIN32\n"
+ "#define A 0\n"
+ "#ifdef VAR2\n"
+ "#define B 1\n"
+ "#include <someheader.h>\n"
+ "#define MACRO \\\n"
+ " some_very_long_func_aaaaaaaaaa();\n"
+ "#endif\n"
+ "#else\n"
+ "#define A 1\n"
+ "#endif",
+ Style);
+ Style.IndentPPDirectives = FormatStyle::PPDIS_AfterHash;
+ verifyFormat("#ifdef _WIN32\n"
+ "# define A 0\n"
+ "# ifdef VAR2\n"
+ "# define B 1\n"
+ "# include <someheader.h>\n"
+ "# define MACRO \\\n"
+ " some_very_long_func_aaaaaaaaaa();\n"
+ "# endif\n"
+ "#else\n"
+ "# define A 1\n"
+ "#endif",
+ Style);
+ verifyFormat("#if A\n"
+ "# define MACRO \\\n"
+ " void a(int x) { \\\n"
+ " b(); \\\n"
+ " c(); \\\n"
+ " d(); \\\n"
+ " e(); \\\n"
+ " f(); \\\n"
+ " }\n"
+ "#endif",
+ Style);
+ // Comments before include guard.
+ verifyFormat("// file comment\n"
+ "// file comment\n"
+ "#ifndef HEADER_H\n"
+ "#define HEADER_H\n"
+ "code();\n"
+ "#endif",
+ Style);
+ // Test with include guards.
+ // EXPECT_EQ is used because verifyFormat() calls messUp() which incorrectly
+ // merges lines.
+ verifyFormat("#ifndef HEADER_H\n"
+ "#define HEADER_H\n"
+ "code();\n"
+ "#endif",
+ Style);
+ // Include guards must have a #define with the same variable immediately
+ // after #ifndef.
+ verifyFormat("#ifndef NOT_GUARD\n"
+ "# define FOO\n"
+ "code();\n"
+ "#endif",
+ Style);
+
+ // Include guards must cover the entire file.
+ verifyFormat("code();\n"
+ "code();\n"
+ "#ifndef NOT_GUARD\n"
+ "# define NOT_GUARD\n"
+ "code();\n"
+ "#endif",
+ Style);
+ verifyFormat("#ifndef NOT_GUARD\n"
+ "# define NOT_GUARD\n"
+ "code();\n"
+ "#endif\n"
+ "code();",
+ Style);
+ // Test with trailing blank lines.
+ verifyFormat("#ifndef HEADER_H\n"
+ "#define HEADER_H\n"
+ "code();\n"
+ "#endif\n",
+ Style);
+ // Include guards don't have #else.
+ verifyFormat("#ifndef NOT_GUARD\n"
+ "# define NOT_GUARD\n"
+ "code();\n"
+ "#else\n"
+ "#endif",
+ Style);
+ verifyFormat("#ifndef NOT_GUARD\n"
+ "# define NOT_GUARD\n"
+ "code();\n"
+ "#elif FOO\n"
+ "#endif",
+ Style);
+ // FIXME: This doesn't handle the case where there's code between the
+ // #ifndef and #define but all other conditions hold. This is because when
+ // the #define line is parsed, UnwrappedLineParser::Lines doesn't hold the
+ // previous code line yet, so we can't detect it.
+ EXPECT_EQ("#ifndef NOT_GUARD\n"
+ "code();\n"
+ "#define NOT_GUARD\n"
+ "code();\n"
+ "#endif",
+ format("#ifndef NOT_GUARD\n"
+ "code();\n"
+ "# define NOT_GUARD\n"
+ "code();\n"
+ "#endif",
+ Style));
+ // FIXME: This doesn't handle cases where legitimate preprocessor lines may
+ // be outside an include guard. Examples are #pragma once and
+ // #pragma GCC diagnostic, or anything else that does not change the meaning
+ // of the file if it's included multiple times.
+ EXPECT_EQ("#ifdef WIN32\n"
+ "# pragma once\n"
+ "#endif\n"
+ "#ifndef HEADER_H\n"
+ "# define HEADER_H\n"
+ "code();\n"
+ "#endif",
+ format("#ifdef WIN32\n"
+ "# pragma once\n"
+ "#endif\n"
+ "#ifndef HEADER_H\n"
+ "#define HEADER_H\n"
+ "code();\n"
+ "#endif",
+ Style));
+ // FIXME: This does not detect when there is a single non-preprocessor line
+ // in front of an include-guard-like structure where other conditions hold
+ // because ScopedLineState hides the line.
+ EXPECT_EQ("code();\n"
+ "#ifndef HEADER_H\n"
+ "#define HEADER_H\n"
+ "code();\n"
+ "#endif",
+ format("code();\n"
+ "#ifndef HEADER_H\n"
+ "# define HEADER_H\n"
+ "code();\n"
+ "#endif",
+ Style));
+ // FIXME: The comment indent corrector in TokenAnnotator gets thrown off by
+ // preprocessor indentation.
+ EXPECT_EQ("#if 1\n"
+ " // comment\n"
+ "# define A 0\n"
+ "// comment\n"
+ "# define B 0\n"
+ "#endif",
+ format("#if 1\n"
+ "// comment\n"
+ "# define A 0\n"
+ " // comment\n"
+ "# define B 0\n"
+ "#endif",
+ Style));
+ // Test with tabs.
+ Style.UseTab = FormatStyle::UT_Always;
+ Style.IndentWidth = 8;
+ Style.TabWidth = 8;
+ verifyFormat("#ifdef _WIN32\n"
+ "#\tdefine A 0\n"
+ "#\tifdef VAR2\n"
+ "#\t\tdefine B 1\n"
+ "#\t\tinclude <someheader.h>\n"
+ "#\t\tdefine MACRO \\\n"
+ "\t\t\tsome_very_long_func_aaaaaaaaaa();\n"
+ "#\tendif\n"
+ "#else\n"
+ "#\tdefine A 1\n"
+ "#endif",
+ Style);
+
+ // Regression test: Multiline-macro inside include guards.
+ verifyFormat("#ifndef HEADER_H\n"
+ "#define HEADER_H\n"
+ "#define A() \\\n"
+ " int i; \\\n"
+ " int j;\n"
+ "#endif // HEADER_H",
+ getLLVMStyleWithColumns(20));
}
TEST_F(FormatTest, FormatHashIfNotAtStartOfLine) {
@@ -2231,13 +2634,63 @@ TEST_F(FormatTest, FormatUnbalancedStructuralElements) {
}
TEST_F(FormatTest, EscapedNewlines) {
- EXPECT_EQ(
- "#define A \\\n int i; \\\n int j;",
- format("#define A \\\nint i;\\\n int j;", getLLVMStyleWithColumns(11)));
+ FormatStyle Narrow = getLLVMStyleWithColumns(11);
+ EXPECT_EQ("#define A \\\n int i; \\\n int j;",
+ format("#define A \\\nint i;\\\n int j;", Narrow));
EXPECT_EQ("#define A\n\nint i;", format("#define A \\\n\n int i;"));
EXPECT_EQ("template <class T> f();", format("\\\ntemplate <class T> f();"));
EXPECT_EQ("/* \\ \\ \\\n */", format("\\\n/* \\ \\ \\\n */"));
EXPECT_EQ("<a\n\\\\\n>", format("<a\n\\\\\n>"));
+
+ FormatStyle AlignLeft = getLLVMStyle();
+ AlignLeft.AlignEscapedNewlines = FormatStyle::ENAS_Left;
+ EXPECT_EQ("#define MACRO(x) \\\n"
+ "private: \\\n"
+ " int x(int a);\n",
+ format("#define MACRO(x) \\\n"
+ "private: \\\n"
+ " int x(int a);\n",
+ AlignLeft));
+
+ // CRLF line endings
+ EXPECT_EQ("#define A \\\r\n int i; \\\r\n int j;",
+ format("#define A \\\r\nint i;\\\r\n int j;", Narrow));
+ EXPECT_EQ("#define A\r\n\r\nint i;", format("#define A \\\r\n\r\n int i;"));
+ EXPECT_EQ("template <class T> f();", format("\\\ntemplate <class T> f();"));
+ EXPECT_EQ("/* \\ \\ \\\r\n */", format("\\\r\n/* \\ \\ \\\r\n */"));
+ EXPECT_EQ("<a\r\n\\\\\r\n>", format("<a\r\n\\\\\r\n>"));
+ EXPECT_EQ("#define MACRO(x) \\\r\n"
+ "private: \\\r\n"
+ " int x(int a);\r\n",
+ format("#define MACRO(x) \\\r\n"
+ "private: \\\r\n"
+ " int x(int a);\r\n",
+ AlignLeft));
+
+ FormatStyle DontAlign = getLLVMStyle();
+ DontAlign.AlignEscapedNewlines = FormatStyle::ENAS_DontAlign;
+ DontAlign.MaxEmptyLinesToKeep = 3;
+ // FIXME: can't use verifyFormat here because the newline before
+ // "public:" is not inserted the first time it's reformatted
+ EXPECT_EQ("#define A \\\n"
+ " class Foo { \\\n"
+ " void bar(); \\\n"
+ "\\\n"
+ "\\\n"
+ "\\\n"
+ " public: \\\n"
+ " void baz(); \\\n"
+ " };",
+ format("#define A \\\n"
+ " class Foo { \\\n"
+ " void bar(); \\\n"
+ "\\\n"
+ "\\\n"
+ "\\\n"
+ " public: \\\n"
+ " void baz(); \\\n"
+ " };",
+ DontAlign));
}
TEST_F(FormatTest, CalculateSpaceOnConsecutiveLinesInMacro) {
@@ -2619,6 +3072,10 @@ TEST_F(FormatTest, LineBreakingInBinaryExpressions) {
"if (aaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaa(\n"
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) == 5) {\n"
"}");
+ verifyFormat(
+ "if (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(\n"
+ " aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) <=> 5) {\n"
+ "}");
// Even explicit parentheses stress the precedence enough to make the
// additional break unnecessary.
verifyFormat("if ((aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +\n"
@@ -2638,6 +3095,10 @@ TEST_F(FormatTest, LineBreakingInBinaryExpressions) {
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ==\n"
" 5) {\n"
"}");
+ verifyFormat("if (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +\n"
+ " aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa <=>\n"
+ " 5) {\n"
+ "}");
FormatStyle OnePerLine = getLLVMStyle();
OnePerLine.BinPackParameters = false;
@@ -3144,6 +3605,10 @@ TEST_F(FormatTest, BreakConstructorInitializersAfterColon) {
Style);
}
+#ifndef EXPENSIVE_CHECKS
+// Expensive checks enables libstdc++ checking which includes validating the
+// state of ranges used in std::priority_queue - this blows out the
+// runtime/scalability of the function and makes this test unacceptably slow.
TEST_F(FormatTest, MemoizationTests) {
// This breaks if the memoization lookup does not take \c Indent and
// \c LastSpace into account.
@@ -3222,6 +3687,7 @@ TEST_F(FormatTest, MemoizationTests) {
input += " a) {}";
verifyFormat(input, OnePerLine);
}
+#endif
TEST_F(FormatTest, BreaksAsHighAsPossible) {
verifyFormat(
@@ -5138,6 +5604,7 @@ TEST_F(FormatTest, UnderstandsOverloadedOperators) {
verifyFormat("bool operator!=();");
verifyFormat("int operator+();");
verifyFormat("int operator++();");
+ verifyFormat("int operator++(int) volatile noexcept;");
verifyFormat("bool operator,();");
verifyFormat("bool operator();");
verifyFormat("bool operator()();");
@@ -5188,7 +5655,8 @@ TEST_F(FormatTest, UnderstandsFunctionRefQualification) {
verifyFormat("SomeType MemberFunction(const Deleted &) && {}");
verifyFormat("SomeType MemberFunction(const Deleted &) && final {}");
verifyFormat("SomeType MemberFunction(const Deleted &) && override {}");
- verifyFormat("SomeType MemberFunction(const Deleted &) const &;");
+ verifyFormat("void Fn(T const &) const &;");
+ verifyFormat("void Fn(T const volatile &&) const volatile &&;");
verifyFormat("template <typename T>\n"
"void F(T) && = delete;",
getGoogleStyle());
@@ -5205,7 +5673,8 @@ TEST_F(FormatTest, UnderstandsFunctionRefQualification) {
verifyFormat("auto Function(T... t) & -> void {}", AlignLeft);
verifyFormat("auto Function(T) & -> void {}", AlignLeft);
verifyFormat("auto Function(T) & -> void;", AlignLeft);
- verifyFormat("SomeType MemberFunction(const Deleted&) const &;", AlignLeft);
+ verifyFormat("void Fn(T const&) const&;", AlignLeft);
+ verifyFormat("void Fn(T const volatile&&) const volatile&&;", AlignLeft);
FormatStyle Spaces = getLLVMStyle();
Spaces.SpacesInCStyleCastParentheses = true;
@@ -5351,6 +5820,10 @@ TEST_F(FormatTest, UnderstandsUsesOfStarAndAmp) {
verifyFormat("for (;; *a = b) {\n}", Left);
verifyFormat("return *this += 1;", Left);
verifyFormat("throw *x;", Left);
+ verifyFormat("delete *x;", Left);
+ verifyFormat("typedef typeof(int(int, int))* MyFuncPtr;", Left);
+ verifyFormat("[](const decltype(*a)* ptr) {}", Left);
+ verifyFormat("typedef typeof /*comment*/ (int(int, int))* MyFuncPtr;", Left);
verifyIndependentOfContext("a = *(x + y);");
verifyIndependentOfContext("a = &(x + y);");
@@ -5397,9 +5870,6 @@ TEST_F(FormatTest, UnderstandsUsesOfStarAndAmp) {
verifyGoogleFormat("T** t = new T*;");
verifyGoogleFormat("T** t = new T*();");
- FormatStyle PointerLeft = getLLVMStyle();
- PointerLeft.PointerAlignment = FormatStyle::PAS_Left;
- verifyFormat("delete *x;", PointerLeft);
verifyFormat("STATIC_ASSERT((a & b) == 0);");
verifyFormat("STATIC_ASSERT(0 == (a & b));");
verifyFormat("template <bool a, bool b> "
@@ -5862,7 +6332,8 @@ TEST_F(FormatTest, HandlesIncludeDirectives) {
"#include_next <test.h>"
"#include \"abc.h\" // this is included for ABC\n"
"#include \"some long include\" // with a comment\n"
- "#include \"some very long include paaaaaaaaaaaaaaaaaaaaaaath\"",
+ "#include \"some very long include path\"\n"
+ "#include <some/very/long/include/path>\n",
getLLVMStyleWithColumns(35));
EXPECT_EQ("#include \"a.h\"", format("#include \"a.h\""));
EXPECT_EQ("#include <a>", format("#include<a>"));
@@ -6672,6 +7143,16 @@ TEST_F(FormatTest, SplitEmptyFunction) {
"}",
Style);
}
+TEST_F(FormatTest, KeepShortFunctionAfterPPElse) {
+ FormatStyle Style = getLLVMStyle();
+ Style.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_All;
+ verifyFormat("#ifdef A\n"
+ "int f() {}\n"
+ "#else\n"
+ "int g() {}\n"
+ "#endif",
+ Style);
+}
TEST_F(FormatTest, SplitEmptyClass) {
FormatStyle Style = getLLVMStyle();
@@ -7259,6 +7740,12 @@ TEST_F(FormatTest, BreaksStringLiterals) {
format("#define A \"some text other\";", AlignLeft));
}
+TEST_F(FormatTest, BreaksStringLiteralsAtColumnLimit) {
+ EXPECT_EQ("C a = \"some more \"\n"
+ " \"text\";",
+ format("C a = \"some more text\";", getLLVMStyleWithColumns(18)));
+}
+
TEST_F(FormatTest, FullyRemoveEmptyLines) {
FormatStyle NoEmptyLines = getLLVMStyleWithColumns(80);
NoEmptyLines.MaxEmptyLinesToKeep = 0;
@@ -7534,9 +8021,9 @@ TEST_F(FormatTest, BreakStringLiteralsBeforeUnbreakableTokenSequence) {
" \"f\");",
format("someFunction1234567890(\"aaabbbcccdddeeefff\");",
getLLVMStyleWithColumns(24)));
- EXPECT_EQ("someFunction(\"aaabbbcc \"\n"
- " \"ddde \"\n"
- " \"efff\");",
+ EXPECT_EQ("someFunction(\n"
+ " \"aaabbbcc ddde \"\n"
+ " \"efff\");",
format("someFunction(\"aaabbbcc ddde efff\");",
getLLVMStyleWithColumns(25)));
EXPECT_EQ("someFunction(\"aaabbbccc \"\n"
@@ -7555,10 +8042,9 @@ TEST_F(FormatTest, BreakStringLiteralsBeforeUnbreakableTokenSequence) {
" int i;",
format("#define A string s = \"1234567890\"; int i;",
getLLVMStyleWithColumns(20)));
- // FIXME: Put additional penalties on breaking at non-whitespace locations.
- EXPECT_EQ("someFunction(\"aaabbbcc \"\n"
- " \"dddeeeff\"\n"
- " \"f\");",
+ EXPECT_EQ("someFunction(\n"
+ " \"aaabbbcc \"\n"
+ " \"dddeeefff\");",
format("someFunction(\"aaabbbcc dddeeefff\");",
getLLVMStyleWithColumns(25)));
}
@@ -8893,7 +9379,7 @@ TEST_F(FormatTest, LinuxBraceBreaking) {
"struct B {\n"
" int x;\n"
"};\n"
- "}\n",
+ "} // namespace a\n",
LinuxBraceStyle);
verifyFormat("enum X {\n"
" Y = 0,\n"
@@ -9023,6 +9509,19 @@ TEST_F(FormatTest, StroustrupBraceBreaking) {
TEST_F(FormatTest, AllmanBraceBreaking) {
FormatStyle AllmanBraceStyle = getLLVMStyle();
AllmanBraceStyle.BreakBeforeBraces = FormatStyle::BS_Allman;
+
+ EXPECT_EQ("namespace a\n"
+ "{\n"
+ "void f();\n"
+ "void g();\n"
+ "} // namespace a\n",
+ format("namespace a\n"
+ "{\n"
+ "void f();\n"
+ "void g();\n"
+ "}\n",
+ AllmanBraceStyle));
+
verifyFormat("namespace a\n"
"{\n"
"class A\n"
@@ -9041,7 +9540,7 @@ TEST_F(FormatTest, AllmanBraceBreaking) {
"{\n"
" int x;\n"
"};\n"
- "}",
+ "} // namespace a",
AllmanBraceStyle);
verifyFormat("void f()\n"
@@ -9247,7 +9746,7 @@ TEST_F(FormatTest, GNUBraceBreaking) {
" }\n"
" void g() { return; }\n"
"}\n"
- "}",
+ "} // namespace a",
GNUBraceStyle);
verifyFormat("void f()\n"
@@ -9414,6 +9913,113 @@ TEST_F(FormatTest, UnderstandPragmaOption) {
EXPECT_EQ("#pragma option -C -A", format("#pragma option -C -A"));
}
+TEST_F(FormatTest, OptimizeBreakPenaltyVsExcess) {
+ FormatStyle Style = getLLVMStyle();
+ Style.ColumnLimit = 20;
+
+ verifyFormat("int a; // the\n"
+ " // comment", Style);
+ EXPECT_EQ("int a; /* first line\n"
+ " * second\n"
+ " * line third\n"
+ " * line\n"
+ " */",
+ format("int a; /* first line\n"
+ " * second\n"
+ " * line third\n"
+ " * line\n"
+ " */",
+ Style));
+ EXPECT_EQ("int a; // first line\n"
+ " // second\n"
+ " // line third\n"
+ " // line",
+ format("int a; // first line\n"
+ " // second line\n"
+ " // third line",
+ Style));
+
+ Style.PenaltyExcessCharacter = 90;
+ verifyFormat("int a; // the comment", Style);
+ EXPECT_EQ("int a; // the comment\n"
+ " // aaa",
+ format("int a; // the comment aaa", Style));
+ EXPECT_EQ("int a; /* first line\n"
+ " * second line\n"
+ " * third line\n"
+ " */",
+ format("int a; /* first line\n"
+ " * second line\n"
+ " * third line\n"
+ " */",
+ Style));
+ EXPECT_EQ("int a; // first line\n"
+ " // second line\n"
+ " // third line",
+ format("int a; // first line\n"
+ " // second line\n"
+ " // third line",
+ Style));
+ // FIXME: Investigate why this is not getting the same layout as the test
+ // above.
+ EXPECT_EQ("int a; /* first line\n"
+ " * second line\n"
+ " * third line\n"
+ " */",
+ format("int a; /* first line second line third line"
+ "\n*/",
+ Style));
+
+ EXPECT_EQ("// foo bar baz bazfoo\n"
+ "// foo bar foo bar\n",
+ format("// foo bar baz bazfoo\n"
+ "// foo bar foo bar\n",
+ Style));
+ EXPECT_EQ("// foo bar baz bazfoo\n"
+ "// foo bar foo bar\n",
+ format("// foo bar baz bazfoo\n"
+ "// foo bar foo bar\n",
+ Style));
+
+ // FIXME: Optimally, we'd keep bazfoo on the first line and reflow bar to the
+ // next one.
+ EXPECT_EQ("// foo bar baz bazfoo\n"
+ "// bar foo bar\n",
+ format("// foo bar baz bazfoo bar\n"
+ "// foo bar\n",
+ Style));
+
+ EXPECT_EQ("// foo bar baz bazfoo\n"
+ "// foo bar baz bazfoo\n"
+ "// bar foo bar\n",
+ format("// foo bar baz bazfoo\n"
+ "// foo bar baz bazfoo bar\n"
+ "// foo bar\n",
+ Style));
+
+ EXPECT_EQ("// foo bar baz bazfoo\n"
+ "// foo bar baz bazfoo\n"
+ "// bar foo bar\n",
+ format("// foo bar baz bazfoo\n"
+ "// foo bar baz bazfoo bar\n"
+ "// foo bar\n",
+ Style));
+
+ // Make sure we do not keep protruding characters if strict mode reflow is
+ // cheaper than keeping protruding characters.
+ Style.ColumnLimit = 21;
+ EXPECT_EQ("// foo foo foo foo\n"
+ "// foo foo foo foo\n"
+ "// foo foo foo foo\n",
+ format("// foo foo foo foo foo foo foo foo foo foo foo foo\n",
+ Style));
+
+ EXPECT_EQ("int a = /* long block\n"
+ " comment */\n"
+ " 42;",
+ format("int a = /* long block comment */ 42;", Style));
+}
+
#define EXPECT_ALL_STYLES_EQUAL(Styles) \
for (size_t i = 1; i < Styles.size(); ++i) \
EXPECT_EQ(Styles[0], Styles[i]) << "Style #" << i << " of " << Styles.size() \
@@ -9584,6 +10190,7 @@ TEST_F(FormatTest, ParsesConfigurationBools) {
CHECK_PARSE_NESTED_BOOL(BraceWrapping, AfterObjCDeclaration);
CHECK_PARSE_NESTED_BOOL(BraceWrapping, AfterStruct);
CHECK_PARSE_NESTED_BOOL(BraceWrapping, AfterUnion);
+ CHECK_PARSE_NESTED_BOOL(BraceWrapping, AfterExternBlock);
CHECK_PARSE_NESTED_BOOL(BraceWrapping, BeforeCatch);
CHECK_PARSE_NESTED_BOOL(BraceWrapping, BeforeElse);
CHECK_PARSE_NESTED_BOOL(BraceWrapping, IndentBraces);
@@ -9800,6 +10407,20 @@ TEST_F(FormatTest, ParsesConfiguration) {
" Priority: 1",
IncludeCategories, ExpectedCategories);
CHECK_PARSE("IncludeIsMainRegex: 'abc$'", IncludeIsMainRegex, "abc$");
+
+ Style.RawStringFormats.clear();
+ std::vector<FormatStyle::RawStringFormat> ExpectedRawStringFormats = {
+ {"pb", FormatStyle::LK_TextProto, "llvm"},
+ {"cpp", FormatStyle::LK_Cpp, "google"}};
+
+ CHECK_PARSE("RawStringFormats:\n"
+ " - Delimiter: 'pb'\n"
+ " Language: TextProto\n"
+ " BasedOnStyle: llvm\n"
+ " - Delimiter: 'cpp'\n"
+ " Language: Cpp\n"
+ " BasedOnStyle: google",
+ RawStringFormats, ExpectedRawStringFormats);
}
TEST_F(FormatTest, ParsesConfigurationWithLanguages) {
@@ -9993,10 +10614,12 @@ TEST_F(FormatTest, SplitsUTF8Strings) {
"\"七 八 九 \"\n"
"\"十\"",
format("\"一 二 三 四 五六 七 八 九 十\"", getLLVMStyleWithColumns(11)));
- EXPECT_EQ("\"一\t二 \"\n"
- "\"\t三 \"\n"
- "\"四 五\t六 \"\n"
- "\"\t七 \"\n"
+ EXPECT_EQ("\"一\t\"\n"
+ "\"二 \t\"\n"
+ "\"三 四 \"\n"
+ "\"五\t\"\n"
+ "\"六 \t\"\n"
+ "\"七 \"\n"
"\"八九十\tqq\"",
format("\"一\t二 \t三 四 五\t六 \t七 八九十\tqq\"",
getLLVMStyleWithColumns(11)));
@@ -10339,6 +10962,8 @@ TEST_F(FormatTest, FormatsLambdas) {
verifyFormat("int c = [&a, &a, a] { [=, a, b, &c] { return b++; }(); }();\n");
verifyFormat("auto c = {[&a, &a, a] { [=, a, b, &c] { return b++; }(); }}\n");
verifyFormat("auto c = {[&a, &a, a] { [=, a, b, &c] {}(); }}\n");
+ verifyFormat("auto c = [a = [b = 42] {}] {};\n");
+ verifyFormat("auto c = [a = &i + 10, b = [] {}] {};\n");
verifyFormat("int x = f(*+[] {});");
verifyFormat("void f() {\n"
" other(x.begin(), x.end(), [&](int, int) { return 1; });\n"
@@ -10490,6 +11115,17 @@ TEST_F(FormatTest, FormatsLambdas) {
" });");
}
+TEST_F(FormatTest, EmptyLinesInLambdas) {
+ verifyFormat("auto lambda = []() {\n"
+ " x(); //\n"
+ "};",
+ "auto lambda = []() {\n"
+ "\n"
+ " x(); //\n"
+ "\n"
+ "};");
+}
+
TEST_F(FormatTest, FormatsBlocks) {
FormatStyle ShortBlocks = getLLVMStyle();
ShortBlocks.AllowShortBlocksOnASingleLine = true;
@@ -11129,6 +11765,78 @@ TEST_F(FormatTest, UTF8CharacterLiteralCpp11) {
EXPECT_EQ("auto c = u8'a';", format("auto c = u8'a';"));
}
+TEST_F(FormatTest, DoNotFormatLikelyXml) {
+ EXPECT_EQ("<!-- ;> -->",
+ format("<!-- ;> -->", getGoogleStyle()));
+ EXPECT_EQ(" <!-- >; -->",
+ format(" <!-- >; -->", getGoogleStyle()));
+}
+
+TEST_F(FormatTest, StructuredBindings) {
+ // Structured bindings is a C++17 feature.
+ // all modes, including C++11, C++14 and C++17
+ verifyFormat("auto [a, b] = f();");
+ EXPECT_EQ("auto [a, b] = f();", format("auto[a, b] = f();"));
+ EXPECT_EQ("const auto [a, b] = f();", format("const auto[a, b] = f();"));
+ EXPECT_EQ("auto const [a, b] = f();", format("auto const[a, b] = f();"));
+ EXPECT_EQ("auto const volatile [a, b] = f();",
+ format("auto const volatile[a, b] = f();"));
+ EXPECT_EQ("auto [a, b, c] = f();", format("auto [ a , b,c ] = f();"));
+ EXPECT_EQ("auto &[a, b, c] = f();",
+ format("auto &[ a , b,c ] = f();"));
+ EXPECT_EQ("auto &&[a, b, c] = f();",
+ format("auto &&[ a , b,c ] = f();"));
+ EXPECT_EQ("auto const &[a, b] = f();", format("auto const&[a, b] = f();"));
+ EXPECT_EQ("auto const volatile &&[a, b] = f();",
+ format("auto const volatile &&[a, b] = f();"));
+ EXPECT_EQ("auto const &&[a, b] = f();", format("auto const && [a, b] = f();"));
+ EXPECT_EQ("const auto &[a, b] = f();", format("const auto & [a, b] = f();"));
+ EXPECT_EQ("const auto volatile &&[a, b] = f();",
+ format("const auto volatile &&[a, b] = f();"));
+ EXPECT_EQ("volatile const auto &&[a, b] = f();",
+ format("volatile const auto &&[a, b] = f();"));
+ EXPECT_EQ("const auto &&[a, b] = f();", format("const auto && [a, b] = f();"));
+
+ // Make sure we don't mistake structured bindings for lambdas.
+ FormatStyle PointerMiddle = getLLVMStyle();
+ PointerMiddle.PointerAlignment = FormatStyle::PAS_Middle;
+ verifyFormat("auto [a1, b]{A * i};", getGoogleStyle());
+ verifyFormat("auto [a2, b]{A * i};", getLLVMStyle());
+ verifyFormat("auto [a3, b]{A * i};", PointerMiddle);
+ verifyFormat("auto const [a1, b]{A * i};", getGoogleStyle());
+ verifyFormat("auto const [a2, b]{A * i};", getLLVMStyle());
+ verifyFormat("auto const [a3, b]{A * i};", PointerMiddle);
+ verifyFormat("auto const& [a1, b]{A * i};", getGoogleStyle());
+ verifyFormat("auto const &[a2, b]{A * i};", getLLVMStyle());
+ verifyFormat("auto const & [a3, b]{A * i};", PointerMiddle);
+ verifyFormat("auto const&& [a1, b]{A * i};", getGoogleStyle());
+ verifyFormat("auto const &&[a2, b]{A * i};", getLLVMStyle());
+ verifyFormat("auto const && [a3, b]{A * i};", PointerMiddle);
+
+ EXPECT_EQ("for (const auto &&[a, b] : some_range) {\n}",
+ format("for (const auto && [a, b] : some_range) {\n}"));
+ EXPECT_EQ("for (const auto &[a, b] : some_range) {\n}",
+ format("for (const auto & [a, b] : some_range) {\n}"));
+ EXPECT_EQ("for (const auto [a, b] : some_range) {\n}",
+ format("for (const auto[a, b] : some_range) {\n}"));
+ EXPECT_EQ("auto [x, y](expr);", format("auto[x,y] (expr);"));
+ EXPECT_EQ("auto &[x, y](expr);", format("auto & [x,y] (expr);"));
+ EXPECT_EQ("auto &&[x, y](expr);", format("auto && [x,y] (expr);"));
+ EXPECT_EQ("auto const &[x, y](expr);", format("auto const & [x,y] (expr);"));
+ EXPECT_EQ("auto const &&[x, y](expr);", format("auto const && [x,y] (expr);"));
+ EXPECT_EQ("auto [x, y]{expr};", format("auto[x,y] {expr};"));
+ EXPECT_EQ("auto const &[x, y]{expr};", format("auto const & [x,y] {expr};"));
+ EXPECT_EQ("auto const &&[x, y]{expr};", format("auto const && [x,y] {expr};"));
+
+ format::FormatStyle Spaces = format::getLLVMStyle();
+ Spaces.SpacesInSquareBrackets = true;
+ verifyFormat("auto [ a, b ] = f();", Spaces);
+ verifyFormat("auto &&[ a, b ] = f();", Spaces);
+ verifyFormat("auto &[ a, b ] = f();", Spaces);
+ verifyFormat("auto const &&[ a, b ] = f();", Spaces);
+ verifyFormat("auto const &[ a, b ] = f();", Spaces);
+}
+
} // end namespace
} // end namespace format
} // end namespace clang
diff --git a/unittests/Format/FormatTestComments.cpp b/unittests/Format/FormatTestComments.cpp
index f3c45fac34a99..ed11fbdb1fc0d 100644
--- a/unittests/Format/FormatTestComments.cpp
+++ b/unittests/Format/FormatTestComments.cpp
@@ -62,6 +62,12 @@ protected:
return Style;
}
+ FormatStyle getTextProtoStyleWithColumns(unsigned ColumnLimit) {
+ FormatStyle Style = getGoogleStyle(FormatStyle::FormatStyle::LK_TextProto);
+ Style.ColumnLimit = ColumnLimit;
+ return Style;
+ }
+
void verifyFormat(llvm::StringRef Code,
const FormatStyle &Style = getLLVMStyle()) {
EXPECT_EQ(Code.str(), format(test::messUp(Code), Style));
@@ -680,8 +686,7 @@ TEST_F(FormatTestComments, SplitsLongCxxComments) {
EXPECT_EQ("{\n"
" //\n"
" //\\\n"
- " // long 1 2 3 4\n"
- " // 5\n"
+ " // long 1 2 3 4 5\n"
"}",
format("{\n"
" //\n"
@@ -689,6 +694,18 @@ TEST_F(FormatTestComments, SplitsLongCxxComments) {
" // long 1 2 3 4 5\n"
"}",
getLLVMStyleWithColumns(20)));
+ EXPECT_EQ("{\n"
+ " //\n"
+ " //\\\n"
+ " // long 1 2 3 4 5\n"
+ " // 6\n"
+ "}",
+ format("{\n"
+ " //\n"
+ " //\\\n"
+ " // long 1 2 3 4 5 6\n"
+ "}",
+ getLLVMStyleWithColumns(20)));
}
TEST_F(FormatTestComments, PreservesHangingIndentInCxxComments) {
@@ -836,6 +853,67 @@ TEST_F(FormatTestComments, KeepsLevelOfCommentBeforePPDirective) {
" int j;\n"
"}"));
+ EXPECT_EQ("int f(int i) {\n"
+ " if (true) {\n"
+ " ++i;\n"
+ " }\n"
+ " // comment\n"
+ "#ifdef A\n"
+ " int j;\n"
+ "#endif\n"
+ "}",
+ format("int f(int i) {\n"
+ " if (true) {\n"
+ " ++i;\n"
+ " }\n"
+ " // comment\n"
+ "#ifdef A\n"
+ "int j;\n"
+ "#endif\n"
+ "}"));
+
+ EXPECT_EQ("int f(int i) {\n"
+ " if (true) {\n"
+ " i++;\n"
+ " } else {\n"
+ " // comment in else\n"
+ "#ifdef A\n"
+ " j++;\n"
+ "#endif\n"
+ " }\n"
+ "}",
+ format("int f(int i) {\n"
+ " if (true) {\n"
+ " i++;\n"
+ " } else {\n"
+ " // comment in else\n"
+ "#ifdef A\n"
+ " j++;\n"
+ "#endif\n"
+ " }\n"
+ "}"));
+
+ EXPECT_EQ("int f(int i) {\n"
+ " if (true) {\n"
+ " i++;\n"
+ " } else {\n"
+ " /* comment in else */\n"
+ "#ifdef A\n"
+ " j++;\n"
+ "#endif\n"
+ " }\n"
+ "}",
+ format("int f(int i) {\n"
+ " if (true) {\n"
+ " i++;\n"
+ " } else {\n"
+ " /* comment in else */\n"
+ "#ifdef A\n"
+ " j++;\n"
+ "#endif\n"
+ " }\n"
+ "}"));
+
// Keep the current level if there is an empty line between the comment and
// the preprocessor directive.
EXPECT_EQ("void f() {\n"
@@ -853,8 +931,95 @@ TEST_F(FormatTestComments, KeepsLevelOfCommentBeforePPDirective) {
" int j;\n"
"}"));
+ EXPECT_EQ("void f() {\n"
+ " int i;\n"
+ " return i;\n"
+ "}\n"
+ "// comment\n"
+ "\n"
+ "#ifdef A\n"
+ "int i;\n"
+ "#endif // A",
+ format("void f() {\n"
+ " int i;\n"
+ " return i;\n"
+ "}\n"
+ "// comment\n"
+ "\n"
+ "#ifdef A\n"
+ "int i;\n"
+ "#endif // A"));
+
+ EXPECT_EQ("int f(int i) {\n"
+ " if (true) {\n"
+ " ++i;\n"
+ " }\n"
+ " // comment\n"
+ "\n"
+ "#ifdef A\n"
+ " int j;\n"
+ "#endif\n"
+ "}",
+ format("int f(int i) {\n"
+ " if (true) {\n"
+ " ++i;\n"
+ " }\n"
+ " // comment\n"
+ "\n"
+ "#ifdef A\n"
+ " int j;\n"
+ "#endif\n"
+ "}"));
+
+ EXPECT_EQ("int f(int i) {\n"
+ " if (true) {\n"
+ " i++;\n"
+ " } else {\n"
+ " // comment in else\n"
+ "\n"
+ "#ifdef A\n"
+ " j++;\n"
+ "#endif\n"
+ " }\n"
+ "}",
+ format("int f(int i) {\n"
+ " if (true) {\n"
+ " i++;\n"
+ " } else {\n"
+ "// comment in else\n"
+ "\n"
+ "#ifdef A\n"
+ " j++;\n"
+ "#endif\n"
+ " }\n"
+ "}"));
+
+ EXPECT_EQ("int f(int i) {\n"
+ " if (true) {\n"
+ " i++;\n"
+ " } else {\n"
+ " /* comment in else */\n"
+ "\n"
+ "#ifdef A\n"
+ " j++;\n"
+ "#endif\n"
+ " }\n"
+ "}",
+ format("int f(int i) {\n"
+ " if (true) {\n"
+ " i++;\n"
+ " } else {\n"
+ "/* comment in else */\n"
+ "\n"
+ "#ifdef A\n"
+ " j++;\n"
+ "#endif\n"
+ " }\n"
+ "}"));
+
// Align with the preprocessor directive if the comment was originally aligned
- // with the preprocessor directive.
+ // with the preprocessor directive and there is no newline between the comment
+ // and the preprocessor directive.
EXPECT_EQ("void f() {\n"
" int i;\n"
"/* comment */\n"
@@ -867,14 +1032,76 @@ TEST_F(FormatTestComments, KeepsLevelOfCommentBeforePPDirective) {
"#ifdef A\n"
" int j;\n"
"}"));
+
+ EXPECT_EQ("int f(int i) {\n"
+ " if (true) {\n"
+ " ++i;\n"
+ " }\n"
+ "// comment\n"
+ "#ifdef A\n"
+ " int j;\n"
+ "#endif\n"
+ "}",
+ format("int f(int i) {\n"
+ " if (true) {\n"
+ " ++i;\n"
+ " }\n"
+ "// comment\n"
+ "#ifdef A\n"
+ " int j;\n"
+ "#endif\n"
+ "}"));
+
+ EXPECT_EQ("int f(int i) {\n"
+ " if (true) {\n"
+ " i++;\n"
+ " } else {\n"
+ "// comment in else\n"
+ "#ifdef A\n"
+ " j++;\n"
+ "#endif\n"
+ " }\n"
+ "}",
+ format("int f(int i) {\n"
+ " if (true) {\n"
+ " i++;\n"
+ " } else {\n"
+ " // comment in else\n"
+ " #ifdef A\n"
+ " j++;\n"
+ "#endif\n"
+ " }\n"
+ "}"));
+
+ EXPECT_EQ("int f(int i) {\n"
+ " if (true) {\n"
+ " i++;\n"
+ " } else {\n"
+ "/* comment in else */\n"
+ "#ifdef A\n"
+ " j++;\n"
+ "#endif\n"
+ " }\n"
+ "}",
+ format("int f(int i) {\n"
+ " if (true) {\n"
+ " i++;\n"
+ " } else {\n"
+ " /* comment in else */\n"
+ " #ifdef A\n"
+ " j++;\n"
+ "#endif\n"
+ " }\n"
+ "}"));
}
TEST_F(FormatTestComments, SplitsLongLinesInComments) {
+ // FIXME: Do we need to fix up the " */" at the end?
+ // It doesn't look like any of our current logic triggers this.
EXPECT_EQ("/* This is a long\n"
" * comment that\n"
- " * doesn't\n"
- " * fit on one line.\n"
- " */",
+ " * doesn't fit on\n"
+ " * one line. */",
format("/* "
"This is a long "
"comment that "
@@ -1852,6 +2079,22 @@ TEST_F(FormatTestComments, ReflowsComments) {
" // longsec\n",
getLLVMStyleWithColumns(20)));
+ // Simple case that correctly handles reflow in parameter lists.
+ EXPECT_EQ("a = f(/* looooooooong\n"
+ " * long long\n"
+ " */\n"
+ " a);",
+ format("a = f(/* looooooooong long\n* long\n*/ a);",
+ getLLVMStyleWithColumns(22)));
+ // Tricky case that has fewer lines if we reflow the comment, ending up with
+ // fewer lines.
+ EXPECT_EQ("a = f(/* loooooong\n"
+ " * long long\n"
+ " */\n"
+ " a);",
+ format("a = f(/* loooooong long\n* long\n*/ a);",
+ getLLVMStyleWithColumns(22)));
+
// Keep empty comment lines.
EXPECT_EQ("/**/", format(" /**/", getLLVMStyleWithColumns(20)));
EXPECT_EQ("/* */", format(" /* */", getLLVMStyleWithColumns(20)));
@@ -1860,6 +2103,85 @@ TEST_F(FormatTestComments, ReflowsComments) {
EXPECT_EQ("///", format(" /// ", getLLVMStyleWithColumns(20)));
}
+TEST_F(FormatTestComments, ReflowsCommentsPrecise) {
+ // FIXME: This assumes we do not continue compressing whitespace once we are
+ // in reflow mode. Consider compressing whitespace.
+
+ // Test that we stop reflowing precisely at the column limit.
+ // After reflowing, "// reflows into foo" does not fit the column limit,
+ // so we compress the whitespace.
+ EXPECT_EQ("// some text that\n"
+ "// reflows into foo\n",
+ format("// some text that reflows\n"
+ "// into foo\n",
+ getLLVMStyleWithColumns(20)));
+ // Given one more column, "// reflows into foo" does fit the limit, so we
+ // do not compress the whitespace.
+ EXPECT_EQ("// some text that\n"
+ "// reflows into foo\n",
+ format("// some text that reflows\n"
+ "// into foo\n",
+ getLLVMStyleWithColumns(21)));
+
+ // Make sure that we correctly account for the space added in the reflow case
+ // when making the reflowing decision.
+ // First, when the next line ends precisely one column over the limit, do not
+ // reflow.
+ EXPECT_EQ("// some text that\n"
+ "// reflows\n"
+ "// into1234567\n",
+ format("// some text that reflows\n"
+ "// into1234567\n",
+ getLLVMStyleWithColumns(21)));
+ // Secondly, when the next line ends later, but the first word in that line
+ // is precisely one column over the limit, do not reflow.
+ EXPECT_EQ("// some text that\n"
+ "// reflows\n"
+ "// into1234567 f\n",
+ format("// some text that reflows\n"
+ "// into1234567 f\n",
+ getLLVMStyleWithColumns(21)));
+}
+
+TEST_F(FormatTestComments, ReflowsCommentsWithExtraWhitespace) {
+ // Baseline.
+ EXPECT_EQ("// some text\n"
+ "// that re flows\n",
+ format("// some text that\n"
+ "// re flows\n",
+ getLLVMStyleWithColumns(16)));
+ EXPECT_EQ("// some text\n"
+ "// that re flows\n",
+ format("// some text that\n"
+ "// re flows\n",
+ getLLVMStyleWithColumns(16)));
+ EXPECT_EQ("/* some text\n"
+ " * that re flows\n"
+ " */\n",
+ format("/* some text that\n"
+ "* re flows\n"
+ "*/\n",
+ getLLVMStyleWithColumns(16)));
+ // FIXME: We do not reflow if the indent of two subsequent lines differs;
+ // given that this is different behavior from block comments, do we want
+ // to keep this?
+ EXPECT_EQ("// some text\n"
+ "// that\n"
+ "// re flows\n",
+ format("// some text that\n"
+ "// re flows\n",
+ getLLVMStyleWithColumns(16)));
+ // Space within parts of a line that fit.
+ // FIXME: Use the earliest possible split while reflowing to compress the
+ // whitespace within the line.
+ EXPECT_EQ("// some text that\n"
+ "// does re flow\n"
+ "// more here\n",
+ format("// some text that does\n"
+ "// re flow more here\n",
+ getLLVMStyleWithColumns(21)));
+}
+
TEST_F(FormatTestComments, IgnoresIf0Contents) {
EXPECT_EQ("#if 0\n"
"}{)(&*(^%%#%@! fsadj f;ldjs ,:;| <<<>>>][)(][\n"
@@ -2198,6 +2520,74 @@ TEST_F(FormatTestComments, BlockCommentsAtEndOfLine) {
getLLVMStyleWithColumns(15)));
}
+TEST_F(FormatTestComments, BreaksAfterMultilineBlockCommentsInParamLists) {
+ EXPECT_EQ("a = f(/* long\n"
+ " long */\n"
+ " a);",
+ format("a = f(/* long long */ a);", getLLVMStyleWithColumns(16)));
+ EXPECT_EQ("a = f(\n"
+ " /* long\n"
+ " long */\n"
+ " a);",
+ format("a = f(/* long long */ a);", getLLVMStyleWithColumns(15)));
+
+ EXPECT_EQ("a = f(/* long\n"
+ " long\n"
+ " */\n"
+ " a);",
+ format("a = f(/* long\n"
+ " long\n"
+ " */a);",
+ getLLVMStyleWithColumns(16)));
+
+ EXPECT_EQ("a = f(/* long\n"
+ " long\n"
+ " */\n"
+ " a);",
+ format("a = f(/* long\n"
+ " long\n"
+ " */ a);",
+ getLLVMStyleWithColumns(16)));
+
+ EXPECT_EQ("a = f(/* long\n"
+ " long\n"
+ " */\n"
+ " (1 + 1));",
+ format("a = f(/* long\n"
+ " long\n"
+ " */ (1 + 1));",
+ getLLVMStyleWithColumns(16)));
+
+ EXPECT_EQ(
+ "a = f(a,\n"
+ " /* long\n"
+ " long */\n"
+ " b);",
+ format("a = f(a, /* long long */ b);", getLLVMStyleWithColumns(16)));
+
+ EXPECT_EQ(
+ "a = f(\n"
+ " a,\n"
+ " /* long\n"
+ " long */\n"
+ " b);",
+ format("a = f(a, /* long long */ b);", getLLVMStyleWithColumns(15)));
+
+ EXPECT_EQ("a = f(a,\n"
+ " /* long\n"
+ " long */\n"
+ " (1 + 1));",
+ format("a = f(a, /* long long */ (1 + 1));",
+ getLLVMStyleWithColumns(16)));
+ EXPECT_EQ("a = f(\n"
+ " a,\n"
+ " /* long\n"
+ " long */\n"
+ " (1 + 1));",
+ format("a = f(a, /* long long */ (1 + 1));",
+ getLLVMStyleWithColumns(15)));
+}
+
TEST_F(FormatTestComments, IndentLineCommentsInStartOfBlockAtEndOfFile) {
verifyFormat("{\n"
" // a\n"
@@ -2578,6 +2968,127 @@ TEST_F(FormatTestComments, AlignsBlockCommentDecorations) {
"* long */",
getLLVMStyleWithColumns(20)));
}
+
+TEST_F(FormatTestComments, NoCrash_Bug34236) {
+ // This is a test case from a crasher reported in:
+ // https://bugs.llvm.org/show_bug.cgi?id=34236
+ // Temporarily disable formatting for readability.
+ // clang-format off
+ EXPECT_EQ(
+"/* */ /*\n"
+" * a\n"
+" * b c d*/",
+ format(
+"/* */ /*\n"
+" * a b\n"
+" * c d*/",
+ getLLVMStyleWithColumns(80)));
+ // clang-format on
+}
+
+TEST_F(FormatTestComments, NonTrailingBlockComments) {
+ verifyFormat("const /** comment comment */ A = B;",
+ getLLVMStyleWithColumns(40));
+
+ verifyFormat("const /** comment comment comment */ A =\n"
+ " B;",
+ getLLVMStyleWithColumns(40));
+
+ EXPECT_EQ("const /** comment comment comment\n"
+ " comment */\n"
+ " A = B;",
+ format("const /** comment comment comment comment */\n"
+ " A = B;",
+ getLLVMStyleWithColumns(40)));
+}
+
+TEST_F(FormatTestComments, PythonStyleComments) {
+ // Keeps a space after '#'.
+ EXPECT_EQ("# comment\n"
+ "key: value",
+ format("#comment\n"
+ "key:value",
+ getTextProtoStyleWithColumns(20)));
+ EXPECT_EQ("# comment\n"
+ "key: value",
+ format("# comment\n"
+ "key:value",
+ getTextProtoStyleWithColumns(20)));
+ // Breaks long comment.
+ EXPECT_EQ("# comment comment\n"
+ "# comment\n"
+ "key: value",
+ format("# comment comment comment\n"
+ "key:value",
+ getTextProtoStyleWithColumns(20)));
+ // Indents comments.
+ EXPECT_EQ("data {\n"
+ " # comment comment\n"
+ " # comment\n"
+ " key: value\n"
+ "}",
+ format("data {\n"
+ "# comment comment comment\n"
+ "key: value}",
+ getTextProtoStyleWithColumns(20)));
+ EXPECT_EQ("data {\n"
+ " # comment comment\n"
+ " # comment\n"
+ " key: value\n"
+ "}",
+ format("data {# comment comment comment\n"
+ "key: value}",
+ getTextProtoStyleWithColumns(20)));
+ // Reflows long comments.
+ EXPECT_EQ("# comment comment\n"
+ "# comment comment\n"
+ "key: value",
+ format("# comment comment comment\n"
+ "# comment\n"
+ "key:value",
+ getTextProtoStyleWithColumns(20)));
+ // Breaks trailing comments.
+ EXPECT_EQ("k: val # comment\n"
+ " # comment\n"
+ "a: 1",
+ format("k:val#comment comment\n"
+ "a:1",
+ getTextProtoStyleWithColumns(20)));
+ EXPECT_EQ("id {\n"
+ " k: val # comment\n"
+ " # comment\n"
+ " # line line\n"
+ " a: 1\n"
+ "}",
+ format("id {k:val#comment comment\n"
+ "# line line\n"
+ "a:1}",
+ getTextProtoStyleWithColumns(20)));
+ // Aligns trailing comments.
+ EXPECT_EQ("k: val # commen1\n"
+ " # commen2\n"
+ " # commen3\n"
+ "# commen4\n"
+ "a: 1 # commen5\n"
+ " # commen6\n"
+ " # commen7",
+ format("k:val#commen1 commen2\n"
+ " # commen3\n"
+ "# commen4\n"
+ "a:1#commen5 commen6\n"
+ " #commen7",
+ getTextProtoStyleWithColumns(20)));
+}
+
+TEST_F(FormatTestComments, BreaksBeforeTrailingUnbreakableSequence) {
+ // The end of /* trail */ is exactly at 80 columns, but the unbreakable
+ // trailing sequence ); after it exceeds the column limit. Make sure we
+ // correctly break the line in that case.
+ verifyFormat("int a =\n"
+ " foo(/* trail */);",
+ getLLVMStyleWithColumns(23));
+}
+
} // end namespace
} // end namespace format
} // end namespace clang
diff --git a/unittests/Format/FormatTestJS.cpp b/unittests/Format/FormatTestJS.cpp
index c256ebe462635..2a929563f7544 100644
--- a/unittests/Format/FormatTestJS.cpp
+++ b/unittests/Format/FormatTestJS.cpp
@@ -65,6 +65,167 @@ protected:
TEST_F(FormatTestJS, BlockComments) {
verifyFormat("/* aaaaaaaaaaaaa */ aaaaaaaaaaaaaaaaaaaaaaaaaaa(\n"
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa);");
+ // Breaks after a single line block comment.
+ EXPECT_EQ("aaaaa = bbbb.ccccccccccccccc(\n"
+ " /** @type_{!cccc.rrrrrrr.MMMMMMMMMMMM.LLLLLLLLLLL.lala} */\n"
+ " mediaMessage);",
+ format("aaaaa = bbbb.ccccccccccccccc(\n"
+ " /** "
+ "@type_{!cccc.rrrrrrr.MMMMMMMMMMMM.LLLLLLLLLLL.lala} */ "
+ "mediaMessage);",
+ getGoogleJSStyleWithColumns(70)));
+ // Breaks after a multiline block comment.
+ EXPECT_EQ(
+ "aaaaa = bbbb.ccccccccccccccc(\n"
+ " /**\n"
+ " * @type_{!cccc.rrrrrrr.MMMMMMMMMMMM.LLLLLLLLLLL.lala}\n"
+ " */\n"
+ " mediaMessage);",
+ format("aaaaa = bbbb.ccccccccccccccc(\n"
+ " /**\n"
+ " * @type_{!cccc.rrrrrrr.MMMMMMMMMMMM.LLLLLLLLLLL.lala}\n"
+ " */ mediaMessage);",
+ getGoogleJSStyleWithColumns(70)));
+}
+
+TEST_F(FormatTestJS, JSDocComments) {
+ // Break the first line of a multiline jsdoc comment.
+ EXPECT_EQ("/**\n"
+ " * jsdoc line 1\n"
+ " * jsdoc line 2\n"
+ " */",
+ format("/** jsdoc line 1\n"
+ " * jsdoc line 2\n"
+ " */",
+ getGoogleJSStyleWithColumns(20)));
+ // Both break after '/**' and break the line itself.
+ EXPECT_EQ("/**\n"
+ " * jsdoc line long\n"
+ " * long jsdoc line 2\n"
+ " */",
+ format("/** jsdoc line long long\n"
+ " * jsdoc line 2\n"
+ " */",
+ getGoogleJSStyleWithColumns(20)));
+ // Break a short first line if the ending '*/' is on a newline.
+ EXPECT_EQ("/**\n"
+ " * jsdoc line 1\n"
+ " */",
+ format("/** jsdoc line 1\n"
+ " */", getGoogleJSStyleWithColumns(20)));
+ // Don't break the first line of a short single line jsdoc comment.
+ EXPECT_EQ("/** jsdoc line 1 */",
+ format("/** jsdoc line 1 */", getGoogleJSStyleWithColumns(20)));
+ // Don't break the first line of a single line jsdoc comment if it just fits
+ // the column limit.
+ EXPECT_EQ("/** jsdoc line 12 */",
+ format("/** jsdoc line 12 */", getGoogleJSStyleWithColumns(20)));
+ // Don't break after '/**' and before '*/' if there is no space between
+ // '/**' and the content.
+ EXPECT_EQ(
+ "/*** nonjsdoc long\n"
+ " * line */",
+ format("/*** nonjsdoc long line */", getGoogleJSStyleWithColumns(20)));
+ EXPECT_EQ(
+ "/**strange long long\n"
+ " * line */",
+ format("/**strange long long line */", getGoogleJSStyleWithColumns(20)));
+ // Break the first line of a single line jsdoc comment if it just exceeds the
+ // column limit.
+ EXPECT_EQ("/**\n"
+ " * jsdoc line 123\n"
+ " */",
+ format("/** jsdoc line 123 */", getGoogleJSStyleWithColumns(20)));
+ // Break also if the leading indent of the first line is more than 1 column.
+ EXPECT_EQ("/**\n"
+ " * jsdoc line 123\n"
+ " */",
+ format("/** jsdoc line 123 */", getGoogleJSStyleWithColumns(20)));
+ // Break also if the leading indent of the first line is more than 1 column.
+ EXPECT_EQ("/**\n"
+ " * jsdoc line 123\n"
+ " */",
+ format("/** jsdoc line 123 */", getGoogleJSStyleWithColumns(20)));
+ // Break after the content of the last line.
+ EXPECT_EQ("/**\n"
+ " * line 1\n"
+ " * line 2\n"
+ " */",
+ format("/**\n"
+ " * line 1\n"
+ " * line 2 */",
+ getGoogleJSStyleWithColumns(20)));
+ // Break both the content and after the content of the last line.
+ EXPECT_EQ("/**\n"
+ " * line 1\n"
+ " * line long long\n"
+ " * long\n"
+ " */",
+ format("/**\n"
+ " * line 1\n"
+ " * line long long long */",
+ getGoogleJSStyleWithColumns(20)));
+
+ // The comment block gets indented.
+ EXPECT_EQ("function f() {\n"
+ " /**\n"
+ " * comment about\n"
+ " * x\n"
+ " */\n"
+ " var x = 1;\n"
+ "}",
+ format("function f() {\n"
+ "/** comment about x */\n"
+ "var x = 1;\n"
+ "}",
+ getGoogleJSStyleWithColumns(20)));
+
+ // Don't break the first line of a single line short jsdoc comment pragma.
+ EXPECT_EQ("/** @returns j */",
+ format("/** @returns j */",
+ getGoogleJSStyleWithColumns(20)));
+
+ // Break a single line long jsdoc comment pragma.
+ EXPECT_EQ("/**\n"
+ " * @returns {string} jsdoc line 12\n"
+ " */",
+ format("/** @returns {string} jsdoc line 12 */",
+ getGoogleJSStyleWithColumns(20)));
+
+ EXPECT_EQ("/**\n"
+ " * @returns {string} jsdoc line 12\n"
+ " */",
+ format("/** @returns {string} jsdoc line 12 */",
+ getGoogleJSStyleWithColumns(20)));
+
+ EXPECT_EQ("/**\n"
+ " * @returns {string} jsdoc line 12\n"
+ " */",
+ format("/** @returns {string} jsdoc line 12*/",
+ getGoogleJSStyleWithColumns(20)));
+
+ // Fix a multiline jsdoc comment ending in a comment pragma.
+ EXPECT_EQ("/**\n"
+ " * line 1\n"
+ " * line 2\n"
+ " * @returns {string} jsdoc line 12\n"
+ " */",
+ format("/** line 1\n"
+ " * line 2\n"
+ " * @returns {string} jsdoc line 12 */",
+ getGoogleJSStyleWithColumns(20)));
+
+ EXPECT_EQ("/**\n"
+ " * line 1\n"
+ " * line 2\n"
+ " *\n"
+ " * @returns j\n"
+ " */",
+ format("/** line 1\n"
+ " * line 2\n"
+ " *\n"
+ " * @returns j */",
+ getGoogleJSStyleWithColumns(20)));
}
TEST_F(FormatTestJS, UnderstandsJavaScriptOperators) {
@@ -131,14 +292,21 @@ TEST_F(FormatTestJS, ReservedWords) {
verifyFormat("x.case = 1;");
verifyFormat("x.interface = 1;");
verifyFormat("x.for = 1;");
- verifyFormat("x.of() = 1;");
+ verifyFormat("x.of();");
verifyFormat("of(null);");
verifyFormat("import {of} from 'x';");
- verifyFormat("x.in() = 1;");
- verifyFormat("x.let() = 1;");
- verifyFormat("x.var() = 1;");
- verifyFormat("x.for() = 1;");
- verifyFormat("x.as() = 1;");
+ verifyFormat("x.in();");
+ verifyFormat("x.let();");
+ verifyFormat("x.var();");
+ verifyFormat("x.for();");
+ verifyFormat("x.as();");
+ verifyFormat("x.instanceof();");
+ verifyFormat("x.switch();");
+ verifyFormat("x.case();");
+ verifyFormat("x.delete();");
+ verifyFormat("x.throw();");
+ verifyFormat("x.throws();");
+ verifyFormat("x.if();");
verifyFormat("x = {\n"
" a: 12,\n"
" interface: 1,\n"
@@ -149,6 +317,17 @@ TEST_F(FormatTestJS, ReservedWords) {
verifyFormat("var interface = 2;");
verifyFormat("interface = 2;");
verifyFormat("x = interface instanceof y;");
+ verifyFormat("interface Test {\n"
+ " x: string;\n"
+ " switch: string;\n"
+ " case: string;\n"
+ " default: string;\n"
+ "}\n");
+ verifyFormat("const Axis = {\n"
+ " for: 'for',\n"
+ " x: 'x'\n"
+ "};",
+ "const Axis = {for: 'for', x: 'x'};");
}
TEST_F(FormatTestJS, ReservedWordsMethods) {
@@ -166,6 +345,16 @@ TEST_F(FormatTestJS, ReservedWordsMethods) {
"}\n");
}
+TEST_F(FormatTestJS, ReservedWordsParenthesized) {
+ // All of these are statements using the keyword, not function calls.
+ verifyFormat("throw (x + y);\n"
+ "await (await x).y;\n"
+ "typeof (x) === 'string';\n"
+ "void (0);\n"
+ "delete (x.y);\n"
+ "return (x);\n");
+}
+
TEST_F(FormatTestJS, CppKeywords) {
// Make sure we don't mess stuff up because of C++ keywords.
verifyFormat("return operator && (aa);");
@@ -395,8 +584,6 @@ TEST_F(FormatTestJS, GoogModules) {
getGoogleJSStyleWithColumns(40));
verifyFormat("var long = goog.require('this.is.really.absurdly.long');",
getGoogleJSStyleWithColumns(40));
- verifyFormat("goog.setTestOnly('this.is.really.absurdly.long');",
- getGoogleJSStyleWithColumns(40));
verifyFormat("goog.forwardDeclare('this.is.really.absurdly.long');",
getGoogleJSStyleWithColumns(40));
@@ -404,6 +591,12 @@ TEST_F(FormatTestJS, GoogModules) {
verifyFormat(
"var MyLongClassName =\n"
" goog.module.get('my.long.module.name.followedBy.MyLongClassName');");
+ verifyFormat("function a() {\n"
+ " goog.setTestOnly();\n"
+ "}\n",
+ "function a() {\n"
+ "goog.setTestOnly();\n"
+ "}\n");
}
TEST_F(FormatTestJS, FormatsNamespaces) {
@@ -739,6 +932,15 @@ TEST_F(FormatTestJS, FunctionLiterals) {
}
+TEST_F(FormatTestJS, DontWrapEmptyLiterals) {
+ verifyFormat("(aaaaaaaaaaaaaaaaaaaaa.getData as jasmine.Spy)\n"
+ " .and.returnValue(Observable.of([]));");
+ verifyFormat("(aaaaaaaaaaaaaaaaaaaaa.getData as jasmine.Spy)\n"
+ " .and.returnValue(Observable.of({}));");
+ verifyFormat("(aaaaaaaaaaaaaaaaaaaaa.getData as jasmine.Spy)\n"
+ " .and.returnValue(Observable.of(()));");
+}
+
TEST_F(FormatTestJS, InliningFunctionLiterals) {
FormatStyle Style = getGoogleStyle(FormatStyle::LK_JavaScript);
Style.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_Inline;
@@ -895,6 +1097,9 @@ TEST_F(FormatTestJS, ArrowFunctions) {
" .doSomethingElse(\n"
" // break\n"
" );");
+ verifyFormat("const f = (x: string|null): string|null => {\n"
+ " return x;\n"
+ "}\n");
}
TEST_F(FormatTestJS, ReturnStatements) {
@@ -910,6 +1115,10 @@ TEST_F(FormatTestJS, ForLoops) {
"}");
verifyFormat("for (let {a, b} of x) {\n"
"}");
+ verifyFormat("for (let {a, b} of [x]) {\n"
+ "}");
+ verifyFormat("for (let [a, b] of [x]) {\n"
+ "}");
verifyFormat("for (let {a, b} in x) {\n"
"}");
}
@@ -919,6 +1128,7 @@ TEST_F(FormatTestJS, WrapRespectsAutomaticSemicolonInsertion) {
// would change due to automatic semicolon insertion.
// See http://www.ecma-international.org/ecma-262/5.1/#sec-7.9.1.
verifyFormat("return aaaaa;", getGoogleJSStyleWithColumns(10));
+ verifyFormat("yield aaaaa;", getGoogleJSStyleWithColumns(10));
verifyFormat("return /* hello! */ aaaaa;", getGoogleJSStyleWithColumns(10));
verifyFormat("continue aaaaa;", getGoogleJSStyleWithColumns(10));
verifyFormat("continue /* hello! */ aaaaa;", getGoogleJSStyleWithColumns(10));
@@ -938,6 +1148,15 @@ TEST_F(FormatTestJS, WrapRespectsAutomaticSemicolonInsertion) {
" readonly ratherLongField = 1;\n"
"}",
getGoogleJSStyleWithColumns(20));
+ verifyFormat("const x = (5 + 9)\n"
+ "const y = 3\n",
+ "const x = ( 5 + 9)\n"
+ "const y = 3\n");
+ // Ideally the foo() bit should be indented relative to the async function().
+ verifyFormat("async function\n"
+ "foo() {}",
+ getGoogleJSStyleWithColumns(10));
+ verifyFormat("await theReckoning;", getGoogleJSStyleWithColumns(10));
}
TEST_F(FormatTestJS, AutomaticSemicolonInsertionHeuristic) {
@@ -988,6 +1207,8 @@ TEST_F(FormatTestJS, AutomaticSemicolonInsertionHeuristic) {
"String");
verifyFormat("function f(@Foo bar) {}", "function f(@Foo\n"
" bar) {}");
+ verifyFormat("function f(@Foo(Param) bar) {}", "function f(@Foo(Param)\n"
+ " bar) {}");
verifyFormat("a = true\n"
"return 1",
"a = true\n"
@@ -1056,7 +1277,6 @@ TEST_F(FormatTestJS, TryCatch) {
// But, of course, "catch" is a perfectly fine function name in JavaScript.
verifyFormat("someObject.catch();");
verifyFormat("someObject.new();");
- verifyFormat("someObject.delete();");
}
TEST_F(FormatTestJS, StringLiteralConcatenation) {
@@ -1199,6 +1419,7 @@ TEST_F(FormatTestJS, TypeAnnotations) {
verifyFormat("function x(y: {a?: number;} = {}): number {\n"
" return 12;\n"
"}");
+ verifyFormat("const x: Array<{a: number; b: string;}> = [];");
verifyFormat("((a: string, b: number): string => a + b);");
verifyFormat("var x: (y: number) => string;");
verifyFormat("var x: P<string, (a: number) => string>;");
@@ -1218,6 +1439,8 @@ TEST_F(FormatTestJS, TypeAnnotations) {
verifyFormat(
"var someValue = (v as aaaaaaaaaaaaaaaaaaaa<T>[])\n"
" .someFunction(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa);");
+ verifyFormat("const xIsALongIdent:\n"" YJustBarelyFitsLinex[];",
+ getGoogleJSStyleWithColumns(20));
}
TEST_F(FormatTestJS, UnionIntersectionTypes) {
@@ -1242,6 +1465,18 @@ TEST_F(FormatTestJS, UnionIntersectionTypes) {
"};");
}
+TEST_F(FormatTestJS, UnionIntersectionTypesInObjectType) {
+ verifyFormat("let x: {x: number|null} = {x: number | null};");
+ verifyFormat("let nested: {x: {y: number|null}};");
+ verifyFormat("let mixed: {x: [number|null, {w: number}]};");
+ verifyFormat("class X {\n"
+ " contructor(x: {\n"
+ " a: a|null,\n"
+ " b: b|null,\n"
+ " }) {}\n"
+ "}");
+}
+
TEST_F(FormatTestJS, ClassDeclarations) {
verifyFormat("class C {\n x: string = 12;\n}");
verifyFormat("class C {\n x(): string => 12;\n}");
@@ -1307,6 +1542,17 @@ TEST_F(FormatTestJS, InterfaceDeclarations) {
"}");
}
+TEST_F(FormatTestJS, ObjectTypesInExtendsImplements) {
+ verifyFormat("class C extends {} {}");
+ verifyFormat("class C implements {bar: number} {}");
+ // Somewhat odd, but probably closest to reasonable formatting?
+ verifyFormat("class C implements {\n"
+ " bar: number,\n"
+ " baz: string,\n"
+ "} {}");
+ verifyFormat("class C<P extends {}> {}");
+}
+
TEST_F(FormatTestJS, EnumDeclarations) {
verifyFormat("enum Foo {\n"
" A = 1,\n"
@@ -1321,9 +1567,17 @@ TEST_F(FormatTestJS, EnumDeclarations) {
" B\n"
"}\n"
"var x = 1;");
+ verifyFormat("const enum Foo {\n"
+ " A = 1,\n"
+ " B\n"
+ "}");
+ verifyFormat("export const enum Foo {\n"
+ " A = 1,\n"
+ " B\n"
+ "}");
}
-TEST_F(FormatTestJS, MetadataAnnotations) {
+TEST_F(FormatTestJS, Decorators) {
verifyFormat("@A\nclass C {\n}");
verifyFormat("@A({arg: 'value'})\nclass C {\n}");
verifyFormat("@A\n@B\nclass C {\n}");
@@ -1352,6 +1606,10 @@ TEST_F(FormatTestJS, TypeAliases) {
" y: number\n"
"};\n"
"class C {}");
+ verifyFormat("export type X = {\n"
+ " a: string,\n"
+ " b?: string,\n"
+ "};\n");
}
TEST_F(FormatTestJS, TypeInterfaceLineWrapping) {
@@ -1368,6 +1626,17 @@ TEST_F(FormatTestJS, TypeInterfaceLineWrapping) {
Style);
}
+TEST_F(FormatTestJS, RemoveEmptyLinesInArrowFunctions) {
+ verifyFormat("x = () => {\n"
+ " foo();\n"
+ "};\n",
+ "x = () => {\n"
+ "\n"
+ " foo();\n"
+ "\n"
+ "};\n");
+}
+
TEST_F(FormatTestJS, Modules) {
verifyFormat("import SomeThing from 'some/module.js';");
verifyFormat("import {X, Y} from 'some/module.js';");
@@ -1410,9 +1679,15 @@ TEST_F(FormatTestJS, Modules) {
" x: number;\n"
" y: string;\n"
"}");
- verifyFormat("export class X { y: number; }");
- verifyFormat("export abstract class X { y: number; }");
- verifyFormat("export default class X { y: number }");
+ verifyFormat("export class X {\n"
+ " y: number;\n"
+ "}");
+ verifyFormat("export abstract class X {\n"
+ " y: number;\n"
+ "}");
+ verifyFormat("export default class X {\n"
+ " y: number\n"
+ "}");
verifyFormat("export default function() {\n return 1;\n}");
verifyFormat("export var x = 12;");
verifyFormat("class C {}\n"
@@ -1434,7 +1709,9 @@ TEST_F(FormatTestJS, Modules) {
"];");
verifyFormat("export default [];");
verifyFormat("export default () => {};");
- verifyFormat("export interface Foo { foo: number; }\n"
+ verifyFormat("export interface Foo {\n"
+ " foo: number;\n"
+ "}\n"
"export class Bar {\n"
" blah(): string {\n"
" return this.blah;\n"
@@ -1580,32 +1857,28 @@ TEST_F(FormatTestJS, TemplateStrings) {
verifyFormat("var x = someFunction(`${})`) //\n"
" .oooooooooooooooooon();");
verifyFormat("var x = someFunction(`${aaaa}${\n"
- " aaaaa( //\n"
- " aaaaa)\n"
- " })`);");
+ " aaaaa( //\n"
+ " aaaaa)})`);");
}
TEST_F(FormatTestJS, TemplateStringMultiLineExpression) {
verifyFormat("var f = `aaaaaaaaaaaaaaaaaa: ${\n"
- " aaaaa + //\n"
- " bbbb\n"
- " }`;",
+ " aaaaa + //\n"
+ " bbbb}`;",
"var f = `aaaaaaaaaaaaaaaaaa: ${aaaaa + //\n"
" bbbb}`;");
verifyFormat("var f = `\n"
" aaaaaaaaaaaaaaaaaa: ${\n"
- " aaaaa + //\n"
- " bbbb\n"
- " }`;",
+ " aaaaa + //\n"
+ " bbbb}`;",
"var f = `\n"
" aaaaaaaaaaaaaaaaaa: ${ aaaaa + //\n"
" bbbb }`;");
verifyFormat("var f = `\n"
" aaaaaaaaaaaaaaaaaa: ${\n"
- " someFunction(\n"
- " aaaaa + //\n"
- " bbbb)\n"
- " }`;",
+ " someFunction(\n"
+ " aaaaa + //\n"
+ " bbbb)}`;",
"var f = `\n"
" aaaaaaaaaaaaaaaaaa: ${someFunction (\n"
" aaaaa + //\n"
@@ -1614,9 +1887,9 @@ TEST_F(FormatTestJS, TemplateStringMultiLineExpression) {
// It might be preferable to wrap before "someFunction".
verifyFormat("var f = `\n"
" aaaaaaaaaaaaaaaaaa: ${someFunction({\n"
- " aaaa: aaaaa,\n"
- " bbbb: bbbbb,\n"
- " })}`;",
+ " aaaa: aaaaa,\n"
+ " bbbb: bbbbb,\n"
+ "})}`;",
"var f = `\n"
" aaaaaaaaaaaaaaaaaa: ${someFunction ({\n"
" aaaa: aaaaa,\n"
@@ -1659,6 +1932,7 @@ TEST_F(FormatTestJS, CastSyntax) {
verifyFormat("x = x as {a: string};");
verifyFormat("x = x as (string);");
verifyFormat("x = x! as (string);");
+ verifyFormat("x = y! in z;");
verifyFormat("var x = something.someFunction() as\n"
" something;",
getGoogleJSStyleWithColumns(40));
@@ -1934,5 +2208,27 @@ TEST_F(FormatTestJS, NestedLiterals) {
"};", FourSpaces);
}
+TEST_F(FormatTestJS, BackslashesInComments) {
+ verifyFormat("// hello \\\n"
+ "if (x) foo();\n",
+ "// hello \\\n"
+ " if ( x) \n"
+ " foo();\n");
+ verifyFormat("/* ignore \\\n"
+ " */\n"
+ "if (x) foo();\n",
+ "/* ignore \\\n"
+ " */\n"
+ " if ( x) foo();\n");
+ verifyFormat("// st \\ art\\\n"
+ "// comment"
+ "// continue \\\n"
+ "formatMe();\n",
+ "// st \\ art\\\n"
+ "// comment"
+ "// continue \\\n"
+ "formatMe( );\n");
+}
+
} // end namespace tooling
} // end namespace clang
diff --git a/unittests/Format/FormatTestJava.cpp b/unittests/Format/FormatTestJava.cpp
index b9cfaffb01812..2f376f765d66d 100644
--- a/unittests/Format/FormatTestJava.cpp
+++ b/unittests/Format/FormatTestJava.cpp
@@ -333,6 +333,11 @@ TEST_F(FormatTestJava, Generics) {
verifyFormat("Iterable<? extends SomeObject> a;");
verifyFormat("A.<B>doSomething();");
+ verifyFormat("A.<B<C>>doSomething();");
+ verifyFormat("A.<B<C<D>>>doSomething();");
+ verifyFormat("A.<B<C<D<E>>>>doSomething();");
+
+ verifyFormat("OrderedPair<String, List<Box<Integer>>> p = null;");
verifyFormat("@Override\n"
"public Map<String, ?> getAll() {}");
@@ -412,6 +417,7 @@ TEST_F(FormatTestJava, SynchronizedKeyword) {
TEST_F(FormatTestJava, AssertKeyword) {
verifyFormat("assert a && b;");
+ verifyFormat("assert (a && b);");
}
TEST_F(FormatTestJava, PackageDeclarations) {
@@ -525,6 +531,15 @@ TEST_F(FormatTestJava, AlignsBlockComments) {
" void f() {}"));
}
+TEST_F(FormatTestJava, KeepsDelimitersOnOwnLineInJavaDocComments) {
+ EXPECT_EQ("/**\n"
+ " * javadoc line 1\n"
+ " * javadoc line 2\n"
+ " */",
+ format("/** javadoc line 1\n"
+ " * javadoc line 2 */"));
+}
+
TEST_F(FormatTestJava, RetainsLogicalShifts) {
verifyFormat("void f() {\n"
" int a = 1;\n"
diff --git a/unittests/Format/FormatTestObjC.cpp b/unittests/Format/FormatTestObjC.cpp
index 1f9fc451d213b..4220b44b4c47f 100644
--- a/unittests/Format/FormatTestObjC.cpp
+++ b/unittests/Format/FormatTestObjC.cpp
@@ -79,6 +79,17 @@ TEST(FormatTestObjCStyle, DetectsObjCInHeaders) {
ASSERT_TRUE((bool)Style);
EXPECT_EQ(FormatStyle::LK_ObjC, Style->Language);
+ Style = getStyle("LLVM", "a.h", "none", "@interface\n"
+ "@end\n"
+ "//comment");
+ ASSERT_TRUE((bool)Style);
+ EXPECT_EQ(FormatStyle::LK_ObjC, Style->Language);
+
+ Style = getStyle("LLVM", "a.h", "none", "@interface\n"
+ "@end //comment");
+ ASSERT_TRUE((bool)Style);
+ EXPECT_EQ(FormatStyle::LK_ObjC, Style->Language);
+
// No recognizable ObjC.
Style = getStyle("LLVM", "a.h", "none", "void f() {}");
ASSERT_TRUE((bool)Style);
diff --git a/unittests/Format/FormatTestProto.cpp b/unittests/Format/FormatTestProto.cpp
index 639da87c6ea99..df94d8172730b 100644
--- a/unittests/Format/FormatTestProto.cpp
+++ b/unittests/Format/FormatTestProto.cpp
@@ -356,6 +356,24 @@ TEST_F(FormatTestProto, FormatsOptions) {
" }\n"
" field_g: OK\n"
">;");
+
+ verifyFormat("option (MyProto.options) = <\n"
+ " data1 <key1: value1>\n"
+ " data2 {key2: value2}\n"
+ ">;");
+
+ verifyFormat("option (MyProto.options) = <\n"
+ " app_id: 'com.javax.swing.salsa.latino'\n"
+ " head_id: 1\n"
+ " data <key: value>\n"
+ ">;");
+
+ verifyFormat("option (MyProto.options) = {\n"
+ " app_id: 'com.javax.swing.salsa.latino'\n"
+ " head_id: 1\n"
+ " headheadheadheadheadhead_id: 1\n"
+ " product_data {product {1}}\n"
+ "};");
}
TEST_F(FormatTestProto, FormatsService) {
diff --git a/unittests/Format/FormatTestRawStrings.cpp b/unittests/Format/FormatTestRawStrings.cpp
new file mode 100644
index 0000000000000..6e7b7065875d9
--- /dev/null
+++ b/unittests/Format/FormatTestRawStrings.cpp
@@ -0,0 +1,733 @@
+//===- unittest/Format/FormatTestRawStrings.cpp - Formatting unit tests ---===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Format/Format.h"
+
+#include "../Tooling/ReplacementTest.h"
+#include "FormatTestUtils.h"
+
+#include "clang/Frontend/TextDiagnosticPrinter.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "gtest/gtest.h"
+
+#define DEBUG_TYPE "format-test"
+
+using clang::tooling::ReplacementTest;
+using clang::tooling::toReplacements;
+
+namespace clang {
+namespace format {
+namespace {
+
+class FormatTestRawStrings : public ::testing::Test {
+protected:
+ enum StatusCheck { SC_ExpectComplete, SC_ExpectIncomplete, SC_DoNotCheck };
+
+ std::string format(llvm::StringRef Code,
+ const FormatStyle &Style = getLLVMStyle(),
+ StatusCheck CheckComplete = SC_ExpectComplete) {
+ DEBUG(llvm::errs() << "---\n");
+ DEBUG(llvm::errs() << Code << "\n\n");
+ std::vector<tooling::Range> Ranges(1, tooling::Range(0, Code.size()));
+ FormattingAttemptStatus Status;
+ tooling::Replacements Replaces =
+ reformat(Style, Code, Ranges, "<stdin>", &Status);
+ if (CheckComplete != SC_DoNotCheck) {
+ bool ExpectedCompleteFormat = CheckComplete == SC_ExpectComplete;
+ EXPECT_EQ(ExpectedCompleteFormat, Status.FormatComplete)
+ << Code << "\n\n";
+ }
+ ReplacementCount = Replaces.size();
+ auto Result = applyAllReplacements(Code, Replaces);
+ EXPECT_TRUE(static_cast<bool>(Result));
+ DEBUG(llvm::errs() << "\n" << *Result << "\n\n");
+ return *Result;
+ }
+
+ FormatStyle getStyleWithColumns(FormatStyle Style, unsigned ColumnLimit) {
+ Style.ColumnLimit = ColumnLimit;
+ return Style;
+ }
+
+ FormatStyle getLLVMStyleWithColumns(unsigned ColumnLimit) {
+ return getStyleWithColumns(getLLVMStyle(), ColumnLimit);
+ }
+
+ int ReplacementCount;
+
+ FormatStyle getRawStringPbStyleWithColumns(unsigned ColumnLimit) {
+ FormatStyle Style = getLLVMStyle();
+ Style.ColumnLimit = ColumnLimit;
+ Style.RawStringFormats = {{/*Delimiter=*/"pb",
+ /*Kind=*/FormatStyle::LK_TextProto,
+ /*BasedOnStyle=*/"google"}};
+ return Style;
+ }
+
+ FormatStyle getRawStringLLVMCppStyleBasedOn(std::string BasedOnStyle) {
+ FormatStyle Style = getLLVMStyle();
+ Style.RawStringFormats = {{/*Delimiter=*/"cpp",
+ /*Kind=*/FormatStyle::LK_Cpp, BasedOnStyle}};
+ return Style;
+ }
+
+ FormatStyle getRawStringGoogleCppStyleBasedOn(std::string BasedOnStyle) {
+ FormatStyle Style = getGoogleStyle(FormatStyle::LK_Cpp);
+ Style.RawStringFormats = {{/*Delimiter=*/"cpp",
+ /*Kind=*/FormatStyle::LK_Cpp, BasedOnStyle}};
+ return Style;
+ }
+
+ // Gcc 4.8 doesn't support raw string literals in macros, which breaks some
+ // build bots. We use this function instead.
+ void expect_eq(const std::string Expected, const std::string Actual) {
+ EXPECT_EQ(Expected, Actual);
+ }
+};
+
+TEST_F(FormatTestRawStrings, ReformatsAccordingToBaseStyle) {
+ // llvm style puts '*' on the right.
+ // google style puts '*' on the left.
+
+ // Use the llvm style if the raw string style has no BasedOnStyle.
+ expect_eq(R"test(int *i = R"cpp(int *p = nullptr;)cpp")test",
+ format(R"test(int * i = R"cpp(int * p = nullptr;)cpp")test",
+ getRawStringLLVMCppStyleBasedOn("")));
+
+ // Use the google style if the raw string style has BasedOnStyle=google.
+ expect_eq(R"test(int *i = R"cpp(int* p = nullptr;)cpp")test",
+ format(R"test(int * i = R"cpp(int * p = nullptr;)cpp")test",
+ getRawStringLLVMCppStyleBasedOn("google")));
+
+ // Use the llvm style if the raw string style has no BasedOnStyle=llvm.
+ expect_eq(R"test(int* i = R"cpp(int *p = nullptr;)cpp")test",
+ format(R"test(int * i = R"cpp(int * p = nullptr;)cpp")test",
+ getRawStringGoogleCppStyleBasedOn("llvm")));
+}
+
+TEST_F(FormatTestRawStrings, MatchesDelimitersCaseSensitively) {
+ // Don't touch the 'PB' raw string, format the 'pb' raw string.
+ expect_eq(R"test(
+s = R"PB(item:1)PB";
+t = R"pb(item: 1)pb";)test",
+ format(R"test(
+s = R"PB(item:1)PB";
+t = R"pb(item:1)pb";)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ FormatStyle MixedStyle = getLLVMStyle();
+ MixedStyle.RawStringFormats = {
+ {/*Delimiter=*/"cpp", /*Kind=*/FormatStyle::LK_Cpp,
+ /*BasedOnStyle=*/"llvm"},
+ {/*Delimiter=*/"CPP", /*Kind=*/FormatStyle::LK_Cpp,
+ /*BasedOnStyle=*/"google"}};
+
+ // Format the 'cpp' raw string with '*' on the right.
+ // Format the 'CPP' raw string with '*' on the left.
+ // Do not format the 'Cpp' raw string.
+ // Do not format non-raw strings.
+ expect_eq(R"test(
+a = R"cpp(int *i = 0;)cpp";
+b = R"CPP(int* j = 0;)CPP";
+c = R"Cpp(int * k = 0;)Cpp";
+d = R"cpp(int * k = 0;)Cpp";)test",
+ format(R"test(
+a = R"cpp(int * i = 0;)cpp";
+b = R"CPP(int * j = 0;)CPP";
+c = R"Cpp(int * k = 0;)Cpp";
+d = R"cpp(int * k = 0;)Cpp";)test",
+ MixedStyle));
+}
+
+TEST_F(FormatTestRawStrings, ReformatsShortRawStringsOnSingleLine) {
+ expect_eq(
+ R"test(P p = TP(R"pb()pb");)test",
+ format(
+ R"test(P p = TP(R"pb( )pb");)test",
+ getRawStringPbStyleWithColumns(40)));
+ expect_eq(
+ R"test(P p = TP(R"pb(item_1: 1)pb");)test",
+ format(
+ R"test(P p = TP(R"pb(item_1:1)pb");)test",
+ getRawStringPbStyleWithColumns(40)));
+ expect_eq(
+ R"test(P p = TP(R"pb(item_1: 1)pb");)test",
+ format(
+ R"test(P p = TP(R"pb( item_1 : 1 )pb");)test",
+ getRawStringPbStyleWithColumns(40)));
+ expect_eq(
+ R"test(P p = TP(R"pb(item_1: 1 item_2: 2)pb");)test",
+ format(
+ R"test(P p = TP(R"pb(item_1:1 item_2:2)pb");)test",
+ getRawStringPbStyleWithColumns(40)));
+ expect_eq(
+ R"test(P p = TP(R"pb(item_1 <1> item_2: {2})pb");)test",
+ format(
+ R"test(P p = TP(R"pb(item_1<1> item_2:{2})pb");)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ // Merge two short lines into one.
+ expect_eq(R"test(
+std::string s = R"pb(
+ item_1: 1 item_2: 2
+)pb";
+)test",
+ format(R"test(
+std::string s = R"pb(
+ item_1:1
+ item_2:2
+)pb";
+)test",
+ getRawStringPbStyleWithColumns(40)));
+}
+
+TEST_F(FormatTestRawStrings, BreaksRawStringsExceedingColumnLimit) {
+ expect_eq(R"test(
+P p = TPPPPPPPPPPPPPPP(
+ R"pb(item_1: 1, item_2: 2)pb");)test",
+ format(R"test(
+P p = TPPPPPPPPPPPPPPP(R"pb(item_1: 1, item_2: 2)pb");)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+P p =
+ TPPPPPPPPPPPPPPP(
+ R"pb(item_1: 1,
+ item_2: 2,
+ item_3: 3)pb");)test",
+ format(R"test(
+P p = TPPPPPPPPPPPPPPP(R"pb(item_1: 1, item_2: 2, item_3: 3)pb");)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+P p = TP(R"pb(item_1 <1>
+ item_2: <2>
+ item_3 {})pb");)test",
+ format(R"test(
+P p = TP(R"pb(item_1<1> item_2:<2> item_3{ })pb");)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(
+ R"test(
+P p = TP(R"pb(item_1: 1,
+ item_2: 2,
+ item_3: 3,
+ item_4: 4)pb");)test",
+ format(
+ R"test(
+P p = TP(R"pb(item_1: 1, item_2: 2, item_3: 3, item_4: 4)pb");)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+P p = TPPPPPPPPPPPPPPP(
+ R"pb(item_1 <1>,
+ item_2: {2},
+ item_3: <3>,
+ item_4: {4})pb");)test",
+ format(R"test(
+P p = TPPPPPPPPPPPPPPP(R"pb(item_1<1>, item_2: {2}, item_3: <3>, item_4:{4})pb");)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ // Breaks before a short raw string exceeding the column limit.
+ expect_eq(R"test(
+FFFFFFFFFFFFFFFFFFFFFFFFFFF(
+ R"pb(key: 1)pb");
+P p = TPPPPPPPPPPPPPPPPPPPP(
+ R"pb(key: 2)pb");
+auto TPPPPPPPPPPPPPPPPPPPP =
+ R"pb(key: 3)pb";
+P p = TPPPPPPPPPPPPPPPPPPPP(
+ R"pb(i: 1, j: 2)pb");
+
+int f(string s) {
+ FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF(
+ R"pb(key: 1)pb");
+ P p = TPPPPPPPPPPPPPPPPPPPP(
+ R"pb(key: 2)pb");
+ auto TPPPPPPPPPPPPPPPPPPPP =
+ R"pb(key: 3)pb";
+ if (s.empty())
+ P p = TPPPPPPPPPPPPPPPPPPPP(
+ R"pb(i: 1, j: 2)pb");
+}
+)test",
+ format(R"test(
+FFFFFFFFFFFFFFFFFFFFFFFFFFF(R"pb(key:1)pb");
+P p = TPPPPPPPPPPPPPPPPPPPP(R"pb(key:2)pb");
+auto TPPPPPPPPPPPPPPPPPPPP = R"pb(key:3)pb";
+P p = TPPPPPPPPPPPPPPPPPPPP(R"pb(i: 1, j:2)pb");
+
+int f(string s) {
+ FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF(R"pb(key:1)pb");
+ P p = TPPPPPPPPPPPPPPPPPPPP(R"pb(key:2)pb");
+ auto TPPPPPPPPPPPPPPPPPPPP = R"pb(key:3)pb";
+ if (s.empty())
+ P p = TPPPPPPPPPPPPPPPPPPPP(R"pb(i: 1, j:2)pb");
+}
+)test",
+ getRawStringPbStyleWithColumns(40)));
+}
+
+TEST_F(FormatTestRawStrings, FormatsRawStringArguments) {
+ expect_eq(R"test(
+P p = TP(R"pb(key {1})pb", param_2);)test",
+ format(R"test(
+P p = TP(R"pb(key{1})pb",param_2);)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+PPPPPPPPPPPPP(R"pb(keykeyk)pb",
+ param_2);)test",
+ format(R"test(
+PPPPPPPPPPPPP(R"pb(keykeyk)pb", param_2);)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+P p =
+ TP(R"pb(item: {i: 1, s: 's'}
+ item: {i: 2, s: 't'})pb");)test",
+ format(R"test(
+P p = TP(R"pb(item: {i: 1, s: 's'} item: {i: 2, s: 't'})pb");)test",
+ getRawStringPbStyleWithColumns(40)));
+ expect_eq(R"test(
+FFFFFFFFFFFFFFFFFFF(
+ R"pb(key: "value")pb",
+ R"pb(key2: "value")pb");)test",
+ format(R"test(
+FFFFFFFFFFFFFFFFFFF(R"pb(key: "value")pb", R"pb(key2: "value")pb");)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ // Formats the first out of two arguments.
+ expect_eq(R"test(
+FFFFFFFF(R"pb(key: 1)pb", argument2);
+struct S {
+ const s =
+ f(R"pb(key: 1)pb", argument2);
+ void f() {
+ if (gol)
+ return g(R"pb(key: 1)pb",
+ 132789237);
+ return g(R"pb(key: 1)pb", "172893");
+ }
+};)test",
+ format(R"test(
+FFFFFFFF(R"pb(key:1)pb", argument2);
+struct S {
+const s = f(R"pb(key:1)pb", argument2);
+void f() {
+ if (gol)
+ return g(R"pb(key:1)pb", 132789237);
+ return g(R"pb(key:1)pb", "172893");
+}
+};)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ // Formats the second out of two arguments.
+ expect_eq(R"test(
+FFFFFFFF(argument1, R"pb(key: 2)pb");
+struct S {
+ const s =
+ f(argument1, R"pb(key: 2)pb");
+ void f() {
+ if (gol)
+ return g(12784137,
+ R"pb(key: 2)pb");
+ return g(17283122, R"pb(key: 2)pb");
+ }
+};)test",
+ format(R"test(
+FFFFFFFF(argument1, R"pb(key:2)pb");
+struct S {
+const s = f(argument1, R"pb(key:2)pb");
+void f() {
+ if (gol)
+ return g(12784137, R"pb(key:2)pb");
+ return g(17283122, R"pb(key:2)pb");
+}
+};)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ // Formats two short raw string arguments.
+ expect_eq(R"test(
+FFFFF(R"pb(key: 1)pb", R"pb(key: 2)pb");)test",
+ format(R"test(
+FFFFF(R"pb(key:1)pb", R"pb(key:2)pb");)test",
+ getRawStringPbStyleWithColumns(40)));
+ // TODO(krasimir): The original source code fits on one line, so the
+ // non-optimizing formatter is chosen. But after the formatting in protos is
+ // made, the code doesn't fit on one line anymore and further formatting
+ // splits it.
+ //
+ // Should we disable raw string formatting for the non-optimizing formatter?
+ expect_eq(R"test(
+FFFFFFF(R"pb(key: 1)pb", R"pb(key: 2)pb");)test",
+ format(R"test(
+FFFFFFF(R"pb(key:1)pb", R"pb(key:2)pb");)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ // Formats two short raw string arguments, puts second on newline.
+ expect_eq(R"test(
+FFFFFFFF(R"pb(key: 1)pb",
+ R"pb(key: 2)pb");)test",
+ format(R"test(
+FFFFFFFF(R"pb(key:1)pb", R"pb(key:2)pb");)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ // Formats both arguments.
+ expect_eq(R"test(
+FFFFFFFF(R"pb(key: 1)pb",
+ R"pb(key: 2)pb");
+struct S {
+ const s = f(R"pb(key: 1)pb",
+ R"pb(key: 2)pb");
+ void f() {
+ if (gol)
+ return g(R"pb(key: 1)pb",
+ R"pb(key: 2)pb");
+ return g(R"pb(k1)pb", R"pb(k2)pb");
+ }
+};)test",
+ format(R"test(
+FFFFFFFF(R"pb(key:1)pb", R"pb(key:2)pb");
+struct S {
+const s = f(R"pb(key:1)pb", R"pb(key:2)pb");
+void f() {
+ if (gol)
+ return g(R"pb(key:1)pb", R"pb(key:2)pb");
+ return g(R"pb( k1 )pb", R"pb( k2 )pb");
+}
+};)test",
+ getRawStringPbStyleWithColumns(40)));
+}
+
+TEST_F(FormatTestRawStrings, RawStringStartingWithNewlines) {
+ expect_eq(R"test(
+std::string s = R"pb(
+ item_1: 1
+)pb";
+)test",
+ format(R"test(
+std::string s = R"pb(
+ item_1:1
+)pb";
+)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+std::string s = R"pb(
+
+ item_1: 1
+)pb";
+)test",
+ format(R"test(
+std::string s = R"pb(
+
+ item_1:1
+)pb";
+)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+std::string s = R"pb(
+ item_1: 1
+)pb";
+)test",
+ format(R"test(
+std::string s = R"pb(
+ item_1:1
+
+)pb";
+)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+std::string s = R"pb(
+ item_1: 1,
+ item_2: 2
+)pb";
+)test",
+ format(R"test(
+std::string s = R"pb(
+ item_1:1, item_2:2
+)pb";
+)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+std::string s = R"pb(
+ book {
+ title: "Alice's Adventures"
+ author: "Lewis Caroll"
+ }
+ book {
+ title: "Peter Pan"
+ author: "J. M. Barrie"
+ }
+)pb";
+)test",
+ format(R"test(
+std::string s = R"pb(
+ book { title: "Alice's Adventures" author: "Lewis Caroll" }
+ book { title: "Peter Pan" author: "J. M. Barrie" }
+)pb";
+)test",
+ getRawStringPbStyleWithColumns(40)));
+}
+
+TEST_F(FormatTestRawStrings, BreaksBeforeRawStrings) {
+ expect_eq(R"test(
+ASSERT_TRUE(
+ ParseFromString(R"pb(item_1: 1)pb"),
+ ptr);)test",
+ format(R"test(
+ASSERT_TRUE(ParseFromString(R"pb(item_1: 1)pb"), ptr);)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+ASSERT_TRUE(toolong::ParseFromString(
+ R"pb(item_1: 1)pb"),
+ ptr);)test",
+ format(R"test(
+ASSERT_TRUE(toolong::ParseFromString(R"pb(item_1: 1)pb"), ptr);)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+ASSERT_TRUE(ParseFromString(
+ R"pb(item_1: 1,
+ item_2: 2)pb"),
+ ptr);)test",
+ format(R"test(
+ASSERT_TRUE(ParseFromString(R"pb(item_1: 1, item_2: 2)pb"), ptr);)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+ASSERT_TRUE(
+ ParseFromString(
+ R"pb(item_1: 1 item_2: 2)pb"),
+ ptr);)test",
+ format(R"test(
+ASSERT_TRUE(ParseFromString(R"pb(item_1: 1 item_2: 2)pb"), ptr);)test",
+ getRawStringPbStyleWithColumns(40)));
+
+}
+
+TEST_F(FormatTestRawStrings, RawStringsInOperands) {
+ // Formats the raw string first operand of a binary operator expression.
+ expect_eq(R"test(auto S = R"pb(item_1: 1)pb" + rest;)test",
+ format(R"test(auto S = R"pb(item_1:1)pb" + rest;)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+auto S = R"pb(item_1: 1, item_2: 2)pb" +
+ rest;)test",
+ format(R"test(
+auto S = R"pb(item_1:1,item_2:2)pb"+rest;)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+auto S =
+ R"pb(item_1: 1 item_2: 2)pb" + rest;)test",
+ format(R"test(
+auto S = R"pb(item_1:1 item_2:2)pb"+rest;)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+auto S = R"pb(item_1: 1,
+ item_2: 2,
+ item_3: 3)pb" + rest;)test",
+ format(R"test(
+auto S = R"pb(item_1:1,item_2:2,item_3:3)pb"+rest;)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+auto S = R"pb(item_1: 1,
+ item_2: 2,
+ item_3: 3)pb" +
+ longlongrest;)test",
+ format(R"test(
+auto S = R"pb(item_1:1,item_2:2,item_3:3)pb"+longlongrest;)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ // Formats the raw string second operand of a binary operator expression.
+ expect_eq(R"test(auto S = first + R"pb(item_1: 1)pb";)test",
+ format(R"test(auto S = first + R"pb(item_1:1)pb";)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+auto S = first + R"pb(item_1: 1,
+ item_2: 2)pb";)test",
+ format(R"test(
+auto S = first+R"pb(item_1:1,item_2:2)pb";)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+auto S = first + R"pb(item_1: 1
+ item_2: 2)pb";)test",
+ format(R"test(
+auto S = first+R"pb(item_1:1 item_2:2)pb";)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+auto S = R"pb(item_1: 1,
+ item_2: 2,
+ item_3: 3)pb" + rest;)test",
+ format(R"test(
+auto S = R"pb(item_1:1,item_2:2,item_3:3)pb"+rest;)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+auto S = R"pb(item_1: 1,
+ item_2: 2,
+ item_3: 3)pb" +
+ longlongrest;)test",
+ format(R"test(
+auto S = R"pb(item_1:1,item_2:2,item_3:3)pb"+longlongrest;)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ // Formats the raw string operands in expressions.
+ expect_eq(R"test(
+auto S = R"pb(item_1: 1)pb" +
+ R"pb(item_2: 2)pb";
+)test",
+ format(R"test(
+auto S=R"pb(item_1:1)pb"+R"pb(item_2:2)pb";
+)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+auto S = R"pb(item_1: 1)pb" +
+ R"pb(item_2: 2)pb" +
+ R"pb(item_3: 3)pb";
+)test",
+ format(R"test(
+auto S=R"pb(item_1:1)pb"+R"pb(item_2:2)pb"+R"pb(item_3:3)pb";
+)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+auto S = (count < 3)
+ ? R"pb(item_1: 1)pb"
+ : R"pb(item_2: 2)pb";
+)test",
+ format(R"test(
+auto S=(count<3)?R"pb(item_1:1)pb":R"pb(item_2:2)pb";
+)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+auto S =
+ (count < 3)
+ ? R"pb(item_1: 1, item_2: 2)pb"
+ : R"pb(item_3: 3)pb";
+)test",
+ format(R"test(
+auto S=(count<3)?R"pb(item_1:1,item_2:2)pb":R"pb(item_3:3)pb";
+)test",
+ getRawStringPbStyleWithColumns(40)));
+
+ expect_eq(R"test(
+auto S =
+ (count < 3)
+ ? R"pb(item_1: 1)pb"
+ : R"pb(item_2: 2, item_3: 3)pb";
+)test",
+ format(R"test(
+auto S=(count<3)?R"pb(item_1:1)pb":R"pb(item_2:2,item_3:3)pb";
+)test",
+ getRawStringPbStyleWithColumns(40)));
+
+}
+
+TEST_F(FormatTestRawStrings, PrefixAndSuffixAlignment) {
+ // Keep the suffix at the end of line if not on newline.
+ expect_eq(R"test(
+int s() {
+ auto S = PTP(
+ R"pb(
+ item_1: 1,
+ item_2: 2)pb");
+})test",
+ format(R"test(
+int s() {
+ auto S = PTP(
+ R"pb(
+ item_1: 1,
+ item_2: 2)pb");
+})test",
+ getRawStringPbStyleWithColumns(20)));
+
+ // Align the suffix with the surrounding FirstIndent if the prefix is not on
+ // a line of its own.
+ expect_eq(R"test(
+int s() {
+ auto S = PTP(
+ R"pb(
+ item_1: 1,
+ item_2: 2
+ )pb");
+})test",
+ format(R"test(
+int s() {
+ auto S = PTP(R"pb(
+ item_1: 1,
+ item_2: 2
+ )pb");
+})test",
+ getRawStringPbStyleWithColumns(20)));
+
+ // Align the prefix with the suffix if both the prefix and suffix are on a
+ // line of their own.
+ expect_eq(R"test(
+int s() {
+ auto S = PTP(
+ R"pb(
+ item_1: 1,
+ item_2: 2,
+ )pb");
+})test",
+ format(R"test(
+int s() {
+ auto S = PTP(
+ R"pb(
+ item_1: 1,
+ item_2: 2,
+ )pb");
+})test",
+ getRawStringPbStyleWithColumns(20)));
+}
+
+TEST_F(FormatTestRawStrings, EstimatesPenalty) {
+ // The penalty for characters exceeding the column limit in the raw string
+ // forces 'hh' to be put on a newline.
+ expect_eq(R"test(
+ff(gggggg,
+ hh(R"pb(key {
+ i1: k1
+ i2: k2
+ })pb"));
+)test",
+ format(R"test(
+ff(gggggg, hh(R"pb(key {
+ i1: k1
+ i2: k2
+ })pb"));
+)test",
+ getRawStringPbStyleWithColumns(20)));
+}
+
+TEST_F(FormatTestRawStrings, DontFormatNonRawStrings) {
+ expect_eq(R"test(a = R"pb(key:value)";)test",
+ format(R"test(a = R"pb(key:value)";)test",
+ getRawStringPbStyleWithColumns(20)));
+}
+
+} // end namespace
+} // end namespace format
+} // end namespace clang
diff --git a/unittests/Format/FormatTestTextProto.cpp b/unittests/Format/FormatTestTextProto.cpp
index 2de7e181f2cb8..0a7bcdd823623 100644
--- a/unittests/Format/FormatTestTextProto.cpp
+++ b/unittests/Format/FormatTestTextProto.cpp
@@ -245,6 +245,50 @@ TEST_F(FormatTestTextProto, SupportsAngleBracketMessageFields) {
">\n"
"field: OK,\n"
"field_c <field <field <>>>");
+
+ verifyFormat("app_id: 'com.javax.swing.salsa.latino'\n"
+ "head_id: 1\n"
+ "data <key: value>");
+
+ verifyFormat("app_id: 'com.javax.swing.salsa.latino'\n"
+ "head_id: 1\n"
+ "data <key: value>\n"
+ "tail_id: 2");
+
+ verifyFormat("app_id: 'com.javax.swing.salsa.latino'\n"
+ "head_id: 1\n"
+ "data <key: value>\n"
+ "data {key: value}");
+
+ verifyFormat("app {\n"
+ " app_id: 'com.javax.swing.salsa.latino'\n"
+ " head_id: 1\n"
+ " data <key: value>\n"
+ "}");
+
+ verifyFormat("app: {\n"
+ " app_id: 'com.javax.swing.salsa.latino'\n"
+ " head_id: 1\n"
+ " data <key: value>\n"
+ "}");
+
+ verifyFormat("app_id: 'com.javax.swing.salsa.latino'\n"
+ "headheadheadheadheadhead_id: 1\n"
+ "product_data {product {1}}");
+
+ verifyFormat("app_id: 'com.javax.swing.salsa.latino'\n"
+ "headheadheadheadheadhead_id: 1\n"
+ "product_data <product {1}>");
+
+ verifyFormat("app_id: 'com.javax.swing.salsa.latino'\n"
+ "headheadheadheadheadhead_id: 1\n"
+ "product_data <product <1>>");
+
+ verifyFormat("app <\n"
+ " app_id: 'com.javax.swing.salsa.latino'\n"
+ " headheadheadheadheadhead_id: 1\n"
+ " product_data <product {1}>\n"
+ ">");
}
} // end namespace tooling
} // end namespace clang
diff --git a/unittests/Format/FormatTestUtils.h b/unittests/Format/FormatTestUtils.h
index bd340e5b0e60e..d82d84ebedeff 100644
--- a/unittests/Format/FormatTestUtils.h
+++ b/unittests/Format/FormatTestUtils.h
@@ -30,7 +30,8 @@ inline std::string messUp(llvm::StringRef Code) {
if (JustReplacedNewline)
MessedUp[i - 1] = '\n';
InComment = true;
- } else if (MessedUp[i] == '#' && (JustReplacedNewline || i == 0)) {
+ } else if (MessedUp[i] == '#' &&
+ (JustReplacedNewline || i == 0 || MessedUp[i - 1] == '\n')) {
if (i != 0)
MessedUp[i - 1] = '\n';
InPreprocessorDirective = true;
diff --git a/unittests/Format/NamespaceEndCommentsFixerTest.cpp b/unittests/Format/NamespaceEndCommentsFixerTest.cpp
index 92f3421629387..fda8b4d69fe09 100644
--- a/unittests/Format/NamespaceEndCommentsFixerTest.cpp
+++ b/unittests/Format/NamespaceEndCommentsFixerTest.cpp
@@ -509,6 +509,134 @@ TEST_F(NamespaceEndCommentsFixerTest,
"}\n"));
}
+TEST_F(NamespaceEndCommentsFixerTest, AddEndCommentForNamespacesAroundMacros) {
+ // Conditional blocks around are fine
+ EXPECT_EQ("namespace A {\n"
+ "#if 1\n"
+ "int i;\n"
+ "#endif\n"
+ "}// namespace A",
+ fixNamespaceEndComments("namespace A {\n"
+ "#if 1\n"
+ "int i;\n"
+ "#endif\n"
+ "}"));
+ EXPECT_EQ("#if 1\n"
+ "#endif\n"
+ "namespace A {\n"
+ "int i;\n"
+ "int j;\n"
+ "}// namespace A",
+ fixNamespaceEndComments("#if 1\n"
+ "#endif\n"
+ "namespace A {\n"
+ "int i;\n"
+ "int j;\n"
+ "}"));
+ EXPECT_EQ("namespace A {\n"
+ "int i;\n"
+ "int j;\n"
+ "}// namespace A\n"
+ "#if 1\n"
+ "#endif",
+ fixNamespaceEndComments("namespace A {\n"
+ "int i;\n"
+ "int j;\n"
+ "}\n"
+ "#if 1\n"
+ "#endif"));
+ EXPECT_EQ("#if 1\n"
+ "namespace A {\n"
+ "int i;\n"
+ "int j;\n"
+ "}// namespace A\n"
+ "#endif",
+ fixNamespaceEndComments("#if 1\n"
+ "namespace A {\n"
+ "int i;\n"
+ "int j;\n"
+ "}\n"
+ "#endif"));
+
+ // Macro definition has no impact
+ EXPECT_EQ("namespace A {\n"
+ "#define FOO\n"
+ "int i;\n"
+ "}// namespace A",
+ fixNamespaceEndComments("namespace A {\n"
+ "#define FOO\n"
+ "int i;\n"
+ "}"));
+ EXPECT_EQ("#define FOO\n"
+ "namespace A {\n"
+ "int i;\n"
+ "int j;\n"
+ "}// namespace A",
+ fixNamespaceEndComments("#define FOO\n"
+ "namespace A {\n"
+ "int i;\n"
+ "int j;\n"
+ "}"));
+ EXPECT_EQ("namespace A {\n"
+ "int i;\n"
+ "int j;\n"
+ "}// namespace A\n"
+ "#define FOO\n",
+ fixNamespaceEndComments("namespace A {\n"
+ "int i;\n"
+ "int j;\n"
+ "}\n"
+ "#define FOO\n"));
+
+ // No replacement if open & close in different conditional blocks
+ EXPECT_EQ("#if 1\n"
+ "namespace A {\n"
+ "#endif\n"
+ "int i;\n"
+ "int j;\n"
+ "#if 1\n"
+ "}\n"
+ "#endif",
+ fixNamespaceEndComments("#if 1\n"
+ "namespace A {\n"
+ "#endif\n"
+ "int i;\n"
+ "int j;\n"
+ "#if 1\n"
+ "}\n"
+ "#endif"));
+ EXPECT_EQ("#ifdef A\n"
+ "namespace A {\n"
+ "#endif\n"
+ "int i;\n"
+ "int j;\n"
+ "#ifdef B\n"
+ "}\n"
+ "#endif",
+ fixNamespaceEndComments("#ifdef A\n"
+ "namespace A {\n"
+ "#endif\n"
+ "int i;\n"
+ "int j;\n"
+ "#ifdef B\n"
+ "}\n"
+ "#endif"));
+
+ // No replacement inside unreachable conditional block
+ EXPECT_EQ("#if 0\n"
+ "namespace A {\n"
+ "int i;\n"
+ "int j;\n"
+ "}\n"
+ "#endif",
+ fixNamespaceEndComments("#if 0\n"
+ "namespace A {\n"
+ "int i;\n"
+ "int j;\n"
+ "}\n"
+ "#endif"));
+}
+
TEST_F(NamespaceEndCommentsFixerTest,
DoesNotAddEndCommentForNamespacesInMacroDeclarations) {
EXPECT_EQ("#ifdef 1\n"
diff --git a/unittests/Format/SortImportsTestJS.cpp b/unittests/Format/SortImportsTestJS.cpp
index 4208b29702dd6..91be0313cf80f 100644
--- a/unittests/Format/SortImportsTestJS.cpp
+++ b/unittests/Format/SortImportsTestJS.cpp
@@ -300,6 +300,14 @@ TEST_F(SortImportsTestJS, SortMultiLine) {
"1;");
}
+TEST_F(SortImportsTestJS, SortDefaultImports) {
+ // Reproduces issue where multi-line import was not parsed correctly.
+ verifySort("import {A} from 'a';\n"
+ "import {default as B} from 'b';\n",
+ "import {default as B} from 'b';\n"
+ "import {A} from 'a';\n");
+}
+
} // end namespace
} // end namespace format
} // end namespace clang
diff --git a/unittests/Format/SortIncludesTest.cpp b/unittests/Format/SortIncludesTest.cpp
index 1128ed829ff2d..09fc0703d42eb 100644
--- a/unittests/Format/SortIncludesTest.cpp
+++ b/unittests/Format/SortIncludesTest.cpp
@@ -77,6 +77,28 @@ TEST_F(SortIncludesTest, NoReplacementsForValidIncludes) {
EXPECT_TRUE(sortIncludes(Style, Code, GetCodeRange(Code), "a.cc").empty());
}
+TEST_F(SortIncludesTest, SortedIncludesInMultipleBlocksAreMerged) {
+ Style.IncludeBlocks = FormatStyle::IBS_Merge;
+ EXPECT_EQ("#include \"a.h\"\n"
+ "#include \"b.h\"\n"
+ "#include \"c.h\"\n",
+ sort("#include \"a.h\"\n"
+ "#include \"c.h\"\n"
+ "\n"
+ "\n"
+ "#include \"b.h\"\n"));
+
+ Style.IncludeBlocks = FormatStyle::IBS_Regroup;
+ EXPECT_EQ("#include \"a.h\"\n"
+ "#include \"b.h\"\n"
+ "#include \"c.h\"\n",
+ sort("#include \"a.h\"\n"
+ "#include \"c.h\"\n"
+ "\n"
+ "\n"
+ "#include \"b.h\"\n"));
+}
+
TEST_F(SortIncludesTest, SupportClangFormatOff) {
EXPECT_EQ("#include <a>\n"
"#include <b>\n"
@@ -159,6 +181,48 @@ TEST_F(SortIncludesTest, SortsLocallyInEachBlock) {
"#include \"b.h\"\n"));
}
+TEST_F(SortIncludesTest, SortsAllBlocksWhenMerging) {
+ Style.IncludeBlocks = FormatStyle::IBS_Merge;
+ EXPECT_EQ("#include \"a.h\"\n"
+ "#include \"b.h\"\n"
+ "#include \"c.h\"\n",
+ sort("#include \"a.h\"\n"
+ "#include \"c.h\"\n"
+ "\n"
+ "#include \"b.h\"\n"));
+}
+
+TEST_F(SortIncludesTest, CommentsAlwaysSeparateGroups) {
+ EXPECT_EQ("#include \"a.h\"\n"
+ "#include \"c.h\"\n"
+ "// comment\n"
+ "#include \"b.h\"\n",
+ sort("#include \"c.h\"\n"
+ "#include \"a.h\"\n"
+ "// comment\n"
+ "#include \"b.h\"\n"));
+
+ Style.IncludeBlocks = FormatStyle::IBS_Merge;
+ EXPECT_EQ("#include \"a.h\"\n"
+ "#include \"c.h\"\n"
+ "// comment\n"
+ "#include \"b.h\"\n",
+ sort("#include \"c.h\"\n"
+ "#include \"a.h\"\n"
+ "// comment\n"
+ "#include \"b.h\"\n"));
+
+ Style.IncludeBlocks = FormatStyle::IBS_Regroup;
+ EXPECT_EQ("#include \"a.h\"\n"
+ "#include \"c.h\"\n"
+ "// comment\n"
+ "#include \"b.h\"\n",
+ sort("#include \"c.h\"\n"
+ "#include \"a.h\"\n"
+ "// comment\n"
+ "#include \"b.h\"\n"));
+}
+
TEST_F(SortIncludesTest, HandlesAngledIncludesAsSeparateBlocks) {
EXPECT_EQ("#include \"a.h\"\n"
"#include \"c.h\"\n"
@@ -180,6 +244,19 @@ TEST_F(SortIncludesTest, HandlesAngledIncludesAsSeparateBlocks) {
"#include \"a.h\"\n"));
}
+TEST_F(SortIncludesTest, RegroupsAngledIncludesInSeparateBlocks) {
+ Style.IncludeBlocks = FormatStyle::IBS_Regroup;
+ EXPECT_EQ("#include \"a.h\"\n"
+ "#include \"c.h\"\n"
+ "\n"
+ "#include <b.h>\n"
+ "#include <d.h>\n",
+ sort("#include <d.h>\n"
+ "#include <b.h>\n"
+ "#include \"c.h\"\n"
+ "#include \"a.h\"\n"));
+}
+
TEST_F(SortIncludesTest, HandlesMultilineIncludes) {
EXPECT_EQ("#include \"a.h\"\n"
"#include \"b.h\"\n"
@@ -266,6 +343,35 @@ TEST_F(SortIncludesTest, LeavesMainHeaderFirst) {
"a.cc"));
}
+TEST_F(SortIncludesTest, RecognizeMainHeaderInAllGroups) {
+ Style.IncludeIsMainRegex = "([-_](test|unittest))?$";
+ Style.IncludeBlocks = FormatStyle::IBS_Merge;
+
+ EXPECT_EQ("#include \"c.h\"\n"
+ "#include \"a.h\"\n"
+ "#include \"b.h\"\n",
+ sort("#include \"b.h\"\n"
+ "\n"
+ "#include \"a.h\"\n"
+ "#include \"c.h\"\n",
+ "c.cc"));
+}
+
+TEST_F(SortIncludesTest, MainHeaderIsSeparatedWhenRegroupping) {
+ Style.IncludeIsMainRegex = "([-_](test|unittest))?$";
+ Style.IncludeBlocks = FormatStyle::IBS_Regroup;
+
+ EXPECT_EQ("#include \"a.h\"\n"
+ "\n"
+ "#include \"b.h\"\n"
+ "#include \"c.h\"\n",
+ sort("#include \"b.h\"\n"
+ "\n"
+ "#include \"a.h\"\n"
+ "#include \"c.h\"\n",
+ "a.cc"));
+}
+
TEST_F(SortIncludesTest, SupportCaseInsensitiveMatching) {
// Setup an regex for main includes so we can cover those as well.
Style.IncludeIsMainRegex = "([-_](test|unittest))?$";
@@ -309,6 +415,34 @@ TEST_F(SortIncludesTest, NegativePriorities) {
"c_main.cc"));
}
+TEST_F(SortIncludesTest, PriorityGroupsAreSeparatedWhenRegroupping) {
+ Style.IncludeCategories = {{".*important_os_header.*", -1}, {".*", 1}};
+ Style.IncludeBlocks = FormatStyle::IBS_Regroup;
+
+ EXPECT_EQ("#include \"important_os_header.h\"\n"
+ "\n"
+ "#include \"c_main.h\"\n"
+ "\n"
+ "#include \"a_other.h\"\n",
+ sort("#include \"c_main.h\"\n"
+ "#include \"a_other.h\"\n"
+ "#include \"important_os_header.h\"\n",
+ "c_main.cc"));
+
+ // check stable when re-run
+ EXPECT_EQ("#include \"important_os_header.h\"\n"
+ "\n"
+ "#include \"c_main.h\"\n"
+ "\n"
+ "#include \"a_other.h\"\n",
+ sort("#include \"important_os_header.h\"\n"
+ "\n"
+ "#include \"c_main.h\"\n"
+ "\n"
+ "#include \"a_other.h\"\n",
+ "c_main.cc"));
+}
+
TEST_F(SortIncludesTest, CalculatesCorrectCursorPosition) {
std::string Code = "#include <ccc>\n" // Start of line: 0
"#include <bbbbbb>\n" // Start of line: 15
@@ -332,6 +466,30 @@ TEST_F(SortIncludesTest, DeduplicateIncludes) {
"#include <b>\n"
"#include <b>\n"
"#include <c>\n"));
+
+ Style.IncludeBlocks = FormatStyle::IBS_Merge;
+ EXPECT_EQ("#include <a>\n"
+ "#include <b>\n"
+ "#include <c>\n",
+ sort("#include <a>\n"
+ "#include <b>\n"
+ "\n"
+ "#include <b>\n"
+ "\n"
+ "#include <b>\n"
+ "#include <c>\n"));
+
+ Style.IncludeBlocks = FormatStyle::IBS_Regroup;
+ EXPECT_EQ("#include <a>\n"
+ "#include <b>\n"
+ "#include <c>\n",
+ sort("#include <a>\n"
+ "#include <b>\n"
+ "\n"
+ "#include <b>\n"
+ "\n"
+ "#include <b>\n"
+ "#include <c>\n"));
}
TEST_F(SortIncludesTest, SortAndDeduplicateIncludes) {
@@ -344,6 +502,30 @@ TEST_F(SortIncludesTest, SortAndDeduplicateIncludes) {
"#include <b>\n"
"#include <c>\n"
"#include <b>\n"));
+
+ Style.IncludeBlocks = FormatStyle::IBS_Merge;
+ EXPECT_EQ("#include <a>\n"
+ "#include <b>\n"
+ "#include <c>\n",
+ sort("#include <b>\n"
+ "#include <a>\n"
+ "\n"
+ "#include <b>\n"
+ "\n"
+ "#include <c>\n"
+ "#include <b>\n"));
+
+ Style.IncludeBlocks = FormatStyle::IBS_Regroup;
+ EXPECT_EQ("#include <a>\n"
+ "#include <b>\n"
+ "#include <c>\n",
+ sort("#include <b>\n"
+ "#include <a>\n"
+ "\n"
+ "#include <b>\n"
+ "\n"
+ "#include <c>\n"
+ "#include <b>\n"));
}
TEST_F(SortIncludesTest, CalculatesCorrectCursorPositionAfterDeduplicate) {
@@ -398,6 +580,17 @@ TEST_F(SortIncludesTest, ValidAffactedRangesAfterDeduplicatingIncludes) {
EXPECT_EQ(26u, Ranges[0].getLength());
}
+TEST_F(SortIncludesTest, DoNotSortLikelyXml) {
+ EXPECT_EQ("<!--;\n"
+ "#include <b>\n"
+ "#include <a>\n"
+ "-->",
+ sort("<!--;\n"
+ "#include <b>\n"
+ "#include <a>\n"
+ "-->"));
+}
+
} // end namespace
} // end namespace format
} // end namespace clang
diff --git a/unittests/Format/UsingDeclarationsSorterTest.cpp b/unittests/Format/UsingDeclarationsSorterTest.cpp
index 858a62c2d799a..c4d971367be9e 100644
--- a/unittests/Format/UsingDeclarationsSorterTest.cpp
+++ b/unittests/Format/UsingDeclarationsSorterTest.cpp
@@ -50,8 +50,8 @@ TEST_F(UsingDeclarationsSorterTest, SwapsTwoConsecutiveUsingDeclarations) {
"using aa;",
sortUsingDeclarations("using aa;\n"
"using a;"));
- EXPECT_EQ("using ::a;\n"
- "using a;",
+ EXPECT_EQ("using a;\n"
+ "using ::a;",
sortUsingDeclarations("using a;\n"
"using ::a;"));
@@ -86,6 +86,93 @@ TEST_F(UsingDeclarationsSorterTest, SwapsTwoConsecutiveUsingDeclarations) {
"using a, b;"));
}
+TEST_F(UsingDeclarationsSorterTest, UsingDeclarationOrder) {
+ EXPECT_EQ("using A;\n"
+ "using a;",
+ sortUsingDeclarations("using A;\n"
+ "using a;"));
+ EXPECT_EQ("using a;\n"
+ "using A;",
+ sortUsingDeclarations("using a;\n"
+ "using A;"));
+ EXPECT_EQ("using a;\n"
+ "using B;",
+ sortUsingDeclarations("using B;\n"
+ "using a;"));
+
+ // Ignores leading '::'.
+ EXPECT_EQ("using ::a;\n"
+ "using A;",
+ sortUsingDeclarations("using ::a;\n"
+ "using A;"));
+
+ EXPECT_EQ("using ::A;\n"
+ "using a;",
+ sortUsingDeclarations("using ::A;\n"
+ "using a;"));
+
+ // Sorts '_' before 'a' and 'A'.
+ EXPECT_EQ("using _;\n"
+ "using A;",
+ sortUsingDeclarations("using A;\n"
+ "using _;"));
+ EXPECT_EQ("using _;\n"
+ "using a;",
+ sortUsingDeclarations("using a;\n"
+ "using _;"));
+ EXPECT_EQ("using a::_;\n"
+ "using a::a;",
+ sortUsingDeclarations("using a::a;\n"
+ "using a::_;"));
+
+ // Sorts non-namespace names before namespace names at the same level.
+ EXPECT_EQ("using ::testing::_;\n"
+ "using ::testing::Aardvark;\n"
+ "using ::testing::kMax;\n"
+ "using ::testing::Xylophone;\n"
+ "using ::testing::apple::Honeycrisp;\n"
+ "using ::testing::zebra::Stripes;",
+ sortUsingDeclarations("using ::testing::Aardvark;\n"
+ "using ::testing::Xylophone;\n"
+ "using ::testing::kMax;\n"
+ "using ::testing::_;\n"
+ "using ::testing::apple::Honeycrisp;\n"
+ "using ::testing::zebra::Stripes;"));
+}
+
+TEST_F(UsingDeclarationsSorterTest, SortsStably) {
+ EXPECT_EQ("using a;\n"
+ "using A;\n"
+ "using a;\n"
+ "using A;\n"
+ "using a;\n"
+ "using A;\n"
+ "using a;\n"
+ "using B;\n"
+ "using b;\n"
+ "using B;\n"
+ "using b;\n"
+ "using B;\n"
+ "using b;",
+ sortUsingDeclarations("using a;\n"
+ "using B;\n"
+ "using a;\n"
+ "using b;\n"
+ "using A;\n"
+ "using a;\n"
+ "using b;\n"
+ "using B;\n"
+ "using b;\n"
+ "using A;\n"
+ "using a;\n"
+ "using b;\n"
+ "using b;\n"
+ "using B;\n"
+ "using b;\n"
+ "using A;\n"
+ "using a;"));
+}
+
TEST_F(UsingDeclarationsSorterTest, SortsMultipleTopLevelDeclarations) {
EXPECT_EQ("using a;\n"
"using b;\n"
@@ -99,14 +186,14 @@ TEST_F(UsingDeclarationsSorterTest, SortsMultipleTopLevelDeclarations) {
"using c;"));
EXPECT_EQ("#include <iostream>\n"
- "using ::std::endl;\n"
"using std::cin;\n"
"using std::cout;\n"
+ "using ::std::endl;\n"
"int main();",
sortUsingDeclarations("#include <iostream>\n"
"using std::cout;\n"
- "using std::cin;\n"
"using ::std::endl;\n"
+ "using std::cin;\n"
"int main();"));
}
@@ -220,13 +307,67 @@ TEST_F(UsingDeclarationsSorterTest, SupportsClangFormatOff) {
}
TEST_F(UsingDeclarationsSorterTest, SortsPartialRangeOfUsingDeclarations) {
- EXPECT_EQ("using b;\n"
- "using a;\n"
+ // Sorts the whole block of using declarations surrounding the range.
+ EXPECT_EQ("using a;\n"
+ "using b;\n"
"using c;",
sortUsingDeclarations("using b;\n"
"using c;\n" // starts at offset 10
"using a;",
{tooling::Range(10, 15)}));
+ EXPECT_EQ("using a;\n"
+ "using b;\n"
+ "using c;\n"
+ "using A = b;",
+ sortUsingDeclarations("using b;\n"
+ "using c;\n" // starts at offset 10
+ "using a;\n"
+ "using A = b;",
+ {tooling::Range(10, 15)}));
+
+ EXPECT_EQ("using d;\n"
+ "using c;\n"
+ "\n"
+ "using a;\n"
+ "using b;\n"
+ "\n"
+ "using f;\n"
+ "using e;",
+ sortUsingDeclarations("using d;\n"
+ "using c;\n"
+ "\n"
+ "using b;\n" // starts at offset 19
+ "using a;\n"
+ "\n"
+ "using f;\n"
+ "using e;",
+ {tooling::Range(19, 1)}));
+}
+
+TEST_F(UsingDeclarationsSorterTest, SortsUsingDeclarationsWithLeadingkComments) {
+ EXPECT_EQ("/* comment */ using a;\n"
+ "/* comment */ using b;",
+ sortUsingDeclarations("/* comment */ using b;\n"
+ "/* comment */ using a;"));
+}
+
+TEST_F(UsingDeclarationsSorterTest, DeduplicatesUsingDeclarations) {
+ EXPECT_EQ("using a;\n"
+ "using b;\n"
+ "using c;\n"
+ "\n"
+ "using a;\n"
+ "using e;",
+ sortUsingDeclarations("using c;\n"
+ "using a;\n"
+ "using b;\n"
+ "using a;\n"
+ "using b;\n"
+ "\n"
+ "using e;\n"
+ "using a;\n"
+ "using e;"));
+
}
} // end namespace
diff --git a/unittests/Frontend/ASTUnitTest.cpp b/unittests/Frontend/ASTUnitTest.cpp
new file mode 100644
index 0000000000000..4f529cf55de98
--- /dev/null
+++ b/unittests/Frontend/ASTUnitTest.cpp
@@ -0,0 +1,87 @@
+//===- unittests/Frontend/ASTUnitTest.cpp - ASTUnit tests -----------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include <fstream>
+
+#include "clang/Frontend/ASTUnit.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/CompilerInvocation.h"
+#include "clang/Frontend/PCHContainerOperations.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/ToolOutputFile.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace clang;
+
+namespace {
+
+TEST(ASTUnit, SaveLoadPreservesLangOptionsInPrintingPolicy) {
+ // Check that the printing policy is restored with the correct language
+ // options when loading an ASTUnit from a file. To this end, an ASTUnit
+ // for a C++ translation unit is set up and written to a temporary file.
+
+ // By default `UseVoidForZeroParams` is true for non-C++ language options,
+ // thus we can check this field after loading the ASTUnit to deduce whether
+ // the correct (C++) language options were used when setting up the printing
+ // policy.
+
+ {
+ PrintingPolicy PolicyWithDefaultLangOpt(LangOptions{});
+ EXPECT_TRUE(PolicyWithDefaultLangOpt.UseVoidForZeroParams);
+ }
+
+ int FD;
+ llvm::SmallString<256> InputFileName;
+ ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("ast-unit", "cpp", FD, InputFileName));
+ ToolOutputFile input_file(InputFileName, FD);
+ input_file.os() << "";
+
+ const char* Args[] = {"clang", "-xc++", InputFileName.c_str()};
+
+ IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
+ CompilerInstance::createDiagnostics(new DiagnosticOptions());
+
+ std::shared_ptr<CompilerInvocation> CInvok =
+ createInvocationFromCommandLine(Args, Diags);
+
+ if (!CInvok)
+ FAIL() << "could not create compiler invocation";
+
+ FileManager *FileMgr =
+ new FileManager(FileSystemOptions(), vfs::getRealFileSystem());
+ auto PCHContainerOps = std::make_shared<PCHContainerOperations>();
+
+ std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation(
+ CInvok, PCHContainerOps, Diags, FileMgr);
+
+ if (!AST)
+ FAIL() << "failed to create ASTUnit";
+
+ EXPECT_FALSE(AST->getASTContext().getPrintingPolicy().UseVoidForZeroParams);
+
+ llvm::SmallString<256> ASTFileName;
+ ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("ast-unit", "ast", FD, ASTFileName));
+ ToolOutputFile ast_file(ASTFileName, FD);
+ AST->Save(ASTFileName.str());
+
+ EXPECT_TRUE(llvm::sys::fs::exists(ASTFileName));
+
+ std::unique_ptr<ASTUnit> AU = ASTUnit::LoadFromASTFile(
+ ASTFileName.str(), PCHContainerOps->getRawReader(), ASTUnit::LoadEverything, Diags,
+ FileSystemOptions(), /*UseDebugInfo=*/false);
+
+ if (!AU)
+ FAIL() << "failed to load ASTUnit";
+
+ EXPECT_FALSE(AU->getASTContext().getPrintingPolicy().UseVoidForZeroParams);
+}
+
+} // anonymous namespace
diff --git a/unittests/Frontend/CMakeLists.txt b/unittests/Frontend/CMakeLists.txt
index 674f77bd0135c..f3c4336ea22fa 100644
--- a/unittests/Frontend/CMakeLists.txt
+++ b/unittests/Frontend/CMakeLists.txt
@@ -3,10 +3,15 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_unittest(FrontendTests
+ ASTUnitTest.cpp
+ CompilerInstanceTest.cpp
FrontendActionTest.cpp
CodeGenActionTest.cpp
+ ParsedSourceLocationTest.cpp
+ PCHPreambleTest.cpp
)
target_link_libraries(FrontendTests
+ PRIVATE
clangAST
clangBasic
clangFrontend
diff --git a/unittests/Frontend/CompilerInstanceTest.cpp b/unittests/Frontend/CompilerInstanceTest.cpp
new file mode 100644
index 0000000000000..b2d9f8bcf00e4
--- /dev/null
+++ b/unittests/Frontend/CompilerInstanceTest.cpp
@@ -0,0 +1,74 @@
+//===- unittests/Frontend/CompilerInstanceTest.cpp - CI tests -------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/CompilerInvocation.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Format.h"
+#include "llvm/Support/ToolOutputFile.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace clang;
+
+namespace {
+
+TEST(CompilerInstance, DefaultVFSOverlayFromInvocation) {
+ // Create a temporary VFS overlay yaml file.
+ int FD;
+ SmallString<256> FileName;
+ ASSERT_FALSE(sys::fs::createTemporaryFile("vfs", "yaml", FD, FileName));
+ ToolOutputFile File(FileName, FD);
+
+ SmallString<256> CurrentPath;
+ sys::fs::current_path(CurrentPath);
+ sys::fs::make_absolute(CurrentPath, FileName);
+
+ // Mount the VFS file itself on the path 'virtual.file'. Makes this test
+ // a bit shorter than creating a new dummy file just for this purpose.
+ const std::string CurrentPathStr = CurrentPath.str();
+ const std::string FileNameStr = FileName.str();
+ const char *VFSYaml = "{ 'version': 0, 'roots': [\n"
+ " { 'name': '%s',\n"
+ " 'type': 'directory',\n"
+ " 'contents': [\n"
+ " { 'name': 'vfs-virtual.file', 'type': 'file',\n"
+ " 'external-contents': '%s'\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ "]}\n";
+ File.os() << format(VFSYaml, CurrentPathStr.c_str(), FileName.c_str());
+ File.os().flush();
+
+ // Create a CompilerInvocation that uses this overlay file.
+ const std::string VFSArg = "-ivfsoverlay" + FileNameStr;
+ const char *Args[] = {"clang", VFSArg.c_str(), "-xc++", "-"};
+
+ IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
+ CompilerInstance::createDiagnostics(new DiagnosticOptions());
+
+ std::shared_ptr<CompilerInvocation> CInvok =
+ createInvocationFromCommandLine(Args, Diags);
+
+ if (!CInvok)
+ FAIL() << "could not create compiler invocation";
+ // Create a minimal CompilerInstance which should use the VFS we specified
+ // in the CompilerInvocation (as we don't explicitly set our own).
+ CompilerInstance Instance;
+ Instance.setDiagnostics(Diags.get());
+ Instance.setInvocation(CInvok);
+ Instance.createFileManager();
+
+ // Check if the virtual file exists which means that our VFS is used by the
+ // CompilerInstance.
+ ASSERT_TRUE(Instance.getFileManager().getFile("vfs-virtual.file"));
+}
+
+} // anonymous namespace
diff --git a/unittests/Frontend/PCHPreambleTest.cpp b/unittests/Frontend/PCHPreambleTest.cpp
new file mode 100644
index 0000000000000..162a281b04d49
--- /dev/null
+++ b/unittests/Frontend/PCHPreambleTest.cpp
@@ -0,0 +1,200 @@
+//====-- unittests/Frontend/PCHPreambleTest.cpp - FrontendAction tests ---====//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Frontend/ASTUnit.h"
+#include "clang/Frontend/CompilerInvocation.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/FrontendActions.h"
+#include "clang/Frontend/FrontendOptions.h"
+#include "clang/Lex/PreprocessorOptions.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/FileManager.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace clang;
+
+namespace {
+
+class ReadCountingInMemoryFileSystem : public vfs::InMemoryFileSystem
+{
+ std::map<std::string, unsigned> ReadCounts;
+
+public:
+ ErrorOr<std::unique_ptr<vfs::File>> openFileForRead(const Twine &Path) override
+ {
+ SmallVector<char, 128> PathVec;
+ Path.toVector(PathVec);
+ llvm::sys::path::remove_dots(PathVec, true);
+ ++ReadCounts[std::string(PathVec.begin(), PathVec.end())];
+ return InMemoryFileSystem::openFileForRead(Path);
+ }
+
+ unsigned GetReadCount(const Twine &Path) const
+ {
+ auto it = ReadCounts.find(Path.str());
+ return it == ReadCounts.end() ? 0 : it->second;
+ }
+};
+
+class PCHPreambleTest : public ::testing::Test {
+ IntrusiveRefCntPtr<ReadCountingInMemoryFileSystem> VFS;
+ StringMap<std::string> RemappedFiles;
+ std::shared_ptr<PCHContainerOperations> PCHContainerOpts;
+ FileSystemOptions FSOpts;
+
+public:
+ void SetUp() override {
+ VFS = new ReadCountingInMemoryFileSystem();
+ // We need the working directory to be set to something absolute,
+ // otherwise it ends up being inadvertently set to the current
+ // working directory in the real file system due to a series of
+ // unfortunate conditions interacting badly.
+ // What's more, this path *must* be absolute on all (real)
+ // filesystems, so just '/' won't work (e.g. on Win32).
+ VFS->setCurrentWorkingDirectory("//./");
+ }
+
+ void TearDown() override {
+ }
+
+ void AddFile(const std::string &Filename, const std::string &Contents) {
+ ::time_t now;
+ ::time(&now);
+ VFS->addFile(Filename, now, MemoryBuffer::getMemBufferCopy(Contents, Filename));
+ }
+
+ void RemapFile(const std::string &Filename, const std::string &Contents) {
+ RemappedFiles[Filename] = Contents;
+ }
+
+ std::unique_ptr<ASTUnit> ParseAST(const std::string &EntryFile) {
+ PCHContainerOpts = std::make_shared<PCHContainerOperations>();
+ std::shared_ptr<CompilerInvocation> CI(new CompilerInvocation);
+ CI->getFrontendOpts().Inputs.push_back(
+ FrontendInputFile(EntryFile, FrontendOptions::getInputKindForExtension(
+ llvm::sys::path::extension(EntryFile).substr(1))));
+
+ CI->getTargetOpts().Triple = "i386-unknown-linux-gnu";
+
+ CI->getPreprocessorOpts().RemappedFileBuffers = GetRemappedFiles();
+
+ PreprocessorOptions &PPOpts = CI->getPreprocessorOpts();
+ PPOpts.RemappedFilesKeepOriginalName = true;
+
+ IntrusiveRefCntPtr<DiagnosticsEngine>
+ Diags(CompilerInstance::createDiagnostics(new DiagnosticOptions, new DiagnosticConsumer));
+
+ FileManager *FileMgr = new FileManager(FSOpts, VFS);
+
+ std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation(
+ CI, PCHContainerOpts, Diags, FileMgr, false, false,
+ /*PrecompilePreambleAfterNParses=*/1);
+ return AST;
+ }
+
+ bool ReparseAST(const std::unique_ptr<ASTUnit> &AST) {
+ bool reparseFailed = AST->Reparse(PCHContainerOpts, GetRemappedFiles(), VFS);
+ return !reparseFailed;
+ }
+
+ unsigned GetFileReadCount(const std::string &Filename) const {
+ return VFS->GetReadCount(Filename);
+ }
+
+private:
+ std::vector<std::pair<std::string, llvm::MemoryBuffer *>>
+ GetRemappedFiles() const {
+ std::vector<std::pair<std::string, llvm::MemoryBuffer *>> Remapped;
+ for (const auto &RemappedFile : RemappedFiles) {
+ std::unique_ptr<MemoryBuffer> buf = MemoryBuffer::getMemBufferCopy(
+ RemappedFile.second, RemappedFile.first());
+ Remapped.emplace_back(RemappedFile.first(), buf.release());
+ }
+ return Remapped;
+ }
+};
+
+TEST_F(PCHPreambleTest, ReparseWithOverriddenFileDoesNotInvalidatePreamble) {
+ std::string Header1 = "//./header1.h";
+ std::string Header2 = "//./header2.h";
+ std::string MainName = "//./main.cpp";
+ AddFile(Header1, "");
+ AddFile(Header2, "#pragma once");
+ AddFile(MainName,
+ "#include \"//./foo/../header1.h\"\n"
+ "#include \"//./foo/../header2.h\"\n"
+ "int main() { return ZERO; }");
+ RemapFile(Header1, "static const int ZERO = 0;\n");
+
+ std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
+ ASSERT_TRUE(AST.get());
+ ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
+
+ unsigned initialCounts[] = {
+ GetFileReadCount(MainName),
+ GetFileReadCount(Header1),
+ GetFileReadCount(Header2)
+ };
+
+ ASSERT_TRUE(ReparseAST(AST));
+
+ ASSERT_NE(initialCounts[0], GetFileReadCount(MainName));
+ ASSERT_EQ(initialCounts[1], GetFileReadCount(Header1));
+ ASSERT_EQ(initialCounts[2], GetFileReadCount(Header2));
+}
+
+TEST_F(PCHPreambleTest, ParseWithBom) {
+ std::string Header = "//./header.h";
+ std::string Main = "//./main.cpp";
+ AddFile(Header, "int random() { return 4; }");
+ AddFile(Main,
+ "\xef\xbb\xbf"
+ "#include \"//./header.h\"\n"
+ "int main() { return random() -2; }");
+
+ std::unique_ptr<ASTUnit> AST(ParseAST(Main));
+ ASSERT_TRUE(AST.get());
+ ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
+
+ unsigned HeaderReadCount = GetFileReadCount(Header);
+
+ ASSERT_TRUE(ReparseAST(AST));
+ ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
+
+ // Check preamble PCH was really reused
+ ASSERT_EQ(HeaderReadCount, GetFileReadCount(Header));
+
+ // Remove BOM
+ RemapFile(Main,
+ "#include \"//./header.h\"\n"
+ "int main() { return random() -2; }");
+
+ ASSERT_TRUE(ReparseAST(AST));
+ ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
+
+ ASSERT_LE(HeaderReadCount, GetFileReadCount(Header));
+ HeaderReadCount = GetFileReadCount(Header);
+
+ // Add BOM back
+ RemapFile(Main,
+ "\xef\xbb\xbf"
+ "#include \"//./header.h\"\n"
+ "int main() { return random() -2; }");
+
+ ASSERT_TRUE(ReparseAST(AST));
+ ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
+
+ ASSERT_LE(HeaderReadCount, GetFileReadCount(Header));
+}
+
+} // anonymous namespace
diff --git a/unittests/Frontend/ParsedSourceLocationTest.cpp b/unittests/Frontend/ParsedSourceLocationTest.cpp
new file mode 100644
index 0000000000000..0cbdc7e1d5a24
--- /dev/null
+++ b/unittests/Frontend/ParsedSourceLocationTest.cpp
@@ -0,0 +1,37 @@
+//===- unittests/Frontend/ParsedSourceLocationTest.cpp - ------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Frontend/CommandLineSourceLoc.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace clang;
+
+namespace {
+
+TEST(ParsedSourceRange, ParseTest) {
+ auto Check = [](StringRef Value, StringRef Filename, unsigned BeginLine,
+ unsigned BeginColumn, unsigned EndLine, unsigned EndColumn) {
+ Optional<ParsedSourceRange> PSR = ParsedSourceRange::fromString(Value);
+ ASSERT_TRUE(PSR);
+ EXPECT_EQ(PSR->FileName, Filename);
+ EXPECT_EQ(PSR->Begin.first, BeginLine);
+ EXPECT_EQ(PSR->Begin.second, BeginColumn);
+ EXPECT_EQ(PSR->End.first, EndLine);
+ EXPECT_EQ(PSR->End.second, EndColumn);
+ };
+
+ Check("/Users/test/a-b.cpp:1:2", "/Users/test/a-b.cpp", 1, 2, 1, 2);
+ Check("/Users/test/a-b.cpp:1:2-3:4", "/Users/test/a-b.cpp", 1, 2, 3, 4);
+
+ Check("C:/Users/bob/a-b.cpp:1:2", "C:/Users/bob/a-b.cpp", 1, 2, 1, 2);
+ Check("C:/Users/bob/a-b.cpp:1:2-3:4", "C:/Users/bob/a-b.cpp", 1, 2, 3, 4);
+}
+
+} // anonymous namespace
diff --git a/unittests/Lex/CMakeLists.txt b/unittests/Lex/CMakeLists.txt
index ef0f06c0b3c9d..ea6f9fd234020 100644
--- a/unittests/Lex/CMakeLists.txt
+++ b/unittests/Lex/CMakeLists.txt
@@ -10,6 +10,7 @@ add_clang_unittest(LexTests
)
target_link_libraries(LexTests
+ PRIVATE
clangAST
clangBasic
clangLex
diff --git a/unittests/Lex/LexerTest.cpp b/unittests/Lex/LexerTest.cpp
index 923aff18472b6..d699a44b13fdb 100644
--- a/unittests/Lex/LexerTest.cpp
+++ b/unittests/Lex/LexerTest.cpp
@@ -37,7 +37,7 @@ protected:
DiagID(new DiagnosticIDs()),
Diags(DiagID, new DiagnosticOptions, new IgnoringDiagConsumer()),
SourceMgr(Diags, FileMgr),
- TargetOpts(new TargetOptions)
+ TargetOpts(new TargetOptions)
{
TargetOpts->Triple = "x86_64-apple-darwin11.1.0";
Target = TargetInfo::CreateTargetInfo(Diags, TargetOpts);
@@ -420,4 +420,100 @@ TEST_F(LexerTest, DontOverallocateStringifyArgs) {
#endif
}
+TEST_F(LexerTest, IsNewLineEscapedValid) {
+ auto hasNewLineEscaped = [](const char *S) {
+ return Lexer::isNewLineEscaped(S, S + strlen(S) - 1);
+ };
+
+ EXPECT_TRUE(hasNewLineEscaped("\\\r"));
+ EXPECT_TRUE(hasNewLineEscaped("\\\n"));
+ EXPECT_TRUE(hasNewLineEscaped("\\\r\n"));
+ EXPECT_TRUE(hasNewLineEscaped("\\\n\r"));
+ EXPECT_TRUE(hasNewLineEscaped("\\ \t\v\f\r"));
+ EXPECT_TRUE(hasNewLineEscaped("\\ \t\v\f\r\n"));
+
+ EXPECT_FALSE(hasNewLineEscaped("\\\r\r"));
+ EXPECT_FALSE(hasNewLineEscaped("\\\r\r\n"));
+ EXPECT_FALSE(hasNewLineEscaped("\\\n\n"));
+ EXPECT_FALSE(hasNewLineEscaped("\r"));
+ EXPECT_FALSE(hasNewLineEscaped("\n"));
+ EXPECT_FALSE(hasNewLineEscaped("\r\n"));
+ EXPECT_FALSE(hasNewLineEscaped("\n\r"));
+ EXPECT_FALSE(hasNewLineEscaped("\r\r"));
+ EXPECT_FALSE(hasNewLineEscaped("\n\n"));
+}
+
+TEST_F(LexerTest, GetBeginningOfTokenWithEscapedNewLine) {
+ // Each line should have the same length for
+ // further offset calculation to be more straightforward.
+ const unsigned IdentifierLength = 8;
+ std::string TextToLex = "rabarbar\n"
+ "foo\\\nbar\n"
+ "foo\\\rbar\n"
+ "fo\\\r\nbar\n"
+ "foo\\\n\rba\n";
+ std::vector<tok::TokenKind> ExpectedTokens{5, tok::identifier};
+ std::vector<Token> LexedTokens = CheckLex(TextToLex, ExpectedTokens);
+
+ for (const Token &Tok : LexedTokens) {
+ std::pair<FileID, unsigned> OriginalLocation =
+ SourceMgr.getDecomposedLoc(Tok.getLocation());
+ for (unsigned Offset = 0; Offset < IdentifierLength; ++Offset) {
+ SourceLocation LookupLocation =
+ Tok.getLocation().getLocWithOffset(Offset);
+
+ std::pair<FileID, unsigned> FoundLocation =
+ SourceMgr.getDecomposedExpansionLoc(
+ Lexer::GetBeginningOfToken(LookupLocation, SourceMgr, LangOpts));
+
+ // Check that location returned by the GetBeginningOfToken
+ // is the same as original token location reported by Lexer.
+ EXPECT_EQ(FoundLocation.second, OriginalLocation.second);
+ }
+ }
+}
+
+TEST_F(LexerTest, AvoidPastEndOfStringDereference) {
+ std::vector<Token> LexedTokens = Lex(" // \\\n");
+ EXPECT_TRUE(LexedTokens.empty());
+}
+
+TEST_F(LexerTest, StringizingRasString) {
+ // For "std::string Lexer::Stringify(StringRef Str, bool Charify)".
+ std::string String1 = R"(foo
+ {"bar":[]}
+ baz)";
+ // For "void Lexer::Stringify(SmallVectorImpl<char> &Str)".
+ SmallString<128> String2;
+ String2 += String1.c_str();
+
+ // Corner cases.
+ std::string String3 = R"(\
+ \n
+ \\n
+ \\)";
+ SmallString<128> String4;
+ String4 += String3.c_str();
+ std::string String5 = R"(a\
+
+
+ \\b)";
+ SmallString<128> String6;
+ String6 += String5.c_str();
+
+ String1 = Lexer::Stringify(StringRef(String1));
+ Lexer::Stringify(String2);
+ String3 = Lexer::Stringify(StringRef(String3));
+ Lexer::Stringify(String4);
+ String5 = Lexer::Stringify(StringRef(String5));
+ Lexer::Stringify(String6);
+
+ EXPECT_EQ(String1, R"(foo\n {\"bar\":[]}\n baz)");
+ EXPECT_EQ(String2, R"(foo\n {\"bar\":[]}\n baz)");
+ EXPECT_EQ(String3, R"(\\\n \\n\n \\\\n\n \\\\)");
+ EXPECT_EQ(String4, R"(\\\n \\n\n \\\\n\n \\\\)");
+ EXPECT_EQ(String5, R"(a\\\n\n\n \\\\b)");
+ EXPECT_EQ(String6, R"(a\\\n\n\n \\\\b)");
+}
+
} // anonymous namespace
diff --git a/unittests/Rename/CMakeLists.txt b/unittests/Rename/CMakeLists.txt
index aa7609260cc0c..b625a7a691fbb 100644
--- a/unittests/Rename/CMakeLists.txt
+++ b/unittests/Rename/CMakeLists.txt
@@ -7,9 +7,14 @@ include_directories(${CLANG_SOURCE_DIR})
add_clang_unittest(ClangRenameTests
RenameClassTest.cpp
+ RenameEnumTest.cpp
+ RenameAliasTest.cpp
+ RenameMemberTest.cpp
+ RenameFunctionTest.cpp
)
target_link_libraries(ClangRenameTests
+ PRIVATE
clangAST
clangASTMatchers
clangBasic
diff --git a/unittests/Rename/RenameAliasTest.cpp b/unittests/Rename/RenameAliasTest.cpp
new file mode 100644
index 0000000000000..59becaef68a10
--- /dev/null
+++ b/unittests/Rename/RenameAliasTest.cpp
@@ -0,0 +1,304 @@
+//===-- RenameAliasTest.cpp - unit tests for renaming alias ---------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangRenameTest.h"
+
+namespace clang {
+namespace clang_rename {
+namespace test {
+namespace {
+
+class RenameAliasTest : public ClangRenameTest {
+public:
+ RenameAliasTest() {
+ AppendToHeader(R"(
+ #define MACRO(x) x
+ namespace some_ns {
+ class A {
+ public:
+ void foo() {}
+ struct Nested {
+ enum NestedEnum {
+ E1, E2,
+ };
+ };
+ };
+ } // namespace some_ns
+ namespace a {
+ typedef some_ns::A TA;
+ using UA = some_ns::A;
+ } // namespace a
+ namespace b {
+ typedef some_ns::A TA;
+ using UA = some_ns::A;
+ }
+ template <typename T> class ptr {};
+ template <typename T>
+
+ using TPtr = ptr<int>;
+ )");
+ }
+};
+
+INSTANTIATE_TEST_CASE_P(
+ RenameAliasTests, RenameAliasTest,
+ testing::ValuesIn(std::vector<Case>({
+ // basic functions
+ {"void f(a::TA a1) {}", "void f(b::TB a1) {}", "a::TA", "b::TB"},
+ {"void f(a::UA a1) {}", "void f(b::UB a1) {}", "a::UA", "b::UB"},
+ {"void f(a::TA* a1) {}", "void f(b::TB* a1) {}", "a::TA", "b::TB"},
+ {"void f(a::TA** a1) {}", "void f(b::TB** a1) {}", "a::TA", "b::TB"},
+ {"a::TA f() { return a::TA(); }", "b::TB f() { return b::TB(); }",
+ "a::TA", "b::TB"},
+ {"a::TA f() { return a::UA(); }", "b::TB f() { return a::UA(); }",
+ "a::TA", "b::TB"},
+ {"a::TA f() { return a::UA(); }", "a::TA f() { return b::UB(); }",
+ "a::UA", "b::UB"},
+ {"void f() { a::TA a; }", "void f() { b::TB a; }", "a::TA", "b::TB"},
+ {"void f(const a::TA& a1) {}", "void f(const b::TB& a1) {}", "a::TA",
+ "b::TB"},
+ {"void f(const a::UA& a1) {}", "void f(const b::UB& a1) {}", "a::UA",
+ "b::UB"},
+ {"void f(const a::TA* a1) {}", "void f(const b::TB* a1) {}", "a::TA",
+ "b::TB"},
+ {"namespace a { void f(TA a1) {} }",
+ "namespace a { void f(b::TB a1) {} }", "a::TA", "b::TB"},
+ {"void f(MACRO(a::TA) a1) {}", "void f(MACRO(b::TB) a1) {}", "a::TA",
+ "b::TB"},
+ {"void f(MACRO(a::TA a1)) {}", "void f(MACRO(b::TB a1)) {}", "a::TA",
+ "b::TB"},
+
+ // shorten/add namespace.
+ {"namespace b { void f(a::UA a1) {} }",
+ "namespace b {void f(UB a1) {} }", "a::UA", "b::UB"},
+ {"namespace a { void f(UA a1) {} }",
+ "namespace a {void f(b::UB a1) {} }", "a::UA", "b::UB"},
+
+ // use namespace and typedefs
+ {"struct S { using T = a::TA; T a_; };",
+ "struct S { using T = b::TB; T a_; };", "a::TA", "b::TB"},
+ {"using T = a::TA; T gA;", "using T = b::TB; T gA;", "a::TA", "b::TB"},
+ {"using T = a::UA; T gA;", "using T = b::UB; T gA;", "a::UA", "b::UB"},
+ {"typedef a::TA T; T gA;", "typedef b::TB T; T gA;", "a::TA", "b::TB"},
+ {"typedef a::UA T; T gA;", "typedef b::UB T; T gA;", "a::UA", "b::UB"},
+ {"typedef MACRO(a::TA) T; T gA;", "typedef MACRO(b::TB) T; T gA;",
+ "a::TA", "b::TB"},
+
+ // types in using shadows.
+ {"using a::TA; TA gA;", "using b::TB; b::TB gA;", "a::TA", "b::TB"},
+ {"using a::UA; UA gA;", "using b::UB; b::UB gA;", "a::UA", "b::UB"},
+
+ // struct members and other oddities
+ {"struct S : public a::TA {};", "struct S : public b::TB {};", "a::TA",
+ "b::TB"},
+ {"struct S : public a::UA {};", "struct S : public b::UB {};", "a::UA",
+ "b::UB"},
+ {"struct F { void f(a::TA a1) {} };",
+ "struct F { void f(b::TB a1) {} };", "a::TA", "b::TB"},
+ {"struct F { a::TA a_; };", "struct F { b::TB a_; };", "a::TA",
+ "b::TB"},
+ {"struct F { ptr<a::TA> a_; };", "struct F { ptr<b::TB> a_; };",
+ "a::TA", "b::TB"},
+ {"struct F { ptr<a::UA> a_; };", "struct F { ptr<b::UB> a_; };",
+ "a::UA", "b::UB"},
+
+ // types in nested name specifiers
+ {"void f() { a::TA::Nested ne; }", "void f() { b::TB::Nested ne; }",
+ "a::TA", "b::TB"},
+ {"void f() { a::UA::Nested ne; }", "void f() { b::UB::Nested ne; }",
+ "a::UA", "b::UB"},
+ {"void f() { a::TA::Nested::NestedEnum e; }",
+ "void f() { b::TB::Nested::NestedEnum e; }", "a::TA", "b::TB"},
+ {"void f() { auto e = a::TA::Nested::NestedEnum::E1; }",
+ "void f() { auto e = b::TB::Nested::NestedEnum::E1; }", "a::TA",
+ "b::TB"},
+ {"void f() { auto e = a::TA::Nested::E1; }",
+ "void f() { auto e = b::TB::Nested::E1; }", "a::TA", "b::TB"},
+
+ // templates
+ {"template <typename T> struct Foo { T t; }; void f() { Foo<a::TA> "
+ "foo; }",
+ "template <typename T> struct Foo { T t; }; void f() { Foo<b::TB> "
+ "foo; }",
+ "a::TA", "b::TB"},
+ {"template <typename T> struct Foo { a::TA a; };",
+ "template <typename T> struct Foo { b::TB a; };", "a::TA", "b::TB"},
+ {"template <typename T> void f(T t) {} void g() { f<a::TA>(a::TA()); }",
+ "template <typename T> void f(T t) {} void g() { f<b::TB>(b::TB()); }",
+ "a::TA", "b::TB"},
+ {"template <typename T> void f(T t) {} void g() { f<a::UA>(a::UA()); }",
+ "template <typename T> void f(T t) {} void g() { f<b::UB>(b::UB()); }",
+ "a::UA", "b::UB"},
+ {"template <typename T> int f() { return 1; } template <> int "
+ "f<a::TA>() { return 2; } int g() { return f<a::TA>(); }",
+ "template <typename T> int f() { return 1; } template <> int "
+ "f<b::TB>() { return 2; } int g() { return f<b::TB>(); }",
+ "a::TA", "b::TB"},
+ {"struct Foo { template <typename T> T foo(); }; void g() { Foo f; "
+ "auto a = f.template foo<a::TA>(); }",
+ "struct Foo { template <typename T> T foo(); }; void g() { Foo f; "
+ "auto a = f.template foo<b::TB>(); }",
+ "a::TA", "b::TB"},
+ {"struct Foo { template <typename T> T foo(); }; void g() { Foo f; "
+ "auto a = f.template foo<a::UA>(); }",
+ "struct Foo { template <typename T> T foo(); }; void g() { Foo f; "
+ "auto a = f.template foo<b::UB>(); }",
+ "a::UA", "b::UB"},
+
+ // The following two templates are distilled from regressions found in
+ // unique_ptr<> and type_traits.h
+ {"template <typename T> struct outer { typedef T type; type Baz(); }; "
+ "outer<a::TA> g_A;",
+ "template <typename T> struct outer { typedef T type; type Baz(); }; "
+ "outer<b::TB> g_A;",
+ "a::TA", "b::TB"},
+ {"template <typename T> struct nested { typedef T type; }; template "
+ "<typename T> struct outer { typename nested<T>::type Foo(); }; "
+ "outer<a::TA> g_A;",
+ "template <typename T> struct nested { typedef T type; }; template "
+ "<typename T> struct outer { typename nested<T>::type Foo(); }; "
+ "outer<b::TB> g_A;",
+ "a::TA", "b::TB"},
+
+ // macros
+ {"#define FOO(T, t) T t\nvoid f() { FOO(a::TA, a1); FOO(a::TA, a2); }",
+ "#define FOO(T, t) T t\nvoid f() { FOO(b::TB, a1); FOO(b::TB, a2); }",
+ "a::TA", "b::TB"},
+ {"#define FOO(n) a::TA n\nvoid f() { FOO(a1); FOO(a2); }",
+ "#define FOO(n) b::TB n\nvoid f() { FOO(a1); FOO(a2); }", "a::TA",
+ "b::TB"},
+ {"#define FOO(n) a::UA n\nvoid f() { FOO(a1); FOO(a2); }",
+ "#define FOO(n) b::UB n\nvoid f() { FOO(a1); FOO(a2); }", "a::UA",
+ "b::UB"},
+
+ // Pointer to member functions
+ {"auto gA = &a::TA::foo;", "auto gA = &b::TB::foo;", "a::TA", "b::TB"},
+ {"using a::TA; auto gA = &TA::foo;",
+ "using b::TB; auto gA = &b::TB::foo;", "a::TA", "b::TB"},
+ {"typedef a::TA T; auto gA = &T::foo;",
+ "typedef b::TB T; auto gA = &T::foo;", "a::TA", "b::TB"},
+ {"auto gA = &MACRO(a::TA)::foo;", "auto gA = &MACRO(b::TB)::foo;",
+ "a::TA", "b::TB"},
+
+ // templated using alias.
+ {"void f(TPtr<int> p) {}", "void f(NewTPtr<int> p) {}", "TPtr",
+ "NewTPtr"},
+ {"void f(::TPtr<int> p) {}", "void f(::NewTPtr<int> p) {}", "TPtr",
+ "NewTPtr"},
+ })), );
+
+TEST_P(RenameAliasTest, RenameAlias) {
+ auto Param = GetParam();
+ assert(!Param.OldName.empty());
+ assert(!Param.NewName.empty());
+ std::string Actual =
+ runClangRenameOnCode(Param.Before, Param.OldName, Param.NewName);
+ CompareSnippets(Param.After, Actual);
+}
+
+TEST_F(RenameAliasTest, RenameTypedefDefinitions) {
+ std::string Before = R"(
+ class X {};
+ typedef X TOld;
+ )";
+ std::string Expected = R"(
+ class X {};
+ typedef X TNew;
+ )";
+ std::string After = runClangRenameOnCode(Before, "TOld", "TNew");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameAliasTest, RenameUsingAliasDefinitions) {
+ std::string Before = R"(
+ class X {};
+ using UOld = X;
+ )";
+ std::string Expected = R"(
+ class X {};
+ using UNew = X;
+ )";
+ std::string After = runClangRenameOnCode(Before, "UOld", "UNew");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameAliasTest, RenameTemplatedAliasDefinitions) {
+ std::string Before = R"(
+ template <typename T>
+ class X { T t; };
+
+ template <typename T>
+ using Old = X<T>;
+ )";
+ std::string Expected = R"(
+ template <typename T>
+ class X { T t; };
+
+ template <typename T>
+ using New = X<T>;
+ )";
+ std::string After = runClangRenameOnCode(Before, "Old", "New");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameAliasTest, RenameAliasesInNamespaces) {
+ std::string Before = R"(
+ namespace x { class X {}; }
+ namespace ns {
+ using UOld = x::X;
+ }
+ )";
+ std::string Expected = R"(
+ namespace x { class X {}; }
+ namespace ns {
+ using UNew = x::X;
+ }
+ )";
+ std::string After = runClangRenameOnCode(Before, "ns::UOld", "ns::UNew");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameAliasTest, AliasesInMacros) {
+ std::string Before = R"(
+ namespace x { class Old {}; }
+ namespace ns {
+ #define REF(alias) alias alias_var;
+
+ #define ALIAS(old) \
+ using old##Alias = x::old; \
+ REF(old##Alias);
+
+ ALIAS(Old);
+
+ OldAlias old_alias;
+ }
+ )";
+ std::string Expected = R"(
+ namespace x { class Old {}; }
+ namespace ns {
+ #define REF(alias) alias alias_var;
+
+ #define ALIAS(old) \
+ using old##Alias = x::old; \
+ REF(old##Alias);
+
+ ALIAS(Old);
+
+ NewAlias old_alias;
+ }
+ )";
+ std::string After =
+ runClangRenameOnCode(Before, "ns::OldAlias", "ns::NewAlias");
+ CompareSnippets(Expected, After);
+}
+
+} // anonymous namespace
+} // namespace test
+} // namespace clang_rename
+} // namesdpace clang
diff --git a/unittests/Rename/RenameClassTest.cpp b/unittests/Rename/RenameClassTest.cpp
index 29b4594fb0a4a..5845d63412b6b 100644
--- a/unittests/Rename/RenameClassTest.cpp
+++ b/unittests/Rename/RenameClassTest.cpp
@@ -51,6 +51,7 @@ INSTANTIATE_TEST_CASE_P(
testing::ValuesIn(std::vector<Case>({
// basic classes
{"a::Foo f;", "b::Bar f;", "", ""},
+ {"::a::Foo f;", "::b::Bar f;", "", ""},
{"void f(a::Foo f) {}", "void f(b::Bar f) {}", "", ""},
{"void f(a::Foo *f) {}", "void f(b::Bar *f) {}", "", ""},
{"a::Foo f() { return a::Foo(); }", "b::Bar f() { return b::Bar(); }",
@@ -469,8 +470,6 @@ TEST_F(ClangRenameTest, RenameClassWithInlineMembers) {
CompareSnippets(Expected, After);
}
-// FIXME: no prefix qualifiers being added to the class definition and
-// constructor.
TEST_F(ClangRenameTest, RenameClassWithNamespaceWithInlineMembers) {
std::string Before = R"(
namespace ns {
@@ -488,9 +487,9 @@ TEST_F(ClangRenameTest, RenameClassWithNamespaceWithInlineMembers) {
)";
std::string Expected = R"(
namespace ns {
- class ns::New {
+ class New {
public:
- ns::New() {}
+ New() {}
~New() {}
New* next() { return next_; }
@@ -504,8 +503,6 @@ TEST_F(ClangRenameTest, RenameClassWithNamespaceWithInlineMembers) {
CompareSnippets(Expected, After);
}
-// FIXME: no prefix qualifiers being added to the class definition and
-// constructor.
TEST_F(ClangRenameTest, RenameClassWithNamespaceWithOutOfInlineMembers) {
std::string Before = R"(
namespace ns {
@@ -527,9 +524,9 @@ TEST_F(ClangRenameTest, RenameClassWithNamespaceWithOutOfInlineMembers) {
)";
std::string Expected = R"(
namespace ns {
- class ns::New {
+ class New {
public:
- ns::New();
+ New();
~New();
New* next();
@@ -538,7 +535,7 @@ TEST_F(ClangRenameTest, RenameClassWithNamespaceWithOutOfInlineMembers) {
New* next_;
};
- New::ns::New() {}
+ New::New() {}
New::~New() {}
New* New::next() { return next_; }
} // namespace ns
@@ -547,12 +544,12 @@ TEST_F(ClangRenameTest, RenameClassWithNamespaceWithOutOfInlineMembers) {
CompareSnippets(Expected, After);
}
-// FIXME: no prefix qualifiers being added to the definition.
TEST_F(ClangRenameTest, RenameClassInInheritedConstructor) {
// `using Base::Base;` will generate an implicit constructor containing usage
// of `::ns::Old` which should not be matched.
std::string Before = R"(
namespace ns {
+ class Old;
class Old {
int x;
};
@@ -574,7 +571,8 @@ TEST_F(ClangRenameTest, RenameClassInInheritedConstructor) {
})";
std::string Expected = R"(
namespace ns {
- class ns::New {
+ class New;
+ class New {
int x;
};
class Base {
@@ -615,7 +613,7 @@ TEST_F(ClangRenameTest, DontRenameReferencesInImplicitFunction) {
)";
std::string Expected = R"(
namespace ns {
- class ::new_ns::New {
+ class New {
};
} // namespace ns
struct S {
@@ -632,7 +630,6 @@ TEST_F(ClangRenameTest, DontRenameReferencesInImplicitFunction) {
CompareSnippets(Expected, After);
}
-// FIXME: no prefix qualifiers being adding to the definition.
TEST_F(ClangRenameTest, ReferencesInLambdaFunctionParameters) {
std::string Before = R"(
template <class T>
@@ -669,7 +666,7 @@ TEST_F(ClangRenameTest, ReferencesInLambdaFunctionParameters) {
};
namespace ns {
- class ::new_ns::New {};
+ class New {};
void f() {
function<void(::new_ns::New)> func;
}
@@ -678,6 +675,124 @@ TEST_F(ClangRenameTest, ReferencesInLambdaFunctionParameters) {
CompareSnippets(Expected, After);
}
+TEST_F(ClangRenameTest, DontChangeIfSameName) {
+ std::string Before = R"(
+ namespace foo {
+ class Old {
+ public:
+ static void foo() {}
+ };
+ }
+
+ void f(foo::Old * x) {
+ foo::Old::foo() ;
+ }
+ using foo::Old;)";
+ std::string Expected = R"(
+ namespace foo {
+ class Old {
+ public:
+ static void foo() {}
+ };
+ }
+
+ void f(foo::Old * x) {
+ foo::Old::foo() ;
+ }
+ using foo::Old;)";
+ std::string After = runClangRenameOnCode(Before, "foo::Old", "foo::Old");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(ClangRenameTest, ChangeIfNewNameWithLeadingDotDot) {
+ std::string Before = R"(
+ namespace foo {
+ class Old {
+ public:
+ static void foo() {}
+ };
+ }
+
+ void f(foo::Old * x) {
+ foo::Old::foo() ;
+ }
+ using foo::Old;)";
+ std::string Expected = R"(
+ namespace foo {
+ class Old {
+ public:
+ static void foo() {}
+ };
+ }
+
+ void f(::foo::Old * x) {
+ ::foo::Old::foo() ;
+ }
+ using ::foo::Old;)";
+ std::string After = runClangRenameOnCode(Before, "foo::Old", "::foo::Old");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(ClangRenameTest, ChangeIfSameNameWithLeadingDotDot) {
+ std::string Before = R"(
+ namespace foo {
+ class Old {
+ public:
+ static void foo() {}
+ };
+ }
+
+ void f(foo::Old * x) {
+ foo::Old::foo() ;
+ }
+ using foo::Old;)";
+ std::string Expected = R"(
+ namespace foo {
+ class Old {
+ public:
+ static void foo() {}
+ };
+ }
+
+ void f(::foo::Old * x) {
+ ::foo::Old::foo() ;
+ }
+ using ::foo::Old;)";
+ std::string After = runClangRenameOnCode(Before, "::foo::Old", "::foo::Old");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameClassTest, UsingAlias) {
+ std::string Before = R"(
+ namespace a { struct A {}; }
+
+ namespace foo {
+ using Alias = a::A;
+ Alias a;
+ })";
+ std::string Expected = R"(
+ namespace a { struct B {}; }
+
+ namespace foo {
+ using Alias = b::B;
+ Alias a;
+ })";
+ std::string After = runClangRenameOnCode(Before, "a::A", "b::B");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(ClangRenameTest, NestedTemplates) {
+ std::string Before = R"(
+ namespace a { template <typename T> struct A {}; }
+ a::A<a::A<int>> foo;)";
+ std::string Expected = R"(
+ namespace a { template <typename T> struct B {}; }
+ b::B<b::B<int>> foo;)";
+ std::string After = runClangRenameOnCode(Before, "a::A", "b::B");
+ CompareSnippets(Expected, After);
+}
+
+
} // anonymous namespace
} // namespace test
} // namespace clang_rename
diff --git a/unittests/Rename/RenameEnumTest.cpp b/unittests/Rename/RenameEnumTest.cpp
new file mode 100644
index 0000000000000..55dcd11ac4128
--- /dev/null
+++ b/unittests/Rename/RenameEnumTest.cpp
@@ -0,0 +1,189 @@
+#include "ClangRenameTest.h"
+
+namespace clang {
+namespace clang_rename {
+namespace test {
+namespace {
+
+class RenameEnumTest : public ClangRenameTest {
+public:
+ RenameEnumTest() {
+ AppendToHeader(R"(
+ #define MACRO(x) x
+ namespace a {
+ enum A1 { Red };
+ enum class A2 { Blue };
+ struct C {
+ enum NestedEnum { White };
+ enum class NestedScopedEnum { Black };
+ };
+ namespace d {
+ enum A3 { Orange };
+ } // namespace d
+ enum A4 { Pink };
+ } // namespace a
+ enum A5 { Green };)");
+ }
+};
+
+INSTANTIATE_TEST_CASE_P(
+ RenameEnumTests, RenameEnumTest,
+ testing::ValuesIn(std::vector<Case>({
+ {"void f(a::A2 arg) { a::A2 t = a::A2::Blue; }",
+ "void f(b::B2 arg) { b::B2 t = b::B2::Blue; }", "a::A2", "b::B2"},
+ {"void f() { a::A1* t1; }", "void f() { b::B1* t1; }", "a::A1",
+ "b::B1"},
+ {"void f() { a::A2* t1; }", "void f() { b::B2* t1; }", "a::A2",
+ "b::B2"},
+ {"void f() { enum a::A2 t = a::A2::Blue; }",
+ "void f() { enum b::B2 t = b::B2::Blue; }", "a::A2", "b::B2"},
+ {"void f() { enum a::A2 t = a::A2::Blue; }",
+ "void f() { enum b::B2 t = b::B2::Blue; }", "a::A2", "b::B2"},
+
+ {"void f() { a::A1 t = a::Red; }", "void f() { b::B1 t = b::B1::Red; }",
+ "a::A1", "b::B1"},
+ {"void f() { a::A1 t = a::A1::Red; }",
+ "void f() { b::B1 t = b::B1::Red; }", "a::A1", "b::B1"},
+ {"void f() { auto t = a::Red; }", "void f() { auto t = b::B1::Red; }",
+ "a::A1", "b::B1"},
+ {"namespace b { void f() { a::A1 t = a::Red; } }",
+ "namespace b { void f() { B1 t = B1::Red; } }", "a::A1", "b::B1"},
+ {"void f() { a::d::A3 t = a::d::Orange; }",
+ "void f() { a::b::B3 t = a::b::B3::Orange; }", "a::d::A3", "a::b::B3"},
+ {"namespace a { void f() { a::d::A3 t = a::d::Orange; } }",
+ "namespace a { void f() { b::B3 t = b::B3::Orange; } }", "a::d::A3",
+ "a::b::B3"},
+ {"void f() { A5 t = Green; }", "void f() { B5 t = Green; }", "A5",
+ "B5"},
+ // FIXME: the new namespace qualifier should be added to the unscoped
+ // enum constant.
+ {"namespace a { void f() { auto t = Green; } }",
+ "namespace a { void f() { auto t = Green; } }", "a::A1", "b::B1"},
+
+ // namespace qualifiers
+ {"namespace a { void f(A1 a1) {} }",
+ "namespace a { void f(b::B1 a1) {} }", "a::A1", "b::B1"},
+ {"namespace a { void f(A2 a2) {} }",
+ "namespace a { void f(b::B2 a2) {} }", "a::A2", "b::B2"},
+ {"namespace b { void f(a::A1 a1) {} }",
+ "namespace b { void f(B1 a1) {} }", "a::A1", "b::B1"},
+ {"namespace b { void f(a::A2 a2) {} }",
+ "namespace b { void f(B2 a2) {} }", "a::A2", "b::B2"},
+
+ // nested enums
+ {"void f() { a::C::NestedEnum t = a::C::White; }",
+ "void f() { a::C::NewNestedEnum t = a::C::NewNestedEnum::White; }",
+ "a::C::NestedEnum", "a::C::NewNestedEnum"},
+ {"void f() { a::C::NestedScopedEnum t = a::C::NestedScopedEnum::Black; "
+ "}",
+ "void f() { a::C::NewNestedScopedEnum t = "
+ "a::C::NewNestedScopedEnum::Black; }",
+ "a::C::NestedScopedEnum", "a::C::NewNestedScopedEnum"},
+
+ // macros
+ {"void f(MACRO(a::A1) a1) {}", "void f(MACRO(b::B1) a1) {}", "a::A1",
+ "b::B1"},
+ {"void f(MACRO(a::A2) a2) {}", "void f(MACRO(b::B2) a2) {}", "a::A2",
+ "b::B2"},
+ {"#define FOO(T, t) T t\nvoid f() { FOO(a::A1, a1); }",
+ "#define FOO(T, t) T t\nvoid f() { FOO(b::B1, a1); }", "a::A1",
+ "b::B1"},
+ {"#define FOO(T, t) T t\nvoid f() { FOO(a::A2, a2); }",
+ "#define FOO(T, t) T t\nvoid f() { FOO(b::B2, a2); }", "a::A2",
+ "b::B2"},
+ {"#define FOO(n) a::A1 n\nvoid f() { FOO(a1); FOO(a2); }",
+ "#define FOO(n) b::B1 n\nvoid f() { FOO(a1); FOO(a2); }", "a::A1",
+ "b::B1"},
+
+ // using and type alias
+ {"using a::A1; A1 gA;", "using b::B1; b::B1 gA;", "a::A1", "b::B1"},
+ {"using a::A2; A2 gA;", "using b::B2; b::B2 gA;", "a::A2", "b::B2"},
+ {"struct S { using T = a::A1; T a_; };",
+ "struct S { using T = b::B1; T a_; };", "a::A1", "b::B1"},
+ {"using T = a::A1; T gA;", "using T = b::B1; T gA;", "a::A1", "b::B1"},
+ {"using T = a::A2; T gA;", "using T = b::B2; T gA;", "a::A2", "b::B2"},
+ {"typedef a::A1 T; T gA;", "typedef b::B1 T; T gA;", "a::A1", "b::B1"},
+ {"typedef a::A2 T; T gA;", "typedef b::B2 T; T gA;", "a::A2", "b::B2"},
+ {"typedef MACRO(a::A1) T; T gA;", "typedef MACRO(b::B1) T; T gA;",
+ "a::A1", "b::B1"},
+
+ // templates
+ {"template<typename T> struct Foo { T t; }; void f() { Foo<a::A1> "
+ "foo1; }",
+ "template<typename T> struct Foo { T t; }; void f() { Foo<b::B1> "
+ "foo1; }",
+ "a::A1", "b::B1"},
+ {"template<typename T> struct Foo { T t; }; void f() { Foo<a::A2> "
+ "foo2; }",
+ "template<typename T> struct Foo { T t; }; void f() { Foo<b::B2> "
+ "foo2; }",
+ "a::A2", "b::B2"},
+ {"template<typename T> struct Foo { a::A1 a1; };",
+ "template<typename T> struct Foo { b::B1 a1; };", "a::A1", "b::B1"},
+ {"template<typename T> struct Foo { a::A2 a2; };",
+ "template<typename T> struct Foo { b::B2 a2; };", "a::A2", "b::B2"},
+ {"template<typename T> int f() { return 1; } template<> int f<a::A1>() "
+ "{ return 2; } int g() { return f<a::A1>(); }",
+ "template<typename T> int f() { return 1; } template<> int f<b::B1>() "
+ "{ return 2; } int g() { return f<b::B1>(); }",
+ "a::A1", "b::B1"},
+ {"template<typename T> int f() { return 1; } template<> int f<a::A2>() "
+ "{ return 2; } int g() { return f<a::A2>(); }",
+ "template<typename T> int f() { return 1; } template<> int f<b::B2>() "
+ "{ return 2; } int g() { return f<b::B2>(); }",
+ "a::A2", "b::B2"},
+ {"struct Foo { template <typename T> T foo(); }; void g() { Foo f; "
+ "f.foo<a::A1>(); }",
+ "struct Foo { template <typename T> T foo(); }; void g() { Foo f; "
+ "f.foo<b::B1>(); }",
+ "a::A1", "b::B1"},
+ {"struct Foo { template <typename T> T foo(); }; void g() { Foo f; "
+ "f.foo<a::A2>(); }",
+ "struct Foo { template <typename T> T foo(); }; void g() { Foo f; "
+ "f.foo<b::B2>(); }",
+ "a::A2", "b::B2"},
+ })), );
+
+TEST_P(RenameEnumTest, RenameEnums) {
+ auto Param = GetParam();
+ assert(!Param.OldName.empty());
+ assert(!Param.NewName.empty());
+ std::string Actual =
+ runClangRenameOnCode(Param.Before, Param.OldName, Param.NewName);
+ CompareSnippets(Param.After, Actual);
+}
+
+TEST_F(RenameEnumTest, RenameEnumDecl) {
+ std::string Before = R"(
+ namespace ns {
+ enum Old1 { Blue };
+ }
+ )";
+ std::string Expected = R"(
+ namespace ns {
+ enum New1 { Blue };
+ }
+ )";
+ std::string After = runClangRenameOnCode(Before, "ns::Old1", "ns::New1");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameEnumTest, RenameScopedEnumDecl) {
+ std::string Before = R"(
+ namespace ns {
+ enum class Old1 { Blue };
+ }
+ )";
+ std::string Expected = R"(
+ namespace ns {
+ enum class New1 { Blue };
+ }
+ )";
+ std::string After = runClangRenameOnCode(Before, "ns::Old1", "ns::New1");
+ CompareSnippets(Expected, After);
+}
+
+} // anonymous namespace
+} // namespace test
+} // namespace clang_rename
+} // namesdpace clang
diff --git a/unittests/Rename/RenameFunctionTest.cpp b/unittests/Rename/RenameFunctionTest.cpp
new file mode 100644
index 0000000000000..b27bbe273af8f
--- /dev/null
+++ b/unittests/Rename/RenameFunctionTest.cpp
@@ -0,0 +1,574 @@
+//===-- RenameFunctionTest.cpp - unit tests for renaming functions --------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangRenameTest.h"
+
+namespace clang {
+namespace clang_rename {
+namespace test {
+namespace {
+
+class RenameFunctionTest : public ClangRenameTest {
+public:
+ RenameFunctionTest() {
+ AppendToHeader(R"(
+ struct A {
+ static bool Foo();
+ static bool Spam();
+ };
+ struct B {
+ static void Same();
+ static bool Foo();
+ static int Eric(int x);
+ };
+ void Same(int x);
+ int Eric(int x);
+ namespace base {
+ void Same();
+ void ToNanoSeconds();
+ void ToInt64NanoSeconds();
+ })");
+ }
+};
+
+TEST_F(RenameFunctionTest, RefactorsAFoo) {
+ std::string Before = R"(
+ void f() {
+ A::Foo();
+ ::A::Foo();
+ })";
+ std::string Expected = R"(
+ void f() {
+ A::Bar();
+ ::A::Bar();
+ })";
+
+ std::string After = runClangRenameOnCode(Before, "A::Foo", "A::Bar");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameFunctionTest, RefactorsNonCallingAFoo) {
+ std::string Before = R"(
+ bool g(bool (*func)()) {
+ return func();
+ }
+ void f() {
+ auto *ref1 = A::Foo;
+ auto *ref2 = ::A::Foo;
+ g(A::Foo);
+ })";
+ std::string Expected = R"(
+ bool g(bool (*func)()) {
+ return func();
+ }
+ void f() {
+ auto *ref1 = A::Bar;
+ auto *ref2 = ::A::Bar;
+ g(A::Bar);
+ })";
+ std::string After = runClangRenameOnCode(Before, "A::Foo", "A::Bar");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameFunctionTest, RefactorsEric) {
+ std::string Before = R"(
+ void f() {
+ if (Eric(3)==4) ::Eric(2);
+ })";
+ std::string Expected = R"(
+ void f() {
+ if (Larry(3)==4) ::Larry(2);
+ })";
+ std::string After = runClangRenameOnCode(Before, "Eric", "Larry");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameFunctionTest, RefactorsNonCallingEric) {
+ std::string Before = R"(
+ int g(int (*func)(int)) {
+ return func(1);
+ }
+ void f() {
+ auto *ref = ::Eric;
+ g(Eric);
+ })";
+ std::string Expected = R"(
+ int g(int (*func)(int)) {
+ return func(1);
+ }
+ void f() {
+ auto *ref = ::Larry;
+ g(Larry);
+ })";
+ std::string After = runClangRenameOnCode(Before, "Eric", "Larry");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameFunctionTest, DoesNotRefactorBFoo) {
+ std::string Before = R"(
+ void f() {
+ B::Foo();
+ })";
+ std::string After = runClangRenameOnCode(Before, "A::Foo", "A::Bar");
+ CompareSnippets(Before, After);
+}
+
+TEST_F(RenameFunctionTest, DoesNotRefactorBEric) {
+ std::string Before = R"(
+ void f() {
+ B::Eric(2);
+ })";
+ std::string After = runClangRenameOnCode(Before, "Eric", "Larry");
+ CompareSnippets(Before, After);
+}
+
+TEST_F(RenameFunctionTest, DoesNotRefactorCEric) {
+ std::string Before = R"(
+ namespace C { int Eric(int x); }
+ void f() {
+ if (C::Eric(3)==4) ::C::Eric(2);
+ })";
+ std::string Expected = R"(
+ namespace C { int Eric(int x); }
+ void f() {
+ if (C::Eric(3)==4) ::C::Eric(2);
+ })";
+ std::string After = runClangRenameOnCode(Before, "Eric", "Larry");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameFunctionTest, DoesNotRefactorEricInNamespaceC) {
+ std::string Before = R"(
+ namespace C {
+ int Eric(int x);
+ void f() {
+ if (Eric(3)==4) Eric(2);
+ }
+ } // namespace C)";
+ std::string After = runClangRenameOnCode(Before, "Eric", "Larry");
+ CompareSnippets(Before, After);
+}
+
+TEST_F(RenameFunctionTest, NamespaceQualified) {
+ std::string Before = R"(
+ void f() {
+ base::ToNanoSeconds();
+ ::base::ToNanoSeconds();
+ }
+ void g() {
+ using base::ToNanoSeconds;
+ base::ToNanoSeconds();
+ ::base::ToNanoSeconds();
+ ToNanoSeconds();
+ }
+ namespace foo {
+ namespace base {
+ void ToNanoSeconds();
+ void f() {
+ base::ToNanoSeconds();
+ }
+ }
+ void f() {
+ ::base::ToNanoSeconds();
+ }
+ })";
+ std::string Expected = R"(
+ void f() {
+ base::ToInt64NanoSeconds();
+ ::base::ToInt64NanoSeconds();
+ }
+ void g() {
+ using base::ToInt64NanoSeconds;
+ base::ToInt64NanoSeconds();
+ ::base::ToInt64NanoSeconds();
+ base::ToInt64NanoSeconds();
+ }
+ namespace foo {
+ namespace base {
+ void ToNanoSeconds();
+ void f() {
+ base::ToNanoSeconds();
+ }
+ }
+ void f() {
+ ::base::ToInt64NanoSeconds();
+ }
+ })";
+ std::string After = runClangRenameOnCode(Before, "base::ToNanoSeconds",
+ "base::ToInt64NanoSeconds");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameFunctionTest, RenameFunctionDecls) {
+ std::string Before = R"(
+ namespace na {
+ void X();
+ void X() {}
+ })";
+ std::string Expected = R"(
+ namespace na {
+ void Y();
+ void Y() {}
+ })";
+ std::string After = runClangRenameOnCode(Before, "na::X", "na::Y");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameFunctionTest, RenameTemplateFunctions) {
+ std::string Before = R"(
+ namespace na {
+ template<typename T> T X();
+ }
+ namespace na { void f() { X<int>(); } }
+ namespace nb { void g() { na::X <int>(); } }
+ )";
+ std::string Expected = R"(
+ namespace na {
+ template<typename T> T Y();
+ }
+ namespace na { void f() { nb::Y<int>(); } }
+ namespace nb { void g() { Y<int>(); } }
+ )";
+ std::string After = runClangRenameOnCode(Before, "na::X", "nb::Y");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameFunctionTest, RenameOutOfLineFunctionDecls) {
+ std::string Before = R"(
+ namespace na {
+ void X();
+ }
+ void na::X() {}
+ )";
+ std::string Expected = R"(
+ namespace na {
+ void Y();
+ }
+ void na::Y() {}
+ )";
+ std::string After = runClangRenameOnCode(Before, "na::X", "na::Y");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameFunctionTest, NewNamespaceWithoutLeadingDotDot) {
+ std::string Before = R"(
+ namespace old_ns {
+ void X();
+ void X() {}
+ }
+ // Assume that the reference is in another file.
+ void f() { old_ns::X(); }
+ namespace old_ns { void g() { X(); } }
+ namespace new_ns { void h() { ::old_ns::X(); } }
+ )";
+ std::string Expected = R"(
+ namespace old_ns {
+ void Y();
+ void Y() {}
+ }
+ // Assume that the reference is in another file.
+ void f() { new_ns::Y(); }
+ namespace old_ns { void g() { new_ns::Y(); } }
+ namespace new_ns { void h() { Y(); } }
+ )";
+ std::string After = runClangRenameOnCode(Before, "::old_ns::X", "new_ns::Y");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameFunctionTest, NewNamespaceWithLeadingDotDot) {
+ std::string Before = R"(
+ namespace old_ns {
+ void X();
+ void X() {}
+ }
+ // Assume that the reference is in another file.
+ void f() { old_ns::X(); }
+ namespace old_ns { void g() { X(); } }
+ namespace new_ns { void h() { ::old_ns::X(); } }
+ )";
+ std::string Expected = R"(
+ namespace old_ns {
+ void Y();
+ void Y() {}
+ }
+ // Assume that the reference is in another file.
+ void f() { ::new_ns::Y(); }
+ namespace old_ns { void g() { ::new_ns::Y(); } }
+ namespace new_ns { void h() { Y(); } }
+ )";
+ std::string After =
+ runClangRenameOnCode(Before, "::old_ns::X", "::new_ns::Y");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameFunctionTest, DontRenameSymbolsDefinedInAnonymousNamespace) {
+ std::string Before = R"(
+ namespace old_ns {
+ class X {};
+ namespace {
+ void X();
+ void X() {}
+ void f() { X(); }
+ }
+ }
+ )";
+ std::string Expected = R"(
+ namespace old_ns {
+ class Y {};
+ namespace {
+ void X();
+ void X() {}
+ void f() { X(); }
+ }
+ }
+ )";
+ std::string After =
+ runClangRenameOnCode(Before, "::old_ns::X", "::old_ns::Y");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameFunctionTest, NewNestedNamespace) {
+ std::string Before = R"(
+ namespace old_ns {
+ void X();
+ void X() {}
+ }
+ // Assume that the reference is in another file.
+ namespace old_ns {
+ void f() { X(); }
+ }
+ )";
+ std::string Expected = R"(
+ namespace old_ns {
+ void X();
+ void X() {}
+ }
+ // Assume that the reference is in another file.
+ namespace old_ns {
+ void f() { older_ns::X(); }
+ }
+ )";
+ std::string After =
+ runClangRenameOnCode(Before, "::old_ns::X", "::old_ns::older_ns::X");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameFunctionTest, MoveFromGlobalToNamespaceWithoutLeadingDotDot) {
+ std::string Before = R"(
+ void X();
+ void X() {}
+
+ // Assume that the reference is in another file.
+ namespace some_ns {
+ void f() { X(); }
+ }
+ )";
+ std::string Expected = R"(
+ void X();
+ void X() {}
+
+ // Assume that the reference is in another file.
+ namespace some_ns {
+ void f() { ns::X(); }
+ }
+ )";
+ std::string After =
+ runClangRenameOnCode(Before, "::X", "ns::X");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameFunctionTest, MoveFromGlobalToNamespaceWithLeadingDotDot) {
+ std::string Before = R"(
+ void Y() {}
+
+ // Assume that the reference is in another file.
+ namespace some_ns {
+ void f() { Y(); }
+ }
+ )";
+ std::string Expected = R"(
+ void Y() {}
+
+ // Assume that the reference is in another file.
+ namespace some_ns {
+ void f() { ::ns::Y(); }
+ }
+ )";
+ std::string After =
+ runClangRenameOnCode(Before, "::Y", "::ns::Y");
+ CompareSnippets(Expected, After);
+}
+
+// FIXME: the rename of overloaded operator is not fully supported yet.
+TEST_F(RenameFunctionTest, DISABLED_DoNotRenameOverloadedOperatorCalls) {
+ std::string Before = R"(
+ namespace old_ns {
+ class T { public: int x; };
+ bool operator==(const T& lhs, const T& rhs) {
+ return lhs.x == rhs.x;
+ }
+ } // namespace old_ns
+
+ // Assume that the reference is in another file.
+ bool f() {
+ auto eq = old_ns::operator==;
+ old_ns::T t1, t2;
+ old_ns::operator==(t1, t2);
+ return t1 == t2;
+ }
+ )";
+ std::string Expected = R"(
+ namespace old_ns {
+ class T { public: int x; };
+ bool operator==(const T& lhs, const T& rhs) {
+ return lhs.x == rhs.x;
+ }
+ } // namespace old_ns
+
+ // Assume that the reference is in another file.
+ bool f() {
+ auto eq = new_ns::operator==;
+ old_ns::T t1, t2;
+ new_ns::operator==(t1, t2);
+ return t1 == t2;
+ }
+ )";
+ std::string After =
+ runClangRenameOnCode(Before, "old_ns::operator==", "new_ns::operator==");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameFunctionTest, FunctionRefAsTemplate) {
+ std::string Before = R"(
+ void X();
+
+ // Assume that the reference is in another file.
+ namespace some_ns {
+ template <void (*Func)(void)>
+ class TIterator {};
+
+ template <void (*Func)(void)>
+ class T {
+ public:
+ typedef TIterator<Func> IterType;
+ using TI = TIterator<Func>;
+ void g() {
+ Func();
+ auto func = Func;
+ TIterator<Func> iter;
+ }
+ };
+
+
+ void f() { T<X> tx; tx.g(); }
+ } // namespace some_ns
+ )";
+ std::string Expected = R"(
+ void X();
+
+ // Assume that the reference is in another file.
+ namespace some_ns {
+ template <void (*Func)(void)>
+ class TIterator {};
+
+ template <void (*Func)(void)>
+ class T {
+ public:
+ typedef TIterator<Func> IterType;
+ using TI = TIterator<Func>;
+ void g() {
+ Func();
+ auto func = Func;
+ TIterator<Func> iter;
+ }
+ };
+
+
+ void f() { T<ns::X> tx; tx.g(); }
+ } // namespace some_ns
+ )";
+ std::string After = runClangRenameOnCode(Before, "::X", "ns::X");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameFunctionTest, RenameFunctionInUsingDecl) {
+ std::string Before = R"(
+ using base::ToNanoSeconds;
+ namespace old_ns {
+ using base::ToNanoSeconds;
+ void f() {
+ using base::ToNanoSeconds;
+ }
+ }
+ )";
+ std::string Expected = R"(
+ using base::ToInt64NanoSeconds;
+ namespace old_ns {
+ using base::ToInt64NanoSeconds;
+ void f() {
+ using base::ToInt64NanoSeconds;
+ }
+ }
+ )";
+ std::string After = runClangRenameOnCode(Before, "base::ToNanoSeconds",
+ "base::ToInt64NanoSeconds");
+ CompareSnippets(Expected, After);
+}
+
+// FIXME: Fix the complex the case where the symbol being renamed is located in
+// `std::function<decltype<renamed_symbol>>`.
+TEST_F(ClangRenameTest, DISABLED_ReferencesInLambdaFunctionParameters) {
+ std::string Before = R"(
+ template <class T>
+ class function;
+ template <class R, class... ArgTypes>
+ class function<R(ArgTypes...)> {
+ public:
+ template <typename Functor>
+ function(Functor f) {}
+
+ function() {}
+
+ R operator()(ArgTypes...) const {}
+ };
+
+ namespace ns {
+ void Old() {}
+ void f() {
+ function<decltype(Old)> func;
+ }
+ } // namespace ns)";
+ std::string Expected = R"(
+ template <class T>
+ class function;
+ template <class R, class... ArgTypes>
+ class function<R(ArgTypes...)> {
+ public:
+ template <typename Functor>
+ function(Functor f) {}
+
+ function() {}
+
+ R operator()(ArgTypes...) const {}
+ };
+
+ namespace ns {
+ void New() {}
+ void f() {
+ function<decltype(::new_ns::New)> func;
+ }
+ } // namespace ns)";
+ std::string After = runClangRenameOnCode(Before, "ns::Old", "::new_ns::New");
+ CompareSnippets(Expected, After);
+}
+
+} // anonymous namespace
+} // namespace test
+} // namespace clang_rename
+} // namesdpace clang
diff --git a/unittests/Rename/RenameMemberTest.cpp b/unittests/Rename/RenameMemberTest.cpp
new file mode 100644
index 0000000000000..463f7c70def72
--- /dev/null
+++ b/unittests/Rename/RenameMemberTest.cpp
@@ -0,0 +1,229 @@
+//===-- ClangMemberTests.cpp - unit tests for renaming class members ------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangRenameTest.h"
+
+namespace clang {
+namespace clang_rename {
+namespace test {
+namespace {
+
+class RenameMemberTest : public ClangRenameTest {
+public:
+ RenameMemberTest() {
+ AppendToHeader(R"(
+ struct NA {
+ void Foo();
+ void NotFoo();
+ static void SFoo();
+ static void SNotFoo();
+ int Moo;
+ };
+ struct A {
+ virtual void Foo();
+ void NotFoo();
+ static void SFoo();
+ static void SNotFoo();
+ int Moo;
+ int NotMoo;
+ static int SMoo;
+ };
+ struct B : public A {
+ void Foo() override;
+ };
+ template <typename T> struct TA {
+ T* Foo();
+ T* NotFoo();
+ static T* SFoo();
+ static T* NotSFoo();
+ };
+ template <typename T> struct TB : public TA<T> {};
+ namespace ns {
+ template <typename T> struct TA {
+ T* Foo();
+ T* NotFoo();
+ static T* SFoo();
+ static T* NotSFoo();
+ static int SMoo;
+ };
+ template <typename T> struct TB : public TA<T> {};
+ struct A {
+ void Foo();
+ void NotFoo();
+ static void SFoo();
+ static void SNotFoo();
+ };
+ struct B : public A {};
+ struct C {
+ template <class T>
+ void SFoo(const T& t) {}
+ template <class T>
+ void Foo() {}
+ };
+ })");
+ }
+};
+
+INSTANTIATE_TEST_CASE_P(
+ DISABLED_RenameTemplatedClassStaticVariableTest, RenameMemberTest,
+ testing::ValuesIn(std::vector<Case>({
+ // FIXME: support renaming static variables for template classes.
+ {"void f() { ns::TA<int>::SMoo; }",
+ "void f() { ns::TA<int>::SMeh; }", "ns::TA::SMoo", "ns::TA::SMeh"},
+ })), );
+
+INSTANTIATE_TEST_CASE_P(
+ RenameMemberTest, RenameMemberTest,
+ testing::ValuesIn(std::vector<Case>({
+ // Normal methods and fields.
+ {"void f() { A a; a.Foo(); }", "void f() { A a; a.Bar(); }", "A::Foo",
+ "A::Bar"},
+ {"void f() { ns::A a; a.Foo(); }", "void f() { ns::A a; a.Bar(); }",
+ "ns::A::Foo", "ns::A::Bar"},
+ {"void f() { A a; int x = a.Moo; }", "void f() { A a; int x = a.Meh; }",
+ "A::Moo", "A::Meh"},
+ {"void f() { B b; b.Foo(); }", "void f() { B b; b.Bar(); }", "B::Foo",
+ "B::Bar"},
+ {"void f() { ns::B b; b.Foo(); }", "void f() { ns::B b; b.Bar(); }",
+ "ns::A::Foo", "ns::A::Bar"},
+ {"void f() { B b; int x = b.Moo; }", "void f() { B b; int x = b.Meh; }",
+ "A::Moo", "A::Meh"},
+
+ // Static methods.
+ {"void f() { A::SFoo(); }", "void f() { A::SBar(); }", "A::SFoo",
+ "A::SBar"},
+ {"void f() { ns::A::SFoo(); }", "void f() { ns::A::SBar(); }",
+ "ns::A::SFoo", "ns::A::SBar"},
+ {"void f() { TA<int>::SFoo(); }", "void f() { TA<int>::SBar(); }",
+ "TA::SFoo", "TA::SBar"},
+ {"void f() { ns::TA<int>::SFoo(); }",
+ "void f() { ns::TA<int>::SBar(); }", "ns::TA::SFoo", "ns::TA::SBar"},
+
+ // Static variables.
+ {"void f() { A::SMoo; }",
+ "void f() { A::SMeh; }", "A::SMoo", "A::SMeh"},
+
+ // Templated methods.
+ {"void f() { TA<int> a; a.Foo(); }", "void f() { TA<int> a; a.Bar(); }",
+ "TA::Foo", "TA::Bar"},
+ {"void f() { ns::TA<int> a; a.Foo(); }",
+ "void f() { ns::TA<int> a; a.Bar(); }", "ns::TA::Foo", "ns::TA::Bar"},
+ {"void f() { TB<int> b; b.Foo(); }", "void f() { TB<int> b; b.Bar(); }",
+ "TA::Foo", "TA::Bar"},
+ {"void f() { ns::TB<int> b; b.Foo(); }",
+ "void f() { ns::TB<int> b; b.Bar(); }", "ns::TA::Foo", "ns::TA::Bar"},
+ {"void f() { ns::C c; int x; c.SFoo(x); }",
+ "void f() { ns::C c; int x; c.SBar(x); }", "ns::C::SFoo",
+ "ns::C::SBar"},
+ {"void f() { ns::C c; c.Foo<int>(); }",
+ "void f() { ns::C c; c.Bar<int>(); }", "ns::C::Foo", "ns::C::Bar"},
+
+ // Pointers to methods.
+ {"void f() { auto p = &A::Foo; }", "void f() { auto p = &A::Bar; }",
+ "A::Foo", "A::Bar"},
+ {"void f() { auto p = &A::SFoo; }", "void f() { auto p = &A::SBar; }",
+ "A::SFoo", "A::SBar"},
+ {"void f() { auto p = &B::Foo; }", "void f() { auto p = &B::Bar; }",
+ "B::Foo", "B::Bar"},
+ {"void f() { auto p = &ns::A::Foo; }",
+ "void f() { auto p = &ns::A::Bar; }", "ns::A::Foo", "ns::A::Bar"},
+ {"void f() { auto p = &ns::A::SFoo; }",
+ "void f() { auto p = &ns::A::SBar; }", "ns::A::SFoo", "ns::A::SBar"},
+ {"void f() { auto p = &ns::C::SFoo<int>; }",
+ "void f() { auto p = &ns::C::SBar<int>; }", "ns::C::SFoo",
+ "ns::C::SBar"},
+
+ // These methods are not declared or overrided in the subclass B, we
+ // have to use the qualified name with parent class A to identify them.
+ {"void f() { auto p = &ns::B::Foo; }",
+ "void f() { auto p = &ns::B::Bar; }", "ns::A::Foo", "ns::B::Bar"},
+ {"void f() { B::SFoo(); }", "void f() { B::SBar(); }", "A::SFoo",
+ "B::SBar"},
+ {"void f() { ns::B::SFoo(); }", "void f() { ns::B::SBar(); }",
+ "ns::A::SFoo", "ns::B::SBar"},
+ {"void f() { auto p = &B::SFoo; }", "void f() { auto p = &B::SBar; }",
+ "A::SFoo", "B::SBar"},
+ {"void f() { auto p = &ns::B::SFoo; }",
+ "void f() { auto p = &ns::B::SBar; }", "ns::A::SFoo", "ns::B::SBar"},
+ {"void f() { TB<int>::SFoo(); }", "void f() { TB<int>::SBar(); }",
+ "TA::SFoo", "TB::SBar"},
+ {"void f() { ns::TB<int>::SFoo(); }",
+ "void f() { ns::TB<int>::SBar(); }", "ns::TA::SFoo", "ns::TB::SBar"},
+ })), );
+
+TEST_P(RenameMemberTest, RenameMembers) {
+ auto Param = GetParam();
+ assert(!Param.OldName.empty());
+ assert(!Param.NewName.empty());
+ std::string Actual =
+ runClangRenameOnCode(Param.Before, Param.OldName, Param.NewName);
+ CompareSnippets(Param.After, Actual);
+}
+
+TEST_F(RenameMemberTest, RenameMemberInsideClassMethods) {
+ std::string Before = R"(
+ struct X {
+ int Moo;
+ void Baz() { Moo = 1; }
+ };)";
+ std::string Expected = R"(
+ struct X {
+ int Meh;
+ void Baz() { Meh = 1; }
+ };)";
+ std::string After = runClangRenameOnCode(Before, "X::Moo", "Y::Meh");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameMemberTest, RenameMethodInsideClassMethods) {
+ std::string Before = R"(
+ struct X {
+ void Foo() {}
+ void Baz() { Foo(); }
+ };)";
+ std::string Expected = R"(
+ struct X {
+ void Bar() {}
+ void Baz() { Bar(); }
+ };)";
+ std::string After = runClangRenameOnCode(Before, "X::Foo", "X::Bar");
+ CompareSnippets(Expected, After);
+}
+
+TEST_F(RenameMemberTest, RenameCtorInitializer) {
+ std::string Before = R"(
+ class X {
+ public:
+ X();
+ A a;
+ A a2;
+ B b;
+ };
+
+ X::X():a(), b() {}
+ )";
+ std::string Expected = R"(
+ class X {
+ public:
+ X();
+ A bar;
+ A a2;
+ B b;
+ };
+
+ X::X():bar(), b() {}
+ )";
+ std::string After = runClangRenameOnCode(Before, "X::a", "X::bar");
+ CompareSnippets(Expected, After);
+}
+
+} // anonymous namespace
+} // namespace test
+} // namespace clang_rename
+} // namesdpace clang
diff --git a/unittests/Rewrite/CMakeLists.txt b/unittests/Rewrite/CMakeLists.txt
index bee7ff6d55418..8edd9ba8f830e 100644
--- a/unittests/Rewrite/CMakeLists.txt
+++ b/unittests/Rewrite/CMakeLists.txt
@@ -6,5 +6,6 @@ add_clang_unittest(RewriteTests
RewriteBufferTest.cpp
)
target_link_libraries(RewriteTests
+ PRIVATE
clangRewrite
)
diff --git a/unittests/Sema/CMakeLists.txt b/unittests/Sema/CMakeLists.txt
index c25db814b7c29..16fae820dfe40 100644
--- a/unittests/Sema/CMakeLists.txt
+++ b/unittests/Sema/CMakeLists.txt
@@ -7,6 +7,7 @@ add_clang_unittest(SemaTests
)
target_link_libraries(SemaTests
+ PRIVATE
clangAST
clangBasic
clangFrontend
diff --git a/unittests/StaticAnalyzer/CMakeLists.txt b/unittests/StaticAnalyzer/CMakeLists.txt
index 4aa5efba77a2d..4ca0be50e5c26 100644
--- a/unittests/StaticAnalyzer/CMakeLists.txt
+++ b/unittests/StaticAnalyzer/CMakeLists.txt
@@ -7,6 +7,7 @@ add_clang_unittest(StaticAnalysisTests
)
target_link_libraries(StaticAnalysisTests
+ PRIVATE
clangBasic
clangAnalysis
clangStaticAnalyzerCore
diff --git a/unittests/Tooling/ASTSelectionTest.cpp b/unittests/Tooling/ASTSelectionTest.cpp
new file mode 100644
index 0000000000000..2f5df8f430096
--- /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 1359c7cabd1ea..557d1007ae2c6 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 0000000000000..b0c16d6cc5d27
--- /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 0000000000000..50727a55fc714
--- /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 edd5060ba0e53..dd48f3bf4595d 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 0000000000000..f0b6466fec468
--- /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 15900940c8872..41836f11ee29d 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 adfd3ef60f500..fb6a76ccadd01 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 b179f033d3579..891907a4d081a 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
diff --git a/unittests/libclang/CMakeLists.txt b/unittests/libclang/CMakeLists.txt
index 1cdc45e2d22a6..36f6089787d26 100644
--- a/unittests/libclang/CMakeLists.txt
+++ b/unittests/libclang/CMakeLists.txt
@@ -3,5 +3,6 @@ add_clang_unittest(libclangTests
)
target_link_libraries(libclangTests
+ PRIVATE
libclang
)