Skip to content

Commit af4bd7c

Browse files
author
Serban Ungureanu
committed
[clang-format] Add formatting options EmptyLinesAfterTopLevelComment and EmptyLinesAfterIncludes
1 parent ea9d75a commit af4bd7c

12 files changed

+378
-0
lines changed

clang/docs/ClangFormatStyleOptions.rst

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3220,6 +3220,58 @@ the configuration (without a prefix: ``Auto``).
32203220

32213221

32223222

3223+
.. _EmptyLinesAfterIncludes:
3224+
3225+
**EmptyLinesAfterIncludes** (``Unsigned``) :versionbadge:`clang-format 1` :ref:`<EmptyLinesAfterIncludes>`
3226+
Number of lines after includes.
3227+
If set, determines the number of lines to insert after includes.
3228+
Limited by MaxEmptyLinesToKeep.
3229+
Example:
3230+
EmptyLinesAfterIncludes = 1
3231+
3232+
.. code-block:: c++
3233+
3234+
#include <string>
3235+
#include <map>
3236+
3237+
class Test {};
3238+
3239+
vs EmptyLinesAfterIncludes = 2
3240+
3241+
.. code-block:: c++
3242+
3243+
#include <string>
3244+
#include <map>
3245+
3246+
3247+
class Test {};
3248+
3249+
.. _EmptyLinesAfterTopLevelComment:
3250+
3251+
**EmptyLinesAfterTopLevelComment** (``Unsigned``) :versionbadge:`clang-format 1` :ref:`<EmptyLinesAfterTopLevelComment>`
3252+
Number of empty lines after top level comment.
3253+
If set, determines the number of empty lines to insert/keep after the top
3254+
level comment. Limited by MaxEmptyLinesToKeep.
3255+
Example:
3256+
EmptyLinesAfterTopLevelComment = 1
3257+
3258+
.. code-block:: c++
3259+
3260+
/* LICENSE TEXT */
3261+
3262+
#include <string>
3263+
class Test {};
3264+
3265+
vs EmptyLinesAfterTopLevelComment = 2
3266+
3267+
.. code-block:: c++
3268+
3269+
/* License Text */
3270+
3271+
3272+
#include <string>
3273+
class Test {};
3274+
32233275
.. _ExperimentalAutoDetectBinPacking:
32243276

32253277
**ExperimentalAutoDetectBinPacking** (``Boolean``) :versionbadge:`clang-format 3.7` :ref:`<ExperimentalAutoDetectBinPacking>`

