xref: /freebsd-src/contrib/llvm-project/clang/lib/Format/DefinitionBlockSeparator.cpp (revision d56accc7c3dcc897489b6a07834763a03b9f3d68)
104eeddc0SDimitry Andric //===--- DefinitionBlockSeparator.cpp ---------------------------*- C++ -*-===//
204eeddc0SDimitry Andric //
304eeddc0SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
404eeddc0SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
504eeddc0SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
604eeddc0SDimitry Andric //
704eeddc0SDimitry Andric //===----------------------------------------------------------------------===//
804eeddc0SDimitry Andric ///
904eeddc0SDimitry Andric /// \file
1004eeddc0SDimitry Andric /// This file implements DefinitionBlockSeparator, a TokenAnalyzer that inserts
1104eeddc0SDimitry Andric /// or removes empty lines separating definition blocks like classes, structs,
1204eeddc0SDimitry Andric /// functions, enums, and namespaces in between.
1304eeddc0SDimitry Andric ///
1404eeddc0SDimitry Andric //===----------------------------------------------------------------------===//
1504eeddc0SDimitry Andric 
1604eeddc0SDimitry Andric #include "DefinitionBlockSeparator.h"
1704eeddc0SDimitry Andric #include "llvm/Support/Debug.h"
1804eeddc0SDimitry Andric #define DEBUG_TYPE "definition-block-separator"
1904eeddc0SDimitry Andric 
2004eeddc0SDimitry Andric namespace clang {
2104eeddc0SDimitry Andric namespace format {
2204eeddc0SDimitry Andric std::pair<tooling::Replacements, unsigned> DefinitionBlockSeparator::analyze(
2304eeddc0SDimitry Andric     TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
2404eeddc0SDimitry Andric     FormatTokenLexer &Tokens) {
2504eeddc0SDimitry Andric   assert(Style.SeparateDefinitionBlocks != FormatStyle::SDS_Leave);
2604eeddc0SDimitry Andric   AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
2704eeddc0SDimitry Andric   tooling::Replacements Result;
2804eeddc0SDimitry Andric   separateBlocks(AnnotatedLines, Result, Tokens);
2904eeddc0SDimitry Andric   return {Result, 0};
3004eeddc0SDimitry Andric }
3104eeddc0SDimitry Andric 
3204eeddc0SDimitry Andric void DefinitionBlockSeparator::separateBlocks(
3304eeddc0SDimitry Andric     SmallVectorImpl<AnnotatedLine *> &Lines, tooling::Replacements &Result,
3404eeddc0SDimitry Andric     FormatTokenLexer &Tokens) {
3504eeddc0SDimitry Andric   const bool IsNeverStyle =
3604eeddc0SDimitry Andric       Style.SeparateDefinitionBlocks == FormatStyle::SDS_Never;
3704eeddc0SDimitry Andric   const AdditionalKeywords &ExtraKeywords = Tokens.getKeywords();
38*d56accc7SDimitry Andric   auto GetBracketLevelChange = [](const FormatToken *Tok) {
39*d56accc7SDimitry Andric     if (Tok->isOneOf(tok::l_brace, tok::l_paren, tok::l_square))
40*d56accc7SDimitry Andric       return 1;
41*d56accc7SDimitry Andric     if (Tok->isOneOf(tok::r_brace, tok::r_paren, tok::r_square))
42*d56accc7SDimitry Andric       return -1;
43*d56accc7SDimitry Andric     return 0;
44*d56accc7SDimitry Andric   };
45*d56accc7SDimitry Andric   auto LikelyDefinition = [&](const AnnotatedLine *Line,
4604eeddc0SDimitry Andric                               bool ExcludeEnum = false) {
4704eeddc0SDimitry Andric     if ((Line->MightBeFunctionDecl && Line->mightBeFunctionDefinition()) ||
4804eeddc0SDimitry Andric         Line->startsWithNamespace())
4904eeddc0SDimitry Andric       return true;
50*d56accc7SDimitry Andric     int BracketLevel = 0;
51*d56accc7SDimitry Andric     for (const FormatToken *CurrentToken = Line->First; CurrentToken;
52*d56accc7SDimitry Andric          CurrentToken = CurrentToken->Next) {
53*d56accc7SDimitry Andric       if (BracketLevel == 0) {
54*d56accc7SDimitry Andric         if ((CurrentToken->isOneOf(tok::kw_class, tok::kw_struct,
55*d56accc7SDimitry Andric                                    tok::kw_union) ||
56*d56accc7SDimitry Andric              (Style.isJavaScript() &&
57*d56accc7SDimitry Andric               CurrentToken->is(ExtraKeywords.kw_function))))
5804eeddc0SDimitry Andric           return true;
5904eeddc0SDimitry Andric         if (!ExcludeEnum && CurrentToken->is(tok::kw_enum))
6004eeddc0SDimitry Andric           return true;
61*d56accc7SDimitry Andric       }
62*d56accc7SDimitry Andric       BracketLevel += GetBracketLevelChange(CurrentToken);
6304eeddc0SDimitry Andric     }
6404eeddc0SDimitry Andric     return false;
6504eeddc0SDimitry Andric   };
6604eeddc0SDimitry Andric   unsigned NewlineCount =
6704eeddc0SDimitry Andric       (Style.SeparateDefinitionBlocks == FormatStyle::SDS_Always ? 1 : 0) + 1;
6804eeddc0SDimitry Andric   WhitespaceManager Whitespaces(
6904eeddc0SDimitry Andric       Env.getSourceManager(), Style,
7004eeddc0SDimitry Andric       Style.DeriveLineEnding
7104eeddc0SDimitry Andric           ? WhitespaceManager::inputUsesCRLF(
7204eeddc0SDimitry Andric                 Env.getSourceManager().getBufferData(Env.getFileID()),
7304eeddc0SDimitry Andric                 Style.UseCRLF)
7404eeddc0SDimitry Andric           : Style.UseCRLF);
7504eeddc0SDimitry Andric   for (unsigned I = 0; I < Lines.size(); ++I) {
7604eeddc0SDimitry Andric     const auto &CurrentLine = Lines[I];
7704eeddc0SDimitry Andric     if (CurrentLine->InPPDirective)
7804eeddc0SDimitry Andric       continue;
7904eeddc0SDimitry Andric     FormatToken *TargetToken = nullptr;
8004eeddc0SDimitry Andric     AnnotatedLine *TargetLine;
8104eeddc0SDimitry Andric     auto OpeningLineIndex = CurrentLine->MatchingOpeningBlockLineIndex;
8204eeddc0SDimitry Andric     AnnotatedLine *OpeningLine = nullptr;
8304eeddc0SDimitry Andric     const auto IsAccessSpecifierToken = [](const FormatToken *Token) {
8404eeddc0SDimitry Andric       return Token->isAccessSpecifier() || Token->isObjCAccessSpecifier();
8504eeddc0SDimitry Andric     };
8604eeddc0SDimitry Andric     const auto InsertReplacement = [&](const int NewlineToInsert) {
8704eeddc0SDimitry Andric       assert(TargetLine);
8804eeddc0SDimitry Andric       assert(TargetToken);
8904eeddc0SDimitry Andric 
9004eeddc0SDimitry Andric       // Do not handle EOF newlines.
9104eeddc0SDimitry Andric       if (TargetToken->is(tok::eof))
9204eeddc0SDimitry Andric         return;
9304eeddc0SDimitry Andric       if (IsAccessSpecifierToken(TargetToken) ||
9404eeddc0SDimitry Andric           (OpeningLineIndex > 0 &&
9504eeddc0SDimitry Andric            IsAccessSpecifierToken(Lines[OpeningLineIndex - 1]->First)))
9604eeddc0SDimitry Andric         return;
9704eeddc0SDimitry Andric       if (!TargetLine->Affected)
9804eeddc0SDimitry Andric         return;
9904eeddc0SDimitry Andric       Whitespaces.replaceWhitespace(*TargetToken, NewlineToInsert,
10004eeddc0SDimitry Andric                                     TargetToken->OriginalColumn,
10104eeddc0SDimitry Andric                                     TargetToken->OriginalColumn);
10204eeddc0SDimitry Andric     };
10304eeddc0SDimitry Andric     const auto IsPPConditional = [&](const size_t LineIndex) {
10404eeddc0SDimitry Andric       const auto &Line = Lines[LineIndex];
10504eeddc0SDimitry Andric       return Line->First->is(tok::hash) && Line->First->Next &&
10604eeddc0SDimitry Andric              Line->First->Next->isOneOf(tok::pp_if, tok::pp_ifdef, tok::pp_else,
10704eeddc0SDimitry Andric                                         tok::pp_ifndef, tok::pp_elifndef,
10804eeddc0SDimitry Andric                                         tok::pp_elifdef, tok::pp_elif,
10904eeddc0SDimitry Andric                                         tok::pp_endif);
11004eeddc0SDimitry Andric     };
11104eeddc0SDimitry Andric     const auto FollowingOtherOpening = [&]() {
11204eeddc0SDimitry Andric       return OpeningLineIndex == 0 ||
11304eeddc0SDimitry Andric              Lines[OpeningLineIndex - 1]->Last->opensScope() ||
11404eeddc0SDimitry Andric              IsPPConditional(OpeningLineIndex - 1);
11504eeddc0SDimitry Andric     };
11604eeddc0SDimitry Andric     const auto HasEnumOnLine = [&]() {
11704eeddc0SDimitry Andric       bool FoundEnumKeyword = false;
118*d56accc7SDimitry Andric       int BracketLevel = 0;
119*d56accc7SDimitry Andric       for (const FormatToken *CurrentToken = CurrentLine->First; CurrentToken;
120*d56accc7SDimitry Andric            CurrentToken = CurrentToken->Next) {
121*d56accc7SDimitry Andric         if (BracketLevel == 0) {
12204eeddc0SDimitry Andric           if (CurrentToken->is(tok::kw_enum))
12304eeddc0SDimitry Andric             FoundEnumKeyword = true;
12404eeddc0SDimitry Andric           else if (FoundEnumKeyword && CurrentToken->is(tok::l_brace))
12504eeddc0SDimitry Andric             return true;
126*d56accc7SDimitry Andric         }
127*d56accc7SDimitry Andric         BracketLevel += GetBracketLevelChange(CurrentToken);
12804eeddc0SDimitry Andric       }
12904eeddc0SDimitry Andric       return FoundEnumKeyword && I + 1 < Lines.size() &&
13004eeddc0SDimitry Andric              Lines[I + 1]->First->is(tok::l_brace);
13104eeddc0SDimitry Andric     };
13204eeddc0SDimitry Andric 
13304eeddc0SDimitry Andric     bool IsDefBlock = false;
13404eeddc0SDimitry Andric     const auto MayPrecedeDefinition = [&](const int Direction = -1) {
13504eeddc0SDimitry Andric       assert(Direction >= -1);
13604eeddc0SDimitry Andric       assert(Direction <= 1);
13704eeddc0SDimitry Andric       const size_t OperateIndex = OpeningLineIndex + Direction;
13804eeddc0SDimitry Andric       assert(OperateIndex < Lines.size());
13904eeddc0SDimitry Andric       const auto &OperateLine = Lines[OperateIndex];
14004eeddc0SDimitry Andric       if (LikelyDefinition(OperateLine))
14104eeddc0SDimitry Andric         return false;
14204eeddc0SDimitry Andric 
14304eeddc0SDimitry Andric       if (OperateLine->First->is(tok::comment))
14404eeddc0SDimitry Andric         return true;
14504eeddc0SDimitry Andric 
14604eeddc0SDimitry Andric       // A single line identifier that is not in the last line.
14704eeddc0SDimitry Andric       if (OperateLine->First->is(tok::identifier) &&
14804eeddc0SDimitry Andric           OperateLine->First == OperateLine->Last &&
14904eeddc0SDimitry Andric           OperateIndex + 1 < Lines.size()) {
15004eeddc0SDimitry Andric         // UnwrappedLineParser's recognition of free-standing macro like
15104eeddc0SDimitry Andric         // Q_OBJECT may also recognize some uppercased type names that may be
15204eeddc0SDimitry Andric         // used as return type as that kind of macros, which is a bit hard to
15304eeddc0SDimitry Andric         // distinguish one from another purely from token patterns. Here, we
15404eeddc0SDimitry Andric         // try not to add new lines below those identifiers.
15504eeddc0SDimitry Andric         AnnotatedLine *NextLine = Lines[OperateIndex + 1];
15604eeddc0SDimitry Andric         if (NextLine->MightBeFunctionDecl &&
15704eeddc0SDimitry Andric             NextLine->mightBeFunctionDefinition() &&
15804eeddc0SDimitry Andric             NextLine->First->NewlinesBefore == 1 &&
15904eeddc0SDimitry Andric             OperateLine->First->is(TT_FunctionLikeOrFreestandingMacro))
16004eeddc0SDimitry Andric           return true;
16104eeddc0SDimitry Andric       }
16204eeddc0SDimitry Andric 
16304eeddc0SDimitry Andric       if ((Style.isCSharp() && OperateLine->First->is(TT_AttributeSquare)))
16404eeddc0SDimitry Andric         return true;
16504eeddc0SDimitry Andric       return false;
16604eeddc0SDimitry Andric     };
16704eeddc0SDimitry Andric 
16804eeddc0SDimitry Andric     if (HasEnumOnLine() &&
16904eeddc0SDimitry Andric         !LikelyDefinition(CurrentLine, /*ExcludeEnum=*/true)) {
17004eeddc0SDimitry Andric       // We have no scope opening/closing information for enum.
17104eeddc0SDimitry Andric       IsDefBlock = true;
17204eeddc0SDimitry Andric       OpeningLineIndex = I;
17304eeddc0SDimitry Andric       while (OpeningLineIndex > 0 && MayPrecedeDefinition())
17404eeddc0SDimitry Andric         --OpeningLineIndex;
17504eeddc0SDimitry Andric       OpeningLine = Lines[OpeningLineIndex];
17604eeddc0SDimitry Andric       TargetLine = OpeningLine;
17704eeddc0SDimitry Andric       TargetToken = TargetLine->First;
17804eeddc0SDimitry Andric       if (!FollowingOtherOpening())
17904eeddc0SDimitry Andric         InsertReplacement(NewlineCount);
18004eeddc0SDimitry Andric       else if (IsNeverStyle)
18104eeddc0SDimitry Andric         InsertReplacement(OpeningLineIndex != 0);
18204eeddc0SDimitry Andric       TargetLine = CurrentLine;
18304eeddc0SDimitry Andric       TargetToken = TargetLine->First;
18404eeddc0SDimitry Andric       while (TargetToken && !TargetToken->is(tok::r_brace))
18504eeddc0SDimitry Andric         TargetToken = TargetToken->Next;
18604eeddc0SDimitry Andric       if (!TargetToken) {
18704eeddc0SDimitry Andric         while (I < Lines.size() && !Lines[I]->First->is(tok::r_brace))
18804eeddc0SDimitry Andric           ++I;
18904eeddc0SDimitry Andric       }
19004eeddc0SDimitry Andric     } else if (CurrentLine->First->closesScope()) {
19104eeddc0SDimitry Andric       if (OpeningLineIndex > Lines.size())
19204eeddc0SDimitry Andric         continue;
19304eeddc0SDimitry Andric       // Handling the case that opening brace has its own line, with checking
19404eeddc0SDimitry Andric       // whether the last line already had an opening brace to guard against
19504eeddc0SDimitry Andric       // misrecognition.
19604eeddc0SDimitry Andric       if (OpeningLineIndex > 0 &&
19704eeddc0SDimitry Andric           Lines[OpeningLineIndex]->First->is(tok::l_brace) &&
19804eeddc0SDimitry Andric           Lines[OpeningLineIndex - 1]->Last->isNot(tok::l_brace))
19904eeddc0SDimitry Andric         --OpeningLineIndex;
20004eeddc0SDimitry Andric       OpeningLine = Lines[OpeningLineIndex];
20104eeddc0SDimitry Andric       // Closing a function definition.
20204eeddc0SDimitry Andric       if (LikelyDefinition(OpeningLine)) {
20304eeddc0SDimitry Andric         IsDefBlock = true;
20404eeddc0SDimitry Andric         while (OpeningLineIndex > 0 && MayPrecedeDefinition())
20504eeddc0SDimitry Andric           --OpeningLineIndex;
20604eeddc0SDimitry Andric         OpeningLine = Lines[OpeningLineIndex];
20704eeddc0SDimitry Andric         TargetLine = OpeningLine;
20804eeddc0SDimitry Andric         TargetToken = TargetLine->First;
20904eeddc0SDimitry Andric         if (!FollowingOtherOpening()) {
21004eeddc0SDimitry Andric           // Avoid duplicated replacement.
21104eeddc0SDimitry Andric           if (TargetToken->isNot(tok::l_brace))
21204eeddc0SDimitry Andric             InsertReplacement(NewlineCount);
21304eeddc0SDimitry Andric         } else if (IsNeverStyle)
21404eeddc0SDimitry Andric           InsertReplacement(OpeningLineIndex != 0);
21504eeddc0SDimitry Andric       }
21604eeddc0SDimitry Andric     }
21704eeddc0SDimitry Andric 
21804eeddc0SDimitry Andric     // Not the last token.
21904eeddc0SDimitry Andric     if (IsDefBlock && I + 1 < Lines.size()) {
22004eeddc0SDimitry Andric       OpeningLineIndex = I + 1;
22104eeddc0SDimitry Andric       TargetLine = Lines[OpeningLineIndex];
22204eeddc0SDimitry Andric       TargetToken = TargetLine->First;
22304eeddc0SDimitry Andric 
22404eeddc0SDimitry Andric       // No empty line for continuously closing scopes. The token will be
22504eeddc0SDimitry Andric       // handled in another case if the line following is opening a
22604eeddc0SDimitry Andric       // definition.
22704eeddc0SDimitry Andric       if (!TargetToken->closesScope() && !IsPPConditional(OpeningLineIndex)) {
22804eeddc0SDimitry Andric         // Check whether current line may precede a definition line.
22904eeddc0SDimitry Andric         while (OpeningLineIndex + 1 < Lines.size() &&
23004eeddc0SDimitry Andric                MayPrecedeDefinition(/*Direction=*/0))
23104eeddc0SDimitry Andric           ++OpeningLineIndex;
23204eeddc0SDimitry Andric         TargetLine = Lines[OpeningLineIndex];
23304eeddc0SDimitry Andric         if (!LikelyDefinition(TargetLine)) {
23404eeddc0SDimitry Andric           OpeningLineIndex = I + 1;
23504eeddc0SDimitry Andric           TargetLine = Lines[I + 1];
23604eeddc0SDimitry Andric           TargetToken = TargetLine->First;
23704eeddc0SDimitry Andric           InsertReplacement(NewlineCount);
23804eeddc0SDimitry Andric         }
23904eeddc0SDimitry Andric       } else if (IsNeverStyle)
24004eeddc0SDimitry Andric         InsertReplacement(/*NewlineToInsert=*/1);
24104eeddc0SDimitry Andric     }
24204eeddc0SDimitry Andric   }
24304eeddc0SDimitry Andric   for (const auto &R : Whitespaces.generateReplacements())
24404eeddc0SDimitry Andric     // The add method returns an Error instance which simulates program exit
24504eeddc0SDimitry Andric     // code through overloading boolean operator, thus false here indicates
24604eeddc0SDimitry Andric     // success.
24704eeddc0SDimitry Andric     if (Result.add(R))
24804eeddc0SDimitry Andric       return;
24904eeddc0SDimitry Andric }
25004eeddc0SDimitry Andric } // namespace format
25104eeddc0SDimitry Andric } // namespace clang
252