In Objective-C some style guides use a style where property declarations are 
aligned, to help with code readability. I've added an option which enable this 
functionality. It is disabled by default.

The option will align code so

```
@property(nonatomic, weak) NSNumber *text;
@property(nonatomic) BOOL trueOrFalse;
```

becomes

```
@property(nonatomic, weak) NSNumber *text;
@property(nonatomic)       BOOL     trueOrFalse;
```

http://reviews.llvm.org/D9433

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

EMAIL PREFERENCES
  http://reviews.llvm.org/settings/panel/emailpreferences/
Index: docs/ClangFormatStyleOptions.rst
===================================================================
--- docs/ClangFormatStyleOptions.rst
+++ docs/ClangFormatStyleOptions.rst
@@ -398,6 +398,19 @@
     Indent in all namespaces.
 
 
+**ObjCAlignPropertyDeclaration** (``bool``)
+  If ``true``, aligns Objective-C property declaration to increase
+  readability.
+
+  This will align the Objective-C property declaration that are on
+  consecutive lines. This will result in formattings like
+  \code
+  @property (nonatomic, strong)           NSMutableString \*text;
+  @property (nonatomic, strong, readonly) NSString        \*readonly;
+  @property (nonatomic, weak)             NSNumber        \*text;
+  @property (nonatomic)                   BOOL            trueOrFalse;
+  \endcode
+
 **ObjCBlockIndentWidth** (``unsigned``)
   The number of characters to use for indentation of ObjC blocks.
 
Index: include/clang/Format/Format.h
===================================================================
--- include/clang/Format/Format.h
+++ include/clang/Format/Format.h
@@ -262,6 +262,19 @@
   /// Otherwise puts them into the right-most column.
   bool AlignEscapedNewlinesLeft;
 
+  /// \brief If \c true, aligns Objective-C property declaration to increase
+  /// readability.
+  ///
+  /// This will align the Objective-C property declaration that are on
+  /// consecutive lines. This will result in formattings like
+  /// \code
+  /// @property (nonatomic, strong)           NSMutableString *text;
+  /// @property (nonatomic, strong, readonly) NSString        *readonly;
+  /// @property (nonatomic, weak)             NSNumber        *text;
+  /// @property (nonatomic)                   BOOL            trueOrFalse;
+  /// \endcode
+  bool ObjCAlignPropertyDeclaration;
+
   /// \brief The number of columns to use for indentation.
   unsigned IndentWidth;
 
Index: lib/Format/Format.cpp
===================================================================
--- lib/Format/Format.cpp
+++ lib/Format/Format.cpp
@@ -175,6 +175,7 @@
     IO.mapOptional("AlignOperands", Style.AlignOperands);
     IO.mapOptional("AlignTrailingComments", Style.AlignTrailingComments);
     IO.mapOptional("AlignConsecutiveAssignments", Style.AlignConsecutiveAssignments);
+    IO.mapOptional("ObjCAlignPropertyDeclaration", Style.ObjCAlignPropertyDeclaration);
     IO.mapOptional("AllowAllParametersOfDeclarationOnNextLine",
                    Style.AllowAllParametersOfDeclarationOnNextLine);
     IO.mapOptional("AllowShortBlocksOnASingleLine",
@@ -364,6 +365,7 @@
   LLVMStyle.MaxEmptyLinesToKeep = 1;
   LLVMStyle.KeepEmptyLinesAtTheStartOfBlocks = true;
   LLVMStyle.NamespaceIndentation = FormatStyle::NI_None;
+  LLVMStyle.ObjCAlignPropertyDeclaration = false;
   LLVMStyle.ObjCBlockIndentWidth = 2;
   LLVMStyle.ObjCSpaceAfterProperty = false;
   LLVMStyle.ObjCSpaceBeforeProtocolList = true;
Index: lib/Format/WhitespaceManager.cpp
===================================================================
--- lib/Format/WhitespaceManager.cpp
+++ lib/Format/WhitespaceManager.cpp
@@ -29,12 +29,13 @@
     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,
+    tok::ObjCKeywordKind ObjCKind, bool ContinuesPPDirective)
     : CreateReplacement(CreateReplacement),
       OriginalWhitespaceRange(OriginalWhitespaceRange),
       StartOfTokenColumn(StartOfTokenColumn), NewlinesBefore(NewlinesBefore),
       PreviousLinePostfix(PreviousLinePostfix),
