From: Sam McCall Date: Tue, 2 Jul 2019 15:53:14 +0000 (+0000) Subject: clang-format: Add new style option AlignConsecutiveMacros X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=54b8029400637989d16f8467643c46296e62e1d3;p=clang clang-format: Add new style option AlignConsecutiveMacros This option behaves similarly to AlignConsecutiveDeclarations and AlignConsecutiveAssignments, aligning the assignment of C/C++ preprocessor macros on consecutive lines. I've worked in many projects (embedded, mostly) where header files full of large, well-aligned "#define" blocks are a common pattern. We normally avoid using clang-format on these files, since it ruins any existing alignment in said blocks. This style option will align "simple" PP macros (no parameters) and PP macros with parameter lists on consecutive lines. Related Bugzilla entry (thanks mcuddie): https://llvm.org/bugs/show_bug.cgi?id=20637 Patch by Nick Renieris (VelocityRa)! Differential Revision: https://reviews.llvm.org/D28462 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@364938 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/docs/ClangFormatStyleOptions.rst b/docs/ClangFormatStyleOptions.rst index 1276a41acb..74648652ce 100644 --- a/docs/ClangFormatStyleOptions.rst +++ b/docs/ClangFormatStyleOptions.rst @@ -192,6 +192,20 @@ the configuration (without a prefix: ``Auto``). +**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. diff --git a/include/clang/Format/Format.h b/include/clang/Format/Format.h index c5034db71a..6388e4fc17 100644 --- a/include/clang/Format/Format.h +++ b/include/clang/Format/Format.h @@ -79,6 +79,19 @@ struct FormatStyle { /// 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; + /// If ``true``, aligns consecutive assignments. /// /// This will align the assignment operators of consecutive lines. This diff --git a/lib/Format/Format.cpp b/lib/Format/Format.cpp index 31442b5124..c48182976b 100644 --- a/lib/Format/Format.cpp +++ b/lib/Format/Format.cpp @@ -341,6 +341,7 @@ template <> struct MappingTraits { IO.mapOptional("AccessModifierOffset", Style.AccessModifierOffset); IO.mapOptional("AlignAfterOpenBracket", Style.AlignAfterOpenBracket); + IO.mapOptional("AlignConsecutiveMacros", Style.AlignConsecutiveMacros); IO.mapOptional("AlignConsecutiveAssignments", Style.AlignConsecutiveAssignments); IO.mapOptional("AlignConsecutiveDeclarations", @@ -666,6 +667,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) { LLVMStyle.AlignTrailingComments = true; LLVMStyle.AlignConsecutiveAssignments = false; LLVMStyle.AlignConsecutiveDeclarations = false; + LLVMStyle.AlignConsecutiveMacros = false; LLVMStyle.AllowAllArgumentsOnNextLine = true; LLVMStyle.AllowAllConstructorInitializersOnNextLine = true; LLVMStyle.AllowAllParametersOfDeclarationOnNextLine = true; diff --git a/lib/Format/WhitespaceManager.cpp b/lib/Format/WhitespaceManager.cpp index 5383addd7e..23fbf94c75 100644 --- a/lib/Format/WhitespaceManager.cpp +++ b/lib/Format/WhitespaceManager.cpp @@ -91,6 +91,7 @@ const tooling::Replacements &WhitespaceManager::generateReplacements() { llvm::sort(Changes, Change::IsBeforeInFile(SourceMgr)); calculateLineBreakInformation(); + alignConsecutiveMacros(); alignConsecutiveDeclarations(); alignConsecutiveAssignments(); alignTrailingComments(); @@ -428,6 +429,130 @@ static unsigned AlignTokens(const FormatStyle &Style, F &&Matches, return i; } +// Aligns a sequence of matching tokens, on the MinColumn column. +// +// Sequences start from the first matching token to align, and end at the +// first token of the first line that doesn't need to be aligned. +// +// We need to adjust the StartOfTokenColumn of each Change that is on a line +// containing any matching token to be aligned and located after such token. +static void AlignMacroSequence( + unsigned &StartOfSequence, unsigned &EndOfSequence, unsigned &MinColumn, + unsigned &MaxColumn, bool &FoundMatchOnLine, + std::function AlignMacrosMatches, + SmallVector &Changes) { + if (StartOfSequence > 0 && StartOfSequence < EndOfSequence) { + + FoundMatchOnLine = false; + int Shift = 0; + + for (unsigned I = StartOfSequence; I != EndOfSequence; ++I) { + if (Changes[I].NewlinesBefore > 0) { + Shift = 0; + FoundMatchOnLine = false; + } + + // 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 && AlignMacrosMatches(Changes[I])) { + FoundMatchOnLine = true; + Shift = MinColumn - Changes[I].StartOfTokenColumn; + Changes[I].Spaces += Shift; + } + + assert(Shift >= 0); + Changes[I].StartOfTokenColumn += Shift; + if (I + 1 != Changes.size()) + Changes[I + 1].PreviousEndOfTokenColumn += Shift; + } + } + + MinColumn = 0; + MaxColumn = UINT_MAX; + StartOfSequence = 0; + EndOfSequence = 0; +} + +void WhitespaceManager::alignConsecutiveMacros() { + if (!Style.AlignConsecutiveMacros) + return; + + auto AlignMacrosMatches = [](const Change &C) { + const FormatToken *Current = C.Tok; + unsigned SpacesRequiredBefore = 1; + + if (Current->SpacesRequiredBefore == 0 || !Current->Previous) + return false; + + Current = Current->Previous; + + // If token is a ")", skip over the parameter list, to the + // token that precedes the "(" + if (Current->is(tok::r_paren) && Current->MatchingParen) { + Current = Current->MatchingParen->Previous; + SpacesRequiredBefore = 0; + } + + if (!Current || !Current->is(tok::identifier)) + return false; + + if (!Current->Previous || !Current->Previous->is(tok::pp_define)) + return false; + + // For a macro function, 0 spaces are required between the + // identifier and the lparen that opens the parameter list. + // For a simple macro, 1 space is required between the + // identifier and the first token of the defined value. + return Current->Next->SpacesRequiredBefore == SpacesRequiredBefore; + }; + + unsigned MinColumn = 0; + unsigned MaxColumn = UINT_MAX; + + // Start and end of the token sequence we're processing. + unsigned StartOfSequence = 0; + unsigned EndOfSequence = 0; + + // Whether a matching token has been found on the current line. + bool FoundMatchOnLine = false; + + unsigned I = 0; + for (unsigned E = Changes.size(); I != E; ++I) { + if (Changes[I].NewlinesBefore != 0) { + EndOfSequence = I; + // If there is a blank line, or if the last line didn't contain any + // matching token, the sequence ends here. + if (Changes[I].NewlinesBefore > 1 || !FoundMatchOnLine) + AlignMacroSequence(StartOfSequence, EndOfSequence, MinColumn, MaxColumn, + FoundMatchOnLine, AlignMacrosMatches, Changes); + + FoundMatchOnLine = false; + } + + if (!AlignMacrosMatches(Changes[I])) + continue; + + FoundMatchOnLine = true; + + if (StartOfSequence == 0) + StartOfSequence = I; + + unsigned ChangeMinColumn = Changes[I].StartOfTokenColumn; + int LineLengthAfter = -Changes[I].Spaces; + for (unsigned j = I; j != E && Changes[j].NewlinesBefore == 0; ++j) + LineLengthAfter += Changes[j].Spaces + Changes[j].TokenLength; + unsigned ChangeMaxColumn = Style.ColumnLimit - LineLengthAfter; + + MinColumn = std::max(MinColumn, ChangeMinColumn); + MaxColumn = std::min(MaxColumn, ChangeMaxColumn); + } + + EndOfSequence = I; + AlignMacroSequence(StartOfSequence, EndOfSequence, MinColumn, MaxColumn, + FoundMatchOnLine, AlignMacrosMatches, Changes); +} + void WhitespaceManager::alignConsecutiveAssignments() { if (!Style.AlignConsecutiveAssignments) return; diff --git a/lib/Format/WhitespaceManager.h b/lib/Format/WhitespaceManager.h index e19b2a5ab9..f47bf40204 100644 --- a/lib/Format/WhitespaceManager.h +++ b/lib/Format/WhitespaceManager.h @@ -171,6 +171,9 @@ private: /// \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(); + /// Align consecutive assignments over all \c Changes. void alignConsecutiveAssignments(); diff --git a/unittests/Format/FormatTest.cpp b/unittests/Format/FormatTest.cpp index 885aac0830..c4abad228d 100644 --- a/unittests/Format/FormatTest.cpp +++ b/unittests/Format/FormatTest.cpp @@ -10165,8 +10165,104 @@ TEST_F(FormatTest, ConfigurableSpaceBeforeColon) { NoSpaceStyle); } +TEST_F(FormatTest, AlignConsecutiveMacros) { + FormatStyle Style = getLLVMStyle(); + Style.AlignConsecutiveAssignments = true; + Style.AlignConsecutiveDeclarations = true; + Style.AlignConsecutiveMacros = false; + + verifyFormat("#define a 3\n" + "#define bbbb 4\n" + "#define ccc (5)", + Style); + + verifyFormat("#define f(x) (x * x)\n" + "#define fff(x, y, z) (x * y + z)\n" + "#define ffff(x, y) (x - y)", + Style); + + verifyFormat("#define foo(x, y) (x + y)\n" + "#define bar (5, 6)(2 + 2)", + Style); + + 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)", + Style); + + Style.AlignConsecutiveMacros = true; + verifyFormat("#define a 3\n" + "#define bbbb 4\n" + "#define ccc (5)", + Style); + + verifyFormat("#define f(x) (x * x)\n" + "#define fff(x, y, z) (x * y + z)\n" + "#define ffff(x, y) (x - y)", + Style); + + verifyFormat("#define foo(x, y) (x + y)\n" + "#define bar (5, 6)(2 + 2)", + Style); + + 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)", + Style); + + 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" + "};", + Style); + + Style.AlignConsecutiveMacros = false; + Style.ColumnLimit = 20; + + verifyFormat("#define a \\\n" + " \"aabbbbbbbbbbbb\"\n" + "#define D \\\n" + " \"aabbbbbbbbbbbb\" \\\n" + " \"ccddeeeeeeeee\"\n" + "#define B \\\n" + " \"QQQQQQQQQQQQQ\" \\\n" + " \"FFFFFFFFFFFFF\" \\\n" + " \"LLLLLLLL\"\n", + Style); + + Style.AlignConsecutiveMacros = true; + verifyFormat("#define a \\\n" + " \"aabbbbbbbbbbbb\"\n" + "#define D \\\n" + " \"aabbbbbbbbbbbb\" \\\n" + " \"ccddeeeeeeeee\"\n" + "#define B \\\n" + " \"QQQQQQQQQQQQQ\" \\\n" + " \"FFFFFFFFFFFFF\" \\\n" + " \"LLLLLLLL\"\n", + Style); +} + TEST_F(FormatTest, AlignConsecutiveAssignments) { FormatStyle Alignment = getLLVMStyle(); + Alignment.AlignConsecutiveMacros = true; Alignment.AlignConsecutiveAssignments = false; verifyFormat("int a = 5;\n" "int oneTwoThree = 123;", @@ -10356,6 +10452,7 @@ TEST_F(FormatTest, AlignConsecutiveAssignments) { TEST_F(FormatTest, AlignConsecutiveDeclarations) { FormatStyle Alignment = getLLVMStyle(); + Alignment.AlignConsecutiveMacros = true; Alignment.AlignConsecutiveDeclarations = false; verifyFormat("float const a = 5;\n" "int oneTwoThree = 123;", @@ -11505,6 +11602,7 @@ TEST_F(FormatTest, ParsesConfigurationBools) { CHECK_PARSE_BOOL(AlignTrailingComments); CHECK_PARSE_BOOL(AlignConsecutiveAssignments); CHECK_PARSE_BOOL(AlignConsecutiveDeclarations); + CHECK_PARSE_BOOL(AlignConsecutiveMacros); CHECK_PARSE_BOOL(AllowAllArgumentsOnNextLine); CHECK_PARSE_BOOL(AllowAllConstructorInitializersOnNextLine); CHECK_PARSE_BOOL(AllowAllParametersOfDeclarationOnNextLine);