Coverage Report

Created: 2024-01-17 10:31

/src/llvm-project/clang/lib/Format/DefinitionBlockSeparator.cpp
Line
Count
Source (jump to first uncovered line)
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
0
    FormatTokenLexer &Tokens) {
25
0
  assert(Style.SeparateDefinitionBlocks != FormatStyle::SDS_Leave);
26
0
  AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
27
0
  tooling::Replacements Result;
28
0
  separateBlocks(AnnotatedLines, Result, Tokens);
29
0
  return {Result, 0};
30
0
}
31
32
void DefinitionBlockSeparator::separateBlocks(
33
    SmallVectorImpl<AnnotatedLine *> &Lines, tooling::Replacements &Result,
34
0
    FormatTokenLexer &Tokens) {
35
0
  const bool IsNeverStyle =
36
0
      Style.SeparateDefinitionBlocks == FormatStyle::SDS_Never;
37
0
  const AdditionalKeywords &ExtraKeywords = Tokens.getKeywords();
38
0
  auto GetBracketLevelChange = [](const FormatToken *Tok) {
39
0
    if (Tok->isOneOf(tok::l_brace, tok::l_paren, tok::l_square))
40
0
      return 1;
41
0
    if (Tok->isOneOf(tok::r_brace, tok::r_paren, tok::r_square))
42
0
      return -1;
43
0
    return 0;
44
0
  };
45
0
  auto LikelyDefinition = [&](const AnnotatedLine *Line,
46
0
                              bool ExcludeEnum = false) {
47
0
    if ((Line->MightBeFunctionDecl && Line->mightBeFunctionDefinition()) ||
48
0
        Line->startsWithNamespace()) {
49
0
      return true;
50
0
    }
51
0
    int BracketLevel = 0;
52
0
    for (const FormatToken *CurrentToken = Line->First; CurrentToken;
53
0
         CurrentToken = CurrentToken->Next) {
54
0
      if (BracketLevel == 0) {
55
0
        if (CurrentToken->isOneOf(tok::kw_class, tok::kw_struct,
56
0
                                  tok::kw_union) ||
57
0
            (Style.isJavaScript() &&
58
0
             CurrentToken->is(ExtraKeywords.kw_function))) {
59
0
          return true;
60
0
        }
61
0
        if (!ExcludeEnum && CurrentToken->is(tok::kw_enum))
62
0
          return true;
63
0
      }
64
0
      BracketLevel += GetBracketLevelChange(CurrentToken);
65
0
    }
66
0
    return false;
67
0
  };
68
0
  unsigned NewlineCount =
69
0
      (Style.SeparateDefinitionBlocks == FormatStyle::SDS_Always ? 1 : 0) + 1;
70
0
  WhitespaceManager Whitespaces(
71
0
      Env.getSourceManager(), Style,
72
0
      Style.LineEnding > FormatStyle::LE_CRLF
73
0
          ? WhitespaceManager::inputUsesCRLF(
74
0
                Env.getSourceManager().getBufferData(Env.getFileID()),
75
0
                Style.LineEnding == FormatStyle::LE_DeriveCRLF)
76
0
          : Style.LineEnding == FormatStyle::LE_CRLF);
77
0
  for (unsigned I = 0; I < Lines.size(); ++I) {
78
0
    const auto &CurrentLine = Lines[I];
79
0
    if (CurrentLine->InPPDirective)
80
0
      continue;
81
0
    FormatToken *TargetToken = nullptr;
82
0
    AnnotatedLine *TargetLine;
83
0
    auto OpeningLineIndex = CurrentLine->MatchingOpeningBlockLineIndex;
84
0
    AnnotatedLine *OpeningLine = nullptr;
85
0
    const auto IsAccessSpecifierToken = [](const FormatToken *Token) {
86
0
      return Token->isAccessSpecifier() || Token->isObjCAccessSpecifier();
87
0
    };
88
0
    const auto InsertReplacement = [&](const int NewlineToInsert) {
89
0
      assert(TargetLine);
90
0
      assert(TargetToken);
91
92
      // Do not handle EOF newlines.
93
0
      if (TargetToken->is(tok::eof))
94
0
        return;
95
0
      if (IsAccessSpecifierToken(TargetToken) ||
96
0
          (OpeningLineIndex > 0 &&
97
0
           IsAccessSpecifierToken(Lines[OpeningLineIndex - 1]->First))) {
98
0
        return;
99
0
      }
100
0
      if (!TargetLine->Affected)
101
0
        return;
102
0
      Whitespaces.replaceWhitespace(*TargetToken, NewlineToInsert,
103
0
                                    TargetToken->OriginalColumn,
104
0
                                    TargetToken->OriginalColumn);
105
0
    };
106
0
    const auto IsPPConditional = [&](const size_t LineIndex) {
107
0
      const auto &Line = Lines[LineIndex];
108
0
      return Line->First->is(tok::hash) && Line->First->Next &&
109
0
             Line->First->Next->isOneOf(tok::pp_if, tok::pp_ifdef, tok::pp_else,
110
0
                                        tok::pp_ifndef, tok::pp_elifndef,
111
0
                                        tok::pp_elifdef, tok::pp_elif,
112
0
                                        tok::pp_endif);
113
0
    };
114
0
    const auto FollowingOtherOpening = [&]() {
115
0
      return OpeningLineIndex == 0 ||
116
0
             Lines[OpeningLineIndex - 1]->Last->opensScope() ||
117
0
             IsPPConditional(OpeningLineIndex - 1);
118
0
    };
119
0
    const auto HasEnumOnLine = [&]() {
120
0
      bool FoundEnumKeyword = false;
121
0
      int BracketLevel = 0;
122
0
      for (const FormatToken *CurrentToken = CurrentLine->First; CurrentToken;
123
0
           CurrentToken = CurrentToken->Next) {
124
0
        if (BracketLevel == 0) {
125
0
          if (CurrentToken->is(tok::kw_enum))
126
0
            FoundEnumKeyword = true;
127
0
          else if (FoundEnumKeyword && CurrentToken->is(tok::l_brace))
128
0
            return true;
129
0
        }
130
0
        BracketLevel += GetBracketLevelChange(CurrentToken);
131
0
      }
132
0
      return FoundEnumKeyword && I + 1 < Lines.size() &&
133
0
             Lines[I + 1]->First->is(tok::l_brace);
134
0
    };
135
136
0
    bool IsDefBlock = false;
137
0
    const auto MayPrecedeDefinition = [&](const int Direction = -1) {
138
0
      assert(Direction >= -1);
139
0
      assert(Direction <= 1);
140
0
      const size_t OperateIndex = OpeningLineIndex + Direction;
141
0
      assert(OperateIndex < Lines.size());
142
0
      const auto &OperateLine = Lines[OperateIndex];
143
0
      if (LikelyDefinition(OperateLine))
144
0
        return false;
145
146
0
      if (const auto *Tok = OperateLine->First;
147
0
          Tok->is(tok::comment) && !isClangFormatOn(Tok->TokenText)) {
148
0
        return true;
149
0
      }
150
151
      // A single line identifier that is not in the last line.
152
0
      if (OperateLine->First->is(tok::identifier) &&
153
0
          OperateLine->First == OperateLine->Last &&
154
0
          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
0
        AnnotatedLine *NextLine = Lines[OperateIndex + 1];
161
0
        if (NextLine->MightBeFunctionDecl &&
162
0
            NextLine->mightBeFunctionDefinition() &&
163
0
            NextLine->First->NewlinesBefore == 1 &&
164
0
            OperateLine->First->is(TT_FunctionLikeOrFreestandingMacro)) {
165
0
          return true;
166
0
        }
167
0
      }
168
169
0
      if (Style.isCSharp() && OperateLine->First->is(TT_AttributeSquare))
170
0
        return true;
171
0
      return false;
172
0
    };
173
174
0
    if (HasEnumOnLine() &&
175
0
        !LikelyDefinition(CurrentLine, /*ExcludeEnum=*/true)) {
176
      // We have no scope opening/closing information for enum.
177
0
      IsDefBlock = true;
178
0
      OpeningLineIndex = I;
179
0
      while (OpeningLineIndex > 0 && MayPrecedeDefinition())
180
0
        --OpeningLineIndex;
181
0
      OpeningLine = Lines[OpeningLineIndex];
182
0
      TargetLine = OpeningLine;
183
0
      TargetToken = TargetLine->First;
184
0
      if (!FollowingOtherOpening())
185
0
        InsertReplacement(NewlineCount);
186
0
      else if (IsNeverStyle)
187
0
        InsertReplacement(OpeningLineIndex != 0);
188
0
      TargetLine = CurrentLine;
189
0
      TargetToken = TargetLine->First;
190
0
      while (TargetToken && TargetToken->isNot(tok::r_brace))
191
0
        TargetToken = TargetToken->Next;
192
0
      if (!TargetToken)
193
0
        while (I < Lines.size() && Lines[I]->First->isNot(tok::r_brace))
194
0
          ++I;
195
0
    } else if (CurrentLine->First->closesScope()) {
196
0
      if (OpeningLineIndex > Lines.size())
197
0
        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
0
      if (OpeningLineIndex > 0 &&
202
0
          Lines[OpeningLineIndex]->First->is(tok::l_brace) &&
203
0
          Lines[OpeningLineIndex - 1]->Last->isNot(tok::l_brace)) {
204
0
        --OpeningLineIndex;
205
0
      }
206
0
      OpeningLine = Lines[OpeningLineIndex];
207
      // Closing a function definition.
208
0
      if (LikelyDefinition(OpeningLine)) {
209
0
        IsDefBlock = true;
210
0
        while (OpeningLineIndex > 0 && MayPrecedeDefinition())
211
0
          --OpeningLineIndex;
212
0
        OpeningLine = Lines[OpeningLineIndex];
213
0
        TargetLine = OpeningLine;
214
0
        TargetToken = TargetLine->First;
215
0
        if (!FollowingOtherOpening()) {
216
          // Avoid duplicated replacement.
217
0
          if (TargetToken->isNot(tok::l_brace))
218
0
            InsertReplacement(NewlineCount);
219
0
        } else if (IsNeverStyle) {
220
0
          InsertReplacement(OpeningLineIndex != 0);
221
0
        }
222
0
      }
223
0
    }
224
225
    // Not the last token.
226
0
    if (IsDefBlock && I + 1 < Lines.size()) {
227
0
      OpeningLineIndex = I + 1;
228
0
      TargetLine = Lines[OpeningLineIndex];
229
0
      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
0
      if (!TargetToken->closesScope() && !IsPPConditional(OpeningLineIndex)) {
235
        // Check whether current line may precede a definition line.
236
0
        while (OpeningLineIndex + 1 < Lines.size() &&
237
0
               MayPrecedeDefinition(/*Direction=*/0)) {
238
0
          ++OpeningLineIndex;
239
0
        }
240
0
        TargetLine = Lines[OpeningLineIndex];
241
0
        if (!LikelyDefinition(TargetLine)) {
242
0
          OpeningLineIndex = I + 1;
243
0
          TargetLine = Lines[I + 1];
244
0
          TargetToken = TargetLine->First;
245
0
          InsertReplacement(NewlineCount);
246
0
        }
247
0
      } else if (IsNeverStyle) {
248
0
        InsertReplacement(/*NewlineToInsert=*/1);
249
0
      }
250
0
    }
251
0
  }
252
0
  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
0
    if (Result.add(R))
257
0
      return;
258
0
  }
259
0
}
260
} // namespace format
261
} // namespace clang