[clang-format] Add the KeepEmptyLinesAtEOF option
authorOwen Pan <owenpiano@gmail.com>
Tue, 6 Jun 2023 19:58:21 +0000 (12:58 -0700)
committerOwen Pan <owenpiano@gmail.com>
Wed, 7 Jun 2023 21:17:58 +0000 (14:17 -0700)
Adds an option KeepEmptyLinesAtEOF to keep empty lines (up to
MaxEmptyLinesToKeep) before EOF. This remedies the probably unintentional
change in behavior introduced in 3d3ea84a4f8f, which started to always
remove empty lines before EOF.

Fixes #56054.
Fixes #63150.

Differential Revision: https://reviews.llvm.org/D152305

clang/docs/ClangFormatStyleOptions.rst
clang/docs/ReleaseNotes.rst
clang/include/clang/Format/Format.h
clang/lib/Format/Format.cpp
clang/lib/Format/UnwrappedLineFormatter.cpp
clang/unittests/Format/ConfigParseTest.cpp
clang/unittests/Format/FormatTest.cpp

index 8f23a4aa27a9227766429c31db1a52520efbe31d..fa552d65e208aff480a15d86e3d62c613e8f51ba 100644 (file)
@@ -3555,6 +3555,11 @@ the configuration (without a prefix: ``Auto``).
      false:
      import {VeryLongImportsAreAnnoying, VeryLongImportsAreAnnoying, VeryLongImportsAreAnnoying,} from "some/module.js"
 
+.. _KeepEmptyLinesAtEOF:
+
+**KeepEmptyLinesAtEOF** (``Boolean``) :versionbadge:`clang-format 17` :ref:`¶ <KeepEmptyLinesAtEOF>`
+  Keep empty lines (up to ``MaxEmptyLinesToKeep``) at end of file.
+
 .. _KeepEmptyLinesAtTheStartOfBlocks:
 
 **KeepEmptyLinesAtTheStartOfBlocks** (``Boolean``) :versionbadge:`clang-format 3.7` :ref:`¶ <KeepEmptyLinesAtTheStartOfBlocks>`
index e24b1cda53711024c77e0734db86cc21fb22d7f4..a30d9f8a38d7e5e4245a6183186078a6a9c9de88 100644 (file)
@@ -700,6 +700,7 @@ clang-format
 - Fix all known issues associated with ``LambdaBodyIndentation: OuterScope``.
 - Add ``BracedInitializerIndentWidth`` which can be used to configure
   the indentation level of the contents of braced init lists.
+- Add ``KeepEmptyLinesAtEOF`` to keep empty lines at end of file.
 
 libclang
 --------
