Index: docs/ClangFormatStyleOptions.rst =================================================================== --- docs/ClangFormatStyleOptions.rst +++ docs/ClangFormatStyleOptions.rst @@ -185,6 +185,20 @@ +**AlignConsecutiveMacros** (``bool``) + If ``true``, aligns consecutive C/C++ preprocessor macros. + + This will align the C/C++ preprocessor macros of consecutive lines. This + will result in formattings like + + .. code-block:: c++ + + #define SHORT_NAME 42 + #define LONGER_NAME 0x007f + #define EVEN_LONGER_NAME (2) + #define foo(x) (x * x) + #define bar(y, z) (y + z) + **AlignConsecutiveAssignments** (``bool``) If ``true``, aligns consecutive assignments. Index: include/clang/Format/Format.h =================================================================== --- include/clang/Format/Format.h +++ include/clang/Format/Format.h @@ -76,6 +76,19 @@ /// brackets. BracketAlignmentStyle AlignAfterOpenBracket; + /// \brief If ``true``, aligns consecutive C/C++ preprocessor macros. + /// + /// This will align C/C++ preprocessor macros of consecutive lines. + /// Will result in formattings like + /// \code + /// #define SHORT_NAME 42 + /// #define LONGER_NAME 0x007f + /// #define EVEN_LONGER_NAME (2) + /// #define foo(x) (x * x) + /// #define bar(y, z) (y + z) + /// \endcode + bool AlignConsecutiveMacros; + /// \brief If ``true``, aligns consecutive assignments. /// /// This will align the assignment operators of consecutive lines. This Index: lib/Format/Format.cpp =================================================================== --- lib/Format/Format.cpp +++ lib/Format/Format.cpp @@ -242,6 +242,7 @@ IO.mapOptional("AccessModifierOffset", Style.AccessModifierOffset); IO.mapOptional("AlignAfterOpenBracket", Style.AlignAfterOpenBracket); + IO.mapOptional("AlignConsecutiveMacros", Style.AlignConsecutiveMacros); IO.mapOptional("AlignConsecutiveAssignments", Style.AlignConsecutiveAssignments); IO.mapOptional("AlignConsecutiveDeclarations", Index: lib/Format/FormatToken.h =================================================================== --- lib/Format/FormatToken.h +++ lib/Format/FormatToken.h @@ -145,6 +145,12 @@ /// \brief Whether the token text contains newlines (escaped or not). bool IsMultiline = false; + /// \brief Whether the token is the final token in the identifier of a PP + // macro. This will be either 1) the identifier token following the 'define' + // keyword in a simple PP macro, or 2) the closing r_paren token in the + // parameter list of a function-like PP macro. + bool EndsPPIdentifier = false; + /// \brief Indicates that this is the first token of the file. bool IsFirst = false; Index: lib/Format/TokenAnnotator.cpp =================================================================== --- lib/Format/TokenAnnotator.cpp +++ lib/Format/TokenAnnotator.cpp @@ -1646,6 +1646,60 @@ Line.First->CanBreakBefore = Line.First->MustBreakBefore; } +// This function heuristically determines whether 'tok' is the +// 'define' keyword in a PP macro +static bool isDefineKeyword(const FormatToken &tok) { + if (!tok.Previous) + return false; + if (!tok.is(tok::identifier) || !tok.TokenText.equals("define")) + return false; + + return tok.Previous->is(tok::hash); +} + +// This function heuristically determines whether 'Current' is the identifier +// following the 'define' keyword in a simple PP macro (i.e. no parameters) +static bool endsMacroIdentifier(const FormatToken &Current) { + const FormatToken *keyword = Current.Previous; + + if (!keyword || Current.Next) + return false; + if (!Current.is(tok::identifier)) + return false; + + return isDefineKeyword(*keyword); +} + +// This function heuristically determines whether 'Current' is the closing +// r_paren token for the parameter list of a function-like PP macro +static bool endsMacroWithArgsIdentifier(const FormatToken &Current) { + const FormatToken *Keyword, *Param = Current.Previous; + + if (!Param || !Current.is(tok::r_paren)) + return false; + + while (Param && !Param->is(tok::l_paren)) { + if (!Param->is(tok::identifier) && !Param->is(tok::comma)) + return false; + if (!Param->Previous) + return false; + + Param = Param->Previous; + } + + if (!Param || Param->SpacesRequiredBefore) + return false; + if (!Param->Previous || !Param->Previous->is(tok::identifier)) + return false; + + Keyword = Param->Previous->Previous; + return Keyword && isDefineKeyword(*Keyword); +} + +static bool endsPPIdentifier(const FormatToken &Current) { + return endsMacroIdentifier(Current) || endsMacroWithArgsIdentifier(Current); +} + // This function heuristically determines whether 'Current' starts the name of a // function declaration. static bool isFunctionDeclarationName(const FormatToken &Current, @@ -1760,6 +1814,7 @@ FormatToken *Current = Line.First->Next; bool InFunctionDecl = Line.MightBeFunctionDecl; while (Current) { + Current->EndsPPIdentifier = endsPPIdentifier(*Current); if (isFunctionDeclarationName(*Current, Line)) Current->Type = TT_FunctionDeclarationName; if (Current->is(TT_LineComment)) { @@ -2626,6 +2681,7 @@ while (Tok) { llvm::errs() << " M=" << Tok->MustBreakBefore << " C=" << Tok->CanBreakBefore + << " E=" << Tok->EndsPPIdentifier << " T=" << getTokenTypeName(Tok->Type) << " S=" << Tok->SpacesRequiredBefore << " B=" << Tok->BlockParameterCount Index: lib/Format/WhitespaceManager.h =================================================================== --- lib/Format/WhitespaceManager.h +++ lib/Format/WhitespaceManager.h @@ -107,7 +107,7 @@ unsigned NewlinesBefore, StringRef PreviousLinePostfix, StringRef CurrentLinePrefix, tok::TokenKind Kind, bool ContinuesPPDirective, bool IsStartOfDeclName, - bool IsInsideToken); + bool IsInsideToken, bool EndsPPIdentifier); bool CreateReplacement; // Changes might be in the middle of a token, so we cannot just keep the @@ -141,6 +141,9 @@ // directly after a newline. bool IsInsideToken; + // If this change is at the final token of a PP macro identifier + bool EndsPPIdentifier; + // \c IsTrailingComment, \c TokenLength, \c PreviousEndOfTokenColumn and // \c EscapedNewlineColumn will be calculated in // \c calculateLineBreakInformation. @@ -167,6 +170,9 @@ /// \c EscapedNewlineColumn for the first tokens or token parts in a line. void calculateLineBreakInformation(); + /// \brief Align consecutive C/C++ preprocessor macros over all \c Changes. + void alignConsecutiveMacros(); + /// \brief Align consecutive assignments over all \c Changes. void alignConsecutiveAssignments(); Index: lib/Format/WhitespaceManager.cpp =================================================================== --- lib/Format/WhitespaceManager.cpp +++ lib/Format/WhitespaceManager.cpp @@ -30,7 +30,7 @@ unsigned IndentLevel, int Spaces, unsigned StartOfTokenColumn, unsigned NewlinesBefore, StringRef PreviousLinePostfix, StringRef CurrentLinePrefix, tok::TokenKind Kind, bool ContinuesPPDirective, - bool IsStartOfDeclName, bool IsInsideToken) + bool IsStartOfDeclName, bool IsInsideToken, bool EndsPPIdentifier) : CreateReplacement(CreateReplacement), OriginalWhitespaceRange(OriginalWhitespaceRange), StartOfTokenColumn(StartOfTokenColumn), NewlinesBefore(NewlinesBefore), @@ -38,7 +38,8 @@ CurrentLinePrefix(CurrentLinePrefix), Kind(Kind), ContinuesPPDirective(ContinuesPPDirective), IsStartOfDeclName(IsStartOfDeclName), IndentLevel(IndentLevel), - Spaces(Spaces), IsInsideToken(IsInsideToken), IsTrailingComment(false), + Spaces(Spaces), IsInsideToken(IsInsideToken), + EndsPPIdentifier(EndsPPIdentifier), IsTrailingComment(false), TokenLength(0), PreviousEndOfTokenColumn(0), EscapedNewlineColumn(0), StartOfBlockComment(nullptr), IndentationOffset(0) {} @@ -54,7 +55,8 @@ Spaces, StartOfTokenColumn, Newlines, "", "", Tok.Tok.getKind(), InPPDirective && !Tok.IsFirst, Tok.is(TT_StartOfName) || Tok.is(TT_FunctionDeclarationName), - /*IsInsideToken=*/false)); + /*IsInsideToken=*/false, + /* EndsPPIdentifier=*/Tok.EndsPPIdentifier)); } void WhitespaceManager::addUntouchableToken(const FormatToken &Tok, @@ -66,7 +68,8 @@ /*Spaces=*/0, Tok.OriginalColumn, Tok.NewlinesBefore, "", "", Tok.Tok.getKind(), InPPDirective && !Tok.IsFirst, Tok.is(TT_StartOfName) || Tok.is(TT_FunctionDeclarationName), - /*IsInsideToken=*/false)); + /*IsInsideToken=*/false, + /* EndsPPIdentifier=*/Tok.EndsPPIdentifier)); } void WhitespaceManager::replaceWhitespaceInToken( @@ -82,7 +85,8 @@ CurrentPrefix, Tok.is(TT_LineComment) ? tok::comment : tok::unknown, InPPDirective && !Tok.IsFirst, Tok.is(TT_StartOfName) || Tok.is(TT_FunctionDeclarationName), - /*IsInsideToken=*/Newlines == 0)); + /*IsInsideToken=*/Newlines == 0, + /* EndsPPIdentifier=*/Tok.EndsPPIdentifier)); } const tooling::Replacements &WhitespaceManager::generateReplacements() { @@ -91,6 +95,7 @@ std::sort(Changes.begin(), Changes.end(), Change::IsBeforeInFile(SourceMgr)); calculateLineBreakInformation(); + alignConsecutiveMacros(); alignConsecutiveDeclarations(); alignConsecutiveAssignments(); alignTrailingComments(); @@ -192,7 +197,8 @@ // finalize the previous sequence. template static void AlignTokens(const FormatStyle &Style, F &&Matches, - SmallVector &Changes) { + SmallVector &Changes, + unsigned MaxNestingLevelIncrease, bool ConsiderCommas) { unsigned MinColumn = 0; unsigned MaxColumn = UINT_MAX; @@ -201,17 +207,19 @@ unsigned EndOfSequence = 0; // Keep track of the nesting level of matching tokens, i.e. the number of - // surrounding (), [], or {}. We will only align a sequence of matching - // token that share the same scope depth. + // surrounding (), [], or {}. If ConsiderNestingLevel is 0, we will + // only align a sequence of matching tokens that share the same scope depth. + // Otherwise, alignment will be allowed to continue until the nesting level + // increases by MaxNestingLevelIncrease. // // FIXME: This could use FormatToken::NestingLevel information, but there is // an outstanding issue wrt the brace scopes. unsigned NestingLevelOfLastMatch = 0; unsigned NestingLevel = 0; - // Keep track of the number of commas before the matching tokens, we will only - // align a sequence of matching tokens if they are preceded by the same number - // of commas. + // Keep track of the number of commas before the matching tokens. If + // ConsiderCommas is true, we will only align a sequence of matching tokens + // if they are preceded by the same number of commas. unsigned CommasBeforeLastMatch = 0; unsigned CommasBeforeMatch = 0; @@ -268,8 +276,10 @@ // If there is more than one matching token per line, or if the number of // preceding commas, or the scope depth, do not match anymore, end the // sequence. - if (FoundMatchOnLine || CommasBeforeMatch != CommasBeforeLastMatch || - NestingLevel != NestingLevelOfLastMatch) + if (FoundMatchOnLine || + (ConsiderCommas && CommasBeforeMatch != CommasBeforeLastMatch) || + NestingLevel < NestingLevelOfLastMatch || + NestingLevel > NestingLevelOfLastMatch + MaxNestingLevelIncrease) AlignCurrentSequence(); CommasBeforeLastMatch = CommasBeforeMatch; @@ -287,7 +297,7 @@ // If we are restricted by the maximum column width, end the sequence. if (ChangeMinColumn > MaxColumn || ChangeMaxColumn < MinColumn || - CommasBeforeLastMatch != CommasBeforeMatch) { + (ConsiderCommas && CommasBeforeLastMatch != CommasBeforeMatch)) { AlignCurrentSequence(); StartOfSequence = i; } @@ -300,6 +310,17 @@ AlignCurrentSequence(); } +void WhitespaceManager::alignConsecutiveMacros() { + if (!Style.AlignConsecutiveMacros) + return; + + AlignTokens(Style, + [&](Change const &C) { + return (&C != &Changes.front() && (&C - 1)->EndsPPIdentifier); + }, + Changes, 1, false); +} + void WhitespaceManager::alignConsecutiveAssignments() { if (!Style.AlignConsecutiveAssignments) return; @@ -316,7 +337,7 @@ return C.Kind == tok::equal; }, - Changes); + Changes, 0, true); } void WhitespaceManager::alignConsecutiveDeclarations() { @@ -331,7 +352,7 @@ // SomeVeryLongType const& v3; AlignTokens(Style, [](Change const &C) { return C.IsStartOfDeclName; }, - Changes); + Changes, 0, true); } void WhitespaceManager::alignTrailingComments() { Index: unittests/Format/FormatTest.cpp =================================================================== --- unittests/Format/FormatTest.cpp +++ unittests/Format/FormatTest.cpp @@ -8514,8 +8514,78 @@ verifyFormat("a or_eq 8;", Spaces); } +TEST_F(FormatTest, AlignConsecutiveMacros) { + FormatStyle Alignment = getLLVMStyle(); + Alignment.AlignConsecutiveAssignments = true; + Alignment.AlignConsecutiveDeclarations = true; + Alignment.AlignConsecutiveMacros = false; + + verifyFormat("#define a 3\n" + "#define bbbb 4\n" + "#define ccc (5)", + Alignment); + + verifyFormat("#define f(x) (x * x)\n" + "#define fff(x, y, z) (x * y + z)\n" + "#define ffff(x, y) (x - y)", + Alignment); + + verifyFormat("#define foo(x, y) (x + y)\n" + "#define bar (5, 6)(2 + 2)", + Alignment); + + verifyFormat("#define a 3\n" + "#define bbbb 4\n" + "#define ccc (5)\n" + "#define f(x) (x * x)\n" + "#define fff(x, y, z) (x * y + z)\n" + "#define ffff(x, y) (x - y)", + Alignment); + + Alignment.AlignConsecutiveMacros = true; + verifyFormat("#define a 3\n" + "#define bbbb 4\n" + "#define ccc (5)", + Alignment); + + verifyFormat("#define f(x) (x * x)\n" + "#define fff(x, y, z) (x * y + z)\n" + "#define ffff(x, y) (x - y)", + Alignment); + + verifyFormat("#define foo(x, y) (x + y)\n" + "#define bar (5, 6)(2 + 2)", + Alignment); + + verifyFormat("#define a 3\n" + "#define bbbb 4\n" + "#define ccc (5)\n" + "#define f(x) (x * x)\n" + "#define fff(x, y, z) (x * y + z)\n" + "#define ffff(x, y) (x - y)", + Alignment); + + verifyFormat("#define a 5\n" + "#define foo(x, y) (x + y)\n" + "#define CCC (6)\n" + "auto lambda = []() {\n" + " auto ii = 0;\n" + " float j = 0;\n" + " return 0;\n" + "};\n" + "int i = 0;\n" + "float i2 = 0;\n" + "auto v = type{\n" + " i = 1, //\n" + " (i = 2), //\n" + " i = 3 //\n" + "};", + Alignment); +} + TEST_F(FormatTest, AlignConsecutiveAssignments) { FormatStyle Alignment = getLLVMStyle(); + Alignment.AlignConsecutiveMacros = true; Alignment.AlignConsecutiveAssignments = false; verifyFormat("int a = 5;\n" "int oneTwoThree = 123;", @@ -8656,7 +8726,10 @@ "int j = 2;", Alignment); - verifyFormat("auto lambda = []() {\n" + verifyFormat("#define a 5\n" + "#define foo(x, y) (x + y)\n" + "#define CCC (6)\n" + "auto lambda = []() {\n" " auto i = 0;\n" " return 0;\n" "};\n" @@ -8692,6 +8765,7 @@ TEST_F(FormatTest, AlignConsecutiveDeclarations) { FormatStyle Alignment = getLLVMStyle(); + Alignment.AlignConsecutiveMacros = true; Alignment.AlignConsecutiveDeclarations = false; verifyFormat("float const a = 5;\n" "int oneTwoThree = 123;", @@ -8851,7 +8925,10 @@ Alignment); Alignment.AlignConsecutiveAssignments = true; - verifyFormat("auto lambda = []() {\n" + verifyFormat("#define a 5\n" + "#define foo(x, y) (x + y)\n" + "#define CCC (6)\n" + "auto lambda = []() {\n" " auto ii = 0;\n" " float j = 0;\n" " return 0;\n" @@ -9576,6 +9653,7 @@ CHECK_PARSE_BOOL(AlignEscapedNewlinesLeft); CHECK_PARSE_BOOL(AlignOperands); CHECK_PARSE_BOOL(AlignTrailingComments); + CHECK_PARSE_BOOL(AlignConsecutiveMacros); CHECK_PARSE_BOOL(AlignConsecutiveAssignments); CHECK_PARSE_BOOL(AlignConsecutiveDeclarations); CHECK_PARSE_BOOL(AllowAllParametersOfDeclarationOnNextLine);