summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorksyx <18738953+ksyx@users.noreply.github.com>2021-12-28 10:01:26 -0500
committerksyx <18738953+ksyx@users.noreply.github.com>2022-01-03 15:47:39 -0500
commit6f6f88ffdae1e12e5f950ef418827a77a55c09c7 (patch)
tree0f1eb44f31242df9fdd124b6fb65624ad59384ed
parentcfe3180742adfc72ad6f5de55cbfc84befb90c97 (diff)
downloadllvm-6f6f88ffdae1e12e5f950ef418827a77a55c09c7.tar.gz
[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
-rw-r--r--clang/docs/ClangFormatStyleOptions.rst59
-rw-r--r--clang/docs/ReleaseNotes.rst4
-rw-r--r--clang/include/clang/Format/Format.h68
-rw-r--r--clang/lib/Format/CMakeLists.txt1
-rw-r--r--clang/lib/Format/DefinitionBlockSeparator.cpp157
-rw-r--r--clang/lib/Format/DefinitionBlockSeparator.h41
-rw-r--r--clang/lib/Format/Format.cpp35
-rw-r--r--clang/lib/Format/WhitespaceManager.cpp6
-rw-r--r--clang/lib/Format/WhitespaceManager.h3
-rw-r--r--clang/unittests/Format/CMakeLists.txt1
-rw-r--r--clang/unittests/Format/DefinitionBlockSeparatorTest.cpp309
11 files changed, 677 insertions, 7 deletions
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"
@@ -430,6 +431,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
enumeration(IO &IO, FormatStyle::SpaceAroundPointerQualifiersStyle &Value) {
@@ -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 @@ public:
WhitespaceManager Whitespaces(
Env.getSourceManager(), Style,
Style.DeriveLineEnding
- ? inputUsesCRLF(
+ ? WhitespaceManager::inputUsesCRLF(
Env.getSourceManager().getBufferData(Env.getFileID()),
Style.UseCRLF)
: Style.UseCRLF);
@@ -1867,12 +1879,6 @@ public:
}
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 @@ public:
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 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