-      CurrentLinePrefix(CurrentLinePrefix), Kind(Kind),
+      CurrentLinePrefix(CurrentLinePrefix), Kind(Kind), ObjCKind(ObjCKind),
       ContinuesPPDirective(ContinuesPPDirective), IndentLevel(IndentLevel),
       Spaces(Spaces), IsTrailingComment(false), TokenLength(0),
       PreviousEndOfTokenColumn(0), EscapedNewlineColumn(0),
@@ -54,7 +55,8 @@
   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));
+                           Tok.Tok.getKind(), Tok.Tok.getObjCKeywordID(),
+                           InPPDirective && !Tok.IsFirst));
 }
 
 void WhitespaceManager::addUntouchableToken(const FormatToken &Tok,
@@ -64,6 +66,7 @@
   Changes.push_back(Change(false, Tok.WhitespaceRange, /*IndentLevel=*/0,
                            /*Spaces=*/0, Tok.OriginalColumn, Tok.NewlinesBefore,
                            "", "", Tok.Tok.getKind(),
+                           Tok.Tok.getObjCKeywordID(),
                            InPPDirective && !Tok.IsFirst));
 }
 
@@ -84,7 +87,7 @@
       // 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));
+      Tok.Tok.getObjCKeywordID(), InPPDirective && !Tok.IsFirst));
 }
 
 const tooling::Replacements &WhitespaceManager::generateReplacements() {
@@ -94,6 +97,7 @@
   std::sort(Changes.begin(), Changes.end(), Change::IsBeforeInFile(SourceMgr));
   calculateLineBreakInformation();
   alignConsecutiveAssignments();
+  alignObjCPropertyDeclarations();
   alignTrailingComments();
   alignEscapedNewlines();
   generateChanges();
@@ -232,6 +236,189 @@
   }
 }
 
