berenm created this revision.
berenm added a reviewer: djasper.
berenm added a subscriber: cfe-commits.
Herald added a subscriber: klimek.

This allows clang-format to align identifiers in consecutive declarations.

This is, arguably, a feature useful for increasing the readability of the code,
in the same way the alignment of assignations is. It is also present in other
tools such as uncrustify for example.

The code is a slightly modified version of the consecutive assignment
alignment code. Currently only the identifiers are aligned, and there is no
support of alignment of the pointer star or reference symbol.

http://reviews.llvm.org/D12362

Files:
  include/clang/Format/Format.h
  lib/Format/Format.cpp
  lib/Format/WhitespaceManager.cpp
  lib/Format/WhitespaceManager.h
  unittests/Format/FormatTest.cpp

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);
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: 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: 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 &&
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to