Skip to content

Commit 772eb24

Browse files
[clang-format] Add option to control the spaces in a line comment
Differential Revision: https://reviews.llvm.org/D92257
1 parent 35f746c commit 772eb24

File tree

9 files changed

+729
-61
lines changed

9 files changed

+729
-61
lines changed

clang/docs/ClangFormatStyleOptions.rst

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3335,6 +3335,43 @@ the configuration (without a prefix: ``Auto``).
33353335
var arr = [ 1, 2, 3 ]; vs. var arr = [1, 2, 3];
33363336
f({a : 1, b : 2, c : 3}); f({a: 1, b: 2, c: 3});
33373337
3338+
**SpacesInLineCommentPrefix** (``SpacesInLineComment``)
3339+
How many spaces are allowed at the start of a line comment. To disable the
3340+
maximum set it to ``-1``, apart from that the maximum takes precedence
3341+
over the minimum.
3342+
Minimum = 1 Maximum = -1
3343+
// One space is forced
3344+
3345+
// but more spaces are possible
3346+
3347+
Minimum = 0
3348+
Maximum = 0
3349+
//Forces to start every comment directly after the slashes
3350+
3351+
Note that in line comment sections the relative indent of the subsequent
3352+
lines is kept, that means the following:
3353+
3354+
.. code-block:: c++
3355+
3356+
before: after:
3357+
Minimum: 1
3358+
//if (b) { // if (b) {
3359+
// return true; // return true;
3360+
//} // }
3361+
3362+
Maximum: 0
3363+
/// List: ///List:
3364+
/// - Foo /// - Foo
3365+
/// - Bar /// - Bar
3366+
3367+
Nested configuration flags:
3368+
3369+
3370+
* ``unsigned Minimum`` The minimum number of spaces at the start of the comment.
3371+
3372+
* ``unsigned Maximum`` The maximum number of spaces at the start of the comment.
3373+
3374+
33383375
**SpacesInParentheses** (``bool``)
33393376
If ``true``, spaces will be inserted after ``(`` and before ``)``.
33403377

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,8 @@ AST Matchers
156156
clang-format
157157
------------
158158

159-
- ...
160-
159+
- Option ``SpacesInLineCommentPrefix`` has been added to control the
160+
number of spaces in a line comments prefix.
161161

162162
libclang
163163
--------

