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:`clang-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 #include + 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 + int method2(T x) { int method1() { + // ... // ... + } } + int method3(int par) {} + }; template 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. 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 #include + /// 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 + /// int method2(T x) { int method1() { + /// // ... // ... + /// } } + /// int method3(int par) {} + /// }; template 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/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 &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 + analyze(TokenAnnotator &Annotator, + SmallVectorImpl &AnnotatedLines, + FormatTokenLexer &Tokens) override { + AffectedRangeMgr.computeAffectedLines(AnnotatedLines); + tooling::Replacements Result; + separateBlocks(AnnotatedLines, Result); + return {Result, 0}; + } + +private: + void separateBlocks(SmallVectorImpl &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)