ksyx created this revision.
ksyx added a reviewer: MyDeveloperDay.
ksyx added projects: clang-format, clang.
ksyx requested review of this revision.
Herald added a subscriber: cfe-commits.

This commit resolves GitHub issue #45895 
<https://github.com/llvm/llvm-project/issues/45895> (Bugzilla #46550 
<https://llvm.org/bz46550>), to add empty line between definition blocks 
including namespace, class, and struct.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D116314

Files:
  clang/docs/ClangFormatStyleOptions.rst
  clang/include/clang/Format/Format.h
  clang/lib/Format/Format.cpp

Index: clang/lib/Format/Format.cpp
===================================================================
--- clang/lib/Format/Format.cpp
+++ clang/lib/Format/Format.cpp
@@ -770,6 +770,7 @@
     IO.mapOptional("ReferenceAlignment", Style.ReferenceAlignment);
     IO.mapOptional("ReflowComments", Style.ReflowComments);
     IO.mapOptional("ShortNamespaceLines", Style.ShortNamespaceLines);
+    IO.mapOptional("SeparateDefinitionBlocks", Style.SeparateDefinitionBlocks);
     IO.mapOptional("SortIncludes", Style.SortIncludes);
     IO.mapOptional("SortJavaStaticImport", Style.SortJavaStaticImport);
     IO.mapOptional("SortUsingDeclarations", Style.SortUsingDeclarations);
@@ -1193,6 +1194,7 @@
   LLVMStyle.ObjCSpaceBeforeProtocolList = true;
   LLVMStyle.PointerAlignment = FormatStyle::PAS_Right;
   LLVMStyle.ReferenceAlignment = FormatStyle::RAS_Pointer;
+  LLVMStyle.SeparateDefinitionBlocks = true;
   LLVMStyle.ShortNamespaceLines = 1;
   LLVMStyle.SpacesBeforeTrailingComments = 1;
   LLVMStyle.Standard = FormatStyle::LS_Latest;
@@ -1333,6 +1335,7 @@
           /*BasedOnStyle=*/"google",
       },
   };
+  GoogleStyle.SeparateDefinitionBlocks = false;
   GoogleStyle.SpacesBeforeTrailingComments = 2;
   GoogleStyle.Standard = FormatStyle::LS_Auto;
 
@@ -1863,13 +1866,13 @@
     return std::make_pair(Result, Penalty);
   }
 
-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;
   }
 
+private:
   bool
   hasCpp03IncompatibleFormat(const SmallVectorImpl<AnnotatedLine *> &Lines) {
     for (const AnnotatedLine *Line : Lines) {
@@ -2012,6 +2015,98 @@
   }
 };
 