clang/include/clang/Format/Format.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2894,6 +2894,43 @@ struct FormatStyle {
28942894
/// \endcode
28952895
bool SpacesInCStyleCastParentheses;
28962896

2897+
/// Control of spaces within a single line comment
2898+
struct SpacesInLineComment {
2899+
/// The minimum number of spaces at the start of the comment.
2900+
unsigned Minimum;
2901+
/// The maximum number of spaces at the start of the comment.
2902+
unsigned Maximum;
2903+
};
2904+
2905+
/// How many spaces are allowed at the start of a line comment. To disable the
2906+
/// maximum set it to ``-1``, apart from that the maximum takes precedence
2907+
/// over the minimum.
2908+
/// \code Minimum = 1 Maximum = -1
2909+
/// // One space is forced
2910+
///
2911+
/// // but more spaces are possible
2912+
///
2913+
/// Minimum = 0
2914+
/// Maximum = 0
2915+
/// //Forces to start every comment directly after the slashes
2916+
/// \endcode
2917+
///
2918+
/// Note that in line comment sections the relative indent of the subsequent
2919+
/// lines is kept, that means the following:
2920+
/// \code
2921+
/// before: after:
2922+
/// Minimum: 1
2923+
/// //if (b) { // if (b) {
2924+
/// // return true; // return true;
2925+
/// //} // }
2926+
///
2927+
/// Maximum: 0
2928+
/// /// List: ///List:
2929+
/// /// - Foo /// - Foo
2930+
/// /// - Bar /// - Bar
2931+
/// \endcode
2932+
SpacesInLineComment SpacesInLineCommentPrefix;
2933+
28972934
/// If ``true``, spaces will be inserted after ``(`` and before ``)``.
28982935
/// \code
28992936
/// true: false:
@@ -3145,6 +3182,10 @@ struct FormatStyle {
31453182
SpacesInConditionalStatement == R.SpacesInConditionalStatement &&
31463183
SpacesInContainerLiterals == R.SpacesInContainerLiterals &&
31473184
SpacesInCStyleCastParentheses == R.SpacesInCStyleCastParentheses &&
3185+
SpacesInLineCommentPrefix.Minimum ==
3186+
R.SpacesInLineCommentPrefix.Minimum &&
3187+
SpacesInLineCommentPrefix.Maximum ==
3188+
R.SpacesInLineCommentPrefix.Maximum &&
31483189
SpacesInParentheses == R.SpacesInParentheses &&
31493190
SpacesInSquareBrackets == R.SpacesInSquareBrackets &&
31503191
SpaceBeforeSquareBrackets == R.SpaceBeforeSquareBrackets &&

clang/lib/Format/BreakableToken.cpp

Lines changed: 68 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,9 @@ BreakableLineCommentSection::BreakableLineCommentSection(
757757
assert(Tok.is(TT_LineComment) &&
758758
"line comment section must start with a line comment");
759759
FormatToken *LineTok = nullptr;
760+
// How many spaces we changed in the first line of the section, this will be
761+
// applied in all following lines
762+
int FirstLineSpaceChange = 0;
760763
for (const FormatToken *CurrentTok = &Tok;
761764
CurrentTok && CurrentTok->is(TT_LineComment);
762765
CurrentTok = CurrentTok->Next) {
@@ -768,44 +771,72 @@ BreakableLineCommentSection::BreakableLineCommentSection(
768771
TokenText.split(Lines, "\n");
769772
Content.resize(Lines.size());
770773
ContentColumn.resize(Lines.size());
771-
OriginalContentColumn.resize(Lines.size());
774+
PrefixSpaceChange.resize(Lines.size());
772775
Tokens.resize(Lines.size());
773776
Prefix.resize(Lines.size());
774777
OriginalPrefix.resize(Lines.size());
775778
for (size_t i = FirstLineIndex, e = Lines.size(); i < e; ++i) {
776779
Lines[i] = Lines[i].ltrim(Blanks);
777780
StringRef IndentPrefix = getLineCommentIndentPrefix(Lines[i], Style);
778-
assert((TokenText.startswith("//") || TokenText.startswith("#")) &&
779-
"unsupported line comment prefix, '//' and '#' are supported");
780-
OriginalPrefix[i] = Prefix[i] = IndentPrefix;
781-
if (Lines[i].size() > Prefix[i].size() &&
782-
isAlphanumeric(Lines[i][Prefix[i].size()])) {
783-
if (Prefix[i] == "//")
784-
Prefix[i] = "// ";
785-
else if (Prefix[i] == "///")
786-
Prefix[i] = "/// ";
787-
else if (Prefix[i] == "//!")
788-
Prefix[i] = "//! ";
789-
else if (Prefix[i] == "///<")
790-
Prefix[i] = "///< ";
791-
else if (Prefix[i] == "//!<")
792-
Prefix[i] = "//!< ";
793-
else if (Prefix[i] == "#")
794-
Prefix[i] = "# ";
795-
else if (Prefix[i] == "##")
796-
Prefix[i] = "## ";
797-
else if (Prefix[i] == "###")
798-
Prefix[i] = "### ";
799-
else if (Prefix[i] == "####")
800-
Prefix[i] = "#### ";
781+
OriginalPrefix[i] = IndentPrefix;
782+
const unsigned SpacesInPrefix =
783+
std::count(IndentPrefix.begin(), IndentPrefix.end(), ' ');
784+
785+
// On the first line of the comment section we calculate how many spaces
786+
// are to be added or removed, all lines after that just get only the
787+
// change and we will not look at the maximum anymore. Additionally to the
788+
// actual first line, we calculate that when the non space Prefix changes,
789+
// e.g. from "///" to "//".
790+
if (i == 0 || OriginalPrefix[i].rtrim(Blanks) !=
791+
OriginalPrefix[i - 1].rtrim(Blanks)) {
792+
if (SpacesInPrefix < Style.SpacesInLineCommentPrefix.Minimum &&
793+
Lines[i].size() > IndentPrefix.size() &&
794+
isAlphanumeric(Lines[i][IndentPrefix.size()])) {
795+
FirstLineSpaceChange =
796+
Style.SpacesInLineCommentPrefix.Minimum - SpacesInPrefix;
797+
} else if (SpacesInPrefix > Style.SpacesInLineCommentPrefix.Maximum) {
798+
FirstLineSpaceChange =
799+
Style.SpacesInLineCommentPrefix.Maximum - SpacesInPrefix;
800+
} else {
801+
FirstLineSpaceChange = 0;
802+
}
803+
}
804+
805+
if (Lines[i].size() != IndentPrefix.size()) {
806+
PrefixSpaceChange[i] = FirstLineSpaceChange;
807+
808+
if (SpacesInPrefix + PrefixSpaceChange[i] <
809+
Style.SpacesInLineCommentPrefix.Minimum) {
810+
PrefixSpaceChange[i] += Style.SpacesInLineCommentPrefix.Minimum -
811+
(SpacesInPrefix + PrefixSpaceChange[i]);
812+
}
813+
814+
assert(Lines[i].size() > IndentPrefix.size());
815+
const auto FirstNonSpace = Lines[i][IndentPrefix.size()];
816+
const auto AllowsSpaceChange =
817+
SpacesInPrefix != 0 ||
818+
(isAlphanumeric(FirstNonSpace) ||
819+
(FirstNonSpace == '}' && FirstLineSpaceChange != 0));
820+
821+
if (PrefixSpaceChange[i] > 0 && AllowsSpaceChange) {
822+
Prefix[i] = IndentPrefix.str();
823+
Prefix[i].append(PrefixSpaceChange[i], ' ');
824+
} else if (PrefixSpaceChange[i] < 0 && AllowsSpaceChange) {
825+
Prefix[i] = IndentPrefix
826+
.drop_back(std::min<std::size_t>(
827+
-PrefixSpaceChange[i], SpacesInPrefix))
828+
.str();
829+
} else {
830+
Prefix[i] = IndentPrefix.str();
831+
}
832+
} else {
833+
// If the IndentPrefix is the whole line, there is no content and we
834+
// drop just all space
835+
Prefix[i] = IndentPrefix.drop_back(SpacesInPrefix).str();
801836
}
802837

803838
Tokens[i] = LineTok;
804839
Content[i] = Lines[i].substr(IndentPrefix.size());
805-
OriginalContentColumn[i] =
806-
StartColumn + encoding::columnWidthWithTabs(OriginalPrefix[i],
807-
StartColumn,
808-
Style.TabWidth, Encoding);
809840
ContentColumn[i] =
810841
StartColumn + encoding::columnWidthWithTabs(Prefix[i], StartColumn,
811842
Style.TabWidth, Encoding);
@@ -848,10 +879,9 @@ BreakableLineCommentSection::getRangeLength(unsigned LineIndex, unsigned Offset,
848879
Encoding);
849880
}
850881

851-
unsigned BreakableLineCommentSection::getContentStartColumn(unsigned LineIndex,
852-
bool Break) const {
853-
if (Break)
854-
return OriginalContentColumn[LineIndex];
882+
unsigned
883+
BreakableLineCommentSection::getContentStartColumn(unsigned LineIndex,
884+
bool /*Break*/) const {
855885
return ContentColumn[LineIndex];
856886
}
857887

@@ -864,16 +894,10 @@ void BreakableLineCommentSection::insertBreak(
864894
unsigned BreakOffsetInToken =
865895
Text.data() - tokenAt(LineIndex).TokenText.data() + Split.first;
866896
unsigned CharsToRemove = Split.second;
867-
// Compute the size of the new indent, including the size of the new prefix of
868-
// the newly broken line.
869-
unsigned IndentAtLineBreak = OriginalContentColumn[LineIndex] +
870-
Prefix[LineIndex].size() -
871-
OriginalPrefix[LineIndex].size();
872-
assert(IndentAtLineBreak >= Prefix[LineIndex].size());
873897
Whitespaces.replaceWhitespaceInToken(
874898
tokenAt(LineIndex), BreakOffsetInToken, CharsToRemove, "",
875899
Prefix[LineIndex], InPPDirective, /*Newlines=*/1,
876-
/*Spaces=*/IndentAtLineBreak - Prefix[LineIndex].size());
900+
/*Spaces=*/ContentColumn[LineIndex] - Prefix[LineIndex].size());
877901
}
878902

879903
BreakableComment::Split BreakableLineCommentSection::getReflowSplit(
@@ -971,14 +995,12 @@ void BreakableLineCommentSection::adaptStartOfLine(
971995
}
972996
if (OriginalPrefix[LineIndex] != Prefix[LineIndex]) {
973997
// Adjust the prefix if necessary.
974-
975-
// Take care of the space possibly introduced after a decoration.
976-
assert(Prefix[LineIndex] == (OriginalPrefix[LineIndex] + " ").str() &&
977-
"Expecting a line comment prefix to differ from original by at most "
978-
"a space");
998+
const auto SpacesToRemove = -std::min(PrefixSpaceChange[LineIndex], 0);
999+
const auto SpacesToAdd = std::max(PrefixSpaceChange[LineIndex], 0);
9791000
Whitespaces.replaceWhitespaceInToken(
980-
tokenAt(LineIndex), OriginalPrefix[LineIndex].size(), 0, "", "",
981-
/*InPPDirective=*/false, /*Newlines=*/0, /*Spaces=*/1);
1001+
tokenAt(LineIndex), OriginalPrefix[LineIndex].size() - SpacesToRemove,
1002+
/*ReplaceChars=*/SpacesToRemove, "", "", -/*InPPDirective=*/false,
1003+
/*Newlines=*/0, /*Spaces=*/SpacesToAdd);
9821004
}
9831005
}
9841006

clang/lib/Format/BreakableToken.h

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -465,15 +465,23 @@ class BreakableLineCommentSection : public BreakableComment {
465465
// then the original prefix is "// ".
466466
SmallVector<StringRef, 16> OriginalPrefix;
467467

468-
// Prefix[i] contains the intended leading "//" with trailing spaces to
469-
// account for the indentation of content within the comment at line i after
470-
// formatting. It can be different than the original prefix when the original
471-
// line starts like this:
472-
// //content
473-
// Then the original prefix is "//", but the prefix is "// ".
474-
SmallVector<StringRef, 16> Prefix;
475-
476-
SmallVector<unsigned, 16> OriginalContentColumn;
468+
/// Prefix[i] + SpacesToAdd[i] contains the intended leading "//" with
469+
/// trailing spaces to account for the indentation of content within the
470+
/// comment at line i after formatting. It can be different than the original
471+
/// prefix.
472+
/// When the original line starts like this:
473+
/// //content
474+
/// Then the OriginalPrefix[i] is "//", but the Prefix[i] is "// " in the LLVM
475+
/// style.
476+
/// When the line starts like:
477+
/// // content
478+
/// And we want to remove the spaces the OriginalPrefix[i] is "// " and
479+
/// Prefix[i] is "//".
480+
SmallVector<std::string, 16> Prefix;
481+
482+
/// How many spaces are added or removed from the OriginalPrefix to form
483+
/// Prefix.
484+
SmallVector<int, 16> PrefixSpaceChange;
477485

478486
/// The token to which the last line of this breakable token belongs
479487
/// to; nullptr if that token is the initial token.

clang/lib/Format/Format.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,8 @@ template <> struct MappingTraits<FormatStyle> {
661661
Style.SpacesInContainerLiterals);
662662
IO.mapOptional("SpacesInCStyleCastParentheses",
663663
Style.SpacesInCStyleCastParentheses);
664+
IO.mapOptional("SpacesInLineCommentPrefix",
665+
Style.SpacesInLineCommentPrefix);
664666
IO.mapOptional("SpacesInParentheses", Style.SpacesInParentheses);
665667
IO.mapOptional("SpacesInSquareBrackets", Style.SpacesInSquareBrackets);
666668
IO.mapOptional("SpaceBeforeSquareBrackets",
@@ -710,6 +712,20 @@ template <> struct MappingTraits<FormatStyle::RawStringFormat> {
710712
}
711713
};
712714

715+
template <> struct MappingTraits<FormatStyle::SpacesInLineComment> {
716+
static void mapping(IO &IO, FormatStyle::SpacesInLineComment &Space) {
717+
// Transform the maximum to signed, to parse "-1" correctly
718+
int signedMaximum = static_cast<int>(Space.Maximum);
719+
IO.mapOptional("Minimum", Space.Minimum);
720+
IO.mapOptional("Maximum", signedMaximum);
721+
Space.Maximum = static_cast<unsigned>(signedMaximum);
722+
723+
if (Space.Maximum != -1u) {
724+
Space.Minimum = std::min(Space.Minimum, Space.Maximum);
725+
}
726+
}
727+
};
728+
713729
// Allows to read vector<FormatStyle> while keeping default values.
714730
// IO.getContext() should contain a pointer to the FormatStyle structure, that
715731
// will be used to get default values for missing keys.
@@ -986,6 +1002,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
9861002
LLVMStyle.SpaceInEmptyParentheses = false;
9871003
LLVMStyle.SpacesInContainerLiterals = true;
9881004
LLVMStyle.SpacesInCStyleCastParentheses = false;
1005+
LLVMStyle.SpacesInLineCommentPrefix = {/*Minimum=*/1, /*Maximum=*/-1u};
9891006
LLVMStyle.SpaceAfterCStyleCast = false;
9901007
LLVMStyle.SpaceAfterLogicalNot = false;
9911008
LLVMStyle.SpaceAfterTemplateKeyword = true;

clang/lib/Format/NamespaceEndCommentsFixer.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,10 @@ std::string computeName(const FormatToken *NamespaceTok) {
6666
}
6767

6868
std::string computeEndCommentText(StringRef NamespaceName, bool AddNewline,
69-
const FormatToken *NamespaceTok) {
70-
std::string text = "// ";
69+
const FormatToken *NamespaceTok,
70+
unsigned SpacesToAdd) {
71+
std::string text = "//";
72+
text.append(SpacesToAdd, ' ');
7173
text += NamespaceTok->TokenText;
7274
if (NamespaceTok->is(TT_NamespaceMacro))
7375
text += "(";
@@ -278,7 +280,8 @@ std::pair<tooling::Replacements, unsigned> NamespaceEndCommentsFixer::analyze(
278280
EndCommentNextTok->NewlinesBefore == 0 &&
279281
EndCommentNextTok->isNot(tok::eof);
280282
const std::string EndCommentText =
281-
computeEndCommentText(NamespaceName, AddNewline, NamespaceTok);
283+
computeEndCommentText(NamespaceName, AddNewline, NamespaceTok,
284+
Style.SpacesInLineCommentPrefix.Minimum);
282285
if (!hasEndComment(EndCommentPrevTok)) {
283286
bool isShort = I - StartLineIndex <= kShortNamespaceMaxLines + 1;
284287
if (!isShort)

clang/unittests/Format/FormatTest.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15994,6 +15994,26 @@ TEST_F(FormatTest, ParsesConfiguration) {
1599415994
" - 'CPPEVAL'\n"
1599515995
" CanonicalDelimiter: 'cc'",
1599615996
RawStringFormats, ExpectedRawStringFormats);
15997+
15998+
CHECK_PARSE("SpacesInLineCommentPrefix:\n"
15999+
" Minimum: 0\n"
16000+
" Maximum: 0",
16001+
SpacesInLineCommentPrefix.Minimum, 0u);
16002+
EXPECT_EQ(Style.SpacesInLineCommentPrefix.Maximum, 0u);
16003+
Style.SpacesInLineCommentPrefix.Minimum = 1;
16004+
CHECK_PARSE("SpacesInLineCommentPrefix:\n"
16005+
" Minimum: 2",
16006+
SpacesInLineCommentPrefix.Minimum, 0u);
16007+
CHECK_PARSE("SpacesInLineCommentPrefix:\n"
16008+
" Maximum: -1",
16009+
SpacesInLineCommentPrefix.Maximum, -1u);
16010+
CHECK_PARSE("SpacesInLineCommentPrefix:\n"
16011+
" Minimum: 2",
16012+
SpacesInLineCommentPrefix.Minimum, 2u);
16013+
CHECK_PARSE("SpacesInLineCommentPrefix:\n"
16014+
" Maximum: 1",
16015+
SpacesInLineCommentPrefix.Maximum, 1u);
16016+
EXPECT_EQ(Style.SpacesInLineCommentPrefix.Minimum, 1u);
1599716017
}
1599816018

1599916019
TEST_F(FormatTest, ParsesConfigurationWithLanguages) {

0 commit comments

Comments
 (0)