[clang] [clang-format] Add KeepFormFeed option (PR #113268)
Owen Pan via cfe-commits
cfe-commits at lists.llvm.org
Mon Oct 21 23:29:14 PDT 2024
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 <owenpiano at 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
More information about the cfe-commits
mailing list