[clang] 6f6f88f - [clang-format] Style to separate definition blocks

via cfe-commits cfe-commits at lists.llvm.org
Mon Jan 3 12:50:02 PST 2022


Author: ksyx
Date: 2022-01-03T15:47:39-05:00
New Revision: 6f6f88ffdae1e12e5f950ef418827a77a55c09c7

URL: https://github.com/llvm/llvm-project/commit/6f6f88ffdae1e12e5f950ef418827a77a55c09c7
DIFF: https://github.com/llvm/llvm-project/commit/6f6f88ffdae1e12e5f950ef418827a77a55c09c7.diff

LOG: [clang-format] Style to separate definition blocks

This commit resolves GitHub issue #45895 (Bugzilla #46550), to
add or remove empty line between definition blocks including
namespaces, classes, structs, enums and functions.

Reviewed By: MyDeveloperDay, curdeius, HazardyKnusperkeks

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

Added: 
    clang/lib/Format/DefinitionBlockSeparator.cpp
    clang/lib/Format/DefinitionBlockSeparator.h
    clang/unittests/Format/DefinitionBlockSeparatorTest.cpp

Modified: 
    clang/docs/ClangFormatStyleOptions.rst
    clang/docs/ReleaseNotes.rst
    clang/include/clang/Format/Format.h
    clang/lib/Format/CMakeLists.txt
    clang/lib/Format/Format.cpp
    clang/lib/Format/WhitespaceManager.cpp
    clang/lib/Format/WhitespaceManager.h
    clang/unittests/Format/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index 777398f460e0..4f3a9eb9f4a6 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -3402,6 +3402,65 @@ the configuration (without a prefix: ``Auto``).
      /* second veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongComment with plenty of
       * information */
 
+**SeparateDefinitionBlocks** (``SeparateDefinitionStyle``) :versionbadge:`clang-format 14`
+  Specifies the use of empty lines to separate definition blocks, including classes,
+  structs, enums, and functions.
+
+  Possible values:
+
+  * ``SDS_Leave`` (in configuration: ``Leave``)
+    Leave definition blocks as they are.
+
+  * ``SDS_Always`` (in configuration: ``Always``)
+    Insert an empty line between definition blocks.
+
+  * ``SDS_Never`` (in configuration: ``Never``)
+    Remove any empty line between definition blocks.
+
+  .. code-block:: c++
+
+     Never                  v.s.     Always
+     #include <cstring>              #include <cstring>
+     struct Foo {
+       int a, b, c;                  struct Foo {
+     };                                int a, b, c;
+     namespace Ns {                  };
+     class Bar {
+     public:                         namespace Ns {
+       struct Foobar {               class Bar {
+         int a;                      public:
+         int b;                        struct Foobar {
+       };                                int a;
+     private:                            int b;
+       int t;                          };
+       int method1() {
+         // ...                      private:
+       }                               int t;
+       enum List {
+         ITEM1,                        int method1() {
+         ITEM2                           // ...
+       };                              }
+       template<typename T>
+       int method2(T x) {              enum List {
+         // ...                          ITEM1,
+       }                                 ITEM2
+       int i, j, k;                    };
+       int method3(int par) {
+         // ...                        template<typename T>
+       }                               int method2(T x) {
+     };                                  // ...
+     class C {};                       }
+     }
+                                       int i, j, k;
+
+                                       int method3(int par) {
+                                         // ...
+                                       }
+                                     };
+
+                                     class C {};
+                                     }
+
 **ShortNamespaceLines** (``Unsigned``) :versionbadge:`clang-format 14`
   The maximal number of unwrapped lines that a short namespace spans.
   Defaults to 1.

diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 20e7e6cc26ce..2f48b1424d09 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -297,6 +297,10 @@ clang-format
   `const` `volatile` `static` `inline` `constexpr` `restrict`
   to be controlled relative to the `type`.
 
+- Option ``SeparateDefinitionBlocks`` has been added to insert or remove empty
+  lines between definition blocks including functions, classes, structs, enums,
+  and namespaces.
+
 - Add a ``Custom`` style to ``SpaceBeforeParens``, to better configure the
   space before parentheses. The custom options can be set using
   ``SpaceBeforeParensOptions``.

