Skip to content

Commit 3519956

Browse files
author
Serban Ungureanu
committed
[clang-format] Add options to set number of empty lines after includes
1 parent cc38cff commit 3519956

File tree

10 files changed

+445
-0
lines changed

10 files changed

+445
-0
lines changed

clang/docs/ClangFormatStyleOptions.rst

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

32213221

32223222

3223+
.. _EmptyLinesAfterIncludes:
3224+
3225+
**EmptyLinesAfterIncludes** (``Unsigned``) :versionbadge:`clang-format 18` :ref:`<EmptyLinesAfterIncludes>`
3226+
Number of lines after each include area. An include area is
3227+
a list of consecutive include statements. The include area may be
3228+
composed of multiple include blocks.
3229+
Limited by MaxEmptyLinesToKeep.
3230+
Example:
3231+
3232+
.. code-block:: c++
3233+
3234+
3235+
EmptyLinesAfterIncludes: 1 vs. EmptyLinesAfterIncludes: 2
3236+
#include <string> #include <string>
3237+
#include <map> #include <map>
3238+
3239+
class Test {};
3240+
class Test {};
3241+
32233242
.. _ExperimentalAutoDetectBinPacking:
32243243

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

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,6 +1260,7 @@ clang-format
12601260
- Add ``.clang-format-ignore`` files.
12611261
- Add ``AlignFunctionPointers`` sub-option for ``AlignConsecutiveDeclarations``.
12621262
- Add ``SkipMacroDefinitionBody`` option.
1263+
- Add ``EmptyLinesAfterIncludes`` option.
12631264

12641265
libclang
12651266
--------

clang/include/clang/Format/Format.h

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

