[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