llvm-project/clang/lib/Format/DefinitionBlockSeparator.cpp

158 lines
5.8 KiB
C++

//===--- 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);
return {Result, 0};
}
void DefinitionBlockSeparator::separateBlocks(
SmallVectorImpl<AnnotatedLine *> &Lines, tooling::Replacements &Result) {
auto LikelyDefinition = [this](const AnnotatedLine *Line) {
if (Line->MightBeFunctionDecl && Line->mightBeFunctionDefinition())
return true;
FormatToken *CurrentToken = Line->First;
while (CurrentToken) {
if (CurrentToken->isOneOf(tok::kw_class, tok::kw_struct,
tok::kw_namespace, tok::kw_enum) ||
(Style.Language == FormatStyle::LK_JavaScript &&
CurrentToken->TokenText == "function"))
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];
FormatToken *TargetToken = nullptr;
AnnotatedLine *TargetLine;
auto OpeningLineIndex = CurrentLine->MatchingOpeningBlockLineIndex;
const auto InsertReplacement = [&](const int NewlineToInsert) {
assert(TargetLine);
assert(TargetToken);
// Do not handle EOF newlines.
if (TargetToken->is(tok::eof) && NewlineToInsert > 0)
return;
if (!TargetLine->Affected)
return;
Whitespaces.replaceWhitespace(*TargetToken, NewlineToInsert,
TargetToken->SpacesRequiredBefore - 1,
TargetToken->StartsColumn);
};
const auto FollowingOtherOpening = [&]() {
return OpeningLineIndex == 0 ||
Lines[OpeningLineIndex - 1]->Last->opensScope();
};
const auto HasEnumOnLine = [CurrentLine]() {
FormatToken *CurrentToken = CurrentLine->First;
while (CurrentToken) {
if (CurrentToken->is(tok::kw_enum))
return true;
CurrentToken = CurrentToken->Next;
}
return false;
};
bool IsDefBlock = 0;
if (HasEnumOnLine()) {
// We have no scope opening/closing information for enum.
IsDefBlock = 1;
OpeningLineIndex = I;
TargetLine = CurrentLine;
TargetToken = CurrentLine->First;
if (!FollowingOtherOpening())
InsertReplacement(NewlineCount);
else
InsertReplacement(OpeningLineIndex != 0);
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 bracket has its own line.
OpeningLineIndex -= Lines[OpeningLineIndex]->First->TokenText == "{";
AnnotatedLine *OpeningLine = Lines[OpeningLineIndex];
// Closing a function definition.
if (LikelyDefinition(OpeningLine)) {
IsDefBlock = 1;
if (OpeningLineIndex > 0) {
OpeningLineIndex -=
Style.Language == FormatStyle::LK_CSharp &&
Lines[OpeningLineIndex - 1]->First->is(tok::l_square);
OpeningLine = Lines[OpeningLineIndex];
}
TargetLine = OpeningLine;
TargetToken = TargetLine->First;
if (!FollowingOtherOpening()) {
// Avoid duplicated replacement.
if (!TargetToken->opensScope())
InsertReplacement(NewlineCount);
} else
InsertReplacement(OpeningLineIndex != 0);
}
}
// Not the last token.
if (IsDefBlock && I + 1 < Lines.size()) {
TargetLine = Lines[I + 1];
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()) {
if (!LikelyDefinition(TargetLine))
InsertReplacement(NewlineCount);
} else {
InsertReplacement(OpeningLineIndex != 0);
}
}
}
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