mirror of
https://github.com/intel/llvm.git
synced 2026-02-02 10:08:59 +08:00
- Add or remove empty lines surrounding union blocks. - Fixes https://github.com/llvm/llvm-project/issues/53229, in which keywords like class and struct in a line ending with left brace or whose next line is left brace only, will be falsely recognized as definition line, causing extra empty lines inserted surrounding blocks with no need to be formatted. Reviewed By: MyDeveloperDay, curdeius, HazardyKnusperkeks, owenpan Differential Revision: https://reviews.llvm.org/D119067
251 lines
10 KiB
C++
251 lines
10 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, 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.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
|