| //===--- DefinitionBlockSeparator.cpp ---------------------------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| /// |
| /// \file |
| /// This file implements DefinitionBlockSeparator, a TokenAnalyzer that inserts |
| /// or removes empty lines separating definition blocks like classes, structs, |
| /// functions, enums, and namespaces in between. |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #include "DefinitionBlockSeparator.h" |
| #include "llvm/Support/Debug.h" |
| #define DEBUG_TYPE "definition-block-separator" |
| |
| namespace clang { |
| namespace format { |
| std::pair<tooling::Replacements, unsigned> DefinitionBlockSeparator::analyze( |
| TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines, |
| FormatTokenLexer &Tokens) { |
| assert(Style.SeparateDefinitionBlocks != FormatStyle::SDS_Leave); |
| AffectedRangeMgr.computeAffectedLines(AnnotatedLines); |
| tooling::Replacements Result; |
| separateBlocks(AnnotatedLines, Result, Tokens); |
| return {Result, 0}; |
| } |
| |
| void DefinitionBlockSeparator::separateBlocks( |
| SmallVectorImpl<AnnotatedLine *> &Lines, tooling::Replacements &Result, |
| FormatTokenLexer &Tokens) { |
| const bool IsNeverStyle = |
| Style.SeparateDefinitionBlocks == FormatStyle::SDS_Never; |
| const AdditionalKeywords &ExtraKeywords = Tokens.getKeywords(); |
| auto GetBracketLevelChange = [](const FormatToken *Tok) { |
| if (Tok->isOneOf(tok::l_brace, tok::l_paren, tok::l_square)) |
| return 1; |
| if (Tok->isOneOf(tok::r_brace, tok::r_paren, tok::r_square)) |
| return -1; |
| return 0; |
| }; |
| auto LikelyDefinition = [&](const AnnotatedLine *Line, |
| bool ExcludeEnum = false) { |
| if ((Line->MightBeFunctionDecl && Line->mightBeFunctionDefinition()) || |
| Line->startsWithNamespace()) { |
| return true; |
| } |
| int BracketLevel = 0; |
| for (const FormatToken *CurrentToken = Line->First; CurrentToken; |
| CurrentToken = CurrentToken->Next) { |
| if (BracketLevel == 0) { |
| if (CurrentToken->isOneOf(tok::kw_class, tok::kw_struct, |
| tok::kw_union) || |
| (Style.isJavaScript() && |
| CurrentToken->is(ExtraKeywords.kw_function))) { |
| return true; |
| } |
| if (!ExcludeEnum && CurrentToken->is(tok::kw_enum)) |
| return true; |
| } |
| BracketLevel += GetBracketLevelChange(CurrentToken); |
| } |
| return false; |
| }; |
| unsigned NewlineCount = |
| (Style.SeparateDefinitionBlocks == FormatStyle::SDS_Always ? 1 : 0) + 1; |
| WhitespaceManager Whitespaces( |
| Env.getSourceManager(), Style, |
| Style.LineEnding > FormatStyle::LE_CRLF |
| ? WhitespaceManager::inputUsesCRLF( |
| Env.getSourceManager().getBufferData(Env.getFileID()), |
| Style.LineEnding == FormatStyle::LE_DeriveCRLF) |
| : Style.LineEnding == FormatStyle::LE_CRLF); |
| for (unsigned I = 0; I < Lines.size(); ++I) { |
| const auto &CurrentLine = Lines[I]; |
| if (CurrentLine->InPPDirective) |
| continue; |
| FormatToken *TargetToken = nullptr; |
| AnnotatedLine *TargetLine; |
| auto OpeningLineIndex = CurrentLine->MatchingOpeningBlockLineIndex; |
| AnnotatedLine *OpeningLine = nullptr; |
| const auto IsAccessSpecifierToken = [](const FormatToken *Token) { |
| return Token->isAccessSpecifier() || Token->isObjCAccessSpecifier(); |
| }; |
| const auto InsertReplacement = [&](const int NewlineToInsert) { |
| assert(TargetLine); |
| assert(TargetToken); |
| |
| // Do not handle EOF newlines. |
| if (TargetToken->is(tok::eof)) |
| return; |
| if (IsAccessSpecifierToken(TargetToken) || |
| (OpeningLineIndex > 0 && |
| IsAccessSpecifierToken(Lines[OpeningLineIndex - 1]->First))) { |
| return; |
| } |
| if (!TargetLine->Affected) |
| return; |
| Whitespaces.replaceWhitespace(*TargetToken, NewlineToInsert, |
| TargetToken->OriginalColumn, |
| TargetToken->OriginalColumn); |
| }; |
| const auto IsPPConditional = [&](const size_t LineIndex) { |
| const auto &Line = Lines[LineIndex]; |
| return Line->First->is(tok::hash) && Line->First->Next && |
| Line->First->Next->isOneOf(tok::pp_if, tok::pp_ifdef, tok::pp_else, |
| tok::pp_ifndef, tok::pp_elifndef, |
| tok::pp_elifdef, tok::pp_elif, |
| tok::pp_endif); |
| }; |
| const auto FollowingOtherOpening = [&]() { |
| return OpeningLineIndex == 0 || |
| Lines[OpeningLineIndex - 1]->Last->opensScope() || |
| IsPPConditional(OpeningLineIndex - 1); |
| }; |
| const auto HasEnumOnLine = [&]() { |
| bool FoundEnumKeyword = false; |
| int BracketLevel = 0; |
| for (const FormatToken *CurrentToken = CurrentLine->First; CurrentToken; |
| CurrentToken = CurrentToken->Next) { |
| if (BracketLevel == 0) { |
| if (CurrentToken->is(tok::kw_enum)) |
| FoundEnumKeyword = true; |
| else if (FoundEnumKeyword && CurrentToken->is(tok::l_brace)) |
| return true; |
| } |
| BracketLevel += GetBracketLevelChange(CurrentToken); |
| } |
| return FoundEnumKeyword && I + 1 < Lines.size() && |
| Lines[I + 1]->First->is(tok::l_brace); |
| }; |
| |
| bool IsDefBlock = false; |
| const auto MayPrecedeDefinition = [&](const int Direction = -1) { |
| assert(Direction >= -1); |
| assert(Direction <= 1); |
| const size_t OperateIndex = OpeningLineIndex + Direction; |
| assert(OperateIndex < Lines.size()); |
| const auto &OperateLine = Lines[OperateIndex]; |
| if (LikelyDefinition(OperateLine)) |
| return false; |
| |
| if (const auto *Tok = OperateLine->First; |
| Tok->is(tok::comment) && !isClangFormatOn(Tok->TokenText)) { |
| return true; |
| } |
| |
| // A single line identifier that is not in the last line. |
| if (OperateLine->First->is(tok::identifier) && |
| OperateLine->First == OperateLine->Last && |
| OperateIndex + 1 < Lines.size()) { |
| // UnwrappedLineParser's recognition of free-standing macro like |
| // Q_OBJECT may also recognize some uppercased type names that may be |
| // used as return type as that kind of macros, which is a bit hard to |
| // distinguish one from another purely from token patterns. Here, we |
| // try not to add new lines below those identifiers. |
| AnnotatedLine *NextLine = Lines[OperateIndex + 1]; |
| if (NextLine->MightBeFunctionDecl && |
| NextLine->mightBeFunctionDefinition() && |
| NextLine->First->NewlinesBefore == 1 && |
| OperateLine->First->is(TT_FunctionLikeOrFreestandingMacro)) { |
| return true; |
| } |
| } |
| |
| if (Style.isCSharp() && OperateLine->First->is(TT_AttributeSquare)) |
| return true; |
| return false; |
| }; |
| |
| if (HasEnumOnLine() && |
| !LikelyDefinition(CurrentLine, /*ExcludeEnum=*/true)) { |
| // We have no scope opening/closing information for enum. |
| IsDefBlock = true; |
| OpeningLineIndex = I; |
| while (OpeningLineIndex > 0 && MayPrecedeDefinition()) |
| --OpeningLineIndex; |
| OpeningLine = Lines[OpeningLineIndex]; |
| TargetLine = OpeningLine; |
| TargetToken = TargetLine->First; |
| if (!FollowingOtherOpening()) |
| InsertReplacement(NewlineCount); |
| else if (IsNeverStyle) |
| InsertReplacement(OpeningLineIndex != 0); |
| TargetLine = CurrentLine; |
| TargetToken = TargetLine->First; |
| while (TargetToken && TargetToken->isNot(tok::r_brace)) |
| TargetToken = TargetToken->Next; |
| if (!TargetToken) |
| while (I < Lines.size() && Lines[I]->First->isNot(tok::r_brace)) |
| ++I; |
| } else if (CurrentLine->First->closesScope()) { |
| if (OpeningLineIndex > Lines.size()) |
| continue; |
| // Handling the case that opening brace has its own line, with checking |
| // whether the last line already had an opening brace to guard against |
| // misrecognition. |
| if (OpeningLineIndex > 0 && |
| Lines[OpeningLineIndex]->First->is(tok::l_brace) && |
| Lines[OpeningLineIndex - 1]->Last->isNot(tok::l_brace)) { |
| --OpeningLineIndex; |
| } |
| OpeningLine = Lines[OpeningLineIndex]; |
| // Closing a function definition. |
| if (LikelyDefinition(OpeningLine)) { |
| IsDefBlock = true; |
| while (OpeningLineIndex > 0 && MayPrecedeDefinition()) |
| --OpeningLineIndex; |
| OpeningLine = Lines[OpeningLineIndex]; |
| TargetLine = OpeningLine; |
| TargetToken = TargetLine->First; |
| if (!FollowingOtherOpening()) { |
| // Avoid duplicated replacement. |
| if (TargetToken->isNot(tok::l_brace)) |
| InsertReplacement(NewlineCount); |
| } else if (IsNeverStyle) { |
| InsertReplacement(OpeningLineIndex != 0); |
| } |
| } |
| } |
| |
| // Not the last token. |
| if (IsDefBlock && I + 1 < Lines.size()) { |
| OpeningLineIndex = I + 1; |
| TargetLine = Lines[OpeningLineIndex]; |
| TargetToken = TargetLine->First; |
| |
| // No empty line for continuously closing scopes. The token will be |
| // handled in another case if the line following is opening a |
| // definition. |
| if (!TargetToken->closesScope() && !IsPPConditional(OpeningLineIndex)) { |
| // Check whether current line may precede a definition line. |
| while (OpeningLineIndex + 1 < Lines.size() && |
| MayPrecedeDefinition(/*Direction=*/0)) { |
| ++OpeningLineIndex; |
| } |
| TargetLine = Lines[OpeningLineIndex]; |
| if (!LikelyDefinition(TargetLine)) { |
| OpeningLineIndex = I + 1; |
| TargetLine = Lines[I + 1]; |
| TargetToken = TargetLine->First; |
| InsertReplacement(NewlineCount); |
| } |
| } else if (IsNeverStyle) { |
| InsertReplacement(/*NewlineToInsert=*/1); |
| } |
| } |
| } |
| for (const auto &R : Whitespaces.generateReplacements()) { |
| // The add method returns an Error instance which simulates program exit |
| // code through overloading boolean operator, thus false here indicates |
| // success. |
| if (Result.add(R)) |
| return; |
| } |
| } |
| } // namespace format |
| } // namespace clang |