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 (OperateLine
->First
->is(tok::comment
))
149 // A single line identifier that is not in the last line.
150 if (OperateLine
->First
->is(tok::identifier
) &&
151 OperateLine
->First
== OperateLine
->Last
&&
152 OperateIndex
+ 1 < Lines
.size()) {
153 // UnwrappedLineParser's recognition of free-standing macro like
154 // Q_OBJECT may also recognize some uppercased type names that may be
155 // used as return type as that kind of macros, which is a bit hard to
156 // distinguish one from another purely from token patterns. Here, we
157 // try not to add new lines below those identifiers.
158 AnnotatedLine
*NextLine
= Lines
[OperateIndex
+ 1];
159 if (NextLine
->MightBeFunctionDecl
&&
160 NextLine
->mightBeFunctionDefinition() &&
161 NextLine
->First
->NewlinesBefore
== 1 &&
162 OperateLine
->First
->is(TT_FunctionLikeOrFreestandingMacro
)) {
167 if ((Style
.isCSharp() && OperateLine
->First
->is(TT_AttributeSquare
)))
172 if (HasEnumOnLine() &&
173 !LikelyDefinition(CurrentLine
, /*ExcludeEnum=*/true)) {
174 // We have no scope opening/closing information for enum.
176 OpeningLineIndex
= I
;
177 while (OpeningLineIndex
> 0 && MayPrecedeDefinition())
179 OpeningLine
= Lines
[OpeningLineIndex
];
180 TargetLine
= OpeningLine
;
181 TargetToken
= TargetLine
->First
;
182 if (!FollowingOtherOpening())
183 InsertReplacement(NewlineCount
);
184 else if (IsNeverStyle
)
185 InsertReplacement(OpeningLineIndex
!= 0);
186 TargetLine
= CurrentLine
;
187 TargetToken
= TargetLine
->First
;
188 while (TargetToken
&& !TargetToken
->is(tok::r_brace
))
189 TargetToken
= TargetToken
->Next
;
191 while (I
< Lines
.size() && !Lines
[I
]->First
->is(tok::r_brace
))
193 } else if (CurrentLine
->First
->closesScope()) {
194 if (OpeningLineIndex
> Lines
.size())
196 // Handling the case that opening brace has its own line, with checking
197 // whether the last line already had an opening brace to guard against
199 if (OpeningLineIndex
> 0 &&
200 Lines
[OpeningLineIndex
]->First
->is(tok::l_brace
) &&
201 Lines
[OpeningLineIndex
- 1]->Last
->isNot(tok::l_brace
)) {
204 OpeningLine
= Lines
[OpeningLineIndex
];
205 // Closing a function definition.
206 if (LikelyDefinition(OpeningLine
)) {
208 while (OpeningLineIndex
> 0 && MayPrecedeDefinition())
210 OpeningLine
= Lines
[OpeningLineIndex
];
211 TargetLine
= OpeningLine
;
212 TargetToken
= TargetLine
->First
;
213 if (!FollowingOtherOpening()) {
214 // Avoid duplicated replacement.
215 if (TargetToken
->isNot(tok::l_brace
))
216 InsertReplacement(NewlineCount
);
217 } else if (IsNeverStyle
) {
218 InsertReplacement(OpeningLineIndex
!= 0);
223 // Not the last token.
224 if (IsDefBlock
&& I
+ 1 < Lines
.size()) {
225 OpeningLineIndex
= I
+ 1;
226 TargetLine
= Lines
[OpeningLineIndex
];
227 TargetToken
= TargetLine
->First
;
229 // No empty line for continuously closing scopes. The token will be
230 // handled in another case if the line following is opening a
232 if (!TargetToken
->closesScope() && !IsPPConditional(OpeningLineIndex
)) {
233 // Check whether current line may precede a definition line.
234 while (OpeningLineIndex
+ 1 < Lines
.size() &&
235 MayPrecedeDefinition(/*Direction=*/0)) {
238 TargetLine
= Lines
[OpeningLineIndex
];
239 if (!LikelyDefinition(TargetLine
)) {
240 OpeningLineIndex
= I
+ 1;
241 TargetLine
= Lines
[I
+ 1];
242 TargetToken
= TargetLine
->First
;
243 InsertReplacement(NewlineCount
);
245 } else if (IsNeverStyle
) {
246 InsertReplacement(/*NewlineToInsert=*/1);
250 for (const auto &R
: Whitespaces
.generateReplacements()) {
251 // The add method returns an Error instance which simulates program exit
252 // code through overloading boolean operator, thus false here indicates
258 } // namespace format