llvm-project
261 строка · 10.2 Кб
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#include "llvm/Support/Debug.h"
18#define DEBUG_TYPE "definition-block-separator"
19
20namespace clang {
21namespace format {
22std::pair<tooling::Replacements, unsigned> DefinitionBlockSeparator::analyze(
23TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
24FormatTokenLexer &Tokens) {
25assert(Style.SeparateDefinitionBlocks != FormatStyle::SDS_Leave);
26AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
27tooling::Replacements Result;
28separateBlocks(AnnotatedLines, Result, Tokens);
29return {Result, 0};
30}
31
32void DefinitionBlockSeparator::separateBlocks(
33SmallVectorImpl<AnnotatedLine *> &Lines, tooling::Replacements &Result,
34FormatTokenLexer &Tokens) {
35const bool IsNeverStyle =
36Style.SeparateDefinitionBlocks == FormatStyle::SDS_Never;
37const AdditionalKeywords &ExtraKeywords = Tokens.getKeywords();
38auto GetBracketLevelChange = [](const FormatToken *Tok) {
39if (Tok->isOneOf(tok::l_brace, tok::l_paren, tok::l_square))
40return 1;
41if (Tok->isOneOf(tok::r_brace, tok::r_paren, tok::r_square))
42return -1;
43return 0;
44};
45auto LikelyDefinition = [&](const AnnotatedLine *Line,
46bool ExcludeEnum = false) {
47if ((Line->MightBeFunctionDecl && Line->mightBeFunctionDefinition()) ||
48Line->startsWithNamespace()) {
49return true;
50}
51int BracketLevel = 0;
52for (const FormatToken *CurrentToken = Line->First; CurrentToken;
53CurrentToken = CurrentToken->Next) {
54if (BracketLevel == 0) {
55if (CurrentToken->isOneOf(tok::kw_class, tok::kw_struct,
56tok::kw_union) ||
57(Style.isJavaScript() &&
58CurrentToken->is(ExtraKeywords.kw_function))) {
59return true;
60}
61if (!ExcludeEnum && CurrentToken->is(tok::kw_enum))
62return true;
63}
64BracketLevel += GetBracketLevelChange(CurrentToken);
65}
66return false;
67};
68unsigned NewlineCount =
69(Style.SeparateDefinitionBlocks == FormatStyle::SDS_Always ? 1 : 0) + 1;
70WhitespaceManager Whitespaces(
71Env.getSourceManager(), Style,
72Style.LineEnding > FormatStyle::LE_CRLF
73? WhitespaceManager::inputUsesCRLF(
74Env.getSourceManager().getBufferData(Env.getFileID()),
75Style.LineEnding == FormatStyle::LE_DeriveCRLF)
76: Style.LineEnding == FormatStyle::LE_CRLF);
77for (unsigned I = 0; I < Lines.size(); ++I) {
78const auto &CurrentLine = Lines[I];
79if (CurrentLine->InPPDirective)
80continue;
81FormatToken *TargetToken = nullptr;
82AnnotatedLine *TargetLine;
83auto OpeningLineIndex = CurrentLine->MatchingOpeningBlockLineIndex;
84AnnotatedLine *OpeningLine = nullptr;
85const auto IsAccessSpecifierToken = [](const FormatToken *Token) {
86return Token->isAccessSpecifier() || Token->isObjCAccessSpecifier();
87};
88const auto InsertReplacement = [&](const int NewlineToInsert) {
89assert(TargetLine);
90assert(TargetToken);
91
92// Do not handle EOF newlines.
93if (TargetToken->is(tok::eof))
94return;
95if (IsAccessSpecifierToken(TargetToken) ||
96(OpeningLineIndex > 0 &&
97IsAccessSpecifierToken(Lines[OpeningLineIndex - 1]->First))) {
98return;
99}
100if (!TargetLine->Affected)
101return;
102Whitespaces.replaceWhitespace(*TargetToken, NewlineToInsert,
103TargetToken->OriginalColumn,
104TargetToken->OriginalColumn);
105};
106const auto IsPPConditional = [&](const size_t LineIndex) {
107const auto &Line = Lines[LineIndex];
108return Line->First->is(tok::hash) && Line->First->Next &&
109Line->First->Next->isOneOf(tok::pp_if, tok::pp_ifdef, tok::pp_else,
110tok::pp_ifndef, tok::pp_elifndef,
111tok::pp_elifdef, tok::pp_elif,
112tok::pp_endif);
113};
114const auto FollowingOtherOpening = [&]() {
115return OpeningLineIndex == 0 ||
116Lines[OpeningLineIndex - 1]->Last->opensScope() ||
117IsPPConditional(OpeningLineIndex - 1);
118};
119const auto HasEnumOnLine = [&]() {
120bool FoundEnumKeyword = false;
121int BracketLevel = 0;
122for (const FormatToken *CurrentToken = CurrentLine->First; CurrentToken;
123CurrentToken = CurrentToken->Next) {
124if (BracketLevel == 0) {
125if (CurrentToken->is(tok::kw_enum))
126FoundEnumKeyword = true;
127else if (FoundEnumKeyword && CurrentToken->is(tok::l_brace))
128return true;
129}
130BracketLevel += GetBracketLevelChange(CurrentToken);
131}
132return FoundEnumKeyword && I + 1 < Lines.size() &&
133Lines[I + 1]->First->is(tok::l_brace);
134};
135
136bool IsDefBlock = false;
137const auto MayPrecedeDefinition = [&](const int Direction = -1) {
138assert(Direction >= -1);
139assert(Direction <= 1);
140const size_t OperateIndex = OpeningLineIndex + Direction;
141assert(OperateIndex < Lines.size());
142const auto &OperateLine = Lines[OperateIndex];
143if (LikelyDefinition(OperateLine))
144return false;
145
146if (const auto *Tok = OperateLine->First;
147Tok->is(tok::comment) && !isClangFormatOn(Tok->TokenText)) {
148return true;
149}
150
151// A single line identifier that is not in the last line.
152if (OperateLine->First->is(tok::identifier) &&
153OperateLine->First == OperateLine->Last &&
154OperateIndex + 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.
160AnnotatedLine *NextLine = Lines[OperateIndex + 1];
161if (NextLine->MightBeFunctionDecl &&
162NextLine->mightBeFunctionDefinition() &&
163NextLine->First->NewlinesBefore == 1 &&
164OperateLine->First->is(TT_FunctionLikeOrFreestandingMacro)) {
165return true;
166}
167}
168
169if (Style.isCSharp() && OperateLine->First->is(TT_AttributeSquare))
170return true;
171return false;
172};
173
174if (HasEnumOnLine() &&
175!LikelyDefinition(CurrentLine, /*ExcludeEnum=*/true)) {
176// We have no scope opening/closing information for enum.
177IsDefBlock = true;
178OpeningLineIndex = I;
179while (OpeningLineIndex > 0 && MayPrecedeDefinition())
180--OpeningLineIndex;
181OpeningLine = Lines[OpeningLineIndex];
182TargetLine = OpeningLine;
183TargetToken = TargetLine->First;
184if (!FollowingOtherOpening())
185InsertReplacement(NewlineCount);
186else if (IsNeverStyle)
187InsertReplacement(OpeningLineIndex != 0);
188TargetLine = CurrentLine;
189TargetToken = TargetLine->First;
190while (TargetToken && TargetToken->isNot(tok::r_brace))
191TargetToken = TargetToken->Next;
192if (!TargetToken)
193while (I < Lines.size() && Lines[I]->First->isNot(tok::r_brace))
194++I;
195} else if (CurrentLine->First->closesScope()) {
196if (OpeningLineIndex > Lines.size())
197continue;
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
200// misrecognition.
201if (OpeningLineIndex > 0 &&
202Lines[OpeningLineIndex]->First->is(tok::l_brace) &&
203Lines[OpeningLineIndex - 1]->Last->isNot(tok::l_brace)) {
204--OpeningLineIndex;
205}
206OpeningLine = Lines[OpeningLineIndex];
207// Closing a function definition.
208if (LikelyDefinition(OpeningLine)) {
209IsDefBlock = true;
210while (OpeningLineIndex > 0 && MayPrecedeDefinition())
211--OpeningLineIndex;
212OpeningLine = Lines[OpeningLineIndex];
213TargetLine = OpeningLine;
214TargetToken = TargetLine->First;
215if (!FollowingOtherOpening()) {
216// Avoid duplicated replacement.
217if (TargetToken->isNot(tok::l_brace))
218InsertReplacement(NewlineCount);
219} else if (IsNeverStyle) {
220InsertReplacement(OpeningLineIndex != 0);
221}
222}
223}
224
225// Not the last token.
226if (IsDefBlock && I + 1 < Lines.size()) {
227OpeningLineIndex = I + 1;
228TargetLine = Lines[OpeningLineIndex];
229TargetToken = TargetLine->First;
230
231// No empty line for continuously closing scopes. The token will be
232// handled in another case if the line following is opening a
233// definition.
234if (!TargetToken->closesScope() && !IsPPConditional(OpeningLineIndex)) {
235// Check whether current line may precede a definition line.
236while (OpeningLineIndex + 1 < Lines.size() &&
237MayPrecedeDefinition(/*Direction=*/0)) {
238++OpeningLineIndex;
239}
240TargetLine = Lines[OpeningLineIndex];
241if (!LikelyDefinition(TargetLine)) {
242OpeningLineIndex = I + 1;
243TargetLine = Lines[I + 1];
244TargetToken = TargetLine->First;
245InsertReplacement(NewlineCount);
246}
247} else if (IsNeverStyle) {
248InsertReplacement(/*NewlineToInsert=*/1);
249}
250}
251}
252for (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
255// success.
256if (Result.add(R))
257return;
258}
259}
260} // namespace format
261} // namespace clang
262