xref: /llvm-project/clang/lib/Format/DefinitionBlockSeparator.cpp (revision b2082a98175b0e5356f23bf21d3dc5b76edea390)
16f6f88ffSksyx //===--- DefinitionBlockSeparator.cpp ---------------------------*- C++ -*-===//
26f6f88ffSksyx //
36f6f88ffSksyx // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
46f6f88ffSksyx // See https://llvm.org/LICENSE.txt for license information.
56f6f88ffSksyx // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
66f6f88ffSksyx //
76f6f88ffSksyx //===----------------------------------------------------------------------===//
86f6f88ffSksyx ///
96f6f88ffSksyx /// \file
106f6f88ffSksyx /// This file implements DefinitionBlockSeparator, a TokenAnalyzer that inserts
116f6f88ffSksyx /// or removes empty lines separating definition blocks like classes, structs,
126f6f88ffSksyx /// functions, enums, and namespaces in between.
136f6f88ffSksyx ///
146f6f88ffSksyx //===----------------------------------------------------------------------===//
156f6f88ffSksyx 
166f6f88ffSksyx #include "DefinitionBlockSeparator.h"
17*b2082a98SOwen Pan #include "llvm/Support/Debug.h"
186f6f88ffSksyx #define DEBUG_TYPE "definition-block-separator"
196f6f88ffSksyx 
206f6f88ffSksyx namespace clang {
216f6f88ffSksyx namespace format {
analyze(TokenAnnotator & Annotator,SmallVectorImpl<AnnotatedLine * > & AnnotatedLines,FormatTokenLexer & Tokens)226f6f88ffSksyx std::pair<tooling::Replacements, unsigned> DefinitionBlockSeparator::analyze(
236f6f88ffSksyx     TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
246f6f88ffSksyx     FormatTokenLexer &Tokens) {
256f6f88ffSksyx   assert(Style.SeparateDefinitionBlocks != FormatStyle::SDS_Leave);
266f6f88ffSksyx   AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
276f6f88ffSksyx   tooling::Replacements Result;
285e5efd8aSksyx   separateBlocks(AnnotatedLines, Result, Tokens);
296f6f88ffSksyx   return {Result, 0};
306f6f88ffSksyx }
316f6f88ffSksyx 
separateBlocks(SmallVectorImpl<AnnotatedLine * > & Lines,tooling::Replacements & Result,FormatTokenLexer & Tokens)326f6f88ffSksyx void DefinitionBlockSeparator::separateBlocks(
335e5efd8aSksyx     SmallVectorImpl<AnnotatedLine *> &Lines, tooling::Replacements &Result,
345e5efd8aSksyx     FormatTokenLexer &Tokens) {
35ee25a327Sksyx   const bool IsNeverStyle =
36ee25a327Sksyx       Style.SeparateDefinitionBlocks == FormatStyle::SDS_Never;
375e5efd8aSksyx   const AdditionalKeywords &ExtraKeywords = Tokens.getKeywords();
38a70549aeSksyx   auto GetBracketLevelChange = [](const FormatToken *Tok) {
39a70549aeSksyx     if (Tok->isOneOf(tok::l_brace, tok::l_paren, tok::l_square))
40a70549aeSksyx       return 1;
41a70549aeSksyx     if (Tok->isOneOf(tok::r_brace, tok::r_paren, tok::r_square))
42a70549aeSksyx       return -1;
43a70549aeSksyx     return 0;
44a70549aeSksyx   };
45a70549aeSksyx   auto LikelyDefinition = [&](const AnnotatedLine *Line,
465e5efd8aSksyx                               bool ExcludeEnum = false) {
47ee25a327Sksyx     if ((Line->MightBeFunctionDecl && Line->mightBeFunctionDefinition()) ||
48bebf7bdfSowenca         Line->startsWithNamespace()) {
496f6f88ffSksyx       return true;
50bebf7bdfSowenca     }
51a70549aeSksyx     int BracketLevel = 0;
52a70549aeSksyx     for (const FormatToken *CurrentToken = Line->First; CurrentToken;
53a70549aeSksyx          CurrentToken = CurrentToken->Next) {
54a70549aeSksyx       if (BracketLevel == 0) {
55875b8811SOwen Pan         if (CurrentToken->isOneOf(tok::kw_class, tok::kw_struct,
56a70549aeSksyx                                   tok::kw_union) ||
57a70549aeSksyx             (Style.isJavaScript() &&
58875b8811SOwen Pan              CurrentToken->is(ExtraKeywords.kw_function))) {
595e5efd8aSksyx           return true;
60bebf7bdfSowenca         }
615e5efd8aSksyx         if (!ExcludeEnum && CurrentToken->is(tok::kw_enum))
626f6f88ffSksyx           return true;
63a70549aeSksyx       }
64a70549aeSksyx       BracketLevel += GetBracketLevelChange(CurrentToken);
656f6f88ffSksyx     }
666f6f88ffSksyx     return false;
676f6f88ffSksyx   };
686f6f88ffSksyx   unsigned NewlineCount =
696f6f88ffSksyx       (Style.SeparateDefinitionBlocks == FormatStyle::SDS_Always ? 1 : 0) + 1;
706f6f88ffSksyx   WhitespaceManager Whitespaces(
716f6f88ffSksyx       Env.getSourceManager(), Style,
72e3eca335SOwen Pan       Style.LineEnding > FormatStyle::LE_CRLF
736f6f88ffSksyx           ? WhitespaceManager::inputUsesCRLF(
746f6f88ffSksyx                 Env.getSourceManager().getBufferData(Env.getFileID()),
75e3eca335SOwen Pan                 Style.LineEnding == FormatStyle::LE_DeriveCRLF)
76e3eca335SOwen Pan           : Style.LineEnding == FormatStyle::LE_CRLF);
77ee25a327Sksyx   for (unsigned I = 0; I < Lines.size(); ++I) {
786f6f88ffSksyx     const auto &CurrentLine = Lines[I];
79ee25a327Sksyx     if (CurrentLine->InPPDirective)
80ee25a327Sksyx       continue;
816f6f88ffSksyx     FormatToken *TargetToken = nullptr;
826f6f88ffSksyx     AnnotatedLine *TargetLine;
836f6f88ffSksyx     auto OpeningLineIndex = CurrentLine->MatchingOpeningBlockLineIndex;
84ee25a327Sksyx     AnnotatedLine *OpeningLine = nullptr;
855e5efd8aSksyx     const auto IsAccessSpecifierToken = [](const FormatToken *Token) {
865e5efd8aSksyx       return Token->isAccessSpecifier() || Token->isObjCAccessSpecifier();
875e5efd8aSksyx     };
886f6f88ffSksyx     const auto InsertReplacement = [&](const int NewlineToInsert) {
896f6f88ffSksyx       assert(TargetLine);
906f6f88ffSksyx       assert(TargetToken);
916f6f88ffSksyx 
926f6f88ffSksyx       // Do not handle EOF newlines.
935e5efd8aSksyx       if (TargetToken->is(tok::eof))
945e5efd8aSksyx         return;
955e5efd8aSksyx       if (IsAccessSpecifierToken(TargetToken) ||
965e5efd8aSksyx           (OpeningLineIndex > 0 &&
97bebf7bdfSowenca            IsAccessSpecifierToken(Lines[OpeningLineIndex - 1]->First))) {
986f6f88ffSksyx         return;
99bebf7bdfSowenca       }
1006f6f88ffSksyx       if (!TargetLine->Affected)
1016f6f88ffSksyx         return;
1026f6f88ffSksyx       Whitespaces.replaceWhitespace(*TargetToken, NewlineToInsert,
1035e5efd8aSksyx                                     TargetToken->OriginalColumn,
1045e5efd8aSksyx                                     TargetToken->OriginalColumn);
1056f6f88ffSksyx     };
106ee25a327Sksyx     const auto IsPPConditional = [&](const size_t LineIndex) {
107ee25a327Sksyx       const auto &Line = Lines[LineIndex];
108ee25a327Sksyx       return Line->First->is(tok::hash) && Line->First->Next &&
109ee25a327Sksyx              Line->First->Next->isOneOf(tok::pp_if, tok::pp_ifdef, tok::pp_else,
110ee25a327Sksyx                                         tok::pp_ifndef, tok::pp_elifndef,
111ee25a327Sksyx                                         tok::pp_elifdef, tok::pp_elif,
112ee25a327Sksyx                                         tok::pp_endif);
113ee25a327Sksyx     };
1146f6f88ffSksyx     const auto FollowingOtherOpening = [&]() {
1156f6f88ffSksyx       return OpeningLineIndex == 0 ||
116ee25a327Sksyx              Lines[OpeningLineIndex - 1]->Last->opensScope() ||
117ee25a327Sksyx              IsPPConditional(OpeningLineIndex - 1);
1186f6f88ffSksyx     };
1195e5efd8aSksyx     const auto HasEnumOnLine = [&]() {
1205e5efd8aSksyx       bool FoundEnumKeyword = false;
121a70549aeSksyx       int BracketLevel = 0;
122a70549aeSksyx       for (const FormatToken *CurrentToken = CurrentLine->First; CurrentToken;
123a70549aeSksyx            CurrentToken = CurrentToken->Next) {
124a70549aeSksyx         if (BracketLevel == 0) {
1256f6f88ffSksyx           if (CurrentToken->is(tok::kw_enum))
1265e5efd8aSksyx             FoundEnumKeyword = true;
1275e5efd8aSksyx           else if (FoundEnumKeyword && CurrentToken->is(tok::l_brace))
1286f6f88ffSksyx             return true;
129a70549aeSksyx         }
130a70549aeSksyx         BracketLevel += GetBracketLevelChange(CurrentToken);
1316f6f88ffSksyx       }
1325e5efd8aSksyx       return FoundEnumKeyword && I + 1 < Lines.size() &&
1335e5efd8aSksyx              Lines[I + 1]->First->is(tok::l_brace);
1346f6f88ffSksyx     };
1356f6f88ffSksyx 
13640446663SKazu Hirata     bool IsDefBlock = false;
137ee25a327Sksyx     const auto MayPrecedeDefinition = [&](const int Direction = -1) {
1385e5efd8aSksyx       assert(Direction >= -1);
1395e5efd8aSksyx       assert(Direction <= 1);
140ee25a327Sksyx       const size_t OperateIndex = OpeningLineIndex + Direction;
141ee25a327Sksyx       assert(OperateIndex < Lines.size());
142ee25a327Sksyx       const auto &OperateLine = Lines[OperateIndex];
1435e5efd8aSksyx       if (LikelyDefinition(OperateLine))
1445e5efd8aSksyx         return false;
1455e5efd8aSksyx 
14658a71c66SOwen Pan       if (const auto *Tok = OperateLine->First;
14758a71c66SOwen Pan           Tok->is(tok::comment) && !isClangFormatOn(Tok->TokenText)) {
1485e5efd8aSksyx         return true;
14958a71c66SOwen Pan       }
1505e5efd8aSksyx 
1515e5efd8aSksyx       // A single line identifier that is not in the last line.
1525e5efd8aSksyx       if (OperateLine->First->is(tok::identifier) &&
1535e5efd8aSksyx           OperateLine->First == OperateLine->Last &&
1545e5efd8aSksyx           OperateIndex + 1 < Lines.size()) {
1555e5efd8aSksyx         // UnwrappedLineParser's recognition of free-standing macro like
1565e5efd8aSksyx         // Q_OBJECT may also recognize some uppercased type names that may be
1575e5efd8aSksyx         // used as return type as that kind of macros, which is a bit hard to
1585e5efd8aSksyx         // distinguish one from another purely from token patterns. Here, we
1595e5efd8aSksyx         // try not to add new lines below those identifiers.
1605e5efd8aSksyx         AnnotatedLine *NextLine = Lines[OperateIndex + 1];
1615e5efd8aSksyx         if (NextLine->MightBeFunctionDecl &&
1625e5efd8aSksyx             NextLine->mightBeFunctionDefinition() &&
1635e5efd8aSksyx             NextLine->First->NewlinesBefore == 1 &&
164bebf7bdfSowenca             OperateLine->First->is(TT_FunctionLikeOrFreestandingMacro)) {
1655e5efd8aSksyx           return true;
1665e5efd8aSksyx         }
167bebf7bdfSowenca       }
1685e5efd8aSksyx 
169875b8811SOwen Pan       if (Style.isCSharp() && OperateLine->First->is(TT_AttributeSquare))
1705e5efd8aSksyx         return true;
1715e5efd8aSksyx       return false;
172ee25a327Sksyx     };
1736f6f88ffSksyx 
1745e5efd8aSksyx     if (HasEnumOnLine() &&
1755e5efd8aSksyx         !LikelyDefinition(CurrentLine, /*ExcludeEnum=*/true)) {
1766f6f88ffSksyx       // We have no scope opening/closing information for enum.
17740446663SKazu Hirata       IsDefBlock = true;
1786f6f88ffSksyx       OpeningLineIndex = I;
179ee25a327Sksyx       while (OpeningLineIndex > 0 && MayPrecedeDefinition())
180ee25a327Sksyx         --OpeningLineIndex;
181ee25a327Sksyx       OpeningLine = Lines[OpeningLineIndex];
182ee25a327Sksyx       TargetLine = OpeningLine;
183ee25a327Sksyx       TargetToken = TargetLine->First;
1846f6f88ffSksyx       if (!FollowingOtherOpening())
1856f6f88ffSksyx         InsertReplacement(NewlineCount);
186ee25a327Sksyx       else if (IsNeverStyle)
1876f6f88ffSksyx         InsertReplacement(OpeningLineIndex != 0);
188ee25a327Sksyx       TargetLine = CurrentLine;
189ee25a327Sksyx       TargetToken = TargetLine->First;
19091c4db00SOwen Pan       while (TargetToken && TargetToken->isNot(tok::r_brace))
1916f6f88ffSksyx         TargetToken = TargetToken->Next;
192d079995dSMarek Kurdej       if (!TargetToken)
19391c4db00SOwen Pan         while (I < Lines.size() && Lines[I]->First->isNot(tok::r_brace))
194359b4e6cSMarek Kurdej           ++I;
1956f6f88ffSksyx     } else if (CurrentLine->First->closesScope()) {
1966f6f88ffSksyx       if (OpeningLineIndex > Lines.size())
1976f6f88ffSksyx         continue;
1985e5efd8aSksyx       // Handling the case that opening brace has its own line, with checking
1995e5efd8aSksyx       // whether the last line already had an opening brace to guard against
2005e5efd8aSksyx       // misrecognition.
2015e5efd8aSksyx       if (OpeningLineIndex > 0 &&
2025e5efd8aSksyx           Lines[OpeningLineIndex]->First->is(tok::l_brace) &&
203bebf7bdfSowenca           Lines[OpeningLineIndex - 1]->Last->isNot(tok::l_brace)) {
2045e5efd8aSksyx         --OpeningLineIndex;
205bebf7bdfSowenca       }
206ee25a327Sksyx       OpeningLine = Lines[OpeningLineIndex];
2076f6f88ffSksyx       // Closing a function definition.
2086f6f88ffSksyx       if (LikelyDefinition(OpeningLine)) {
20940446663SKazu Hirata         IsDefBlock = true;
210ee25a327Sksyx         while (OpeningLineIndex > 0 && MayPrecedeDefinition())
211ee25a327Sksyx           --OpeningLineIndex;
2126f6f88ffSksyx         OpeningLine = Lines[OpeningLineIndex];
2136f6f88ffSksyx         TargetLine = OpeningLine;
2146f6f88ffSksyx         TargetToken = TargetLine->First;
2156f6f88ffSksyx         if (!FollowingOtherOpening()) {
2166f6f88ffSksyx           // Avoid duplicated replacement.
217ee25a327Sksyx           if (TargetToken->isNot(tok::l_brace))
2186f6f88ffSksyx             InsertReplacement(NewlineCount);
219bebf7bdfSowenca         } else if (IsNeverStyle) {
2206f6f88ffSksyx           InsertReplacement(OpeningLineIndex != 0);
2216f6f88ffSksyx         }
2226f6f88ffSksyx       }
223bebf7bdfSowenca     }
2246f6f88ffSksyx 
2256f6f88ffSksyx     // Not the last token.
2266f6f88ffSksyx     if (IsDefBlock && I + 1 < Lines.size()) {
227ee25a327Sksyx       OpeningLineIndex = I + 1;
228ee25a327Sksyx       TargetLine = Lines[OpeningLineIndex];
2296f6f88ffSksyx       TargetToken = TargetLine->First;
2306f6f88ffSksyx 
2316f6f88ffSksyx       // No empty line for continuously closing scopes. The token will be
2326f6f88ffSksyx       // handled in another case if the line following is opening a
2336f6f88ffSksyx       // definition.
234ee25a327Sksyx       if (!TargetToken->closesScope() && !IsPPConditional(OpeningLineIndex)) {
2356cbebfc7SMarek Kurdej         // Check whether current line may precede a definition line.
236ee25a327Sksyx         while (OpeningLineIndex + 1 < Lines.size() &&
237bebf7bdfSowenca                MayPrecedeDefinition(/*Direction=*/0)) {
238ee25a327Sksyx           ++OpeningLineIndex;
239bebf7bdfSowenca         }
240ee25a327Sksyx         TargetLine = Lines[OpeningLineIndex];
241ee25a327Sksyx         if (!LikelyDefinition(TargetLine)) {
2425e5efd8aSksyx           OpeningLineIndex = I + 1;
243ee25a327Sksyx           TargetLine = Lines[I + 1];
244ee25a327Sksyx           TargetToken = TargetLine->First;
2456f6f88ffSksyx           InsertReplacement(NewlineCount);
246ee25a327Sksyx         }
247bebf7bdfSowenca       } else if (IsNeverStyle) {
2485e5efd8aSksyx         InsertReplacement(/*NewlineToInsert=*/1);
2496f6f88ffSksyx       }
2506f6f88ffSksyx     }
251bebf7bdfSowenca   }
252bebf7bdfSowenca   for (const auto &R : Whitespaces.generateReplacements()) {
2536f6f88ffSksyx     // The add method returns an Error instance which simulates program exit
2546f6f88ffSksyx     // code through overloading boolean operator, thus false here indicates
2556f6f88ffSksyx     // success.
2566f6f88ffSksyx     if (Result.add(R))
2576f6f88ffSksyx       return;
2586f6f88ffSksyx   }
259bebf7bdfSowenca }
2606f6f88ffSksyx } // namespace format
2616f6f88ffSksyx } // namespace clang
262