diff options
Diffstat (limited to 'unittests')
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 ) |