index 6a9d435174cdd1d39528ff64402c45e94a6d073b..74b3e15918184a8f4db44a868f4e8672e41ebcf4 100644 (file)
@@ -2675,6 +2675,10 @@ struct FormatStyle {
   bool JavaScriptWrapImports;
   // clang-format on
 
+  /// Keep empty lines (up to ``MaxEmptyLinesToKeep``) at end of file.
+  /// \version 17
+  bool KeepEmptyLinesAtEOF;
+
   /// If true, the empty line at the start of blocks is kept.
   /// \code
   ///    true:                                  false:
@@ -4364,6 +4368,7 @@ struct FormatStyle {
            JavaImportGroups == R.JavaImportGroups &&
            JavaScriptQuotes == R.JavaScriptQuotes &&
            JavaScriptWrapImports == R.JavaScriptWrapImports &&
+           KeepEmptyLinesAtEOF == R.KeepEmptyLinesAtEOF &&
            KeepEmptyLinesAtTheStartOfBlocks ==
                R.KeepEmptyLinesAtTheStartOfBlocks &&
            Language == R.Language &&
index 6e2b6a662e7e19c5d4639c11fe888289b14be788..5fee5e6c261a936462de95d357eca23afd34e054 100644 (file)
@@ -942,6 +942,7 @@ template <> struct MappingTraits<FormatStyle> {
     IO.mapOptional("JavaScriptWrapImports", Style.JavaScriptWrapImports);
     IO.mapOptional("KeepEmptyLinesAtTheStartOfBlocks",
                    Style.KeepEmptyLinesAtTheStartOfBlocks);
+    IO.mapOptional("KeepEmptyLinesAtEOF", Style.KeepEmptyLinesAtEOF);
     IO.mapOptional("LambdaBodyIndentation", Style.LambdaBodyIndentation);
     IO.mapOptional("LineEnding", Style.LineEnding);
     IO.mapOptional("MacroBlockBegin", Style.MacroBlockBegin);
@@ -1410,6 +1411,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
       /*Hex=*/0,     /*HexMinDigits=*/0};
   LLVMStyle.JavaScriptQuotes = FormatStyle::JSQS_Leave;
   LLVMStyle.JavaScriptWrapImports = true;
+  LLVMStyle.KeepEmptyLinesAtEOF = false;
   LLVMStyle.KeepEmptyLinesAtTheStartOfBlocks = true;
   LLVMStyle.LambdaBodyIndentation = FormatStyle::LBI_Signature;
   LLVMStyle.LineEnding = FormatStyle::LE_DeriveLF;
index fc5d4150ed777f3fc1ec52dcfafb408e16b877d6..f229742b19d970d4a351e8cb077b4e8861bc048d 100644 (file)
@@ -1513,7 +1513,9 @@ void UnwrappedLineFormatter::formatFirstToken(
     unsigned NewlineIndent) {
   FormatToken &RootToken = *Line.First;
   if (RootToken.is(tok::eof)) {
-    unsigned Newlines = std::min(RootToken.NewlinesBefore, 1u);
+    unsigned Newlines =
+        std::min(RootToken.NewlinesBefore,
+                 Style.KeepEmptyLinesAtEOF ? Style.MaxEmptyLinesToKeep + 1 : 1);
     unsigned TokenIndent = Newlines ? NewlineIndent : 0;
     Whitespaces->replaceWhitespace(RootToken, Newlines, TokenIndent,
                                    TokenIndent);
index 169c93d1143eb296b0ea8c66d847dbdc97ee647a..6c720ec22054cfb9837f3a4d2cbf487d36b5fc2e 100644 (file)
@@ -167,6 +167,7 @@ TEST(ConfigParseTest, ParsesConfigurationBools) {
   CHECK_PARSE_BOOL(IndentWrappedFunctionNames);
   CHECK_PARSE_BOOL(InsertBraces);
   CHECK_PARSE_BOOL(InsertNewlineAtEOF);
+  CHECK_PARSE_BOOL(KeepEmptyLinesAtEOF);
   CHECK_PARSE_BOOL(KeepEmptyLinesAtTheStartOfBlocks);
   CHECK_PARSE_BOOL(ObjCSpaceAfterProperty);
   CHECK_PARSE_BOOL(ObjCSpaceBeforeProtocolList);
index f188ab6f581cf9f2524abf66e8990f6bd7030fad..adc1eda41a91a59bdf88d354c5bd41471057a1c3 100644 (file)
@@ -25744,6 +25744,15 @@ TEST_F(FormatTest, InsertNewlineAtEOF) {
   verifyFormat("int i;\n", "int i;", Style);
 }
 
+TEST_F(FormatTest, KeepEmptyLinesAtEOF) {
+  FormatStyle Style = getLLVMStyle();
+  Style.KeepEmptyLinesAtEOF = true;
+
+  const StringRef Code{"int i;\n\n"};
+  verifyFormat(Code, Code, Style);
+  verifyFormat(Code, "int i;\n\n\n", Style);
+}
+
 TEST_F(FormatTest, SpaceAfterUDL) {
   verifyFormat("auto c = (4s).count();");
   verifyFormat("auto x = 5s .count() == 5;");