1 //===--- DefinitionBlockSeparator.cpp ---------------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
10 /// This file implements DefinitionBlockSeparator, a TokenAnalyzer that inserts
11 /// or removes empty lines separating definition blocks like classes, structs,
12 /// functions, enums, and namespaces in between.
14 //===----------------------------------------------------------------------===//
16 #include "DefinitionBlockSeparator.h"
17 #include "llvm/Support/Debug.h"
18 #define DEBUG_TYPE "definition-block-separator"
22 std::pair
<tooling::Replacements
, unsigned> DefinitionBlockSeparator::analyze(
23 TokenAnnotator
&Annotator
, SmallVectorImpl
<AnnotatedLine
*> &AnnotatedLines
,
24 FormatTokenLexer
&Tokens
) {
25 assert(Style
.SeparateDefinitionBlocks
!= FormatStyle::SDS_Leave
);
26 AffectedRangeMgr
.computeAffectedLines(AnnotatedLines
);
27 tooling::Replacements Result
;
28 separateBlocks(AnnotatedLines
, Result
, Tokens
);
32 void DefinitionBlockSeparator::separateBlocks(
33 SmallVectorImpl
<AnnotatedLine
*> &Lines
, tooling::Replacements
&Result
,
34 FormatTokenLexer
&Tokens
) {
35 const bool IsNeverStyle
=
36 Style
.SeparateDefinitionBlocks
== FormatStyle::SDS_Never
;
37 const AdditionalKeywords
&ExtraKeywords
= Tokens
.getKeywords();
38 auto GetBracketLevelChange
= [](const FormatToken
*Tok
) {
39 if (Tok
->isOneOf(tok::l_brace
, tok::l_paren
, tok::l_square
))
41 if (Tok
->isOneOf(tok::r_brace
, tok::r_paren
, tok::r_square
))
45 auto LikelyDefinition
= [&](const AnnotatedLine
*Line
,
46 bool ExcludeEnum
= false) {
47 if ((Line
->MightBeFunctionDecl
&& Line
->mightBeFunctionDefinition()) ||
48 Line
->startsWithNamespace()) {
52 for (const FormatToken
*CurrentToken
= Line
->First
; CurrentToken
;
53 CurrentToken
= CurrentToken
->Next
) {
54 if (BracketLevel
== 0) {
55 if (CurrentToken
->isOneOf(tok::kw_class
, tok::kw_struct
,
57 (Style
.isJavaScript() &&
58 CurrentToken
->is(ExtraKeywords
.kw_function
))) {
61 if (!ExcludeEnum
&& CurrentToken
->is(tok::kw_enum
))
64 BracketLevel
+= GetBracketLevelChange(CurrentToken
);
68 unsigned NewlineCount
=
69 (Style
.SeparateDefinitionBlocks
== FormatStyle::SDS_Always
? 1 : 0) + 1;
70 WhitespaceManager
Whitespaces(
71 Env
.getSourceManager(), Style
,
72 Style
.LineEnding
> FormatStyle::LE_CRLF
73 ? WhitespaceManager::inputUsesCRLF(
74 Env
.getSourceManager().getBufferData(Env
.getFileID()),
75 Style
.LineEnding
== FormatStyle::LE_DeriveCRLF
)
76 : Style
.LineEnding
== FormatStyle::LE_CRLF
);
77 for (unsigned I
= 0; I
< Lines
.size(); ++I
) {
78 const auto &CurrentLine
= Lines
[I
];
79 if (CurrentLine
->InPPDirective
)
81 FormatToken
*TargetToken
= nullptr;
82 AnnotatedLine
*TargetLine
;
83 auto OpeningLineIndex
= CurrentLine
->MatchingOpeningBlockLineIndex
;
84 AnnotatedLine
*OpeningLine
= nullptr;
85 const auto IsAccessSpecifierToken
= [](const FormatToken
*Token
) {
86 return Token
->isAccessSpecifier() || Token
->isObjCAccessSpecifier();
88 const auto InsertReplacement
= [&](const int NewlineToInsert
) {
92 // Do not handle EOF newlines.
93 if (TargetToken
->is(tok::eof
))
95 if (IsAccessSpecifierToken(TargetToken
) ||
96 (OpeningLineIndex
> 0 &&
97 IsAccessSpecifierToken(Lines
[OpeningLineIndex
- 1]->First
))) {
100 if (!TargetLine
->Affected
)
102 Whitespaces
.replaceWhitespace(*TargetToken
, NewlineToInsert
,
103 TargetToken
->OriginalColumn
,
104 TargetToken
->OriginalColumn
);
106 const auto IsPPConditional
= [&](const size_t LineIndex
) {
107 const auto &Line
= Lines
[LineIndex
];
108 return Line
->First
->is(tok::hash
) && Line
->First
->Next
&&
109 Line
->First
->Next
->isOneOf(tok::pp_if
, tok::pp_ifdef
, tok::pp_else
,
110 tok::pp_ifndef
, tok::pp_elifndef
,
111 tok::pp_elifdef
, tok::pp_elif
,
114 const auto FollowingOtherOpening
= [&]() {
115 return OpeningLineIndex
== 0 ||
116 Lines
[OpeningLineIndex
- 1]->Last
->opensScope() ||
117 IsPPConditional(OpeningLineIndex
- 1);
119 const auto HasEnumOnLine
= [&]() {
120 bool FoundEnumKeyword
= false;
121 int BracketLevel
= 0;
122 for (const FormatToken
*CurrentToken
= CurrentLine
->First
; CurrentToken
;
123 CurrentToken
= CurrentToken
->Next
) {
124 if (BracketLevel
== 0) {
125 if (CurrentToken
->is(tok::kw_enum
))
126 FoundEnumKeyword
= true;
127 else if (FoundEnumKeyword
&& CurrentToken
->is(tok::l_brace
))
130 BracketLevel
+= GetBracketLevelChange(CurrentToken
);
132 return FoundEnumKeyword
&& I
+ 1 < Lines
.size() &&
133 Lines
[I
+ 1]->First
->is(tok::l_brace
);
136 bool IsDefBlock
= false;
137 const auto MayPrecedeDefinition
= [&](const int Direction
= -1) {
138 assert(Direction
>= -1);
139 assert(Direction
<= 1);
140 const size_t OperateIndex
= OpeningLineIndex
+ Direction
;
141 assert(OperateIndex
< Lines
.size());
142 const auto &OperateLine
= Lines
[OperateIndex
];
143 if (LikelyDefinition(OperateLine
))
146 if (const auto *Tok
= OperateLine
->First
;
147 Tok
->is(tok::comment
) && !isClangFormatOn(Tok
->TokenText
)) {
151 // A single line identifier that is not in the last line.
152 if (OperateLine
->First
->is(tok::identifier
) &&
153 OperateLine
->First
== OperateLine
->Last
&&
154 OperateIndex
+ 1 < Lines
.size()) {
155 // UnwrappedLineParser's recognition of free-standing macro like
156 // Q_OBJECT may also recognize some uppercased type names that may be
157 // used as return type as that kind of macros, which is a bit hard to
158 // distinguish one from another purely from token patterns. Here, we
159 // try not to add new lines below those identifiers.
160 AnnotatedLine
*NextLine
= Lines
[OperateIndex
+ 1];
161 if (NextLine
->MightBeFunctionDecl
&&
162 NextLine
->mightBeFunctionDefinition() &&
163 NextLine
->First
->NewlinesBefore
== 1 &&
164 OperateLine
->First
->is(TT_FunctionLikeOrFreestandingMacro
)) {
169 if (Style
.isCSharp() && OperateLine
->First
->is(TT_AttributeSquare
))
174 if (HasEnumOnLine() &&
175 !LikelyDefinition(CurrentLine
, /*ExcludeEnum=*/true)) {
176 // We have no scope opening/closing information for enum.
178 OpeningLineIndex
= I
;
179 while (OpeningLineIndex
> 0 && MayPrecedeDefinition())
181 OpeningLine
= Lines
[OpeningLineIndex
];
182 TargetLine
= OpeningLine
;
183 TargetToken
= TargetLine
->First
;
184 if (!FollowingOtherOpening())
185 InsertReplacement(NewlineCount
);
186 else if (IsNeverStyle
)
187 InsertReplacement(OpeningLineIndex
!= 0);
188 TargetLine
= CurrentLine
;
189 TargetToken
= TargetLine
->First
;
190 while (TargetToken
&& TargetToken
->isNot(tok::r_brace
))
191 TargetToken
= TargetToken
->Next
;
193 while (I
< Lines
.size() && Lines
[I
]->First
->isNot(tok::r_brace
))
195 } else if (CurrentLine
->First
->closesScope()) {
196 if (OpeningLineIndex
> Lines
.size())
198 // Handling the case that opening brace has its own line, with checking
199 // whether the last line already had an opening brace to guard against
201 if (OpeningLineIndex
> 0 &&
202 Lines
[OpeningLineIndex
]->First
->is(tok::l_brace
) &&
203 Lines
[OpeningLineIndex
- 1]->Last
->isNot(tok::l_brace
)) {
206 OpeningLine
= Lines
[OpeningLineIndex
];
207 // Closing a function definition.
208 if (LikelyDefinition(OpeningLine
)) {
210 while (OpeningLineIndex
> 0 && MayPrecedeDefinition())
212 OpeningLine
= Lines
[OpeningLineIndex
];
213 TargetLine
= OpeningLine
;
214 TargetToken
= TargetLine
->First
;
215 if (!FollowingOtherOpening()) {
216 // Avoid duplicated replacement.
217 if (TargetToken
->isNot(tok::l_brace
))
218 InsertReplacement(NewlineCount
);
219 } else if (IsNeverStyle
) {
220 InsertReplacement(OpeningLineIndex
!= 0);
225 // Not the last token.
226 if (IsDefBlock
&& I
+ 1 < Lines
.size()) {
227 OpeningLineIndex
= I
+ 1;
228 TargetLine
= Lines
[OpeningLineIndex
];
229 TargetToken
= TargetLine
->First
;
231 // No empty line for continuously closing scopes. The token will be
232 // handled in another case if the line following is opening a
234 if (!TargetToken
->closesScope() && !IsPPConditional(OpeningLineIndex
)) {
235 // Check whether current line may precede a definition line.
236 while (OpeningLineIndex
+ 1 < Lines
.size() &&
237 MayPrecedeDefinition(/*Direction=*/0)) {
240 TargetLine
= Lines
[OpeningLineIndex
];
241 if (!LikelyDefinition(TargetLine
)) {
242 OpeningLineIndex
= I
+ 1;
243 TargetLine
= Lines
[I
+ 1];
244 TargetToken
= TargetLine
->First
;
245 InsertReplacement(NewlineCount
);
247 } else if (IsNeverStyle
) {
248 InsertReplacement(/*NewlineToInsert=*/1);
252 for (const auto &R
: Whitespaces
.generateReplacements()) {
253 // The add method returns an Error instance which simulates program exit
254 // code through overloading boolean operator, thus false here indicates
260 } // namespace format