Coverage Report

Created: 2022-07-16 07:03

/Users/buildslave/jenkins/workspace/coverage/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
84
    FormatTokenLexer &Tokens) {
25
84
  assert(Style.SeparateDefinitionBlocks != FormatStyle::SDS_Leave);
26
0
  AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
27
84
  tooling::Replacements Result;
28
84
  separateBlocks(AnnotatedLines, Result, Tokens);
29
84
  return {Result, 0};
30
84
}
31
32
void DefinitionBlockSeparator::separateBlocks(
33
    SmallVectorImpl<AnnotatedLine *> &Lines, tooling::Replacements &Result,
34
84
    FormatTokenLexer &Tokens) {
35
84
  const bool IsNeverStyle =
36
84
      Style.SeparateDefinitionBlocks == FormatStyle::SDS_Never;
37
84
  const AdditionalKeywords &ExtraKeywords = Tokens.getKeywords();
38
6.52k
  auto GetBracketLevelChange = [](const FormatToken *Tok) {
39
6.52k
    if (Tok->isOneOf(tok::l_brace, tok::l_paren, tok::l_square))
40
626
      return 1;
41
5.90k
    if (Tok->isOneOf(tok::r_brace, tok::r_paren, tok::r_square))
42
698
      return -1;
43
5.20k
    return 0;
44
5.90k
  };
45
84
  auto LikelyDefinition = [&](const AnnotatedLine *Line,
46
1.33k
                              bool ExcludeEnum = false) {
47
1.33k
    if ((Line->MightBeFunctionDecl && 
Line->mightBeFunctionDefinition()394
) ||
48
1.33k
        
Line->startsWithNamespace()936
) {
49
510
      return true;
50
510
    }
51
820
    int BracketLevel = 0;
52
2.62k
    for (const FormatToken *CurrentToken = Line->First; CurrentToken;
53
2.03k
         
CurrentToken = CurrentToken->Next1.80k
) {
54
2.03k
      if (BracketLevel == 0) {
55
1.55k
        if ((CurrentToken->isOneOf(tok::kw_class, tok::kw_struct,
56
1.55k
                                   tok::kw_union) ||
57
1.55k
             
(1.48k
Style.isJavaScript()1.48k
&&
58
1.48k
              
CurrentToken->is(ExtraKeywords.kw_function)195
))) {
59
94
          return true;
60
94
        }
61
1.45k
        if (!ExcludeEnum && 
CurrentToken->is(tok::kw_enum)1.07k
)
62
133
          return true;
63
1.45k
      }
64
1.80k
      BracketLevel += GetBracketLevelChange(CurrentToken);
65
1.80k
    }
66
593
    return false;
67
820
  };
68
84
  unsigned NewlineCount =
69
84
      (Style.SeparateDefinitionBlocks == FormatStyle::SDS_Always ? 
151
:
033
) + 1;
70
84
  WhitespaceManager Whitespaces(
71
84
      Env.getSourceManager(), Style,
72
84
      Style.DeriveLineEnding
73
84
          ? WhitespaceManager::inputUsesCRLF(
74
84
                Env.getSourceManager().getBufferData(Env.getFileID()),
75
84
                Style.UseCRLF)
76
84
          : 
Style.UseCRLF0
);
77
1.47k
  for (unsigned I = 0; I < Lines.size(); 
++I1.39k
) {
78
1.39k
    const auto &CurrentLine = Lines[I];
79
1.39k
    if (CurrentLine->InPPDirective)
80
72
      continue;
81
1.32k
    FormatToken *TargetToken = nullptr;
82
1.32k
    AnnotatedLine *TargetLine;
83
1.32k
    auto OpeningLineIndex = CurrentLine->MatchingOpeningBlockLineIndex;
84
1.32k
    AnnotatedLine *OpeningLine = nullptr;
85
1.32k
    const auto IsAccessSpecifierToken = [](const FormatToken *Token) {
86
587
      return Token->isAccessSpecifier() || 
Token->isObjCAccessSpecifier()586
;
87
587
    };
88
1.32k
    const auto InsertReplacement = [&](const int NewlineToInsert) {
89
386
      assert(TargetLine);
90
0
      assert(TargetToken);
91
92
      // Do not handle EOF newlines.
93
386
      if (TargetToken->is(tok::eof))
94
82
        return;
95
304
      if (IsAccessSpecifierToken(TargetToken) ||
96
304
          (OpeningLineIndex > 0 &&
97
304
           
IsAccessSpecifierToken(Lines[OpeningLineIndex - 1]->First)283
)) {
98
1
        return;
99
1
      }
100
303
      if (!TargetLine->Affected)
101
0
        return;
102
303
      Whitespaces.replaceWhitespace(*TargetToken, NewlineToInsert,
103
303
                                    TargetToken->OriginalColumn,
104
303
                                    TargetToken->OriginalColumn);
105
303
    };
106
1.32k
    const auto IsPPConditional = [&](const size_t LineIndex) {
107
549
      const auto &Line = Lines[LineIndex];
108
549
      return Line->First->is(tok::hash) && 
Line->First->Next72
&&
109
549
             Line->First->Next->isOneOf(tok::pp_if, tok::pp_ifdef, tok::pp_else,
110
72
                                        tok::pp_ifndef, tok::pp_elifndef,
111
72
                                        tok::pp_elifdef, tok::pp_elif,
112
72
                                        tok::pp_endif);
113
549
    };
114
1.32k
    const auto FollowingOtherOpening = [&]() {
115
344
      return OpeningLineIndex == 0 ||
116
344
             
Lines[OpeningLineIndex - 1]->Last->opensScope()286
||
117
344
             
IsPPConditional(OpeningLineIndex - 1)246
;
118
344
    };
119
1.32k
    const auto HasEnumOnLine = [&]() {
120
1.32k
      bool FoundEnumKeyword = false;
121
1.32k
      int BracketLevel = 0;
122
6.03k
      for (const FormatToken *CurrentToken = CurrentLine->First; CurrentToken;
123
4.81k
           
CurrentToken = CurrentToken->Next4.71k
) {
124
4.81k
        if (BracketLevel == 0) {
125
3.67k
          if (CurrentToken->is(tok::kw_enum))
126
95
            FoundEnumKeyword = true;
127
3.58k
          else if (FoundEnumKeyword && 
CurrentToken->is(tok::l_brace)190
)
128
95
            return true;
129
3.67k
        }
130
4.71k
        BracketLevel += GetBracketLevelChange(CurrentToken);
131
4.71k
      }
132
1.22k
      return FoundEnumKeyword && 
I + 1 < Lines.size()0
&&
133
1.22k
             
Lines[I + 1]->First->is(tok::l_brace)0
;
134
1.32k
    };
135
136
1.32k
    bool IsDefBlock = false;
137
1.32k
    const auto MayPrecedeDefinition = [&](const int Direction = -1) {
138
679
      assert(Direction >= -1);
139
0
      assert(Direction <= 1);
140
0
      const size_t OperateIndex = OpeningLineIndex + Direction;
141
679
      assert(OperateIndex < Lines.size());
142
0
      const auto &OperateLine = Lines[OperateIndex];
143
679
      if (LikelyDefinition(OperateLine))
144
304
        return false;
145
146
375
      if (OperateLine->First->is(tok::comment))
147
184
        return true;
148
149
      // A single line identifier that is not in the last line.
150
191
      if (OperateLine->First->is(tok::identifier) &&
151
191
          
OperateLine->First == OperateLine->Last12
&&
152
191
          
OperateIndex + 1 < Lines.size()6
) {
153
        // UnwrappedLineParser's recognition of free-standing macro like
154
        // Q_OBJECT may also recognize some uppercased type names that may be
155
        // used as return type as that kind of macros, which is a bit hard to
156
        // distinguish one from another purely from token patterns. Here, we
157
        // try not to add new lines below those identifiers.
158
6
        AnnotatedLine *NextLine = Lines[OperateIndex + 1];
159
6
        if (NextLine->MightBeFunctionDecl &&
160
6
            NextLine->mightBeFunctionDefinition() &&
161
6
            NextLine->First->NewlinesBefore == 1 &&
162
6
            OperateLine->First->is(TT_FunctionLikeOrFreestandingMacro)) {
163
6
          return true;
164
6
        }
165
6
      }
166
167
185
      if ((Style.isCSharp() && 
OperateLine->First->is(TT_AttributeSquare)24
))
168
6
        return true;
169
179
      return false;
170
185
    };
171
172
1.32k
    if (HasEnumOnLine() &&
173
1.32k
        
!LikelyDefinition(CurrentLine, /*ExcludeEnum=*/true)95
) {
174
      // We have no scope opening/closing information for enum.
175
95
      IsDefBlock = true;
176
95
      OpeningLineIndex = I;
177
145
      while (OpeningLineIndex > 0 && 
MayPrecedeDefinition()133
)
178
50
        --OpeningLineIndex;
179
95
      OpeningLine = Lines[OpeningLineIndex];
180
95
      TargetLine = OpeningLine;
181
95
      TargetToken = TargetLine->First;
182
95
      if (!FollowingOtherOpening())
183
25
        InsertReplacement(NewlineCount);
184
70
      else if (IsNeverStyle)
185
32
        InsertReplacement(OpeningLineIndex != 0);
186
95
      TargetLine = CurrentLine;
187
95
      TargetToken = TargetLine->First;
188
653
      while (TargetToken && 
!TargetToken->is(tok::r_brace)644
)
189
558
        TargetToken = TargetToken->Next;
190
95
      if (!TargetToken)
191
42
        
while (9
I < Lines.size() && !Lines[I]->First->is(tok::r_brace))
192
33
          ++I;
193
1.22k
    } else if (CurrentLine->First->closesScope()) {
194
277
      if (OpeningLineIndex > Lines.size())
195
0
        continue;
196
      // Handling the case that opening brace has its own line, with checking
197
      // whether the last line already had an opening brace to guard against
198
      // misrecognition.
199
277
      if (OpeningLineIndex > 0 &&
200
277
          
Lines[OpeningLineIndex]->First->is(tok::l_brace)236
&&
201
277
          
Lines[OpeningLineIndex - 1]->Last->isNot(tok::l_brace)38
) {
202
28
        --OpeningLineIndex;
203
28
      }
204
277
      OpeningLine = Lines[OpeningLineIndex];
205
      // Closing a function definition.
206
277
      if (LikelyDefinition(OpeningLine)) {
207
249
        IsDefBlock = true;
208
314
        while (OpeningLineIndex > 0 && 
MayPrecedeDefinition()268
)
209
65
          --OpeningLineIndex;
210
249
        OpeningLine = Lines[OpeningLineIndex];
211
249
        TargetLine = OpeningLine;
212
249
        TargetToken = TargetLine->First;
213
249
        if (!FollowingOtherOpening()) {
214
          // Avoid duplicated replacement.
215
173
          if (TargetToken->isNot(tok::l_brace))
216
173
            InsertReplacement(NewlineCount);
217
173
        } else 
if (76
IsNeverStyle76
) {
218
31
          InsertReplacement(OpeningLineIndex != 0);
219
31
        }
220
249
      }
221
277
    }
222
223
    // Not the last token.
224
1.32k
    if (IsDefBlock && 
I + 1 < Lines.size()344
) {
225
344
      OpeningLineIndex = I + 1;
226
344
      TargetLine = Lines[OpeningLineIndex];
227
344
      TargetToken = TargetLine->First;
228
229
      // No empty line for continuously closing scopes. The token will be
230
      // handled in another case if the line following is opening a
231
      // definition.
232
344
      if (!TargetToken->closesScope() && 
!IsPPConditional(OpeningLineIndex)303
) {
233
        // Check whether current line may precede a definition line.
234
360
        while (OpeningLineIndex + 1 < Lines.size() &&
235
360
               
MayPrecedeDefinition(/*Direction=*/0)278
) {
236
81
          ++OpeningLineIndex;
237
81
        }
238
279
        TargetLine = Lines[OpeningLineIndex];
239
279
        if (!LikelyDefinition(TargetLine)) {
240
95
          OpeningLineIndex = I + 1;
241
95
          TargetLine = Lines[I + 1];
242
95
          TargetToken = TargetLine->First;
243
95
          InsertReplacement(NewlineCount);
244
95
        }
245
279
      } else 
if (65
IsNeverStyle65
) {
246
30
        InsertReplacement(/*NewlineToInsert=*/1);
247
30
      }
248
344
    }
249
1.32k
  }
250
157
  for (const auto &R : Whitespaces.generateReplacements()) {
251
    // The add method returns an Error instance which simulates program exit
252
    // code through overloading boolean operator, thus false here indicates
253
    // success.
254
157
    if (Result.add(R))
255
0
      return;
256
157
  }
257
84
}
258
} // namespace format
259
} // namespace clang