//===--- 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 DefinitionBlockSeparator::analyze( TokenAnnotator &Annotator, SmallVectorImpl &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 &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.DeriveLineEnding ? WhitespaceManager::inputUsesCRLF( Env.getSourceManager().getBufferData(Env.getFileID()), Style.UseCRLF) : Style.UseCRLF); 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 (OperateLine->First->is(tok::comment)) 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->is(tok::r_brace)) TargetToken = TargetToken->Next; if (!TargetToken) { while (I < Lines.size() && !Lines[I]->First->is(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