Index: lib/Format/BreakableToken.h =================================================================== --- lib/Format/BreakableToken.h +++ lib/Format/BreakableToken.h @@ -21,6 +21,7 @@ #include "Encoding.h" #include "TokenAnnotator.h" #include "WhitespaceManager.h" +#include "llvm/ADT/StringSet.h" #include "llvm/Support/Regex.h" #include @@ -135,6 +136,19 @@ virtual unsigned getContentStartColumn(unsigned LineIndex, bool Break) const = 0; + /// Returns additional content indent required for the second line after the + /// content at line \p LineIndex is broken. + /// + /// For example, Javadoc @param annotations require and indent of 4 spaces and + /// in this example getContentIndex(1) returns 4. + /// /** + /// * @param loooooooooooooong line + /// * continuation + /// */ + virtual unsigned getContentIndent(unsigned LineIndex) const { + return 0; + } + /// Returns a range (offset, length) at which to break the line at /// \p LineIndex, if previously broken at \p TailOffset. If possible, do not /// violate \p ColumnLimit, assuming the text starting at \p TailOffset in @@ -146,6 +160,7 @@ /// Emits the previously retrieved \p Split via \p Whitespaces. virtual void insertBreak(unsigned LineIndex, unsigned TailOffset, Split Split, + unsigned ContentIndent, WhitespaceManager &Whitespaces) const = 0; /// Returns the number of columns needed to format @@ -210,7 +225,7 @@ Split SplitAfterLastLine, WhitespaceManager &Whitespaces) const { insertBreak(getLineCount() - 1, TailOffset, SplitAfterLastLine, - Whitespaces); + /*ContentIndent=*/0, Whitespaces); } /// Updates the next token of \p State to the next token after this @@ -245,6 +260,7 @@ unsigned ContentStartColumn, llvm::Regex &CommentPragmasRegex) const override; void insertBreak(unsigned LineIndex, unsigned TailOffset, Split Split, + unsigned ContentIndent, WhitespaceManager &Whitespaces) const override; void compressWhitespace(unsigned LineIndex, unsigned TailOffset, Split Split, WhitespaceManager &Whitespaces) const override {} @@ -354,7 +370,9 @@ unsigned getRemainingLength(unsigned LineIndex, unsigned Offset, unsigned StartColumn) const override; unsigned getContentStartColumn(unsigned LineIndex, bool Break) const override; + unsigned getContentIndent(unsigned LineIndex) const override; void insertBreak(unsigned LineIndex, unsigned TailOffset, Split Split, + unsigned ContentIndent, WhitespaceManager &Whitespaces) const override; Split getReflowSplit(unsigned LineIndex, llvm::Regex &CommentPragmasRegex) const override; @@ -368,6 +386,10 @@ bool mayReflow(unsigned LineIndex, llvm::Regex &CommentPragmasRegex) const override; + // Contains Javadoc annotations that require additional indent when continued + // on multiple lines. + static const llvm::StringSet<> ContentIndentingJavadocAnnotations; + private: // Rearranges the whitespace between Lines[LineIndex-1] and Lines[LineIndex]. // @@ -423,6 +445,7 @@ unsigned StartColumn) const override; unsigned getContentStartColumn(unsigned LineIndex, bool Break) const override; void insertBreak(unsigned LineIndex, unsigned TailOffset, Split Split, + unsigned ContentIndent, WhitespaceManager &Whitespaces) const override; Split getReflowSplit(unsigned LineIndex, llvm::Regex &CommentPragmasRegex) const override; Index: lib/Format/BreakableToken.cpp =================================================================== --- lib/Format/BreakableToken.cpp +++ lib/Format/BreakableToken.cpp @@ -235,6 +235,7 @@ void BreakableStringLiteral::insertBreak(unsigned LineIndex, unsigned TailOffset, Split Split, + unsigned ContentIndent, WhitespaceManager &Whitespaces) const { Whitespaces.replaceWhitespaceInToken( Tok, Prefix.size() + TailOffset + Split.first, Split.second, Postfix, @@ -510,8 +511,25 @@ return std::max(0, ContentColumn[LineIndex]); } +const llvm::StringSet<> + BreakableBlockComment::ContentIndentingJavadocAnnotations = { + "@param", "@return", "@throws", "@type", + "@template", "@see", "@deprecated", "@define", +}; + +unsigned BreakableBlockComment::getContentIndent(unsigned LineIndex) const { + if (Style.Language != FormatStyle::LK_Java && + Style.Language != FormatStyle::LK_JavaScript) + return 0; + StringRef FirstWord = Content[LineIndex].substr( + 0, Content[LineIndex].find_first_of(Blanks)); + if (FirstWord == "@param") + return Style.ContinuationIndentWidth; + return 0; +} + void BreakableBlockComment::insertBreak(unsigned LineIndex, unsigned TailOffset, - Split Split, + Split Split, unsigned ContentIndent, WhitespaceManager &Whitespaces) const { StringRef Text = Content[LineIndex].substr(TailOffset); StringRef Prefix = Decoration; @@ -532,10 +550,14 @@ Text.data() - tokenAt(LineIndex).TokenText.data() + Split.first; unsigned CharsToRemove = Split.second; assert(LocalIndentAtLineBreak >= Prefix.size()); + std::string PrefixWithTrailingIndent = Prefix; + for (unsigned I = 0; I < ContentIndent; ++I) + PrefixWithTrailingIndent += " "; Whitespaces.replaceWhitespaceInToken( - tokenAt(LineIndex), BreakOffsetInToken, CharsToRemove, "", Prefix, - InPPDirective, /*Newlines=*/1, - /*Spaces=*/LocalIndentAtLineBreak - Prefix.size()); + tokenAt(LineIndex), BreakOffsetInToken, CharsToRemove, "", + PrefixWithTrailingIndent, InPPDirective, /*Newlines=*/1, + /*Spaces=*/LocalIndentAtLineBreak + ContentIndent - + PrefixWithTrailingIndent.size()); } BreakableToken::Split @@ -543,8 +565,17 @@ llvm::Regex &CommentPragmasRegex) const { if (!mayReflow(LineIndex, CommentPragmasRegex)) return Split(StringRef::npos, 0); - + + // If we're reflowing into a line with content indent, only reflow the next + // line if its starting whitespace matches the content indent. size_t Trimmed = Content[LineIndex].find_first_not_of(Blanks); + if (LineIndex) { + unsigned PreviousContentIndent = getContentIndent(LineIndex - 1); + if (PreviousContentIndent && Trimmed != StringRef::npos && + Trimmed != PreviousContentIndent) + return Split(StringRef::npos, 0); + } + return Split(0, Trimmed != StringRef::npos ? Trimmed : 0); } @@ -583,7 +614,8 @@ // break length are the same. size_t BreakLength = Lines[0].substr(1).find_first_not_of(Blanks); if (BreakLength != StringRef::npos) - insertBreak(LineIndex, 0, Split(1, BreakLength), Whitespaces); + insertBreak(LineIndex, 0, Split(1, BreakLength), /*ContentIndent=*/0, + Whitespaces); } return; } @@ -754,7 +786,7 @@ void BreakableLineCommentSection::insertBreak( unsigned LineIndex, unsigned TailOffset, Split Split, - WhitespaceManager &Whitespaces) const { + unsigned ContentIndent, WhitespaceManager &Whitespaces) const { StringRef Text = Content[LineIndex].substr(TailOffset); // Compute the offset of the split relative to the beginning of the token // text. Index: lib/Format/ContinuationIndenter.cpp =================================================================== --- lib/Format/ContinuationIndenter.cpp +++ lib/Format/ContinuationIndenter.cpp @@ -1782,6 +1782,7 @@ if (!DryRun) Token->adaptStartOfLine(0, Whitespaces); + unsigned ContentIndent = 0; unsigned Penalty = 0; LLVM_DEBUG(llvm::dbgs() << "Breaking protruding token at column " << StartColumn << ".\n"); @@ -1903,8 +1904,15 @@ } } LLVM_DEBUG(llvm::dbgs() << " Breaking...\n"); - ContentStartColumn = - Token->getContentStartColumn(LineIndex, /*Break=*/true); + // Update the ContentIndent only if the current line was not reflown with + // the previous line, since in that case the previous line should still + // determine the ContentIndent. + if (!Reflow) ContentIndent = Token->getContentIndent(LineIndex); + LLVM_DEBUG(llvm::dbgs() + << " ContentIndent: " << ContentIndent << "\n"); + ContentStartColumn = ContentIndent + Token->getContentStartColumn( + LineIndex, /*Break=*/true); + unsigned NewRemainingTokenColumns = Token->getRemainingLength( LineIndex, TailOffset + Split.first + Split.second, ContentStartColumn); @@ -1921,7 +1929,8 @@ LLVM_DEBUG(llvm::dbgs() << " Breaking at: " << TailOffset + Split.first << ", " << Split.second << "\n"); if (!DryRun) - Token->insertBreak(LineIndex, TailOffset, Split, Whitespaces); + Token->insertBreak(LineIndex, TailOffset, Split, ContentIndent, + Whitespaces); Penalty += NewBreakPenalty; TailOffset += Split.first + Split.second; Index: unittests/Format/FormatTestComments.cpp =================================================================== --- unittests/Format/FormatTestComments.cpp +++ unittests/Format/FormatTestComments.cpp @@ -3105,6 +3105,87 @@ // clang-format on } +TEST_F(FormatTestComments, IndentsLongJavadocAnnotatedLines) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_Java); + Style.ColumnLimit = 60; + FormatStyle Style20 = getGoogleStyle(FormatStyle::LK_Java); + Style20.ColumnLimit = 20; + EXPECT_EQ( + "/**\n" + " * @param x long long long long long long long long long\n" + " * long\n" + " */\n", + format("/**\n" + " * @param x long long long long long long long long long long\n" + " */\n", + Style)); + EXPECT_EQ("/**\n" + " * @param x long long long long long long long long long\n" + " * long long long long long long long long long long\n" + " */\n", + format("/**\n" + " * @param x long long long long long long long long long " + "long long long long long long long long long long\n" + " */\n", + Style)); + EXPECT_EQ("/**\n" + " * @param x long long long long long long long long long\n" + " * long long long long long long long long long long\n" + " * long\n" + " */\n", + format("/**\n" + " * @param x long long long long long long long long long " + "long long long long long long long long long long long\n" + " */\n", + Style)); + EXPECT_EQ( + "/**\n" + " * Sentence that\n" + " * should be broken.\n" + " * @param short\n" + " * keep indentation\n" + " */\n", format( + "/**\n" + " * Sentence that should be broken.\n" + " * @param short\n" + " * keep indentation\n" + " */\n", Style20)); + + EXPECT_EQ("/**\n" + " * @param l1 long1\n" + " * to break\n" + " * @param l2 long2\n" + " * to break\n" + " */\n", + format("/**\n" + " * @param l1 long1 to break\n" + " * @param l2 long2 to break\n" + " */\n", + Style20)); + + EXPECT_EQ("/**\n" + " * @param xx to\n" + " * break\n" + " * no reflow\n" + " */\n", + format("/**\n" + " * @param xx to break\n" + " * no reflow\n" + " */\n", + Style20)); + + EXPECT_EQ("/**\n" + " * @param xx to\n" + " * break yes\n" + " * reflow\n" + " */\n", + format("/**\n" + " * @param xx to break\n" + " * yes reflow\n" + " */\n", + Style20)); +} + } // end namespace } // end namespace format } // end namespace clang Index: unittests/Format/FormatTestJS.cpp =================================================================== --- unittests/Format/FormatTestJS.cpp +++ unittests/Format/FormatTestJS.cpp @@ -2058,8 +2058,8 @@ verifyFormat( "/**\n" " * @param This is a\n" - " * long comment but\n" - " * no type\n" + " * long comment\n" + " * but no type\n" " */", "/**\n" " * @param This is a long comment but no type\n"