https://github.com/owenca created https://github.com/llvm/llvm-project/pull/113268
Closes #113170. >From 0d1c2446a225d73e014e711b0d74931bff9237bc Mon Sep 17 00:00:00 2001 From: Owen Pan <owenpi...@gmail.com> Date: Mon, 21 Oct 2024 23:27:35 -0700 Subject: [PATCH] [clang-format] Add KeepFormFeed option Closes #113170. --- clang/docs/ClangFormatStyleOptions.rst | 8 ++++ clang/docs/ReleaseNotes.rst | 1 + clang/include/clang/Format/Format.h | 10 +++- clang/lib/Format/Format.cpp | 2 + clang/lib/Format/FormatToken.h | 3 ++ clang/lib/Format/FormatTokenLexer.cpp | 8 ++++ clang/lib/Format/WhitespaceManager.cpp | 23 +++++---- clang/lib/Format/WhitespaceManager.h | 2 +- clang/unittests/Format/ConfigParseTest.cpp | 7 +-- clang/unittests/Format/FormatTest.cpp | 56 +++++++++++++++++++++- 10 files changed, 104 insertions(+), 16 deletions(-) diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index f36a5472b7e17d..9ef1fd5f36d1d3 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -4663,6 +4663,14 @@ the configuration (without a prefix: ``Auto``). **KeepEmptyLinesAtTheStartOfBlocks** (``Boolean``) :versionbadge:`clang-format 3.7` :ref:`¶ <KeepEmptyLinesAtTheStartOfBlocks>` This option is deprecated. See ``AtStartOfBlock`` of ``KeepEmptyLines``. +.. _KeepFormFeed: + +**KeepFormFeed** (``Boolean``) :versionbadge:`clang-format 20` :ref:`¶ <KeepFormFeed>` + Keep the form feed character if it's immediately preceded and followed by + a newline. Multiple form feeds and newlines within a whitespace range are + replaced with a single newline and form feed followed by the remaining + newlines. + .. _LambdaBodyIndentation: **LambdaBodyIndentation** (``LambdaBodyIndentationKind``) :versionbadge:`clang-format 13` :ref:`¶ <LambdaBodyIndentation>` diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index b7a6ace8bb895d..9649768afdac53 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -705,6 +705,7 @@ clang-format multi-line comments without touching their contents, renames ``false`` to ``Never``, and ``true`` to ``Always``. - Adds ``RemoveEmptyLinesInUnwrappedLines`` option. +- Adds ``KeepFormFeed`` option. libclang -------- diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index debba1c7822839..a5fd7750c20b93 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -3207,6 +3207,13 @@ struct FormatStyle { /// \version 3.7 // bool KeepEmptyLinesAtTheStartOfBlocks; + /// Keep the form feed character if it's immediately preceded and followed by + /// a newline. Multiple form feeds and newlines within a whitespace range are + /// replaced with a single newline and form feed followed by the remaining + /// newlines. + /// \version 20 + bool KeepFormFeed; + /// Indentation logic for lambda bodies. enum LambdaBodyIndentationKind : int8_t { /// Align lambda body relative to the lambda signature. This is the default. @@ -5222,7 +5229,8 @@ struct FormatStyle { JavaImportGroups == R.JavaImportGroups && JavaScriptQuotes == R.JavaScriptQuotes && JavaScriptWrapImports == R.JavaScriptWrapImports && - KeepEmptyLines == R.KeepEmptyLines && Language == R.Language && + KeepEmptyLines == R.KeepEmptyLines && + KeepFormFeed == R.KeepFormFeed && Language == R.Language && LambdaBodyIndentation == R.LambdaBodyIndentation && LineEnding == R.LineEnding && MacroBlockBegin == R.MacroBlockBegin && MacroBlockEnd == R.MacroBlockEnd && Macros == R.Macros && diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index c612960ff37ac8..6bbde527c1eed8 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -1052,6 +1052,7 @@ template <> struct MappingTraits<FormatStyle> { IO.mapOptional("JavaScriptQuotes", Style.JavaScriptQuotes); IO.mapOptional("JavaScriptWrapImports", Style.JavaScriptWrapImports); IO.mapOptional("KeepEmptyLines", Style.KeepEmptyLines); + IO.mapOptional("KeepFormFeed", Style.KeepFormFeed); IO.mapOptional("LambdaBodyIndentation", Style.LambdaBodyIndentation); IO.mapOptional("LineEnding", Style.LineEnding); IO.mapOptional("MacroBlockBegin", Style.MacroBlockBegin); @@ -1567,6 +1568,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) { /*AtStartOfBlock=*/true, /*AtStartOfFile=*/true, }; + LLVMStyle.KeepFormFeed = false; LLVMStyle.LambdaBodyIndentation = FormatStyle::LBI_Signature; LLVMStyle.Language = Language; LLVMStyle.LineEnding = FormatStyle::LE_DeriveLF; diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h index 7d342a7dcca01d..f6bb860a1fea31 100644 --- a/clang/lib/Format/FormatToken.h +++ b/clang/lib/Format/FormatToken.h @@ -586,6 +586,9 @@ struct FormatToken { /// Might be function declaration open/closing paren. bool MightBeFunctionDeclParen = false; + /// Has "\n\f\n" or "\n\f\r\n" before TokenText. + bool HasFormFeedBefore = false; + /// Number of optional braces to be inserted after this token: /// -1: a single left brace /// 0: no braces diff --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp index 2cdf6cd286b280..8679654d19805e 100644 --- a/clang/lib/Format/FormatTokenLexer.cpp +++ b/clang/lib/Format/FormatTokenLexer.cpp @@ -1186,6 +1186,14 @@ FormatToken *FormatTokenLexer::getNextToken() { Column = 0; break; case '\f': + if (!InEscape && !FormatTok->HasFormFeedBefore && + // The form feed is immediately preceded and followed by a newline. + i > 0 && Text[i - 1] == '\n' && + ((i + 1 < e && Text[i + 1] == '\n') || + (i + 2 < e && Text[i + 1] == '\r' && Text[i + 2] == '\n'))) { + FormatTok->HasFormFeedBefore = true; + } + [[fallthrough]]; case '\v': Column = 0; break; diff --git a/clang/lib/Format/WhitespaceManager.cpp b/clang/lib/Format/WhitespaceManager.cpp index b6b24672f6a39d..bf1953e8c2a09c 100644 --- a/clang/lib/Format/WhitespaceManager.cpp +++ b/clang/lib/Format/WhitespaceManager.cpp @@ -1674,7 +1674,7 @@ void WhitespaceManager::generateChanges() { C.PreviousEndOfTokenColumn, C.EscapedNewlineColumn); } else { - appendNewlineText(ReplacementText, C.NewlinesBefore); + appendNewlineText(ReplacementText, C); } // FIXME: This assert should hold if we computed the column correctly. // assert((int)C.StartOfTokenColumn >= C.Spaces); @@ -1706,15 +1706,18 @@ void WhitespaceManager::storeReplacement(SourceRange Range, StringRef Text) { } } -void WhitespaceManager::appendNewlineText(std::string &Text, - unsigned Newlines) { - if (UseCRLF) { - Text.reserve(Text.size() + 2 * Newlines); - for (unsigned i = 0; i < Newlines; ++i) - Text.append("\r\n"); - } else { - Text.append(Newlines, '\n'); - } +void WhitespaceManager::appendNewlineText(std::string &Text, const Change &C) { + if (C.NewlinesBefore <= 0) + return; + + StringRef Newline = UseCRLF ? "\r\n" : "\n"; + Text.append(Newline); + + if (Style.KeepFormFeed && C.Tok->HasFormFeedBefore) + Text.append("\f"); + + for (unsigned I = 1; I < C.NewlinesBefore; ++I) + Text.append(Newline); } void WhitespaceManager::appendEscapedNewlineText( diff --git a/clang/lib/Format/WhitespaceManager.h b/clang/lib/Format/WhitespaceManager.h index 7b91d8bf4db72b..5dd667fb4ba3bf 100644 --- a/clang/lib/Format/WhitespaceManager.h +++ b/clang/lib/Format/WhitespaceManager.h @@ -349,7 +349,7 @@ class WhitespaceManager { /// Stores \p Text as the replacement for the whitespace in \p Range. void storeReplacement(SourceRange Range, StringRef Text); - void appendNewlineText(std::string &Text, unsigned Newlines); + void appendNewlineText(std::string &Text, const Change &C); void appendEscapedNewlineText(std::string &Text, unsigned Newlines, unsigned PreviousEndOfTokenColumn, unsigned EscapedNewlineColumn); diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index 9e8529050ed83d..7fc7492271668b 100644 --- a/clang/unittests/Format/ConfigParseTest.cpp +++ b/clang/unittests/Format/ConfigParseTest.cpp @@ -165,24 +165,25 @@ TEST(ConfigParseTest, ParsesConfigurationBools) { CHECK_PARSE_BOOL(BreakBeforeTernaryOperators); CHECK_PARSE_BOOL(BreakStringLiterals); CHECK_PARSE_BOOL(CompactNamespaces); + CHECK_PARSE_BOOL(Cpp11BracedListStyle); CHECK_PARSE_BOOL(DerivePointerAlignment); CHECK_PARSE_BOOL_FIELD(DerivePointerAlignment, "DerivePointerBinding"); CHECK_PARSE_BOOL(DisableFormat); CHECK_PARSE_BOOL(IndentAccessModifiers); - CHECK_PARSE_BOOL(IndentCaseLabels); CHECK_PARSE_BOOL(IndentCaseBlocks); + CHECK_PARSE_BOOL(IndentCaseLabels); CHECK_PARSE_BOOL(IndentGotoLabels); - CHECK_PARSE_BOOL_FIELD(IndentRequiresClause, "IndentRequires"); CHECK_PARSE_BOOL(IndentRequiresClause); + CHECK_PARSE_BOOL_FIELD(IndentRequiresClause, "IndentRequires"); CHECK_PARSE_BOOL(IndentWrappedFunctionNames); CHECK_PARSE_BOOL(InsertBraces); CHECK_PARSE_BOOL(InsertNewlineAtEOF); CHECK_PARSE_BOOL_FIELD(KeepEmptyLines.AtEndOfFile, "KeepEmptyLinesAtEOF"); CHECK_PARSE_BOOL_FIELD(KeepEmptyLines.AtStartOfBlock, "KeepEmptyLinesAtTheStartOfBlocks"); + CHECK_PARSE_BOOL(KeepFormFeed); CHECK_PARSE_BOOL(ObjCSpaceAfterProperty); CHECK_PARSE_BOOL(ObjCSpaceBeforeProtocolList); - CHECK_PARSE_BOOL(Cpp11BracedListStyle); CHECK_PARSE_BOOL(RemoveBracesLLVM); CHECK_PARSE_BOOL(RemoveEmptyLinesInUnwrappedLines); CHECK_PARSE_BOOL(RemoveSemicolon); diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index 8f4c92148adae4..5159066d0a5b72 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -28135,7 +28135,7 @@ TEST_F(FormatTest, BreakBinaryOperations) { Style); } -TEST_F(FormatTest, RemovesEmptyLinesInUnwrappedLines) { +TEST_F(FormatTest, RemoveEmptyLinesInUnwrappedLines) { auto Style = getLLVMStyle(); Style.RemoveEmptyLinesInUnwrappedLines = true; @@ -28212,6 +28212,60 @@ TEST_F(FormatTest, RemovesEmptyLinesInUnwrappedLines) { Style); } +TEST_F(FormatTest, KeepFormFeed) { + auto Style = getLLVMStyle(); + Style.KeepFormFeed = true; + + constexpr StringRef NoFormFeed{"int i;\n" + "\n" + "void f();"}; + verifyFormat(NoFormFeed, + "int i;\n" + " \f\n" + "void f();", + Style); + verifyFormat(NoFormFeed, + "int i;\n" + "\n" + "\fvoid f();", + Style); + verifyFormat(NoFormFeed, + "\fint i;\n" + "\n" + "void f();", + Style); + verifyFormat(NoFormFeed, + "int i;\n" + "\n" + "void f();\f", + Style); + + constexpr StringRef FormFeed{"int i;\n" + "\f\n" + "void f();"}; + verifyNoChange(FormFeed, Style); + + Style.LineEnding = FormatStyle::LE_LF; + verifyFormat(FormFeed, + "int i;\r\n" + "\f\r\n" + "void f();", + Style); + + Style.MaxEmptyLinesToKeep = 3; + verifyFormat("int i;\n" + "\f\n" + "\n" + "\n" + "void f();", + "int i;\n" + "\n" + "\f\n" + "\n" + "void f();", + Style); +} + } // namespace } // namespace test } // namespace format _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits