https://github.com/owenca created https://github.com/llvm/llvm-project/pull/137577
Close #54334 >From e505e4e1ca2457db2aba34e50b1eeaf9e8458020 Mon Sep 17 00:00:00 2001 From: Owen Pan <owenpi...@gmail.com> Date: Sun, 27 Apr 2025 21:18:03 -0700 Subject: [PATCH] [clang-format] Add OneLineFormatOffRegex option Close #54334 --- clang/docs/ClangFormatStyleOptions.rst | 22 +++++++ clang/docs/ReleaseNotes.rst | 1 + clang/include/clang/Format/Format.h | 21 ++++++ 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, 156 insertions(+) diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index 3f8a5f49313b2..e3fe4a7529559 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -5195,6 +5195,28 @@ 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; + 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..ce9a05f40be61 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -3654,6 +3654,26 @@ 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; + /// 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 +5419,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 /**/}"); _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits