[clang-format] Adds a formatter for aligning arrays of structs

This adds a new formatter to arrange array of struct initializers into
neat columns.

Differential Revision: https://reviews.llvm.org/D101868
This commit is contained in:
Fred Grim 2021-06-13 16:36:42 +02:00 committed by Björn Schäpers
parent 5be3a1a064
commit 673c5ba584
11 changed files with 1056 additions and 5 deletions

View File

@ -204,6 +204,41 @@ the configuration (without a prefix: ``Auto``).
**AlignArrayOfStructures** (``ArrayInitializerAlignmentStyle``)
if not ``None``, when using initialization for an array of structs
aligns the fields into columns.
Possible values:
* ``AIAS_Left`` (in configuration: ``Left``)
Align array column and left justify the columns e.g.:
.. code-block:: c++
struct test demo[] =
{
{56, 23, "hello"},
{-1, 93463, "world"},
{7, 5, "!!" }
};
* ``AIAS_Right`` (in configuration: ``Right``)
Align array column and right justify the columns e.g.:
.. code-block:: c++
struct test demo[] =
{
{56, 23, "hello"},
{-1, 93463, "world"},
{ 7, 5, "!!"}
};
* ``AIAS_None`` (in configuration: ``None``)
Don't align array initializer columns.
**AlignConsecutiveAssignments** (``AlignConsecutiveStyle``)
Style of aligning consecutive assignments.

View File

