[clang-format] Fix aligning with linebreaks

Breaking a string literal or a function calls arguments with
AlignConsecutiveDeclarations or AlignConsecutiveAssignments did misalign
the continued line. E.g.:

void foo() {
  int myVar = 5;
  double x  = 3.14;
  auto str  = "Hello"
            "World";
}

or

void foo() {
  int    myVar = 5;
  double x = 3.14;
  auto   str = "Hello"
             "World";
}

Differential Revision: https://reviews.llvm.org/D98214
This commit is contained in:
Björn Schäpers 2021-03-10 22:08:07 +01:00
parent 8c6c357897
commit c5243c63cd
2 changed files with 146 additions and 9 deletions

View File

@ -278,6 +278,14 @@ AlignTokenSequence(unsigned Start, unsigned End, unsigned Column, F &&Matches,
// 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<unsigned, 16> ScopeStack;
@ -298,8 +306,12 @@ AlignTokenSequence(unsigned Start, unsigned End, unsigned Column, F &&Matches,
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 @@ AlignTokenSequence(unsigned Start, unsigned End, unsigned Column, F &&Matches,
// 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 @@ AlignTokenSequence(unsigned Start, unsigned End, unsigned Column, F &&Matches,
// 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 @@ static unsigned AlignTokens(
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;
}

View File

@ -14297,6 +14297,102 @@ TEST_F(FormatTest, AlignConsecutiveDeclarations) {
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;