+// Walk through all of the changed and find the property declarations to align.
+// We do so in two passes. The first will find consecutive lines which begin
+// with a property token and then find the start of the type declaration,
+// the first token after the right parenthesis. These are then aligned. In the
+// second pass we detect the same property tokens but look for the ending
+// semi-colon. We then align on either the variable name (if there is no
+// pointer or the pointer is not right aligned) or the pointer symbol.
+void WhitespaceManager::alignObjCPropertyDeclarations() {
+  if (!Style.ObjCAlignPropertyDeclaration)
+    return;
+
+  unsigned StartOfSequence = 0;
+  unsigned EndOfSequence = 0;
+  bool FoundPropertyOnLine = false;
+  bool FoundRightParenOnLine = false;
+  bool FoundSemiColonOnLine = false;
+  unsigned MinTypeColumn = 0;
+  unsigned MinVariableColumn = 0;
+
+  auto AlignSequence = [&] {
+    if (MinTypeColumn > 0) {
+      alignObjCPropertyTypeDeclarations(StartOfSequence, EndOfSequence,
+                                        MinTypeColumn);
+    } else if (MinVariableColumn > 0) {
+      alignObjCPropertyVariableDeclarations(StartOfSequence, EndOfSequence,
+                                            MinVariableColumn);
+    }
+    StartOfSequence = 0;
+    EndOfSequence = 0;
+    MinTypeColumn = 0;
+    MinVariableColumn = 0;
+  };
+
+  auto BeginNewline = [&](unsigned i) {
+    if (StartOfSequence > 0 &&
+        (Changes[i].NewlinesBefore > 1 || !FoundPropertyOnLine ||
+         (FoundPropertyOnLine &&
+          (!FoundRightParenOnLine || !FoundSemiColonOnLine)))) {
+      EndOfSequence = i - 1;
+      AlignSequence();
+    }
+    FoundPropertyOnLine = false;
+    FoundRightParenOnLine = false;
+    FoundSemiColonOnLine = false;
+  };
+
+  for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
+    if (Changes[i].NewlinesBefore != 0)
+      BeginNewline(i);
+
+    if (Changes[i].ObjCKind == tok::objc_property) {
+      FoundPropertyOnLine = true;
+      if (StartOfSequence == 0)
+        StartOfSequence = i;
+    } else if (FoundPropertyOnLine && Changes[i].Kind == tok::r_paren &&
+               !FoundRightParenOnLine) {
+      FoundRightParenOnLine = true;
+      if (i + 1 != Changes.size()) {
+        unsigned ChangeTypeColumn = Changes[i + 1].StartOfTokenColumn;
+        MinTypeColumn = std::max(MinTypeColumn, ChangeTypeColumn);
+      }
+    } else if (Changes[i].Kind == tok::semi && !FoundSemiColonOnLine) {
+      FoundSemiColonOnLine = true;
+    }
+  }
+
+  if (StartOfSequence > 0) {
+    EndOfSequence = Changes.size();
+    AlignSequence();
+  }
+
+  StartOfSequence = 0;
+  EndOfSequence = 0;
+  FoundPropertyOnLine = false;
+  FoundRightParenOnLine = false;
+  FoundSemiColonOnLine = false;
+  MinTypeColumn = 0;
+  MinVariableColumn = 0;
+
+  for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
+    // Check if we started a newline and if a sequence ended. Align it if we
+    // did.
+    if (Changes[i].NewlinesBefore != 0)
+      BeginNewline(i);
+
+    if (Changes[i].ObjCKind == tok::objc_property) {
+      FoundPropertyOnLine = true;
+      if (StartOfSequence == 0)
+        StartOfSequence = i;
+    } else if (FoundPropertyOnLine && Changes[i].Kind == tok::r_paren &&
+               !FoundRightParenOnLine) {
+      FoundRightParenOnLine = true;
+    } else if (FoundRightParenOnLine && Changes[i].Kind == tok::semi &&
+               !FoundSemiColonOnLine) {
+      FoundSemiColonOnLine = true;
+      if (Changes[i - 1].Kind == tok::identifier) {
+        unsigned VariableChangeIndex = i - 1;
+        if (Style.PointerAlignment == FormatStyle::PAS_Right &&
+            Changes[i - 2].Kind == tok::star)
+          VariableChangeIndex = i - 2;
+        unsigned ChangeVariableColumn =
+            Changes[VariableChangeIndex].StartOfTokenColumn;
+        MinVariableColumn = std::max(MinVariableColumn, ChangeVariableColumn);
+      }
+    }
+  }
+
+  if (StartOfSequence > 0) {
+    EndOfSequence = Changes.size();
+    AlignSequence();
+  }
+}
+
+void WhitespaceManager::alignObjCPropertyTypeDeclarations(unsigned Start,
+                                                          unsigned End,
+                                                          unsigned Column) {
+  bool FoundRightParenOnLine = false;
+  unsigned PreviousShift = 0;
+  for (unsigned i = Start; i != End; ++i) {
+    int Shift = 0;
+    if (Changes[i].NewlinesBefore > 0) {
+      FoundRightParenOnLine = false;
+      PreviousShift = 0;
+    }
+
+    if (Changes[i].Kind == tok::r_paren && !FoundRightParenOnLine) {
+      FoundRightParenOnLine = true;
+      if (i + 1 != Changes.size()) {
+        Shift = Column - Changes[i + 1].StartOfTokenColumn;
+        assert(Shift >= 0);
+        Changes[i + 1].Spaces += Shift;
+        if (i + 2 != Changes.size())
+          Changes[i + 2].PreviousEndOfTokenColumn += Shift;
+        Changes[i + 1].StartOfTokenColumn += Shift;
+        PreviousShift = Shift;
+      }
+    }
+
+    if (FoundRightParenOnLine) {
+      Changes[i].StartOfTokenColumn += PreviousShift;
+      if (i + 1 != Changes.size())
+        Changes[i + 1].PreviousEndOfTokenColumn += PreviousShift;
+    }
+  }
+}
+
+void WhitespaceManager::alignObjCPropertyVariableDeclarations(unsigned Start,
+                                                              unsigned End,
+                                                              unsigned Column) {
+  bool FoundSemiColonOnLine = false;
+  unsigned PreviousShift = 0;
+  for (unsigned i = Start; i != End; ++i) {
+    int Shift = 0;
+    if (Changes[i].NewlinesBefore > 0) {
+      FoundSemiColonOnLine = false;
+      PreviousShift = 0;
+    }
+
+    if (Changes[i].Kind == tok::semi && !FoundSemiColonOnLine) {
+      FoundSemiColonOnLine = true;
+      if (Changes[i - 1].Kind == tok::identifier) {
+        unsigned VariableChangeIndex = i - 1;
+        if (Style.PointerAlignment == FormatStyle::PAS_Right &&
+            Changes[i - 2].Kind == tok::star)
+          VariableChangeIndex = i - 2;
+        Shift = Column - Changes[VariableChangeIndex].StartOfTokenColumn;
+        assert(Shift >= 0);
+        Changes[VariableChangeIndex].Spaces += Shift;
+        if (VariableChangeIndex + 1 != Changes.size())
+          Changes[VariableChangeIndex + 1].PreviousEndOfTokenColumn += Shift;
+        Changes[VariableChangeIndex + 1].StartOfTokenColumn += Shift;
+        PreviousShift = Shift;
+      }
+    }
+
+    if (FoundSemiColonOnLine) {
+      Changes[i].StartOfTokenColumn += PreviousShift;
+      if (i + 1 != Changes.size())
+        Changes[i + 1].PreviousEndOfTokenColumn += PreviousShift;
+    }
+  }
+}
+
 void WhitespaceManager::alignTrailingComments() {
   unsigned MinColumn = 0;
   unsigned MaxColumn = UINT_MAX;
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);
+           tok::ObjCKeywordKind ObjCKind, bool ContinuesPPDirective);
 
     bool CreateReplacement;
     // Changes might be in the middle of a token, so we cannot just keep the
