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