xref: /freebsd/contrib/llvm-project/clang/lib/Format/DefinitionBlockSeparator.cpp (revision 7ef62cebc2f965b0f640263e179276928885e33d)
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 {
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 
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 (OperateLine->First->is(tok::comment))
147         return true;
148 
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)) {
163           return true;
164         }
165       }
166 
167       if ((Style.isCSharp() && OperateLine->First->is(TT_AttributeSquare)))
168         return true;
169       return false;
170     };
171 
172     if (HasEnumOnLine() &&
173         !LikelyDefinition(CurrentLine, /*ExcludeEnum=*/true)) {
174       // We have no scope opening/closing information for enum.
175       IsDefBlock = true;
176       OpeningLineIndex = I;
177       while (OpeningLineIndex > 0 && MayPrecedeDefinition())
178         --OpeningLineIndex;
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;
190       if (!TargetToken)
191         while (I < Lines.size() && !Lines[I]->First->is(tok::r_brace))
192           ++I;
193     } else if (CurrentLine->First->closesScope()) {
194       if (OpeningLineIndex > Lines.size())
195         continue;
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
198       // misrecognition.
199       if (OpeningLineIndex > 0 &&
200           Lines[OpeningLineIndex]->First->is(tok::l_brace) &&
201           Lines[OpeningLineIndex - 1]->Last->isNot(tok::l_brace)) {
202         --OpeningLineIndex;
203       }
204       OpeningLine = Lines[OpeningLineIndex];
205       // Closing a function definition.
206       if (LikelyDefinition(OpeningLine)) {
207         IsDefBlock = true;
208         while (OpeningLineIndex > 0 && MayPrecedeDefinition())
209           --OpeningLineIndex;
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);
219         }
220       }
221     }
222 
223     // Not the last token.
224     if (IsDefBlock && I + 1 < Lines.size()) {
225       OpeningLineIndex = I + 1;
226       TargetLine = Lines[OpeningLineIndex];
227       TargetToken = TargetLine->First;
228 
229       // No empty line for continuously closing scopes. The token will be
230       // handled in another case if the line following is opening a
231       // definition.
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)) {
236           ++OpeningLineIndex;
237         }
238         TargetLine = Lines[OpeningLineIndex];
239         if (!LikelyDefinition(TargetLine)) {
240           OpeningLineIndex = I + 1;
241           TargetLine = Lines[I + 1];
242           TargetToken = TargetLine->First;
243           InsertReplacement(NewlineCount);
244         }
245       } else if (IsNeverStyle) {
246         InsertReplacement(/*NewlineToInsert=*/1);
247       }
248     }
249   }
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
253     // success.
254     if (Result.add(R))
255       return;
256   }
257 }
258 } // namespace format
259 } // namespace clang
260