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 
20 namespace clang {
21 namespace format {
analyze(TokenAnnotator & Annotator,SmallVectorImpl<AnnotatedLine * > & AnnotatedLines,FormatTokenLexer & Tokens)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);
29   return {Result, 0};
30 }
31 
separateBlocks(SmallVectorImpl<AnnotatedLine * > & Lines,tooling::Replacements & Result,FormatTokenLexer & 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))
40       return 1;
41     if (Tok->isOneOf(tok::r_brace, tok::r_paren, tok::r_square))
42       return -1;
43     return 0;
44   };
45   auto LikelyDefinition = [&](const AnnotatedLine *Line,
46                               bool ExcludeEnum = false) {
47     if ((Line->MightBeFunctionDecl && Line->mightBeFunctionDefinition()) ||
48         Line->startsWithNamespace()) {
49       return true;
50     }
51     int BracketLevel = 0;
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,
56                                   tok::kw_union) ||
57             (Style.isJavaScript() &&
58              CurrentToken->is(ExtraKeywords.kw_function))) {
59           return true;
60         }
61         if (!ExcludeEnum && CurrentToken->is(tok::kw_enum))
62           return true;
63       }
64       BracketLevel += GetBracketLevelChange(CurrentToken);
65     }
66     return false;
67   };
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)
80       continue;
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();
87     };
88     const auto InsertReplacement = [&](const int NewlineToInsert) {
89       assert(TargetLine);
90       assert(TargetToken);
91 
92       // Do not handle EOF newlines.
93       if (TargetToken->is(tok::eof))
94         return;
95       if (IsAccessSpecifierToken(TargetToken) ||
96           (OpeningLineIndex > 0 &&
97            IsAccessSpecifierToken(Lines[OpeningLineIndex - 1]->First))) {
98         return;
99       }
100       if (!TargetLine->Affected)
101         return;
102       Whitespaces.replaceWhitespace(*TargetToken, NewlineToInsert,
103                                     TargetToken->OriginalColumn,
104                                     TargetToken->OriginalColumn);
105     };
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,
112                                         tok::pp_endif);
113     };
114     const auto FollowingOtherOpening = [&]() {
115       return OpeningLineIndex == 0 ||
116              Lines[OpeningLineIndex - 1]->Last->opensScope() ||
117              IsPPConditional(OpeningLineIndex - 1);
118     };
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))
128             return true;
129         }
130         BracketLevel += GetBracketLevelChange(CurrentToken);
131       }
132       return FoundEnumKeyword && I + 1 < Lines.size() &&
133              Lines[I + 1]->First->is(tok::l_brace);
134     };
135 
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))
144         return false;
145 
146       if (const auto *Tok = OperateLine->First;
147           Tok->is(tok::comment) && !isClangFormatOn(Tok->TokenText)) {
148         return true;
149       }
150 
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)) {
165           return true;
166         }
167       }
168 
169       if (Style.isCSharp() && OperateLine->First->is(TT_AttributeSquare))
170         return true;
171       return false;
172     };
173 
174     if (HasEnumOnLine() &&
175         !LikelyDefinition(CurrentLine, /*ExcludeEnum=*/true)) {
176       // We have no scope opening/closing information for enum.
177       IsDefBlock = true;
178       OpeningLineIndex = I;
179       while (OpeningLineIndex > 0 && MayPrecedeDefinition())
180         --OpeningLineIndex;
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;
192       if (!TargetToken)
193         while (I < Lines.size() && Lines[I]->First->isNot(tok::r_brace))
194           ++I;
195     } else if (CurrentLine->First->closesScope()) {
196       if (OpeningLineIndex > Lines.size())
197         continue;
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.
201       if (OpeningLineIndex > 0 &&
202           Lines[OpeningLineIndex]->First->is(tok::l_brace) &&
203           Lines[OpeningLineIndex - 1]->Last->isNot(tok::l_brace)) {
204         --OpeningLineIndex;
205       }
206       OpeningLine = Lines[OpeningLineIndex];
207       // Closing a function definition.
208       if (LikelyDefinition(OpeningLine)) {
209         IsDefBlock = true;
210         while (OpeningLineIndex > 0 && MayPrecedeDefinition())
211           --OpeningLineIndex;
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);
221         }
222       }
223     }
224 
225     // Not the last token.
226     if (IsDefBlock && I + 1 < Lines.size()) {
227       OpeningLineIndex = I + 1;
228       TargetLine = Lines[OpeningLineIndex];
229       TargetToken = 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.
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)) {
238           ++OpeningLineIndex;
239         }
240         TargetLine = Lines[OpeningLineIndex];
241         if (!LikelyDefinition(TargetLine)) {
242           OpeningLineIndex = I + 1;
243           TargetLine = Lines[I + 1];
244           TargetToken = TargetLine->First;
245           InsertReplacement(NewlineCount);
246         }
247       } else if (IsNeverStyle) {
248         InsertReplacement(/*NewlineToInsert=*/1);
249       }
250     }
251   }
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
255     // success.
256     if (Result.add(R))
257       return;
258   }
259 }
260 } // namespace format
261 } // namespace clang
262