diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -2707,6 +2707,26 @@ 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}); +**SpacesInLineComments** (``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 + + 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/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 @@ -2282,6 +2282,27 @@ /// \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 + SpacesInLineComment SpacesInLineComments; + /// If ``true``, spaces will be inserted after ``(`` and before ``)``. /// \code /// true: false: @@ -2512,6 +2533,8 @@ SpacesInConditionalStatement == R.SpacesInConditionalStatement && SpacesInContainerLiterals == R.SpacesInContainerLiterals && SpacesInCStyleCastParentheses == R.SpacesInCStyleCastParentheses && + SpacesInLineComments.Minimum == R.SpacesInLineComments.Minimum && + SpacesInLineComments.Maximum == R.SpacesInLineComments.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 @@ -471,8 +471,9 @@ // 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; + // Then the original prefix is "//", but the prefix could be "// ", if adding + // spaces is desired. + SmallVector Prefix; SmallVector OriginalContentColumn; 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 @@ -779,22 +779,25 @@ getLineCommentIndentPrefix(Lines[i].ltrim(Blanks), 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] == "#" && - Style.Language == FormatStyle::LK_TextProto) - Prefix[i] = "# "; + OriginalPrefix[i] = IndentPrefix; + const auto SpacesInPrefix = + std::count(IndentPrefix.begin(), IndentPrefix.end(), ' '); + + if (SpacesInPrefix < Style.SpacesInLineComments.Minimum && + Lines[i].size() > IndentPrefix.size() && + isAlphanumeric(Lines[i][IndentPrefix.size()]) && + (Style.Language != FormatStyle::LK_TextProto || + OriginalPrefix[i].substr(0, 2) != "##")) { + Prefix[i] = IndentPrefix.str(); + Prefix[i].append(Style.SpacesInLineComments.Minimum - SpacesInPrefix, + ' '); + } else if (SpacesInPrefix > Style.SpacesInLineComments.Maximum) { + Prefix[i] = + IndentPrefix + .drop_back(SpacesInPrefix - Style.SpacesInLineComments.Maximum) + .str(); + } else { + Prefix[i] = IndentPrefix.str(); } Tokens[i] = LineTok; @@ -968,14 +971,18 @@ } 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"); + bool RemoveWhitespace = + OriginalPrefix[LineIndex].size() > Prefix[LineIndex].size(); + int SpacesToRemove = RemoveWhitespace ? OriginalPrefix[LineIndex].size() - + Prefix[LineIndex].size() + : 0; + int SpacesToAdd = RemoveWhitespace ? 0 + : Prefix[LineIndex].size() - + OriginalPrefix[LineIndex].size(); 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 @@ -624,6 +624,7 @@ Style.SpacesInContainerLiterals); IO.mapOptional("SpacesInCStyleCastParentheses", Style.SpacesInCStyleCastParentheses); + IO.mapOptional("SpacesInLineComments", Style.SpacesInLineComments); IO.mapOptional("SpacesInParentheses", Style.SpacesInParentheses); IO.mapOptional("SpacesInSquareBrackets", Style.SpacesInSquareBrackets); IO.mapOptional("SpaceBeforeSquareBrackets", @@ -673,6 +674,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. @@ -945,6 +960,7 @@ LLVMStyle.SpaceInEmptyParentheses = false; LLVMStyle.SpacesInContainerLiterals = true; LLVMStyle.SpacesInCStyleCastParentheses = false; + LLVMStyle.SpacesInLineComments = {/*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.SpacesInLineComments.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 @@ -14571,6 +14571,26 @@ " - 'CPPEVAL'\n" " CanonicalDelimiter: 'cc'", RawStringFormats, ExpectedRawStringFormats); + + CHECK_PARSE("SpacesInLineComments:\n" + " Minimum: 0\n" + " Maximum: 0", + SpacesInLineComments.Minimum, 0u); + EXPECT_EQ(Style.SpacesInLineComments.Maximum, 0u); + Style.SpacesInLineComments.Minimum = 1; + CHECK_PARSE("SpacesInLineComments:\n" + " Minimum: 2", + SpacesInLineComments.Minimum, 0u); + CHECK_PARSE("SpacesInLineComments:\n" + " Maximum: -1", + SpacesInLineComments.Maximum, -1u); + CHECK_PARSE("SpacesInLineComments:\n" + " Minimum: 2", + SpacesInLineComments.Minimum, 2u); + CHECK_PARSE("SpacesInLineComments:\n" + " Maximum: 1", + SpacesInLineComments.Maximum, 1u); + EXPECT_EQ(Style.SpacesInLineComments.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 @@ -3269,6 +3269,183 @@ 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.SpacesInLineComments.Minimum = 0; + EXPECT_EQ("//\n" + "\n" + "void foo() { //\n" + " //\n" + "}", + format(NoTextInComment, Style)); + + Style = getLLVMStyle(); + StringRef Code = "//Free comment without space\n" + "// Free comment with 3 spaces\n" + "///Free Doxygen without space\n" + "/// Free Doxygen with 3 spaces\n" + "namespace Foo {\n" + "bool bar(bool b) {\n" + " bool ret1 = true; ///