Index: include/clang/Format/Format.h =================================================================== --- include/clang/Format/Format.h +++ include/clang/Format/Format.h @@ -64,6 +64,17 @@ /// \endcode bool AlignConsecutiveAssignments; + /// \brief If \c true, aligns consecutive declarations. + /// + /// This will align the declaration names of consecutive lines. This + /// will result in formattings like + /// \code + /// int aaaa = 12; + /// float b = 23; + /// std::string ccc = 23; + /// \endcode + bool AlignConsecutiveDeclarations; + /// \brief If \c true, aligns escaped newlines as far left as possible. /// Otherwise puts them into the right-most column. bool AlignEscapedNewlinesLeft; @@ -446,6 +457,7 @@ return AccessModifierOffset == R.AccessModifierOffset && AlignAfterOpenBracket == R.AlignAfterOpenBracket && AlignConsecutiveAssignments == R.AlignConsecutiveAssignments && + AlignConsecutiveDeclarations == R.AlignConsecutiveDeclarations && AlignEscapedNewlinesLeft == R.AlignEscapedNewlinesLeft && AlignOperands == R.AlignOperands && AlignTrailingComments == R.AlignTrailingComments && Index: lib/Format/Format.cpp =================================================================== --- lib/Format/Format.cpp +++ lib/Format/Format.cpp @@ -198,6 +198,8 @@ IO.mapOptional("AlignAfterOpenBracket", Style.AlignAfterOpenBracket); IO.mapOptional("AlignConsecutiveAssignments", Style.AlignConsecutiveAssignments); + IO.mapOptional("AlignConsecutiveDeclarations", + Style.AlignConsecutiveDeclarations); IO.mapOptional("AlignEscapedNewlinesLeft", Style.AlignEscapedNewlinesLeft); IO.mapOptional("AlignOperands", Style.AlignOperands); IO.mapOptional("AlignTrailingComments", Style.AlignTrailingComments); @@ -349,6 +351,7 @@ LLVMStyle.AlignOperands = true; LLVMStyle.AlignTrailingComments = true; LLVMStyle.AlignConsecutiveAssignments = false; + LLVMStyle.AlignConsecutiveDeclarations = false; LLVMStyle.AllowAllParametersOfDeclarationOnNextLine = true; LLVMStyle.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_All; LLVMStyle.AllowShortBlocksOnASingleLine = false; Index: lib/Format/WhitespaceManager.h =================================================================== --- lib/Format/WhitespaceManager.h +++ lib/Format/WhitespaceManager.h @@ -110,7 +110,7 @@ unsigned IndentLevel, int Spaces, unsigned StartOfTokenColumn, unsigned NewlinesBefore, StringRef PreviousLinePostfix, StringRef CurrentLinePrefix, tok::TokenKind Kind, - bool ContinuesPPDirective); + bool ContinuesPPDirective, bool IsStartOfDeclName); bool CreateReplacement; // Changes might be in the middle of a token, so we cannot just keep the @@ -126,6 +126,7 @@ // the \c BreakableToken is still doing its own alignment. tok::TokenKind Kind; bool ContinuesPPDirective; + bool IsStartOfDeclName; // The number of nested blocks the token is in. This is used to add tabs // only for the indentation, and not for alignment, when @@ -173,6 +174,14 @@ void alignConsecutiveAssignments(unsigned Start, unsigned End, unsigned Column); + /// \brief Align consecutive declarations over all \c Changes. + void alignConsecutiveDeclarations(); + + /// \brief Align consecutive declarations from change \p Start to change \p + /// End at the specified \p Column. + void alignConsecutiveDeclarations(unsigned Start, unsigned End, + unsigned Column); + /// \brief Align trailing comments over all \c Changes. void alignTrailingComments(); Index: lib/Format/WhitespaceManager.cpp =================================================================== --- lib/Format/WhitespaceManager.cpp +++ lib/Format/WhitespaceManager.cpp @@ -29,13 +29,15 @@ bool CreateReplacement, const SourceRange &OriginalWhitespaceRange, unsigned IndentLevel, int Spaces, unsigned StartOfTokenColumn, unsigned NewlinesBefore, StringRef PreviousLinePostfix, - StringRef CurrentLinePrefix, tok::TokenKind Kind, bool ContinuesPPDirective) + StringRef CurrentLinePrefix, tok::TokenKind Kind, bool ContinuesPPDirective, + bool IsStartOfDeclName) : CreateReplacement(CreateReplacement), OriginalWhitespaceRange(OriginalWhitespaceRange), StartOfTokenColumn(StartOfTokenColumn), NewlinesBefore(NewlinesBefore), PreviousLinePostfix(PreviousLinePostfix), CurrentLinePrefix(CurrentLinePrefix), Kind(Kind), - ContinuesPPDirective(ContinuesPPDirective), IndentLevel(IndentLevel), + ContinuesPPDirective(ContinuesPPDirective), + IsStartOfDeclName(IsStartOfDeclName), IndentLevel(IndentLevel), Spaces(Spaces), IsTrailingComment(false), TokenLength(0), PreviousEndOfTokenColumn(0), EscapedNewlineColumn(0), StartOfBlockComment(nullptr), IndentationOffset(0) {} @@ -52,19 +54,21 @@ if (Tok.Finalized) return; Tok.Decision = (Newlines > 0) ? FD_Break : FD_Continue; - Changes.push_back(Change(true, Tok.WhitespaceRange, IndentLevel, Spaces, - StartOfTokenColumn, Newlines, "", "", - Tok.Tok.getKind(), InPPDirective && !Tok.IsFirst)); + Changes.push_back( + Change(true, Tok.WhitespaceRange, IndentLevel, Spaces, StartOfTokenColumn, + Newlines, "", "", Tok.Tok.getKind(), InPPDirective && !Tok.IsFirst, + Tok.is(TT_StartOfName) || Tok.is(TT_FunctionDeclarationName))); } void WhitespaceManager::addUntouchableToken(const FormatToken &Tok, bool InPPDirective) { if (Tok.Finalized) return; - Changes.push_back(Change(false, Tok.WhitespaceRange, /*IndentLevel=*/0, - /*Spaces=*/0, Tok.OriginalColumn, Tok.NewlinesBefore, - "", "", Tok.Tok.getKind(), - InPPDirective && !Tok.IsFirst)); + Changes.push_back( + Change(false, Tok.WhitespaceRange, /*IndentLevel=*/0, + /*Spaces=*/0, Tok.OriginalColumn, Tok.NewlinesBefore, "", "", + Tok.Tok.getKind(), InPPDirective && !Tok.IsFirst, + Tok.is(TT_StartOfName) || Tok.is(TT_FunctionDeclarationName))); } void WhitespaceManager::replaceWhitespaceInToken( @@ -84,7 +88,8 @@ // calculate the new length of the comment and to calculate the changes // for which to do the alignment when aligning comments. Tok.is(TT_LineComment) && Newlines > 0 ? tok::comment : tok::unknown, - InPPDirective && !Tok.IsFirst)); + InPPDirective && !Tok.IsFirst, + Tok.is(TT_StartOfName) || Tok.is(TT_FunctionDeclarationName))); } const tooling::Replacements &WhitespaceManager::generateReplacements() { @@ -93,6 +98,7 @@ std::sort(Changes.begin(), Changes.end(), Change::IsBeforeInFile(SourceMgr)); calculateLineBreakInformation(); + alignConsecutiveDeclarations(); alignConsecutiveAssignments(); alignTrailingComments(); alignEscapedNewlines(); @@ -232,6 +238,98 @@ } } +// Walk through all of the changes and find sequences of declaration names to +// align. To do so, keep track of the lines and whether or not a name was found +// on align. If a name is found on a line, extend the current sequence. If the +// current line cannot be part of a sequence, e.g. because there is an empty +// line before it or it contains non-declarations, finalize the previous +// sequence. +void WhitespaceManager::alignConsecutiveDeclarations() { + if (!Style.AlignConsecutiveDeclarations) + return; + + unsigned MinColumn = 0; + unsigned StartOfSequence = 0; + unsigned EndOfSequence = 0; + bool FoundDeclarationOnLine = false; + bool FoundLeftBraceOnLine = false; + bool FoundLeftParenOnLine = false; + + auto AlignSequence = [&] { + if (StartOfSequence > 0 && StartOfSequence < EndOfSequence) + alignConsecutiveDeclarations(StartOfSequence, EndOfSequence, MinColumn); + MinColumn = 0; + StartOfSequence = 0; + EndOfSequence = 0; + }; + + for (unsigned i = 0, e = Changes.size(); i != e; ++i) { + if (Changes[i].NewlinesBefore != 0) { + EndOfSequence = i; + if (Changes[i].NewlinesBefore > 1 || !FoundDeclarationOnLine || + FoundLeftBraceOnLine || FoundLeftParenOnLine) { + AlignSequence(); + } + FoundDeclarationOnLine = false; + FoundLeftBraceOnLine = false; + FoundLeftParenOnLine = false; + } + + if (Changes[i].Kind == tok::r_brace) { + if (!FoundLeftBraceOnLine) + AlignSequence(); + FoundLeftBraceOnLine = false; + } else if (Changes[i].Kind == tok::l_brace) { + FoundLeftBraceOnLine = true; + if (!FoundDeclarationOnLine) + AlignSequence(); + } else if (Changes[i].Kind == tok::r_paren) { + if (!FoundLeftParenOnLine) + AlignSequence(); + FoundLeftParenOnLine = false; + } else if (Changes[i].Kind == tok::l_paren) { + FoundLeftParenOnLine = true; + if (!FoundDeclarationOnLine) + AlignSequence(); + } else if (!FoundDeclarationOnLine && !FoundLeftBraceOnLine && + !FoundLeftParenOnLine && Changes[i].IsStartOfDeclName) { + FoundDeclarationOnLine = true; + if (StartOfSequence == 0) + StartOfSequence = i; + + unsigned ChangeMinColumn = Changes[i].StartOfTokenColumn; + MinColumn = std::max(MinColumn, ChangeMinColumn); + } + } + + EndOfSequence = Changes.size(); + AlignSequence(); +} + +void WhitespaceManager::alignConsecutiveDeclarations(unsigned Start, + unsigned End, + unsigned Column) { + bool FoundDeclarationOnLine = false; + int Shift = 0; + for (unsigned i = Start; i != End; ++i) { + if (Changes[i].NewlinesBefore != 0) { + FoundDeclarationOnLine = false; + Shift = 0; + } + + if (!FoundDeclarationOnLine && Changes[i].IsStartOfDeclName) { + FoundDeclarationOnLine = true; + Shift = Column - Changes[i].StartOfTokenColumn; + Changes[i].Spaces += Shift; + } + + assert(Shift >= 0); + Changes[i].StartOfTokenColumn += Shift; + if (i + 1 != Changes.size()) + Changes[i + 1].PreviousEndOfTokenColumn += Shift; + } +} + void WhitespaceManager::alignTrailingComments() { unsigned MinColumn = 0; unsigned MaxColumn = UINT_MAX; Index: unittests/Format/FormatTest.cpp =================================================================== --- unittests/Format/FormatTest.cpp +++ unittests/Format/FormatTest.cpp @@ -8616,6 +8616,152 @@ Alignment); } +TEST_F(FormatTest, AlignConsecutiveDeclarations) { + FormatStyle Alignment = getLLVMStyle(); + Alignment.AlignConsecutiveDeclarations = false; + verifyFormat("float const a = 5;\n" + "int oneTwoThree = 123;", + Alignment); + verifyFormat("int a = 5;\n" + "float const oneTwoThree = 123;", + Alignment); + + Alignment.AlignConsecutiveDeclarations = true; + verifyFormat("float const a = 5;\n" + "int oneTwoThree = 123;", + Alignment); + verifyFormat("int a = method();\n" + "float const oneTwoThree = 133;", + Alignment); + verifyFormat("a &= 5;\n" + "bcd *= 5;\n" + "ghtyf += 5;\n" + "dvfvdb -= 5;\n" + "a /= 5;\n" + "vdsvsv %= 5;\n" + "sfdbddfbdfbb ^= 5;\n" + "dvsdsv |= 5;\n" + "int dsvvdvsdvvv = 123;", + Alignment); + verifyFormat("int i = 1, j = 10;\n" + "something = 2000;", + Alignment); + verifyFormat("something = 2000;\n" + "int i = 1, j = 10;\n", + Alignment); + verifyFormat("float something = 2000;\n" + "double another = 911;\n" + "int i = 1, j = 10;\n" + "const int *oneMore = 1;\n" + "unsigned i = 2;", + Alignment); + verifyFormat("float a = 5;\n" + "int one = 1;\n" + "method();\n" + "const double oneTwoThree = 123;\n" + "const unsigned int oneTwo = 12;", + Alignment); + verifyFormat("int oneTwoThree{0}; // comment\n" + "unsigned oneTwo; // comment", + Alignment); + EXPECT_EQ("float const a = 5;\n" + "\n" + "int oneTwoThree = 123;", + format("float const a = 5;\n" + "\n" + "int oneTwoThree= 123;", + Alignment)); + EXPECT_EQ("float a = 5;\n" + "int one = 1;\n" + "\n" + "unsigned oneTwoThree = 123;", + format("float a = 5;\n" + "int one = 1;\n" + "\n" + "unsigned oneTwoThree = 123;", + Alignment)); + EXPECT_EQ("float a = 5;\n" + "int one = 1;\n" + "\n" + "unsigned oneTwoThree = 123;\n" + "int oneTwo = 12;", + format("float a = 5;\n" + "int one = 1;\n" + "\n" + "unsigned oneTwoThree = 123;\n" + "int oneTwo = 12;", + Alignment)); + Alignment.AlignEscapedNewlinesLeft = true; + verifyFormat("#define A \\\n" + " int aaaa = 12; \\\n" + " float b = 23; \\\n" + " const int ccc = 234; \\\n" + " unsigned dddddddddd = 2345;", + Alignment); + Alignment.AlignEscapedNewlinesLeft = false; + verifyFormat("#define A " + " \\\n" + " int aaaa = 12; " + " \\\n" + " float b = 23; " + " \\\n" + " const int ccc = 234; " + " \\\n" + " int dddddddddd = 2345;", + Alignment); + verifyFormat("void SomeFunction(int parameter = 1, int i = 2, int j = 3, int " + "k = 4, int l = 5,\n" + " int m = 6) {\n" + " const int j = 10;\n" + " otherThing = 1;\n" + "}", + Alignment); + verifyFormat("void SomeFunction(int parameter = 0) {\n" + " int const i = 1;\n" + " int * j = 2;\n" + " int big = 10000;\n" + "}", + Alignment); + verifyFormat("class C {\n" + "public:\n" + " int i = 1;\n" + " virtual void f() = 0;\n" + "};", + Alignment); + verifyFormat("float i = 1;\n" + "if (SomeType t = getSomething()) {\n" + "}\n" + "const unsigned j = 2;\n" + "int big = 10000;", + Alignment); + verifyFormat("float j = 7;\n" + "for (int k = 0; k < N; ++k) {\n" + "}\n" + "unsigned j = 2;\n" + "int big = 10000;\n" + "}", + Alignment); + Alignment.BreakBeforeBinaryOperators = FormatStyle::BOS_All; + verifyFormat("float i = 1;\n" + "LooooooooooongType loooooooooooooooooooooongVariable\n" + " = someLooooooooooooooooongFunction();\n" + "int j = 2;", + Alignment); + Alignment.BreakBeforeBinaryOperators = FormatStyle::BOS_None; + verifyFormat("int i = 1;\n" + "LooooooooooongType loooooooooooooooooooooongVariable =\n" + " someLooooooooooooooooongFunction();\n" + "int j = 2;", + Alignment); + // FIXME: Should align all three assignments + verifyFormat( + "int i = 1;\n" + "SomeType a = SomeFunction(looooooooooooooooooooooongParameterA,\n" + " loooooooooooooooooooooongParameterB);\n" + "int j = 2;", + Alignment); +} + TEST_F(FormatTest, LinuxBraceBreaking) { FormatStyle LinuxBraceStyle = getLLVMStyle(); LinuxBraceStyle.BreakBeforeBraces = FormatStyle::BS_Linux; @@ -9247,6 +9393,7 @@ CHECK_PARSE_BOOL(AlignOperands); CHECK_PARSE_BOOL(AlignTrailingComments); CHECK_PARSE_BOOL(AlignConsecutiveAssignments); + CHECK_PARSE_BOOL(AlignConsecutiveDeclarations); CHECK_PARSE_BOOL(AllowAllParametersOfDeclarationOnNextLine); CHECK_PARSE_BOOL(AllowShortBlocksOnASingleLine); CHECK_PARSE_BOOL(AllowShortCaseLabelsOnASingleLine);