[clang] [clang-format] Add OneLineFormatOffRegex option (PR #137577)
Owen Pan via cfe-commits
cfe-commits at lists.llvm.org
Mon Apr 28 22:49:03 PDT 2025
https://github.com/owenca updated https://github.com/llvm/llvm-project/pull/137577
>From ce33e11aedf297e9cfb5e20efb4ce316886e6cb1 Mon Sep 17 00:00:00 2001
From: Owen Pan <owenpiano at gmail.com>
Date: Sun, 27 Apr 2025 21:18:03 -0700
Subject: [PATCH 1/2] [clang-format] Add OneLineFormatOffRegex option
Close #54334
---
clang/docs/ClangFormatStyleOptions.rst | 23 +++++++
clang/docs/ReleaseNotes.rst | 1 +
clang/include/clang/Format/Format.h | 22 +++++++
clang/lib/Format/Format.cpp | 1 +
clang/lib/Format/FormatTokenLexer.cpp | 34 ++++++++++
clang/unittests/Format/ConfigParseTest.cpp | 1 +
clang/unittests/Format/FormatTest.cpp | 76 ++++++++++++++++++++++
7 files changed, 158 insertions(+)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index 3f8a5f49313b2..f1343ee6ee516 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -5195,6 +5195,29 @@ the configuration (without a prefix: ``Auto``).
Add a space in front of an Objective-C protocol list, i.e. use
``Foo <Protocol>`` instead of ``Foo<Protocol>``.
+.. _OneLineFormatOffRegex:
+
+**OneLineFormatOffRegex** (``String``) :versionbadge:`clang-format 21` :ref:`¶ <OneLineFormatOffRegex>`
+ A regular expression that describes markers for turning formatting off for
+ one line. If it matches a line comment that is the first/only token of a
+ line, clang-format skips the next line. Otherwise, clang-format skips the
+ line that contains a matched token.
+
+ .. code-block:: c++
+
+ // OneLineFormatOffRegex: ^(// NOLINT|logger$)
+ // results in the output below:
+ int a;
+ int b ; // NOLINT
+ int c;
+ // NOLINTNEXTLINE
+ int d ;
+ int e;
+ s = "// NOLINT";
+ logger() ;
+ logger2();
+ my_logger();
+
.. _PPIndentWidth:
**PPIndentWidth** (``Integer``) :versionbadge:`clang-format 13` :ref:`¶ <PPIndentWidth>`
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 3724c8cbc70fe..b22b3f13659ce 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -692,6 +692,7 @@ clang-format
top of the file.
- Add ``EnumTrailingComma`` option for inserting/removing commas at the end of
``enum`` enumerator lists.
+- Add ``OneLineFormatOffRegex`` option for turning formatting off for one line.
libclang
--------
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index f6ceef08b46da..e5606a8a2a419 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -3654,6 +3654,27 @@ struct FormatStyle {
/// \version 3.7
bool ObjCSpaceBeforeProtocolList;
+ /// A regular expression that describes markers for turning formatting off for
+ /// one line. If it matches a line comment that is the first/only token of a
+ /// line, clang-format skips the next line. Otherwise, clang-format skips the
+ /// line that contains a matched token.
+ /// \code
+ /// // OneLineFormatOffRegex: ^(// NOLINT|logger$)
+ /// // results in the output below:
+ /// int a;
+ /// int b ; // NOLINT
+ /// int c;
+ /// // NOLINTNEXTLINE
+ /// int d ;
+ /// int e;
+ /// s = "// NOLINT";
+ /// logger() ;
+ /// logger2();
+ /// my_logger();
+ /// \endcode
+ /// \version 21
+ std::string OneLineFormatOffRegex;
+
/// Different ways to try to fit all constructor initializers on a line.
enum PackConstructorInitializersStyle : int8_t {
/// Always put each constructor initializer on its own line.
@@ -5399,6 +5420,7 @@ struct FormatStyle {
ObjCPropertyAttributeOrder == R.ObjCPropertyAttributeOrder &&
ObjCSpaceAfterProperty == R.ObjCSpaceAfterProperty &&
ObjCSpaceBeforeProtocolList == R.ObjCSpaceBeforeProtocolList &&
+ OneLineFormatOffRegex == R.OneLineFormatOffRegex &&
PackConstructorInitializers == R.PackConstructorInitializers &&
PenaltyBreakAssignment == R.PenaltyBreakAssignment &&
PenaltyBreakBeforeFirstCallParameter ==
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 5a1c3f556b331..2f4b64ef4f5fe 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1100,6 +1100,7 @@ template <> struct MappingTraits<FormatStyle> {
IO.mapOptional("ObjCSpaceAfterProperty", Style.ObjCSpaceAfterProperty);
IO.mapOptional("ObjCSpaceBeforeProtocolList",
Style.ObjCSpaceBeforeProtocolList);
+ IO.mapOptional("OneLineFormatOffRegex", Style.OneLineFormatOffRegex);
IO.mapOptional("PackConstructorInitializers",
Style.PackConstructorInitializers);
IO.mapOptional("PenaltyBreakAssignment", Style.PenaltyBreakAssignment);
diff --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp
index a4c94ac411fe0..58991f9681c97 100644
--- a/clang/lib/Format/FormatTokenLexer.cpp
+++ b/clang/lib/Format/FormatTokenLexer.cpp
@@ -83,8 +83,42 @@ FormatTokenLexer::FormatTokenLexer(
ArrayRef<FormatToken *> FormatTokenLexer::lex() {
assert(Tokens.empty());
assert(FirstInLineIndex == 0);
+ const llvm::Regex FormatOffRegex(Style.OneLineFormatOffRegex);
+ enum { FO_None, FO_CurrentLine, FO_NextLine } FormatOff = FO_None;
do {
Tokens.push_back(getNextToken());
+ auto &Tok = *Tokens.back();
+ const auto NewlinesBefore = Tok.NewlinesBefore;
+ switch (FormatOff) {
+ case FO_CurrentLine:
+ if (NewlinesBefore == 0)
+ Tok.Finalized = true;
+ else
+ FormatOff = FO_None;
+ break;
+ case FO_NextLine:
+ if (NewlinesBefore == 1) {
+ FormatOff = FO_CurrentLine;
+ Tok.Finalized = true;
+ } else {
+ FormatOff = FO_None;
+ }
+ break;
+ default:
+ if (!FormattingDisabled && FormatOffRegex.match(Tok.TokenText)) {
+ if (Tok.TokenText.starts_with("//") &&
+ (NewlinesBefore > 0 || &Tok == Tokens.front())) {
+ FormatOff = FO_NextLine;
+ } else {
+ FormatOff = FO_CurrentLine;
+ for (auto *Token : reverse(Tokens)) {
+ Token->Finalized = true;
+ if (Token->NewlinesBefore > 0)
+ break;
+ }
+ }
+ }
+ }
if (Style.isJavaScript()) {
tryParseJSRegexLiteral();
handleTemplateStrings();
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index 2b08b794792e9..f7ab5546c7193 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -295,6 +295,7 @@ TEST(ConfigParseTest, ParsesConfiguration) {
FormatStyle Style = {};
Style.Language = FormatStyle::LK_Cpp;
CHECK_PARSE("CommentPragmas: '// abc$'", CommentPragmas, "// abc$");
+ CHECK_PARSE("OneLineFormatOffRegex: // ab$", OneLineFormatOffRegex, "// ab$");
Style.QualifierAlignment = FormatStyle::QAS_Right;
CHECK_PARSE("QualifierAlignment: Leave", QualifierAlignment,
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 333d40d481025..3b07fc8e189c6 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -24954,6 +24954,82 @@ TEST_F(FormatTest, DisableRegions) {
"// clang-format on");
}
+TEST_F(FormatTest, OneLineFormatOffRegex) {
+ auto Style = getLLVMStyle();
+ Style.OneLineFormatOffRegex = "// format off$";
+
+ verifyFormat("// format off\n"
+ "int i ;\n"
+ "int j;",
+ " // format off\n"
+ "int i ;\n"
+ "int j ;",
+ Style);
+ verifyFormat("// format off?\n"
+ "int i;",
+ "// format off?\n"
+ "int i ;",
+ Style);
+ verifyFormat("f(\"// format off\");", "f(\"// format off\") ;", Style);
+
+ verifyFormat("int i;\n"
+ "// format off\n"
+ "int j ;\n"
+ "int k;",
+ "int i ;\n"
+ " // format off\n"
+ "int j ;\n"
+ "int k ;",
+ Style);
+
+ verifyFormat("int i;\n"
+ "int j ; // format off\n"
+ "int k;",
+ "int i ;\n"
+ "int j ; // format off\n"
+ "int k ;",
+ Style);
+
+ verifyFormat("// clang-format off\n"
+ "int i ;\n"
+ "int j ; // format off\n"
+ "int k ;\n"
+ "// clang-format on\n"
+ "f();",
+ "// clang-format off\n"
+ "int i ;\n"
+ "int j ; // format off\n"
+ "int k ;\n"
+ "// clang-format on\n"
+ "f() ;",
+ Style);
+
+ Style.OneLineFormatOffRegex = "^/\\* format off \\*/";
+ verifyFormat("int i;\n"
+ " /* format off */ int j ;\n"
+ "int k;",
+ "int i ;\n"
+ " /* format off */ int j ;\n"
+ "int k ;",
+ Style);
+ verifyFormat("f(\"/* format off */\");", "f(\"/* format off */\") ;", Style);
+
+ Style.ColumnLimit = 50;
+ Style.OneLineFormatOffRegex = "^LogErrorPrint$";
+ verifyFormat("myproject::LogErrorPrint(logger, \"Don't split me!\");\n"
+ "myproject::MyLogErrorPrinter(myLogger,\n"
+ " \"Split me!\");",
+ "myproject::LogErrorPrint(logger, \"Don't split me!\");\n"
+ "myproject::MyLogErrorPrinter(myLogger, \"Split me!\");",
+ Style);
+
+ Style.OneLineFormatOffRegex = "//(< clang-format off| NO_TRANSLATION)$";
+ verifyNoChange(
+ "int i ; //< clang-format off\n"
+ "msg = sprintf(\"Long string with placeholders.\"); // NO_TRANSLATION",
+ Style);
+}
+
TEST_F(FormatTest, DoNotCrashOnInvalidInput) {
format("? ) =");
verifyNoCrash("#define a\\\n /**/}");
>From 5cd0610b680e42a029f1f5afe4e24e1dcdc9a5b9 Mon Sep 17 00:00:00 2001
From: Owen Pan <owenpiano at gmail.com>
Date: Mon, 28 Apr 2025 22:45:59 -0700
Subject: [PATCH 2/2] Also allow block comments as markers for turning off
formatting for next line
---
clang/docs/ClangFormatStyleOptions.rst | 10 +--
clang/include/clang/Format/Format.h | 10 +--
clang/lib/Format/FormatTokenLexer.cpp | 13 ++--
clang/unittests/Format/FormatTest.cpp | 89 ++++++++++++++++----------
4 files changed, 73 insertions(+), 49 deletions(-)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index f1343ee6ee516..b47291599649d 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -5199,9 +5199,9 @@ the configuration (without a prefix: ``Auto``).
**OneLineFormatOffRegex** (``String``) :versionbadge:`clang-format 21` :ref:`¶ <OneLineFormatOffRegex>`
A regular expression that describes markers for turning formatting off for
- one line. If it matches a line comment that is the first/only token of a
- line, clang-format skips the next line. Otherwise, clang-format skips the
- line that contains a matched token.
+ one line. If it matches a comment that is the only token of a line,
+ clang-format skips the comment and the next line. Otherwise, clang-format
+ skips lines containing a matched token.
.. code-block:: c++
@@ -5210,11 +5210,11 @@ the configuration (without a prefix: ``Auto``).
int a;
int b ; // NOLINT
int c;
- // NOLINTNEXTLINE
+ // NOLINTNEXTLINE
int d ;
int e;
s = "// NOLINT";
- logger() ;
+ logger() ;
logger2();
my_logger();
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index e5606a8a2a419..7fe41d800ccb3 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -3655,20 +3655,20 @@ struct FormatStyle {
bool ObjCSpaceBeforeProtocolList;
/// A regular expression that describes markers for turning formatting off for
- /// one line. If it matches a line comment that is the first/only token of a
- /// line, clang-format skips the next line. Otherwise, clang-format skips the
- /// line that contains a matched token.
+ /// one line. If it matches a comment that is the only token of a line,
+ /// clang-format skips the comment and the next line. Otherwise, clang-format
+ /// skips lines containing a matched token.
/// \code
/// // OneLineFormatOffRegex: ^(// NOLINT|logger$)
/// // results in the output below:
/// int a;
/// int b ; // NOLINT
/// int c;
- /// // NOLINTNEXTLINE
+ /// // NOLINTNEXTLINE
/// int d ;
/// int e;
/// s = "// NOLINT";
- /// logger() ;
+ /// logger() ;
/// logger2();
/// my_logger();
/// \endcode
diff --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp
index 58991f9681c97..bff9db22ac657 100644
--- a/clang/lib/Format/FormatTokenLexer.cpp
+++ b/clang/lib/Format/FormatTokenLexer.cpp
@@ -97,25 +97,26 @@ ArrayRef<FormatToken *> FormatTokenLexer::lex() {
FormatOff = FO_None;
break;
case FO_NextLine:
- if (NewlinesBefore == 1) {
- FormatOff = FO_CurrentLine;
- Tok.Finalized = true;
- } else {
+ if (NewlinesBefore > 1) {
FormatOff = FO_None;
+ } else {
+ Tok.Finalized = true;
+ FormatOff = FO_CurrentLine;
}
break;
default:
if (!FormattingDisabled && FormatOffRegex.match(Tok.TokenText)) {
- if (Tok.TokenText.starts_with("//") &&
+ if (Tok.is(tok::comment) &&
(NewlinesBefore > 0 || &Tok == Tokens.front())) {
+ Tok.Finalized = true;
FormatOff = FO_NextLine;
} else {
- FormatOff = FO_CurrentLine;
for (auto *Token : reverse(Tokens)) {
Token->Finalized = true;
if (Token->NewlinesBefore > 0)
break;
}
+ FormatOff = FO_CurrentLine;
}
}
}
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 3b07fc8e189c6..c4fcc5506d152 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -24958,75 +24958,98 @@ TEST_F(FormatTest, OneLineFormatOffRegex) {
auto Style = getLLVMStyle();
Style.OneLineFormatOffRegex = "// format off$";
- verifyFormat("// format off\n"
- "int i ;\n"
+ verifyFormat(" // format off\n"
+ " int i ;\n"
"int j;",
" // format off\n"
- "int i ;\n"
- "int j ;",
+ " int i ;\n"
+ " int j ;",
Style);
verifyFormat("// format off?\n"
"int i;",
- "// format off?\n"
- "int i ;",
+ " // format off?\n"
+ " int i ;",
Style);
- verifyFormat("f(\"// format off\");", "f(\"// format off\") ;", Style);
+ verifyFormat("f(\"// format off\");", " f(\"// format off\") ;", Style);
verifyFormat("int i;\n"
- "// format off\n"
- "int j ;\n"
+ " // format off\n"
+ " int j ;\n"
"int k;",
- "int i ;\n"
+ " int i ;\n"
" // format off\n"
- "int j ;\n"
- "int k ;",
+ " int j ;\n"
+ " int k ;",
+ Style);
+
+ verifyFormat(" // format off\n"
+ "\n"
+ "int i;",
+ " // format off\n"
+ " \n"
+ " int i ;",
Style);
verifyFormat("int i;\n"
- "int j ; // format off\n"
+ " int j ; // format off\n"
"int k;",
- "int i ;\n"
- "int j ; // format off\n"
- "int k ;",
+ " int i ;\n"
+ " int j ; // format off\n"
+ " int k ;",
Style);
verifyFormat("// clang-format off\n"
- "int i ;\n"
- "int j ; // format off\n"
- "int k ;\n"
+ " int i ;\n"
+ " int j ; // format off\n"
+ " int k ;\n"
"// clang-format on\n"
"f();",
- "// clang-format off\n"
- "int i ;\n"
- "int j ; // format off\n"
- "int k ;\n"
- "// clang-format on\n"
- "f() ;",
+ " // clang-format off\n"
+ " int i ;\n"
+ " int j ; // format off\n"
+ " int k ;\n"
+ " // clang-format on\n"
+ " f() ;",
Style);
Style.OneLineFormatOffRegex = "^/\\* format off \\*/";
verifyFormat("int i;\n"
" /* format off */ int j ;\n"
"int k;",
- "int i ;\n"
+ " int i ;\n"
" /* format off */ int j ;\n"
- "int k ;",
+ " int k ;",
+ Style);
+ verifyFormat("f(\"/* format off */\");", " f(\"/* format off */\") ;", Style);
+
+ Style.AlignEscapedNewlines = FormatStyle::ENAS_DontAlign;
+ verifyFormat("#define A \\\n"
+ " do { \\\n"
+ " /* format off */\\\n"
+ " f() ; \\\n"
+ " g(); \\\n"
+ " } while (0)",
+ "# define A\\\n"
+ " do{ \\\n"
+ " /* format off */\\\n"
+ " f() ; \\\n"
+ " g() ;\\\n"
+ " } while (0 )",
Style);
- verifyFormat("f(\"/* format off */\");", "f(\"/* format off */\") ;", Style);
Style.ColumnLimit = 50;
Style.OneLineFormatOffRegex = "^LogErrorPrint$";
- verifyFormat("myproject::LogErrorPrint(logger, \"Don't split me!\");\n"
+ verifyFormat(" myproject::LogErrorPrint(logger, \"Don't split me!\");\n"
"myproject::MyLogErrorPrinter(myLogger,\n"
" \"Split me!\");",
- "myproject::LogErrorPrint(logger, \"Don't split me!\");\n"
- "myproject::MyLogErrorPrinter(myLogger, \"Split me!\");",
+ " myproject::LogErrorPrint(logger, \"Don't split me!\");\n"
+ " myproject::MyLogErrorPrinter(myLogger, \"Split me!\");",
Style);
Style.OneLineFormatOffRegex = "//(< clang-format off| NO_TRANSLATION)$";
verifyNoChange(
- "int i ; //< clang-format off\n"
- "msg = sprintf(\"Long string with placeholders.\"); // NO_TRANSLATION",
+ " int i ; //< clang-format off\n"
+ " msg = sprintf(\"Long string with placeholders.\"); // NO_TRANSLATION",
Style);
}
More information about the cfe-commits
mailing list