xref: /netbsd-src/external/apache2/llvm/dist/clang/lib/Format/NamespaceEndCommentsFixer.cpp (revision e038c9c4676b0f19b1b7dd08a940c6ed64a6d5ae)
17330f729Sjoerg //===--- NamespaceEndCommentsFixer.cpp --------------------------*- C++ -*-===//
27330f729Sjoerg //
37330f729Sjoerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
47330f729Sjoerg // See https://llvm.org/LICENSE.txt for license information.
57330f729Sjoerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
67330f729Sjoerg //
77330f729Sjoerg //===----------------------------------------------------------------------===//
87330f729Sjoerg ///
97330f729Sjoerg /// \file
107330f729Sjoerg /// This file implements NamespaceEndCommentsFixer, a TokenAnalyzer that
117330f729Sjoerg /// fixes namespace end comments.
127330f729Sjoerg ///
137330f729Sjoerg //===----------------------------------------------------------------------===//
147330f729Sjoerg 
157330f729Sjoerg #include "NamespaceEndCommentsFixer.h"
167330f729Sjoerg #include "llvm/Support/Debug.h"
177330f729Sjoerg #include "llvm/Support/Regex.h"
187330f729Sjoerg 
197330f729Sjoerg #define DEBUG_TYPE "namespace-end-comments-fixer"
207330f729Sjoerg 
217330f729Sjoerg namespace clang {
227330f729Sjoerg namespace format {
237330f729Sjoerg 
247330f729Sjoerg namespace {
257330f729Sjoerg // Computes the name of a namespace given the namespace token.
267330f729Sjoerg // Returns "" for anonymous namespace.
computeName(const FormatToken * NamespaceTok)277330f729Sjoerg std::string computeName(const FormatToken *NamespaceTok) {
287330f729Sjoerg   assert(NamespaceTok &&
297330f729Sjoerg          NamespaceTok->isOneOf(tok::kw_namespace, TT_NamespaceMacro) &&
307330f729Sjoerg          "expecting a namespace token");
317330f729Sjoerg   std::string name = "";
327330f729Sjoerg   const FormatToken *Tok = NamespaceTok->getNextNonComment();
337330f729Sjoerg   if (NamespaceTok->is(TT_NamespaceMacro)) {
347330f729Sjoerg     // Collects all the non-comment tokens between opening parenthesis
357330f729Sjoerg     // and closing parenthesis or comma.
367330f729Sjoerg     assert(Tok && Tok->is(tok::l_paren) && "expected an opening parenthesis");
377330f729Sjoerg     Tok = Tok->getNextNonComment();
387330f729Sjoerg     while (Tok && !Tok->isOneOf(tok::r_paren, tok::comma)) {
397330f729Sjoerg       name += Tok->TokenText;
407330f729Sjoerg       Tok = Tok->getNextNonComment();
417330f729Sjoerg     }
427330f729Sjoerg   } else {
437330f729Sjoerg     // For `namespace [[foo]] A::B::inline C {` or
447330f729Sjoerg     // `namespace MACRO1 MACRO2 A::B::inline C {`, returns "A::B::inline C".
457330f729Sjoerg     // Peek for the first '::' (or '{') and then return all tokens from one
467330f729Sjoerg     // token before that up until the '{'.
477330f729Sjoerg     const FormatToken *FirstNSTok = Tok;
487330f729Sjoerg     while (Tok && !Tok->is(tok::l_brace) && !Tok->is(tok::coloncolon)) {
497330f729Sjoerg       FirstNSTok = Tok;
507330f729Sjoerg       Tok = Tok->getNextNonComment();
517330f729Sjoerg     }
527330f729Sjoerg 
537330f729Sjoerg     Tok = FirstNSTok;
547330f729Sjoerg     while (Tok && !Tok->is(tok::l_brace)) {
557330f729Sjoerg       name += Tok->TokenText;
567330f729Sjoerg       if (Tok->is(tok::kw_inline))
577330f729Sjoerg         name += " ";
587330f729Sjoerg       Tok = Tok->getNextNonComment();
597330f729Sjoerg     }
607330f729Sjoerg   }
617330f729Sjoerg   return name;
627330f729Sjoerg }
637330f729Sjoerg 
computeEndCommentText(StringRef NamespaceName,bool AddNewline,const FormatToken * NamespaceTok,unsigned SpacesToAdd)647330f729Sjoerg std::string computeEndCommentText(StringRef NamespaceName, bool AddNewline,
65*e038c9c4Sjoerg                                   const FormatToken *NamespaceTok,
66*e038c9c4Sjoerg                                   unsigned SpacesToAdd) {
677330f729Sjoerg   std::string text = "//";
68*e038c9c4Sjoerg   text.append(SpacesToAdd, ' ');
697330f729Sjoerg   text += NamespaceTok->TokenText;
707330f729Sjoerg   if (NamespaceTok->is(TT_NamespaceMacro))
717330f729Sjoerg     text += "(";
727330f729Sjoerg   else if (!NamespaceName.empty())
737330f729Sjoerg     text += ' ';
747330f729Sjoerg   text += NamespaceName;
757330f729Sjoerg   if (NamespaceTok->is(TT_NamespaceMacro))
767330f729Sjoerg     text += ")";
777330f729Sjoerg   if (AddNewline)
787330f729Sjoerg     text += '\n';
797330f729Sjoerg   return text;
807330f729Sjoerg }
817330f729Sjoerg 
hasEndComment(const FormatToken * RBraceTok)827330f729Sjoerg bool hasEndComment(const FormatToken *RBraceTok) {
837330f729Sjoerg   return RBraceTok->Next && RBraceTok->Next->is(tok::comment);
847330f729Sjoerg }
857330f729Sjoerg 
validEndComment(const FormatToken * RBraceTok,StringRef NamespaceName,const FormatToken * NamespaceTok)867330f729Sjoerg bool validEndComment(const FormatToken *RBraceTok, StringRef NamespaceName,
877330f729Sjoerg                      const FormatToken *NamespaceTok) {
887330f729Sjoerg   assert(hasEndComment(RBraceTok));
897330f729Sjoerg   const FormatToken *Comment = RBraceTok->Next;
907330f729Sjoerg 
917330f729Sjoerg   // Matches a valid namespace end comment.
927330f729Sjoerg   // Valid namespace end comments don't need to be edited.
93*e038c9c4Sjoerg   static const llvm::Regex NamespaceCommentPattern =
94*e038c9c4Sjoerg       llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *"
957330f729Sjoerg                   "namespace( +([a-zA-Z0-9:_]+))?\\.? *(\\*/)?$",
967330f729Sjoerg                   llvm::Regex::IgnoreCase);
97*e038c9c4Sjoerg   static const llvm::Regex NamespaceMacroCommentPattern =
98*e038c9c4Sjoerg       llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *"
997330f729Sjoerg                   "([a-zA-Z0-9_]+)\\(([a-zA-Z0-9:_]*)\\)\\.? *(\\*/)?$",
1007330f729Sjoerg                   llvm::Regex::IgnoreCase);
1017330f729Sjoerg 
1027330f729Sjoerg   SmallVector<StringRef, 8> Groups;
1037330f729Sjoerg   if (NamespaceTok->is(TT_NamespaceMacro) &&
104*e038c9c4Sjoerg       NamespaceMacroCommentPattern.match(Comment->TokenText, &Groups)) {
1057330f729Sjoerg     StringRef NamespaceTokenText = Groups.size() > 4 ? Groups[4] : "";
1067330f729Sjoerg     // The name of the macro must be used.
1077330f729Sjoerg     if (NamespaceTokenText != NamespaceTok->TokenText)
1087330f729Sjoerg       return false;
1097330f729Sjoerg   } else if (NamespaceTok->isNot(tok::kw_namespace) ||
110*e038c9c4Sjoerg              !NamespaceCommentPattern.match(Comment->TokenText, &Groups)) {
1117330f729Sjoerg     // Comment does not match regex.
1127330f729Sjoerg     return false;
1137330f729Sjoerg   }
1147330f729Sjoerg   StringRef NamespaceNameInComment = Groups.size() > 5 ? Groups[5] : "";
1157330f729Sjoerg   // Anonymous namespace comments must not mention a namespace name.
1167330f729Sjoerg   if (NamespaceName.empty() && !NamespaceNameInComment.empty())
1177330f729Sjoerg     return false;
1187330f729Sjoerg   StringRef AnonymousInComment = Groups.size() > 3 ? Groups[3] : "";
1197330f729Sjoerg   // Named namespace comments must not mention anonymous namespace.
1207330f729Sjoerg   if (!NamespaceName.empty() && !AnonymousInComment.empty())
1217330f729Sjoerg     return false;
122*e038c9c4Sjoerg   if (NamespaceNameInComment == NamespaceName)
123*e038c9c4Sjoerg     return true;
124*e038c9c4Sjoerg 
125*e038c9c4Sjoerg   // Has namespace comment flowed onto the next line.
126*e038c9c4Sjoerg   // } // namespace
127*e038c9c4Sjoerg   //   // verylongnamespacenamethatdidnotfitonthepreviouscommentline
128*e038c9c4Sjoerg   if (!(Comment->Next && Comment->Next->is(TT_LineComment)))
129*e038c9c4Sjoerg     return false;
130*e038c9c4Sjoerg 
131*e038c9c4Sjoerg   static const llvm::Regex CommentPattern = llvm::Regex(
132*e038c9c4Sjoerg       "^/[/*] *( +([a-zA-Z0-9:_]+))?\\.? *(\\*/)?$", llvm::Regex::IgnoreCase);
133*e038c9c4Sjoerg 
134*e038c9c4Sjoerg   // Pull out just the comment text.
135*e038c9c4Sjoerg   if (!CommentPattern.match(Comment->Next->TokenText, &Groups)) {
136*e038c9c4Sjoerg     return false;
137*e038c9c4Sjoerg   }
138*e038c9c4Sjoerg   NamespaceNameInComment = Groups.size() > 2 ? Groups[2] : "";
139*e038c9c4Sjoerg 
140*e038c9c4Sjoerg   return (NamespaceNameInComment == NamespaceName);
1417330f729Sjoerg }
1427330f729Sjoerg 
addEndComment(const FormatToken * RBraceTok,StringRef EndCommentText,const SourceManager & SourceMgr,tooling::Replacements * Fixes)1437330f729Sjoerg void addEndComment(const FormatToken *RBraceTok, StringRef EndCommentText,
1447330f729Sjoerg                    const SourceManager &SourceMgr,
1457330f729Sjoerg                    tooling::Replacements *Fixes) {
1467330f729Sjoerg   auto EndLoc = RBraceTok->Tok.getEndLoc();
1477330f729Sjoerg   auto Range = CharSourceRange::getCharRange(EndLoc, EndLoc);
1487330f729Sjoerg   auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText));
1497330f729Sjoerg   if (Err) {
1507330f729Sjoerg     llvm::errs() << "Error while adding namespace end comment: "
1517330f729Sjoerg                  << llvm::toString(std::move(Err)) << "\n";
1527330f729Sjoerg   }
1537330f729Sjoerg }
1547330f729Sjoerg 
updateEndComment(const FormatToken * RBraceTok,StringRef EndCommentText,const SourceManager & SourceMgr,tooling::Replacements * Fixes)1557330f729Sjoerg void updateEndComment(const FormatToken *RBraceTok, StringRef EndCommentText,
1567330f729Sjoerg                       const SourceManager &SourceMgr,
1577330f729Sjoerg                       tooling::Replacements *Fixes) {
1587330f729Sjoerg   assert(hasEndComment(RBraceTok));
1597330f729Sjoerg   const FormatToken *Comment = RBraceTok->Next;
1607330f729Sjoerg   auto Range = CharSourceRange::getCharRange(Comment->getStartOfNonWhitespace(),
1617330f729Sjoerg                                              Comment->Tok.getEndLoc());
1627330f729Sjoerg   auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText));
1637330f729Sjoerg   if (Err) {
1647330f729Sjoerg     llvm::errs() << "Error while updating namespace end comment: "
1657330f729Sjoerg                  << llvm::toString(std::move(Err)) << "\n";
1667330f729Sjoerg   }
1677330f729Sjoerg }
1687330f729Sjoerg } // namespace
1697330f729Sjoerg 
1707330f729Sjoerg const FormatToken *
getNamespaceToken(const AnnotatedLine * Line,const SmallVectorImpl<AnnotatedLine * > & AnnotatedLines)1717330f729Sjoerg getNamespaceToken(const AnnotatedLine *Line,
1727330f729Sjoerg                   const SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) {
1737330f729Sjoerg   if (!Line->Affected || Line->InPPDirective || !Line->startsWith(tok::r_brace))
1747330f729Sjoerg     return nullptr;
1757330f729Sjoerg   size_t StartLineIndex = Line->MatchingOpeningBlockLineIndex;
1767330f729Sjoerg   if (StartLineIndex == UnwrappedLine::kInvalidIndex)
1777330f729Sjoerg     return nullptr;
1787330f729Sjoerg   assert(StartLineIndex < AnnotatedLines.size());
1797330f729Sjoerg   const FormatToken *NamespaceTok = AnnotatedLines[StartLineIndex]->First;
1807330f729Sjoerg   if (NamespaceTok->is(tok::l_brace)) {
1817330f729Sjoerg     // "namespace" keyword can be on the line preceding '{', e.g. in styles
1827330f729Sjoerg     // where BraceWrapping.AfterNamespace is true.
1837330f729Sjoerg     if (StartLineIndex > 0)
1847330f729Sjoerg       NamespaceTok = AnnotatedLines[StartLineIndex - 1]->First;
1857330f729Sjoerg   }
1867330f729Sjoerg   return NamespaceTok->getNamespaceToken();
1877330f729Sjoerg }
1887330f729Sjoerg 
1897330f729Sjoerg StringRef
getNamespaceTokenText(const AnnotatedLine * Line,const SmallVectorImpl<AnnotatedLine * > & AnnotatedLines)1907330f729Sjoerg getNamespaceTokenText(const AnnotatedLine *Line,
1917330f729Sjoerg                       const SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) {
1927330f729Sjoerg   const FormatToken *NamespaceTok = getNamespaceToken(Line, AnnotatedLines);
1937330f729Sjoerg   return NamespaceTok ? NamespaceTok->TokenText : StringRef();
1947330f729Sjoerg }
1957330f729Sjoerg 
NamespaceEndCommentsFixer(const Environment & Env,const FormatStyle & Style)1967330f729Sjoerg NamespaceEndCommentsFixer::NamespaceEndCommentsFixer(const Environment &Env,
1977330f729Sjoerg                                                      const FormatStyle &Style)
1987330f729Sjoerg     : TokenAnalyzer(Env, Style) {}
1997330f729Sjoerg 
analyze(TokenAnnotator & Annotator,SmallVectorImpl<AnnotatedLine * > & AnnotatedLines,FormatTokenLexer & Tokens)2007330f729Sjoerg std::pair<tooling::Replacements, unsigned> NamespaceEndCommentsFixer::analyze(
2017330f729Sjoerg     TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
2027330f729Sjoerg     FormatTokenLexer &Tokens) {
2037330f729Sjoerg   const SourceManager &SourceMgr = Env.getSourceManager();
2047330f729Sjoerg   AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
2057330f729Sjoerg   tooling::Replacements Fixes;
206*e038c9c4Sjoerg 
207*e038c9c4Sjoerg   // Spin through the lines and ensure we have balanced braces.
208*e038c9c4Sjoerg   int Braces = 0;
209*e038c9c4Sjoerg   for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I) {
210*e038c9c4Sjoerg     FormatToken *Tok = AnnotatedLines[I]->First;
211*e038c9c4Sjoerg     while (Tok) {
212*e038c9c4Sjoerg       Braces += Tok->is(tok::l_brace) ? 1 : Tok->is(tok::r_brace) ? -1 : 0;
213*e038c9c4Sjoerg       Tok = Tok->Next;
214*e038c9c4Sjoerg     }
215*e038c9c4Sjoerg   }
216*e038c9c4Sjoerg   // Don't attempt to comment unbalanced braces or this can
217*e038c9c4Sjoerg   // lead to comments being placed on the closing brace which isn't
218*e038c9c4Sjoerg   // the matching brace of the namespace. (occurs during incomplete editing).
219*e038c9c4Sjoerg   if (Braces != 0) {
220*e038c9c4Sjoerg     return {Fixes, 0};
221*e038c9c4Sjoerg   }
222*e038c9c4Sjoerg 
2237330f729Sjoerg   std::string AllNamespaceNames = "";
2247330f729Sjoerg   size_t StartLineIndex = SIZE_MAX;
2257330f729Sjoerg   StringRef NamespaceTokenText;
2267330f729Sjoerg   unsigned int CompactedNamespacesCount = 0;
2277330f729Sjoerg   for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I) {
2287330f729Sjoerg     const AnnotatedLine *EndLine = AnnotatedLines[I];
2297330f729Sjoerg     const FormatToken *NamespaceTok =
2307330f729Sjoerg         getNamespaceToken(EndLine, AnnotatedLines);
2317330f729Sjoerg     if (!NamespaceTok)
2327330f729Sjoerg       continue;
2337330f729Sjoerg     FormatToken *RBraceTok = EndLine->First;
2347330f729Sjoerg     if (RBraceTok->Finalized)
2357330f729Sjoerg       continue;
2367330f729Sjoerg     RBraceTok->Finalized = true;
2377330f729Sjoerg     const FormatToken *EndCommentPrevTok = RBraceTok;
2387330f729Sjoerg     // Namespaces often end with '};'. In that case, attach namespace end
2397330f729Sjoerg     // comments to the semicolon tokens.
2407330f729Sjoerg     if (RBraceTok->Next && RBraceTok->Next->is(tok::semi)) {
2417330f729Sjoerg       EndCommentPrevTok = RBraceTok->Next;
2427330f729Sjoerg     }
2437330f729Sjoerg     if (StartLineIndex == SIZE_MAX)
2447330f729Sjoerg       StartLineIndex = EndLine->MatchingOpeningBlockLineIndex;
2457330f729Sjoerg     std::string NamespaceName = computeName(NamespaceTok);
2467330f729Sjoerg     if (Style.CompactNamespaces) {
2477330f729Sjoerg       if (CompactedNamespacesCount == 0)
2487330f729Sjoerg         NamespaceTokenText = NamespaceTok->TokenText;
2497330f729Sjoerg       if ((I + 1 < E) &&
2507330f729Sjoerg           NamespaceTokenText ==
2517330f729Sjoerg               getNamespaceTokenText(AnnotatedLines[I + 1], AnnotatedLines) &&
2527330f729Sjoerg           StartLineIndex - CompactedNamespacesCount - 1 ==
2537330f729Sjoerg               AnnotatedLines[I + 1]->MatchingOpeningBlockLineIndex &&
2547330f729Sjoerg           !AnnotatedLines[I + 1]->First->Finalized) {
2557330f729Sjoerg         if (hasEndComment(EndCommentPrevTok)) {
2567330f729Sjoerg           // remove end comment, it will be merged in next one
2577330f729Sjoerg           updateEndComment(EndCommentPrevTok, std::string(), SourceMgr, &Fixes);
2587330f729Sjoerg         }
2597330f729Sjoerg         CompactedNamespacesCount++;
2607330f729Sjoerg         AllNamespaceNames = "::" + NamespaceName + AllNamespaceNames;
2617330f729Sjoerg         continue;
2627330f729Sjoerg       }
2637330f729Sjoerg       NamespaceName += AllNamespaceNames;
2647330f729Sjoerg       CompactedNamespacesCount = 0;
2657330f729Sjoerg       AllNamespaceNames = std::string();
2667330f729Sjoerg     }
2677330f729Sjoerg     // The next token in the token stream after the place where the end comment
2687330f729Sjoerg     // token must be. This is either the next token on the current line or the
2697330f729Sjoerg     // first token on the next line.
2707330f729Sjoerg     const FormatToken *EndCommentNextTok = EndCommentPrevTok->Next;
2717330f729Sjoerg     if (EndCommentNextTok && EndCommentNextTok->is(tok::comment))
2727330f729Sjoerg       EndCommentNextTok = EndCommentNextTok->Next;
2737330f729Sjoerg     if (!EndCommentNextTok && I + 1 < E)
2747330f729Sjoerg       EndCommentNextTok = AnnotatedLines[I + 1]->First;
2757330f729Sjoerg     bool AddNewline = EndCommentNextTok &&
2767330f729Sjoerg                       EndCommentNextTok->NewlinesBefore == 0 &&
2777330f729Sjoerg                       EndCommentNextTok->isNot(tok::eof);
2787330f729Sjoerg     const std::string EndCommentText =
279*e038c9c4Sjoerg         computeEndCommentText(NamespaceName, AddNewline, NamespaceTok,
280*e038c9c4Sjoerg                               Style.SpacesInLineCommentPrefix.Minimum);
2817330f729Sjoerg     if (!hasEndComment(EndCommentPrevTok)) {
282*e038c9c4Sjoerg       bool isShort = I - StartLineIndex <= Style.ShortNamespaceLines + 1;
2837330f729Sjoerg       if (!isShort)
2847330f729Sjoerg         addEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes);
2857330f729Sjoerg     } else if (!validEndComment(EndCommentPrevTok, NamespaceName,
2867330f729Sjoerg                                 NamespaceTok)) {
2877330f729Sjoerg       updateEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes);
2887330f729Sjoerg     }
2897330f729Sjoerg     StartLineIndex = SIZE_MAX;
2907330f729Sjoerg   }
2917330f729Sjoerg   return {Fixes, 0};
2927330f729Sjoerg }
2937330f729Sjoerg 
2947330f729Sjoerg } // namespace format
2957330f729Sjoerg } // namespace clang
296