@@ -125,6 +125,7 @@
     // FIXME: Currently this is not set correctly for breaks inside comments, as
     // the \c BreakableToken is still doing its own alignment.
     tok::TokenKind Kind;
+    tok::ObjCKeywordKind ObjCKind;
     bool ContinuesPPDirective;
 
     // The number of nested blocks the token is in. This is used to add tabs
@@ -171,6 +172,18 @@
   /// the specified \p Column.
   void alignConsecutiveAssignments(unsigned Start, unsigned End, unsigned Column);
 
+  /// \brief Aligns Objective-C property declarations over all \c Changes.
+  void alignObjCPropertyDeclarations();
+
+  /// \brief Aligns the type declaration part of Objective-C property
+  /// declarations from change \p Start to change \p End at the specified
+  /// \p Column.
+  void alignObjCPropertyTypeDeclarations(unsigned Start, unsigned End, unsigned Column);
+
+  /// \brief Aligns the variable name part of Objective-C property declarations
+  /// from change \p Start to change \p End at the specified \p Column.
+  void alignObjCPropertyVariableDeclarations(unsigned Start, unsigned End, unsigned Column);
+
   /// \brief Align trailing comments over all \c Changes.
   void alignTrailingComments();
 
Index: unittests/Format/FormatTest.cpp
===================================================================
--- unittests/Format/FormatTest.cpp
+++ unittests/Format/FormatTest.cpp
@@ -8549,6 +8549,110 @@
       Alignment);
 }
 
