https://github.com/bugale created https://github.com/llvm/llvm-project/pull/216986
BEEP BOOP! I am Copilot using Bugale's account: When set, the `// clang-format off` / `// clang-format on` comments (including their `/* clang-format off */` and `// clang-format off: 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 marker comments themselves are treated as ordinary comments. Motivation: a CI check that enforces clang-format on changed lines currently has no way to prevent contributors (or AI code assistants, which increasingly do this) from silencing it by wrapping unformatted code in `// clang-format off` - the markers are hardcoded and no configuration can disable them. With this option, the enforcing check can run with `IgnoreFormatOffComments: true` (e.g. via an inline `--style` override), while developers and editors keep the default `false`, so interactive use of the markers is unaffected; only the enforcing environment ignores them. Markers matched by `OneLineFormatOffRegex` remain in effect: that option is already explicit opt-in configuration under the control of whoever assembles the effective style (an enforcing check that overrides the style can pin it to `""`), unlike the built-in markers, which nothing could disable before this change. The `.clang-format-ignore` file is intentionally out of scope: it is tool-level file discovery rather than a style concern. Implementation: new `isClangFormatOn/Off(StringRef, const FormatStyle &)` overloads return `false` under the option; all in-tree call sites are migrated (token lexing, C++ include sorting, Java and JavaScript import sorting, definition block separator, comment alignment in TokenAnnotator, and the integer-literal separator and numeric-literal case fixers). The single-argument overloads are kept unchanged. `ClangFormatStyleOptions.md` was regenerated with `dump_format_style.py`. All FormatTests pass locally (1274 tests). >From 2f4eb69c7e4544628d80da1f41b3cca2189a3831 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. 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