@ -265,6 +265,9 @@ clang-format
- Makes ``PointerAligment: Right`` working with ``AlignConsecutiveDeclarations``.
(Fixes https://llvm.org/PR27353)
- Option ``AlignArrayOfStructure`` has been added to allow for ordering array-like
initializers.
libclang
--------

View File

@ -90,6 +90,35 @@ struct FormatStyle {
/// brackets.
BracketAlignmentStyle AlignAfterOpenBracket;
/// Different style for aligning array initializers.
enum ArrayInitializerAlignmentStyle {
/// Align array column and left justify the columns e.g.:
/// \code
/// struct test demo[] =
/// {
/// {56, 23, "hello"},
/// {-1, 93463, "world"},
/// {7, 5, "!!" }
/// };
/// \endcode
AIAS_Left,
/// Align array column and right justify the columns e.g.:
/// \code
/// struct test demo[] =
/// {
/// {56, 23, "hello"},
/// {-1, 93463, "world"},
/// { 7, 5, "!!"}
/// };
/// \endcode
AIAS_Right,
/// Don't align array initializer columns.
AIAS_None
};
/// if not ``None``, when using initialization for an array of structs
/// aligns the fields into columns.
ArrayInitializerAlignmentStyle AlignArrayOfStructures;
/// Styles for alignment of consecutive tokens. Tokens can be assignment signs
/// (see
/// ``AlignConsecutiveAssignments``), bitfield member separators (see
@ -3272,6 +3301,7 @@ struct FormatStyle {
bool operator==(const FormatStyle &R) const {
return AccessModifierOffset == R.AccessModifierOffset &&
AlignAfterOpenBracket == R.AlignAfterOpenBracket &&
AlignArrayOfStructures == R.AlignArrayOfStructures &&
AlignConsecutiveAssignments == R.AlignConsecutiveAssignments &&
AlignConsecutiveBitFields == R.AlignConsecutiveBitFields &&
AlignConsecutiveDeclarations == R.AlignConsecutiveDeclarations &&

View File

@ -143,6 +143,16 @@ template <> struct ScalarEnumerationTraits<FormatStyle::AlignConsecutiveStyle> {
}
};
template <>
struct ScalarEnumerationTraits<FormatStyle::ArrayInitializerAlignmentStyle> {
static void enumeration(IO &IO,
FormatStyle::ArrayInitializerAlignmentStyle &Value) {
IO.enumCase(Value, "None", FormatStyle::AIAS_None);
IO.enumCase(Value, "Left", FormatStyle::AIAS_Left);
IO.enumCase(Value, "Right", FormatStyle::AIAS_Right);
}
};
template <> struct ScalarEnumerationTraits<FormatStyle::ShortIfStyle> {
static void enumeration(IO &IO, FormatStyle::ShortIfStyle &Value) {
IO.enumCase(Value, "Never", FormatStyle::SIS_Never);
@ -507,6 +517,7 @@ template <> struct MappingTraits<FormatStyle> {
IO.mapOptional("AccessModifierOffset", Style.AccessModifierOffset);
IO.mapOptional("AlignAfterOpenBracket", Style.AlignAfterOpenBracket);
IO.mapOptional("AlignArrayOfStructures", Style.AlignArrayOfStructures);
IO.mapOptional("AlignConsecutiveMacros", Style.AlignConsecutiveMacros);
IO.mapOptional("AlignConsecutiveAssignments",
Style.AlignConsecutiveAssignments);
@ -943,6 +954,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
LLVMStyle.AccessModifierOffset = -2;
LLVMStyle.AlignEscapedNewlines = FormatStyle::ENAS_Right;
LLVMStyle.AlignAfterOpenBracket = FormatStyle::BAS_Align;
LLVMStyle.AlignArrayOfStructures = FormatStyle::AIAS_None;
LLVMStyle.AlignOperands = FormatStyle::OAS_Align;
LLVMStyle.AlignTrailingComments = true;
LLVMStyle.AlignConsecutiveAssignments = FormatStyle::ACS_None;

View File

@ -431,6 +431,15 @@ public:
/// The next token in the unwrapped line.
FormatToken *Next = nullptr;
/// The first token in set of column elements.
bool StartsColumn = false;
/// This notes the start of the line of an array initializer.
bool ArrayInitializerLineStart = false;
/// This starts an array initializer.
bool IsArrayInitializer = false;
/// If this token starts a block, this contains all the unwrapped lines
/// in it.
SmallVector<AnnotatedLine *, 1> Children;

View File

@ -729,6 +729,21 @@ private:
return false;
}
bool couldBeInStructArrayInitializer() const {
if (Contexts.size() < 2)
return false;
// We want to back up no more then 2 context levels i.e.
// . { { <-
const auto End = std::next(Contexts.rbegin(), 2);
auto Last = Contexts.rbegin();
unsigned Depth = 0;
for (; Last != End; ++Last) {
if (Last->ContextKind == tok::l_brace)
++Depth;
}
return Depth == 2 && Last->ContextKind != tok::l_brace;
}
bool parseBrace() {
if (CurrentToken) {
FormatToken *Left = CurrentToken->Previous;
@ -746,10 +761,17 @@ private:
Left->Previous->is(TT_JsTypeColon))
Contexts.back().IsExpression = false;
unsigned CommaCount = 0;
while (CurrentToken) {
if (CurrentToken->is(tok::r_brace)) {
Left->MatchingParen = CurrentToken;
CurrentToken->MatchingParen = Left;
if (Style.AlignArrayOfStructures != FormatStyle::AIAS_None) {
if (Left->ParentBracket == tok::l_brace &&
couldBeInStructArrayInitializer() && CommaCount > 0) {
Contexts.back().InStructArrayInitializer = true;
}
}
next();
return true;
}
@ -773,9 +795,11 @@ private:
Style.Language == FormatStyle::LK_JavaScript)
Left->setType(TT_DictLiteral);
}
if (CurrentToken->is(tok::comma) &&
Style.Language == FormatStyle::LK_JavaScript)
Left->setType(TT_DictLiteral);
if (CurrentToken->is(tok::comma)) {
if (Style.Language == FormatStyle::LK_JavaScript)
Left->setType(TT_DictLiteral);
++CommaCount;
}
if (!consumeToken())
return false;
}
@ -1339,6 +1363,12 @@ public:
return LT_ObjCMethodDecl;
}
for (const auto &ctx : Contexts) {
if (ctx.InStructArrayInitializer) {
return LT_ArrayOfStructInitializer;
}
}
return LT_Other;
}
@ -1414,6 +1444,7 @@ private:
bool IsForEachMacro = false;
bool InCpp11AttributeSpecifier = false;
bool InCSharpAttributeSpecifier = false;
bool InStructArrayInitializer = false;
};
/// Puts a new \c Context onto the stack \c Contexts for the lifetime
@ -1429,7 +1460,16 @@ private:
P.Contexts.back().IsExpression));
}
~ScopedContextCreator() { P.Contexts.pop_back(); }
~ScopedContextCreator() {
if (P.Style.AlignArrayOfStructures != FormatStyle::AIAS_None) {
if (P.Contexts.back().InStructArrayInitializer) {
P.Contexts.pop_back();
P.Contexts.back().InStructArrayInitializer = true;
return;
}
}
P.Contexts.pop_back();
}
};
void modifyContext(const FormatToken &Current) {
@ -2473,6 +2513,12 @@ void TokenAnnotator::calculateFormattingInformation(AnnotatedLine &Line) {
: Line.FirstStartColumn + Line.First->ColumnWidth;
FormatToken *Current = Line.First->Next;
bool InFunctionDecl = Line.MightBeFunctionDecl;
bool AlignArrayOfStructures =
(Style.AlignArrayOfStructures != FormatStyle::AIAS_None &&
Line.Type == LT_ArrayOfStructInitializer);
if (AlignArrayOfStructures)
calculateArrayInitializerColumnList(Line);
while (Current) {
if (isFunctionDeclarationName(*Current, Line))
Current->setType(TT_FunctionDeclarationName);
@ -2592,6 +2638,45 @@ void TokenAnnotator::calculateUnbreakableTailLengths(AnnotatedLine &Line) {
}
}
void TokenAnnotator::calculateArrayInitializerColumnList(AnnotatedLine &Line) {
if (Line.First == Line.Last) {
return;
}
auto *CurrentToken = Line.First;
CurrentToken->ArrayInitializerLineStart = true;
unsigned Depth = 0;
while (CurrentToken != nullptr && CurrentToken != Line.Last) {
if (CurrentToken->is(tok::l_brace)) {
CurrentToken->IsArrayInitializer = true;
if (CurrentToken->Next != nullptr)
CurrentToken->Next->MustBreakBefore = true;
CurrentToken =
calculateInitializerColumnList(Line, CurrentToken->Next, Depth + 1);
} else {
CurrentToken = CurrentToken->Next;
}
}
}
FormatToken *TokenAnnotator::calculateInitializerColumnList(
AnnotatedLine &Line, FormatToken *CurrentToken, unsigned Depth) {
while (CurrentToken != nullptr && CurrentToken != Line.Last) {
if (CurrentToken->is(tok::l_brace))
++Depth;
else if (CurrentToken->is(tok::r_brace))
--Depth;
if (Depth == 2 && CurrentToken->isOneOf(tok::l_brace, tok::comma)) {
CurrentToken = CurrentToken->Next;
if (CurrentToken == nullptr)
break;
CurrentToken->StartsColumn = true;
CurrentToken = CurrentToken->Previous;
}
CurrentToken = CurrentToken->Next;
}
return CurrentToken;
}
unsigned TokenAnnotator::splitPenalty(const AnnotatedLine &Line,
const FormatToken &Tok,
bool InFunctionDecl) {

View File

@ -31,7 +31,8 @@ enum LineType {
LT_ObjCProperty, // An @property line.
LT_Other,
LT_PreprocessorDirective,
LT_VirtualFunctionDecl
LT_VirtualFunctionDecl,
LT_ArrayOfStructInitializer,
};
class AnnotatedLine {
@ -189,6 +190,12 @@ private:
void calculateUnbreakableTailLengths(AnnotatedLine &Line);
void calculateArrayInitializerColumnList(AnnotatedLine &Line);
FormatToken *calculateInitializerColumnList(AnnotatedLine &Line,
FormatToken *CurrentToken,
unsigned Depth);
const FormatStyle &Style;
const AdditionalKeywords &Keywords;

View File

@ -13,6 +13,8 @@
#include "WhitespaceManager.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include <algorithm>
namespace clang {
namespace format {
@ -100,6 +102,7 @@ const tooling::Replacements &WhitespaceManager::generateReplacements() {
alignChainedConditionals();
alignTrailingComments();
alignEscapedNewlines();
alignArrayInitializers();
generateChanges();
return Replaces;
@ -952,6 +955,305 @@ void WhitespaceManager::alignEscapedNewlines(unsigned Start, unsigned End,
}
}
void WhitespaceManager::alignArrayInitializers() {
if (Style.AlignArrayOfStructures == FormatStyle::AIAS_None)
return;
for (unsigned ChangeIndex = 1U, ChangeEnd = Changes.size();
ChangeIndex < ChangeEnd; ++ChangeIndex) {
auto &C = Changes[ChangeIndex];
if (C.Tok->IsArrayInitializer) {
bool FoundComplete = false;
for (unsigned InsideIndex = ChangeIndex + 1; InsideIndex < ChangeEnd;
++InsideIndex) {
if (Changes[InsideIndex].Tok == C.Tok->MatchingParen) {
alignArrayInitializers(ChangeIndex, InsideIndex + 1);
ChangeIndex = InsideIndex + 1;
FoundComplete = true;
break;
}
}
if (!FoundComplete)
ChangeIndex = ChangeEnd;
}
}
}
void WhitespaceManager::alignArrayInitializers(unsigned Start, unsigned End) {
if (Style.AlignArrayOfStructures == FormatStyle::AIAS_Right)
alignArrayInitializersRightJustified(getCells(Start, End));
else if (Style.AlignArrayOfStructures == FormatStyle::AIAS_Left)
alignArrayInitializersLeftJustified(getCells(Start, End));
}
void WhitespaceManager::alignArrayInitializersRightJustified(
CellDescriptions &&CellDescs) {
auto &Cells = CellDescs.Cells;
// Now go through and fixup the spaces.
auto *CellIter = Cells.begin();
for (auto i = 0U; i < CellDescs.CellCount; i++, ++CellIter) {
unsigned NetWidth = 0U;
if (isSplitCell(*CellIter))
NetWidth = getNetWidth(Cells.begin(), CellIter, CellDescs.InitialSpaces);
auto CellWidth = getMaximumCellWidth(CellIter, NetWidth);
if (Changes[CellIter->Index].Tok->is(tok::r_brace)) {
// So in here we want to see if there is a brace that falls
// on a line that was split. If so on that line we make sure that
// the spaces in front of the brace are enough.
Changes[CellIter->Index].NewlinesBefore = 0;
Changes[CellIter->Index].Spaces = 0;
for (const auto *Next = CellIter->NextColumnElement; Next != nullptr;
Next = Next->NextColumnElement) {
Changes[Next->Index].Spaces = 0;
Changes[Next->Index].NewlinesBefore = 0;
}
// Unless the array is empty, we need the position of all the
// immediately adjacent cells
if (CellIter != Cells.begin()) {
auto ThisNetWidth =
getNetWidth(Cells.begin(), CellIter, CellDescs.InitialSpaces);
auto MaxNetWidth =
getMaximumNetWidth(Cells.begin(), CellIter, CellDescs.InitialSpaces,
CellDescs.CellCount);
if (ThisNetWidth < MaxNetWidth)
Changes[CellIter->Index].Spaces = (MaxNetWidth - ThisNetWidth);
auto RowCount = 1U;
auto Offset = std::distance(Cells.begin(), CellIter);
for (const auto *Next = CellIter->NextColumnElement; Next != nullptr;
Next = Next->NextColumnElement) {
auto *Start = (Cells.begin() + RowCount * CellDescs.CellCount);
auto *End = Start + Offset;
ThisNetWidth = getNetWidth(Start, End, CellDescs.InitialSpaces);
if (ThisNetWidth < MaxNetWidth)
Changes[Next->Index].Spaces = (MaxNetWidth - ThisNetWidth);
++RowCount;
}
}
} else {
auto ThisWidth =
calculateCellWidth(CellIter->Index, CellIter->EndIndex, true) +
NetWidth;
if (Changes[CellIter->Index].NewlinesBefore == 0) {
Changes[CellIter->Index].Spaces = (CellWidth - (ThisWidth + NetWidth));
Changes[CellIter->Index].Spaces += (i > 0) ? 1 : 0;
}
alignToStartOfCell(CellIter->Index, CellIter->EndIndex);
for (const auto *Next = CellIter->NextColumnElement; Next != nullptr;
Next = Next->NextColumnElement) {
ThisWidth =
calculateCellWidth(Next->Index, Next->EndIndex, true) + NetWidth;
if (Changes[Next->Index].NewlinesBefore == 0) {
Changes[Next->Index].Spaces = (CellWidth - ThisWidth);
Changes[Next->Index].Spaces += (i > 0) ? 1 : 0;
}
alignToStartOfCell(Next->Index, Next->EndIndex);
}
}
}
}
void WhitespaceManager::alignArrayInitializersLeftJustified(
CellDescriptions &&CellDescs) {
auto &Cells = CellDescs.Cells;
// Now go through and fixup the spaces.
auto *CellIter = Cells.begin();
// The first cell needs to be against the left brace.
if (Changes[CellIter->Index].NewlinesBefore == 0)
Changes[CellIter->Index].Spaces = 0;
else
Changes[CellIter->Index].Spaces = CellDescs.InitialSpaces;
++CellIter;
for (auto i = 1U; i < CellDescs.CellCount; i++, ++CellIter) {
unsigned NetWidth = 0U;
if (isSplitCell(*CellIter))
NetWidth = getNetWidth(Cells.begin(), CellIter, CellDescs.InitialSpaces);
auto MaxNetWidth = getMaximumNetWidth(
Cells.begin(), CellIter, CellDescs.InitialSpaces, CellDescs.CellCount);
auto ThisNetWidth =
getNetWidth(Cells.begin(), CellIter, CellDescs.InitialSpaces);
if (Changes[CellIter->Index].NewlinesBefore == 0) {
Changes[CellIter->Index].Spaces =
MaxNetWidth - ThisNetWidth +
(Changes[CellIter->Index].Tok->isNot(tok::r_brace) ? 1 : 0);
}
auto RowCount = 1U;
auto Offset = std::distance(Cells.begin(), CellIter);
for (const auto *Next = CellIter->NextColumnElement; Next != nullptr;
Next = Next->NextColumnElement) {
auto *Start = (Cells.begin() + RowCount * CellDescs.CellCount);
auto *End = Start + Offset;
auto ThisNetWidth = getNetWidth(Start, End, CellDescs.InitialSpaces);
if (Changes[Next->Index].NewlinesBefore == 0) {
Changes[Next->Index].Spaces =
MaxNetWidth - ThisNetWidth +
(Changes[Next->Index].Tok->isNot(tok::r_brace) ? 1 : 0);
}
++RowCount;
}
}
}
bool WhitespaceManager::isSplitCell(const CellDescription &Cell) {
if (Cell.HasSplit)
return true;
for (const auto *Next = Cell.NextColumnElement; Next != nullptr;
Next = Next->NextColumnElement) {
if (Next->HasSplit)
return true;
}
return false;
}
WhitespaceManager::CellDescriptions WhitespaceManager::getCells(unsigned Start,
unsigned End) {
unsigned Depth = 0;
unsigned Cell = 0;
unsigned CellCount = 0;
unsigned InitialSpaces = 0;
unsigned InitialTokenLength = 0;
unsigned EndSpaces = 0;
SmallVector<CellDescription> Cells;
const FormatToken *MatchingParen = nullptr;
for (unsigned i = Start; i < End; ++i) {
auto &C = Changes[i];
if (C.Tok->is(tok::l_brace))
++Depth;
else if (C.Tok->is(tok::r_brace))
--Depth;
if (Depth == 2) {
if (C.Tok->is(tok::l_brace)) {
Cell = 0;
MatchingParen = C.Tok->MatchingParen;
if (InitialSpaces == 0) {
InitialSpaces = C.Spaces + C.TokenLength;
InitialTokenLength = C.TokenLength;
auto j = i - 1;
for (; Changes[j].NewlinesBefore == 0 && j > Start; --j) {
InitialSpaces += Changes[j].Spaces + Changes[j].TokenLength;
InitialTokenLength += Changes[j].TokenLength;
}
if (C.NewlinesBefore == 0) {
InitialSpaces += Changes[j].Spaces + Changes[j].TokenLength;
InitialTokenLength += Changes[j].TokenLength;
}
}
} else if (C.Tok->is(tok::comma)) {
if (!Cells.empty())
Cells.back().EndIndex = i;
Cell++;
}
} else if (Depth == 1) {
if (C.Tok == MatchingParen) {
if (!Cells.empty())
Cells.back().EndIndex = i;
Cells.push_back(CellDescription{i, ++Cell, i + 1, false, nullptr});
CellCount = Cell + 1;
// Go to the next non-comment and ensure there is a break in front
const auto *NextNonComment = C.Tok->getNextNonComment();
while (NextNonComment->is(tok::comma))
NextNonComment = NextNonComment->getNextNonComment();
auto j = i;
while (Changes[j].Tok != NextNonComment && j < End)
j++;
if (j < End && Changes[j].NewlinesBefore == 0 &&
Changes[j].Tok->isNot(tok::r_brace)) {
Changes[j].NewlinesBefore = 1;
// Account for the added token lengths
Changes[j].Spaces = InitialSpaces - InitialTokenLength;
}
} else if (C.Tok->is(tok::comment)) {
// Trailing comments stay at a space past the last token
C.Spaces = Changes[i - 1].Tok->is(tok::comma) ? 1 : 2;
} else if (C.Tok->is(tok::l_brace)) {
// We need to make sure that the ending braces is aligned to the
// start of our initializer
auto j = i - 1;
for (; j > 0 && !Changes[j].Tok->ArrayInitializerLineStart; --j)
; // Nothing the loop does the work
EndSpaces = Changes[j].Spaces;
}
} else if (Depth == 0 && C.Tok->is(tok::r_brace)) {
C.NewlinesBefore = 1;
C.Spaces = EndSpaces;
}
if (C.Tok->StartsColumn) {
// This gets us past tokens that have been split over multiple
// lines
bool HasSplit = false;
if (Changes[i].NewlinesBefore > 0) {
// So if we split a line previously and the tail line + this token is
// less then the column limit we remove the split here and just put
// the column start at a space past the comma
auto j = i - 1;
if ((j - 1) > Start && Changes[j].Tok->is(tok::comma) &&
Changes[j - 1].NewlinesBefore > 0) {
--j;
auto LineLimit = Changes[j].Spaces + Changes[j].TokenLength;
if (LineLimit < Style.ColumnLimit) {
Changes[i].NewlinesBefore = 0;
Changes[i].Spaces = 1;
}
}
}
while (Changes[i].NewlinesBefore > 0 && Changes[i].Tok == C.Tok) {
Changes[i].Spaces = InitialSpaces;
++i;
HasSplit = true;
}
if (Changes[i].Tok != C.Tok)
--i;
Cells.push_back(CellDescription{i, Cell, i, HasSplit, nullptr});
}
}
return linkCells({Cells, CellCount, InitialSpaces});
}
unsigned WhitespaceManager::calculateCellWidth(unsigned Start, unsigned End,
bool WithSpaces) const {
unsigned CellWidth = 0;
for (auto i = Start; i < End; i++) {
if (Changes[i].NewlinesBefore > 0)
CellWidth = 0;
CellWidth += Changes[i].TokenLength;
CellWidth += (WithSpaces ? Changes[i].Spaces : 0);
}
return CellWidth;
}
void WhitespaceManager::alignToStartOfCell(unsigned Start, unsigned End) {
if ((End - Start) <= 1)
return;
// If the line is broken anywhere in there make sure everything
// is aligned to the parent
for (auto i = Start + 1; i < End; i++) {
if (Changes[i].NewlinesBefore > 0)
Changes[i].Spaces = Changes[Start].Spaces;
}
}
WhitespaceManager::CellDescriptions
WhitespaceManager::linkCells(CellDescriptions &&CellDesc) {
auto &Cells = CellDesc.Cells;
for (auto *CellIter = Cells.begin(); CellIter != Cells.end(); ++CellIter) {
if (CellIter->NextColumnElement == nullptr &&
((CellIter + 1) != Cells.end())) {
for (auto *NextIter = CellIter + 1; NextIter != Cells.end(); ++NextIter) {
if (NextIter->Cell == CellIter->Cell) {
CellIter->NextColumnElement = &(*NextIter);
break;
}
}
}
}
return std::move(CellDesc);
}
void WhitespaceManager::generateChanges() {
for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
const Change &C = Changes[i];

View File

@ -18,6 +18,8 @@
#include "TokenAnnotator.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Format/Format.h"
#include "llvm/ADT/SmallVector.h"
#include <algorithm>
#include <string>
#include <tuple>
@ -173,6 +175,28 @@ public:
};
private:
struct CellDescription {
unsigned Index = 0;
unsigned Cell = 0;
unsigned EndIndex = 0;
bool HasSplit = false;
CellDescription *NextColumnElement = nullptr;
constexpr bool operator==(const CellDescription &Other) const {
return Index == Other.Index && Cell == Other.Cell &&
EndIndex == Other.EndIndex;
}
constexpr bool operator!=(const CellDescription &Other) const {
return !(*this == Other);
}
};
struct CellDescriptions {
SmallVector<CellDescription> Cells;
unsigned CellCount = 0;
unsigned InitialSpaces = 0;
};
/// Calculate \c IsTrailingComment, \c TokenLength for the last tokens
/// or token parts in a line and \c PreviousEndOfTokenColumn and
/// \c EscapedNewlineColumn for the first tokens or token parts in a line.
@ -207,6 +231,89 @@ private:
/// the specified \p Column.
void alignEscapedNewlines(unsigned Start, unsigned End, unsigned Column);
/// Align Array Initializers over all \c Changes.
void alignArrayInitializers();
/// Align Array Initializers from change \p Start to change \p End at
/// the specified \p Column.
void alignArrayInitializers(unsigned Start, unsigned End);
/// Align Array Initializers being careful to right justify the columns
/// as described by \p CellDescs.
void alignArrayInitializersRightJustified(CellDescriptions &&CellDescs);
/// Align Array Initializers being careful to leftt justify the columns
/// as described by \p CellDescs.
void alignArrayInitializersLeftJustified(CellDescriptions &&CellDescs);
/// Calculate the cell width between two indexes.
unsigned calculateCellWidth(unsigned Start, unsigned End,
bool WithSpaces = false) const;
/// Get a set of fully specified CellDescriptions between \p Start and
/// \p End of the change list.
CellDescriptions getCells(unsigned Start, unsigned End);
/// Does this \p Cell contain a split element?
static bool isSplitCell(const CellDescription &Cell);
/// Get the width of the preceeding cells from \p Start to \p End.
template <typename I>
auto getNetWidth(const I &Start, const I &End, unsigned InitialSpaces) const {
auto NetWidth = InitialSpaces;
for (auto PrevIter = Start; PrevIter != End; ++PrevIter) {
// If we broke the line the initial spaces are already
// accounted for.
if (Changes[PrevIter->Index].NewlinesBefore > 0)
NetWidth = 0;
NetWidth +=
calculateCellWidth(PrevIter->Index, PrevIter->EndIndex, true) + 1;
}
return NetWidth;
}
/// Get the maximum width of a cell in a sequence of columns.
template <typename I>
unsigned getMaximumCellWidth(I CellIter, unsigned NetWidth) const {
unsigned CellWidth =
calculateCellWidth(CellIter->Index, CellIter->EndIndex, true);
if (Changes[CellIter->Index].NewlinesBefore == 0)
CellWidth += NetWidth;
for (const auto *Next = CellIter->NextColumnElement; Next != nullptr;
Next = Next->NextColumnElement) {
auto ThisWidth = calculateCellWidth(Next->Index, Next->EndIndex, true);
if (Changes[Next->Index].NewlinesBefore == 0)
ThisWidth += NetWidth;
CellWidth = std::max(CellWidth, ThisWidth);
}
return CellWidth;
}
/// Get The maximum width of all columns to a given cell.
template <typename I>
unsigned getMaximumNetWidth(const I &CellStart, const I &CellStop,
unsigned InitialSpaces,
unsigned CellCount) const {
auto MaxNetWidth = getNetWidth(CellStart, CellStop, InitialSpaces);
auto RowCount = 1U;
auto Offset = std::distance(CellStart, CellStop);
for (const auto *Next = CellStop->NextColumnElement; Next != nullptr;
Next = Next->NextColumnElement) {
auto Start = (CellStart + RowCount * CellCount);
auto End = Start + Offset;
MaxNetWidth =
std::max(MaxNetWidth, getNetWidth(Start, End, InitialSpaces));
++RowCount;
}
return MaxNetWidth;
}
/// Align a split cell with a newline to the first element in the cell.
void alignToStartOfCell(unsigned Start, unsigned End);
/// Link the Cell pointers in the list of Cells.
static CellDescriptions linkCells(CellDescriptions &&CellDesc);
/// Fill \c Replaces with the replacements for all effective changes.
void generateChanges();

View File

@ -0,0 +1,60 @@
// RUN: grep -Ev "// *[A-Z-]+:" %s \
// RUN: | clang-format -style="{BasedOnStyle: LLVM, AlignArrayOfStructures: Right}" %s \
// RUN: | FileCheck -strict-whitespace -check-prefix=CHECK1 %s
// RUN: grep -Ev "// *[A-Z-]+:" %s \
// RUN: | clang-format -style="{BasedOnStyle: LLVM, AlignArrayOfStructures: Left}" %s \
// RUN: | FileCheck -strict-whitespace -check-prefix=CHECK2 %s
struct test {
int a;
int b;
const char *c;
};
struct toast {
int a;
const char *b;
int c;
float d;
};
void f() {
struct test demo[] = {{56, 23, "hello"}, {-1, 93463, "world"}, {7, 5, "!!"}};
// CHECK1: {{^[[:space:]]{2}struct test demo\[\] = \{$}}
// CHECK1-NEXT: {{([[:space:]]{4})}}{56, 23, "hello"},
// CHECK1-NEXT: {{([[:space:]]{4})}}{-1, 93463, "world"},
// CHECK1-NEXT: {{([[:space:]]{4})}}{ 7, 5, "!!"}
// CHECK1-NEXT: {{^[[:space:]]{2}\};$}}
}
void g() {
struct toast demo[] = {
{56, "hello world I have some things to say", 30, 4.2},
{93463, "those things are really comments", 1, 3.1},
{7, "about a wide range of topics", 789, .112233}};
// CHECK1: {{^[[:space:]]{2}struct toast demo\[\] = \{$}}
// CHECK1-NEXT: {{([[:space:]]{4})}}{ 56, "hello world I have some things to say", 30, 4.2},
// CHECK1-NEXT: {{([[:space:]]{4})}}{93463, "those things are really comments", 1, 3.1},
// CHECK1-NEXT: {{([[:space:]]{4})}}{ 7, "about a wide range of topics", 789, .112233}
// CHECK1-NEXT: {{^[[:space:]]{2}\};$}}
}
void h() {
struct test demo[] = {{56, 23, "hello"}, {-1, 93463, "world"}, {7, 5, "!!"}};
// CHECK2: {{^[[:space:]]{2}struct test demo\[\] = \{$}}
// CHECK2-NEXT: {{([[:space:]]{4})}}{56, 23, "hello"},
// CHECK2-NEXT: {{([[:space:]]{4})}}{-1, 93463, "world"},
// CHECK2-NEXT: {{([[:space:]]{4})}}{7, 5, "!!" }
// CHECK2-NEXT: {{^[[:space:]]{2}\};$}}
}
void i() {
struct toast demo[] = {
{56, "hello world I have some things to say", 30, 4.2},
{93463, "those things are really comments", 1, 3.1},
{7, "about a wide range of topics", 789, .112233}};
// CHECK2: {{^[[:space:]]{2}struct toast demo\[\] = \{$}}
// CHECK2-NEXT: {{([[:space:]]{4})}}{56, "hello world I have some things to say", 30, 4.2 },
// CHECK2-NEXT: {{([[:space:]]{4})}}{93463, "those things are really comments", 1, 3.1 },
// CHECK2-NEXT: {{([[:space:]]{4})}}{7, "about a wide range of topics", 789, .112233}
// CHECK2-NEXT: {{^[[:space:]]{2}\};$}}
}

View File

@ -16602,6 +16602,407 @@ TEST_F(FormatTest, CatchExceptionReferenceBinding) {
getLLVMStyle());
}
TEST_F(FormatTest, CatchAlignArrayOfStructuresRightAlignment) {
auto Style = getLLVMStyle();
Style.AlignArrayOfStructures = FormatStyle::AIAS_Right;
Style.AlignConsecutiveAssignments =
FormatStyle::AlignConsecutiveStyle::ACS_Consecutive;
Style.AlignConsecutiveDeclarations =
FormatStyle::AlignConsecutiveStyle::ACS_Consecutive;
verifyFormat("struct test demo[] = {\n"
" {56, 23, \"hello\"},\n"
" {-1, 93463, \"world\"},\n"
" { 7, 5, \"!!\"}\n"
"};\n",
Style);
verifyFormat("struct test demo[] = {\n"
" {56, 23, \"hello\"}, // first line\n"
" {-1, 93463, \"world\"}, // second line\n"
" { 7, 5, \"!!\"} // third line\n"
"};\n",
Style);
verifyFormat("struct test demo[4] = {\n"
" { 56, 23, 21, \"oh\"}, // first line\n"
" { -1, 93463, 22, \"my\"}, // second line\n"
" { 7, 5, 1, \"goodness\"} // third line\n"
" {234, 5, 1, \"gracious\"} // fourth line\n"
"};\n",
Style);
verifyFormat("struct test demo[3] = {\n"
" {56, 23, \"hello\"},\n"
" {-1, 93463, \"world\"},\n"
" { 7, 5, \"!!\"}\n"
"};\n",
Style);
verifyFormat("struct test demo[3] = {\n"
" {int{56}, 23, \"hello\"},\n"
" {int{-1}, 93463, \"world\"},\n"
" { int{7}, 5, \"!!\"}\n"
"};\n",
Style);
verifyFormat("struct test demo[] = {\n"
" {56, 23, \"hello\"},\n"
" {-1, 93463, \"world\"},\n"
" { 7, 5, \"!!\"},\n"
"};\n",
Style);
verifyFormat("test demo[] = {\n"
" {56, 23, \"hello\"},\n"
" {-1, 93463, \"world\"},\n"
" { 7, 5, \"!!\"},\n"
"};\n",
Style);
verifyFormat("demo = std::array<struct test, 3>{\n"
" test{56, 23, \"hello\"},\n"
" test{-1, 93463, \"world\"},\n"
" test{ 7, 5, \"!!\"},\n"
"};\n",
Style);
verifyFormat("test demo[] = {\n"
" {56, 23, \"hello\"},\n"
"#if X\n"
" {-1, 93463, \"world\"},\n"
"#endif\n"
" { 7, 5, \"!!\"}\n"
"};\n",
Style);
verifyFormat(
"test demo[] = {\n"
" { 7, 23,\n"
" \"hello world i am a very long line that really, in any\"\n"
" \"just world, ought to be split over multiple lines\"},\n"
" {-1, 93463, \"world\"},\n"
" {56, 5, \"!!\"}\n"
"};\n",
Style);
verifyFormat("return GradForUnaryCwise(g, {\n"
" {{\"sign\"}, \"Sign\", "
" {\"x\", \"dy\"}},\n"
" { {\"dx\"}, \"Mul\", {\"dy\""
", \"sign\"}},\n"
"});\n",
Style);
Style.ColumnLimit = 0;
EXPECT_EQ(
"test demo[] = {\n"
" {56, 23, \"hello world i am a very long line that really, "
"in any just world, ought to be split over multiple lines\"},\n"
" {-1, 93463, "
" \"world\"},\n"
" { 7, 5, "
" \"!!\"},\n"
"};",
format("test demo[] = {{56, 23, \"hello world i am a very long line "
"that really, in any just world, ought to be split over multiple "
"lines\"},{-1, 93463, \"world\"},{7, 5, \"!!\"},};",
Style));
Style.ColumnLimit = 80;
verifyFormat("test demo[] = {\n"
" {56, 23, /* a comment */ \"hello\"},\n"
" {-1, 93463, \"world\"},\n"
" { 7, 5, \"!!\"}\n"
"};\n",
Style);
verifyFormat("test demo[] = {\n"
" {56, 23, \"hello\"},\n"
" {-1, 93463, \"world\" /* comment here */},\n"
" { 7, 5, \"!!\"}\n"
"};\n",
Style);
verifyFormat("test demo[] = {\n"
" {56, /* a comment */ 23, \"hello\"},\n"
" {-1, 93463, \"world\"},\n"
" { 7, 5, \"!!\"}\n"
"};\n",
Style);
Style.ColumnLimit = 20;
EXPECT_EQ(
"demo = std::array<\n"
" struct test, 3>{\n"
" test{\n"
" 56, 23,\n"
" \"hello \"\n"
" \"world i \"\n"
" \"am a very \"\n"
" \"long line \"\n"
" \"that \"\n"
" \"really, \"\n"
" \"in any \"\n"
" \"just \"\n"
" \"world, \"\n"
" \"ought to \"\n"
" \"be split \"\n"
" \"over \"\n"
" \"multiple \"\n"
" \"lines\"},\n"
" test{-1, 93463,\n"
" \"world\"},\n"
" test{ 7, 5,\n"
" \"!!\" },\n"
"};",
format("demo = std::array<struct test, 3>{test{56, 23, \"hello world "
"i am a very long line that really, in any just world, ought "
"to be split over multiple lines\"},test{-1, 93463, \"world\"},"
"test{7, 5, \"!!\"},};",
Style));
// This caused a core dump by enabling Alignment in the LLVMStyle globally
Style = getLLVMStyleWithColumns(50);
Style.AlignArrayOfStructures = FormatStyle::AIAS_Right;
verifyFormat("static A x = {\n"
" {{init1, init2, init3, init4},\n"
" {init1, init2, init3, init4}}\n"
"};",
Style);
Style.ColumnLimit = 100;
EXPECT_EQ(
"test demo[] = {\n"
" {56, 23,\n"
" \"hello world i am a very long line that really, in any just world"
", ought to be split over \"\n"
" \"multiple lines\" },\n"
" {-1, 93463, \"world\"},\n"
" { 7, 5, \"!!\"},\n"
"};",
format("test demo[] = {{56, 23, \"hello world i am a very long line "
"that really, in any just world, ought to be split over multiple "
"lines\"},{-1, 93463, \"world\"},{7, 5, \"!!\"},};",
Style));
Style = getLLVMStyleWithColumns(50);
Style.AlignArrayOfStructures = FormatStyle::AIAS_Right;
Style.AlignConsecutiveAssignments =
FormatStyle::AlignConsecutiveStyle::ACS_Consecutive;
Style.AlignConsecutiveDeclarations =
FormatStyle::AlignConsecutiveStyle::ACS_Consecutive;
verifyFormat("struct test demo[] = {\n"
" {56, 23, \"hello\"},\n"
" {-1, 93463, \"world\"},\n"
" { 7, 5, \"!!\"}\n"
"};\n"
"static A x = {\n"
" {{init1, init2, init3, init4},\n"
" {init1, init2, init3, init4}}\n"
"};",
Style);
Style.ColumnLimit = 100;
Style.AlignConsecutiveAssignments =
FormatStyle::AlignConsecutiveStyle::ACS_AcrossComments;
Style.AlignConsecutiveDeclarations =
FormatStyle::AlignConsecutiveStyle::ACS_AcrossComments;
verifyFormat("struct test demo[] = {\n"
" {56, 23, \"hello\"},\n"
" {-1, 93463, \"world\"},\n"
" { 7, 5, \"!!\"}\n"
"};\n"
"struct test demo[4] = {\n"
" { 56, 23, 21, \"oh\"}, // first line\n"
" { -1, 93463, 22, \"my\"}, // second line\n"
" { 7, 5, 1, \"goodness\"} // third line\n"
" {234, 5, 1, \"gracious\"} // fourth line\n"
"};\n",
Style);
EXPECT_EQ(
"test demo[] = {\n"
" {56,\n"
" \"hello world i am a very long line that really, in any just world"
", ought to be split over \"\n"
" \"multiple lines\", 23},\n"
" {-1, \"world\", 93463},\n"
" { 7, \"!!\", 5},\n"
"};",
format("test demo[] = {{56, \"hello world i am a very long line "
"that really, in any just world, ought to be split over multiple "
"lines\", 23},{-1, \"world\", 93463},{7, \"!!\", 5},};",
Style));
}
TEST_F(FormatTest, CatchAlignArrayOfStructuresLeftAlignment) {
auto Style = getLLVMStyle();
Style.AlignArrayOfStructures = FormatStyle::AIAS_Left;
verifyFormat("struct test demo[] = {\n"
" {56, 23, \"hello\"},\n"
" {-1, 93463, \"world\"},\n"
" {7, 5, \"!!\" }\n"
"};\n",
Style);
verifyFormat("struct test demo[] = {\n"
" {56, 23, \"hello\"}, // first line\n"
" {-1, 93463, \"world\"}, // second line\n"
" {7, 5, \"!!\" } // third line\n"
"};\n",
Style);
verifyFormat("struct test demo[4] = {\n"
" {56, 23, 21, \"oh\" }, // first line\n"
" {-1, 93463, 22, \"my\" }, // second line\n"
" {7, 5, 1, \"goodness\"} // third line\n"
" {234, 5, 1, \"gracious\"} // fourth line\n"
"};\n",
Style);
verifyFormat("struct test demo[3] = {\n"
" {56, 23, \"hello\"},\n"
" {-1, 93463, \"world\"},\n"
" {7, 5, \"!!\" }\n"
"};\n",
Style);
verifyFormat("struct test demo[3] = {\n"
" {int{56}, 23, \"hello\"},\n"
" {int{-1}, 93463, \"world\"},\n"
" {int{7}, 5, \"!!\" }\n"
"};\n",
Style);
verifyFormat("struct test demo[] = {\n"
" {56, 23, \"hello\"},\n"
" {-1, 93463, \"world\"},\n"
" {7, 5, \"!!\" },\n"
"};\n",
Style);
verifyFormat("test demo[] = {\n"
" {56, 23, \"hello\"},\n"
" {-1, 93463, \"world\"},\n"
" {7, 5, \"!!\" },\n"
"};\n",
Style);
verifyFormat("demo = std::array<struct test, 3>{\n"
" test{56, 23, \"hello\"},\n"
" test{-1, 93463, \"world\"},\n"
" test{7, 5, \"!!\" },\n"
"};\n",
Style);
verifyFormat("test demo[] = {\n"
" {56, 23, \"hello\"},\n"
"#if X\n"
" {-1, 93463, \"world\"},\n"
"#endif\n"
" {7, 5, \"!!\" }\n"
"};\n",
Style);
verifyFormat(
"test demo[] = {\n"
" {7, 23,\n"
" \"hello world i am a very long line that really, in any\"\n"
" \"just world, ought to be split over multiple lines\"},\n"
" {-1, 93463, \"world\" },\n"
" {56, 5, \"!!\" }\n"
"};\n",
Style);
verifyFormat("return GradForUnaryCwise(g, {\n"
" {{\"sign\"}, \"Sign\", {\"x\", "
"\"dy\"} },\n"
" {{\"dx\"}, \"Mul\", "
"{\"dy\", \"sign\"}},\n"
"});\n",
Style);
Style.ColumnLimit = 0;
EXPECT_EQ(
"test demo[] = {\n"
" {56, 23, \"hello world i am a very long line that really, in any "
"just world, ought to be split over multiple lines\"},\n"
" {-1, 93463, \"world\" "
" },\n"
" {7, 5, \"!!\" "
" },\n"
"};",
format("test demo[] = {{56, 23, \"hello world i am a very long line "
"that really, in any just world, ought to be split over multiple "
"lines\"},{-1, 93463, \"world\"},{7, 5, \"!!\"},};",
Style));
Style.ColumnLimit = 80;
verifyFormat("test demo[] = {\n"
" {56, 23, /* a comment */ \"hello\"},\n"
" {-1, 93463, \"world\" },\n"
" {7, 5, \"!!\" }\n"
"};\n",
Style);
verifyFormat("test demo[] = {\n"
" {56, 23, \"hello\" },\n"
" {-1, 93463, \"world\" /* comment here */},\n"
" {7, 5, \"!!\" }\n"
"};\n",
Style);
verifyFormat("test demo[] = {\n"
" {56, /* a comment */ 23, \"hello\"},\n"
" {-1, 93463, \"world\"},\n"
" {7, 5, \"!!\" }\n"
"};\n",
Style);
Style.ColumnLimit = 20;
EXPECT_EQ(
"demo = std::array<\n"
" struct test, 3>{\n"
" test{\n"
" 56, 23,\n"
" \"hello \"\n"
" \"world i \"\n"
" \"am a very \"\n"
" \"long line \"\n"
" \"that \"\n"
" \"really, \"\n"
" \"in any \"\n"
" \"just \"\n"
" \"world, \"\n"
" \"ought to \"\n"
" \"be split \"\n"
" \"over \"\n"
" \"multiple \"\n"
" \"lines\"},\n"
" test{-1, 93463,\n"
" \"world\"},\n"
" test{7, 5,\n"
" \"!!\" },\n"
"};",
format("demo = std::array<struct test, 3>{test{56, 23, \"hello world "
"i am a very long line that really, in any just world, ought "
"to be split over multiple lines\"},test{-1, 93463, \"world\"},"
"test{7, 5, \"!!\"},};",
Style));
// This caused a core dump by enabling Alignment in the LLVMStyle globally
Style = getLLVMStyleWithColumns(50);
Style.AlignArrayOfStructures = FormatStyle::AIAS_Left;
verifyFormat("static A x = {\n"
" {{init1, init2, init3, init4},\n"
" {init1, init2, init3, init4}}\n"
"};",
Style);
Style.ColumnLimit = 100;
EXPECT_EQ(
"test demo[] = {\n"
" {56, 23,\n"
" \"hello world i am a very long line that really, in any just world"
", ought to be split over \"\n"
" \"multiple lines\" },\n"
" {-1, 93463, \"world\"},\n"
" {7, 5, \"!!\" },\n"
"};",
format("test demo[] = {{56, 23, \"hello world i am a very long line "
"that really, in any just world, ought to be split over multiple "
"lines\"},{-1, 93463, \"world\"},{7, 5, \"!!\"},};",
Style));
}
TEST_F(FormatTest, UnderstandsPragmas) {
verifyFormat("#pragma omp reduction(| : var)");
verifyFormat("#pragma omp reduction(+ : var)");