diff options
Diffstat (limited to 'clang/lib/Format/DefinitionBlockSeparator.cpp')
-rw-r--r-- | clang/lib/Format/DefinitionBlockSeparator.cpp | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/clang/lib/Format/DefinitionBlockSeparator.cpp b/clang/lib/Format/DefinitionBlockSeparator.cpp new file mode 100644 index 000000000000..827564357f78 --- /dev/null +++ b/clang/lib/Format/DefinitionBlockSeparator.cpp @@ -0,0 +1,236 @@ +//===--- 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 LikelyDefinition = [this, ExtraKeywords](const AnnotatedLine *Line, + bool ExcludeEnum = false) { + if ((Line->MightBeFunctionDecl && Line->mightBeFunctionDefinition()) || + Line->startsWithNamespace()) + return true; + FormatToken *CurrentToken = Line->First; + while (CurrentToken) { + if (CurrentToken->isOneOf(tok::kw_class, tok::kw_struct) || + (Style.isJavaScript() && CurrentToken->is(ExtraKeywords.kw_function))) + return true; + if (!ExcludeEnum && CurrentToken->is(tok::kw_enum)) + return true; + CurrentToken = CurrentToken->Next; + } + 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 = [&]() { + FormatToken *CurrentToken = CurrentLine->First; + bool FoundEnumKeyword = false; + while (CurrentToken) { + if (CurrentToken->is(tok::kw_enum)) + FoundEnumKeyword = true; + else if (FoundEnumKeyword && CurrentToken->is(tok::l_brace)) + return true; + CurrentToken = CurrentToken->Next; + } + 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 |