+TEST_F(FormatTest, ObjCAlignPropertyDeclaration) {
+  FormatStyle PropertyAlignment = getLLVMStyle();
+  PropertyAlignment.ObjCAlignPropertyDeclaration = false;
+  verifyFormat("@property(nonatomic, strong) NSMutableString *text;\n"
+               "@property(nonatomic, strong, readonly) NSString *readonly;\n"
+               "@property(nonatomic, weak) NSNumber *text;\n"
+               "@property(nonatomic) BOOL trueOrFalse;",
+               PropertyAlignment);
+
+  PropertyAlignment.ObjCAlignPropertyDeclaration = true;
+  verifyFormat("@property(nonatomic, strong)           NSMutableString *text;\n"
+               "@property(nonatomic, strong, readonly) NSString        *readonly;\n"
+               "@property(nonatomic, weak)             NSNumber        *text;\n"
+               "@property(nonatomic)                   BOOL            trueOrFalse;",
+               PropertyAlignment);
+  verifyFormat("@property(nonatomic, strong)           NSMutableString *text;\n"
+               "@property(nonatomic, strong, readonly) NSString        *readonly;\n"
+               "// Nothing here\n"
+               "@property(nonatomic, weak) NSNumber *text;\n"
+               "@property(nonatomic)       BOOL     trueOrFalse;",
+               PropertyAlignment);
+  verifyFormat("@property(nonatomic, strong, readonly) NSString        *readonly;\n"
+               "@property(nonatomic, strong)           NSMutableString *text;\n"
+               "// Nothing here\n"
+               "@property(nonatomic)       BOOL     trueOrFalse;\n"
+               "@property(nonatomic, weak) NSNumber *text;\n",
+               PropertyAlignment);
+  verifyFormat("@property(nonatomic, strong, getter=iNeedText) NSMutableString *text;\n"
+               "@property(nonatomic, strong, readonly)         NSString        *readonly;",
+               PropertyAlignment);
+  verifyFormat("@property(nonatomic, strong)           NSArray /* NSString */ *text;\n"
+               "@property(nonatomic, strong, readonly) NSString               *readonly;",
+               PropertyAlignment);
+
+  verifyFormat("@property(nonatomic) BOOL trueOrFalse;\n"
+               "@property(nonatomic, strong)\n"
+               "    NSLooooooooooooooonnnnnnngggggggTyyyyyppppppeeee *loooooooooonnnnngVariable;\n"
+               "@property(nonatomic, weak)           NSNumber *text;\n"
+               "@property(nonatomic, weak, readonly) NSArray  *array;",
+               PropertyAlignment);
+  verifyFormat("@property(nonatomic, strong)\n"
+               "    NSLooooooooooooooonnnnnnngggggggTyyyyyppppppeeee *loooooooooonnnnngVariable;\n"
+               "@property(nonatomic, weak)           NSNumber *text;\n"
+               "@property(nonatomic, weak, readonly) NSArray  *array;",
+               PropertyAlignment);
+  verifyFormat("@property(nonatomic, weak)   NSNumberThingy *text;\n"
+               "@property(nonatomic, strong) NSArray        *array;\n"
+               "@property(nonatomic, strong)\n"
+               "    NSLooooooooooooooonnnnnnngggggggTyyyyyppppppeeee *loooooooooonnnnngVariable;\n"
+               "@property(nonatomic, weak)           NSNumber *text;\n"
+               "@property(nonatomic, weak, readonly) NSArray  *array;",
+               PropertyAlignment);
+  verifyFormat("@property(nonatomic, weak)           NSNumber *text;\n"
+               "@property(nonatomic, weak, readonly) NSArray  *array;\n"
+               "@property(nonatomic, strong)\n"
+               "    NSLooooooooooooooonnnnnnngggggggTyyyyyppppppeeee *loooooooooonnnnngVariable;",
+               PropertyAlignment);
+  verifyFormat("@property(nonatomic, strong)           NSMutableString *text;\n"
+               "@property(nonatomic, strong, readonly) NSString *\n"
+               "    loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongVariableName;\n"
+               "@property(nonatomic, weak) NSNumber *text;\n"
+               "@property(nonatomic)       BOOL     trueOrFalse;",
+               PropertyAlignment);
+
+  PropertyAlignment.AlignTrailingComments = false;
+  verifyFormat("@property(nonatomic, strong)           NSMutableString *text; // Comment\n"
+               "@property(nonatomic, strong, readonly) NSString        *readonly; // Comment",
+               PropertyAlignment);
+  verifyFormat("@property(nonatomic, strong, readonly) NSString        *readonly; // Comment\n"
+               "@property(nonatomic, strong)           NSMutableString *text; // Comment",
+               PropertyAlignment);
+  PropertyAlignment.AlignTrailingComments = true;
+  verifyFormat("@property(nonatomic, strong)           NSMutableString *text;     // Comment\n"
+               "@property(nonatomic, strong, readonly) NSString        *readonly; // Comment",
+               PropertyAlignment);
+  verifyFormat("@property(nonatomic, strong, readonly) NSString        *readonly; // Comment\n"
+               "@property(nonatomic, strong)           NSMutableString *text;     // Comment",
+               PropertyAlignment);
+
+  PropertyAlignment.ObjCSpaceAfterProperty = true;
+  verifyFormat("@property (nonatomic, strong)           NSMutableString *text;\n"
+               "@property (nonatomic, strong, readonly) NSString        *readonly;\n"
+               "@property (nonatomic, weak)             NSNumber        *text;\n"
+               "@property (nonatomic)                   BOOL            trueOrFalse;",
+               PropertyAlignment);
+  PropertyAlignment.PointerAlignment = FormatStyle::PAS_Left;
+  verifyFormat("@property (nonatomic, strong)           NSMutableString* text;\n"
+               "@property (nonatomic, strong, readonly) NSString*        readonly;",
+               PropertyAlignment);
+  PropertyAlignment.PointerAlignment = FormatStyle::PAS_Middle;
+  verifyFormat("@property (nonatomic, strong)           NSMutableString * text;\n"
+               "@property (nonatomic, strong, readonly) NSString *        readonly;",
+               PropertyAlignment);
+  PropertyAlignment.ObjCSpaceAfterProperty = false;
+  PropertyAlignment.PointerAlignment = FormatStyle::PAS_Left;
+  verifyFormat("@property(nonatomic, strong)           NSMutableString* text;\n"
+               "@property(nonatomic, strong, readonly) NSString*        readonly;",
+               PropertyAlignment);
+  PropertyAlignment.PointerAlignment = FormatStyle::PAS_Middle;
+  verifyFormat("@property(nonatomic, strong)           NSMutableString * text;\n"
+               "@property(nonatomic, strong, readonly) NSString *        readonly;",
+               PropertyAlignment);
+}
+
 TEST_F(FormatTest, LinuxBraceBreaking) {
   FormatStyle LinuxBraceStyle = getLLVMStyle();
   LinuxBraceStyle.BreakBeforeBraces = FormatStyle::BS_Linux;
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits

Reply via email to