Index: clang/docs/ClangFormatStyleOptions.rst =================================================================== --- clang/docs/ClangFormatStyleOptions.rst +++ clang/docs/ClangFormatStyleOptions.rst @@ -3395,6 +3395,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 empty lines 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. Index: clang/include/clang/Format/Format.h =================================================================== --- clang/include/clang/Format/Format.h +++ clang/include/clang/Format/Format.h @@ -3050,6 +3050,63 @@ bool ReflowComments; // clang-format on + enum SeparateDefinitionStyle { + /// Leave definition blocks as they are. + SDS_Leave, + /// Insert empty lines 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. /// @@ -4028,6 +4085,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. /// Index: clang/lib/Format/CMakeLists.txt =================================================================== --- clang/lib/Format/CMakeLists.txt +++ clang/lib/Format/CMakeLists.txt @@ -4,6 +4,7 @@ AffectedRangeManager.cpp BreakableToken.cpp ContinuationIndenter.cpp + DefinitionBlockSeparator.cpp Format.cpp FormatToken.cpp FormatTokenLexer.cpp Index: clang/lib/Format/DefinitionBlockSeparator.h =================================================================== --- /dev/null +++ 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 Index: clang/lib/Format/DefinitionBlockSeparator.cpp =================================================================== --- /dev/null +++ clang/lib/Format/DefinitionBlockSeparator.cpp @@ -0,0 +1,152 @@ +//===--- 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) { + AffectedRangeMgr.computeAffectedLines(AnnotatedLines); + tooling::Replacements Result; + separateBlocks(AnnotatedLines, Result); + return {Result, 0}; +} + +void DefinitionBlockSeparator::separateBlocks( + SmallVectorImpl &Lines, tooling::Replacements &Result) { + if (Style.SeparateDefinitionBlocks == FormatStyle::SDS_Leave) + return; + auto likelyDefinition = [this](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++) { + auto Line = Lines[I]; + FormatToken *TargetToken = nullptr; + AnnotatedLine *TargetLine; + auto OpeningLineIndex = Line->MatchingOpeningBlockLineIndex; + const auto insertReplacement = [&](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 = [Line]() { + FormatToken *CurrentToken = Line->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 = Line; + TargetToken = Line->First; + if (!followingOtherOpening()) + insertReplacement(NewlineCount); + else + insertReplacement(OpeningLineIndex != 0); + } else if (Line->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 && !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 Index: clang/lib/Format/Format.cpp =================================================================== --- clang/lib/Format/Format.cpp +++ 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" @@ -450,6 +451,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 enumeration(IO &IO, FormatStyle::SpaceBeforeParensStyle &Value) { @@ -770,6 +780,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 +1204,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; @@ -1840,7 +1852,7 @@ WhitespaceManager Whitespaces( Env.getSourceManager(), Style, Style.DeriveLineEnding - ? inputUsesCRLF( + ? WhitespaceManager::inputUsesCRLF( Env.getSourceManager().getBufferData(Env.getFileID()), Style.UseCRLF) : Style.UseCRLF); @@ -1864,12 +1876,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) { @@ -3050,6 +3056,11 @@ }); } + if (Style.SeparateDefinitionBlocks) + 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(); @@ -3138,6 +3149,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, Index: clang/lib/Format/WhitespaceManager.h =================================================================== --- clang/lib/Format/WhitespaceManager.h +++ 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. /// Index: clang/lib/Format/WhitespaceManager.cpp =================================================================== --- clang/lib/Format/WhitespaceManager.cpp +++ 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, Index: clang/unittests/Format/CMakeLists.txt =================================================================== --- clang/unittests/Format/CMakeLists.txt +++ clang/unittests/Format/CMakeLists.txt @@ -4,6 +4,7 @@ add_clang_unittest(FormatTests CleanupTest.cpp + DefinitionBlockSeparatorTest.cpp FormatTest.cpp FormatTestComments.cpp FormatTestCSharp.cpp Index: clang/unittests/Format/DefinitionBlockSeparatorTest.cpp =================================================================== --- /dev/null +++ clang/unittests/Format/DefinitionBlockSeparatorTest.cpp @@ -0,0 +1,316 @@ +//===- 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 = + clang::format::separateDefinitionBlocks(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() {\n" + " int i, j;\n" + "}\n" + "\n" + "int bar() {\n" + " int j, k;\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 {\n" + " FOO, BAR\n" + "};\n" + "\n" + "enum Bar {\n" + " 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 {\n" + " FOO, BAR\n" + "};\n" + "\n" + "int foo() {\n" + " int i, j;\n" + "}\n" + "\n" + "int i, j, k;\n" + "\n" + "int bar() {\n" + " int j, k;\n" + "}\n" + "\n" + "enum Bar {\n" + " FOOBAR, BARFOO\n" + "};\n" + "}"; + verifyFormat(Prefix + "\n\n\n" + 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 {\n" + " FOO, BAR\n" + "};\n" + "\n" + "int foo() {\n" + " int i, j;\n" + "}\n" + "\n" + "int i, j, k;\n" + "\n" + "int bar() {\n" + " int j, k;\n" + "}\n" + "\n" + "enum Bar {\n" + " FOOBAR, BARFOO\n" + "};\n" + "}"; + verifyFormat(Prefix + "\n\n\n" + Postfix, Style, + Prefix + removeEmptyLines(Postfix)); +} + +TEST_F(DefinitionBlockSeparatorTest, OpeningBracketOwnsLine) { + FormatStyle Style = getLLVMStyle(); + Style.SeparateDefinitionBlocks = FormatStyle::SDS_Always; + verifyFormat("enum Foo\n" + "{\n" + " FOO, BAR\n" + "};\n" + "\n" + "int foo()\n" + "{\n" + " int i, j;\n" + "}\n" + "\n" + "int i, j, k;\n" + "\n" + "int bar()\n" + "{\n" + " int j, k;\n" + "}\n" + "\n" + "enum Bar\n" + "{\n" + " FOOBAR, BARFOO\n" + "};", + Style); +} + +TEST_F(DefinitionBlockSeparatorTest, Leave) { + FormatStyle Style = getLLVMStyle(); + Style.SeparateDefinitionBlocks = FormatStyle::SDS_Leave; + std::string LeaveAs = "namespace {\n" + "\n" + "enum Foo {\n" + " FOO, BAR\n" + "};\n" + "\n\n\n" + "int foo() {\n" + " int i, j;\n" + "}\n" + "\n" + "int i, j, k;\n" + "\n" + "int bar() {\n" + " int j, k;\n" + "}\n" + "\n" + "enum Bar {\n" + " FOOBAR, BARFOO\n" + "};\n" + "\n" + "}"; + verifyFormat(LeaveAs, Style, LeaveAs); +} + +TEST_F(DefinitionBlockSeparatorTest, CSharp) { + FormatStyle Style = getLLVMStyle(FormatStyle::LK_CSharp); + Style.SeparateDefinitionBlocks = FormatStyle::SDS_Always; + verifyFormat("namespace\r\n" + "{\r\n" + "public " + "class SomeTinyClass\r\n" + "{\r\n" + " int X;\r\n" + "}\r\n" + "\r\n" + "public " + "class AnotherTinyClass\r\n" + "{\r\n" + " int Y;\r\n" + "}\r\n" + "\r\n" + "internal static String toString()\r\n" + "{\r\n" + "}\r\n" + "\r\n" + "public " + "enum var\r\n" + "{\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" + "{\r\n" + "}\r\n" + "\r\n" + "static decimal Test()\r\n" + "{\r\n" + "}\r\n" + "}\r\n" + "\r\n" + "namespace\r\n" + "{\r\n" + "}\r\n", + Style); +} + +TEST_F(DefinitionBlockSeparatorTest, JavaScript) { + FormatStyle Style = getLLVMStyle(FormatStyle::LK_JavaScript); + Style.SeparateDefinitionBlocks = FormatStyle::SDS_Always; + 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" + "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