diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -3335,6 +3335,43 @@ var arr = [ 1, 2, 3 ]; vs. var arr = [1, 2, 3]; f({a : 1, b : 2, c : 3}); f({a: 1, b: 2, c: 3}); +**SpacesInLineCommentPrefix** (``SpacesInLineComment``) + How many spaces are allowed at the start of a line comment. To disable the + maximum set it to ``-1``, apart from that the maximum takes precedence + over the minimum. + Minimum = 1 Maximum = -1 + // One space is forced + + // but more spaces are possible + + Minimum = 0 + Maximum = 0 + //Forces to start every comment directly after the slashes + + Note that in line comment sections the relative indent of the subsequent + lines is kept, that means the following: + + .. code-block:: c++ + + before: after: + Minimum: 1 + //if (b) { // if (b) { + // return true; // return true; + //} // } + + Maximum: 0 + /// List: ///List: + /// - Foo /// - Foo + /// - Bar /// - Bar + + Nested configuration flags: + + + * ``unsigned Minimum`` The minimum number of spaces at the start of the comment. + + * ``unsigned Maximum`` The maximum number of spaces at the start of the comment. + + **SpacesInParentheses** (``bool``) If ``true``, spaces will be inserted after ``(`` and before ``)``. diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -156,8 +156,8 @@ clang-format ------------ -- ... - +- Option ``SpacesInLineCommentPrefix`` has been added to control the + number of spaces in a line comments prefix. libclang -------- diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -2894,6 +2894,43 @@ /// \endcode bool SpacesInCStyleCastParentheses; + /// Control of spaces within a single line comment + struct SpacesInLineComment { + /// The minimum number of spaces at the start of the comment. + unsigned Minimum; + /// The maximum number of spaces at the start of the comment. + unsigned Maximum; + }; + + /// How many spaces are allowed at the start of a line comment. To disable the + /// maximum set it to ``-1``, apart from that the maximum takes precedence + /// over the minimum. + /// \code Minimum = 1 Maximum = -1 + /// // One space is forced + /// + /// // but more spaces are possible + /// + /// Minimum = 0 + /// Maximum = 0 + /// //Forces to start every comment directly after the slashes + /// \endcode + /// + /// Note that in line comment sections the relative indent of the subsequent + /// lines is kept, that means the following: + /// \code + /// before: after: + /// Minimum: 1 + /// //if (b) { // if (b) { + /// // return true; // return true; + /// //} // } + /// + /// Maximum: 0 + /// /// List: ///List: + /// /// - Foo /// - Foo + /// /// - Bar /// - Bar + /// \endcode + SpacesInLineComment SpacesInLineCommentPrefix; + /// If ``true``, spaces will be inserted after ``(`` and before ``)``. /// \code /// true: false: @@ -3145,6 +3182,10 @@ SpacesInConditionalStatement == R.SpacesInConditionalStatement && SpacesInContainerLiterals == R.SpacesInContainerLiterals && SpacesInCStyleCastParentheses == R.SpacesInCStyleCastParentheses && + SpacesInLineCommentPrefix.Minimum == + R.SpacesInLineCommentPrefix.Minimum && + SpacesInLineCommentPrefix.Maximum == + R.SpacesInLineCommentPrefix.Maximum && SpacesInParentheses == R.SpacesInParentheses && SpacesInSquareBrackets == R.SpacesInSquareBrackets && SpaceBeforeSquareBrackets == R.SpaceBeforeSquareBrackets && diff --git a/clang/lib/Format/BreakableToken.h b/clang/lib/Format/BreakableToken.h --- a/clang/lib/Format/BreakableToken.h +++ b/clang/lib/Format/BreakableToken.h @@ -465,15 +465,23 @@ // then the original prefix is "// ". SmallVector OriginalPrefix; - // Prefix[i] contains the intended leading "//" with trailing spaces to - // account for the indentation of content within the comment at line i after - // formatting. It can be different than the original prefix when the original - // line starts like this: - // //content - // Then the original prefix is "//", but the prefix is "// ". - SmallVector Prefix; - - SmallVector OriginalContentColumn; + /// Prefix[i] + SpacesToAdd[i] contains the intended leading "//" with + /// trailing spaces to account for the indentation of content within the + /// comment at line i after formatting. It can be different than the original + /// prefix. + /// When the original line starts like this: + /// //content + /// Then the OriginalPrefix[i] is "//", but the Prefix[i] is "// " in the LLVM + /// style. + /// When the line starts like: + /// // content + /// And we want to remove the spaces the OriginalPrefix[i] is "// " and + /// Prefix[i] is "//". + SmallVector Prefix; + + /// How many spaces are added or removed from the OriginalPrefix to form + /// Prefix. + SmallVector PrefixSpaceChange; /// The token to which the last line of this breakable token belongs /// to; nullptr if that token is the initial token. diff --git a/clang/lib/Format/BreakableToken.cpp b/clang/lib/Format/BreakableToken.cpp --- a/clang/lib/Format/BreakableToken.cpp +++ b/clang/lib/Format/BreakableToken.cpp @@ -757,6 +757,9 @@ assert(Tok.is(TT_LineComment) && "line comment section must start with a line comment"); FormatToken *LineTok = nullptr; + // How many spaces we changed in the first line of the section, this will be + // applied in all following lines + int FirstLineSpaceChange = 0; for (const FormatToken *CurrentTok = &Tok; CurrentTok && CurrentTok->is(TT_LineComment); CurrentTok = CurrentTok->Next) { @@ -768,44 +771,72 @@ TokenText.split(Lines, "\n"); Content.resize(Lines.size()); ContentColumn.resize(Lines.size()); - OriginalContentColumn.resize(Lines.size()); + PrefixSpaceChange.resize(Lines.size()); Tokens.resize(Lines.size()); Prefix.resize(Lines.size()); OriginalPrefix.resize(Lines.size()); for (size_t i = FirstLineIndex, e = Lines.size(); i < e; ++i) { Lines[i] = Lines[i].ltrim(Blanks); StringRef IndentPrefix = getLineCommentIndentPrefix(Lines[i], Style); - assert((TokenText.startswith("//") || TokenText.startswith("#")) && - "unsupported line comment prefix, '//' and '#' are supported"); - OriginalPrefix[i] = Prefix[i] = IndentPrefix; - if (Lines[i].size() > Prefix[i].size() && - isAlphanumeric(Lines[i][Prefix[i].size()])) { - if (Prefix[i] == "//") - Prefix[i] = "// "; - else if (Prefix[i] == "///") - Prefix[i] = "/// "; - else if (Prefix[i] == "//!") - Prefix[i] = "//! "; - else if (Prefix[i] == "///<") - Prefix[i] = "///< "; - else if (Prefix[i] == "//!<") - Prefix[i] = "//!< "; - else if (Prefix[i] == "#") - Prefix[i] = "# "; - else if (Prefix[i] == "##") - Prefix[i] = "## "; - else if (Prefix[i] == "###") - Prefix[i] = "### "; - else if (Prefix[i] == "####") - Prefix[i] = "#### "; + OriginalPrefix[i] = IndentPrefix; + const unsigned SpacesInPrefix = + std::count(IndentPrefix.begin(), IndentPrefix.end(), ' '); + + // On the first line of the comment section we calculate how many spaces + // are to be added or removed, all lines after that just get only the + // change and we will not look at the maximum anymore. Additionally to the + // actual first line, we calculate that when the non space Prefix changes, + // e.g. from "///" to "//". + if (i == 0 || OriginalPrefix[i].rtrim(Blanks) != + OriginalPrefix[i - 1].rtrim(Blanks)) { + if (SpacesInPrefix < Style.SpacesInLineCommentPrefix.Minimum && + Lines[i].size() > IndentPrefix.size() && + isAlphanumeric(Lines[i][IndentPrefix.size()])) { + FirstLineSpaceChange = + Style.SpacesInLineCommentPrefix.Minimum - SpacesInPrefix; + } else if (SpacesInPrefix > Style.SpacesInLineCommentPrefix.Maximum) { + FirstLineSpaceChange = + Style.SpacesInLineCommentPrefix.Maximum - SpacesInPrefix; + } else { + FirstLineSpaceChange = 0; + } + } + + if (Lines[i].size() != IndentPrefix.size()) { + PrefixSpaceChange[i] = FirstLineSpaceChange; + + if (SpacesInPrefix + PrefixSpaceChange[i] < + Style.SpacesInLineCommentPrefix.Minimum) { + PrefixSpaceChange[i] += Style.SpacesInLineCommentPrefix.Minimum - + (SpacesInPrefix + PrefixSpaceChange[i]); + } + + assert(Lines[i].size() > IndentPrefix.size()); + const auto FirstNonSpace = Lines[i][IndentPrefix.size()]; + const auto AllowsSpaceChange = + SpacesInPrefix != 0 || + (isAlphanumeric(FirstNonSpace) || + (FirstNonSpace == '}' && FirstLineSpaceChange != 0)); + + if (PrefixSpaceChange[i] > 0 && AllowsSpaceChange) { + Prefix[i] = IndentPrefix.str(); + Prefix[i].append(PrefixSpaceChange[i], ' '); + } else if (PrefixSpaceChange[i] < 0 && AllowsSpaceChange) { + Prefix[i] = IndentPrefix + .drop_back(std::min( + -PrefixSpaceChange[i], SpacesInPrefix)) + .str(); + } else { + Prefix[i] = IndentPrefix.str(); + } + } else { + // If the IndentPrefix is the whole line, there is no content and we + // drop just all space + Prefix[i] = IndentPrefix.drop_back(SpacesInPrefix).str(); } Tokens[i] = LineTok; Content[i] = Lines[i].substr(IndentPrefix.size()); - OriginalContentColumn[i] = - StartColumn + encoding::columnWidthWithTabs(OriginalPrefix[i], - StartColumn, - Style.TabWidth, Encoding); ContentColumn[i] = StartColumn + encoding::columnWidthWithTabs(Prefix[i], StartColumn, Style.TabWidth, Encoding); @@ -848,10 +879,9 @@ Encoding); } -unsigned BreakableLineCommentSection::getContentStartColumn(unsigned LineIndex, - bool Break) const { - if (Break) - return OriginalContentColumn[LineIndex]; +unsigned +BreakableLineCommentSection::getContentStartColumn(unsigned LineIndex, + bool /*Break*/) const { return ContentColumn[LineIndex]; } @@ -864,16 +894,10 @@ unsigned BreakOffsetInToken = Text.data() - tokenAt(LineIndex).TokenText.data() + Split.first; unsigned CharsToRemove = Split.second; - // Compute the size of the new indent, including the size of the new prefix of - // the newly broken line. - unsigned IndentAtLineBreak = OriginalContentColumn[LineIndex] + - Prefix[LineIndex].size() - - OriginalPrefix[LineIndex].size(); - assert(IndentAtLineBreak >= Prefix[LineIndex].size()); Whitespaces.replaceWhitespaceInToken( tokenAt(LineIndex), BreakOffsetInToken, CharsToRemove, "", Prefix[LineIndex], InPPDirective, /*Newlines=*/1, - /*Spaces=*/IndentAtLineBreak - Prefix[LineIndex].size()); + /*Spaces=*/ContentColumn[LineIndex] - Prefix[LineIndex].size()); } BreakableComment::Split BreakableLineCommentSection::getReflowSplit( @@ -971,14 +995,12 @@ } if (OriginalPrefix[LineIndex] != Prefix[LineIndex]) { // Adjust the prefix if necessary. - - // Take care of the space possibly introduced after a decoration. - assert(Prefix[LineIndex] == (OriginalPrefix[LineIndex] + " ").str() && - "Expecting a line comment prefix to differ from original by at most " - "a space"); + const auto SpacesToRemove = -std::min(PrefixSpaceChange[LineIndex], 0); + const auto SpacesToAdd = std::max(PrefixSpaceChange[LineIndex], 0); Whitespaces.replaceWhitespaceInToken( - tokenAt(LineIndex), OriginalPrefix[LineIndex].size(), 0, "", "", - /*InPPDirective=*/false, /*Newlines=*/0, /*Spaces=*/1); + tokenAt(LineIndex), OriginalPrefix[LineIndex].size() - SpacesToRemove, + /*ReplaceChars=*/SpacesToRemove, "", "", -/*InPPDirective=*/false, + /*Newlines=*/0, /*Spaces=*/SpacesToAdd); } } diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -661,6 +661,8 @@ Style.SpacesInContainerLiterals); IO.mapOptional("SpacesInCStyleCastParentheses", Style.SpacesInCStyleCastParentheses); + IO.mapOptional("SpacesInLineCommentPrefix", + Style.SpacesInLineCommentPrefix); IO.mapOptional("SpacesInParentheses", Style.SpacesInParentheses); IO.mapOptional("SpacesInSquareBrackets", Style.SpacesInSquareBrackets); IO.mapOptional("SpaceBeforeSquareBrackets", @@ -710,6 +712,20 @@ } }; +template <> struct MappingTraits { + static void mapping(IO &IO, FormatStyle::SpacesInLineComment &Space) { + // Transform the maximum to signed, to parse "-1" correctly + int signedMaximum = static_cast(Space.Maximum); + IO.mapOptional("Minimum", Space.Minimum); + IO.mapOptional("Maximum", signedMaximum); + Space.Maximum = static_cast(signedMaximum); + + if (Space.Maximum != -1u) { + Space.Minimum = std::min(Space.Minimum, Space.Maximum); + } + } +}; + // Allows to read vector while keeping default values. // IO.getContext() should contain a pointer to the FormatStyle structure, that // will be used to get default values for missing keys. @@ -986,6 +1002,7 @@ LLVMStyle.SpaceInEmptyParentheses = false; LLVMStyle.SpacesInContainerLiterals = true; LLVMStyle.SpacesInCStyleCastParentheses = false; + LLVMStyle.SpacesInLineCommentPrefix = {/*Minimum=*/1, /*Maximum=*/-1u}; LLVMStyle.SpaceAfterCStyleCast = false; LLVMStyle.SpaceAfterLogicalNot = false; LLVMStyle.SpaceAfterTemplateKeyword = true; diff --git a/clang/lib/Format/NamespaceEndCommentsFixer.cpp b/clang/lib/Format/NamespaceEndCommentsFixer.cpp --- a/clang/lib/Format/NamespaceEndCommentsFixer.cpp +++ b/clang/lib/Format/NamespaceEndCommentsFixer.cpp @@ -66,8 +66,10 @@ } std::string computeEndCommentText(StringRef NamespaceName, bool AddNewline, - const FormatToken *NamespaceTok) { - std::string text = "// "; + const FormatToken *NamespaceTok, + unsigned SpacesToAdd) { + std::string text = "//"; + text.append(SpacesToAdd, ' '); text += NamespaceTok->TokenText; if (NamespaceTok->is(TT_NamespaceMacro)) text += "("; @@ -278,7 +280,8 @@ EndCommentNextTok->NewlinesBefore == 0 && EndCommentNextTok->isNot(tok::eof); const std::string EndCommentText = - computeEndCommentText(NamespaceName, AddNewline, NamespaceTok); + computeEndCommentText(NamespaceName, AddNewline, NamespaceTok, + Style.SpacesInLineCommentPrefix.Minimum); if (!hasEndComment(EndCommentPrevTok)) { bool isShort = I - StartLineIndex <= kShortNamespaceMaxLines + 1; if (!isShort) diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -15994,6 +15994,26 @@ " - 'CPPEVAL'\n" " CanonicalDelimiter: 'cc'", RawStringFormats, ExpectedRawStringFormats); + + CHECK_PARSE("SpacesInLineCommentPrefix:\n" + " Minimum: 0\n" + " Maximum: 0", + SpacesInLineCommentPrefix.Minimum, 0u); + EXPECT_EQ(Style.SpacesInLineCommentPrefix.Maximum, 0u); + Style.SpacesInLineCommentPrefix.Minimum = 1; + CHECK_PARSE("SpacesInLineCommentPrefix:\n" + " Minimum: 2", + SpacesInLineCommentPrefix.Minimum, 0u); + CHECK_PARSE("SpacesInLineCommentPrefix:\n" + " Maximum: -1", + SpacesInLineCommentPrefix.Maximum, -1u); + CHECK_PARSE("SpacesInLineCommentPrefix:\n" + " Minimum: 2", + SpacesInLineCommentPrefix.Minimum, 2u); + CHECK_PARSE("SpacesInLineCommentPrefix:\n" + " Maximum: 1", + SpacesInLineCommentPrefix.Maximum, 1u); + EXPECT_EQ(Style.SpacesInLineCommentPrefix.Minimum, 1u); } TEST_F(FormatTest, ParsesConfigurationWithLanguages) { diff --git a/clang/unittests/Format/FormatTestComments.cpp b/clang/unittests/Format/FormatTestComments.cpp --- a/clang/unittests/Format/FormatTestComments.cpp +++ b/clang/unittests/Format/FormatTestComments.cpp @@ -3144,7 +3144,7 @@ " # commen6\n" " # commen7", format("k:val#commen1 commen2\n" - " # commen3\n" + " #commen3\n" "# commen4\n" "a:1#commen5 commen6\n" " #commen7", @@ -3275,6 +3275,526 @@ JSStyle20)); } +TEST_F(FormatTestComments, SpaceAtLineCommentBegin) { + FormatStyle Style = getLLVMStyle(); + StringRef NoTextInComment = " // \n" + "\n" + "void foo() {// \n" + "// \n" + "}"; + + EXPECT_EQ("//\n" + "\n" + "void foo() { //\n" + " //\n" + "}", + format(NoTextInComment, Style)); + + Style.SpacesInLineCommentPrefix.Minimum = 0; + EXPECT_EQ("//\n" + "\n" + "void foo() { //\n" + " //\n" + "}", + format(NoTextInComment, Style)); + + Style.SpacesInLineCommentPrefix.Minimum = 5; + EXPECT_EQ("//\n" + "\n" + "void foo() { //\n" + " //\n" + "}", + format(NoTextInComment, Style)); + + Style = getLLVMStyle(); + StringRef Code = + "//Free comment without space\n" + "\n" + "// Free comment with 3 spaces\n" + "\n" + "///Free Doxygen without space\n" + "\n" + "/// Free Doxygen with 3 spaces\n" + "\n" + "/// A Doxygen Comment with a nested list:\n" + "/// - Foo\n" + "/// - Bar\n" + "/// - Baz\n" + "/// - End\n" + "/// of the inner list\n" + "/// .\n" + "/// .\n" + "\n" + "namespace Foo {\n" + "bool bar(bool b) {\n" + " bool ret1 = true; ///