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 <[email protected]> Date: Tue, 7 Oct 2025 23:14:20 +0900 Subject: [PATCH 01/19] [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 <[email protected]> Date: Wed, 29 Oct 2025 09:25:23 +0900 Subject: [PATCH 02/19] 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 <[email protected]> Date: Wed, 29 Oct 2025 10:25:19 +0900 Subject: [PATCH 03/19] 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 <[email protected]> Date: Wed, 29 Oct 2025 10:26:17 +0900 Subject: [PATCH 04/19] 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 <[email protected]> Date: Wed, 29 Oct 2025 10:50:28 +0900 Subject: [PATCH 05/19] 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 <[email protected]> Date: Fri, 31 Oct 2025 02:09:51 +0900 Subject: [PATCH 06/19] 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 <[email protected]> Date: Fri, 31 Oct 2025 02:32:35 +0900 Subject: [PATCH 07/19] 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 <[email protected]> Date: Fri, 31 Oct 2025 02:36:14 +0900 Subject: [PATCH 08/19] 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 <[email protected]> Date: Fri, 31 Oct 2025 02:44:44 +0900 Subject: [PATCH 09/19] 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, >From 96ed4ee3cc97ccfca6aaf76264ec02e3e61458b9 Mon Sep 17 00:00:00 2001 From: mencotton <[email protected]> Date: Fri, 31 Oct 2025 02:53:27 +0900 Subject: [PATCH 10/19] Fix: remove unnecessary BreakableToken includes --- clang/lib/Format/TokenAnnotator.cpp | 1 - clang/lib/Format/WhitespaceManager.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp index 9865eb7371b47..132006bdf40bf 100644 --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -13,7 +13,6 @@ //===----------------------------------------------------------------------===// #include "TokenAnnotator.h" -#include "BreakableToken.h" #include "FormatToken.h" #include "clang/Basic/TokenKinds.h" #include "llvm/ADT/SmallPtrSet.h" diff --git a/clang/lib/Format/WhitespaceManager.cpp b/clang/lib/Format/WhitespaceManager.cpp index b7332f60d03c9..54f366fc02502 100644 --- a/clang/lib/Format/WhitespaceManager.cpp +++ b/clang/lib/Format/WhitespaceManager.cpp @@ -12,7 +12,6 @@ //===----------------------------------------------------------------------===// #include "WhitespaceManager.h" -#include "BreakableToken.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include <algorithm> >From ffaebac405bb32c60a5869bb647e16cf7600b58b Mon Sep 17 00:00:00 2001 From: mencotton <[email protected]> Date: Fri, 31 Oct 2025 16:46:12 +0900 Subject: [PATCH 11/19] Fix: split block comment test literals --- clang/unittests/Format/FormatTestComments.cpp | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/clang/unittests/Format/FormatTestComments.cpp b/clang/unittests/Format/FormatTestComments.cpp index 8c41e057decc1..45fbb0634c992 100644 --- a/clang/unittests/Format/FormatTestComments.cpp +++ b/clang/unittests/Format/FormatTestComments.cpp @@ -349,9 +349,11 @@ TEST_F(FormatTestComments, InsertsSpaceAfterOpeningBlockComment) { "/*\n" "comment */", Style); - verifyFormat("/* \ncomment */\n" + verifyFormat("/* \n" + "comment */\n" "int x;", - "/*\ncomment */\n" + "/*\n" + "comment */\n" "int x;", Style); verifyFormat("/* \n" @@ -374,8 +376,18 @@ TEST_F(FormatTestComments, InsertsSpaceAfterOpeningBlockComment) { "next */", Style); verifyFormat("/* */", "/* */", Style); - verifyFormat("/*\n*/", "/*\n*/", Style); - verifyFormat("/*\n\n*/", "/*\n \n*/", 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. */", @@ -509,8 +521,18 @@ TEST_F(FormatTestComments, InsertsSpaceBeforeClosingBlockComment) { "next*/", Style); verifyFormat("/* */", "/* */", Style); - verifyFormat("/*\n*/", "/*\n*/", Style); - verifyFormat("/*\n\n*/", "/*\n \n*/", 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. */", >From 4d168a045517b27c122b5c4b319bf3a7b59ae0e2 Mon Sep 17 00:00:00 2001 From: mencotton <[email protected]> Date: Fri, 31 Oct 2025 16:48:49 +0900 Subject: [PATCH 12/19] Fix: split block comment test literals --- clang/unittests/Format/FormatTestComments.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/clang/unittests/Format/FormatTestComments.cpp b/clang/unittests/Format/FormatTestComments.cpp index 45fbb0634c992..43cc9b2c70aac 100644 --- a/clang/unittests/Format/FormatTestComments.cpp +++ b/clang/unittests/Format/FormatTestComments.cpp @@ -653,8 +653,18 @@ TEST_F(FormatTestComments, CommentSpacesAlwaysAtBothEnds) { "next*/", Style); verifyFormat("/* */", "/* */", Style); - verifyFormat("/*\n*/", "/*\n*/", Style); - verifyFormat("/*\n\n*/", "/*\n \n*/", 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. */", >From 9c04ae487536aa4f53a0aa003c1d301eae9fcd87 Mon Sep 17 00:00:00 2001 From: mencotton <[email protected]> Date: Sun, 2 Nov 2025 11:11:40 +0900 Subject: [PATCH 13/19] Fix: remove Sentinel comment kind --- clang/lib/Format/FormatToken.h | 2 -- clang/lib/Format/FormatTokenLexer.cpp | 18 ------------------ clang/unittests/Format/TokenAnnotatorTest.cpp | 7 ------- 3 files changed, 27 deletions(-) diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h index 89441521668ea..ab88063a05bb3 100644 --- a/clang/lib/Format/FormatToken.h +++ b/clang/lib/Format/FormatToken.h @@ -324,8 +324,6 @@ enum class CommentKind { DocString, // A comment that looks like a parameter, e.g. /*param=*/. Parameter, - // A comment that is a sentinel, e.g. /*FALLTHROUGH*/. - Sentinel, }; struct FormatToken { diff --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp index 74bc1ad834e9d..2b4fec501bbbe 100644 --- a/clang/lib/Format/FormatTokenLexer.cpp +++ b/clang/lib/Format/FormatTokenLexer.cpp @@ -34,24 +34,6 @@ CommentKind classifyBlockComment(StringRef Text) { 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 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('=')) return CommentKind::Parameter; return CommentKind::Plain; diff --git a/clang/unittests/Format/TokenAnnotatorTest.cpp b/clang/unittests/Format/TokenAnnotatorTest.cpp index 3af8dc3cc6ec2..a914e7d0247ad 100644 --- a/clang/unittests/Format/TokenAnnotatorTest.cpp +++ b/clang/unittests/Format/TokenAnnotatorTest.cpp @@ -4204,13 +4204,6 @@ TEST_F(TokenAnnotatorTest, ClassifiesBlockCommentKinds) { {"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) { >From 657ed3831980c5a1b9818e31294b7a01e0d8e47a Mon Sep 17 00:00:00 2001 From: mencotton <[email protected]> Date: Sat, 8 Nov 2025 14:47:03 +0900 Subject: [PATCH 14/19] Fix: revert the comment --- clang/lib/Format/TokenAnnotator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp index 132006bdf40bf..c378821a8ece0 100644 --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -4820,6 +4820,7 @@ bool TokenAnnotator::spaceRequiredBetween(const AnnotatedLine &Line, return !Style.Cpp11BracedListStyle || Style.SpacesInParensOptions.Other; } if (Left.is(TT_BlockComment)) { + // No whitespace in x(/*foo=*/1), except for JavaScript. return Style.isJavaScript() || Left.getBlockCommentKind() != CommentKind::Parameter; } >From cf332e44c6a020770b6cd716a85b383a0c8e2f3c Mon Sep 17 00:00:00 2001 From: mencotton <[email protected]> Date: Sat, 8 Nov 2025 14:50:02 +0900 Subject: [PATCH 15/19] Fix: simplify `classifyBlockComment` --- clang/lib/Format/FormatTokenLexer.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp index 2b4fec501bbbe..b50ec291ebe7a 100644 --- a/clang/lib/Format/FormatTokenLexer.cpp +++ b/clang/lib/Format/FormatTokenLexer.cpp @@ -31,9 +31,6 @@ CommentKind classifyBlockComment(StringRef Text) { 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; - if (Content.ends_with('=')) return CommentKind::Parameter; return CommentKind::Plain; >From e92c51e9c2923c0da444da9eb4cba6cf55639826 Mon Sep 17 00:00:00 2001 From: mencotton <[email protected]> Date: Sat, 8 Nov 2025 15:04:45 +0900 Subject: [PATCH 16/19] Fix: refactor enum handling to switch for compile-time safety --- clang/lib/Format/BreakableToken.cpp | 63 +++++++++++++++-------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/clang/lib/Format/BreakableToken.cpp b/clang/lib/Format/BreakableToken.cpp index f6d71be01e5f4..902759e80f186 100644 --- a/clang/lib/Format/BreakableToken.cpp +++ b/clang/lib/Format/BreakableToken.cpp @@ -176,12 +176,8 @@ void applyAfterOpeningBlockCommentSpacing(const FormatStyle &Style, countLeadingHorizontalWhitespaceAfterOpening(Tok); switch (Mode) { - case CommentSpaceMode::Never: - if (LeadingSpaces > 0) { - replaceCommentWhitespace(Tok, OpeningOffset, LeadingSpaces, "", - /*Newlines=*/0, Whitespaces, InPPDirective); - } - return; + case CommentSpaceMode::Leave: + break; case CommentSpaceMode::Always: if (OnlyWhitespace && !Interior.empty()) { const unsigned ReplaceChars = Interior.size(); @@ -204,8 +200,12 @@ void applyAfterOpeningBlockCommentSpacing(const FormatStyle &Style, /*Newlines=*/0, Whitespaces, InPPDirective); } return; - case CommentSpaceMode::Leave: - break; + case CommentSpaceMode::Never: + if (LeadingSpaces > 0) { + replaceCommentWhitespace(Tok, OpeningOffset, LeadingSpaces, "", + /*Newlines=*/0, Whitespaces, InPPDirective); + } + return; } llvm_unreachable("Unhandled CommentSpaceMode"); } @@ -230,20 +230,20 @@ void applyBeforeClosingBlockCommentSpacing(const FormatStyle &Style, Text.size() - BlockCommentCloserLength - TrailingSpaces; switch (Mode) { - case CommentSpaceMode::Never: - if (TrailingSpaces > 0) { - replaceCommentWhitespace(Tok, ReplaceOffset, TrailingSpaces, "", - /*Newlines=*/0, Whitespaces, InPPDirective); - } - return; + case CommentSpaceMode::Leave: + break; case CommentSpaceMode::Always: if (TrailingSpaces != 1) { replaceCommentWhitespace(Tok, ReplaceOffset, TrailingSpaces, " ", /*Newlines=*/0, Whitespaces, InPPDirective); } return; - case CommentSpaceMode::Leave: - break; + case CommentSpaceMode::Never: + if (TrailingSpaces > 0) { + replaceCommentWhitespace(Tok, ReplaceOffset, TrailingSpaces, "", + /*Newlines=*/0, Whitespaces, InPPDirective); + } + return; } llvm_unreachable("Unhandled CommentSpaceMode"); } @@ -1184,24 +1184,25 @@ bool BreakableBlockComment::isWhitespaceOnlySingleLineBlockComment() const { int BreakableBlockComment::calculateTerminatorIndent( unsigned LineIndex, StringRef Prefix, FormatStyle::CommentSpaceMode Mode, int BaseSpaces) const { - if (Mode == FormatStyle::CommentSpaceMode::Leave) + switch (Mode) { + case FormatStyle::CommentSpaceMode::Leave: return BaseSpaces; - if (Mode == FormatStyle::CommentSpaceMode::Never) - return 0; - - assert(Mode == FormatStyle::CommentSpaceMode::Always && - "Unexpected CommentSpaceMode"); + case FormatStyle::CommentSpaceMode::Always: { + if (!Tok.NeedsSpaceBeforeClosingBlockComment) + return allPreviousLinesEmpty(LineIndex) ? 0 : BaseSpaces; - if (!Tok.NeedsSpaceBeforeClosingBlockComment) - return allPreviousLinesEmpty(LineIndex) ? 0 : BaseSpaces; + if (BaseSpaces <= 0) + return allPreviousLinesEmpty(LineIndex) ? 0 : 1; - if (BaseSpaces <= 0) - return allPreviousLinesEmpty(LineIndex) ? 0 : 1; - - const int TrailingSpacesInPrefix = countTrailingSpaces(Prefix); - return TrailingSpacesInPrefix == 0 - ? BaseSpaces - : std::max(0, BaseSpaces - TrailingSpacesInPrefix); + const int TrailingSpacesInPrefix = countTrailingSpaces(Prefix); + return TrailingSpacesInPrefix == 0 + ? BaseSpaces + : std::max(0, BaseSpaces - TrailingSpacesInPrefix); + } + case FormatStyle::CommentSpaceMode::Never: + return 0; + } + llvm_unreachable("Unhandled CommentSpaceMode"); } BreakableToken::Split >From 2e039821d0bc3c561c3aaf1adb454d0c88d3d1ef Mon Sep 17 00:00:00 2001 From: mencotton <[email protected]> Date: Sat, 8 Nov 2025 15:24:24 +0900 Subject: [PATCH 17/19] Fix: parameter comment spacing overrides --- clang/lib/Format/BreakableToken.cpp | 6 ++---- clang/unittests/Format/FormatTestComments.cpp | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/clang/lib/Format/BreakableToken.cpp b/clang/lib/Format/BreakableToken.cpp index 902759e80f186..3efdf8a9d54eb 100644 --- a/clang/lib/Format/BreakableToken.cpp +++ b/clang/lib/Format/BreakableToken.cpp @@ -44,10 +44,8 @@ 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 (Tok.getBlockCommentKind() == CommentKind::Parameter) + 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. diff --git a/clang/unittests/Format/FormatTestComments.cpp b/clang/unittests/Format/FormatTestComments.cpp index 43cc9b2c70aac..83f0282bff61e 100644 --- a/clang/unittests/Format/FormatTestComments.cpp +++ b/clang/unittests/Format/FormatTestComments.cpp @@ -477,6 +477,22 @@ TEST_F(FormatTestComments, AfterOpeningParamCommentOverrides) { verifyFormat("call(/*Arg=*/value);", "call(/* Arg=*/value);", Style); } +TEST_F(FormatTestComments, AfterOpeningCommentLeaveParamComment) { + FormatStyle Style = getLLVMStyle(); + Style.SpaceInComments.AfterOpeningComment = + FormatStyle::CommentSpaceMode::Always; + + verifyFormat("call(/*Arg=*/value);", "call(/*Arg=*/value);", Style); +} + +TEST_F(FormatTestComments, BeforeClosingCommentLeaveParamComment) { + FormatStyle Style = getLLVMStyle(); + Style.SpaceInComments.BeforeClosingComment = + FormatStyle::CommentSpaceMode::Never; + + verifyFormat("call(/*arg= */value);", "call(/*arg= */value);", Style); +} + TEST_F(FormatTestComments, InsertsSpaceBeforeClosingBlockComment) { FormatStyle Style = getLLVMStyle(); Style.SpaceInComments.BeforeClosingComment = >From 2310d3c7780dc5b03c567fb72198c99e53b2e243 Mon Sep 17 00:00:00 2001 From: mencotton <[email protected]> Date: Sat, 8 Nov 2025 19:05:21 +0900 Subject: [PATCH 18/19] Fix: avoid overlapping whitespace edits in block comments and refactor --- clang/lib/Format/BreakableToken.cpp | 84 +++++++++++++---------------- clang/lib/Format/BreakableToken.h | 6 +-- 2 files changed, 38 insertions(+), 52 deletions(-) diff --git a/clang/lib/Format/BreakableToken.cpp b/clang/lib/Format/BreakableToken.cpp index 3efdf8a9d54eb..92ffeb875ad51 100644 --- a/clang/lib/Format/BreakableToken.cpp +++ b/clang/lib/Format/BreakableToken.cpp @@ -133,26 +133,6 @@ static unsigned countTrailingHorizontalWhitespace(StringRef Body) { 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, @@ -178,17 +158,18 @@ void applyAfterOpeningBlockCommentSpacing(const FormatStyle &Style, break; 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); + if (Interior.contains('\n') || Interior.contains('\r')) { + if (LeadingSpaces > 0) { + // Trim only the horizontal padding before the first line break so + // multi-line whitespace-only comments can still be handled by the + // generic block comment machinery without overlapping edits. + replaceCommentWhitespace(Tok, OpeningOffset, LeadingSpaces, "", + /*Newlines=*/0, Whitespaces, InPPDirective); + } } else { // Normalizes purely whitespace single-line comments such as "/* */" // to contain exactly one space of interior padding. - replaceCommentWhitespace(Tok, OpeningOffset, ReplaceChars, " ", + replaceCommentWhitespace(Tok, OpeningOffset, Interior.size(), " ", /*Newlines=*/0, Whitespaces, InPPDirective); } return; @@ -220,10 +201,25 @@ void applyBeforeClosingBlockCommentSpacing(const FormatStyle &Style, if (Mode == CommentSpaceMode::Leave) return; + const StringRef Interior = getBlockCommentInterior(Tok); + const bool TerminatorOnSeparateLine = [&Interior]() { + const size_t LastNewline = Interior.find_last_of("\n\r"); + if (LastNewline == StringRef::npos) + return false; + size_t AfterNewline = LastNewline + 1; + while (AfterNewline < Interior.size() && + (Interior[AfterNewline] == '\n' || Interior[AfterNewline] == '\r')) { + ++AfterNewline; + } + return Interior.substr(AfterNewline).find_first_not_of(" \t\v\f\r") == + StringRef::npos; + }(); + if (TerminatorOnSeparateLine) + return; + const StringRef Text = Tok.TokenText; - const unsigned TrailingSpaces = - countTrailingHorizontalWhitespaceBeforeClosing(Tok); + const unsigned TrailingSpaces = countTrailingHorizontalWhitespace(Interior); const unsigned ReplaceOffset = Text.size() - BlockCommentCloserLength - TrailingSpaces; @@ -1033,7 +1029,7 @@ void BreakableBlockComment::adaptStartOfLine( } if (LineIndex == 0) { - adaptFirstLineOfMultiLineComment(Whitespaces, BeforeClosingMode); + adaptFirstLineOfMultiLineComment(Whitespaces); return; } @@ -1048,23 +1044,10 @@ void BreakableBlockComment::adaptSingleLineComment( } void BreakableBlockComment::adaptFirstLineOfMultiLineComment( - WhitespaceManager &Whitespaces, - FormatStyle::CommentSpaceMode BeforeClosingMode) const { + WhitespaceManager &Whitespaces) 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); - } - } + applyBeforeClosingBlockCommentSpacing(Style, Tok, Whitespaces, InPPDirective); if (DelimitersOnNewline) { // Since we're breaking at index 1 below, the break position and the break @@ -1137,9 +1120,16 @@ BreakableBlockComment::calculateInterLineWhitespace(unsigned LineIndex) const { const StringRef TokenText = tokenAt(LineIndex).TokenText; const StringRef Previous = Content[LineIndex - 1]; const StringRef Current = Content[LineIndex]; + const StringRef PreviousLine = Lines[LineIndex - 1]; + const bool PreviousLineIsOpeningWhitespace = + LineIndex == 1 && + PreviousLine.find_first_not_of(Blanks) == StringRef::npos && + getAfterOpeningSpaceMode(Style, Tok) != + FormatStyle::CommentSpaceMode::Leave; const auto TokenBegin = TokenText.begin(); - const auto PreviousEnd = Previous.end(); + const auto PreviousEnd = + PreviousLineIsOpeningWhitespace ? PreviousLine.end() : Previous.end(); const auto CurrentBegin = Current.begin(); assert(TokenBegin <= PreviousEnd && PreviousEnd <= TokenText.end() && diff --git a/clang/lib/Format/BreakableToken.h b/clang/lib/Format/BreakableToken.h index 573e061e2bde8..8b14f6bc9bc60 100644 --- a/clang/lib/Format/BreakableToken.h +++ b/clang/lib/Format/BreakableToken.h @@ -39,8 +39,6 @@ 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, @@ -484,9 +482,7 @@ class BreakableBlockComment : public BreakableComment { void adaptSingleLineComment(WhitespaceManager &Whitespaces, FormatStyle::CommentSpaceMode BeforeClosingMode) const; - void adaptFirstLineOfMultiLineComment( - WhitespaceManager &Whitespaces, - FormatStyle::CommentSpaceMode BeforeClosingMode) const; + void adaptFirstLineOfMultiLineComment(WhitespaceManager &Whitespaces) const; void adaptIntermediateLineOfComment( unsigned LineIndex, WhitespaceManager &Whitespaces, FormatStyle::CommentSpaceMode BeforeClosingMode) const; >From 7e9640cd9d4baacc78b03308324fc57401168d5d Mon Sep 17 00:00:00 2001 From: mencotton <[email protected]> Date: Sat, 8 Nov 2025 23:08:15 +0900 Subject: [PATCH 19/19] Fix: privatize BreakableToken block-comment helpers --- clang/lib/Format/BreakableToken.cpp | 4 ++++ clang/lib/Format/BreakableToken.h | 29 ----------------------------- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/clang/lib/Format/BreakableToken.cpp b/clang/lib/Format/BreakableToken.cpp index 92ffeb875ad51..582ddaed1b353 100644 --- a/clang/lib/Format/BreakableToken.cpp +++ b/clang/lib/Format/BreakableToken.cpp @@ -82,6 +82,8 @@ bool isWellFormedBlockCommentText(StringRef Text) { Text.starts_with("/*") && Text.ends_with("*/"); } +namespace { + FormatStyle::CommentSpaceMode getAfterOpeningSpaceMode(const FormatStyle &Style, const FormatToken &Tok) { return resolveCommentSpaceMode(Style, Tok, @@ -242,6 +244,8 @@ void applyBeforeClosingBlockCommentSpacing(const FormatStyle &Style, llvm_unreachable("Unhandled CommentSpaceMode"); } +} // namespace + static StringRef getLineCommentIndentPrefix(StringRef Comment, const FormatStyle &Style) { static constexpr StringRef KnownCStylePrefixes[] = {"///<", "//!<", "///", diff --git a/clang/lib/Format/BreakableToken.h b/clang/lib/Format/BreakableToken.h index 8b14f6bc9bc60..99afc92f89bc3 100644 --- a/clang/lib/Format/BreakableToken.h +++ b/clang/lib/Format/BreakableToken.h @@ -25,30 +25,8 @@ namespace clang { namespace format { -class BreakableBlockComment; - bool isWellFormedBlockCommentText(llvm::StringRef Text); -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); - -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); @@ -454,13 +432,6 @@ 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; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
