https://github.com/bugale updated https://github.com/llvm/llvm-project/pull/216986
>From a0f89714d750df8fd2d7079af199e11d96c09511 Mon Sep 17 00:00:00 2001 From: Bugale <[email protected]> Date: Tue, 18 Aug 2026 13:56:18 +0300 Subject: [PATCH] [clang-format] Add IgnoreFormatOffComments option When set, the '// clang-format off' and '// clang-format on' comments (including their block-comment and ': reason' forms) lose their special meaning: the code between them is formatted like any other code, include sorting is not disabled by them, and the markers are treated as ordinary comments. This lets an environment that enforces a format check on changed lines make sure the check cannot be suppressed from inside a file, while developers and editors keep the default and can still use the markers interactively. Markers matched by OneLineFormatOffRegex remain in effect, since that option is explicit configuration under the control of whoever assembles the effective style, unlike the built-in markers, which nothing could disable before this change. Fixes #216988 Assisted-by: GitHub Copilot (Claude Fable 5) Co-authored-by: Copilot App <[email protected]> --- clang/docs/ClangFormatStyleOptions.md | 14 +++++ clang/docs/ReleaseNotes.md | 4 ++ clang/include/clang/Format/Format.h | 17 ++++++ clang/lib/Format/DefinitionBlockSeparator.cpp | 4 +- clang/lib/Format/Format.cpp | 18 +++++-- clang/lib/Format/FormatTokenLexer.cpp | 4 +- .../Format/IntegerLiteralSeparatorFixer.cpp | 4 +- clang/lib/Format/NumericLiteralCaseFixer.cpp | 4 +- clang/lib/Format/SortJavaScriptImports.cpp | 7 +-- clang/lib/Format/TokenAnnotator.cpp | 2 +- clang/unittests/Format/ConfigParseTest.cpp | 1 + clang/unittests/Format/FormatTest.cpp | 53 +++++++++++++++++++ clang/unittests/Format/SortIncludesTest.cpp | 21 ++++++++ 13 files changed, 137 insertions(+), 16 deletions(-) diff --git a/clang/docs/ClangFormatStyleOptions.md b/clang/docs/ClangFormatStyleOptions.md index 8230e6f5139e7..4c6b353544421 100644 --- a/clang/docs/ClangFormatStyleOptions.md +++ b/clang/docs/ClangFormatStyleOptions.md @@ -4581,6 +4581,20 @@ the configuration (without a prefix: `Auto`). For example: [KJ_IF_MAYBE](https://github.com/capnproto/capnproto/blob/master/kjdoc/tour.md#maybes) +(ignoreformatoffcomments)= + +**IgnoreFormatOffComments** (`Boolean`) {versionbadge}`clang-format 23` {ref}`¶ <IgnoreFormatOffComments>` + +: If `true`, the `// clang-format off` and `// clang-format on` comments + have no effect: the code between them is formatted like any other, and + include sorting is not disabled by them. This lets an environment that + enforces a format check make sure the check cannot be suppressed from + inside a file. + + :::{note} + Markers matched by `OneLineFormatOffRegex` are still in effect. + ::: + (includeblocks)= **IncludeBlocks** (`IncludeBlocksStyle`) {versionbadge}`clang-format 6` {ref}`¶ <IncludeBlocks>` diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index f5f9958543e34..e9f201b1ba8e5 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -600,6 +600,10 @@ features cannot lower the translation-unit ABI level; `thread_local`, `extern`, `mutable`, `signed`, `unsigned`, `long`, `short`, and `explicit` declaration specifiers. +- Add `IgnoreFormatOffComments` option to strip `// clang-format off` and + `// clang-format on` comments of their special meaning, so that formatting + cannot be suppressed from inside a file. + ### libclang - visit identifier initializers in lambda capture as VarDecl instead of VariableRef. Warning: this changes behaviour. diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index 49e4666ae5e99..3399ae09b614f 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -3161,6 +3161,17 @@ struct FormatStyle { /// \version 13 std::vector<std::string> IfMacros; + /// If `true`, the `// clang-format off` and `// clang-format on` comments + /// have no effect: the code between them is formatted like any other, and + /// include sorting is not disabled by them. This lets an environment that + /// enforces a format check make sure the check cannot be suppressed from + /// inside a file. + /// \note + /// Markers matched by `OneLineFormatOffRegex` are still in effect. + /// \endnote + /// \version 23 + bool IgnoreFormatOffComments; + /// Specify whether access modifiers should have their own indentation level. /// /// When `false`, access modifiers are indented (or outdented) relative to @@ -6191,6 +6202,7 @@ struct FormatStyle { R.ExperimentalAutoDetectBinPacking && FixNamespaceComments == R.FixNamespaceComments && ForEachMacros == R.ForEachMacros && + IgnoreFormatOffComments == R.IgnoreFormatOffComments && IncludeStyle.IncludeBlocks == R.IncludeStyle.IncludeBlocks && IncludeStyle.IncludeCategories == R.IncludeStyle.IncludeCategories && IncludeStyle.IncludeIsMainRegex == @@ -6625,6 +6637,11 @@ inline StringRef getLanguageName(FormatStyle::LanguageKind Language) { bool isClangFormatOn(StringRef Comment); bool isClangFormatOff(StringRef Comment); +// Like the overloads above, but return false when the style ignores the +// clang-format on/off comments. +bool isClangFormatOn(StringRef Comment, const FormatStyle &Style); +bool isClangFormatOff(StringRef Comment, const FormatStyle &Style); + } // end namespace format } // end namespace clang diff --git a/clang/lib/Format/DefinitionBlockSeparator.cpp b/clang/lib/Format/DefinitionBlockSeparator.cpp index 6b52b1fc0deff..5edf25bda9c3d 100644 --- a/clang/lib/Format/DefinitionBlockSeparator.cpp +++ b/clang/lib/Format/DefinitionBlockSeparator.cpp @@ -90,7 +90,7 @@ void DefinitionBlockSeparator::separateBlocks( // Lines should not be added in the disabled region. if (TargetToken->is(tok::comment) && - isClangFormatOn(TargetToken->TokenText)) { + isClangFormatOn(TargetToken->TokenText, Style)) { return; } // Do not handle EOF newlines. @@ -155,7 +155,7 @@ void DefinitionBlockSeparator::separateBlocks( OperateIndex + 1 < Lines.size() ? Lines[OperateIndex + 1] : nullptr; if (const auto *Tok = OperateLine->First; - Tok->is(tok::comment) && !isClangFormatOn(Tok->TokenText)) { + Tok->is(tok::comment) && !isClangFormatOn(Tok->TokenText, Style)) { const bool IsEndComment = Tok->NewlinesBefore == 1 && NextLine && NextLine->First->NewlinesBefore > 1; if (!IsEndComment) diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index 2b6e65efbf026..5ef62c3ee741b 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -1385,6 +1385,7 @@ template <> struct MappingTraits<FormatStyle> { IO.mapOptional("FixNamespaceComments", Style.FixNamespaceComments); IO.mapOptional("ForEachMacros", Style.ForEachMacros); IO.mapOptional("IfMacros", Style.IfMacros); + IO.mapOptional("IgnoreFormatOffComments", Style.IgnoreFormatOffComments); IO.mapOptional("IncludeBlocks", Style.IncludeStyle.IncludeBlocks); IO.mapOptional("IncludeCategories", Style.IncludeStyle.IncludeCategories); IO.mapOptional("IncludeIsMainRegex", Style.IncludeStyle.IncludeIsMainRegex); @@ -1951,6 +1952,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) { LLVMStyle.ForEachMacros.push_back("Q_FOREACH"); LLVMStyle.ForEachMacros.push_back("BOOST_FOREACH"); LLVMStyle.IfMacros.push_back("KJ_IF_MAYBE"); + LLVMStyle.IgnoreFormatOffComments = false; LLVMStyle.IncludeStyle.IncludeBlocks = tooling::IncludeStyle::IBS_Preserve; LLVMStyle.IncludeStyle.IncludeCategories = { {"^\"(llvm|llvm-c|clang|clang-c)/", 2, 0, false}, @@ -3831,9 +3833,9 @@ tooling::Replacements sortCppIncludes(const FormatStyle &Style, StringRef Code, bool IsBlockComment = false; - if (isClangFormatOff(Trimmed)) { + if (isClangFormatOff(Trimmed, Style)) { FormattingOff = true; - } else if (isClangFormatOn(Trimmed)) { + } else if (isClangFormatOn(Trimmed, Style)) { FormattingOff = false; } else if (Trimmed.starts_with("/*")) { IsBlockComment = true; @@ -4023,9 +4025,9 @@ tooling::Replacements sortJavaImports(const FormatStyle &Style, StringRef Code, StringRef Trimmed = Line.trim(); if (Trimmed.empty() || PackageRegex.match(Trimmed)) { // Skip empty line and package statement. - } else if (isClangFormatOff(Trimmed)) { + } else if (isClangFormatOff(Trimmed, Style)) { FormattingOff = true; - } else if (isClangFormatOn(Trimmed)) { + } else if (isClangFormatOn(Trimmed, Style)) { FormattingOff = false; } else if (Trimmed.starts_with("//")) { // Associating comments within the imports with the nearest import below. @@ -4917,5 +4919,13 @@ bool isClangFormatOff(StringRef Comment) { return isClangFormatOnOff(Comment, /*On=*/false); } +bool isClangFormatOn(StringRef Comment, const FormatStyle &Style) { + return !Style.IgnoreFormatOffComments && isClangFormatOn(Comment); +} + +bool isClangFormatOff(StringRef Comment, const FormatStyle &Style) { + return !Style.IgnoreFormatOffComments && isClangFormatOff(Comment); +} + } // namespace format } // namespace clang diff --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp index 2a8bef21ad414..8d0dde82105f0 100644 --- a/clang/lib/Format/FormatTokenLexer.cpp +++ b/clang/lib/Format/FormatTokenLexer.cpp @@ -1633,12 +1633,12 @@ void FormatTokenLexer::readRawToken(FormatToken &Tok) { if ((Style.isJavaScript() || Style.isProto()) && Tok.is(tok::char_constant)) Tok.Tok.setKind(tok::string_literal); - if (Tok.is(tok::comment) && isClangFormatOn(Tok.TokenText)) + if (Tok.is(tok::comment) && isClangFormatOn(Tok.TokenText, Style)) FormattingDisabled = false; Tok.Finalized = FormattingDisabled; - if (Tok.is(tok::comment) && isClangFormatOff(Tok.TokenText)) + if (Tok.is(tok::comment) && isClangFormatOff(Tok.TokenText, Style)) FormattingDisabled = true; } diff --git a/clang/lib/Format/IntegerLiteralSeparatorFixer.cpp b/clang/lib/Format/IntegerLiteralSeparatorFixer.cpp index eea9fcc56af63..478ed65040eff 100644 --- a/clang/lib/Format/IntegerLiteralSeparatorFixer.cpp +++ b/clang/lib/Format/IntegerLiteralSeparatorFixer.cpp @@ -108,9 +108,9 @@ IntegerLiteralSeparatorFixer::process(const Environment &Env, auto Location = Tok.getLocation(); auto Text = StringRef(SourceMgr.getCharacterData(Location), Length); if (Tok.is(tok::comment)) { - if (isClangFormatOff(Text)) + if (isClangFormatOff(Text, Style)) Skip = true; - else if (isClangFormatOn(Text)) + else if (isClangFormatOn(Text, Style)) Skip = false; continue; } diff --git a/clang/lib/Format/NumericLiteralCaseFixer.cpp b/clang/lib/Format/NumericLiteralCaseFixer.cpp index b58b3c7ee0189..2f97190d5f88c 100644 --- a/clang/lib/Format/NumericLiteralCaseFixer.cpp +++ b/clang/lib/Format/NumericLiteralCaseFixer.cpp @@ -150,9 +150,9 @@ NumericLiteralCaseFixer::process(const Environment &Env, auto Location = Tok.getLocation(); auto Text = StringRef(SourceMgr.getCharacterData(Location), Length); if (Tok.is(tok::comment)) { - if (isClangFormatOff(Text)) + if (isClangFormatOff(Text, Style)) Skip = true; - else if (isClangFormatOn(Text)) + else if (isClangFormatOn(Text, Style)) Skip = false; continue; } diff --git a/clang/lib/Format/SortJavaScriptImports.cpp b/clang/lib/Format/SortJavaScriptImports.cpp index a403a4fed664c..bc54f207eab32 100644 --- a/clang/lib/Format/SortJavaScriptImports.cpp +++ b/clang/lib/Format/SortJavaScriptImports.cpp @@ -192,7 +192,8 @@ class JavaScriptImportSorter : public TokenAnalyzer { // Separate references from the main code body of the file. if (FirstNonImportLine && FirstNonImportLine->First->NewlinesBefore < 2 && !(FirstNonImportLine->First->is(tok::comment) && - isClangFormatOn(FirstNonImportLine->First->TokenText.trim()))) { + isClangFormatOn(FirstNonImportLine->First->TokenText.trim(), + Style))) { ReferencesText += "\n"; } @@ -372,9 +373,9 @@ class JavaScriptImportSorter : public TokenAnalyzer { // This is tracked in FormattingOff here and on JsModuleReference. while (Current && Current->is(tok::comment)) { StringRef CommentText = Current->TokenText.trim(); - if (isClangFormatOff(CommentText)) { + if (isClangFormatOff(CommentText, Style)) { FormattingOff = true; - } else if (isClangFormatOn(CommentText)) { + } else if (isClangFormatOn(CommentText, Style)) { FormattingOff = false; // Special case: consider a trailing "clang-format on" line to be part // of the module reference, so that it gets moved around together with diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp index b6c33279b0aca..830c4112f0fc7 100644 --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -3671,7 +3671,7 @@ void TokenAnnotator::setCommentLineLevels( // it, that's probably intentional and we should keep it. if (const auto Column = Line->First->OriginalColumn; NextNonCommentLine && NextNonCommentLine->First->NewlinesBefore < 2 && - Line->isComment() && !isClangFormatOff(Line->First->TokenText) && + Line->isComment() && !isClangFormatOff(Line->First->TokenText, Style) && NextNonCommentLine->First->OriginalColumn == Column) { const bool PPDirectiveOrImportStmt = NextNonCommentLine->Type == LT_PreprocessorDirective || diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index 9350ba7eb3de4..89e6c0de5aae5 100644 --- a/clang/unittests/Format/ConfigParseTest.cpp +++ b/clang/unittests/Format/ConfigParseTest.cpp @@ -189,6 +189,7 @@ TEST(ConfigParseTest, ParsesConfigurationBools) { CHECK_PARSE_BOOL(DerivePointerAlignment); CHECK_PARSE_BOOL_FIELD(DerivePointerAlignment, "DerivePointerBinding"); CHECK_PARSE_BOOL(DisableFormat); + CHECK_PARSE_BOOL(IgnoreFormatOffComments); CHECK_PARSE_BOOL(IndentAccessModifiers); CHECK_PARSE_BOOL(IndentCaseBlocks); CHECK_PARSE_BOOL(IndentCaseLabels); diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index 6f604167f785c..7e142542cc19a 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -22422,6 +22422,59 @@ TEST_F(FormatTest, DisableRegions) { "// clang-format on"); } +TEST_F(FormatTest, IgnoreFormatOffComments) { + auto Style = getLLVMStyle(); + Style.IgnoreFormatOffComments = true; + + verifyFormat("int i;\n" + "// clang-format off\n" + "int j;\n" + "// clang-format on\n" + "int k;", + " int i;\n" + " // clang-format off\n" + " int j;\n" + " // clang-format on\n" + " int k;", + Style); + + verifyFormat("int i;\n" + "/* clang-format off */\n" + "int j;\n" + "/* clang-format on */\n" + "int k;", + " int i;\n" + " /* clang-format off */\n" + " int j;\n" + " /* clang-format on */\n" + " int k;", + Style); + + verifyFormat("int *i;\n" + "// clang-format off: reason\n" + "int *j;\n" + "// clang-format on: reason\n" + "int *k;", + "int* i;\n" + "// clang-format off: reason\n" + "int* j;\n" + "// clang-format on: reason\n" + "int* k;", + Style); + + // Markers matched by OneLineFormatOffRegex are still in effect. + Style.OneLineFormatOffRegex = "^// NOLINT$"; + verifyFormat("int i;\n" + "// NOLINT\n" + "int j ;\n" + "int k;", + "int i ;\n" + "// NOLINT\n" + "int j ;\n" + "int k ;", + Style); +} + TEST_F(FormatTest, OneLineFormatOffRegex) { auto Style = getLLVMStyle(); Style.OneLineFormatOffRegex = "// format off$"; diff --git a/clang/unittests/Format/SortIncludesTest.cpp b/clang/unittests/Format/SortIncludesTest.cpp index a6e9e18496f8d..17f9c2910418c 100644 --- a/clang/unittests/Format/SortIncludesTest.cpp +++ b/clang/unittests/Format/SortIncludesTest.cpp @@ -283,6 +283,27 @@ TEST_F(SortIncludesTest, SupportClangFormatOffCStyle) { "input.h", 2)); } +TEST_F(SortIncludesTest, IgnoreFormatOffComments) { + FmtStyle.IgnoreFormatOffComments = true; + verifyFormat("#include <a>\n" + "#include <b>\n" + "#include <c>\n" + "// clang-format off\n" + "#include <a>\n" + "#include <b>\n" + "#include <c>\n" + "// clang-format on", + sort("#include <b>\n" + "#include <a>\n" + "#include <c>\n" + "// clang-format off\n" + "#include <b>\n" + "#include <a>\n" + "#include <c>\n" + "// clang-format on", + "input.h", 2)); +} + TEST_F(SortIncludesTest, IncludeSortingCanBeDisabled) { FmtStyle.SortIncludes = {}; verifyFormat("#include \"a.h\"\n" _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
