[clang] [clang-format] Option to insert spaces before the closing `*/` (PR #162105)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Oct 7 19:48:39 PDT 2025
https://github.com/Men-cotton updated https://github.com/llvm/llvm-project/pull/162105
>From ed55a3b0e862e1a278ffa4703bc388f04adc9929 Mon Sep 17 00:00:00 2001
From: mencotton <mencotton0410 at gmail.com>
Date: Tue, 7 Oct 2025 23:14:20 +0900
Subject: [PATCH] [clang-format] Add option to insert space before closing
block comments
---
clang/docs/ClangFormatStyleOptions.rst | 11 +++++
clang/include/clang/Format/Format.h | 10 ++++
clang/lib/Format/BreakableToken.cpp | 42 ++++++++++++++++-
clang/lib/Format/Format.cpp | 3 ++
clang/lib/Format/FormatToken.h | 14 ++++--
clang/lib/Format/FormatTokenLexer.cpp | 16 +++++++
clang/lib/Format/TokenAnnotator.cpp | 5 +-
clang/unittests/Format/FormatTestComments.cpp | 47 +++++++++++++++++++
8 files changed, 143 insertions(+), 5 deletions(-)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index b746df5dab264..70582b6c40980 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -6343,6 +6343,17 @@ the configuration (without a prefix: ``Auto``).
case 1 : break; case 1: break;
} }
+.. _SpaceBeforeClosingBlockComment:
+
+**SpaceBeforeClosingBlockComment** (``Boolean``) :versionbadge:`clang-format 21` :ref:`¶ <SpaceBeforeClosingBlockComment>`
+ If ``true``, a space is inserted immediately before the closing ``*/`` in
+ block comments that contain content.
+
+ .. code-block:: c++
+
+ true: false:
+ /* comment */ vs. /* comment*/
+
.. _SpaceBeforeCpp11BracedList:
**SpaceBeforeCpp11BracedList** (``Boolean``) :versionbadge:`clang-format 7` :ref:`¶ <SpaceBeforeCpp11BracedList>`
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 3df5b92654094..7136fd2c5a4f8 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -4684,6 +4684,15 @@ struct FormatStyle {
/// \version 17
bool SpaceBeforeJsonColon;
+ /// If ``true``, a space is inserted immediately before the closing ``*/`` in
+ /// block comments that contain content.
+ /// \code
+ /// true: false:
+ /// /* comment */ vs. /* comment*/
+ /// \endcode
+ /// \version 21
+ bool SpaceBeforeClosingBlockComment;
+
/// Different ways to put a space before opening parentheses.
enum SpaceBeforeParensStyle : int8_t {
/// This is **deprecated** and replaced by ``Custom`` below, with all
@@ -5611,6 +5620,7 @@ struct FormatStyle {
SpaceAroundPointerQualifiers == R.SpaceAroundPointerQualifiers &&
SpaceBeforeRangeBasedForLoopColon ==
R.SpaceBeforeRangeBasedForLoopColon &&
+ SpaceBeforeClosingBlockComment == R.SpaceBeforeClosingBlockComment &&
SpaceBeforeSquareBrackets == R.SpaceBeforeSquareBrackets &&
SpaceInEmptyBraces == R.SpaceInEmptyBraces &&
SpacesBeforeTrailingComments == R.SpacesBeforeTrailingComments &&
diff --git a/clang/lib/Format/BreakableToken.cpp b/clang/lib/Format/BreakableToken.cpp
index 29db20067c361..48feaeb06d186 100644
--- a/clang/lib/Format/BreakableToken.cpp
+++ b/clang/lib/Format/BreakableToken.cpp
@@ -776,6 +776,30 @@ void BreakableBlockComment::reflow(unsigned LineIndex,
void BreakableBlockComment::adaptStartOfLine(
unsigned LineIndex, WhitespaceManager &Whitespaces) const {
if (LineIndex == 0) {
+ if (Style.SpaceBeforeClosingBlockComment) {
+ StringRef Body = Tok.TokenText;
+ Body = Body.drop_front(std::min<size_t>(2, Body.size()));
+ Body = Body.drop_back(std::min<size_t>(2, Body.size()));
+ bool BodyHasContent =
+ Body.find_first_not_of(" \t\v\f\r\n") != StringRef::npos;
+ bool HasNewline = Tok.TokenText.contains('\n');
+ if (!BodyHasContent && !HasNewline) {
+ unsigned ReplaceChars =
+ Tok.TokenText.size() >= 4 ? Tok.TokenText.size() - 4 : 0;
+ Whitespaces.replaceWhitespaceInToken(
+ Tok, /*Offset=*/2, ReplaceChars, /*PreviousPostfix=*/"",
+ /*CurrentPrefix=*/"", InPPDirective, /*Newlines=*/1,
+ /*Spaces=*/0);
+ }
+ }
+ if (Style.SpaceBeforeClosingBlockComment &&
+ Tok.NeedsSpaceBeforeClosingBlockComment &&
+ Tok.SpaceBeforeClosingBlockCommentOffset < Tok.TokenText.size()) {
+ Whitespaces.replaceWhitespaceInToken(
+ Tok, Tok.SpaceBeforeClosingBlockCommentOffset,
+ /*ReplaceChars=*/0, /*PreviousPostfix=*/"", /*CurrentPrefix=*/" ",
+ InPPDirective, /*Newlines=*/0, /*Spaces=*/0);
+ }
if (DelimitersOnNewline) {
// Since we're breaking at index 1 below, the break position and the
// break length are the same.
@@ -816,9 +840,25 @@ void BreakableBlockComment::adaptStartOfLine(
unsigned WhitespaceLength = Content[LineIndex].data() -
tokenAt(LineIndex).TokenText.data() -
WhitespaceOffsetInToken;
+ int Spaces = ContentColumn[LineIndex] - Prefix.size();
+ const bool LastLineHasNoContent = Content[LineIndex].ltrim(Blanks).empty();
+ if (!Tok.NeedsSpaceBeforeClosingBlockComment && LastLineHasNoContent &&
+ LineIndex + 1 == Lines.size()) {
+ const bool HasExistingSpaceBeforeClosing =
+ Tok.TokenText.size() >= 3 &&
+ isHorizontalWhitespace(static_cast<unsigned char>(
+ Tok.TokenText[Tok.TokenText.size() - 3]));
+ if (!HasExistingSpaceBeforeClosing)
+ Spaces = 0;
+ }
+ if (Tok.NeedsSpaceBeforeClosingBlockComment &&
+ Tok.SpaceBeforeClosingBlockCommentOffset < Tok.TokenText.size() &&
+ LineIndex + 1 == Lines.size() && Spaces > 0 && LastLineHasNoContent) {
+ --Spaces;
+ }
Whitespaces.replaceWhitespaceInToken(
tokenAt(LineIndex), WhitespaceOffsetInToken, WhitespaceLength, "", Prefix,
- InPPDirective, /*Newlines=*/1, ContentColumn[LineIndex] - Prefix.size());
+ InPPDirective, /*Newlines=*/1, Spaces);
}
BreakableToken::Split
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 686e54128d372..06292c75f27e0 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1222,6 +1222,8 @@ template <> struct MappingTraits<FormatStyle> {
IO.mapOptional("SpaceBeforeInheritanceColon",
Style.SpaceBeforeInheritanceColon);
IO.mapOptional("SpaceBeforeJsonColon", Style.SpaceBeforeJsonColon);
+ IO.mapOptional("SpaceBeforeClosingBlockComment",
+ Style.SpaceBeforeClosingBlockComment);
IO.mapOptional("SpaceBeforeParens", Style.SpaceBeforeParens);
IO.mapOptional("SpaceBeforeParensOptions", Style.SpaceBeforeParensOptions);
IO.mapOptional("SpaceBeforeRangeBasedForLoopColon",
@@ -1717,6 +1719,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
LLVMStyle.SpaceBeforeCtorInitializerColon = true;
LLVMStyle.SpaceBeforeInheritanceColon = true;
LLVMStyle.SpaceBeforeJsonColon = false;
+ LLVMStyle.SpaceBeforeClosingBlockComment = false;
LLVMStyle.SpaceBeforeParens = FormatStyle::SBPO_ControlStatements;
LLVMStyle.SpaceBeforeParensOptions = {};
LLVMStyle.SpaceBeforeParensOptions.AfterControlStatements = true;
diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h
index f015d27bed6af..0e0d407c37519 100644
--- a/clang/lib/Format/FormatToken.h
+++ b/clang/lib/Format/FormatToken.h
@@ -324,9 +324,9 @@ struct FormatToken {
EndsBinaryExpression(false), PartOfMultiVariableDeclStmt(false),
ContinuesLineCommentSection(false), Finalized(false),
ClosesRequiresClause(false), EndsCppAttributeGroup(false),
- BlockKind(BK_Unknown), Decision(FD_Unformatted),
- PackingKind(PPK_Inconclusive), TypeIsFinalized(false),
- Type(TT_Unknown) {}
+ NeedsSpaceBeforeClosingBlockComment(false), BlockKind(BK_Unknown),
+ Decision(FD_Unformatted), PackingKind(PPK_Inconclusive),
+ TypeIsFinalized(false), Type(TT_Unknown) {}
/// The \c Token.
Token Tok;
@@ -402,6 +402,9 @@ struct FormatToken {
/// \c true if this token ends a group of C++ attributes.
unsigned EndsCppAttributeGroup : 1;
+ /// \c true if clang-format should insert a space before the closing '*/'.
+ unsigned NeedsSpaceBeforeClosingBlockComment : 1;
+
private:
/// Contains the kind of block if this token is a brace.
unsigned BlockKind : 2;
@@ -505,6 +508,11 @@ struct FormatToken {
/// token.
unsigned LastLineColumnWidth = 0;
+ /// Offset (from the start of the token) where a space should be inserted
+ /// before the closing '*/' when \c NeedsSpaceBeforeClosingBlockComment is
+ /// set.
+ unsigned SpaceBeforeClosingBlockCommentOffset = 0;
+
/// The number of spaces that should be inserted before this token.
unsigned SpacesRequiredBefore = 0;
diff --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp
index 86a5185a92a52..374caa4c1020c 100644
--- a/clang/lib/Format/FormatTokenLexer.cpp
+++ b/clang/lib/Format/FormatTokenLexer.cpp
@@ -1386,6 +1386,22 @@ FormatToken *FormatTokenLexer::getNextToken() {
StringRef UntrimmedText = FormatTok->TokenText;
FormatTok->TokenText = FormatTok->TokenText.rtrim(" \t\v\f");
TrailingWhitespace = UntrimmedText.size() - FormatTok->TokenText.size();
+ if (Style.SpaceBeforeClosingBlockComment &&
+ FormatTok->TokenText.starts_with("/*") &&
+ FormatTok->TokenText.ends_with("*/")) {
+ StringRef Body = FormatTok->TokenText.drop_front(2).drop_back(2);
+ if (!Body.empty()) {
+ while (!Body.empty() && (Body.back() == '\n' || Body.back() == '\r'))
+ Body = Body.drop_back();
+ const bool BodyEmptyAfterStrippingNewlines = Body.empty();
+ if (!BodyEmptyAfterStrippingNewlines &&
+ !isHorizontalWhitespace(static_cast<unsigned char>(Body.back()))) {
+ FormatTok->NeedsSpaceBeforeClosingBlockComment = true;
+ FormatTok->SpaceBeforeClosingBlockCommentOffset =
+ FormatTok->TokenText.size() - 2;
+ }
+ }
+ }
} else if (FormatTok->is(tok::raw_identifier)) {
IdentifierInfo &Info = IdentTable.get(FormatTok->TokenText);
FormatTok->Tok.setIdentifierInfo(&Info);
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 5b784eded4601..80aa34c855493 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -4821,7 +4821,10 @@ bool TokenAnnotator::spaceRequiredBetween(const AnnotatedLine &Line,
}
if (Left.is(TT_BlockComment)) {
// No whitespace in x(/*foo=*/1), except for JavaScript.
- return Style.isJavaScript() || !Left.TokenText.ends_with("=*/");
+ bool EndsWithAssignmentComment = Left.TokenText.ends_with("=*/");
+ if (!EndsWithAssignmentComment && Style.SpaceBeforeClosingBlockComment)
+ EndsWithAssignmentComment = Left.TokenText.ends_with("= */");
+ return Style.isJavaScript() || !EndsWithAssignmentComment;
}
// Space between template and attribute.
diff --git a/clang/unittests/Format/FormatTestComments.cpp b/clang/unittests/Format/FormatTestComments.cpp
index 69026bce98705..92ab32da9a2ba 100644
--- a/clang/unittests/Format/FormatTestComments.cpp
+++ b/clang/unittests/Format/FormatTestComments.cpp
@@ -332,6 +332,53 @@ TEST_F(FormatTestComments, UnderstandsSingleLineComments) {
verifyNoCrash(StringRef("/*\\\0\n/", 6));
}
+TEST_F(FormatTestComments, InsertsSpaceBeforeClosingBlockComment) {
+ FormatStyle Style = getLLVMStyle();
+ Style.SpaceBeforeClosingBlockComment = true;
+
+ verifyFormat("foo(/* comment */);", "foo(/* comment*/);", Style);
+ verifyFormat("foo(/*Logger= */nullptr);", "foo(/*Logger=*/nullptr);", Style);
+ verifyFormat("/* comment */", Style);
+ verifyFormat("/* comment before code */\nint x;",
+ "/* comment before code*/\nint x;", Style);
+ verifyFormat("/* comment\n */", "/* comment\n*/", Style);
+ verifyFormat("/* comment\n */\nint x;", "/* comment\n*/\nint x;", Style);
+ verifyFormat("/*\ncomment line\n */", "/*\ncomment line\n*/", Style);
+ verifyFormat("/*\n * comment star\n */", "/*\n * comment star\n*/", Style);
+ verifyFormat("/* comment line\nnext */", "/* comment line\nnext*/", Style);
+ verifyFormat("/*\n*/", Style);
+ verifyFormat("/*\n\n*/", "/*\n \n*/", Style);
+ verifyFormat("/*This is a multi line comment\n"
+ "this is the next line\n"
+ "and this is the 3th line. */",
+ "/*This is a multi line comment\n"
+ "this is the next line\n"
+ "and this is the 3th line.*/",
+ Style);
+ verifyFormat(
+ "/*\n * Another multi line comment\n * this is the next line. */",
+ "/*\n * Another multi line comment\n * this is the next line.*/", Style);
+}
+
+TEST_F(FormatTestComments, BlockCommentDoesNotForceBreakBeforeFollowingToken) {
+ FormatStyle Style = getLLVMStyle();
+ Style.SpaceBeforeClosingBlockComment = true;
+
+ verifyFormat("switch (n) {\n"
+ "case 1:\n"
+ " foo();\n"
+ "/*FALLTHROUGH */ case 2:\n"
+ " bar();\n"
+ "}\n",
+ "switch (n) {\n"
+ "case 1:\n"
+ " foo();\n"
+ " /*FALLTHROUGH*/ case 2:\n"
+ " bar();\n"
+ "}\n",
+ Style);
+}
+
TEST_F(FormatTestComments, KeepsParameterWithTrailingCommentsOnTheirOwnLine) {
EXPECT_EQ("SomeFunction(a,\n"
" b, // comment\n"
More information about the cfe-commits
mailing list