Path: blob/main/contrib/llvm-project/clang/lib/Format/DefinitionBlockSeparator.cpp
35233 views
//===--- DefinitionBlockSeparator.cpp ---------------------------*- C++ -*-===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7///8/// \file9/// This file implements DefinitionBlockSeparator, a TokenAnalyzer that inserts10/// or removes empty lines separating definition blocks like classes, structs,11/// functions, enums, and namespaces in between.12///13//===----------------------------------------------------------------------===//1415#include "DefinitionBlockSeparator.h"16#include "llvm/Support/Debug.h"17#define DEBUG_TYPE "definition-block-separator"1819namespace clang {20namespace format {21std::pair<tooling::Replacements, unsigned> DefinitionBlockSeparator::analyze(22TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,23FormatTokenLexer &Tokens) {24assert(Style.SeparateDefinitionBlocks != FormatStyle::SDS_Leave);25AffectedRangeMgr.computeAffectedLines(AnnotatedLines);26tooling::Replacements Result;27separateBlocks(AnnotatedLines, Result, Tokens);28return {Result, 0};29}3031void DefinitionBlockSeparator::separateBlocks(32SmallVectorImpl<AnnotatedLine *> &Lines, tooling::Replacements &Result,33FormatTokenLexer &Tokens) {34const bool IsNeverStyle =35Style.SeparateDefinitionBlocks == FormatStyle::SDS_Never;36const AdditionalKeywords &ExtraKeywords = Tokens.getKeywords();37auto GetBracketLevelChange = [](const FormatToken *Tok) {38if (Tok->isOneOf(tok::l_brace, tok::l_paren, tok::l_square))39return 1;40if (Tok->isOneOf(tok::r_brace, tok::r_paren, tok::r_square))41return -1;42return 0;43};44auto LikelyDefinition = [&](const AnnotatedLine *Line,45bool ExcludeEnum = false) {46if ((Line->MightBeFunctionDecl && Line->mightBeFunctionDefinition()) ||47Line->startsWithNamespace()) {48return true;49}50int BracketLevel = 0;51for (const FormatToken *CurrentToken = Line->First; CurrentToken;52CurrentToken = CurrentToken->Next) {53if (BracketLevel == 0) {54if (CurrentToken->isOneOf(tok::kw_class, tok::kw_struct,55tok::kw_union) ||56(Style.isJavaScript() &&57CurrentToken->is(ExtraKeywords.kw_function))) {58return true;59}60if (!ExcludeEnum && CurrentToken->is(tok::kw_enum))61return true;62}63BracketLevel += GetBracketLevelChange(CurrentToken);64}65return false;66};67unsigned NewlineCount =68(Style.SeparateDefinitionBlocks == FormatStyle::SDS_Always ? 1 : 0) + 1;69WhitespaceManager Whitespaces(70Env.getSourceManager(), Style,71Style.LineEnding > FormatStyle::LE_CRLF72? WhitespaceManager::inputUsesCRLF(73Env.getSourceManager().getBufferData(Env.getFileID()),74Style.LineEnding == FormatStyle::LE_DeriveCRLF)75: Style.LineEnding == FormatStyle::LE_CRLF);76for (unsigned I = 0; I < Lines.size(); ++I) {77const auto &CurrentLine = Lines[I];78if (CurrentLine->InPPDirective)79continue;80FormatToken *TargetToken = nullptr;81AnnotatedLine *TargetLine;82auto OpeningLineIndex = CurrentLine->MatchingOpeningBlockLineIndex;83AnnotatedLine *OpeningLine = nullptr;84const auto IsAccessSpecifierToken = [](const FormatToken *Token) {85return Token->isAccessSpecifier() || Token->isObjCAccessSpecifier();86};87const auto InsertReplacement = [&](const int NewlineToInsert) {88assert(TargetLine);89assert(TargetToken);9091// Do not handle EOF newlines.92if (TargetToken->is(tok::eof))93return;94if (IsAccessSpecifierToken(TargetToken) ||95(OpeningLineIndex > 0 &&96IsAccessSpecifierToken(Lines[OpeningLineIndex - 1]->First))) {97return;98}99if (!TargetLine->Affected)100return;101Whitespaces.replaceWhitespace(*TargetToken, NewlineToInsert,102TargetToken->OriginalColumn,103TargetToken->OriginalColumn);104};105const auto IsPPConditional = [&](const size_t LineIndex) {106const auto &Line = Lines[LineIndex];107return Line->First->is(tok::hash) && Line->First->Next &&108Line->First->Next->isOneOf(tok::pp_if, tok::pp_ifdef, tok::pp_else,109tok::pp_ifndef, tok::pp_elifndef,110tok::pp_elifdef, tok::pp_elif,111tok::pp_endif);112};113const auto FollowingOtherOpening = [&]() {114return OpeningLineIndex == 0 ||115Lines[OpeningLineIndex - 1]->Last->opensScope() ||116IsPPConditional(OpeningLineIndex - 1);117};118const auto HasEnumOnLine = [&]() {119bool FoundEnumKeyword = false;120int BracketLevel = 0;121for (const FormatToken *CurrentToken = CurrentLine->First; CurrentToken;122CurrentToken = CurrentToken->Next) {123if (BracketLevel == 0) {124if (CurrentToken->is(tok::kw_enum))125FoundEnumKeyword = true;126else if (FoundEnumKeyword && CurrentToken->is(tok::l_brace))127return true;128}129BracketLevel += GetBracketLevelChange(CurrentToken);130}131return FoundEnumKeyword && I + 1 < Lines.size() &&132Lines[I + 1]->First->is(tok::l_brace);133};134135bool IsDefBlock = false;136const auto MayPrecedeDefinition = [&](const int Direction = -1) {137assert(Direction >= -1);138assert(Direction <= 1);139const size_t OperateIndex = OpeningLineIndex + Direction;140assert(OperateIndex < Lines.size());141const auto &OperateLine = Lines[OperateIndex];142if (LikelyDefinition(OperateLine))143return false;144145if (const auto *Tok = OperateLine->First;146Tok->is(tok::comment) && !isClangFormatOn(Tok->TokenText)) {147return true;148}149150// A single line identifier that is not in the last line.151if (OperateLine->First->is(tok::identifier) &&152OperateLine->First == OperateLine->Last &&153OperateIndex + 1 < Lines.size()) {154// UnwrappedLineParser's recognition of free-standing macro like155// Q_OBJECT may also recognize some uppercased type names that may be156// used as return type as that kind of macros, which is a bit hard to157// distinguish one from another purely from token patterns. Here, we158// try not to add new lines below those identifiers.159AnnotatedLine *NextLine = Lines[OperateIndex + 1];160if (NextLine->MightBeFunctionDecl &&161NextLine->mightBeFunctionDefinition() &&162NextLine->First->NewlinesBefore == 1 &&163OperateLine->First->is(TT_FunctionLikeOrFreestandingMacro)) {164return true;165}166}167168if (Style.isCSharp() && OperateLine->First->is(TT_AttributeSquare))169return true;170return false;171};172173if (HasEnumOnLine() &&174!LikelyDefinition(CurrentLine, /*ExcludeEnum=*/true)) {175// We have no scope opening/closing information for enum.176IsDefBlock = true;177OpeningLineIndex = I;178while (OpeningLineIndex > 0 && MayPrecedeDefinition())179--OpeningLineIndex;180OpeningLine = Lines[OpeningLineIndex];181TargetLine = OpeningLine;182TargetToken = TargetLine->First;183if (!FollowingOtherOpening())184InsertReplacement(NewlineCount);185else if (IsNeverStyle)186InsertReplacement(OpeningLineIndex != 0);187TargetLine = CurrentLine;188TargetToken = TargetLine->First;189while (TargetToken && TargetToken->isNot(tok::r_brace))190TargetToken = TargetToken->Next;191if (!TargetToken)192while (I < Lines.size() && Lines[I]->First->isNot(tok::r_brace))193++I;194} else if (CurrentLine->First->closesScope()) {195if (OpeningLineIndex > Lines.size())196continue;197// Handling the case that opening brace has its own line, with checking198// whether the last line already had an opening brace to guard against199// misrecognition.200if (OpeningLineIndex > 0 &&201Lines[OpeningLineIndex]->First->is(tok::l_brace) &&202Lines[OpeningLineIndex - 1]->Last->isNot(tok::l_brace)) {203--OpeningLineIndex;204}205OpeningLine = Lines[OpeningLineIndex];206// Closing a function definition.207if (LikelyDefinition(OpeningLine)) {208IsDefBlock = true;209while (OpeningLineIndex > 0 && MayPrecedeDefinition())210--OpeningLineIndex;211OpeningLine = Lines[OpeningLineIndex];212TargetLine = OpeningLine;213TargetToken = TargetLine->First;214if (!FollowingOtherOpening()) {215// Avoid duplicated replacement.216if (TargetToken->isNot(tok::l_brace))217InsertReplacement(NewlineCount);218} else if (IsNeverStyle) {219InsertReplacement(OpeningLineIndex != 0);220}221}222}223224// Not the last token.225if (IsDefBlock && I + 1 < Lines.size()) {226OpeningLineIndex = I + 1;227TargetLine = Lines[OpeningLineIndex];228TargetToken = TargetLine->First;229230// No empty line for continuously closing scopes. The token will be231// handled in another case if the line following is opening a232// definition.233if (!TargetToken->closesScope() && !IsPPConditional(OpeningLineIndex)) {234// Check whether current line may precede a definition line.235while (OpeningLineIndex + 1 < Lines.size() &&236MayPrecedeDefinition(/*Direction=*/0)) {237++OpeningLineIndex;238}239TargetLine = Lines[OpeningLineIndex];240if (!LikelyDefinition(TargetLine)) {241OpeningLineIndex = I + 1;242TargetLine = Lines[I + 1];243TargetToken = TargetLine->First;244InsertReplacement(NewlineCount);245}246} else if (IsNeverStyle) {247InsertReplacement(/*NewlineToInsert=*/1);248}249}250}251for (const auto &R : Whitespaces.generateReplacements()) {252// The add method returns an Error instance which simulates program exit253// code through overloading boolean operator, thus false here indicates254// success.255if (Result.add(R))256return;257}258}259} // namespace format260} // namespace clang261262263