265 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8///9/// \file10/// This file implements DefinitionBlockSeparator, a TokenAnalyzer that inserts11/// 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#define DEBUG_TYPE "definition-block-separator"18 19namespace clang {20namespace format {21std::pair<tooling::Replacements, unsigned> DefinitionBlockSeparator::analyze(22 TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,23 FormatTokenLexer &Tokens) {24 assert(Style.SeparateDefinitionBlocks != FormatStyle::SDS_Leave);25 AffectedRangeMgr.computeAffectedLines(AnnotatedLines);26 tooling::Replacements Result;27 separateBlocks(AnnotatedLines, Result, Tokens);28 return {Result, 0};29}30 31void DefinitionBlockSeparator::separateBlocks(32 SmallVectorImpl<AnnotatedLine *> &Lines, tooling::Replacements &Result,33 FormatTokenLexer &Tokens) {34 const bool IsNeverStyle =35 Style.SeparateDefinitionBlocks == FormatStyle::SDS_Never;36 const AdditionalKeywords &ExtraKeywords = Tokens.getKeywords();37 auto GetBracketLevelChange = [](const FormatToken *Tok) {38 if (Tok->isOneOf(tok::l_brace, tok::l_paren, tok::l_square))39 return 1;40 if (Tok->isOneOf(tok::r_brace, tok::r_paren, tok::r_square))41 return -1;42 return 0;43 };44 auto LikelyDefinition = [&](const AnnotatedLine *Line,45 bool ExcludeEnum = false) {46 if ((Line->MightBeFunctionDecl && Line->mightBeFunctionDefinition()) ||47 Line->startsWithNamespace()) {48 return true;49 }50 int BracketLevel = 0;51 for (const FormatToken *CurrentToken = Line->First; CurrentToken;52 CurrentToken = CurrentToken->Next) {53 if (BracketLevel == 0) {54 if (CurrentToken->isOneOf(tok::kw_class, tok::kw_struct,55 tok::kw_union) ||56 (Style.isJavaScript() &&57 CurrentToken->is(ExtraKeywords.kw_function))) {58 return true;59 }60 if (!ExcludeEnum && CurrentToken->is(tok::kw_enum))61 return true;62 }63 BracketLevel += GetBracketLevelChange(CurrentToken);64 }65 return false;66 };67 unsigned NewlineCount =68 (Style.SeparateDefinitionBlocks == FormatStyle::SDS_Always ? 1 : 0) + 1;69 WhitespaceManager Whitespaces(70 Env.getSourceManager(), Style,71 Style.LineEnding > FormatStyle::LE_CRLF72 ? WhitespaceManager::inputUsesCRLF(73 Env.getSourceManager().getBufferData(Env.getFileID()),74 Style.LineEnding == FormatStyle::LE_DeriveCRLF)75 : Style.LineEnding == FormatStyle::LE_CRLF);76 for (unsigned I = 0; I < Lines.size(); ++I) {77 const auto &CurrentLine = Lines[I];78 if (CurrentLine->InPPDirective)79 continue;80 FormatToken *TargetToken = nullptr;81 AnnotatedLine *TargetLine;82 auto OpeningLineIndex = CurrentLine->MatchingOpeningBlockLineIndex;83 AnnotatedLine *OpeningLine = nullptr;84 const auto IsAccessSpecifierToken = [](const FormatToken *Token) {85 return Token->isAccessSpecifier() || Token->isObjCAccessSpecifier();86 };87 const auto InsertReplacement = [&](const int NewlineToInsert) {88 assert(TargetLine);89 assert(TargetToken);90 91 // Do not handle EOF newlines.92 if (TargetToken->is(tok::eof))93 return;94 if (IsAccessSpecifierToken(TargetToken) ||95 (OpeningLineIndex > 0 &&96 IsAccessSpecifierToken(Lines[OpeningLineIndex - 1]->First))) {97 return;98 }99 if (!TargetLine->Affected)100 return;101 Whitespaces.replaceWhitespace(*TargetToken, NewlineToInsert,102 TargetToken->OriginalColumn,103 TargetToken->OriginalColumn);104 };105 const auto IsPPConditional = [&](const size_t LineIndex) {106 const auto &Line = Lines[LineIndex];107 return Line->First->is(tok::hash) && Line->First->Next &&108 Line->First->Next->isOneOf(tok::pp_if, tok::pp_ifdef, tok::pp_else,109 tok::pp_ifndef, tok::pp_elifndef,110 tok::pp_elifdef, tok::pp_elif,111 tok::pp_endif);112 };113 const auto FollowingOtherOpening = [&]() {114 return OpeningLineIndex == 0 ||115 Lines[OpeningLineIndex - 1]->Last->opensScope() ||116 IsPPConditional(OpeningLineIndex - 1);117 };118 const auto HasEnumOnLine = [&]() {119 bool FoundEnumKeyword = false;120 int BracketLevel = 0;121 for (const FormatToken *CurrentToken = CurrentLine->First; CurrentToken;122 CurrentToken = CurrentToken->Next) {123 if (BracketLevel == 0) {124 if (CurrentToken->is(tok::kw_enum))125 FoundEnumKeyword = true;126 else if (FoundEnumKeyword && CurrentToken->is(tok::l_brace))127 return true;128 }129 BracketLevel += GetBracketLevelChange(CurrentToken);130 }131 return FoundEnumKeyword && I + 1 < Lines.size() &&132 Lines[I + 1]->First->is(tok::l_brace);133 };134 135 bool IsDefBlock = false;136 const auto MayPrecedeDefinition = [&](const int Direction = -1) {137 assert(Direction >= -1);138 assert(Direction <= 1);139 140 if (Lines[OpeningLineIndex]->First->is(TT_CSharpGenericTypeConstraint))141 return true;142 143 const size_t OperateIndex = OpeningLineIndex + Direction;144 assert(OperateIndex < Lines.size());145 const auto &OperateLine = Lines[OperateIndex];146 if (LikelyDefinition(OperateLine))147 return false;148 149 if (const auto *Tok = OperateLine->First;150 Tok->is(tok::comment) && !isClangFormatOn(Tok->TokenText)) {151 return true;152 }153 154 // A single line identifier that is not in the last line.155 if (OperateLine->First->is(tok::identifier) &&156 OperateLine->First == OperateLine->Last &&157 OperateIndex + 1 < Lines.size()) {158 // UnwrappedLineParser's recognition of free-standing macro like159 // Q_OBJECT may also recognize some uppercased type names that may be160 // used as return type as that kind of macros, which is a bit hard to161 // distinguish one from another purely from token patterns. Here, we162 // try not to add new lines below those identifiers.163 AnnotatedLine *NextLine = Lines[OperateIndex + 1];164 if (NextLine->MightBeFunctionDecl &&165 NextLine->mightBeFunctionDefinition() &&166 NextLine->First->NewlinesBefore == 1 &&167 OperateLine->First->is(TT_FunctionLikeOrFreestandingMacro)) {168 return true;169 }170 }171 172 if (Style.isCSharp() && OperateLine->First->is(TT_AttributeLSquare))173 return true;174 return false;175 };176 177 if (HasEnumOnLine() &&178 !LikelyDefinition(CurrentLine, /*ExcludeEnum=*/true)) {179 // We have no scope opening/closing information for enum.180 IsDefBlock = true;181 OpeningLineIndex = I;182 while (OpeningLineIndex > 0 && MayPrecedeDefinition())183 --OpeningLineIndex;184 OpeningLine = Lines[OpeningLineIndex];185 TargetLine = OpeningLine;186 TargetToken = TargetLine->First;187 if (!FollowingOtherOpening())188 InsertReplacement(NewlineCount);189 else if (IsNeverStyle)190 InsertReplacement(OpeningLineIndex != 0);191 TargetLine = CurrentLine;192 TargetToken = TargetLine->First;193 while (TargetToken && TargetToken->isNot(tok::r_brace))194 TargetToken = TargetToken->Next;195 if (!TargetToken)196 while (I < Lines.size() && Lines[I]->First->isNot(tok::r_brace))197 ++I;198 } else if (CurrentLine->First->closesScope()) {199 if (OpeningLineIndex > Lines.size())200 continue;201 // Handling the case that opening brace has its own line, with checking202 // whether the last line already had an opening brace to guard against203 // misrecognition.204 if (OpeningLineIndex > 0 &&205 Lines[OpeningLineIndex]->First->is(tok::l_brace) &&206 Lines[OpeningLineIndex - 1]->Last->isNot(tok::l_brace)) {207 --OpeningLineIndex;208 }209 OpeningLine = Lines[OpeningLineIndex];210 // Closing a function definition.211 if (LikelyDefinition(OpeningLine)) {212 IsDefBlock = true;213 while (OpeningLineIndex > 0 && MayPrecedeDefinition())214 --OpeningLineIndex;215 OpeningLine = Lines[OpeningLineIndex];216 TargetLine = OpeningLine;217 TargetToken = TargetLine->First;218 if (!FollowingOtherOpening()) {219 // Avoid duplicated replacement.220 if (TargetToken->isNot(tok::l_brace))221 InsertReplacement(NewlineCount);222 } else if (IsNeverStyle) {223 InsertReplacement(OpeningLineIndex != 0);224 }225 }226 }227 228 // Not the last token.229 if (IsDefBlock && I + 1 < Lines.size()) {230 OpeningLineIndex = I + 1;231 TargetLine = Lines[OpeningLineIndex];232 TargetToken = TargetLine->First;233 234 // No empty line for continuously closing scopes. The token will be235 // handled in another case if the line following is opening a236 // definition.237 if (!TargetToken->closesScope() && !IsPPConditional(OpeningLineIndex)) {238 // Check whether current line may precede a definition line.239 while (OpeningLineIndex + 1 < Lines.size() &&240 MayPrecedeDefinition(/*Direction=*/0)) {241 ++OpeningLineIndex;242 }243 TargetLine = Lines[OpeningLineIndex];244 if (!LikelyDefinition(TargetLine)) {245 OpeningLineIndex = I + 1;246 TargetLine = Lines[I + 1];247 TargetToken = TargetLine->First;248 InsertReplacement(NewlineCount);249 }250 } else if (IsNeverStyle) {251 InsertReplacement(/*NewlineToInsert=*/1);252 }253 }254 }255 for (const auto &R : Whitespaces.generateReplacements()) {256 // The add method returns an Error instance which simulates program exit257 // code through overloading boolean operator, thus false here indicates258 // success.259 if (Result.add(R))260 return;261 }262}263} // namespace format264} // namespace clang265