diff --git a/clang/lib/Format/WhitespaceManager.cpp b/clang/lib/Format/WhitespaceManager.cpp --- a/clang/lib/Format/WhitespaceManager.cpp +++ b/clang/lib/Format/WhitespaceManager.cpp @@ -278,6 +278,14 @@ // double z); // In the above example, we need to take special care to ensure that // 'double z' is indented along with it's owning function 'b'. + // The same holds for calling a function: + // double a = foo(x); + // int b = bar(foo(y), + // foor(z)); + // Similar for broken string literals: + // double x = 3.14; + // auto s = "Hello" + // "World"; // Special handling is required for 'nested' ternary operators. SmallVector ScopeStack; @@ -298,8 +306,12 @@ ScopeStack.push_back(i); bool InsideNestedScope = ScopeStack.size() != 0; + bool ContinuedStringLiteral = i > Start && + Changes[i].Tok->is(tok::string_literal) && + Changes[i - 1].Tok->is(tok::string_literal); + bool SkipMatchCheck = InsideNestedScope || ContinuedStringLiteral; - if (Changes[i].NewlinesBefore > 0 && !InsideNestedScope) { + if (Changes[i].NewlinesBefore > 0 && !SkipMatchCheck) { Shift = 0; FoundMatchOnLine = false; } @@ -307,7 +319,7 @@ // If this is the first matching token to be aligned, remember by how many // spaces it has to be shifted, so the rest of the changes on the line are // shifted by the same amount - if (!FoundMatchOnLine && !InsideNestedScope && Matches(Changes[i])) { + if (!FoundMatchOnLine && !SkipMatchCheck && Matches(Changes[i])) { FoundMatchOnLine = true; Shift = Column - Changes[i].StartOfTokenColumn; Changes[i].Spaces += Shift; @@ -317,15 +329,41 @@ // as mentioned in the ScopeStack comment. if (InsideNestedScope && Changes[i].NewlinesBefore > 0) { unsigned ScopeStart = ScopeStack.back(); - if (Changes[ScopeStart - 1].Tok->is(TT_FunctionDeclarationName) || - (ScopeStart > Start + 1 && - Changes[ScopeStart - 2].Tok->is(TT_FunctionDeclarationName)) || - Changes[i].Tok->is(TT_ConditionalExpr) || - (Changes[i].Tok->Previous && - Changes[i].Tok->Previous->is(TT_ConditionalExpr))) + auto ShouldShiftBeAdded = [&] { + // Function declaration + if (Changes[ScopeStart - 1].Tok->is(TT_FunctionDeclarationName)) + return true; + + // Continued function declaration + if (ScopeStart > Start + 1 && + Changes[ScopeStart - 2].Tok->is(TT_FunctionDeclarationName)) + return true; + + // Continued function call + if (ScopeStart > Start + 1 && + Changes[ScopeStart - 2].Tok->is(tok::identifier) && + Changes[ScopeStart - 1].Tok->is(tok::l_paren)) + return true; + + // Ternary operator + if (Changes[i].Tok->is(TT_ConditionalExpr)) + return true; + + // Continued ternary operator + if (Changes[i].Tok->Previous && + Changes[i].Tok->Previous->is(TT_ConditionalExpr)) + return true; + + return false; + }; + + if (ShouldShiftBeAdded()) Changes[i].Spaces += Shift; } + if (ContinuedStringLiteral) + Changes[i].Spaces += Shift; + assert(Shift >= 0); Changes[i].StartOfTokenColumn += Shift; if (i + 1 != Changes.size()) @@ -434,7 +472,10 @@ AlignCurrentSequence(); // A new line starts, re-initialize line status tracking bools. - FoundMatchOnLine = false; + // Keep the match state if a string literal is continued on this line. + if (i == 0 || !Changes[i].Tok->is(tok::string_literal) || + !Changes[i - 1].Tok->is(tok::string_literal)) + FoundMatchOnLine = false; LineIsComment = true; } diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -14297,6 +14297,102 @@ Alignment); } +TEST_F(FormatTest, AlignWithLineBreaks) { + auto Style = getLLVMStyleWithColumns(120); + + EXPECT_EQ(Style.AlignConsecutiveAssignments, FormatStyle::ACS_None); + EXPECT_EQ(Style.AlignConsecutiveDeclarations, FormatStyle::ACS_None); + verifyFormat("void foo() {\n" + " int myVar = 5;\n" + " double x = 3.14;\n" + " auto str = \"Hello \"\n" + " \"World\";\n" + " auto s = \"Hello \"\n" + " \"Again\";\n" + "}", + Style); + + // clang-format off + verifyFormat("void foo() {\n" + " const int capacityBefore = Entries.capacity();\n" + " const auto newEntry = Entries.emplaceHint(std::piecewise_construct, std::forward_as_tuple(uniqueId),\n" + " std::forward_as_tuple(id, uniqueId, name, threadCreation));\n" + " const X newEntry2 = Entries.emplaceHint(std::piecewise_construct, std::forward_as_tuple(uniqueId),\n" + " std::forward_as_tuple(id, uniqueId, name, threadCreation));\n" + "}", + Style); + // clang-format on + + Style.AlignConsecutiveAssignments = FormatStyle::ACS_Consecutive; + verifyFormat("void foo() {\n" + " int myVar = 5;\n" + " double x = 3.14;\n" + " auto str = \"Hello \"\n" + " \"World\";\n" + " auto s = \"Hello \"\n" + " \"Again\";\n" + "}", + Style); + + // clang-format off + verifyFormat("void foo() {\n" + " const int capacityBefore = Entries.capacity();\n" + " const auto newEntry = Entries.emplaceHint(std::piecewise_construct, std::forward_as_tuple(uniqueId),\n" + " std::forward_as_tuple(id, uniqueId, name, threadCreation));\n" + " const X newEntry2 = Entries.emplaceHint(std::piecewise_construct, std::forward_as_tuple(uniqueId),\n" + " std::forward_as_tuple(id, uniqueId, name, threadCreation));\n" + "}", + Style); + // clang-format on + + Style.AlignConsecutiveAssignments = FormatStyle::ACS_None; + Style.AlignConsecutiveDeclarations = FormatStyle::ACS_Consecutive; + verifyFormat("void foo() {\n" + " int myVar = 5;\n" + " double x = 3.14;\n" + " auto str = \"Hello \"\n" + " \"World\";\n" + " auto s = \"Hello \"\n" + " \"Again\";\n" + "}", + Style); + + // clang-format off + verifyFormat("void foo() {\n" + " const int capacityBefore = Entries.capacity();\n" + " const auto newEntry = Entries.emplaceHint(std::piecewise_construct, std::forward_as_tuple(uniqueId),\n" + " std::forward_as_tuple(id, uniqueId, name, threadCreation));\n" + " const X newEntry2 = Entries.emplaceHint(std::piecewise_construct, std::forward_as_tuple(uniqueId),\n" + " std::forward_as_tuple(id, uniqueId, name, threadCreation));\n" + "}", + Style); + // clang-format on + + Style.AlignConsecutiveAssignments = FormatStyle::ACS_Consecutive; + Style.AlignConsecutiveDeclarations = FormatStyle::ACS_Consecutive; + + verifyFormat("void foo() {\n" + " int myVar = 5;\n" + " double x = 3.14;\n" + " auto str = \"Hello \"\n" + " \"World\";\n" + " auto s = \"Hello \"\n" + " \"Again\";\n" + "}", + Style); + + // clang-format off + verifyFormat("void foo() {\n" + " const int capacityBefore = Entries.capacity();\n" + " const auto newEntry = Entries.emplaceHint(std::piecewise_construct, std::forward_as_tuple(uniqueId),\n" + " std::forward_as_tuple(id, uniqueId, name, threadCreation));\n" + " const X newEntry2 = Entries.emplaceHint(std::piecewise_construct, std::forward_as_tuple(uniqueId),\n" + " std::forward_as_tuple(id, uniqueId, name, threadCreation));\n" + "}", + Style); + // clang-format on +} + TEST_F(FormatTest, LinuxBraceBreaking) { FormatStyle LinuxBraceStyle = getLLVMStyle(); LinuxBraceStyle.BreakBeforeBraces = FormatStyle::BS_Linux;