xref: /freebsd/contrib/llvm-project/clang/lib/Format/DefinitionBlockSeparator.cpp (revision 770cf0a5f02dc8983a89c6568d741fbc25baa999)
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 
19 namespace clang {
20 namespace format {
21 std::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(AnnotatedLines);
26   tooling::Replacements Result;
27   separateBlocks(AnnotatedLines, Result, Tokens);
28   return {Result, 0};
29 }
30 
31 void 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(tok::l_brace, tok::l_paren, tok::l_square))
39       return 1;
40     if (Tok->isOneOf(tok::r_brace, tok::r_paren, 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(tok::kw_class, tok::kw_struct,
55                                   tok::kw_union) ||
56             (Style.isJavaScript() &&
57              CurrentToken->is(ExtraKeywords.kw_function))) {
58           return true;
59         }
60         if (!ExcludeEnum && CurrentToken->is(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   WhitespaceManager Whitespaces(
70       Env.getSourceManager(), Style,
71       Style.LineEnding > FormatStyle::LE_CRLF
72           ? WhitespaceManager::inputUsesCRLF(
73                 Env.getSourceManager().getBufferData(Env.getFileID()),
74                 Style.LineEnding == FormatStyle::LE_DeriveCRLF)
75           : Style.LineEnding == FormatStyle::LE_CRLF);
76   for (unsigned I = 0; I < Lines.size(); ++I) {
77     const auto &CurrentLine = Lines[I];
78     if (CurrentLine->InPPDirective)
79       continue;
80     FormatToken *TargetToken = nullptr;
81     AnnotatedLine *TargetLine;
82     auto OpeningLineIndex = CurrentLine->MatchingOpeningBlockLineIndex;
83     AnnotatedLine *OpeningLine = nullptr;
84     const auto IsAccessSpecifierToken = [](const FormatToken *Token) {
85       return Token->isAccessSpecifier() || Token->isObjCAccessSpecifier();
86     };
87     const auto InsertReplacement = [&](const int NewlineToInsert) {
88       assert(TargetLine);
89       assert(TargetToken);
90 
91       // Do not handle EOF newlines.
92       if (TargetToken->is(tok::eof))
93         return;
94       if (IsAccessSpecifierToken(TargetToken) ||
95           (OpeningLineIndex > 0 &&
96            IsAccessSpecifierToken(Lines[OpeningLineIndex - 1]->First))) {
97         return;
98       }
99       if (!TargetLine->Affected)
100         return;
101       Whitespaces.replaceWhitespace(*TargetToken, NewlineToInsert,
102                                     TargetToken->OriginalColumn,
103                                     TargetToken->OriginalColumn);
104     };
105     const auto IsPPConditional = [&](const size_t LineIndex) {
106       const auto &Line = Lines[LineIndex];
107       return Line->First->is(tok::hash) && Line->First->Next &&
108              Line->First->Next->isOneOf(tok::pp_if, tok::pp_ifdef, tok::pp_else,
109                                         tok::pp_ifndef, tok::pp_elifndef,
110                                         tok::pp_elifdef, tok::pp_elif,
111                                         tok::pp_endif);
112     };
113     const auto FollowingOtherOpening = [&]() {
114       return OpeningLineIndex == 0 ||
115              Lines[OpeningLineIndex - 1]->Last->opensScope() ||
116              IsPPConditional(OpeningLineIndex - 1);
117     };
118     const auto HasEnumOnLine = [&]() {
119       bool FoundEnumKeyword = false;
120       int BracketLevel = 0;
121       for (const FormatToken *CurrentToken = CurrentLine->First; CurrentToken;
122            CurrentToken = CurrentToken->Next) {
123         if (BracketLevel == 0) {
124           if (CurrentToken->is(tok::kw_enum))
125             FoundEnumKeyword = true;
126           else if (FoundEnumKeyword && CurrentToken->is(tok::l_brace))
127             return true;
128         }
129         BracketLevel += GetBracketLevelChange(CurrentToken);
130       }
131       return FoundEnumKeyword && I + 1 < Lines.size() &&
132              Lines[I + 1]->First->is(tok::l_brace);
133     };
134 
135     bool IsDefBlock = false;
136     const auto MayPrecedeDefinition = [&](const int Direction = -1) {
137       assert(Direction >= -1);
138       assert(Direction <= 1);
139 
140       if (Lines[OpeningLineIndex]->First->is(TT_CSharpGenericTypeConstraint))
141         return true;
142 
143       const size_t OperateIndex = OpeningLineIndex + Direction;
144       assert(OperateIndex < Lines.size());
145       const auto &OperateLine = Lines[OperateIndex];
146       if (LikelyDefinition(OperateLine))
147         return false;
148 
149       if (const auto *Tok = OperateLine->First;
150           Tok->is(tok::comment) && !isClangFormatOn(Tok->TokenText)) {
151         return true;
152       }
153 
154       // A single line identifier that is not in the last line.
155       if (OperateLine->First->is(tok::identifier) &&
156           OperateLine->First == OperateLine->Last &&
157           OperateIndex + 1 < Lines.size()) {
158         // UnwrappedLineParser's recognition of free-standing macro like
159         // Q_OBJECT may also recognize some uppercased type names that may be
160         // used as return type as that kind of macros, which is a bit hard to
161         // distinguish one from another purely from token patterns. Here, we
162         // try not to add new lines below those identifiers.
163         AnnotatedLine *NextLine = Lines[OperateIndex + 1];
164         if (NextLine->MightBeFunctionDecl &&
165             NextLine->mightBeFunctionDefinition() &&
166             NextLine->First->NewlinesBefore == 1 &&
167             OperateLine->First->is(TT_FunctionLikeOrFreestandingMacro)) {
168           return true;
169         }
170       }
171 
172       if (Style.isCSharp() && OperateLine->First->is(TT_AttributeSquare))
173         return true;
174       return false;
175     };
176 
177     if (HasEnumOnLine() &&
178         !LikelyDefinition(CurrentLine, /*ExcludeEnum=*/true)) {
179       // We have no scope opening/closing information for enum.
180       IsDefBlock = true;
181       OpeningLineIndex = I;
182       while (OpeningLineIndex > 0 && MayPrecedeDefinition())
183         --OpeningLineIndex;
184       OpeningLine = Lines[OpeningLineIndex];
185       TargetLine = OpeningLine;
186       TargetToken = TargetLine->First;
187       if (!FollowingOtherOpening())
188         InsertReplacement(NewlineCount);
189       else if (IsNeverStyle)
190         InsertReplacement(OpeningLineIndex != 0);
191       TargetLine = CurrentLine;
192       TargetToken = TargetLine->First;
193       while (TargetToken && TargetToken->isNot(tok::r_brace))
194         TargetToken = TargetToken->Next;
195       if (!TargetToken)
196         while (I < Lines.size() && Lines[I]->First->isNot(tok::r_brace))
197           ++I;
198     } else if (CurrentLine->First->closesScope()) {
199       if (OpeningLineIndex > Lines.size())
200         continue;
201       // Handling the case that opening brace has its own line, with checking
202       // whether the last line already had an opening brace to guard against
203       // misrecognition.
204       if (OpeningLineIndex > 0 &&
205           Lines[OpeningLineIndex]->First->is(tok::l_brace) &&
206           Lines[OpeningLineIndex - 1]->Last->isNot(tok::l_brace)) {
207         --OpeningLineIndex;
208       }
209       OpeningLine = Lines[OpeningLineIndex];
210       // Closing a function definition.
211       if (LikelyDefinition(OpeningLine)) {
212         IsDefBlock = true;
213         while (OpeningLineIndex > 0 && MayPrecedeDefinition())
214           --OpeningLineIndex;
215         OpeningLine = Lines[OpeningLineIndex];
216         TargetLine = OpeningLine;
217         TargetToken = TargetLine->First;
218         if (!FollowingOtherOpening()) {
219           // Avoid duplicated replacement.
220           if (TargetToken->isNot(tok::l_brace))
221             InsertReplacement(NewlineCount);
222         } else if (IsNeverStyle) {
223           InsertReplacement(OpeningLineIndex != 0);
224         }
225       }
226     }
227 
228     // Not the last token.
229     if (IsDefBlock && I + 1 < Lines.size()) {
230       OpeningLineIndex = I + 1;
231       TargetLine = Lines[OpeningLineIndex];
232       TargetToken = TargetLine->First;
233 
234       // No empty line for continuously closing scopes. The token will be
235       // handled in another case if the line following is opening a
236       // definition.
237       if (!TargetToken->closesScope() && !IsPPConditional(OpeningLineIndex)) {
238         // Check whether current line may precede a definition line.
239         while (OpeningLineIndex + 1 < Lines.size() &&
240                MayPrecedeDefinition(/*Direction=*/0)) {
241           ++OpeningLineIndex;
242         }
243         TargetLine = Lines[OpeningLineIndex];
244         if (!LikelyDefinition(TargetLine)) {
245           OpeningLineIndex = I + 1;
246           TargetLine = Lines[I + 1];
247           TargetToken = TargetLine->First;
248           InsertReplacement(NewlineCount);
249         }
250       } else if (IsNeverStyle) {
251         InsertReplacement(/*NewlineToInsert=*/1);
252       }
253     }
254   }
255   for (const auto &R : Whitespaces.generateReplacements()) {
256     // The add method returns an Error instance which simulates program exit
257     // code through overloading boolean operator, thus false here indicates
258     // success.
259     if (Result.add(R))
260       return;
261   }
262 }
263 } // namespace format
264 } // namespace clang
265