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.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: 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: 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;