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);
29   return {Result, 0};
30 }
31 
32 void DefinitionBlockSeparator::separateBlocks(
33     SmallVectorImpl<AnnotatedLine *> &Lines, tooling::Replacements &Result) {
34   const bool IsNeverStyle =
35       Style.SeparateDefinitionBlocks == FormatStyle::SDS_Never;
36   auto LikelyDefinition = [this](const AnnotatedLine *Line) {
37     if ((Line->MightBeFunctionDecl && Line->mightBeFunctionDefinition()) ||
38         Line->startsWithNamespace())
39       return true;
40     FormatToken *CurrentToken = Line->First;
41     while (CurrentToken) {
42       if (CurrentToken->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_enum) ||
43           (Style.isJavaScript() && CurrentToken->TokenText == "function"))
44         return true;
45       CurrentToken = CurrentToken->Next;
46     }
47     return false;
48   };
49   unsigned NewlineCount =
50       (Style.SeparateDefinitionBlocks == FormatStyle::SDS_Always ? 1 : 0) + 1;
51   WhitespaceManager Whitespaces(
52       Env.getSourceManager(), Style,
53       Style.DeriveLineEnding
54           ? WhitespaceManager::inputUsesCRLF(
55                 Env.getSourceManager().getBufferData(Env.getFileID()),
56                 Style.UseCRLF)
57           : Style.UseCRLF);
58   for (unsigned I = 0; I < Lines.size(); ++I) {
59     const auto &CurrentLine = Lines[I];
60     if (CurrentLine->InPPDirective)
61       continue;
62     FormatToken *TargetToken = nullptr;
63     AnnotatedLine *TargetLine;
64     auto OpeningLineIndex = CurrentLine->MatchingOpeningBlockLineIndex;
65     AnnotatedLine *OpeningLine = nullptr;
66     const auto InsertReplacement = [&](const int NewlineToInsert) {
67       assert(TargetLine);
68       assert(TargetToken);
69 
70       // Do not handle EOF newlines.
71       if (TargetToken->is(tok::eof) && NewlineToInsert > 0)
72         return;
73       if (!TargetLine->Affected)
74         return;
75       Whitespaces.replaceWhitespace(*TargetToken, NewlineToInsert,
76                                     TargetToken->SpacesRequiredBefore - 1,
77                                     TargetToken->StartsColumn);
78     };
79     const auto IsPPConditional = [&](const size_t LineIndex) {
80       const auto &Line = Lines[LineIndex];
81       return Line->First->is(tok::hash) && Line->First->Next &&
82              Line->First->Next->isOneOf(tok::pp_if, tok::pp_ifdef, tok::pp_else,
83                                         tok::pp_ifndef, tok::pp_elifndef,
84                                         tok::pp_elifdef, tok::pp_elif,
85                                         tok::pp_endif);
86     };
87     const auto FollowingOtherOpening = [&]() {
88       return OpeningLineIndex == 0 ||
89              Lines[OpeningLineIndex - 1]->Last->opensScope() ||
90              IsPPConditional(OpeningLineIndex - 1);
91     };
92     const auto HasEnumOnLine = [CurrentLine]() {
93       FormatToken *CurrentToken = CurrentLine->First;
94       while (CurrentToken) {
95         if (CurrentToken->is(tok::kw_enum))
96           return true;
97         CurrentToken = CurrentToken->Next;
98       }
99       return false;
100     };
101 
102     bool IsDefBlock = false;
103     const auto MayPrecedeDefinition = [&](const int Direction = -1) {
104       const size_t OperateIndex = OpeningLineIndex + Direction;
105       assert(OperateIndex < Lines.size());
106       const auto &OperateLine = Lines[OperateIndex];
107       return (Style.isCSharp() && OperateLine->First->is(TT_AttributeSquare)) ||
108              OperateLine->First->is(tok::comment);
109     };
110 
111     if (HasEnumOnLine()) {
112       // We have no scope opening/closing information for enum.
113       IsDefBlock = true;
114       OpeningLineIndex = I;
115       while (OpeningLineIndex > 0 && MayPrecedeDefinition())
116         --OpeningLineIndex;
117       OpeningLine = Lines[OpeningLineIndex];
118       TargetLine = OpeningLine;
119       TargetToken = TargetLine->First;
120       if (!FollowingOtherOpening())
121         InsertReplacement(NewlineCount);
122       else if (IsNeverStyle)
123         InsertReplacement(OpeningLineIndex != 0);
124       TargetLine = CurrentLine;
125       TargetToken = TargetLine->First;
126       while (TargetToken && !TargetToken->is(tok::r_brace))
127         TargetToken = TargetToken->Next;
128       if (!TargetToken) {
129         while (I < Lines.size() && !Lines[I]->First->is(tok::r_brace))
130           ++I;
131       }
132     } else if (CurrentLine->First->closesScope()) {
133       if (OpeningLineIndex > Lines.size())
134         continue;
135       // Handling the case that opening bracket has its own line.
136       OpeningLineIndex -= Lines[OpeningLineIndex]->First->is(tok::l_brace);
137       OpeningLine = Lines[OpeningLineIndex];
138       // Closing a function definition.
139       if (LikelyDefinition(OpeningLine)) {
140         IsDefBlock = true;
141         while (OpeningLineIndex > 0 && MayPrecedeDefinition())
142           --OpeningLineIndex;
143         OpeningLine = Lines[OpeningLineIndex];
144         TargetLine = OpeningLine;
145         TargetToken = TargetLine->First;
146         if (!FollowingOtherOpening()) {
147           // Avoid duplicated replacement.
148           if (TargetToken->isNot(tok::l_brace))
149             InsertReplacement(NewlineCount);
150         } else if (IsNeverStyle)
151           InsertReplacement(OpeningLineIndex != 0);
152       }
153     }
154 
155     // Not the last token.
156     if (IsDefBlock && I + 1 < Lines.size()) {
157       OpeningLineIndex = I + 1;
158       TargetLine = Lines[OpeningLineIndex];
159       TargetToken = TargetLine->First;
160 
161       // No empty line for continuously closing scopes. The token will be
162       // handled in another case if the line following is opening a
163       // definition.
164       if (!TargetToken->closesScope() && !IsPPConditional(OpeningLineIndex)) {
165         // Check whether current line may precede a definition line.
166         while (OpeningLineIndex + 1 < Lines.size() &&
167                MayPrecedeDefinition(/*Direction=*/0))
168           ++OpeningLineIndex;
169         TargetLine = Lines[OpeningLineIndex];
170         if (!LikelyDefinition(TargetLine)) {
171           TargetLine = Lines[I + 1];
172           TargetToken = TargetLine->First;
173           InsertReplacement(NewlineCount);
174         }
175       } else if (IsNeverStyle) {
176         TargetLine = Lines[I + 1];
177         TargetToken = TargetLine->First;
178         InsertReplacement(OpeningLineIndex != 0);
179       }
180     }
181   }
182   for (const auto &R : Whitespaces.generateReplacements())
183     // The add method returns an Error instance which simulates program exit
184     // code through overloading boolean operator, thus false here indicates
185     // success.
186     if (Result.add(R))
187       return;
188 }
189 } // namespace format
190 } // namespace clang
191