diff  --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 5044158a2015..24c245642e6a 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -3054,6 +3054,63 @@ struct FormatStyle {
   bool ReflowComments;
   // clang-format on
 
+  enum SeparateDefinitionStyle {
+    /// Leave definition blocks as they are.
+    SDS_Leave,
+    /// Insert an empty line between definition blocks.
+    SDS_Always,
+    /// Remove any empty line between definition blocks.
+    SDS_Never
+  };
+
+  /// Specifies the use of empty lines to separate definition blocks, including
+  /// classes, structs, enums, and functions.
+  /// \code
+  ///    Never                  v.s.     Always
+  ///    #include <cstring>              #include <cstring>
+  ///    struct Foo {
+  ///      int a, b, c;                  struct Foo {
+  ///    };                                int a, b, c;
+  ///    namespace Ns {                  };
+  ///    class Bar {
+  ///    public:                         namespace Ns {
+  ///      struct Foobar {               class Bar {
+  ///        int a;                      public:
+  ///        int b;                        struct Foobar {
+  ///      };                                int a;
+  ///    private:                            int b;
+  ///      int t;                          };
+  ///      int method1() {
+  ///        // ...                      private:
+  ///      }                               int t;
+  ///      enum List {
+  ///        ITEM1,                        int method1() {
+  ///        ITEM2                           // ...
+  ///      };                              }
+  ///      template<typename T>
+  ///      int method2(T x) {              enum List {
+  ///        // ...                          ITEM1,
+  ///      }                                 ITEM2
+  ///      int i, j, k;                    };
+  ///      int method3(int par) {
+  ///        // ...                        template<typename T>
+  ///      }                               int method2(T x) {
+  ///    };                                  // ...
+  ///    class C {};                       }
+  ///    }
+  ///                                      int i, j, k;
+  ///
+  ///                                      int method3(int par) {
+  ///                                        // ...
+  ///                                      }
+  ///                                    };
+  ///
+  ///                                    class C {};
+  ///                                    }
+  /// \endcode
+  /// \version 14
+  SeparateDefinitionStyle SeparateDefinitionBlocks;
+
   /// The maximal number of unwrapped lines that a short namespace spans.
   /// Defaults to 1.
   ///
@@ -4033,6 +4090,17 @@ tooling::Replacements fixNamespaceEndComments(const FormatStyle &Style,
                                               ArrayRef<tooling::Range> Ranges,
                                               StringRef FileName = "<stdin>");
 
+/// Inserts or removes empty lines separating definition blocks including
+/// classes, structs, functions, namespaces, and enums in the given \p Ranges in
+/// \p Code.
+///
+/// Returns the ``Replacements`` that inserts or removes empty lines separating
+/// definition blocks in all \p Ranges in \p Code.
+tooling::Replacements separateDefinitionBlocks(const FormatStyle &Style,
+                                               StringRef Code,
+                                               ArrayRef<tooling::Range> Ranges,
+                                               StringRef FileName = "<stdin>");
+
 /// Sort consecutive using declarations in the given \p Ranges in
 /// \p Code.
 ///

diff  --git a/clang/lib/Format/CMakeLists.txt b/clang/lib/Format/CMakeLists.txt
index 4ff6a532119d..ca455157ae44 100644
--- a/clang/lib/Format/CMakeLists.txt
+++ b/clang/lib/Format/CMakeLists.txt
@@ -4,6 +4,7 @@ add_clang_library(clangFormat
   AffectedRangeManager.cpp
   BreakableToken.cpp
   ContinuationIndenter.cpp
+  DefinitionBlockSeparator.cpp
   Format.cpp
   FormatToken.cpp
   FormatTokenLexer.cpp

diff  --git a/clang/lib/Format/DefinitionBlockSeparator.cpp b/clang/lib/Format/DefinitionBlockSeparator.cpp
new file mode 100644
index 000000000000..ba51594f3f69
--- /dev/null
+++ b/clang/lib/Format/DefinitionBlockSeparator.cpp
@@ -0,0 +1,157 @@
+//===--- DefinitionBlockSeparator.cpp ---------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file implements DefinitionBlockSeparator, a TokenAnalyzer that inserts
+/// or removes empty lines separating definition blocks like classes, structs,
+/// functions, enums, and namespaces in between.
+///
+//===----------------------------------------------------------------------===//
+
+#include "DefinitionBlockSeparator.h"
+#include "llvm/Support/Debug.h"
+#define DEBUG_TYPE "definition-block-separator"
+
+namespace clang {
+namespace format {
+std::pair<tooling::Replacements, unsigned> DefinitionBlockSeparator::analyze(
+    TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
+    FormatTokenLexer &Tokens) {
+  assert(Style.SeparateDefinitionBlocks != FormatStyle::SDS_Leave);
+  AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
+  tooling::Replacements Result;
+  separateBlocks(AnnotatedLines, Result);
+  return {Result, 0};
+}
+
+void DefinitionBlockSeparator::separateBlocks(
+    SmallVectorImpl<AnnotatedLine *> &Lines, tooling::Replacements &Result) {
+  auto LikelyDefinition = [this](const AnnotatedLine *Line) {
+    if (Line->MightBeFunctionDecl && Line->mightBeFunctionDefinition())
+      return true;
+    FormatToken *CurrentToken = Line->First;
+    while (CurrentToken) {
+      if (CurrentToken->isOneOf(tok::kw_class, tok::kw_struct,
+                                tok::kw_namespace, tok::kw_enum) ||
+          (Style.Language == FormatStyle::LK_JavaScript &&
+           CurrentToken->TokenText == "function"))
+        return true;
+      CurrentToken = CurrentToken->Next;
+    }
+    return false;
+  };
+  unsigned NewlineCount =
+      (Style.SeparateDefinitionBlocks == FormatStyle::SDS_Always ? 1 : 0) + 1;
+  WhitespaceManager Whitespaces(
+      Env.getSourceManager(), Style,
+      Style.DeriveLineEnding
+          ? WhitespaceManager::inputUsesCRLF(
+                Env.getSourceManager().getBufferData(Env.getFileID()),
+                Style.UseCRLF)
+          : Style.UseCRLF);
+  for (unsigned I = 0; I < Lines.size(); I++) {
+    const auto &CurrentLine = Lines[I];
+    FormatToken *TargetToken = nullptr;
+    AnnotatedLine *TargetLine;
+    auto OpeningLineIndex = CurrentLine->MatchingOpeningBlockLineIndex;
+    const auto InsertReplacement = [&](const int NewlineToInsert) {
+      assert(TargetLine);
+      assert(TargetToken);
+
+      // Do not handle EOF newlines.
+      if (TargetToken->is(tok::eof) && NewlineToInsert > 0)
+        return;
+      if (!TargetLine->Affected)
+        return;
+      Whitespaces.replaceWhitespace(*TargetToken, NewlineToInsert,
+                                    TargetToken->SpacesRequiredBefore - 1,
+                                    TargetToken->StartsColumn);
+    };
+    const auto FollowingOtherOpening = [&]() {
+      return OpeningLineIndex == 0 ||
+             Lines[OpeningLineIndex - 1]->Last->opensScope();
+    };
+    const auto HasEnumOnLine = [CurrentLine]() {
+      FormatToken *CurrentToken = CurrentLine->First;
+      while (CurrentToken) {
+        if (CurrentToken->is(tok::kw_enum))
+          return true;
+        CurrentToken = CurrentToken->Next;
+      }
+      return false;
+    };
+
+    bool IsDefBlock = 0;
+
+    if (HasEnumOnLine()) {
+      // We have no scope opening/closing information for enum.
+      IsDefBlock = 1;
+      OpeningLineIndex = I;
+      TargetLine = CurrentLine;
+      TargetToken = CurrentLine->First;
+      if (!FollowingOtherOpening())
+        InsertReplacement(NewlineCount);
+      else
+        InsertReplacement(OpeningLineIndex != 0);
+      while (TargetToken && !TargetToken->is(tok::r_brace))
+        TargetToken = TargetToken->Next;
+      if (!TargetToken) {
+        while (I < Lines.size() && !Lines[I]->First->is(tok::r_brace))
+          I++;
+      }
+    } else if (CurrentLine->First->closesScope()) {
+      if (OpeningLineIndex > Lines.size())
+        continue;
+      // Handling the case that opening bracket has its own line.
+      OpeningLineIndex -= Lines[OpeningLineIndex]->First->TokenText == "{";
+      AnnotatedLine *OpeningLine = Lines[OpeningLineIndex];
+      // Closing a function definition.
+      if (LikelyDefinition(OpeningLine)) {
+        IsDefBlock = 1;
+        if (OpeningLineIndex > 0) {
+          OpeningLineIndex -=
+              Style.Language == FormatStyle::LK_CSharp &&
+              Lines[OpeningLineIndex - 1]->First->is(tok::l_square);
+          OpeningLine = Lines[OpeningLineIndex];
+        }
+        TargetLine = OpeningLine;
+        TargetToken = TargetLine->First;
+        if (!FollowingOtherOpening()) {
+          // Avoid duplicated replacement.
+          if (!TargetToken->opensScope())
+            InsertReplacement(NewlineCount);
+        } else
+          InsertReplacement(OpeningLineIndex != 0);
+      }
+    }
+
+    // Not the last token.
+    if (IsDefBlock && I + 1 < Lines.size()) {
+      TargetLine = Lines[I + 1];
+      TargetToken = TargetLine->First;
+
+      // No empty line for continuously closing scopes. The token will be
+      // handled in another case if the line following is opening a
+      // definition.
+      if (!TargetToken->closesScope()) {
+        if (!LikelyDefinition(TargetLine))
+          InsertReplacement(NewlineCount);
+      } else {
+        InsertReplacement(OpeningLineIndex != 0);
+      }
+    }
+  }
+  for (const auto &R : Whitespaces.generateReplacements())
+    // The add method returns an Error instance which simulates program exit
+    // code through overloading boolean operator, thus false here indicates
+    // success.
+    if (Result.add(R))
+      return;
+}
+} // namespace format
+} // namespace clang

diff  --git a/clang/lib/Format/DefinitionBlockSeparator.h b/clang/lib/Format/DefinitionBlockSeparator.h
new file mode 100644
index 000000000000..13b90c5ab083
--- /dev/null
+++ b/clang/lib/Format/DefinitionBlockSeparator.h
@@ -0,0 +1,41 @@
+//===--- DefinitionBlockSeparator.h -----------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file declares DefinitionBlockSeparator, a TokenAnalyzer that inserts or
+/// removes empty lines separating definition blocks like classes, structs,
+/// functions, enums, and namespaces in between.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_LIB_FORMAT_DEFINITIONBLOCKSEPARATOR_H
+#define LLVM_CLANG_LIB_FORMAT_DEFINITIONBLOCKSEPARATOR_H
+
+#include "TokenAnalyzer.h"
+#include "WhitespaceManager.h"
+
+namespace clang {
+namespace format {
+class DefinitionBlockSeparator : public TokenAnalyzer {
+public:
+  DefinitionBlockSeparator(const Environment &Env, const FormatStyle &Style)
+      : TokenAnalyzer(Env, Style) {}
+
+  std::pair<tooling::Replacements, unsigned>
+  analyze(TokenAnnotator &Annotator,
+          SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
+          FormatTokenLexer &Tokens) override;
+
+private:
+  void separateBlocks(SmallVectorImpl<AnnotatedLine *> &Lines,
+                      tooling::Replacements &Result);
+};
+} // namespace format
+} // namespace clang
+
+#endif

diff  --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index fdccb8b15e82..11c190ebfba7 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -16,6 +16,7 @@
 #include "AffectedRangeManager.h"
 #include "BreakableToken.h"
 #include "ContinuationIndenter.h"
+#include "DefinitionBlockSeparator.h"
 #include "FormatInternal.h"
 #include "FormatTokenLexer.h"
 #include "NamespaceEndCommentsFixer.h"
@@ -429,6 +430,15 @@ template <> struct ScalarEnumerationTraits<FormatStyle::PointerAlignmentStyle> {
   }
 };
 
+template <>
+struct ScalarEnumerationTraits<FormatStyle::SeparateDefinitionStyle> {
+  static void enumeration(IO &IO, FormatStyle::SeparateDefinitionStyle &Value) {
+    IO.enumCase(Value, "Leave", FormatStyle::SDS_Leave);
+    IO.enumCase(Value, "Always", FormatStyle::SDS_Always);
+    IO.enumCase(Value, "Never", FormatStyle::SDS_Never);
+  }
+};
+
 template <>
 struct ScalarEnumerationTraits<FormatStyle::SpaceAroundPointerQualifiersStyle> {
   static void
@@ -771,6 +781,7 @@ template <> struct MappingTraits<FormatStyle> {
     IO.mapOptional("RawStringFormats", Style.RawStringFormats);
     IO.mapOptional("ReferenceAlignment", Style.ReferenceAlignment);
     IO.mapOptional("ReflowComments", Style.ReflowComments);
+    IO.mapOptional("SeparateDefinitionBlocks", Style.SeparateDefinitionBlocks);
     IO.mapOptional("ShortNamespaceLines", Style.ShortNamespaceLines);
     IO.mapOptional("SortIncludes", Style.SortIncludes);
     IO.mapOptional("SortJavaStaticImport", Style.SortJavaStaticImport);
@@ -1195,6 +1206,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
   LLVMStyle.ObjCSpaceBeforeProtocolList = true;
   LLVMStyle.PointerAlignment = FormatStyle::PAS_Right;
   LLVMStyle.ReferenceAlignment = FormatStyle::RAS_Pointer;
+  LLVMStyle.SeparateDefinitionBlocks = FormatStyle::SDS_Leave;
   LLVMStyle.ShortNamespaceLines = 1;
   LLVMStyle.SpacesBeforeTrailingComments = 1;
   LLVMStyle.Standard = FormatStyle::LS_Latest;
@@ -1843,7 +1855,7 @@ class Formatter : public TokenAnalyzer {
     WhitespaceManager Whitespaces(
         Env.getSourceManager(), Style,
         Style.DeriveLineEnding
-            ? inputUsesCRLF(
+            ? WhitespaceManager::inputUsesCRLF(
                   Env.getSourceManager().getBufferData(Env.getFileID()),
                   Style.UseCRLF)
             : Style.UseCRLF);
@@ -1867,12 +1879,6 @@ class Formatter : public TokenAnalyzer {
   }
 
 private:
-  static bool inputUsesCRLF(StringRef Text, bool DefaultToCRLF) {
-    size_t LF = Text.count('\n');
-    size_t CR = Text.count('\r') * 2;
-    return LF == CR ? DefaultToCRLF : CR > LF;
-  }
-
   bool
   hasCpp03IncompatibleFormat(const SmallVectorImpl<AnnotatedLine *> &Lines) {
     for (const AnnotatedLine *Line : Lines) {
@@ -3053,6 +3059,11 @@ reformat(const FormatStyle &Style, StringRef Code,
       });
   }
 
+  if (Style.SeparateDefinitionBlocks != FormatStyle::SDS_Leave)
+    Passes.emplace_back([&](const Environment &Env) {
+      return DefinitionBlockSeparator(Env, Expanded).process();
+    });
+
   if (Style.isJavaScript() && Style.JavaScriptQuotes != FormatStyle::JSQS_Leave)
     Passes.emplace_back([&](const Environment &Env) {
       return JavaScriptRequoter(Env, Expanded).process();
@@ -3141,6 +3152,16 @@ tooling::Replacements fixNamespaceEndComments(const FormatStyle &Style,
   return NamespaceEndCommentsFixer(*Env, Style).process().first;
 }
 
+tooling::Replacements separateDefinitionBlocks(const FormatStyle &Style,
+                                               StringRef Code,
+                                               ArrayRef<tooling::Range> Ranges,
+                                               StringRef FileName) {
+  auto Env = Environment::make(Code, FileName, Ranges);
+  if (!Env)
+    return {};
+  return DefinitionBlockSeparator(*Env, Style).process().first;
+}
+
 tooling::Replacements sortUsingDeclarations(const FormatStyle &Style,
                                             StringRef Code,
                                             ArrayRef<tooling::Range> Ranges,

diff  --git a/clang/lib/Format/WhitespaceManager.cpp b/clang/lib/Format/WhitespaceManager.cpp
index 96a66da0f82b..f0e0247ce33e 100644
--- a/clang/lib/Format/WhitespaceManager.cpp
+++ b/clang/lib/Format/WhitespaceManager.cpp
@@ -74,6 +74,12 @@ WhitespaceManager::addReplacement(const tooling::Replacement &Replacement) {
   return Replaces.add(Replacement);
 }
 
+bool WhitespaceManager::inputUsesCRLF(StringRef Text, bool DefaultToCRLF) {
+  size_t LF = Text.count('\n');
+  size_t CR = Text.count('\r') * 2;
+  return LF == CR ? DefaultToCRLF : CR > LF;
+}
+
 void WhitespaceManager::replaceWhitespaceInToken(
     const FormatToken &Tok, unsigned Offset, unsigned ReplaceChars,
     StringRef PreviousPostfix, StringRef CurrentPrefix, bool InPPDirective,

diff  --git a/clang/lib/Format/WhitespaceManager.h b/clang/lib/Format/WhitespaceManager.h
index 029f4159b748..e6943b7d167b 100644
--- a/clang/lib/Format/WhitespaceManager.h
+++ b/clang/lib/Format/WhitespaceManager.h
@@ -45,6 +45,9 @@ class WhitespaceManager {
 
   bool useCRLF() const { return UseCRLF; }
 
+  /// Infers whether the input is using CRLF.
+  static bool inputUsesCRLF(StringRef Text, bool DefaultToCRLF);
+
   /// Replaces the whitespace in front of \p Tok. Only call once for
   /// each \c AnnotatedToken.
   ///

diff  --git a/clang/unittests/Format/CMakeLists.txt b/clang/unittests/Format/CMakeLists.txt
index 47075807c3b0..a4ece033d607 100644
--- a/clang/unittests/Format/CMakeLists.txt
+++ b/clang/unittests/Format/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
 
 add_clang_unittest(FormatTests
   CleanupTest.cpp
+  DefinitionBlockSeparatorTest.cpp
   FormatTest.cpp
   FormatTestComments.cpp
   FormatTestCSharp.cpp

diff  --git a/clang/unittests/Format/DefinitionBlockSeparatorTest.cpp b/clang/unittests/Format/DefinitionBlockSeparatorTest.cpp
new file mode 100644
index 000000000000..91933956c174
--- /dev/null
+++ b/clang/unittests/Format/DefinitionBlockSeparatorTest.cpp
@@ -0,0 +1,309 @@
+//===- DefinitionBlockSeparatorTest.cpp - Formatting unit tests -----------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "FormatTestUtils.h"
+#include "clang/Format/Format.h"
+
+#include "llvm/Support/Debug.h"
+#include "gtest/gtest.h"
+
+#define DEBUG_TYPE "definition-block-separator-test"
+
+namespace clang {
+namespace format {
+namespace {
+
+class DefinitionBlockSeparatorTest : public ::testing::Test {
+protected:
+  static std::string
+  separateDefinitionBlocks(llvm::StringRef Code,
+                           const std::vector<tooling::Range> &Ranges,
+                           const FormatStyle &Style = getLLVMStyle()) {
+    LLVM_DEBUG(llvm::errs() << "---\n");
+    LLVM_DEBUG(llvm::errs() << Code << "\n\n");
+    tooling::Replacements Replaces = reformat(Style, Code, Ranges, "<stdin>");
+    auto Result = applyAllReplacements(Code, Replaces);
+    EXPECT_TRUE(static_cast<bool>(Result));
+    LLVM_DEBUG(llvm::errs() << "\n" << *Result << "\n\n");
+    return *Result;
+  }
+
+  static std::string
+  separateDefinitionBlocks(llvm::StringRef Code,
+                           const FormatStyle &Style = getLLVMStyle()) {
+    return separateDefinitionBlocks(
+        Code,
+        /*Ranges=*/{1, tooling::Range(0, Code.size())}, Style);
+  }
+
+  static void verifyFormat(llvm::StringRef Code,
+                           const FormatStyle &Style = getLLVMStyle(),
+                           llvm::StringRef ExpectedCode = "") {
+    bool HasOriginalCode = true;
+    if (ExpectedCode == "") {
+      ExpectedCode = Code;
+      HasOriginalCode = false;
+    }
+
+    FormatStyle InverseStyle = Style;
+    if (Style.SeparateDefinitionBlocks == FormatStyle::SDS_Always)
+      InverseStyle.SeparateDefinitionBlocks = FormatStyle::SDS_Never;
+    else
+      InverseStyle.SeparateDefinitionBlocks = FormatStyle::SDS_Always;
+    EXPECT_EQ(ExpectedCode.str(), separateDefinitionBlocks(ExpectedCode, Style))
+        << "Expected code is not stable";
+    std::string InverseResult = separateDefinitionBlocks(Code, InverseStyle);
+    EXPECT_NE(Code.str(), InverseResult)
+        << "Inverse formatting makes no 
diff erence";
+    std::string CodeToFormat =
+        HasOriginalCode ? Code.str() : removeEmptyLines(Code);
+    std::string Result = separateDefinitionBlocks(CodeToFormat, Style);
+    EXPECT_EQ(ExpectedCode.str(), Result) << "Test failed. Formatted:\n"
+                                          << Result;
+  }
+
+  static std::string removeEmptyLines(llvm::StringRef Code) {
+    std::string Result = "";
+    for (auto Char : Code.str()) {
+      if (Result.size()) {
+        auto LastChar = Result.back();
+        if ((Char == '\n' && LastChar == '\n') ||
+            (Char == '\r' && (LastChar == '\r' || LastChar == '\n')))
+          continue;
+      }
+      Result.push_back(Char);
+    }
+    return Result;
+  }
+};
+
+TEST_F(DefinitionBlockSeparatorTest, Basic) {
+  FormatStyle Style = getLLVMStyle();
+  Style.SeparateDefinitionBlocks = FormatStyle::SDS_Always;
+  verifyFormat("int foo(int i, int j) {\n"
+               "  int r = i + j;\n"
+               "  return r;\n"
+               "}\n"
+               "\n"
+               "int bar(int j, int k) {\n"
+               "  int r = j + k;\n"
+               "  return r;\n"
+               "}",
+               Style);
+
+  verifyFormat("struct foo {\n"
+               "  int i, j;\n"
+               "};\n"
+               "\n"
+               "struct bar {\n"
+               "  int j, k;\n"
+               "};",
+               Style);
+
+  verifyFormat("class foo {\n"
+               "  int i, j;\n"
+               "};\n"
+               "\n"
+               "class bar {\n"
+               "  int j, k;\n"
+               "};",
+               Style);
+
+  verifyFormat("namespace foo {\n"
+               "int i, j;\n"
+               "}\n"
+               "\n"
+               "namespace bar {\n"
+               "int j, k;\n"
+               "}",
+               Style);
+
+  verifyFormat("enum Foo { FOO, BAR };\n"
+               "\n"
+               "enum Bar { FOOBAR, BARFOO };\n",
+               Style);
+}
+
+TEST_F(DefinitionBlockSeparatorTest, Always) {
+  FormatStyle Style = getLLVMStyle();
+  Style.SeparateDefinitionBlocks = FormatStyle::SDS_Always;
+  std::string Prefix = "namespace {\n";
+  std::string Postfix = "enum Foo { FOO, BAR };\n"
+                        "\n"
+                        "int foo(int i, int j) {\n"
+                        "  int r = i + j;\n"
+                        "  return r;\n"
+                        "}\n"
+                        "\n"
+                        "int i, j, k;\n"
+                        "\n"
+                        "int bar(int j, int k) {\n"
+                        "  int r = j * k;\n"
+                        "  return r;\n"
+                        "}\n"
+                        "\n"
+                        "enum Bar { FOOBAR, BARFOO };\n"
+                        "} // namespace";
+  verifyFormat(Prefix + "\n\n\n" + removeEmptyLines(Postfix), Style,
+               Prefix + Postfix);
+}
+
+TEST_F(DefinitionBlockSeparatorTest, Never) {
+  FormatStyle Style = getLLVMStyle();
+  Style.SeparateDefinitionBlocks = FormatStyle::SDS_Never;
+  std::string Prefix = "namespace {\n";
+  std::string Postfix = "enum Foo { FOO, BAR };\n"
+                        "\n"
+                        "int foo(int i, int j) {\n"
+                        "  int r = i + j;\n"
+                        "  return r;\n"
+                        "}\n"
+                        "\n"
+                        "int i, j, k;\n"
+                        "\n"
+                        "int bar(int j, int k) {\n"
+                        "  int r = j * k;\n"
+                        "  return r;\n"
+                        "}\n"
+                        "\n"
+                        "enum Bar { FOOBAR, BARFOO };\n"
+                        "} // namespace";
+  verifyFormat(Prefix + "\n\n\n" + Postfix, Style,
+               Prefix + removeEmptyLines(Postfix));
+}
+
+TEST_F(DefinitionBlockSeparatorTest, OpeningBracketOwnsLine) {
+  FormatStyle Style = getLLVMStyle();
+  Style.BreakBeforeBraces = FormatStyle::BS_Allman;
+  Style.SeparateDefinitionBlocks = FormatStyle::SDS_Always;
+  verifyFormat("enum Foo\n"
+               "{\n"
+               "  FOO,\n"
+               "  BAR\n"
+               "};\n"
+               "\n"
+               "int foo(int i, int j)\n"
+               "{\n"
+               "  int r = i + j;\n"
+               "  return r;\n"
+               "}\n"
+               "\n"
+               "int i, j, k;\n"
+               "\n"
+               "int bar(int j, int k)\n"
+               "{\n"
+               "  int r = j * k;\n"
+               "  return r;\n"
+               "}\n"
+               "\n"
+               "enum Bar\n"
+               "{\n"
+               "  FOOBAR,\n"
+               "  BARFOO\n"
+               "};",
+               Style);
+}
+
+TEST_F(DefinitionBlockSeparatorTest, Leave) {
+  FormatStyle Style = getLLVMStyle();
+  Style.SeparateDefinitionBlocks = FormatStyle::SDS_Leave;
+  Style.MaxEmptyLinesToKeep = 3;
+  std::string LeaveAs = "namespace {\n"
+                        "\n"
+                        "enum Foo { FOO, BAR };\n"
+                        "\n\n\n"
+                        "int foo(int i, int j) {\n"
+                        "  int r = i + j;\n"
+                        "  return r;\n"
+                        "}\n"
+                        "\n"
+                        "int i, j, k;\n"
+                        "\n"
+                        "int bar(int j, int k) {\n"
+                        "  int r = j * k;\n"
+                        "  return r;\n"
+                        "}\n"
+                        "\n"
+                        "enum Bar { FOOBAR, BARFOO };\n"
+                        "} // namespace";
+  verifyFormat(LeaveAs, Style, LeaveAs);
+}
+
+TEST_F(DefinitionBlockSeparatorTest, CSharp) {
+  FormatStyle Style = getLLVMStyle(FormatStyle::LK_CSharp);
+  Style.SeparateDefinitionBlocks = FormatStyle::SDS_Always;
+  Style.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_None;
+  Style.AllowShortEnumsOnASingleLine = false;
+  verifyFormat("namespace {\r\n"
+               "public class SomeTinyClass {\r\n"
+               "  int X;\r\n"
+               "}\r\n"
+               "\r\n"
+               "public class AnotherTinyClass {\r\n"
+               "  int Y;\r\n"
+               "}\r\n"
+               "\r\n"
+               "internal static String toString() {\r\n"
+               "}\r\n"
+               "\r\n"
+               "public enum var {\r\n"
+               "  none,\r\n"
+               "  @string,\r\n"
+               "  bool,\r\n"
+               "  @enum\r\n"
+               "}\r\n"
+               "\r\n"
+               "[STAThread]\r\n"
+               "static void Main(string[] args) {\r\n"
+               "  Console.WriteLine(\"HelloWorld\");\r\n"
+               "}\r\n"
+               "\r\n"
+               "static decimal Test() {\r\n"
+               "}\r\n"
+               "}\r\n"
+               "\r\n"
+               "public class FoobarClass {\r\n"
+               "  int foobar;\r\n"
+               "}",
+               Style);
+}
+
+TEST_F(DefinitionBlockSeparatorTest, JavaScript) {
+  FormatStyle Style = getLLVMStyle(FormatStyle::LK_JavaScript);
+  Style.SeparateDefinitionBlocks = FormatStyle::SDS_Always;
+  Style.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_None;
+  Style.AllowShortEnumsOnASingleLine = false;
+  verifyFormat("export const enum Foo {\n"
+               "  A = 1,\n"
+               "  B\n"
+               "}\n"
+               "\n"
+               "export function A() {\n"
+               "}\n"
+               "\n"
+               "export default function B() {\n"
+               "}\n"
+               "\n"
+               "export function C() {\n"
+               "}\n"
+               "\n"
+               "var t, p, q;\n"
+               "\n"
+               "export abstract class X {\n"
+               "  y: number;\n"
+               "}\n"
+               "\n"
+               "export const enum Bar {\n"
+               "  D = 1,\n"
+               "  E\n"
+               "}",
+               Style);
+}
+} // namespace
+} // namespace format
+} // namespace clang


        


More information about the cfe-commits mailing list