2462+
/// \brief Number of lines after each include area. An include area is
2463+
/// a list of consecutive include statements. The include area may be
2464+
/// composed of multiple include blocks.
2465+
/// Limited by MaxEmptyLinesToKeep.
2466+
/// Example:
2467+
/// \code
2468+
///
2469+
/// EmptyLinesAfterIncludes: 1 vs. EmptyLinesAfterIncludes: 2
2470+
/// #include <string> #include <string>
2471+
/// #include <map> #include <map>
2472+
///
2473+
/// class Test {};
2474+
/// class Test {};
2475+
/// \endcode
2476+
/// \version 18
2477+
std::optional<unsigned> EmptyLinesAfterIncludes;
2478+
24622479
/// If ``true``, clang-format detects whether function calls and
24632480
/// definitions are formatted with one parameter per line.
24642481
///
@@ -4831,6 +4848,7 @@ struct FormatStyle {
48314848
DerivePointerAlignment == R.DerivePointerAlignment &&
48324849
DisableFormat == R.DisableFormat &&
48334850
EmptyLineAfterAccessModifier == R.EmptyLineAfterAccessModifier &&
4851+
EmptyLinesAfterIncludes == R.EmptyLinesAfterIncludes &&
48344852
EmptyLineBeforeAccessModifier == R.EmptyLineBeforeAccessModifier &&
48354853
ExperimentalAutoDetectBinPacking ==
48364854
R.ExperimentalAutoDetectBinPacking &&

clang/lib/Format/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ add_clang_library(clangFormat
88
Format.cpp
99
FormatToken.cpp
1010
FormatTokenLexer.cpp
11+
IncludesSeparator.cpp
1112
IntegerLiteralSeparatorFixer.cpp
1213
MacroCallReconstructor.cpp
1314
MacroExpander.cpp

clang/lib/Format/Format.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "FormatInternal.h"
2121
#include "FormatToken.h"
2222
#include "FormatTokenLexer.h"
23+
#include "IncludesSeparator.h"
2324
#include "IntegerLiteralSeparatorFixer.h"
2425
#include "NamespaceEndCommentsFixer.h"
2526
#include "ObjCPropertyAttributeOrderFixer.h"
@@ -995,6 +996,7 @@ template <> struct MappingTraits<FormatStyle> {
995996
IO.mapOptional("DisableFormat", Style.DisableFormat);
996997
IO.mapOptional("EmptyLineAfterAccessModifier",
997998
Style.EmptyLineAfterAccessModifier);
999+
IO.mapOptional("EmptyLinesAfterIncludes", Style.EmptyLinesAfterIncludes);
9981000
IO.mapOptional("EmptyLineBeforeAccessModifier",
9991001
Style.EmptyLineBeforeAccessModifier);
10001002
IO.mapOptional("ExperimentalAutoDetectBinPacking",
@@ -1502,6 +1504,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
15021504
LLVMStyle.DerivePointerAlignment = false;
15031505
LLVMStyle.DisableFormat = false;
15041506
LLVMStyle.EmptyLineAfterAccessModifier = FormatStyle::ELAAMS_Never;
1507+
LLVMStyle.EmptyLinesAfterIncludes = std::nullopt;
15051508
LLVMStyle.EmptyLineBeforeAccessModifier = FormatStyle::ELBAMS_LogicalBlock;
15061509
LLVMStyle.ExperimentalAutoDetectBinPacking = false;
15071510
LLVMStyle.FixNamespaceComments = true;
@@ -3715,6 +3718,12 @@ reformat(const FormatStyle &Style, StringRef Code,
37153718
});
37163719
}
37173720

3721+
if (Style.EmptyLinesAfterIncludes.has_value()) {
3722+
Passes.emplace_back([&](const Environment &Env) {
3723+
return IncludesSeparator(Env, Expanded).process();
3724+
});
3725+
}
3726+
37183727
if (Style.Language == FormatStyle::LK_ObjC &&
37193728
!Style.ObjCPropertyAttributeOrder.empty()) {
37203729
Passes.emplace_back([&](const Environment &Env) {
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
//===--- IncludesSeparator.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 IncludesSeparator, a TokenAnalyzer that inserts
11+
/// new lines or removes empty lines after an include area.
12+
/// An includes area is a list of consecutive include statements.
13+
///
14+
//===----------------------------------------------------------------------===//
15+
16+
#include "IncludesSeparator.h"
17+
#include "TokenAnnotator.h"
18+
#define DEBUG_TYPE "includes-separator"
19+
20+
namespace {
21+
bool isConditionalCompilationStart(const clang::format::AnnotatedLine &Line) {
22+
if (!Line.First)
23+
return false;
24+
const auto *NextToken = Line.First->getNextNonComment();
25+
return Line.First->is(clang::tok::hash) && NextToken &&
26+
NextToken->isOneOf(clang::tok::pp_if, clang::tok::pp_ifdef,
27+
clang::tok::pp_ifndef, clang::tok::pp_defined);
28+
}
29+
30+
bool isConditionalCompilationEnd(const clang::format::AnnotatedLine &Line) {
31+
if (!Line.First)
32+
return false;
33+
const auto *NextToken = Line.First->getNextNonComment();
34+
return Line.First->is(clang::tok::hash) && NextToken &&
35+
NextToken->is(clang::tok::pp_endif);
36+
}
37+
38+
bool isConditionalCompilationStatement(
39+
const clang::format::AnnotatedLine &Line) {
40+
if (!Line.First)
41+
return false;
42+
const auto *NextToken = Line.First->getNextNonComment();
43+
return Line.First->is(clang::tok::hash) && NextToken &&
44+
NextToken->isOneOf(clang::tok::pp_if, clang::tok::pp_ifdef,
45+
clang::tok::pp_ifndef, clang::tok::pp_elif,
46+
clang::tok::pp_elifdef, clang::tok::pp_elifndef,
47+
clang::tok::pp_else, clang::tok::pp_defined,
48+
clang::tok::pp_endif);
49+
}
50+
51+
bool isCCOnlyWithIncludes(
52+
const llvm::SmallVectorImpl<clang::format::AnnotatedLine *> &Lines,
53+
unsigned StartIdx) {
54+
int CCLevel = 0;
55+
for (unsigned I = StartIdx; I < Lines.size(); ++I) {
56+
const auto &CurrentLine = *Lines[I];
57+
if (isConditionalCompilationStart(CurrentLine))
58+
CCLevel++;
59+
60+
if (isConditionalCompilationEnd(CurrentLine))
61+
CCLevel--;
62+
63+
if (CCLevel == 0)
64+
break;
65+
66+
if (!(CurrentLine.isInclude() ||
67+
isConditionalCompilationStatement(CurrentLine))) {
68+
return false;
69+
}
70+
}
71+
return true;
72+
}
73+
74+
unsigned getEndOfCCBlock(
75+
const llvm::SmallVectorImpl<clang::format::AnnotatedLine *> &Lines,
76+
unsigned StartIdx) {
77+
int CCLevel = 0;
78+
unsigned I = StartIdx;
79+
for (; I < Lines.size(); ++I) {
80+
const auto &CurrentLine = *Lines[I];
81+
if (isConditionalCompilationStart(CurrentLine))
82+
CCLevel++;
83+
84+
if (isConditionalCompilationEnd(CurrentLine))
85+
CCLevel--;
86+
87+
if (CCLevel == 0)
88+
break;
89+
}
90+
return I;
91+
}
92+
} // namespace
93+
94+
namespace clang {
95+
namespace format {
96+
std::pair<tooling::Replacements, unsigned>
97+
IncludesSeparator::analyze(TokenAnnotator &Annotator,
98+
SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
99+
FormatTokenLexer &Tokens) {
100+
assert(Style.EmptyLinesAfterIncludes.has_value());
101+
AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
102+
tooling::Replacements Result;
103+
separateIncludes(AnnotatedLines, Result, Tokens);
104+
return {Result, 0};
105+
}
106+
107+
void IncludesSeparator::separateIncludes(
108+
SmallVectorImpl<AnnotatedLine *> &Lines, tooling::Replacements &Result,
109+
FormatTokenLexer &Tokens) {
110+
const unsigned NewlineCount =
111+
std::min(Style.MaxEmptyLinesToKeep, *Style.EmptyLinesAfterIncludes) + 1;
112+
WhitespaceManager Whitespaces(
113+
Env.getSourceManager(), Style,
114+
Style.LineEnding > FormatStyle::LE_CRLF
115+
? WhitespaceManager::inputUsesCRLF(
116+
Env.getSourceManager().getBufferData(Env.getFileID()),
117+
Style.LineEnding == FormatStyle::LE_DeriveCRLF)
118+
: Style.LineEnding == FormatStyle::LE_CRLF);
119+
120+
bool InIncludeArea = false;
121+
for (unsigned I = 0; I < Lines.size(); ++I) {
122+
const auto &CurrentLine = *Lines[I];
123+
124+
if (InIncludeArea) {
125+
if (CurrentLine.isInclude())
126+
continue;
127+
128+
if (isConditionalCompilationStart(CurrentLine)) {
129+
const bool CCWithOnlyIncludes = isCCOnlyWithIncludes(Lines, I);
130+
I = getEndOfCCBlock(Lines, I);
131+
132+
// Conditional compilation blocks that only contain
133+
// include statements are considered part of the include area.
134+
if (CCWithOnlyIncludes)
135+
continue;
136+
}
137+
138+
if (!CurrentLine.First->is(tok::eof) && CurrentLine.Affected) {
139+
Whitespaces.replaceWhitespace(*CurrentLine.First, NewlineCount,
140+
CurrentLine.First->OriginalColumn,
141+
CurrentLine.First->OriginalColumn);
142+
}
143+
InIncludeArea = false;
144+
} else {
145+
if (CurrentLine.isInclude())
146+
InIncludeArea = true;
147+
}
148+
}
149+
150+
for (const auto &R : Whitespaces.generateReplacements()) {
151+
// The add method returns an Error instance which simulates program exit
152+
// code through overloading boolean operator, thus false here indicates
153+
// success.
154+
if (Result.add(R))
155+
return;
156+
}
157+
}
158+
} // namespace format
159+
} // namespace clang
160+
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//===--- IncludesSeparator.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 IncludesSeparator, a TokenAnalyzer that inserts
11+
/// new lines or removes empty lines after an includes area.
12+
/// An includes area is a list of consecutive include statements.
13+
///
14+
//===----------------------------------------------------------------------===//
15+
16+
#ifndef LLVM_CLANG_LIB_FORMAT_INCLUDESSEPARATOR_H
17+
#define LLVM_CLANG_LIB_FORMAT_INCLUDESSEPARATOR_H
18+
19+
#include "TokenAnalyzer.h"
20+
#include "WhitespaceManager.h"
21+
22+
namespace clang {
23+
namespace format {
24+
class IncludesSeparator : public TokenAnalyzer {
25+
public:
26+
IncludesSeparator(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 separateIncludes(SmallVectorImpl<AnnotatedLine *> &Lines,
36+
tooling::Replacements &Result,
37+
FormatTokenLexer &Tokens);
38+
};
39+
} // namespace format
40+
} // namespace clang
41+
42+
#endif

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 {

clang/unittests/Format/ConfigParseTest.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,8 @@ TEST(ConfigParseTest, ParsesConfiguration) {
10051005
FormatStyle::SDS_Leave);
10061006
CHECK_PARSE("SeparateDefinitionBlocks: Never", SeparateDefinitionBlocks,
10071007
FormatStyle::SDS_Never);
1008+
1009+
CHECK_PARSE("EmptyLinesAfterIncludes: 2", EmptyLinesAfterIncludes, 2);
10081010
}
10091011

10101012
TEST(ConfigParseTest, ParsesConfigurationWithLanguages) {

0 commit comments

Comments
 (0)