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, Tokens); 29 return {Result, 0}; 30 } 31 32 void 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 LikelyDefinition = [this, ExtraKeywords](const AnnotatedLine *Line, 39 bool ExcludeEnum = false) { 40 if ((Line->MightBeFunctionDecl && Line->mightBeFunctionDefinition()) || 41 Line->startsWithNamespace()) 42 return true; 43 FormatToken *CurrentToken = Line->First; 44 while (CurrentToken) { 45 if (CurrentToken->isOneOf(tok::kw_class, tok::kw_struct) || 46 (Style.isJavaScript() && CurrentToken->is(ExtraKeywords.kw_function))) 47 return true; 48 if (!ExcludeEnum && CurrentToken->is(tok::kw_enum)) 49 return true; 50 CurrentToken = CurrentToken->Next; 51 } 52 return false; 53 }; 54 unsigned NewlineCount = 55 (Style.SeparateDefinitionBlocks == FormatStyle::SDS_Always ? 1 : 0) + 1; 56 WhitespaceManager Whitespaces( 57 Env.getSourceManager(), Style, 58 Style.DeriveLineEnding 59 ? WhitespaceManager::inputUsesCRLF( 60 Env.getSourceManager().getBufferData(Env.getFileID()), 61 Style.UseCRLF) 62 : Style.UseCRLF); 63 for (unsigned I = 0; I < Lines.size(); ++I) { 64 const auto &CurrentLine = Lines[I]; 65 if (CurrentLine->InPPDirective) 66 continue; 67 FormatToken *TargetToken = nullptr; 68 AnnotatedLine *TargetLine; 69 auto OpeningLineIndex = CurrentLine->MatchingOpeningBlockLineIndex; 70 AnnotatedLine *OpeningLine = nullptr; 71 const auto IsAccessSpecifierToken = [](const FormatToken *Token) { 72 return Token->isAccessSpecifier() || Token->isObjCAccessSpecifier(); 73 }; 74 const auto InsertReplacement = [&](const int NewlineToInsert) { 75 assert(TargetLine); 76 assert(TargetToken); 77 78 // Do not handle EOF newlines. 79 if (TargetToken->is(tok::eof)) 80 return; 81 if (IsAccessSpecifierToken(TargetToken) || 82 (OpeningLineIndex > 0 && 83 IsAccessSpecifierToken(Lines[OpeningLineIndex - 1]->First))) 84 return; 85 if (!TargetLine->Affected) 86 return; 87 Whitespaces.replaceWhitespace(*TargetToken, NewlineToInsert, 88 TargetToken->OriginalColumn, 89 TargetToken->OriginalColumn); 90 }; 91 const auto IsPPConditional = [&](const size_t LineIndex) { 92 const auto &Line = Lines[LineIndex]; 93 return Line->First->is(tok::hash) && Line->First->Next && 94 Line->First->Next->isOneOf(tok::pp_if, tok::pp_ifdef, tok::pp_else, 95 tok::pp_ifndef, tok::pp_elifndef, 96 tok::pp_elifdef, tok::pp_elif, 97 tok::pp_endif); 98 }; 99 const auto FollowingOtherOpening = [&]() { 100 return OpeningLineIndex == 0 || 101 Lines[OpeningLineIndex - 1]->Last->opensScope() || 102 IsPPConditional(OpeningLineIndex - 1); 103 }; 104 const auto HasEnumOnLine = [&]() { 105 FormatToken *CurrentToken = CurrentLine->First; 106 bool FoundEnumKeyword = false; 107 while (CurrentToken) { 108 if (CurrentToken->is(tok::kw_enum)) 109 FoundEnumKeyword = true; 110 else if (FoundEnumKeyword && CurrentToken->is(tok::l_brace)) 111 return true; 112 CurrentToken = CurrentToken->Next; 113 } 114 return FoundEnumKeyword && I + 1 < Lines.size() && 115 Lines[I + 1]->First->is(tok::l_brace); 116 }; 117 118 bool IsDefBlock = false; 119 const auto MayPrecedeDefinition = [&](const int Direction = -1) { 120 assert(Direction >= -1); 121 assert(Direction <= 1); 122 const size_t OperateIndex = OpeningLineIndex + Direction; 123 assert(OperateIndex < Lines.size()); 124 const auto &OperateLine = Lines[OperateIndex]; 125 if (LikelyDefinition(OperateLine)) 126 return false; 127 128 if (OperateLine->First->is(tok::comment)) 129 return true; 130 131 // A single line identifier that is not in the last line. 132 if (OperateLine->First->is(tok::identifier) && 133 OperateLine->First == OperateLine->Last && 134 OperateIndex + 1 < Lines.size()) { 135 // UnwrappedLineParser's recognition of free-standing macro like 136 // Q_OBJECT may also recognize some uppercased type names that may be 137 // used as return type as that kind of macros, which is a bit hard to 138 // distinguish one from another purely from token patterns. Here, we 139 // try not to add new lines below those identifiers. 140 AnnotatedLine *NextLine = Lines[OperateIndex + 1]; 141 if (NextLine->MightBeFunctionDecl && 142 NextLine->mightBeFunctionDefinition() && 143 NextLine->First->NewlinesBefore == 1 && 144 OperateLine->First->is(TT_FunctionLikeOrFreestandingMacro)) 145 return true; 146 } 147 148 if ((Style.isCSharp() && OperateLine->First->is(TT_AttributeSquare))) 149 return true; 150 return false; 151 }; 152 153 if (HasEnumOnLine() && 154 !LikelyDefinition(CurrentLine, /*ExcludeEnum=*/true)) { 155 // We have no scope opening/closing information for enum. 156 IsDefBlock = true; 157 OpeningLineIndex = I; 158 while (OpeningLineIndex > 0 && MayPrecedeDefinition()) 159 --OpeningLineIndex; 160 OpeningLine = Lines[OpeningLineIndex]; 161 TargetLine = OpeningLine; 162 TargetToken = TargetLine->First; 163 if (!FollowingOtherOpening()) 164 InsertReplacement(NewlineCount); 165 else if (IsNeverStyle) 166 InsertReplacement(OpeningLineIndex != 0); 167 TargetLine = CurrentLine; 168 TargetToken = TargetLine->First; 169 while (TargetToken && !TargetToken->is(tok::r_brace)) 170 TargetToken = TargetToken->Next; 171 if (!TargetToken) { 172 while (I < Lines.size() && !Lines[I]->First->is(tok::r_brace)) 173 ++I; 174 } 175 } else if (CurrentLine->First->closesScope()) { 176 if (OpeningLineIndex > Lines.size()) 177 continue; 178 // Handling the case that opening brace has its own line, with checking 179 // whether the last line already had an opening brace to guard against 180 // misrecognition. 181 if (OpeningLineIndex > 0 && 182 Lines[OpeningLineIndex]->First->is(tok::l_brace) && 183 Lines[OpeningLineIndex - 1]->Last->isNot(tok::l_brace)) 184 --OpeningLineIndex; 185 OpeningLine = Lines[OpeningLineIndex]; 186 // Closing a function definition. 187 if (LikelyDefinition(OpeningLine)) { 188 IsDefBlock = true; 189 while (OpeningLineIndex > 0 && MayPrecedeDefinition()) 190 --OpeningLineIndex; 191 OpeningLine = Lines[OpeningLineIndex]; 192 TargetLine = OpeningLine; 193 TargetToken = TargetLine->First; 194 if (!FollowingOtherOpening()) { 195 // Avoid duplicated replacement. 196 if (TargetToken->isNot(tok::l_brace)) 197 InsertReplacement(NewlineCount); 198 } else if (IsNeverStyle) 199 InsertReplacement(OpeningLineIndex != 0); 200 } 201 } 202 203 // Not the last token. 204 if (IsDefBlock && I + 1 < Lines.size()) { 205 OpeningLineIndex = I + 1; 206 TargetLine = Lines[OpeningLineIndex]; 207 TargetToken = TargetLine->First; 208 209 // No empty line for continuously closing scopes. The token will be 210 // handled in another case if the line following is opening a 211 // definition. 212 if (!TargetToken->closesScope() && !IsPPConditional(OpeningLineIndex)) { 213 // Check whether current line may precede a definition line. 214 while (OpeningLineIndex + 1 < Lines.size() && 215 MayPrecedeDefinition(/*Direction=*/0)) 216 ++OpeningLineIndex; 217 TargetLine = Lines[OpeningLineIndex]; 218 if (!LikelyDefinition(TargetLine)) { 219 OpeningLineIndex = I + 1; 220 TargetLine = Lines[I + 1]; 221 TargetToken = TargetLine->First; 222 InsertReplacement(NewlineCount); 223 } 224 } else if (IsNeverStyle) 225 InsertReplacement(/*NewlineToInsert=*/1); 226 } 227 } 228 for (const auto &R : Whitespaces.generateReplacements()) 229 // The add method returns an Error instance which simulates program exit 230 // code through overloading boolean operator, thus false here indicates 231 // success. 232 if (Result.add(R)) 233 return; 234 } 235 } // namespace format 236 } // namespace clang 237