[clang] [clang-format] Option to insert spaces before the closing `*/` (PR #162105)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Oct 27 20:18:19 PDT 2025
https://github.com/Men-cotton updated https://github.com/llvm/llvm-project/pull/162105
>From 1cf571b409ec379dc9c52b51f58a63db318df691 Mon Sep 17 00:00:00 2001
From: mencotton <mencotton0410 at gmail.com>
Date: Tue, 7 Oct 2025 23:14:20 +0900
Subject: [PATCH] [clang-format] Add SpaceInComments controls for block comment
delimiters
---
clang/docs/ClangFormatStyleOptions.rst | 42 ++
clang/include/clang/Format/Format.h | 68 +++
clang/lib/Format/BreakableToken.cpp | 443 ++++++++++++++++--
clang/lib/Format/BreakableToken.h | 57 +++
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 | 333 +++++++++++++
clang/unittests/Format/TokenAnnotatorTest.cpp | 32 ++
12 files changed, 1104 insertions(+), 43 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..bede3505ada24 100644
--- a/clang/lib/Format/BreakableToken.cpp
+++ b/clang/lib/Format/BreakableToken.cpp
@@ -19,6 +19,7 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Debug.h"
#include <algorithm>
+#include <iterator>
#define DEBUG_TYPE "format-token-breaker"
@@ -26,6 +27,214 @@ namespace clang {
namespace format {
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;
+ }
+ if (ForceDocstringLeave &&
+ Tok.getBlockCommentKind() == CommentKind::DocString) {
+ return FormatStyle::CommentSpaceMode::Leave;
+ }
+ return GeneralMode;
+}
+
+StringRef getBlockCommentInterior(const FormatToken &Tok) {
+ const StringRef Text = Tok.TokenText;
+ if (!isWellFormedBlockCommentText(Text))
+ return {};
+ return Text.drop_front(BlockCommentOpenerLength)
+ .drop_back(BlockCommentCloserLength);
+}
+
+} // 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) {
+ if (!isWellFormedBlockCommentText(Tok.TokenText))
+ return {};
+ return Tok.TokenText.drop_front(BlockCommentOpenerLength);
+}
+
+unsigned countLeadingHorizontalWhitespaceAfterOpening(const FormatToken &Tok) {
+ const StringRef Body = getSliceAfterOpening(Tok);
+ unsigned Count = 0;
+ while (Count < Body.size()) {
+ const char C = Body[Count];
+ if (C == '\n' || C == '\r')
+ break;
+ if (!isHorizontalWhitespace(static_cast<unsigned char>(C)))
+ break;
+ ++Count;
+ }
+ return Count;
+}
+
+FormatStyle::CommentSpaceMode
+getBeforeClosingSpaceMode(const FormatStyle &Style, const FormatToken &Tok) {
+ return resolveCommentSpaceMode(
+ Style, Tok, Style.SpaceInComments.BeforeClosingComment,
+ Style.SpaceInComments.BeforeClosingParamComment,
+ /*ForceDocstringLeave=*/false);
+}
+
+unsigned
+countTrailingHorizontalWhitespaceBeforeClosing(const FormatToken &Tok) {
+ const StringRef Body = getBlockCommentInterior(Tok);
+ size_t Pos = Body.size();
+ while (Pos > 0) {
+ const char C = Body[Pos - 1];
+ if (C == '\n' || C == '\r') {
+ --Pos;
+ continue;
+ }
+ break;
+ }
+
+ size_t Tail = Pos;
+ while (Tail > 0 &&
+ isHorizontalWhitespace(static_cast<unsigned char>(Body[Tail - 1]))) {
+ --Tail;
+ }
+ return Pos - Tail;
+}
+
+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 Body = getSliceAfterOpening(Tok);
+ if (Body.empty())
+ return;
+
+ const StringRef Interior = getBlockCommentInterior(Tok);
+
+ const bool OnlyWhitespace =
+ Interior.find_first_not_of(" \t\r\n") == StringRef::npos;
+ const unsigned OpeningOffset = Tok.TokenText.size() - Body.size();
+ const unsigned LeadingSpaces =
+ countLeadingHorizontalWhitespaceAfterOpening(Tok);
+
+ if (Mode == CommentSpaceMode::Always && OnlyWhitespace && !Interior.empty()) {
+ const unsigned ReplaceChars = Interior.size();
+ if (Interior.contains('\n')) {
+ unsigned NewlineCount = countLogicalNewlines(Interior);
+ if (NewlineCount == 0)
+ NewlineCount = 1;
+ Whitespaces.replaceWhitespaceInToken(
+ Tok, OpeningOffset, ReplaceChars, /*PreviousPostfix=*/"",
+ /*CurrentPrefix=*/"", InPPDirective, /*Newlines=*/NewlineCount,
+ /*Spaces=*/0);
+ } else {
+ Whitespaces.replaceWhitespaceInToken(
+ Tok, OpeningOffset, ReplaceChars, /*PreviousPostfix=*/"",
+ /*CurrentPrefix=*/" ", InPPDirective, /*Newlines=*/0,
+ /*Spaces=*/0);
+ }
+ return;
+ }
+
+ if (Mode == CommentSpaceMode::Never) {
+ if (LeadingSpaces == 0)
+ return;
+ Whitespaces.replaceWhitespaceInToken(
+ Tok, OpeningOffset, LeadingSpaces, /*PreviousPostfix=*/"",
+ /*CurrentPrefix=*/"", InPPDirective, /*Newlines=*/0, /*Spaces=*/0);
+ return;
+ }
+
+ assert(Mode == CommentSpaceMode::Always && "Unexpected CommentSpaceMode");
+ if (LeadingSpaces == 1)
+ return;
+ Whitespaces.replaceWhitespaceInToken(
+ Tok, OpeningOffset, LeadingSpaces, /*PreviousPostfix=*/"",
+ /*CurrentPrefix=*/" ", InPPDirective, /*Newlines=*/0, /*Spaces=*/0);
+}
+
+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;
+
+ if (Mode == CommentSpaceMode::Never) {
+ if (TrailingSpaces == 0)
+ return;
+ Whitespaces.replaceWhitespaceInToken(
+ Tok, ReplaceOffset, TrailingSpaces, /*PreviousPostfix=*/"",
+ /*CurrentPrefix=*/"", InPPDirective, /*Newlines=*/0, /*Spaces=*/0);
+ return;
+ }
+
+ assert(Mode == CommentSpaceMode::Always && "Unexpected CommentSpaceMode");
+ if (TrailingSpaces == 1)
+ return;
+ Whitespaces.replaceWhitespaceInToken(
+ Tok, ReplaceOffset, TrailingSpaces, /*PreviousPostfix=*/"",
+ /*CurrentPrefix=*/" ", InPPDirective, /*Newlines=*/0, /*Spaces=*/0);
+}
static StringRef getLineCommentIndentPrefix(StringRef Comment,
const FormatStyle &Style) {
@@ -475,7 +684,7 @@ BreakableBlockComment::BreakableBlockComment(
"block comment section must start with a block comment");
StringRef TokenText(Tok.TokenText);
- assert(TokenText.starts_with("/*") && TokenText.ends_with("*/"));
+ assert(isWellFormedBlockCommentText(Tok.TokenText));
TokenText.substr(2, TokenText.size() - 4)
.split(Lines, UseCRLF ? "\r\n" : "\n");
@@ -775,50 +984,206 @@ 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);
+
+ if (BeforeClosingMode == FormatStyle::CommentSpaceMode::Always &&
+ isWhitespaceOnlySingleLineBlockComment()) {
+ formatWhitespaceOnlySingleLineBlockComment(Whitespaces);
+ }
+}
+
+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()) {
+ Whitespaces.replaceWhitespaceInToken(
+ Tok, Tok.SpaceBeforeClosingBlockCommentOffset,
+ /*ReplaceChars=*/0, /*PreviousPostfix=*/"", /*CurrentPrefix=*/" ",
+ InPPDirective, /*Newlines=*/0, /*Spaces=*/0);
}
- } 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;
+
+ 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 TokenEnd = TokenText.end();
+ const auto PreviousEnd = Previous.end();
+ const auto CurrentBegin = Current.begin();
+
+ assert(TokenBegin <= PreviousEnd && PreviousEnd <= TokenEnd &&
+ "Previous line must be within the token text.");
+ assert(TokenBegin <= CurrentBegin && CurrentBegin <= TokenEnd &&
+ "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();
+}
+
+void BreakableBlockComment::formatWhitespaceOnlySingleLineBlockComment(
+ WhitespaceManager &Whitespaces) const {
+ const StringRef Body = getBlockCommentInterior(Tok);
+ const unsigned WhitespaceLength = Body.size();
Whitespaces.replaceWhitespaceInToken(
- tokenAt(LineIndex), WhitespaceOffsetInToken, WhitespaceLength, "", Prefix,
- InPPDirective, /*Newlines=*/1, ContentColumn[LineIndex] - Prefix.size());
+ Tok, /*Offset=*/BlockCommentOpenerLength, WhitespaceLength,
+ /*PreviousPostfix=*/"", /*CurrentPrefix=*/"", InPPDirective,
+ /*Newlines=*/1, /*Spaces=*/0);
+}
+
+int BreakableBlockComment::calculateTerminatorIndent(
+ unsigned LineIndex, StringRef Prefix, FormatStyle::CommentSpaceMode Mode,
+ int BaseSpaces) const {
+ switch (Mode) {
+ case FormatStyle::CommentSpaceMode::Leave:
+ return BaseSpaces;
+ case FormatStyle::CommentSpaceMode::Never:
+ return 0;
+ case FormatStyle::CommentSpaceMode::Always:
+ break;
+ }
+
+ assert(Mode == FormatStyle::CommentSpaceMode::Always);
+
+ if (!Tok.NeedsSpaceBeforeClosingBlockComment) {
+ if (allPreviousLinesEmpty(LineIndex))
+ return 0;
+ return BaseSpaces;
+ }
+
+ if (BaseSpaces <= 0)
+ return allPreviousLinesEmpty(LineIndex) ? 0 : 1;
+
+ const int TrailingSpacesInPrefix = countTrailingSpaces(Prefix);
+ if (TrailingSpacesInPrefix == 0)
+ return BaseSpaces;
+
+ return std::max(0, BaseSpaces - TrailingSpacesInPrefix);
}
BreakableToken::Split
diff --git a/clang/lib/Format/BreakableToken.h b/clang/lib/Format/BreakableToken.h
index 45c00b35fd01e..182a856306e5a 100644
--- a/clang/lib/Format/BreakableToken.h
+++ b/clang/lib/Format/BreakableToken.h
@@ -19,11 +19,34 @@
#include "Encoding.h"
#include "WhitespaceManager.h"
+#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSet.h"
namespace clang {
namespace format {
+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);
@@ -434,6 +457,40 @@ class BreakableBlockComment : public BreakableComment {
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;
+ void formatWhitespaceOnlySingleLineBlockComment(
+ WhitespaceManager &Whitespaces) 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
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..230b8d68ac649 100644
--- a/clang/unittests/Format/FormatTestComments.cpp
+++ b/clang/unittests/Format/FormatTestComments.cpp
@@ -332,6 +332,339 @@ TEST_F(FormatTestComments, UnderstandsSingleLineComments) {
verifyNoCrash(StringRef("/*\\\0\n/", 6));
}
+TEST_F(FormatTestComments, InsertsSpaceAfterOpeningBlockComment) {
+ FormatStyle Style = getLLVMStyle();
+ Style.SpaceInComments.AfterOpeningComment =
+ FormatStyle::CommentSpaceMode::Always;
+ Style.ReflowComments = FormatStyle::RCS_Never;
+
+ 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("/*\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;
+ Style.ReflowComments = FormatStyle::RCS_Never;
+
+ 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;
+ Style.ReflowComments = FormatStyle::RCS_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;
+ Style.ReflowComments = FormatStyle::RCS_Never;
+
+ verifyFormat("call(/* Arg=*/value);", "call(/*Arg=*/value);", Style);
+
+ Style.SpaceInComments.AfterOpeningParamComment =
+ FormatStyle::CommentSpaceMode::Never;
+ Style.ReflowComments = FormatStyle::RCS_Never;
+
+ 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("/*\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;
+ Style.ReflowComments = FormatStyle::RCS_Never;
+
+ 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;
+ Style.ReflowComments = FormatStyle::RCS_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, BeforeClosingParamCommentOverrides) {
+ FormatStyle Style = getLLVMStyle();
+ Style.SpaceInComments.BeforeClosingParamComment =
+ FormatStyle::CommentSpaceMode::Always;
+ Style.ReflowComments = FormatStyle::RCS_Never;
+
+ verifyFormat("call(/*Arg= */value);", "call(/*Arg=*/value);", Style);
+
+ Style.SpaceInComments.BeforeClosingParamComment =
+ FormatStyle::CommentSpaceMode::Never;
+ Style.ReflowComments = FormatStyle::RCS_Never;
+
+ 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;
+ Style.ReflowComments = FormatStyle::RCS_Never;
+
+ 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;
+ Style.ReflowComments = FormatStyle::RCS_Never;
+
+ 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;
+ Style.ReflowComments = FormatStyle::RCS_Never;
+
+ 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();");
More information about the cfe-commits
mailing list