+// This class separates definition blocks like classes, functions, and
+// namespaces by inserting a line break between them.
+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 {
+    AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
+    tooling::Replacements Result;
+    separateBlocks(AnnotatedLines, Result);
+    return {Result, 0};
+  }
+
+private:
+  void separateBlocks(SmallVectorImpl<AnnotatedLine *> &Lines,
+                      tooling::Replacements &Result) {
+    auto likelyDefinition = [](AnnotatedLine *Line) {
+      return (Line->MightBeFunctionDecl && Line->mightBeFunctionDefinition()) ||
+             Line->First->isOneOf(tok::kw_class, tok::kw_struct,
+                                  tok::kw_namespace);
+    };
+    WhitespaceManager Whitespaces(
+        Env.getSourceManager(), Style,
+        Style.DeriveLineEnding
+            ? Formatter::inputUsesCRLF(
+                  Env.getSourceManager().getBufferData(Env.getFileID()),
+                  Style.UseCRLF)
+            : Style.UseCRLF);
+    for (unsigned I = 0; I < Lines.size(); I++) {
+      auto Line = Lines[I];
+      if (Line->First->closesScope()) {
+        auto OpeningLineIndex = Line->MatchingOpeningBlockLineIndex;
+        // Case: Opening bracket has its own line
+        if (OpeningLineIndex > 0 &&
+            Lines[OpeningLineIndex]->First->TokenText == "{") {
+          OpeningLineIndex--;
+        }
+        AnnotatedLine *OpeningLine = Lines[OpeningLineIndex];
+        // Closing a function definition
+        if (likelyDefinition(OpeningLine)) {
+          FormatToken *TargetToken = nullptr;
+          AnnotatedLine *TargetLine;
+          auto insertReplacement = [&]() {
+            assert(TargetToken);
+            Whitespaces.replaceWhitespace(*TargetToken, 2,
+                                          TargetToken->SpacesRequiredBefore,
+                                          TargetToken->StartsColumn);
+          };
+
+          // Not the first token
+          if (OpeningLineIndex > 0) {
+            TargetLine = Lines[OpeningLineIndex - 1];
+            // Not immediately following other scopes' opening
+            if (TargetLine->Affected && !TargetLine->Last->opensScope()) {
+              // Change target, since we need to put line break *before*
+              // a token
+              TargetLine = OpeningLine;
+              TargetToken = TargetLine->First;
+
+              // Avoid duplicated replacement
+              if (TargetToken && !TargetToken->opensScope()) {
+                insertReplacement();
+              }
+            }
+          }
+
+          // Not the last token
+          if (I + 1 < Lines.size()) {
+            TargetLine = Lines[I + 1];
+            TargetToken = TargetLine->First;
+
+            // Not continuously closing scopes (e.g. function + class +
+            // namespace); The token will be handled in another case if
+            // it is a definition line
+            if (TargetLine->Affected && !TargetToken->closesScope() &&
+                !likelyDefinition(TargetLine)) {
+              insertReplacement();
+            }
+          }
+        }
+      }
+    }
+    for (const auto &R : Whitespaces.generateReplacements())
+      if (Result.add(R))
+        return;
+  }
+};
+
 // This class clean up the erroneous/redundant code around the given ranges in
 // file.
 class Cleaner : public TokenAnalyzer {
@@ -3048,6 +3143,11 @@
       Passes.emplace_back([&](const Environment &Env) {
         return UsingDeclarationsSorter(Env, Expanded).process();
       });
+
+    if (Style.SeparateDefinitionBlocks)
+      Passes.emplace_back([&](const Environment &Env) {
+        return DefinitionBlockSeparator(Env, Expanded).process();
+      });
   }
 
   if (Style.isJavaScript() && Style.JavaScriptQuotes != FormatStyle::JSQS_Leave)
Index: clang/include/clang/Format/Format.h
===================================================================
--- clang/include/clang/Format/Format.h
+++ clang/include/clang/Format/Format.h
@@ -3050,6 +3050,45 @@
   bool ReflowComments;
   // clang-format on
 
+  /// If ``true``, clang-format will insert empty lines between definition blocks
+  /// for C++ language.
+  /// \code
+  ///    SeparateDefinitions
+  ///    true                 v.s.   false
+  ///    #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;
+  ///      template<typename T>
+  ///      int method2(T x) {          int method1() {
+  ///        // ...                      // ...
+  ///      }                           }
+  ///      int method3(int par) {}
+  ///    };                            template <typename T> int method2(T x) {
+  ///    class C {                       // ...
+  ///    }                             }
+  ///    }
+  ///                                  int method3(int par) {}
+  ///                                };
+  ///
+  ///                                class C {}
+  ///                                } // namespace Ns
+  /// \endcode
+  /// \version 15
+  bool SeparateDefinitionBlocks;
+
   /// The maximal number of unwrapped lines that a short namespace spans.
   /// Defaults to 1.
   ///
Index: clang/docs/ClangFormatStyleOptions.rst
===================================================================
--- clang/docs/ClangFormatStyleOptions.rst
+++ clang/docs/ClangFormatStyleOptions.rst
@@ -3395,6 +3395,45 @@
      /* second veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongComment with plenty of
       * information */
 
+**SeparateDefinitionBlocks** (``Boolean``) :versionbadge:`calng-format 15`
+  If ``true``, clang-format will insert empty lines between definition blocks
+  for C++ language.
+
+  .. code-block:: c++
+
+     SeparateDefinitions
+     true                 v.s.   false
+     #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;
+       template<typename T>
+       int method2(T x) {          int method1() {
+         // ...                      // ...
+       }                           }
+       int method3(int par) {}
+     };                            template <typename T> int method2(T x) {
+     class C {                       // ...
+     }                             }
+     }
+                                   int method3(int par) {}
+                                 };
+
+                                 class C {}
+                                 } // namespace Ns
+
 **ShortNamespaceLines** (``Unsigned``) :versionbadge:`clang-format 14`
   The maximal number of unwrapped lines that a short namespace spans.
   Defaults to 1.
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to