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); 29 return {Result, 0}; 30 } 31 32 void DefinitionBlockSeparator::separateBlocks( 33 SmallVectorImpl<AnnotatedLine *> &Lines, tooling::Replacements &Result) { 34 const bool IsNeverStyle = 35 Style.SeparateDefinitionBlocks == FormatStyle::SDS_Never; 36 auto LikelyDefinition = [this](const AnnotatedLine *Line) { 37 if ((Line->MightBeFunctionDecl && Line->mightBeFunctionDefinition()) || 38 Line->startsWithNamespace()) 39 return true; 40 FormatToken *CurrentToken = Line->First; 41 while (CurrentToken) { 42 if (CurrentToken->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_enum) || 43 (Style.isJavaScript() && CurrentToken->TokenText == "function")) 44 return true; 45 CurrentToken = CurrentToken->Next; 46 } 47 return false; 48 }; 49 unsigned NewlineCount = 50 (Style.SeparateDefinitionBlocks == FormatStyle::SDS_Always ? 1 : 0) + 1; 51 WhitespaceManager Whitespaces( 52 Env.getSourceManager(), Style, 53 Style.DeriveLineEnding 54 ? WhitespaceManager::inputUsesCRLF( 55 Env.getSourceManager().getBufferData(Env.getFileID()), 56 Style.UseCRLF) 57 : Style.UseCRLF); 58 for (unsigned I = 0; I < Lines.size(); ++I) { 59 const auto &CurrentLine = Lines[I]; 60 if (CurrentLine->InPPDirective) 61 continue; 62 FormatToken *TargetToken = nullptr; 63 AnnotatedLine *TargetLine; 64 auto OpeningLineIndex = CurrentLine->MatchingOpeningBlockLineIndex; 65 AnnotatedLine *OpeningLine = nullptr; 66 const auto InsertReplacement = [&](const int NewlineToInsert) { 67 assert(TargetLine); 68 assert(TargetToken); 69 70 // Do not handle EOF newlines. 71 if (TargetToken->is(tok::eof) && NewlineToInsert > 0) 72 return; 73 if (!TargetLine->Affected) 74 return; 75 Whitespaces.replaceWhitespace(*TargetToken, NewlineToInsert, 76 TargetToken->SpacesRequiredBefore - 1, 77 TargetToken->StartsColumn); 78 }; 79 const auto IsPPConditional = [&](const size_t LineIndex) { 80 const auto &Line = Lines[LineIndex]; 81 return Line->First->is(tok::hash) && Line->First->Next && 82 Line->First->Next->isOneOf(tok::pp_if, tok::pp_ifdef, tok::pp_else, 83 tok::pp_ifndef, tok::pp_elifndef, 84 tok::pp_elifdef, tok::pp_elif, 85 tok::pp_endif); 86 }; 87 const auto FollowingOtherOpening = [&]() { 88 return OpeningLineIndex == 0 || 89 Lines[OpeningLineIndex - 1]->Last->opensScope() || 90 IsPPConditional(OpeningLineIndex - 1); 91 }; 92 const auto HasEnumOnLine = [CurrentLine]() { 93 FormatToken *CurrentToken = CurrentLine->First; 94 while (CurrentToken) { 95 if (CurrentToken->is(tok::kw_enum)) 96 return true; 97 CurrentToken = CurrentToken->Next; 98 } 99 return false; 100 }; 101 102 bool IsDefBlock = false; 103 const auto MayPrecedeDefinition = [&](const int Direction = -1) { 104 const size_t OperateIndex = OpeningLineIndex + Direction; 105 assert(OperateIndex < Lines.size()); 106 const auto &OperateLine = Lines[OperateIndex]; 107 return (Style.isCSharp() && OperateLine->First->is(TT_AttributeSquare)) || 108 OperateLine->First->is(tok::comment); 109 }; 110 111 if (HasEnumOnLine()) { 112 // We have no scope opening/closing information for enum. 113 IsDefBlock = true; 114 OpeningLineIndex = I; 115 while (OpeningLineIndex > 0 && MayPrecedeDefinition()) 116 --OpeningLineIndex; 117 OpeningLine = Lines[OpeningLineIndex]; 118 TargetLine = OpeningLine; 119 TargetToken = TargetLine->First; 120 if (!FollowingOtherOpening()) 121 InsertReplacement(NewlineCount); 122 else if (IsNeverStyle) 123 InsertReplacement(OpeningLineIndex != 0); 124 TargetLine = CurrentLine; 125 TargetToken = TargetLine->First; 126 while (TargetToken && !TargetToken->is(tok::r_brace)) 127 TargetToken = TargetToken->Next; 128 if (!TargetToken) { 129 while (I < Lines.size() && !Lines[I]->First->is(tok::r_brace)) 130 ++I; 131 } 132 } else if (CurrentLine->First->closesScope()) { 133 if (OpeningLineIndex > Lines.size()) 134 continue; 135 // Handling the case that opening bracket has its own line. 136 OpeningLineIndex -= Lines[OpeningLineIndex]->First->is(tok::l_brace); 137 OpeningLine = Lines[OpeningLineIndex]; 138 // Closing a function definition. 139 if (LikelyDefinition(OpeningLine)) { 140 IsDefBlock = true; 141 while (OpeningLineIndex > 0 && MayPrecedeDefinition()) 142 --OpeningLineIndex; 143 OpeningLine = Lines[OpeningLineIndex]; 144 TargetLine = OpeningLine; 145 TargetToken = TargetLine->First; 146 if (!FollowingOtherOpening()) { 147 // Avoid duplicated replacement. 148 if (TargetToken->isNot(tok::l_brace)) 149 InsertReplacement(NewlineCount); 150 } else if (IsNeverStyle) 151 InsertReplacement(OpeningLineIndex != 0); 152 } 153 } 154 155 // Not the last token. 156 if (IsDefBlock && I + 1 < Lines.size()) { 157 OpeningLineIndex = I + 1; 158 TargetLine = Lines[OpeningLineIndex]; 159 TargetToken = TargetLine->First; 160 161 // No empty line for continuously closing scopes. The token will be 162 // handled in another case if the line following is opening a 163 // definition. 164 if (!TargetToken->closesScope() && !IsPPConditional(OpeningLineIndex)) { 165 // Check whether current line may precede a definition line. 166 while (OpeningLineIndex + 1 < Lines.size() && 167 MayPrecedeDefinition(/*Direction=*/0)) 168 ++OpeningLineIndex; 169 TargetLine = Lines[OpeningLineIndex]; 170 if (!LikelyDefinition(TargetLine)) { 171 TargetLine = Lines[I + 1]; 172 TargetToken = TargetLine->First; 173 InsertReplacement(NewlineCount); 174 } 175 } else if (IsNeverStyle) { 176 TargetLine = Lines[I + 1]; 177 TargetToken = TargetLine->First; 178 InsertReplacement(OpeningLineIndex != 0); 179 } 180 } 181 } 182 for (const auto &R : Whitespaces.generateReplacements()) 183 // The add method returns an Error instance which simulates program exit 184 // code through overloading boolean operator, thus false here indicates 185 // success. 186 if (Result.add(R)) 187 return; 188 } 189 } // namespace format 190 } // namespace clang 191