diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -3402,6 +3402,65 @@ /* 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 #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; + enum List { + ITEM1, int method1() { + ITEM2 // ... + }; } + template + int method2(T x) { enum List { + // ... ITEM1, + } ITEM2 + int i, j, k; }; + int method3(int par) { + // ... template + } 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 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -297,6 +297,10 @@ `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 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -3054,6 +3054,63 @@ 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 #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; + /// enum List { + /// ITEM1, int method1() { + /// ITEM2 // ... + /// }; } + /// template + /// int method2(T x) { enum List { + /// // ... ITEM1, + /// } ITEM2 + /// int i, j, k; }; + /// int method3(int par) { + /// // ... template + /// } 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 @@ ArrayRef Ranges, StringRef FileName = ""); +/// 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 Ranges, + StringRef FileName = ""); + /// 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 --- a/clang/lib/Format/CMakeLists.txt +++ b/clang/lib/Format/CMakeLists.txt @@ -4,6 +4,7 @@ AffectedRangeManager.cpp BreakableToken.cpp ContinuationIndenter.cpp + DefinitionBlockSeparator.cpp Format.cpp FormatToken.cpp FormatTokenLexer.cpp diff --git a/clang/lib/Format/DefinitionBlockSeparator.h b/clang/lib/Format/DefinitionBlockSeparator.h new file mode 100644 --- /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 + analyze(TokenAnnotator &Annotator, + SmallVectorImpl &AnnotatedLines, + FormatTokenLexer &Tokens) override; + +private: + void separateBlocks(SmallVectorImpl &Lines, + tooling::Replacements &Result); +}; +} // namespace format +} // namespace clang + +#endif diff --git a/clang/lib/Format/DefinitionBlockSeparator.cpp b/clang/lib/Format/DefinitionBlockSeparator.cpp new file mode 100644 --- /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 DefinitionBlockSeparator::analyze( + TokenAnnotator &Annotator, SmallVectorImpl &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 &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/Format.cpp b/clang/lib/Format/Format.cpp --- 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 { + 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 { static void @@ -771,6 +781,7 @@ 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 @@ 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 @@ WhitespaceManager Whitespaces( Env.getSourceManager(), Style, Style.DeriveLineEnding - ? inputUsesCRLF( + ? WhitespaceManager::inputUsesCRLF( Env.getSourceManager().getBufferData(Env.getFileID()), Style.UseCRLF) : Style.UseCRLF); @@ -1867,12 +1879,6 @@ } 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 &Lines) { for (const AnnotatedLine *Line : Lines) { @@ -3053,6 +3059,11 @@ }); } + 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 @@ return NamespaceEndCommentsFixer(*Env, Style).process().first; } +tooling::Replacements separateDefinitionBlocks(const FormatStyle &Style, + StringRef Code, + ArrayRef 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 Ranges, diff --git a/clang/lib/Format/WhitespaceManager.h b/clang/lib/Format/WhitespaceManager.h --- a/clang/lib/Format/WhitespaceManager.h +++ b/clang/lib/Format/WhitespaceManager.h @@ -45,6 +45,9 @@ 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/lib/Format/WhitespaceManager.cpp b/clang/lib/Format/WhitespaceManager.cpp --- a/clang/lib/Format/WhitespaceManager.cpp +++ b/clang/lib/Format/WhitespaceManager.cpp @@ -74,6 +74,12 @@ 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/unittests/Format/CMakeLists.txt b/clang/unittests/Format/CMakeLists.txt --- a/clang/unittests/Format/CMakeLists.txt +++ b/clang/unittests/Format/CMakeLists.txt @@ -4,6 +4,7 @@ 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 --- /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 &Ranges, + const FormatStyle &Style = getLLVMStyle()) { + LLVM_DEBUG(llvm::errs() << "---\n"); + LLVM_DEBUG(llvm::errs() << Code << "\n\n"); + tooling::Replacements Replaces = reformat(Style, Code, Ranges, ""); + auto Result = applyAllReplacements(Code, Replaces); + EXPECT_TRUE(static_cast(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 difference"; + 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