1//===--- DefinitionBlockSeparator.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 DefinitionBlockSeparator, a TokenAnalyzer that inserts
11/// or removes empty lines separating definition blocks like classes, structs,
12/// functions, enums, and namespaces in between.
13///
14//===----------------------------------------------------------------------===//
15
16#include "DefinitionBlockSeparator.h"
17#define DEBUG_TYPE "definition-block-separator"
18
19namespace clang {
20namespace format {
21std::pair<tooling::Replacements, unsigned> DefinitionBlockSeparator::analyze(
22 TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
23 FormatTokenLexer &Tokens) {
24 assert(Style.SeparateDefinitionBlocks != FormatStyle::SDS_Leave);
25 AffectedRangeMgr.computeAffectedLines(Lines&: AnnotatedLines);
26 tooling::Replacements Result;
27 separateBlocks(Lines&: AnnotatedLines, Result, Tokens);
28 return {Result, 0};
29}
30
31void DefinitionBlockSeparator::separateBlocks(
32 SmallVectorImpl<AnnotatedLine *> &Lines, tooling::Replacements &Result,
33 FormatTokenLexer &Tokens) {
34 const bool IsNeverStyle =
35 Style.SeparateDefinitionBlocks == FormatStyle::SDS_Never;
36 const AdditionalKeywords &ExtraKeywords = Tokens.getKeywords();
37 auto GetBracketLevelChange = [](const FormatToken *Tok) {
38 if (Tok->isOneOf(K1: tok::l_brace, K2: tok::l_paren, Ks: tok::l_square))
39 return 1;
40 if (Tok->isOneOf(K1: tok::r_brace, K2: tok::r_paren, Ks: tok::r_square))
41 return -1;
42 return 0;
43 };
44 auto LikelyDefinition = [&](const AnnotatedLine *Line,
45 bool ExcludeEnum = false) {
46 if ((Line->MightBeFunctionDecl && Line->mightBeFunctionDefinition()) ||
47 Line->startsWithNamespace()) {
48 return true;
49 }
50 int BracketLevel = 0;
51 for (const FormatToken *CurrentToken = Line->First; CurrentToken;
52 CurrentToken = CurrentToken->Next) {
53 if (BracketLevel == 0) {
54 if (CurrentToken->isOneOf(K1: tok::kw_class, K2: tok::kw_struct,
55 Ks: tok::kw_union) ||
56 (Style.isJavaScript() &&
57 CurrentToken->is(II: ExtraKeywords.kw_function))) {
58 return true;
59 }
60 if (!ExcludeEnum && CurrentToken->is(Kind: tok::kw_enum))
61 return true;
62 }
63 BracketLevel += GetBracketLevelChange(CurrentToken);
64 }
65 return false;
66 };
67 unsigned NewlineCount =
68 (Style.SeparateDefinitionBlocks == FormatStyle::SDS_Always ? 1 : 0) + 1;
69
70 Style.MaxEmptyLinesToKeep =
71 std::max(a: Style.MaxEmptyLinesToKeep, b: NewlineCount - 1);
72
73 WhitespaceManager Whitespaces(
74 Env.getSourceManager(), Style,
75 Style.LineEnding > FormatStyle::LE_CRLF
76 ? WhitespaceManager::inputUsesCRLF(
77 Text: Env.getSourceManager().getBufferData(FID: Env.getFileID()),
78 DefaultToCRLF: Style.LineEnding == FormatStyle::LE_DeriveCRLF)
79 : Style.LineEnding == FormatStyle::LE_CRLF);
80 for (unsigned I = 0; I < Lines.size(); ++I) {
81 const auto &CurrentLine = Lines[I];
82 if (CurrentLine->InPPDirective)
83 continue;
84 FormatToken *TargetToken = nullptr;
85 AnnotatedLine *TargetLine;
86 auto OpeningLineIndex = CurrentLine->MatchingOpeningBlockLineIndex;
87 AnnotatedLine *OpeningLine = nullptr;
88 const auto IsAccessSpecifierToken = [](const FormatToken *Token) {
89 return Token->isAccessSpecifier() || Token->isObjCAccessSpecifier();
90 };
91 const auto InsertReplacement = [&](const int NewlineToInsert) {
92 assert(TargetLine);
93 assert(TargetToken);
94
95 // Lines should not be added in the disabled region.
96 if (TargetToken->is(Kind: tok::comment) &&
97 isClangFormatOn(Comment: TargetToken->TokenText)) {
98 return;
99 }
100 // Do not handle EOF newlines.
101 if (TargetToken->is(Kind: tok::eof))
102 return;
103 if (IsAccessSpecifierToken(TargetToken) ||
104 (OpeningLineIndex > 0 &&
105 IsAccessSpecifierToken(Lines[OpeningLineIndex - 1]->First))) {
106 return;
107 }
108 if (!TargetLine->Affected)
109 return;
110 Whitespaces.replaceWhitespace(Tok&: *TargetToken, Newlines: NewlineToInsert,
111 Spaces: TargetToken->OriginalColumn,
112 StartOfTokenColumn: TargetToken->OriginalColumn);
113 };
114 const auto IsPPConditional = [&](const size_t LineIndex) {
115 const auto &Line = Lines[LineIndex];
116 return Line->First->is(Kind: tok::hash) && Line->First->Next &&
117 Line->First->Next->isOneOf(K1: tok::pp_if, K2: tok::pp_ifdef, Ks: tok::pp_else,
118 Ks: tok::pp_ifndef, Ks: tok::pp_elifndef,
119 Ks: tok::pp_elifdef, Ks: tok::pp_elif,
120 Ks: tok::pp_endif);
121 };
122 const auto FollowingOtherOpening = [&]() {
123 return OpeningLineIndex == 0 ||
124 Lines[OpeningLineIndex - 1]->Last->opensScope() ||
125 IsPPConditional(OpeningLineIndex - 1);
126 };
127 const auto HasEnumOnLine = [&]() {
128 bool FoundEnumKeyword = false;
129 int BracketLevel = 0;
130 for (const FormatToken *CurrentToken = CurrentLine->First; CurrentToken;
131 CurrentToken = CurrentToken->Next) {
132 if (BracketLevel == 0) {
133 if (CurrentToken->is(Kind: tok::kw_enum))
134 FoundEnumKeyword = true;
135 else if (FoundEnumKeyword && CurrentToken->is(Kind: tok::l_brace))
136 return true;
137 }
138 BracketLevel += GetBracketLevelChange(CurrentToken);
139 }
140 return FoundEnumKeyword && I + 1 < Lines.size() &&
141 Lines[I + 1]->First->is(Kind: tok::l_brace);
142 };
143
144 bool IsDefBlock = false;
145 const auto MayPrecedeDefinition = [&](const int Direction = -1) {
146 assert(Direction >= -1);
147 assert(Direction <= 1);
148
149 if (Lines[OpeningLineIndex]->First->is(TT: TT_CSharpGenericTypeConstraint))
150 return true;
151
152 const size_t OperateIndex = OpeningLineIndex + Direction;
153 assert(OperateIndex < Lines.size());
154 const auto &OperateLine = Lines[OperateIndex];
155 if (LikelyDefinition(OperateLine))
156 return false;
157
158 const auto *NextLine =
159 OperateIndex + 1 < Lines.size() ? Lines[OperateIndex + 1] : nullptr;
160
161 if (const auto *Tok = OperateLine->First;
162 Tok->is(Kind: tok::comment) && !isClangFormatOn(Comment: Tok->TokenText)) {
163 const bool IsEndComment = Tok->NewlinesBefore == 1 && NextLine &&
164 NextLine->First->NewlinesBefore > 1;
165 if (!IsEndComment)
166 return true;
167 }
168
169 // A single line identifier that is not in the last line.
170 if (OperateLine->First->is(Kind: tok::identifier) &&
171 OperateLine->First == OperateLine->Last && NextLine) {
172 // UnwrappedLineParser's recognition of free-standing macro like
173 // Q_OBJECT may also recognize some uppercased type names that may be
174 // used as return type as that kind of macros, which is a bit hard to
175 // distinguish one from another purely from token patterns. Here, we
176 // try not to add new lines below those identifiers.
177 if (NextLine->MightBeFunctionDecl &&
178 NextLine->mightBeFunctionDefinition() &&
179 NextLine->First->NewlinesBefore == 1 &&
180 OperateLine->First->is(TT: TT_FunctionLikeOrFreestandingMacro)) {
181 return true;
182 }
183 }
184
185 if (Style.isCSharp() && OperateLine->First->is(TT: TT_AttributeLSquare))
186 return true;
187 return false;
188 };
189
190 if (HasEnumOnLine() &&
191 !LikelyDefinition(CurrentLine, /*ExcludeEnum=*/true)) {
192 // We have no scope opening/closing information for enum.
193 IsDefBlock = true;
194 OpeningLineIndex = I;
195 while (OpeningLineIndex > 0 && MayPrecedeDefinition())
196 --OpeningLineIndex;
197 OpeningLine = Lines[OpeningLineIndex];
198 TargetLine = OpeningLine;
199 TargetToken = TargetLine->First;
200 if (!FollowingOtherOpening())
201 InsertReplacement(NewlineCount);
202 else if (IsNeverStyle)
203 InsertReplacement(OpeningLineIndex != 0);
204 TargetLine = CurrentLine;
205 TargetToken = TargetLine->First;
206 while (TargetToken && TargetToken->isNot(Kind: tok::r_brace))
207 TargetToken = TargetToken->Next;
208 if (!TargetToken)
209 while (I < Lines.size() && Lines[I]->First->isNot(Kind: tok::r_brace))
210 ++I;
211 } else if (CurrentLine->First->closesScope()) {
212 if (OpeningLineIndex > Lines.size())
213 continue;
214 // A function try block should be together.
215 if (CurrentLine->First->startsSequence(K1: tok::r_brace, Tokens: tok::kw_catch))
216 continue;
217 // Handling the case that opening brace has its own line, with checking
218 // whether the last line already had an opening brace to guard against
219 // misrecognition.
220 if (OpeningLineIndex > 0 &&
221 Lines[OpeningLineIndex]->First->is(Kind: tok::l_brace) &&
222 Lines[OpeningLineIndex - 1]->Last->isNot(Kind: tok::l_brace)) {
223 --OpeningLineIndex;
224 }
225 OpeningLine = Lines[OpeningLineIndex];
226 // Closing a function definition.
227 if (LikelyDefinition(OpeningLine)) {
228 IsDefBlock = true;
229 while (OpeningLineIndex > 0 && MayPrecedeDefinition())
230 --OpeningLineIndex;
231 OpeningLine = Lines[OpeningLineIndex];
232 TargetLine = OpeningLine;
233 TargetToken = TargetLine->First;
234 if (!FollowingOtherOpening()) {
235 // Avoid duplicated replacement.
236 if (TargetToken->isNot(Kind: tok::l_brace))
237 InsertReplacement(NewlineCount);
238 } else if (IsNeverStyle) {
239 InsertReplacement(OpeningLineIndex != 0);
240 }
241 }
242 }
243
244 // Not the last token.
245 if (IsDefBlock && I + 1 < Lines.size()) {
246 OpeningLineIndex = I + 1;
247 TargetLine = Lines[OpeningLineIndex];
248 TargetToken = TargetLine->First;
249
250 // No empty line for continuously closing scopes. The token will be
251 // handled in another case if the line following is opening a
252 // definition.
253 if (!TargetToken->closesScope() && !IsPPConditional(OpeningLineIndex)) {
254 // Check whether current line may precede a definition line.
255 while (OpeningLineIndex + 1 < Lines.size() &&
256 MayPrecedeDefinition(/*Direction=*/0)) {
257 ++OpeningLineIndex;
258 }
259 TargetLine = Lines[OpeningLineIndex];
260 if (!LikelyDefinition(TargetLine)) {
261 OpeningLineIndex = I + 1;
262 TargetLine = Lines[I + 1];
263 TargetToken = TargetLine->First;
264 InsertReplacement(NewlineCount);
265 }
266 } else if (IsNeverStyle) {
267 InsertReplacement(/*NewlineToInsert=*/1);
268 }
269 }
270 }
271 for (const auto &R : Whitespaces.generateReplacements()) {
272 // The add method returns an Error instance which simulates program exit
273 // code through overloading boolean operator, thus false here indicates
274 // success.
275 if (Result.add(R))
276 return;
277 }
278}
279} // namespace format
280} // namespace clang
281