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   auto LikelyDefinition = [this](const AnnotatedLine *Line) {
35     if (Line->MightBeFunctionDecl && Line->mightBeFunctionDefinition())
36       return true;
37     FormatToken *CurrentToken = Line->First;
38     while (CurrentToken) {
39       if (CurrentToken->isOneOf(tok::kw_class, tok::kw_struct,
40                                 tok::kw_namespace, tok::kw_enum) ||
41           (Style.Language == FormatStyle::LK_JavaScript &&
42            CurrentToken->TokenText == "function"))
43         return true;
44       CurrentToken = CurrentToken->Next;
45     }
46     return false;
47   };
48   unsigned NewlineCount =
49       (Style.SeparateDefinitionBlocks == FormatStyle::SDS_Always ? 1 : 0) + 1;
50   WhitespaceManager Whitespaces(
51       Env.getSourceManager(), Style,
52       Style.DeriveLineEnding
53           ? WhitespaceManager::inputUsesCRLF(
54                 Env.getSourceManager().getBufferData(Env.getFileID()),
55                 Style.UseCRLF)
56           : Style.UseCRLF);
57   for (unsigned I = 0; I < Lines.size(); I++) {
58     const auto &CurrentLine = Lines[I];
59     FormatToken *TargetToken = nullptr;
60     AnnotatedLine *TargetLine;
61     auto OpeningLineIndex = CurrentLine->MatchingOpeningBlockLineIndex;
62     const auto InsertReplacement = [&](const int NewlineToInsert) {
63       assert(TargetLine);
64       assert(TargetToken);
65 
66       // Do not handle EOF newlines.
67       if (TargetToken->is(tok::eof) && NewlineToInsert > 0)
68         return;
69       if (!TargetLine->Affected)
70         return;
71       Whitespaces.replaceWhitespace(*TargetToken, NewlineToInsert,
72                                     TargetToken->SpacesRequiredBefore - 1,
73                                     TargetToken->StartsColumn);
74     };
75     const auto FollowingOtherOpening = [&]() {
76       return OpeningLineIndex == 0 ||
77              Lines[OpeningLineIndex - 1]->Last->opensScope();
78     };
79     const auto HasEnumOnLine = [CurrentLine]() {
80       FormatToken *CurrentToken = CurrentLine->First;
81       while (CurrentToken) {
82         if (CurrentToken->is(tok::kw_enum))
83           return true;
84         CurrentToken = CurrentToken->Next;
85       }
86       return false;
87     };
88 
89     bool IsDefBlock = 0;
90 
91     if (HasEnumOnLine()) {
92       // We have no scope opening/closing information for enum.
93       IsDefBlock = 1;
94       OpeningLineIndex = I;
95       TargetLine = CurrentLine;
96       TargetToken = CurrentLine->First;
97       if (!FollowingOtherOpening())
98         InsertReplacement(NewlineCount);
99       else
100         InsertReplacement(OpeningLineIndex != 0);
101       while (TargetToken && !TargetToken->is(tok::r_brace))
102         TargetToken = TargetToken->Next;
103       if (!TargetToken) {
104         while (I < Lines.size() && !Lines[I]->First->is(tok::r_brace))
105           ++I;
106       }
107     } else if (CurrentLine->First->closesScope()) {
108       if (OpeningLineIndex > Lines.size())
109         continue;
110       // Handling the case that opening bracket has its own line.
111       OpeningLineIndex -= Lines[OpeningLineIndex]->First->TokenText == "{";
112       AnnotatedLine *OpeningLine = Lines[OpeningLineIndex];
113       // Closing a function definition.
114       if (LikelyDefinition(OpeningLine)) {
115         IsDefBlock = 1;
116         if (OpeningLineIndex > 0) {
117           OpeningLineIndex -=
118               Style.Language == FormatStyle::LK_CSharp &&
119               Lines[OpeningLineIndex - 1]->First->is(tok::l_square);
120           OpeningLine = Lines[OpeningLineIndex];
121         }
122         TargetLine = OpeningLine;
123         TargetToken = TargetLine->First;
124         if (!FollowingOtherOpening()) {
125           // Avoid duplicated replacement.
126           if (!TargetToken->opensScope())
127             InsertReplacement(NewlineCount);
128         } else
129           InsertReplacement(OpeningLineIndex != 0);
130       }
131     }
132 
133     // Not the last token.
134     if (IsDefBlock && I + 1 < Lines.size()) {
135       TargetLine = Lines[I + 1];
136       TargetToken = TargetLine->First;
137 
138       // No empty line for continuously closing scopes. The token will be
139       // handled in another case if the line following is opening a
140       // definition.
141       if (!TargetToken->closesScope()) {
142         if (!LikelyDefinition(TargetLine))
143           InsertReplacement(NewlineCount);
144       } else {
145         InsertReplacement(OpeningLineIndex != 0);
146       }
147     }
148   }
149   for (const auto &R : Whitespaces.generateReplacements())
150     // The add method returns an Error instance which simulates program exit
151     // code through overloading boolean operator, thus false here indicates
152     // success.
153     if (Result.add(R))
154       return;
155 }
156 } // namespace format
157 } // namespace clang
158