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 } else if (CurrentLine->First->closesScope()) { 175 if (OpeningLineIndex > Lines.size()) 176 continue; 177 // Handling the case that opening brace has its own line, with checking 178 // whether the last line already had an opening brace to guard against 179 // misrecognition. 180 if (OpeningLineIndex > 0 && 181 Lines[OpeningLineIndex]->First->is(tok::l_brace) && 182 Lines[OpeningLineIndex - 1]->Last->isNot(tok::l_brace)) 183 --OpeningLineIndex; 184 OpeningLine = Lines[OpeningLineIndex]; 185 // Closing a function definition. 186 if (LikelyDefinition(OpeningLine)) { 187 IsDefBlock = true; 188 while (OpeningLineIndex > 0 && MayPrecedeDefinition()) 189 --OpeningLineIndex; 190 OpeningLine = Lines[OpeningLineIndex]; 191 TargetLine = OpeningLine; 192 TargetToken = TargetLine->First; 193 if (!FollowingOtherOpening()) { 194 // Avoid duplicated replacement. 195 if (TargetToken->isNot(tok::l_brace)) 196 InsertReplacement(NewlineCount); 197 } else if (IsNeverStyle) 198 InsertReplacement(OpeningLineIndex != 0); 199 } 200 } 201 202 // Not the last token. 203 if (IsDefBlock && I + 1 < Lines.size()) { 204 OpeningLineIndex = I + 1; 205 TargetLine = Lines[OpeningLineIndex]; 206 TargetToken = TargetLine->First; 207 208 // No empty line for continuously closing scopes. The token will be 209 // handled in another case if the line following is opening a 210 // definition. 211 if (!TargetToken->closesScope() && !IsPPConditional(OpeningLineIndex)) { 212 // Check whether current line may precede a definition line. 213 while (OpeningLineIndex + 1 < Lines.size() && 214 MayPrecedeDefinition(/*Direction=*/0)) 215 ++OpeningLineIndex; 216 TargetLine = Lines[OpeningLineIndex]; 217 if (!LikelyDefinition(TargetLine)) { 218 OpeningLineIndex = I + 1; 219 TargetLine = Lines[I + 1]; 220 TargetToken = TargetLine->First; 221 InsertReplacement(NewlineCount); 222 } 223 } else if (IsNeverStyle) 224 InsertReplacement(/*NewlineToInsert=*/1); 225 } 226 } 227 for (const auto &R : Whitespaces.generateReplacements()) 228 // The add method returns an Error instance which simulates program exit 229 // code through overloading boolean operator, thus false here indicates 230 // success. 231 if (Result.add(R)) 232 return; 233 } 234 } // namespace format 235 } // namespace clang 236