[clang] [clang-format] Option to insert spaces before the closing `*/` (PR #162105)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Oct 30 10:45:16 PDT 2025
https://github.com/Men-cotton updated https://github.com/llvm/llvm-project/pull/162105
>From 7a29a63fe2bfca5e7bf6af5ab2fd3f0ff1ea52cf Mon Sep 17 00:00:00 2001
From: mencotton <mencotton0410 at gmail.com>
Date: Tue, 7 Oct 2025 23:14:20 +0900
Subject: [PATCH 1/9] [clang-format] Add SpaceInComments controls for block
comment delimiters (#162105)
---
clang/docs/ClangFormatStyleOptions.rst | 42 ++
clang/include/clang/Format/Format.h | 68 +++
clang/lib/Format/BreakableToken.cpp | 477 ++++++++++++++++--
clang/lib/Format/BreakableToken.h | 71 +++
clang/lib/Format/Format.cpp | 19 +
clang/lib/Format/FormatToken.h | 39 +-
clang/lib/Format/FormatTokenLexer.cpp | 72 +++
clang/lib/Format/TokenAnnotator.cpp | 12 +-
clang/lib/Format/WhitespaceManager.cpp | 5 +
clang/unittests/Format/ConfigParseTest.cpp | 25 +
clang/unittests/Format/FormatTestComments.cpp | 391 ++++++++++++++
clang/unittests/Format/TokenAnnotatorTest.cpp | 32 ++
12 files changed, 1202 insertions(+), 51 deletions(-)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index b746df5dab264..45966c9e7b33a 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -6612,6 +6612,48 @@ the configuration (without a prefix: ``Auto``).
int a [5]; vs. int a[5];
int a [5][5]; vs. int a[5][5];
+.. _SpaceInComments:
+
+**SpaceInComments** (``SpaceInCommentsOptions``) :versionbadge:`clang-format 21` :ref:`¶ <SpaceInComments>`
+ Controls whitespace around block comment delimiters and parameter-style
+ inline comments. Each field accepts a ``CommentSpaceMode``: ``Leave``
+ (preserve existing spacing, the default), ``Always`` (insert a single
+ space), or ``Never`` (remove all spaces).
+
+ The available controls are:
+
+ ``AfterOpeningComment``
+ Governs the space immediately after ``/*`` in regular block comments.
+ ``BeforeClosingComment``
+ Governs the space before ``*/`` in regular block comments.
+ ``AfterOpeningParamComment``
+ Governs the space after ``/*`` in parameter comments such as
+ ``/*param=*/``.
+ ``BeforeClosingParamComment``
+ Governs the space before ``*/`` in parameter comments.
+
+ .. code-block:: c++
+
+ // BeforeClosingComment: Always
+ auto Value = foo(/* comment */);
+ // BeforeClosingParamComment: Never
+ auto Number = foo(/*param=*/42);
+
+ Nested configuration flags:
+
+ Specifies spacing behavior for different block comment forms.
+
+ * ``CommentSpaceMode AfterOpeningComment`` :versionbadge:`clang-format 21`
+ Governs the space immediately after ``/*`` in regular block comments.
+
+ * ``CommentSpaceMode BeforeClosingComment`` Governs the space before ``*/`` in regular block comments.
+
+ * ``CommentSpaceMode AfterOpeningParamComment`` Governs the space after ``/*`` in parameter comments such as
+ ``/*param=*/``.
+
+ * ``CommentSpaceMode BeforeClosingParamComment`` Governs the space before ``*/`` in parameter comments.
+
+
.. _SpaceInEmptyBlock:
**SpaceInEmptyBlock** (``Boolean``) :versionbadge:`clang-format 10` :ref:`¶ <SpaceInEmptyBlock>`
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 3df5b92654094..906c490626c9d 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -2510,6 +2510,19 @@ struct FormatStyle {
/// \version 19
BreakTemplateDeclarationsStyle BreakTemplateDeclarations;
+ /// Defines how clang-format should treat spaces around block comment
+ /// delimiters and specialized inline comments (such as parameter name
+ /// annotations). The default `Leave` mode preserves existing whitespace.
+ /// \version 21
+ enum class CommentSpaceMode : int8_t {
+ /// Preserve existing whitespace, making no formatting changes.
+ Leave,
+ /// Ensure exactly one space is present.
+ Always,
+ /// Ensure no space is present.
+ Never,
+ };
+
/// If ``true``, consecutive namespace declarations will be on the same
/// line. If ``false``, each namespace is declared on a new line.
/// \code
@@ -4898,6 +4911,60 @@ struct FormatStyle {
/// \version 7
bool SpaceBeforeRangeBasedForLoopColon;
+ /// Specifies spacing behavior for different block comment forms.
+ /// \version 21
+ struct SpaceInCommentsOptions {
+ /// Governs the space immediately after ``/*`` in regular block comments.
+ CommentSpaceMode AfterOpeningComment;
+ /// Governs the space before ``*/`` in regular block comments.
+ CommentSpaceMode BeforeClosingComment;
+ /// Governs the space after ``/*`` in parameter comments such as
+ /// ``/*param=*/``.
+ CommentSpaceMode AfterOpeningParamComment;
+ /// Governs the space before ``*/`` in parameter comments.
+ CommentSpaceMode BeforeClosingParamComment;
+
+ SpaceInCommentsOptions()
+ : AfterOpeningComment(CommentSpaceMode::Leave),
+ BeforeClosingComment(CommentSpaceMode::Leave),
+ AfterOpeningParamComment(CommentSpaceMode::Leave),
+ BeforeClosingParamComment(CommentSpaceMode::Leave) {}
+
+ constexpr bool operator==(const SpaceInCommentsOptions &R) const {
+ return AfterOpeningComment == R.AfterOpeningComment &&
+ BeforeClosingComment == R.BeforeClosingComment &&
+ AfterOpeningParamComment == R.AfterOpeningParamComment &&
+ BeforeClosingParamComment == R.BeforeClosingParamComment;
+ }
+ };
+
+ /// Controls whitespace around block comment delimiters and parameter-style
+ /// inline comments. Each field accepts a ``CommentSpaceMode``: ``Leave``
+ /// (preserve existing spacing, the default), ``Always`` (insert a single
+ /// space), or ``Never`` (remove all spaces).
+ ///
+ /// The available controls are:
+ ///
+ /// ``AfterOpeningComment``
+ /// Governs the space immediately after ``/*`` in regular block comments.
+ /// ``BeforeClosingComment``
+ /// Governs the space before ``*/`` in regular block comments.
+ /// ``AfterOpeningParamComment``
+ /// Governs the space after ``/*`` in parameter comments such as
+ /// ``/*param=*/``.
+ /// ``BeforeClosingParamComment``
+ /// Governs the space before ``*/`` in parameter comments.
+ ///
+ /// .. code-block:: c++
+ ///
+ /// // BeforeClosingComment: Always
+ /// auto Value = foo(/* comment */);
+ /// // BeforeClosingParamComment: Never
+ /// auto Number = foo(/*param=*/42);
+ ///
+ /// \version 21
+ SpaceInCommentsOptions SpaceInComments;
+
/// This option is **deprecated**. See ``Block`` of ``SpaceInEmptyBraces``.
/// \version 10
// bool SpaceInEmptyBlock;
@@ -5612,6 +5679,7 @@ struct FormatStyle {
SpaceBeforeRangeBasedForLoopColon ==
R.SpaceBeforeRangeBasedForLoopColon &&
SpaceBeforeSquareBrackets == R.SpaceBeforeSquareBrackets &&
+ SpaceInComments == R.SpaceInComments &&
SpaceInEmptyBraces == R.SpaceInEmptyBraces &&
SpacesBeforeTrailingComments == R.SpacesBeforeTrailingComments &&
SpacesInAngles == R.SpacesInAngles &&
diff --git a/clang/lib/Format/BreakableToken.cpp b/clang/lib/Format/BreakableToken.cpp
index 29db20067c361..8f2a589c0c201 100644
--- a/clang/lib/Format/BreakableToken.cpp
+++ b/clang/lib/Format/BreakableToken.cpp
@@ -19,13 +19,234 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Debug.h"
#include <algorithm>
+#include <iterator>
#define DEBUG_TYPE "format-token-breaker"
namespace clang {
namespace format {
+class BreakableBlockComment;
+
static constexpr StringRef Blanks(" \t\v\f\r");
+static constexpr size_t BlockCommentOpenerLength = 2;
+static constexpr size_t BlockCommentCloserLength = 2;
+
+namespace {
+
+bool isWellFormedBlockCommentText(StringRef Text) {
+ return Text.size() >= BlockCommentOpenerLength + BlockCommentCloserLength &&
+ Text.starts_with("/*") && Text.ends_with("*/");
+}
+
+int countTrailingSpaces(StringRef Text) {
+ const size_t TrimmedSize = Text.rtrim(Blanks).size();
+ return static_cast<int>(Text.size() - TrimmedSize);
+}
+
+FormatStyle::CommentSpaceMode
+resolveCommentSpaceMode(const FormatStyle &Style, const FormatToken &Tok,
+ FormatStyle::CommentSpaceMode GeneralMode,
+ FormatStyle::CommentSpaceMode ParamOverrideMode,
+ const bool ForceDocstringLeave) {
+ if (Tok.getBlockCommentKind() == CommentKind::Parameter) {
+ if (ParamOverrideMode != FormatStyle::CommentSpaceMode::Leave)
+ return ParamOverrideMode;
+ }
+ // Docstrings intentionally keep their leading whitespace when we tidy up
+ // the opening delimiter. Callers that pass ForceDocstringLeave are operating
+ // on that opening boundary and must not disturb the docstring layout.
+ if (ForceDocstringLeave &&
+ Tok.getBlockCommentKind() == CommentKind::DocString) {
+ return FormatStyle::CommentSpaceMode::Leave;
+ }
+ return GeneralMode;
+}
+
+StringRef getBlockCommentInterior(const FormatToken &Tok) {
+ const StringRef Text = Tok.TokenText;
+ assert(isWellFormedBlockCommentText(Text) &&
+ "Caller must ensure block comment validity.");
+ return Text.drop_front(BlockCommentOpenerLength)
+ .drop_back(BlockCommentCloserLength);
+}
+
+void replaceCommentWhitespace(const FormatToken &Tok, unsigned Offset,
+ unsigned Length, StringRef Prefix,
+ unsigned Newlines, WhitespaceManager &Whitespaces,
+ bool InPPDirective) {
+ Whitespaces.replaceWhitespaceInToken(Tok, Offset, Length,
+ /*PreviousPostfix=*/"",
+ /*CurrentPrefix=*/Prefix, InPPDirective,
+ Newlines,
+ /*Spaces=*/0);
+}
+
+} // namespace
+
+FormatStyle::CommentSpaceMode getAfterOpeningSpaceMode(const FormatStyle &Style,
+ const FormatToken &Tok) {
+ return resolveCommentSpaceMode(Style, Tok,
+ Style.SpaceInComments.AfterOpeningComment,
+ Style.SpaceInComments.AfterOpeningParamComment,
+ /*ForceDocstringLeave=*/true);
+}
+
+StringRef getSliceAfterOpening(const FormatToken &Tok) {
+ assert(isWellFormedBlockCommentText(Tok.TokenText) &&
+ "Caller must ensure block comment validity.");
+ return Tok.TokenText.drop_front(BlockCommentOpenerLength);
+}
+
+static unsigned countLeadingHorizontalWhitespace(StringRef Text) {
+ const size_t FirstNonHorizontal = Text.find_first_not_of(" \t\v\f\r");
+ if (FirstNonHorizontal == StringRef::npos)
+ return Text.size();
+ if (Text[FirstNonHorizontal] == '\n' || Text[FirstNonHorizontal] == '\r')
+ return static_cast<unsigned>(FirstNonHorizontal);
+ return static_cast<unsigned>(FirstNonHorizontal);
+}
+
+unsigned countLeadingHorizontalWhitespaceAfterOpening(const FormatToken &Tok) {
+ return countLeadingHorizontalWhitespace(getBlockCommentInterior(Tok));
+}
+
+FormatStyle::CommentSpaceMode
+getBeforeClosingSpaceMode(const FormatStyle &Style, const FormatToken &Tok) {
+ return resolveCommentSpaceMode(
+ Style, Tok, Style.SpaceInComments.BeforeClosingComment,
+ Style.SpaceInComments.BeforeClosingParamComment,
+ /*ForceDocstringLeave=*/false);
+}
+
+static unsigned countTrailingHorizontalWhitespace(StringRef Body) {
+ // Measure horizontal whitespace at the end of the last content line while
+ // ignoring any trailing line terminators.
+ const size_t LastNonNewline = Body.find_last_not_of("\n\r");
+ if (LastNonNewline == StringRef::npos)
+ return 0;
+
+ const StringRef WithoutTrailingNewlines = Body.take_front(LastNonNewline + 1);
+ const size_t TrimmedSize = WithoutTrailingNewlines.size();
+ const size_t LastNonBlank = WithoutTrailingNewlines.find_last_not_of(Blanks);
+ if (LastNonBlank == StringRef::npos)
+ return static_cast<unsigned>(TrimmedSize);
+
+ return static_cast<unsigned>(TrimmedSize - (LastNonBlank + 1));
+}
+
+unsigned
+countTrailingHorizontalWhitespaceBeforeClosing(const FormatToken &Tok) {
+ return countTrailingHorizontalWhitespace(getBlockCommentInterior(Tok));
+}
+
+static unsigned countLogicalNewlines(StringRef Text) {
+ unsigned Count = 0;
+ const size_t End = Text.size();
+ for (size_t I = 0; I < End; ++I) {
+ if (Text[I] == '\r') {
+ ++Count;
+ if (I + 1 < End && Text[I + 1] == '\n')
+ ++I;
+ } else if (Text[I] == '\n') {
+ ++Count;
+ }
+ }
+ return Count;
+}
+
+void applyAfterOpeningBlockCommentSpacing(const FormatStyle &Style,
+ const FormatToken &Tok,
+ WhitespaceManager &Whitespaces,
+ bool InPPDirective) {
+ using CommentSpaceMode = FormatStyle::CommentSpaceMode;
+ if (!Tok.is(tok::comment) || !isWellFormedBlockCommentText(Tok.TokenText))
+ return;
+
+ const CommentSpaceMode Mode = getAfterOpeningSpaceMode(Style, Tok);
+ if (Mode == CommentSpaceMode::Leave)
+ return;
+
+ const StringRef Interior = getBlockCommentInterior(Tok);
+
+ const bool OnlyWhitespace =
+ Interior.find_first_not_of(" \t\r\n") == StringRef::npos;
+ const unsigned OpeningOffset = BlockCommentOpenerLength;
+ const unsigned LeadingSpaces =
+ countLeadingHorizontalWhitespaceAfterOpening(Tok);
+
+ switch (Mode) {
+ case CommentSpaceMode::Never:
+ if (LeadingSpaces > 0) {
+ replaceCommentWhitespace(Tok, OpeningOffset, LeadingSpaces, "",
+ /*Newlines=*/0, Whitespaces, InPPDirective);
+ }
+ return;
+ case CommentSpaceMode::Always:
+ if (OnlyWhitespace && !Interior.empty()) {
+ const unsigned ReplaceChars = Interior.size();
+ if (Interior.contains('\n')) {
+ // Collapses empty multi-line bodies like "/* \n */" so the opener and
+ // closer sit on neighboring lines without inventing placeholder spaces.
+ unsigned NewlineCount = countLogicalNewlines(Interior);
+ replaceCommentWhitespace(Tok, OpeningOffset, ReplaceChars, "",
+ NewlineCount, Whitespaces, InPPDirective);
+ } else {
+ // Normalizes purely whitespace single-line comments such as "/* */"
+ // to contain exactly one space of interior padding.
+ replaceCommentWhitespace(Tok, OpeningOffset, ReplaceChars, " ",
+ /*Newlines=*/0, Whitespaces, InPPDirective);
+ }
+ return;
+ }
+ if (LeadingSpaces != 1) {
+ replaceCommentWhitespace(Tok, OpeningOffset, LeadingSpaces, " ",
+ /*Newlines=*/0, Whitespaces, InPPDirective);
+ }
+ return;
+ case CommentSpaceMode::Leave:
+ break;
+ }
+ llvm_unreachable("Unhandled CommentSpaceMode");
+}
+
+void applyBeforeClosingBlockCommentSpacing(const FormatStyle &Style,
+ const FormatToken &Tok,
+ WhitespaceManager &Whitespaces,
+ bool InPPDirective) {
+ using CommentSpaceMode = FormatStyle::CommentSpaceMode;
+ if (!Tok.is(tok::comment) || !isWellFormedBlockCommentText(Tok.TokenText))
+ return;
+
+ const CommentSpaceMode Mode = getBeforeClosingSpaceMode(Style, Tok);
+ if (Mode == CommentSpaceMode::Leave)
+ return;
+
+ const StringRef Text = Tok.TokenText;
+
+ const unsigned TrailingSpaces =
+ countTrailingHorizontalWhitespaceBeforeClosing(Tok);
+ const unsigned ReplaceOffset =
+ Text.size() - BlockCommentCloserLength - TrailingSpaces;
+
+ switch (Mode) {
+ case CommentSpaceMode::Never:
+ if (TrailingSpaces > 0) {
+ replaceCommentWhitespace(Tok, ReplaceOffset, TrailingSpaces, "",
+ /*Newlines=*/0, Whitespaces, InPPDirective);
+ }
+ return;
+ case CommentSpaceMode::Always:
+ if (TrailingSpaces != 1) {
+ replaceCommentWhitespace(Tok, ReplaceOffset, TrailingSpaces, " ",
+ /*Newlines=*/0, Whitespaces, InPPDirective);
+ }
+ return;
+ case CommentSpaceMode::Leave:
+ break;
+ }
+ llvm_unreachable("Unhandled CommentSpaceMode");
+}
static StringRef getLineCommentIndentPrefix(StringRef Comment,
const FormatStyle &Style) {
@@ -475,9 +696,21 @@ BreakableBlockComment::BreakableBlockComment(
"block comment section must start with a block comment");
StringRef TokenText(Tok.TokenText);
- assert(TokenText.starts_with("/*") && TokenText.ends_with("*/"));
- TokenText.substr(2, TokenText.size() - 4)
- .split(Lines, UseCRLF ? "\r\n" : "\n");
+ assert(isWellFormedBlockCommentText(Tok.TokenText));
+ const StringRef Interior = getBlockCommentInterior(Tok);
+ LeadingWhitespaceAfterOpening = countLeadingHorizontalWhitespace(Interior);
+ TrailingWhitespaceBeforeClosing = countTrailingHorizontalWhitespace(Interior);
+ getBlockCommentInterior(Tok).split(Lines, UseCRLF ? "\r\n" : "\n");
+
+ if (!Lines.empty() && Lines[0].empty() &&
+ getAfterOpeningSpaceMode(Style, Tok) ==
+ FormatStyle::CommentSpaceMode::Always) {
+ StringRef AfterOpening = getSliceAfterOpening(Tok);
+ if (!AfterOpening.empty() &&
+ (AfterOpening.front() == '\n' || AfterOpening.front() == '\r')) {
+ PreservedFirstLineSpaceAfterOpening = true;
+ }
+ }
int IndentDelta = StartColumn - OriginalStartColumn;
Content.resize(Lines.size());
@@ -534,9 +767,13 @@ BreakableBlockComment::BreakableBlockComment(
// trailing */. We also need to preserve whitespace, so that */ is
// correctly indented.
LastLineNeedsDecoration = false;
- // Align the star in the last '*/' with the stars on the previous lines.
- if (e >= 2 && !Decoration.empty())
+ // Align the star in the last '*/' with the stars on the previous lines,
+ // unless we purposely preserved a space-only first line to add a blank
+ // after the opening token.
+ if (e >= 2 && !Decoration.empty() &&
+ !PreservedFirstLineSpaceAfterOpening) {
ContentColumn[i] = DecorationColumn;
+ }
} else if (Decoration.empty()) {
// For all other lines, set the start column to 0 if they're empty, so
// we do not insert trailing whitespace anywhere.
@@ -616,10 +853,23 @@ void BreakableBlockComment::adjustWhitespace(unsigned LineIndex,
// Calculate the end of the non-whitespace text in the previous line.
EndOfPreviousLine =
Lines[LineIndex - 1].find_last_not_of(Blanks, EndOfPreviousLine);
- if (EndOfPreviousLine == StringRef::npos)
- EndOfPreviousLine = 0;
- else
+ if (EndOfPreviousLine == StringRef::npos) {
+ const bool IsFirstContentLine = LineIndex == 1;
+ const bool ShouldKeepSpaceAfterOpening =
+ IsFirstContentLine &&
+ getAfterOpeningSpaceMode(Style, Tok) ==
+ FormatStyle::CommentSpaceMode::Always &&
+ !Lines[LineIndex - 1].empty();
+ // PreservedFirstLineSpaceAfterOpening tracks whether we already committed
+ // to keeping a single-space first line in the constructor. Re-checking the
+ // mode here ensures we only lock that state in if the style still requires
+ // it while walking the lines.
+ if (ShouldKeepSpaceAfterOpening)
+ PreservedFirstLineSpaceAfterOpening = true;
+ EndOfPreviousLine = ShouldKeepSpaceAfterOpening ? 1u : 0u;
+ } else {
++EndOfPreviousLine;
+ }
// Calculate the start of the non-whitespace text in the current line.
size_t StartOfLine = Lines[LineIndex].find_first_not_of(Blanks);
if (StartOfLine == StringRef::npos)
@@ -775,50 +1025,183 @@ void BreakableBlockComment::reflow(unsigned LineIndex,
void BreakableBlockComment::adaptStartOfLine(
unsigned LineIndex, WhitespaceManager &Whitespaces) const {
+ const FormatStyle::CommentSpaceMode BeforeClosingMode =
+ getBeforeClosingSpaceMode(Style, Tok);
+
+ if (isSingleLineBlockComment()) {
+ if (LineIndex == 0)
+ adaptSingleLineComment(Whitespaces, BeforeClosingMode);
+ return;
+ }
+
if (LineIndex == 0) {
- if (DelimitersOnNewline) {
- // Since we're breaking at index 1 below, the break position and the
- // break length are the same.
- // Note: this works because getCommentSplit is careful never to split at
- // the beginning of a line.
- size_t BreakLength = Lines[0].substr(1).find_first_not_of(Blanks);
- if (BreakLength != StringRef::npos) {
- insertBreak(LineIndex, 0, Split(1, BreakLength), /*ContentIndent=*/0,
- Whitespaces);
- }
- }
+ adaptFirstLineOfMultiLineComment(Whitespaces, BeforeClosingMode);
return;
}
- // Here no reflow with the previous line will happen.
- // Fix the decoration of the line at LineIndex.
- StringRef Prefix = Decoration;
- if (Content[LineIndex].empty()) {
- if (LineIndex + 1 == Lines.size()) {
- if (!LastLineNeedsDecoration) {
- // If the last line was empty, we don't need a prefix, as the */ will
- // line up with the decoration (if it exists).
- Prefix = "";
- }
- } else if (!Decoration.empty()) {
- // For other empty lines, if we do have a decoration, adapt it to not
- // contain a trailing whitespace.
- Prefix = Prefix.substr(0, 1);
+
+ adaptIntermediateLineOfComment(LineIndex, Whitespaces, BeforeClosingMode);
+}
+
+void BreakableBlockComment::adaptSingleLineComment(
+ WhitespaceManager &Whitespaces,
+ FormatStyle::CommentSpaceMode BeforeClosingMode) const {
+ applyAfterOpeningBlockCommentSpacing(Style, Tok, Whitespaces, InPPDirective);
+ applyBeforeClosingBlockCommentSpacing(Style, Tok, Whitespaces, InPPDirective);
+}
+
+void BreakableBlockComment::adaptFirstLineOfMultiLineComment(
+ WhitespaceManager &Whitespaces,
+ FormatStyle::CommentSpaceMode BeforeClosingMode) const {
+ applyAfterOpeningBlockCommentSpacing(Style, Tok, Whitespaces, InPPDirective);
+
+ if (BeforeClosingMode == FormatStyle::CommentSpaceMode::Always) {
+ const bool TerminatorOnSeparateLine =
+ !Lines.empty() && Lines.back().ltrim(Blanks).empty();
+
+ if (!TerminatorOnSeparateLine && isWellFormedBlockComment() &&
+ Tok.NeedsSpaceBeforeClosingBlockComment &&
+ Tok.SpaceBeforeClosingBlockCommentOffset < Tok.TokenText.size()) {
+ replaceCommentWhitespace(Tok, Tok.SpaceBeforeClosingBlockCommentOffset,
+ /*Length=*/0,
+ /*CurrentPrefix=*/" ", /*Newlines=*/0,
+ Whitespaces, InPPDirective);
}
- } else if (ContentColumn[LineIndex] == 1) {
- // This line starts immediately after the decorating *.
- Prefix = Prefix.substr(0, 1);
}
- // This is the offset of the end of the last line relative to the start of the
- // token text in the token.
- unsigned WhitespaceOffsetInToken = Content[LineIndex - 1].data() +
- Content[LineIndex - 1].size() -
- tokenAt(LineIndex).TokenText.data();
- unsigned WhitespaceLength = Content[LineIndex].data() -
- tokenAt(LineIndex).TokenText.data() -
- WhitespaceOffsetInToken;
- Whitespaces.replaceWhitespaceInToken(
- tokenAt(LineIndex), WhitespaceOffsetInToken, WhitespaceLength, "", Prefix,
- InPPDirective, /*Newlines=*/1, ContentColumn[LineIndex] - Prefix.size());
+
+ if (DelimitersOnNewline) {
+ // Since we're breaking at index 1 below, the break position and the break
+ // length are the same.
+ // Note: this works because getCommentSplit is careful never to split at the
+ // beginning of a line.
+ size_t LeadingWhitespaceLength =
+ Lines[0].substr(1).find_first_not_of(Blanks);
+ if (LeadingWhitespaceLength != StringRef::npos) {
+ insertBreak(/*LineIndex=*/0, /*TailOffset=*/0,
+ Split(/*First=*/1, LeadingWhitespaceLength),
+ /*ContentIndent=*/0, Whitespaces);
+ }
+ }
+}
+
+void BreakableBlockComment::adaptIntermediateLineOfComment(
+ unsigned LineIndex, WhitespaceManager &Whitespaces,
+ FormatStyle::CommentSpaceMode BeforeClosingMode) const {
+ assert(LineIndex > 0 && "First line should be handled separately.");
+
+ CommentLineInfo LineInfo;
+ LineInfo.IsEmpty = Content[LineIndex].empty();
+ LineInfo.IsLastLine = (LineIndex + 1 == Lines.size());
+ LineInfo.LastLineNeedsDecoration = LastLineNeedsDecoration;
+ LineInfo.StartsImmediatelyAfterDecoration = (ContentColumn[LineIndex] == 1);
+ LineInfo.Decoration = Decoration;
+
+ const StringRef Prefix = calculateLinePrefix(LineInfo);
+ const InterLineWhitespace Whitespace =
+ calculateInterLineWhitespace(LineIndex);
+ int Spaces = ContentColumn[LineIndex] - static_cast<int>(Prefix.size());
+ const bool IsTerminatorOnSeparateLine =
+ Content[LineIndex].ltrim(Blanks).empty();
+
+ if (LineInfo.IsLastLine && IsTerminatorOnSeparateLine) {
+ Spaces =
+ calculateTerminatorIndent(LineIndex, Prefix, BeforeClosingMode, Spaces);
+ }
+
+ Whitespaces.replaceWhitespaceInToken(tokenAt(LineIndex), Whitespace.Offset,
+ Whitespace.Length, "", Prefix,
+ InPPDirective, /*Newlines=*/1, Spaces);
+}
+
+StringRef
+BreakableBlockComment::calculateLinePrefix(const CommentLineInfo &Info) const {
+ if (Info.IsEmpty) {
+ if (Info.IsLastLine)
+ return Info.LastLineNeedsDecoration ? Info.Decoration : StringRef();
+ // For empty lines other than the last one, we only want the star,
+ // not the trailing space of the decoration.
+ if (!Info.Decoration.empty())
+ return Info.Decoration.substr(0, 1);
+ return {};
+ }
+
+ // If the content starts immediately after the decoration, it means the user
+ // has not put a space after the star. In that case, we should probably not
+ // add one either. We only add the star.
+ if (Info.StartsImmediatelyAfterDecoration && !Info.Decoration.empty())
+ return Info.Decoration.substr(0, 1);
+
+ return Info.Decoration;
+}
+
+BreakableBlockComment::InterLineWhitespace
+BreakableBlockComment::calculateInterLineWhitespace(unsigned LineIndex) const {
+ assert(LineIndex > 0 && "Expected a previous line to exist.");
+ const StringRef TokenText = tokenAt(LineIndex).TokenText;
+ const StringRef Previous = Content[LineIndex - 1];
+ const StringRef Current = Content[LineIndex];
+
+ const auto TokenBegin = TokenText.begin();
+ const auto PreviousEnd = Previous.end();
+ const auto CurrentBegin = Current.begin();
+
+ assert(TokenBegin <= PreviousEnd && PreviousEnd <= TokenText.end() &&
+ "Previous line must be within the token text.");
+ assert(TokenBegin <= CurrentBegin && CurrentBegin <= TokenText.end() &&
+ "Current line must be within the token text.");
+
+ InterLineWhitespace Result;
+ Result.Offset = static_cast<unsigned>(std::distance(TokenBegin, PreviousEnd));
+ Result.Length =
+ static_cast<unsigned>(std::distance(PreviousEnd, CurrentBegin));
+ return Result;
+}
+
+bool BreakableBlockComment::allPreviousLinesEmpty(unsigned LineIndex) const {
+ assert(LineIndex > 0 && "First line has no predecessors.");
+ return std::all_of(Content.begin(), Content.begin() + LineIndex,
+ [](StringRef Line) { return Line.trim(Blanks).empty(); });
+}
+
+bool BreakableBlockComment::isWellFormedBlockComment() const {
+ return isWellFormedBlockCommentText(Tok.TokenText);
+}
+
+bool BreakableBlockComment::isSingleLineBlockComment() const {
+ // Treat long single-line JSDoc/JavaDoc (that request delimiter newlines)
+ // as multi-line to trigger the opening break after "/**".
+ return isWellFormedBlockComment() && Lines.size() == 1 &&
+ !DelimitersOnNewline;
+}
+
+bool BreakableBlockComment::isWhitespaceOnlySingleLineBlockComment() const {
+ if (!isSingleLineBlockComment())
+ return false;
+
+ const StringRef Body = getBlockCommentInterior(Tok);
+ return Body.trim(" \t").empty();
+}
+
+int BreakableBlockComment::calculateTerminatorIndent(
+ unsigned LineIndex, StringRef Prefix, FormatStyle::CommentSpaceMode Mode,
+ int BaseSpaces) const {
+ if (Mode == FormatStyle::CommentSpaceMode::Leave)
+ return BaseSpaces;
+ if (Mode == FormatStyle::CommentSpaceMode::Never)
+ return 0;
+
+ assert(Mode == FormatStyle::CommentSpaceMode::Always &&
+ "Unexpected CommentSpaceMode");
+
+ if (!Tok.NeedsSpaceBeforeClosingBlockComment)
+ return allPreviousLinesEmpty(LineIndex) ? 0 : BaseSpaces;
+
+ if (BaseSpaces <= 0)
+ return allPreviousLinesEmpty(LineIndex) ? 0 : 1;
+
+ const int TrailingSpacesInPrefix = countTrailingSpaces(Prefix);
+ return TrailingSpacesInPrefix == 0
+ ? BaseSpaces
+ : std::max(0, BaseSpaces - TrailingSpacesInPrefix);
}
BreakableToken::Split
diff --git a/clang/lib/Format/BreakableToken.h b/clang/lib/Format/BreakableToken.h
index 45c00b35fd01e..29122eacebdf7 100644
--- a/clang/lib/Format/BreakableToken.h
+++ b/clang/lib/Format/BreakableToken.h
@@ -19,11 +19,36 @@
#include "Encoding.h"
#include "WhitespaceManager.h"
+#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSet.h"
namespace clang {
namespace format {
+class BreakableBlockComment;
+
+FormatStyle::CommentSpaceMode
+getBeforeClosingSpaceMode(const FormatStyle &Style, const FormatToken &Tok);
+
+FormatStyle::CommentSpaceMode getAfterOpeningSpaceMode(const FormatStyle &Style,
+ const FormatToken &Tok);
+
+llvm::StringRef getBlockCommentBody(const FormatToken &Tok);
+
+unsigned countLeadingHorizontalWhitespaceAfterOpening(const FormatToken &Tok);
+
+unsigned countTrailingHorizontalWhitespaceBeforeClosing(const FormatToken &Tok);
+
+void applyAfterOpeningBlockCommentSpacing(const FormatStyle &Style,
+ const FormatToken &Tok,
+ WhitespaceManager &Whitespaces,
+ bool InPPDirective);
+
+void applyBeforeClosingBlockCommentSpacing(const FormatStyle &Style,
+ const FormatToken &Tok,
+ WhitespaceManager &Whitespaces,
+ bool InPPDirective);
+
/// Checks if \p Token switches formatting, like /* clang-format off */.
/// \p Token must be a comment.
bool switchesFormatting(const FormatToken &Token);
@@ -429,11 +454,50 @@ class BreakableBlockComment : public BreakableComment {
bool mayReflow(unsigned LineIndex,
const llvm::Regex &CommentPragmasRegex) const override;
+ unsigned getLeadingWhitespaceAfterOpening() const {
+ return LeadingWhitespaceAfterOpening;
+ }
+ unsigned getTrailingWhitespaceBeforeClosing() const {
+ return TrailingWhitespaceBeforeClosing;
+ }
+
// Contains Javadoc annotations that require additional indent when continued
// on multiple lines.
static const llvm::StringSet<> ContentIndentingJavadocAnnotations;
private:
+ struct CommentLineInfo {
+ bool IsEmpty = false;
+ bool IsLastLine = false;
+ bool LastLineNeedsDecoration = false;
+ bool StartsImmediatelyAfterDecoration = false;
+ StringRef Decoration;
+ };
+
+ struct InterLineWhitespace {
+ unsigned Offset = 0;
+ unsigned Length = 0;
+ };
+
+ void
+ adaptSingleLineComment(WhitespaceManager &Whitespaces,
+ FormatStyle::CommentSpaceMode BeforeClosingMode) const;
+ void adaptFirstLineOfMultiLineComment(
+ WhitespaceManager &Whitespaces,
+ FormatStyle::CommentSpaceMode BeforeClosingMode) const;
+ void adaptIntermediateLineOfComment(
+ unsigned LineIndex, WhitespaceManager &Whitespaces,
+ FormatStyle::CommentSpaceMode BeforeClosingMode) const;
+ StringRef calculateLinePrefix(const CommentLineInfo &Info) const;
+ InterLineWhitespace calculateInterLineWhitespace(unsigned LineIndex) const;
+ bool allPreviousLinesEmpty(unsigned LineIndex) const;
+ bool isWellFormedBlockComment() const;
+ bool isSingleLineBlockComment() const;
+ bool isWhitespaceOnlySingleLineBlockComment() const;
+ int calculateTerminatorIndent(unsigned LineIndex, StringRef Prefix,
+ FormatStyle::CommentSpaceMode Mode,
+ int BaseSpaces) const;
+
// Rearranges the whitespace between Lines[LineIndex-1] and Lines[LineIndex].
//
// Updates Content[LineIndex-1] and Content[LineIndex] by stripping off
@@ -463,6 +527,13 @@ class BreakableBlockComment : public BreakableComment {
// Either "* " if all lines begin with a "*", or empty.
StringRef Decoration;
+ // Tracks whether we intentionally kept whitespace-only content on the first
+ // line to honour SpaceInComments.AfterOpeningComment = Always.
+ bool PreservedFirstLineSpaceAfterOpening = false;
+
+ unsigned LeadingWhitespaceAfterOpening = 0;
+ unsigned TrailingWhitespaceBeforeClosing = 0;
+
// If this block comment has decorations, this is the column of the start of
// the decorations.
unsigned DecorationColumn;
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 686e54128d372..813201708e528 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -44,6 +44,14 @@ struct ScalarEnumerationTraits<FormatStyle::BreakBeforeNoexceptSpecifierStyle> {
}
};
+template <> struct ScalarEnumerationTraits<FormatStyle::CommentSpaceMode> {
+ static void enumeration(IO &IO, FormatStyle::CommentSpaceMode &Value) {
+ IO.enumCase(Value, "Leave", FormatStyle::CommentSpaceMode::Leave);
+ IO.enumCase(Value, "Always", FormatStyle::CommentSpaceMode::Always);
+ IO.enumCase(Value, "Never", FormatStyle::CommentSpaceMode::Never);
+ }
+};
+
template <> struct MappingTraits<FormatStyle::AlignConsecutiveStyle> {
static void enumInput(IO &IO, FormatStyle::AlignConsecutiveStyle &Value) {
IO.enumCase(Value, "None", FormatStyle::AlignConsecutiveStyle({}));
@@ -94,6 +102,16 @@ template <> struct MappingTraits<FormatStyle::AlignConsecutiveStyle> {
}
};
+template <> struct MappingTraits<FormatStyle::SpaceInCommentsOptions> {
+ static void mapping(IO &IO, FormatStyle::SpaceInCommentsOptions &Value) {
+ IO.mapOptional("AfterOpeningComment", Value.AfterOpeningComment);
+ IO.mapOptional("BeforeClosingComment", Value.BeforeClosingComment);
+ IO.mapOptional("AfterOpeningParamComment", Value.AfterOpeningParamComment);
+ IO.mapOptional("BeforeClosingParamComment",
+ Value.BeforeClosingParamComment);
+ }
+};
+
template <>
struct MappingTraits<FormatStyle::ShortCaseStatementsAlignmentStyle> {
static void mapping(IO &IO,
@@ -1228,6 +1246,7 @@ template <> struct MappingTraits<FormatStyle> {
Style.SpaceBeforeRangeBasedForLoopColon);
IO.mapOptional("SpaceBeforeSquareBrackets",
Style.SpaceBeforeSquareBrackets);
+ IO.mapOptional("SpaceInComments", Style.SpaceInComments);
IO.mapOptional("SpaceInEmptyBraces", Style.SpaceInEmptyBraces);
IO.mapOptional("SpacesBeforeTrailingComments",
Style.SpacesBeforeTrailingComments);
diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h
index f015d27bed6af..89441521668ea 100644
--- a/clang/lib/Format/FormatToken.h
+++ b/clang/lib/Format/FormatToken.h
@@ -315,6 +315,19 @@ class AnnotatedLine;
/// A wrapper around a \c Token storing information about the
/// whitespace characters preceding it.
+
+// Describes the kind of a block comment.
+enum class CommentKind {
+ // A plain comment, i.e. /* ... */.
+ Plain,
+ // A comment that starts with /*! or /**.
+ DocString,
+ // A comment that looks like a parameter, e.g. /*param=*/.
+ Parameter,
+ // A comment that is a sentinel, e.g. /*FALLTHROUGH*/.
+ Sentinel,
+};
+
struct FormatToken {
FormatToken()
: HasUnescapedNewline(false), IsMultiline(false), IsFirst(false),
@@ -324,9 +337,10 @@ 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),
+ BlockCommentKind(static_cast<unsigned>(CommentKind::Plain)),
+ Decision(FD_Unformatted), PackingKind(PPK_Inconclusive),
+ TypeIsFinalized(false), Type(TT_Unknown) {}
/// The \c Token.
Token Tok;
@@ -402,10 +416,16 @@ 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;
+ /// Kind of block comment.
+ unsigned BlockCommentKind : 2;
+
public:
BraceBlockKind getBlockKind() const {
return static_cast<BraceBlockKind>(BlockKind);
@@ -415,6 +435,14 @@ struct FormatToken {
assert(getBlockKind() == BBK && "BraceBlockKind overflow!");
}
+ CommentKind getBlockCommentKind() const {
+ return static_cast<CommentKind>(BlockCommentKind);
+ }
+ void setBlockCommentKind(CommentKind Kind) {
+ BlockCommentKind = static_cast<unsigned>(Kind);
+ assert(getBlockCommentKind() == Kind && "CommentKind overflow!");
+ }
+
private:
/// Stores the formatting decision for the token once it was made.
unsigned Decision : 2;
@@ -505,6 +533,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..36ea88c18e916 100644
--- a/clang/lib/Format/FormatTokenLexer.cpp
+++ b/clang/lib/Format/FormatTokenLexer.cpp
@@ -18,11 +18,66 @@
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Format/Format.h"
+#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Regex.h"
namespace clang {
namespace format {
+namespace {
+
+CommentKind classifyBlockComment(StringRef Text) {
+ if (!Text.starts_with("/*") || !Text.ends_with("*/"))
+ return CommentKind::Plain;
+ if (Text.starts_with("/**") || Text.starts_with("/*!"))
+ return CommentKind::DocString;
+ const StringRef Content = Text.drop_front(2).drop_back(2).trim();
+ if (Content.empty())
+ return CommentKind::Plain;
+
+ // Allow '$' in identifiers. This is required for languages like JavaScript
+ // which clang-format supports, to correctly classify parameter/sentinel
+ // comments such as /*$scope=*/ or /*$FALLTHROUGH*/.
+ const auto IsIdentifierStart = [](char C) {
+ return llvm::isAlpha(C) || C == '_' || C == '$';
+ };
+ const auto IsIdentifierBody = [](char C) {
+ return llvm::isAlnum(C) || C == '_' || C == '$';
+ };
+ const auto IsIdentifierLike = [&](StringRef Value) {
+ if (Value.empty())
+ return false;
+ if (!IsIdentifierStart(Value.front()))
+ return false;
+ for (char C : Value.drop_front())
+ if (!IsIdentifierBody(C))
+ return false;
+ return true;
+ };
+ const auto IsUppercaseWord = [](StringRef Value) {
+ if (Value.empty())
+ return false;
+ for (char C : Value) {
+ if (llvm::isUpper(C) || llvm::isDigit(C) || C == '_' || C == '$')
+ continue;
+ return false;
+ }
+ return true;
+ };
+ const bool HasWhitespace =
+ Content.find_first_of(" \t\n\v\f\r") != StringRef::npos;
+
+ if (!HasWhitespace && IsUppercaseWord(Content))
+ return CommentKind::Sentinel;
+ if (Content.ends_with('=')) {
+ const StringRef MaybeIdentifier = Content.drop_back().rtrim();
+ if (IsIdentifierLike(MaybeIdentifier))
+ return CommentKind::Parameter;
+ }
+ return CommentKind::Plain;
+}
+} // namespace
+
FormatTokenLexer::FormatTokenLexer(
const SourceManager &SourceMgr, FileID ID, unsigned Column,
const FormatStyle &Style, encoding::Encoding Encoding,
@@ -1386,6 +1441,23 @@ FormatToken *FormatTokenLexer::getNextToken() {
StringRef UntrimmedText = FormatTok->TokenText;
FormatTok->TokenText = FormatTok->TokenText.rtrim(" \t\v\f");
TrailingWhitespace = UntrimmedText.size() - FormatTok->TokenText.size();
+ FormatTok->setBlockCommentKind(classifyBlockComment(FormatTok->TokenText));
+
+ if (FormatTok->TokenText.starts_with("/*") &&
+ FormatTok->TokenText.ends_with("*/") &&
+ FormatTok->TokenText.size() >= 4) {
+ const StringRef Content =
+ FormatTok->TokenText.drop_front(2).drop_back(2).rtrim("\r\n");
+ if (!Content.empty()) {
+ const unsigned char LastChar =
+ static_cast<unsigned char>(Content.back());
+ if (!isHorizontalWhitespace(LastChar)) {
+ 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..a3da496ef8b87 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===//
#include "TokenAnnotator.h"
+#include "BreakableToken.h"
#include "FormatToken.h"
#include "clang/Basic/TokenKinds.h"
#include "llvm/ADT/SmallPtrSet.h"
@@ -4821,7 +4822,16 @@ 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("=*/");
+ const StringRef Trimmed = Left.TokenText.rtrim(" \t");
+ bool EndsWithAssignmentComment = Trimmed.ends_with("=*/");
+ const FormatStyle::CommentSpaceMode BeforeClosingMode =
+ getBeforeClosingSpaceMode(Style, Left);
+ if (!EndsWithAssignmentComment && Trimmed.ends_with("= */") &&
+ (BeforeClosingMode == FormatStyle::CommentSpaceMode::Always ||
+ BeforeClosingMode == FormatStyle::CommentSpaceMode::Never)) {
+ EndsWithAssignmentComment = true;
+ }
+ return Style.isJavaScript() || !EndsWithAssignmentComment;
}
// Space between template and attribute.
diff --git a/clang/lib/Format/WhitespaceManager.cpp b/clang/lib/Format/WhitespaceManager.cpp
index 54f366fc02502..d008e099a99c0 100644
--- a/clang/lib/Format/WhitespaceManager.cpp
+++ b/clang/lib/Format/WhitespaceManager.cpp
@@ -12,6 +12,7 @@
//===----------------------------------------------------------------------===//
#include "WhitespaceManager.h"
+#include "BreakableToken.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include <algorithm>
@@ -61,6 +62,10 @@ void WhitespaceManager::replaceWhitespace(FormatToken &Tok, unsigned Newlines,
Spaces, StartOfTokenColumn, Newlines, "", "",
IsAligned, InPPDirective && !Tok.IsFirst,
/*IsInsideToken=*/false));
+ if (Style.ReflowComments == FormatStyle::RCS_Never) {
+ applyAfterOpeningBlockCommentSpacing(Style, Tok, *this, InPPDirective);
+ applyBeforeClosingBlockCommentSpacing(Style, Tok, *this, InPPDirective);
+ }
}
void WhitespaceManager::addUntouchableToken(const FormatToken &Tok,
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index 6111e86ff7076..660897c985fcd 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -699,6 +699,31 @@ TEST(ConfigParseTest, ParsesConfiguration) {
CHECK_PARSE("SpaceInEmptyBlock: true", SpaceInEmptyBraces,
FormatStyle::SIEB_Block);
+ CHECK_PARSE_NESTED_VALUE("AfterOpeningComment: Always", SpaceInComments,
+ AfterOpeningComment,
+ FormatStyle::CommentSpaceMode::Always);
+ CHECK_PARSE_NESTED_VALUE("AfterOpeningComment: Never", SpaceInComments,
+ AfterOpeningComment,
+ FormatStyle::CommentSpaceMode::Never);
+ CHECK_PARSE_NESTED_VALUE("BeforeClosingComment: Always", SpaceInComments,
+ BeforeClosingComment,
+ FormatStyle::CommentSpaceMode::Always);
+ CHECK_PARSE_NESTED_VALUE("BeforeClosingComment: Never", SpaceInComments,
+ BeforeClosingComment,
+ FormatStyle::CommentSpaceMode::Never);
+ CHECK_PARSE_NESTED_VALUE("AfterOpeningParamComment: Always", SpaceInComments,
+ AfterOpeningParamComment,
+ FormatStyle::CommentSpaceMode::Always);
+ CHECK_PARSE_NESTED_VALUE("AfterOpeningParamComment: Never", SpaceInComments,
+ AfterOpeningParamComment,
+ FormatStyle::CommentSpaceMode::Never);
+ CHECK_PARSE_NESTED_VALUE("BeforeClosingParamComment: Always", SpaceInComments,
+ BeforeClosingParamComment,
+ FormatStyle::CommentSpaceMode::Always);
+ CHECK_PARSE_NESTED_VALUE("BeforeClosingParamComment: Never", SpaceInComments,
+ BeforeClosingParamComment,
+ FormatStyle::CommentSpaceMode::Never);
+
// For backward compatibility:
Style.SpacesInParens = FormatStyle::SIPO_Never;
Style.SpacesInParensOptions = {};
diff --git a/clang/unittests/Format/FormatTestComments.cpp b/clang/unittests/Format/FormatTestComments.cpp
index 69026bce98705..8c41e057decc1 100644
--- a/clang/unittests/Format/FormatTestComments.cpp
+++ b/clang/unittests/Format/FormatTestComments.cpp
@@ -332,6 +332,397 @@ TEST_F(FormatTestComments, UnderstandsSingleLineComments) {
verifyNoCrash(StringRef("/*\\\0\n/", 6));
}
+TEST_F(FormatTestComments, InsertsSpaceAfterOpeningBlockComment) {
+ FormatStyle Style = getLLVMStyle();
+ Style.SpaceInComments.AfterOpeningComment =
+ FormatStyle::CommentSpaceMode::Always;
+
+ verifyFormat("foo(/* comment */);", "foo(/*comment */);", Style);
+ verifyFormat("/* comment */", "/*comment */", Style);
+ verifyFormat("/* comment before code */\n"
+ "int x;",
+ "/*comment before code */\n"
+ "int x;",
+ Style);
+ verifyFormat("/* \n"
+ "comment */",
+ "/*\n"
+ "comment */",
+ Style);
+ verifyFormat("/* \ncomment */\n"
+ "int x;",
+ "/*\ncomment */\n"
+ "int x;",
+ Style);
+ verifyFormat("/* \n"
+ "comment line\n"
+ "*/",
+ "/*\n"
+ "comment line\n"
+ "*/",
+ Style);
+ verifyFormat("/* \n"
+ " * comment star\n"
+ "*/",
+ "/*\n"
+ " * comment star\n"
+ "*/",
+ Style);
+ verifyFormat("/* comment line\n"
+ "next */",
+ "/*comment line\n"
+ "next */",
+ Style);
+ verifyFormat("/* */", "/* */", Style);
+ verifyFormat("/*\n*/", "/*\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, AfterOpeningCommentModes) {
+ FormatStyle Style = getLLVMStyle();
+ Style.SpaceInComments.AfterOpeningComment =
+ FormatStyle::CommentSpaceMode::Always;
+
+ verifyFormat("foo(/* comment */);", "foo(/*comment */);", Style);
+ verifyFormat("foo(/* comment */);", "foo(/* comment */);", Style);
+ verifyFormat("/* \n"
+ "comment */",
+ "/*\n"
+ "comment */",
+ Style);
+ verifyFormat("/* */", "/* */", Style);
+ 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);
+
+ Style = getLLVMStyle();
+ Style.SpaceInComments.AfterOpeningComment =
+ FormatStyle::CommentSpaceMode::Never;
+
+ verifyFormat("foo(/*comment */);", "foo(/* comment */);", Style);
+ verifyFormat("/*\n"
+ "comment */",
+ "/* \n"
+ "comment */",
+ Style);
+ verifyFormat("/*\n"
+ "comment */",
+ "/*\n"
+ "comment */",
+ Style);
+ verifyFormat("/**/", "/* */", Style);
+ 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, AfterOpeningParamCommentOverrides) {
+ FormatStyle Style = getLLVMStyle();
+ Style.SpaceInComments.AfterOpeningParamComment =
+ FormatStyle::CommentSpaceMode::Always;
+
+ verifyFormat("/*comment*/", "/*comment*/", Style);
+ verifyFormat("call(/* Arg=*/value);", "call(/*Arg=*/value);", Style);
+
+ Style.SpaceInComments.AfterOpeningParamComment =
+ FormatStyle::CommentSpaceMode::Never;
+
+ verifyFormat("/*comment*/", "/*comment*/", Style);
+ verifyFormat("call(/*Arg=*/value);", "call(/* Arg=*/value);", Style);
+}
+
+TEST_F(FormatTestComments, InsertsSpaceBeforeClosingBlockComment) {
+ FormatStyle Style = getLLVMStyle();
+ Style.SpaceInComments.BeforeClosingComment =
+ FormatStyle::CommentSpaceMode::Always;
+
+ verifyFormat("foo(/* comment */);", "foo(/* comment*/);", Style);
+ verifyFormat("/* comment */", Style);
+ verifyFormat("/* comment before code */\n"
+ "int x;",
+ "/* comment before code*/\n"
+ "int x;",
+ Style);
+ verifyFormat("/* comment\n"
+ " */",
+ "/* comment\n"
+ "*/",
+ Style);
+ verifyFormat("/* comment\n"
+ " */\n"
+ "int x;",
+ "/* comment\n"
+ "*/\n"
+ "int x;",
+ Style);
+ verifyFormat("/*\n"
+ "comment line\n"
+ " */",
+ "/*\n"
+ "comment line\n"
+ "*/",
+ Style);
+ verifyFormat("/*\n"
+ " * comment star\n"
+ " */",
+ "/*\n"
+ " * comment star\n"
+ "*/",
+ Style);
+ verifyFormat("/* comment line\n"
+ "next */",
+ "/* comment line\n"
+ "next*/",
+ Style);
+ verifyFormat("/* */", "/* */", Style);
+ verifyFormat("/*\n*/", "/*\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, BeforeClosingCommentModes) {
+ FormatStyle Style = getLLVMStyle();
+ Style.SpaceInComments.BeforeClosingComment =
+ FormatStyle::CommentSpaceMode::Always;
+
+ verifyFormat("foo(/* comment */);", "foo(/* comment*/);", Style);
+ verifyFormat("foo(/* comment */);", "foo(/* comment */);", Style);
+ verifyFormat("/* comment\n"
+ " */",
+ "/* comment\n"
+ "*/",
+ Style);
+ verifyFormat("/* */", "/* */", Style);
+ 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);
+
+ Style = getLLVMStyle();
+ Style.SpaceInComments.BeforeClosingComment =
+ FormatStyle::CommentSpaceMode::Never;
+
+ verifyFormat("foo(/* comment*/);", "foo(/* comment */);", Style);
+ verifyFormat("/* comment\n"
+ "*/",
+ "/* comment\n"
+ " */",
+ Style);
+ verifyFormat("/* comment\n"
+ "*/",
+ "/* comment\n"
+ "*/",
+ Style);
+ verifyFormat("/**/", "/* */", Style);
+ 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, CommentSpacesAlwaysAtBothEnds) {
+ FormatStyle Style = getLLVMStyle();
+ Style.SpaceInComments.AfterOpeningComment =
+ FormatStyle::CommentSpaceMode::Always;
+ Style.SpaceInComments.BeforeClosingComment =
+ FormatStyle::CommentSpaceMode::Always;
+
+ verifyFormat("foo(/* comment */);", "foo(/*comment*/);", Style);
+ verifyFormat("/* comment */", "/*comment*/", Style);
+ verifyFormat("/* comment before code */\n"
+ "int x;",
+ "/*comment before code*/\n"
+ "int x;",
+ Style);
+ verifyFormat("/* comment\n"
+ " */",
+ "/*comment\n"
+ "*/",
+ Style);
+ verifyFormat("/* comment\n"
+ " */\n"
+ "int x;",
+ "/*comment\n"
+ "*/\n"
+ "int x;",
+ Style);
+ verifyFormat("/* \n"
+ "comment line\n"
+ " */",
+ "/*\n"
+ "comment line\n"
+ "*/",
+ Style);
+ verifyFormat("/* \n"
+ " * comment star\n"
+ " */",
+ "/*\n"
+ " * comment star\n"
+ "*/",
+ Style);
+ verifyFormat("/* comment line\n"
+ "next */",
+ "/*comment line\n"
+ "next*/",
+ Style);
+ verifyFormat("/* */", "/* */", Style);
+ verifyFormat("/*\n*/", "/*\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, BeforeClosingParamCommentOverrides) {
+ FormatStyle Style = getLLVMStyle();
+ Style.SpaceInComments.BeforeClosingParamComment =
+ FormatStyle::CommentSpaceMode::Always;
+
+ verifyFormat("/*comment*/", "/*comment*/", Style);
+ verifyFormat("call(/*Arg= */value);", "call(/*Arg=*/value);", Style);
+
+ Style.SpaceInComments.BeforeClosingParamComment =
+ FormatStyle::CommentSpaceMode::Never;
+
+ verifyFormat("/*comment*/", "/*comment*/", Style);
+ verifyFormat("call(/*Arg=*/value);", "call(/*Arg= */value);", Style);
+}
+
+TEST_F(FormatTestComments, DocstringCommentsRespectLeaveAfterOpening) {
+ FormatStyle Style = getLLVMStyle();
+ Style.SpaceInComments.AfterOpeningComment =
+ FormatStyle::CommentSpaceMode::Always;
+ Style.SpaceInComments.BeforeClosingComment =
+ FormatStyle::CommentSpaceMode::Leave;
+
+ verifyFormat("/**doc*/", "/**doc*/", Style);
+ verifyFormat("int value; /**doc*/", "int value; /**doc*/", Style);
+
+ Style.SpaceInComments.AfterOpeningComment =
+ FormatStyle::CommentSpaceMode::Leave;
+ Style.SpaceInComments.BeforeClosingComment =
+ FormatStyle::CommentSpaceMode::Always;
+ verifyFormat("/**doc */", "/**doc*/", Style);
+}
+
+TEST_F(FormatTestComments, BlockCommentDoesNotForceBreakBeforeFollowingToken) {
+ FormatStyle Style = getLLVMStyle();
+ Style.SpaceInComments.AfterOpeningComment =
+ FormatStyle::CommentSpaceMode::Leave;
+ Style.SpaceInComments.BeforeClosingComment =
+ FormatStyle::CommentSpaceMode::Always;
+
+ 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);
+
+ Style = getLLVMStyle();
+ Style.SpaceInComments.AfterOpeningComment =
+ FormatStyle::CommentSpaceMode::Always;
+ Style.SpaceInComments.BeforeClosingComment =
+ FormatStyle::CommentSpaceMode::Leave;
+
+ 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"
diff --git a/clang/unittests/Format/TokenAnnotatorTest.cpp b/clang/unittests/Format/TokenAnnotatorTest.cpp
index c21b118847d99..3af8dc3cc6ec2 100644
--- a/clang/unittests/Format/TokenAnnotatorTest.cpp
+++ b/clang/unittests/Format/TokenAnnotatorTest.cpp
@@ -4196,6 +4196,38 @@ TEST_F(TokenAnnotatorTest, LineCommentTrailingBackslash) {
EXPECT_TOKEN(Tokens[1], tok::comment, TT_LineComment);
}
+TEST_F(TokenAnnotatorTest, ClassifiesBlockCommentKinds) {
+ static constexpr struct {
+ StringRef Code;
+ CommentKind Kind;
+ } Cases[] = {
+ {"int value; /* comment */\n", CommentKind::Plain},
+ {"int value; /** doc */\n", CommentKind::DocString},
+ {"call(/*Arg=*/value);", CommentKind::Parameter},
+ {"switch (x) {\n"
+ "case 0:\n"
+ " /*FALLTHROUGH*/\n"
+ "default:\n"
+ " break;\n"
+ "}\n",
+ CommentKind::Sentinel},
+ };
+
+ for (const auto &Test : Cases) {
+ const auto Tokens = annotate(Test.Code);
+ FormatToken *Comment = nullptr;
+ for (FormatToken *Tok : Tokens) {
+ if (Tok->is(tok::comment)) {
+ Comment = Tok;
+ break;
+ }
+ }
+ ASSERT_NE(Comment, nullptr) << "Missing comment token in: " << Test.Code;
+ EXPECT_EQ(Comment->getBlockCommentKind(), Test.Kind)
+ << "Comment text: " << Comment->TokenText;
+ }
+}
+
TEST_F(TokenAnnotatorTest, ArrowAfterSubscript) {
auto Tokens =
annotate("return (getStructType()->getElements())[eIdx]->getName();");
>From 4fb65bd52b9c665e68b362c48b723ff569fff785 Mon Sep 17 00:00:00 2001
From: mencotton <mencotton0410 at gmail.com>
Date: Wed, 29 Oct 2025 09:25:23 +0900
Subject: [PATCH 2/9] Fix the style of Format.h
---
clang/docs/ClangFormatStyleOptions.rst | 32 +++++++++-----------------
clang/include/clang/Format/Format.h | 32 ++++++++------------------
2 files changed, 21 insertions(+), 43 deletions(-)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index 45966c9e7b33a..8776a6299a77a 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -6620,39 +6620,29 @@ the configuration (without a prefix: ``Auto``).
(preserve existing spacing, the default), ``Always`` (insert a single
space), or ``Never`` (remove all spaces).
- The available controls are:
-
- ``AfterOpeningComment``
- Governs the space immediately after ``/*`` in regular block comments.
- ``BeforeClosingComment``
- Governs the space before ``*/`` in regular block comments.
- ``AfterOpeningParamComment``
- Governs the space after ``/*`` in parameter comments such as
- ``/*param=*/``.
- ``BeforeClosingParamComment``
- Governs the space before ``*/`` in parameter comments.
-
- .. code-block:: c++
-
- // BeforeClosingComment: Always
- auto Value = foo(/* comment */);
- // BeforeClosingParamComment: Never
- auto Number = foo(/*param=*/42);
-
Nested configuration flags:
Specifies spacing behavior for different block comment forms.
- * ``CommentSpaceMode AfterOpeningComment`` :versionbadge:`clang-format 21`
- Governs the space immediately after ``/*`` in regular block comments.
+ * ``CommentSpaceMode AfterOpeningComment`` Governs the space immediately after ``/*`` in regular block comments.
* ``CommentSpaceMode BeforeClosingComment`` Governs the space before ``*/`` in regular block comments.
+ .. code-block:: c++
+
+ // BeforeClosingComment: Always
+ auto Value = foo(/* comment */);
+
* ``CommentSpaceMode AfterOpeningParamComment`` Governs the space after ``/*`` in parameter comments such as
``/*param=*/``.
* ``CommentSpaceMode BeforeClosingParamComment`` Governs the space before ``*/`` in parameter comments.
+ .. code-block:: c++
+
+ // BeforeClosingParamComment: Never
+ auto Number = foo(/*param=*/42);
+
.. _SpaceInEmptyBlock:
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 906c490626c9d..3988ec0331140 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -2513,7 +2513,6 @@ struct FormatStyle {
/// Defines how clang-format should treat spaces around block comment
/// delimiters and specialized inline comments (such as parameter name
/// annotations). The default `Leave` mode preserves existing whitespace.
- /// \version 21
enum class CommentSpaceMode : int8_t {
/// Preserve existing whitespace, making no formatting changes.
Leave,
@@ -4912,16 +4911,25 @@ struct FormatStyle {
bool SpaceBeforeRangeBasedForLoopColon;
/// Specifies spacing behavior for different block comment forms.
- /// \version 21
struct SpaceInCommentsOptions {
/// Governs the space immediately after ``/*`` in regular block comments.
CommentSpaceMode AfterOpeningComment;
/// Governs the space before ``*/`` in regular block comments.
+ ///
+ /// .. code-block:: c++
+ ///
+ /// // BeforeClosingComment: Always
+ /// auto Value = foo(/* comment */);
CommentSpaceMode BeforeClosingComment;
/// Governs the space after ``/*`` in parameter comments such as
/// ``/*param=*/``.
CommentSpaceMode AfterOpeningParamComment;
/// Governs the space before ``*/`` in parameter comments.
+ ///
+ /// .. code-block:: c++
+ ///
+ /// // BeforeClosingParamComment: Never
+ /// auto Number = foo(/*param=*/42);
CommentSpaceMode BeforeClosingParamComment;
SpaceInCommentsOptions()
@@ -4942,26 +4950,6 @@ struct FormatStyle {
/// inline comments. Each field accepts a ``CommentSpaceMode``: ``Leave``
/// (preserve existing spacing, the default), ``Always`` (insert a single
/// space), or ``Never`` (remove all spaces).
- ///
- /// The available controls are:
- ///
- /// ``AfterOpeningComment``
- /// Governs the space immediately after ``/*`` in regular block comments.
- /// ``BeforeClosingComment``
- /// Governs the space before ``*/`` in regular block comments.
- /// ``AfterOpeningParamComment``
- /// Governs the space after ``/*`` in parameter comments such as
- /// ``/*param=*/``.
- /// ``BeforeClosingParamComment``
- /// Governs the space before ``*/`` in parameter comments.
- ///
- /// .. code-block:: c++
- ///
- /// // BeforeClosingComment: Always
- /// auto Value = foo(/* comment */);
- /// // BeforeClosingParamComment: Never
- /// auto Number = foo(/*param=*/42);
- ///
/// \version 21
SpaceInCommentsOptions SpaceInComments;
>From 1cf5c5ec79476c89822f0fb8b0b9c19ff688e06a Mon Sep 17 00:00:00 2001
From: mencotton <mencotton0410 at gmail.com>
Date: Wed, 29 Oct 2025 10:25:19 +0900
Subject: [PATCH 3/9] Fix: use `isWellFormedBlockCommentText` in
FormatTokenLexer.cpp
---
clang/lib/Format/BreakableToken.cpp | 10 +++++-----
clang/lib/Format/BreakableToken.h | 2 ++
clang/lib/Format/FormatTokenLexer.cpp | 5 ++---
3 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/clang/lib/Format/BreakableToken.cpp b/clang/lib/Format/BreakableToken.cpp
index 8f2a589c0c201..f6d71be01e5f4 100644
--- a/clang/lib/Format/BreakableToken.cpp
+++ b/clang/lib/Format/BreakableToken.cpp
@@ -34,11 +34,6 @@ static constexpr size_t BlockCommentCloserLength = 2;
namespace {
-bool isWellFormedBlockCommentText(StringRef Text) {
- return Text.size() >= BlockCommentOpenerLength + BlockCommentCloserLength &&
- Text.starts_with("/*") && Text.ends_with("*/");
-}
-
int countTrailingSpaces(StringRef Text) {
const size_t TrimmedSize = Text.rtrim(Blanks).size();
return static_cast<int>(Text.size() - TrimmedSize);
@@ -84,6 +79,11 @@ void replaceCommentWhitespace(const FormatToken &Tok, unsigned Offset,
} // namespace
+bool isWellFormedBlockCommentText(StringRef Text) {
+ return Text.size() >= BlockCommentOpenerLength + BlockCommentCloserLength &&
+ Text.starts_with("/*") && Text.ends_with("*/");
+}
+
FormatStyle::CommentSpaceMode getAfterOpeningSpaceMode(const FormatStyle &Style,
const FormatToken &Tok) {
return resolveCommentSpaceMode(Style, Tok,
diff --git a/clang/lib/Format/BreakableToken.h b/clang/lib/Format/BreakableToken.h
index 29122eacebdf7..573e061e2bde8 100644
--- a/clang/lib/Format/BreakableToken.h
+++ b/clang/lib/Format/BreakableToken.h
@@ -27,6 +27,8 @@ namespace format {
class BreakableBlockComment;
+bool isWellFormedBlockCommentText(llvm::StringRef Text);
+
FormatStyle::CommentSpaceMode
getBeforeClosingSpaceMode(const FormatStyle &Style, const FormatToken &Tok);
diff --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp
index 36ea88c18e916..a088ab84561a3 100644
--- a/clang/lib/Format/FormatTokenLexer.cpp
+++ b/clang/lib/Format/FormatTokenLexer.cpp
@@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===//
#include "FormatTokenLexer.h"
+#include "BreakableToken.h"
#include "FormatToken.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/SourceLocation.h"
@@ -1443,9 +1444,7 @@ FormatToken *FormatTokenLexer::getNextToken() {
TrailingWhitespace = UntrimmedText.size() - FormatTok->TokenText.size();
FormatTok->setBlockCommentKind(classifyBlockComment(FormatTok->TokenText));
- if (FormatTok->TokenText.starts_with("/*") &&
- FormatTok->TokenText.ends_with("*/") &&
- FormatTok->TokenText.size() >= 4) {
+ if (isWellFormedBlockCommentText(FormatTok->TokenText)) {
const StringRef Content =
FormatTok->TokenText.drop_front(2).drop_back(2).rtrim("\r\n");
if (!Content.empty()) {
>From 8b7bd3e714597b25a56a58c75c27ac2b218a14d9 Mon Sep 17 00:00:00 2001
From: mencotton <mencotton0410 at gmail.com>
Date: Wed, 29 Oct 2025 10:26:17 +0900
Subject: [PATCH 4/9] Fix: use `auto` instead of `unsigned char`
---
clang/lib/Format/FormatTokenLexer.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp
index a088ab84561a3..49cf38b860a61 100644
--- a/clang/lib/Format/FormatTokenLexer.cpp
+++ b/clang/lib/Format/FormatTokenLexer.cpp
@@ -1448,8 +1448,7 @@ FormatToken *FormatTokenLexer::getNextToken() {
const StringRef Content =
FormatTok->TokenText.drop_front(2).drop_back(2).rtrim("\r\n");
if (!Content.empty()) {
- const unsigned char LastChar =
- static_cast<unsigned char>(Content.back());
+ const auto LastChar = static_cast<unsigned char>(Content.back());
if (!isHorizontalWhitespace(LastChar)) {
FormatTok->NeedsSpaceBeforeClosingBlockComment = true;
FormatTok->SpaceBeforeClosingBlockCommentOffset =
>From 9aa4020c82dc16e8af74b97d3ff5ca059141cd06 Mon Sep 17 00:00:00 2001
From: mencotton <mencotton0410 at gmail.com>
Date: Wed, 29 Oct 2025 10:50:28 +0900
Subject: [PATCH 5/9] Fix the place to call `classifyBlockComment`
---
clang/lib/Format/FormatTokenLexer.cpp | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp
index 49cf38b860a61..add3eaf21e7d0 100644
--- a/clang/lib/Format/FormatTokenLexer.cpp
+++ b/clang/lib/Format/FormatTokenLexer.cpp
@@ -28,8 +28,6 @@ namespace format {
namespace {
CommentKind classifyBlockComment(StringRef Text) {
- if (!Text.starts_with("/*") || !Text.ends_with("*/"))
- return CommentKind::Plain;
if (Text.starts_with("/**") || Text.starts_with("/*!"))
return CommentKind::DocString;
const StringRef Content = Text.drop_front(2).drop_back(2).trim();
@@ -1442,9 +1440,10 @@ FormatToken *FormatTokenLexer::getNextToken() {
StringRef UntrimmedText = FormatTok->TokenText;
FormatTok->TokenText = FormatTok->TokenText.rtrim(" \t\v\f");
TrailingWhitespace = UntrimmedText.size() - FormatTok->TokenText.size();
- FormatTok->setBlockCommentKind(classifyBlockComment(FormatTok->TokenText));
if (isWellFormedBlockCommentText(FormatTok->TokenText)) {
+ FormatTok->setBlockCommentKind(
+ classifyBlockComment(FormatTok->TokenText));
const StringRef Content =
FormatTok->TokenText.drop_front(2).drop_back(2).rtrim("\r\n");
if (!Content.empty()) {
>From 2f9acc80f7fb96675109d624271a66c853f9c986 Mon Sep 17 00:00:00 2001
From: mencotton <mencotton0410 at gmail.com>
Date: Fri, 31 Oct 2025 02:09:51 +0900
Subject: [PATCH 6/9] Fix parameter comment detection in FormatTokenLexer
---
clang/lib/Format/FormatTokenLexer.cpp | 23 ++---------------------
1 file changed, 2 insertions(+), 21 deletions(-)
diff --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp
index add3eaf21e7d0..74bc1ad834e9d 100644
--- a/clang/lib/Format/FormatTokenLexer.cpp
+++ b/clang/lib/Format/FormatTokenLexer.cpp
@@ -37,22 +37,6 @@ CommentKind classifyBlockComment(StringRef Text) {
// Allow '$' in identifiers. This is required for languages like JavaScript
// which clang-format supports, to correctly classify parameter/sentinel
// comments such as /*$scope=*/ or /*$FALLTHROUGH*/.
- const auto IsIdentifierStart = [](char C) {
- return llvm::isAlpha(C) || C == '_' || C == '$';
- };
- const auto IsIdentifierBody = [](char C) {
- return llvm::isAlnum(C) || C == '_' || C == '$';
- };
- const auto IsIdentifierLike = [&](StringRef Value) {
- if (Value.empty())
- return false;
- if (!IsIdentifierStart(Value.front()))
- return false;
- for (char C : Value.drop_front())
- if (!IsIdentifierBody(C))
- return false;
- return true;
- };
const auto IsUppercaseWord = [](StringRef Value) {
if (Value.empty())
return false;
@@ -68,11 +52,8 @@ CommentKind classifyBlockComment(StringRef Text) {
if (!HasWhitespace && IsUppercaseWord(Content))
return CommentKind::Sentinel;
- if (Content.ends_with('=')) {
- const StringRef MaybeIdentifier = Content.drop_back().rtrim();
- if (IsIdentifierLike(MaybeIdentifier))
- return CommentKind::Parameter;
- }
+ if (Content.ends_with('='))
+ return CommentKind::Parameter;
return CommentKind::Plain;
}
} // namespace
>From 6a0d4d1d6000cfa6ba706859e8f20cd13daae9d1 Mon Sep 17 00:00:00 2001
From: mencotton <mencotton0410 at gmail.com>
Date: Fri, 31 Oct 2025 02:32:35 +0900
Subject: [PATCH 7/9] Fix: revert parameter comment spacing in TokenAnnotator
---
clang/lib/Format/TokenAnnotator.cpp | 16 +++-------------
1 file changed, 3 insertions(+), 13 deletions(-)
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index a3da496ef8b87..de717c149baa0 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -4820,19 +4820,9 @@ bool TokenAnnotator::spaceRequiredBetween(const AnnotatedLine &Line,
Right.MatchingParen->isNot(BK_Block))) {
return !Style.Cpp11BracedListStyle || Style.SpacesInParensOptions.Other;
}
- if (Left.is(TT_BlockComment)) {
- // No whitespace in x(/*foo=*/1), except for JavaScript.
- const StringRef Trimmed = Left.TokenText.rtrim(" \t");
- bool EndsWithAssignmentComment = Trimmed.ends_with("=*/");
- const FormatStyle::CommentSpaceMode BeforeClosingMode =
- getBeforeClosingSpaceMode(Style, Left);
- if (!EndsWithAssignmentComment && Trimmed.ends_with("= */") &&
- (BeforeClosingMode == FormatStyle::CommentSpaceMode::Always ||
- BeforeClosingMode == FormatStyle::CommentSpaceMode::Never)) {
- EndsWithAssignmentComment = true;
- }
- return Style.isJavaScript() || !EndsWithAssignmentComment;
- }
+ if (Left.is(TT_BlockComment))
+ return Style.isJavaScript() ||
+ Left.getBlockCommentKind() != CommentKind::Parameter;
// Space between template and attribute.
// e.g. template <typename T> [[nodiscard]] ...
>From 9ee8a07bc66d8dd6c92ea8613246abd0411c2b15 Mon Sep 17 00:00:00 2001
From: mencotton <mencotton0410 at gmail.com>
Date: Fri, 31 Oct 2025 02:36:14 +0900
Subject: [PATCH 8/9] FIx: apply clang-format
---
clang/lib/Format/TokenAnnotator.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index de717c149baa0..9865eb7371b47 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -4820,9 +4820,10 @@ bool TokenAnnotator::spaceRequiredBetween(const AnnotatedLine &Line,
Right.MatchingParen->isNot(BK_Block))) {
return !Style.Cpp11BracedListStyle || Style.SpacesInParensOptions.Other;
}
- if (Left.is(TT_BlockComment))
+ if (Left.is(TT_BlockComment)) {
return Style.isJavaScript() ||
Left.getBlockCommentKind() != CommentKind::Parameter;
+ }
// Space between template and attribute.
// e.g. template <typename T> [[nodiscard]] ...
>From 91e4e919dcc1af47657a7aeb5d625bb5a5f9c887 Mon Sep 17 00:00:00 2001
From: mencotton <mencotton0410 at gmail.com>
Date: Fri, 31 Oct 2025 02:44:44 +0900
Subject: [PATCH 9/9] Fix: honor `RCS_Never` in WhitespaceManager
---
clang/lib/Format/WhitespaceManager.cpp | 4 ----
1 file changed, 4 deletions(-)
diff --git a/clang/lib/Format/WhitespaceManager.cpp b/clang/lib/Format/WhitespaceManager.cpp
index d008e099a99c0..b7332f60d03c9 100644
--- a/clang/lib/Format/WhitespaceManager.cpp
+++ b/clang/lib/Format/WhitespaceManager.cpp
@@ -62,10 +62,6 @@ void WhitespaceManager::replaceWhitespace(FormatToken &Tok, unsigned Newlines,
Spaces, StartOfTokenColumn, Newlines, "", "",
IsAligned, InPPDirective && !Tok.IsFirst,
/*IsInsideToken=*/false));
- if (Style.ReflowComments == FormatStyle::RCS_Never) {
- applyAfterOpeningBlockCommentSpacing(Style, Tok, *this, InPPDirective);
- applyBeforeClosingBlockCommentSpacing(Style, Tok, *this, InPPDirective);
- }
}
void WhitespaceManager::addUntouchableToken(const FormatToken &Tok,
More information about the cfe-commits
mailing list