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
20namespace clang {
21namespace format {
22std::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
32void 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 (const auto *Tok = OperateLine->First;
147          Tok->is(tok::comment) && !isClangFormatOn(Tok->TokenText)) {
148        return true;
149      }
150
151      // A single line identifier that is not in the last line.
152      if (OperateLine->First->is(tok::identifier) &&
153          OperateLine->First == OperateLine->Last &&
154          OperateIndex + 1 < Lines.size()) {
155        // UnwrappedLineParser's recognition of free-standing macro like
156        // Q_OBJECT may also recognize some uppercased type names that may be
157        // used as return type as that kind of macros, which is a bit hard to
158        // distinguish one from another purely from token patterns. Here, we
159        // try not to add new lines below those identifiers.
160        AnnotatedLine *NextLine = Lines[OperateIndex + 1];
161        if (NextLine->MightBeFunctionDecl &&
162            NextLine->mightBeFunctionDefinition() &&
163            NextLine->First->NewlinesBefore == 1 &&
164            OperateLine->First->is(TT_FunctionLikeOrFreestandingMacro)) {
165          return true;
166        }
167      }
168
169      if (Style.isCSharp() && OperateLine->First->is(TT_AttributeSquare))
170        return true;
171      return false;
172    };
173
174    if (HasEnumOnLine() &&
175        !LikelyDefinition(CurrentLine, /*ExcludeEnum=*/true)) {
176      // We have no scope opening/closing information for enum.
177      IsDefBlock = true;
178      OpeningLineIndex = I;
179      while (OpeningLineIndex > 0 && MayPrecedeDefinition())
180        --OpeningLineIndex;
181      OpeningLine = Lines[OpeningLineIndex];
182      TargetLine = OpeningLine;
183      TargetToken = TargetLine->First;
184      if (!FollowingOtherOpening())
185        InsertReplacement(NewlineCount);
186      else if (IsNeverStyle)
187        InsertReplacement(OpeningLineIndex != 0);
188      TargetLine = CurrentLine;
189      TargetToken = TargetLine->First;
190      while (TargetToken && TargetToken->isNot(tok::r_brace))
191        TargetToken = TargetToken->Next;
192      if (!TargetToken)
193        while (I < Lines.size() && Lines[I]->First->isNot(tok::r_brace))
194          ++I;
195    } else if (CurrentLine->First->closesScope()) {
196      if (OpeningLineIndex > Lines.size())
197        continue;
198      // Handling the case that opening brace has its own line, with checking
199      // whether the last line already had an opening brace to guard against
200      // misrecognition.
201      if (OpeningLineIndex > 0 &&
202          Lines[OpeningLineIndex]->First->is(tok::l_brace) &&
203          Lines[OpeningLineIndex - 1]->Last->isNot(tok::l_brace)) {
204        --OpeningLineIndex;
205      }
206      OpeningLine = Lines[OpeningLineIndex];
207      // Closing a function definition.
208      if (LikelyDefinition(OpeningLine)) {
209        IsDefBlock = true;
210        while (OpeningLineIndex > 0 && MayPrecedeDefinition())
211          --OpeningLineIndex;
212        OpeningLine = Lines[OpeningLineIndex];
213        TargetLine = OpeningLine;
214        TargetToken = TargetLine->First;
215        if (!FollowingOtherOpening()) {
216          // Avoid duplicated replacement.
217          if (TargetToken->isNot(tok::l_brace))
218            InsertReplacement(NewlineCount);
219        } else if (IsNeverStyle) {
220          InsertReplacement(OpeningLineIndex != 0);
221        }
222      }
223    }
224
225    // Not the last token.
226    if (IsDefBlock && I + 1 < Lines.size()) {
227      OpeningLineIndex = I + 1;
228      TargetLine = Lines[OpeningLineIndex];
229      TargetToken = TargetLine->First;
230
231      // No empty line for continuously closing scopes. The token will be
232      // handled in another case if the line following is opening a
233      // definition.
234      if (!TargetToken->closesScope() && !IsPPConditional(OpeningLineIndex)) {
235        // Check whether current line may precede a definition line.
236        while (OpeningLineIndex + 1 < Lines.size() &&
237               MayPrecedeDefinition(/*Direction=*/0)) {
238          ++OpeningLineIndex;
239        }
240        TargetLine = Lines[OpeningLineIndex];
241        if (!LikelyDefinition(TargetLine)) {
242          OpeningLineIndex = I + 1;
243          TargetLine = Lines[I + 1];
244          TargetToken = TargetLine->First;
245          InsertReplacement(NewlineCount);
246        }
247      } else if (IsNeverStyle) {
248        InsertReplacement(/*NewlineToInsert=*/1);
249      }
250    }
251  }
252  for (const auto &R : Whitespaces.generateReplacements()) {
253    // The add method returns an Error instance which simulates program exit
254    // code through overloading boolean operator, thus false here indicates
255    // success.
256    if (Result.add(R))
257      return;
258  }
259}
260} // namespace format
261} // namespace clang
262