clang/include/clang/Format/Format.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2459,6 +2459,52 @@ struct FormatStyle {
24592459
/// \version 12
24602460
EmptyLineBeforeAccessModifierStyle EmptyLineBeforeAccessModifier;
24612461

2462+
/// \brief Number of lines after includes.
2463+
/// If set, determines the number of lines to insert after includes.
2464+
/// Limited by MaxEmptyLinesToKeep.
2465+
/// Example:
2466+
/// EmptyLinesAfterIncludes = 1
2467+
/// \code
2468+
/// #include <string>
2469+
/// #include <map>
2470+
///
2471+
/// class Test {};
2472+
///
2473+
/// \endcode
2474+
/// vs EmptyLinesAfterIncludes = 2
2475+
/// \code
2476+
/// #include <string>
2477+
/// #include <map>
2478+
///
2479+
///
2480+
/// class Test {};
2481+
/// \endcode
2482+
/// \version 1
2483+
std::optional<unsigned> EmptyLinesAfterIncludes;
2484+
2485+
/// \brief Number of empty lines after top level comment.
2486+
/// If set, determines the number of empty lines to insert/keep after the top
2487+
/// level comment. Limited by MaxEmptyLinesToKeep.
2488+
/// Example:
2489+
/// EmptyLinesAfterTopLevelComment = 1
2490+
/// \code
2491+
/// /* LICENSE TEXT */
2492+
///
2493+
/// #include <string>
2494+
/// class Test {};
2495+
///
2496+
/// \endcode
2497+
/// vs EmptyLinesAfterTopLevelComment = 2
2498+
/// \code
2499+
/// /* License Text */
2500+
///
2501+
///
2502+
/// #include <string>
2503+
/// class Test {};
2504+
/// \endcode
2505+
/// \version 1
2506+
std::optional<unsigned> EmptyLinesAfterTopLevelComment;
2507+
24622508
/// If ``true``, clang-format detects whether function calls and
24632509
/// definitions are formatted with one parameter per line.
24642510
///
@@ -4827,6 +4873,8 @@ struct FormatStyle {
48274873
DerivePointerAlignment == R.DerivePointerAlignment &&
48284874
DisableFormat == R.DisableFormat &&
48294875
EmptyLineAfterAccessModifier == R.EmptyLineAfterAccessModifier &&
4876+
EmptyLinesAfterIncludes == R.EmptyLinesAfterIncludes &&
4877+
EmptyLinesAfterTopLevelComment == R.EmptyLinesAfterTopLevelComment &&
48304878
EmptyLineBeforeAccessModifier == R.EmptyLineBeforeAccessModifier &&
48314879
ExperimentalAutoDetectBinPacking ==
48324880
R.ExperimentalAutoDetectBinPacking &&

clang/lib/Format/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ add_clang_library(clangFormat
1818
SortJavaScriptImports.cpp
1919
TokenAnalyzer.cpp
2020
TokenAnnotator.cpp
21+
TopLevelCommentSeparator.cpp
2122
UnwrappedLineFormatter.cpp
2223
UnwrappedLineParser.cpp
2324
UsingDeclarationsSorter.cpp

clang/lib/Format/Format.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "SortJavaScriptImports.h"
2828
#include "TokenAnalyzer.h"
2929
#include "TokenAnnotator.h"
30+
#include "TopLevelCommentSeparator.h"
3031
#include "UnwrappedLineFormatter.h"
3132
#include "UnwrappedLineParser.h"
3233
#include "UsingDeclarationsSorter.h"
@@ -995,6 +996,9 @@ template <> struct MappingTraits<FormatStyle> {
995996
IO.mapOptional("DisableFormat", Style.DisableFormat);
996997
IO.mapOptional("EmptyLineAfterAccessModifier",
997998
Style.EmptyLineAfterAccessModifier);
999+
IO.mapOptional("EmptyLinesAfterIncludes", Style.EmptyLinesAfterIncludes);
1000+
IO.mapOptional("EmptyLinesAfterTopLevelComment",
1001+
Style.EmptyLinesAfterTopLevelComment);
9981002
IO.mapOptional("EmptyLineBeforeAccessModifier",
9991003
Style.EmptyLineBeforeAccessModifier);
10001004
IO.mapOptional("ExperimentalAutoDetectBinPacking",
@@ -1035,6 +1039,8 @@ template <> struct MappingTraits<FormatStyle> {
10351039
IO.mapOptional("MaxEmptyLinesToKeep", Style.MaxEmptyLinesToKeep);
10361040
IO.mapOptional("NamespaceIndentation", Style.NamespaceIndentation);
10371041
IO.mapOptional("NamespaceMacros", Style.NamespaceMacros);
1042+
IO.mapOptional("EmptyLinesAfterTopLevelComment",
1043+
Style.EmptyLinesAfterTopLevelComment);
10381044
IO.mapOptional("ObjCBinPackProtocolList", Style.ObjCBinPackProtocolList);
10391045
IO.mapOptional("ObjCBlockIndentWidth", Style.ObjCBlockIndentWidth);
10401046
IO.mapOptional("ObjCBreakBeforeNestedBlockParam",
@@ -1501,6 +1507,8 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
15011507
LLVMStyle.DerivePointerAlignment = false;
15021508
LLVMStyle.DisableFormat = false;
15031509
LLVMStyle.EmptyLineAfterAccessModifier = FormatStyle::ELAAMS_Never;
1510+
LLVMStyle.EmptyLinesAfterIncludes = std::nullopt;
1511+
LLVMStyle.EmptyLinesAfterTopLevelComment = std::nullopt;
15041512
LLVMStyle.EmptyLineBeforeAccessModifier = FormatStyle::ELBAMS_LogicalBlock;
15051513
LLVMStyle.ExperimentalAutoDetectBinPacking = false;
15061514
LLVMStyle.FixNamespaceComments = true;
@@ -3713,6 +3721,12 @@ reformat(const FormatStyle &Style, StringRef Code,
37133721
});
37143722
}
37153723

3724+
if (Style.EmptyLinesAfterTopLevelComment.has_value()) {
3725+
Passes.emplace_back([&](const Environment &Env) {
3726+
return TopLevelCommentSeparator(Env, Expanded).process();
3727+
});
3728+
}
3729+
37163730
if (Style.Language == FormatStyle::LK_ObjC &&
37173731
!Style.ObjCPropertyAttributeOrder.empty()) {
37183732
Passes.emplace_back([&](const Environment &Env) {

clang/lib/Format/TokenAnnotator.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ class AnnotatedLine {
113113
return First && First->is(tok::comment) && !First->getNextNonComment();
114114
}
115115

116+
bool isInclude() const {
117+
if (!First)
118+
return false;
119+
120+
const auto *NextToken = First->getNextNonComment();
121+
return First->is(tok::hash) && NextToken && NextToken->is(tok::pp_include);
122+
}
123+
116124
/// \c true if this line starts with the given tokens in order, ignoring
117125
/// comments.
118126
template <typename... Ts> bool startsWith(Ts... Tokens) const {
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//===--- TopLevelCommentSeparator.cpp ---------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
///
9+
/// \file
10+
/// This file implements TopLevelCommentSeparator, a TokenAnalyzer that inserts
11+
/// new lines or removes empty lines after the top level comment (i.e. comment
12+
/// block at the top of the source file), usually license text or documentation.
13+
///
14+
//===----------------------------------------------------------------------===//
15+
16+
#include "TopLevelCommentSeparator.h"
17+
#define DEBUG_TYPE "top-level-comment-separator"
18+
19+
namespace clang {
20+
namespace format {
21+
std::pair<tooling::Replacements, unsigned> TopLevelCommentSeparator::analyze(
22+
TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
23+
FormatTokenLexer &Tokens) {
24+
assert(Style.EmptyLinesAfterTopLevelComment.has_value());
25+
AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
26+
tooling::Replacements Result;
27+
separateTopLevelComment(AnnotatedLines, Result, Tokens);
28+
return {Result, 0};
29+
}
30+
31+
void TopLevelCommentSeparator::separateTopLevelComment(
32+
SmallVectorImpl<AnnotatedLine *> &Lines, tooling::Replacements &Result,
33+
FormatTokenLexer &Tokens) {
34+
unsigned NewlineCount = std::min(Style.MaxEmptyLinesToKeep,
35+
*Style.EmptyLinesAfterTopLevelComment) +
36+
1;
37+
WhitespaceManager Whitespaces(
38+
Env.getSourceManager(), Style,
39+
Style.LineEnding > FormatStyle::LE_CRLF
40+
? WhitespaceManager::inputUsesCRLF(
41+
Env.getSourceManager().getBufferData(Env.getFileID()),
42+
Style.LineEnding == FormatStyle::LE_DeriveCRLF)
43+
: Style.LineEnding == FormatStyle::LE_CRLF);
44+
45+
bool InTopLevelComment = false;
46+
for (unsigned I = 0; I < Lines.size(); ++I) {
47+
const auto &CurrentLine = Lines[I];
48+
if (CurrentLine->isComment()) {
49+
InTopLevelComment = true;
50+
} else if (InTopLevelComment) {
51+
// Do not handle EOF newlines.
52+
if (!CurrentLine->First->is(tok::eof) && CurrentLine->Affected) {
53+
Whitespaces.replaceWhitespace(*CurrentLine->First, NewlineCount,
54+
CurrentLine->First->OriginalColumn,
55+
CurrentLine->First->OriginalColumn);
56+
}
57+
break;
58+
}
59+
}
60+
61+
for (const auto &R : Whitespaces.generateReplacements()) {
62+
// The add method returns an Error instance which simulates program exit
63+
// code through overloading boolean operator, thus false here indicates
64+
// success.
65+
if (Result.add(R))
66+
return;
67+
}
68+
}
69+
} // namespace format
70+
} // namespace clang
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//===--- TopLevelCommentSeparator.h -----------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
///
9+
/// \file
10+
/// This file declares TopLevelCommentSeparator, a TokenAnalyzer that inserts
11+
/// new lines or removes empty lines after the top level comment (i.e. comment
12+
/// block at the top of the source file), usually license text or documentation.
13+
///
14+
//===----------------------------------------------------------------------===//
15+
16+
#ifndef LLVM_CLANG_LIB_FORMAT_TOPLEVELCOMMENTSEPARATOR_H
17+
#define LLVM_CLANG_LIB_FORMAT_TOPLEVELCOMMENTSEPARATOR_H
18+
19+
#include "TokenAnalyzer.h"
20+
#include "WhitespaceManager.h"
21+
22+
namespace clang {
23+
namespace format {
24+
class TopLevelCommentSeparator : public TokenAnalyzer {
25+
public:
26+
TopLevelCommentSeparator(const Environment &Env, const FormatStyle &Style)
27+
: TokenAnalyzer(Env, Style) {}
28+
29+
std::pair<tooling::Replacements, unsigned>
30+
analyze(TokenAnnotator &Annotator,
31+
SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
32+
FormatTokenLexer &Tokens) override;
33+
34+
private:
35+
void separateTopLevelComment(SmallVectorImpl<AnnotatedLine *> &Lines,
36+
tooling::Replacements &Result,
37+
FormatTokenLexer &Tokens);
38+
};
39+
} // namespace format
40+
} // namespace clang
41+
42+
#endif

clang/lib/Format/UnwrappedLineFormatter.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,6 +1535,12 @@ static auto computeNewlines(const AnnotatedLine &Line,
15351535
}
15361536
}
15371537

1538+
if (Style.EmptyLinesAfterIncludes.has_value() && !Line.InMacroBody &&
1539+
PreviousLine && PreviousLine->isInclude() && !Line.isInclude()) {
1540+
Newlines =
1541+
1 + std::min(Style.MaxEmptyLinesToKeep, *Style.EmptyLinesAfterIncludes);
1542+
}
1543+
15381544
return Newlines;
15391545
}
15401546

clang/unittests/Format/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ add_clang_unittest(FormatTests
3636
SortIncludesTest.cpp
3737
UsingDeclarationsSorterTest.cpp
3838
TokenAnnotatorTest.cpp
39+
TopLevelCommentSeparatorTest.cpp
3940
)
4041

4142
clang_target_link_libraries(FormatTests

clang/unittests/Format/ConfigParseTest.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,10 @@ TEST(ConfigParseTest, ParsesConfiguration) {
10041004
FormatStyle::SDS_Leave);
10051005
CHECK_PARSE("SeparateDefinitionBlocks: Never", SeparateDefinitionBlocks,
10061006
FormatStyle::SDS_Never);
1007+
1008+
CHECK_PARSE("EmptyLinesAfterIncludes: 2", EmptyLinesAfterIncludes, 2);
1009+
CHECK_PARSE("EmptyLinesAfterTopLevelComment: 2",
1010+
EmptyLinesAfterTopLevelComment, 2);
10071011
}
10081012

10091013
TEST(ConfigParseTest, ParsesConfigurationWithLanguages) {

0 commit comments

Comments
 (0)