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