[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
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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``.
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -4,6 +4,7 @@ add_clang_library(clangFormat
|
||||
AffectedRangeManager.cpp
|
||||
BreakableToken.cpp
|
||||
ContinuationIndenter.cpp
|
||||
DefinitionBlockSeparator.cpp
|
||||
Format.cpp
|
||||
FormatToken.cpp
|
||||
FormatTokenLexer.cpp
|
||||
|
||||
157
clang/lib/Format/DefinitionBlockSeparator.cpp
Normal file
157
clang/lib/Format/DefinitionBlockSeparator.cpp
Normal file
@@ -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
|
||||
41
clang/lib/Format/DefinitionBlockSeparator.h
Normal file
41
clang/lib/Format/DefinitionBlockSeparator.h
Normal file
@@ -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
|
||||
@@ -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<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
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
|
||||
|
||||
add_clang_unittest(FormatTests
|
||||
CleanupTest.cpp
|
||||
DefinitionBlockSeparatorTest.cpp
|
||||
FormatTest.cpp
|
||||
FormatTestComments.cpp
|
||||
FormatTestCSharp.cpp
|
||||
|
||||
309
clang/unittests/Format/DefinitionBlockSeparatorTest.cpp
Normal file
309
clang/unittests/Format/DefinitionBlockSeparatorTest.cpp
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user