10b57cec5SDimitry Andric //===--- NamespaceEndCommentsFixer.cpp --------------------------*- C++ -*-===// 20b57cec5SDimitry Andric // 30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 60b57cec5SDimitry Andric // 70b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 80b57cec5SDimitry Andric /// 90b57cec5SDimitry Andric /// \file 100b57cec5SDimitry Andric /// This file implements NamespaceEndCommentsFixer, a TokenAnalyzer that 110b57cec5SDimitry Andric /// fixes namespace end comments. 120b57cec5SDimitry Andric /// 130b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 140b57cec5SDimitry Andric 150b57cec5SDimitry Andric #include "NamespaceEndCommentsFixer.h" 160b57cec5SDimitry Andric #include "llvm/Support/Debug.h" 170b57cec5SDimitry Andric #include "llvm/Support/Regex.h" 180b57cec5SDimitry Andric 190b57cec5SDimitry Andric #define DEBUG_TYPE "namespace-end-comments-fixer" 200b57cec5SDimitry Andric 210b57cec5SDimitry Andric namespace clang { 220b57cec5SDimitry Andric namespace format { 230b57cec5SDimitry Andric 240b57cec5SDimitry Andric namespace { 250b57cec5SDimitry Andric // Computes the name of a namespace given the namespace token. 260b57cec5SDimitry Andric // Returns "" for anonymous namespace. 270b57cec5SDimitry Andric std::string computeName(const FormatToken *NamespaceTok) { 280b57cec5SDimitry Andric assert(NamespaceTok && 290b57cec5SDimitry Andric NamespaceTok->isOneOf(tok::kw_namespace, TT_NamespaceMacro) && 300b57cec5SDimitry Andric "expecting a namespace token"); 310b57cec5SDimitry Andric std::string name = ""; 320b57cec5SDimitry Andric const FormatToken *Tok = NamespaceTok->getNextNonComment(); 330b57cec5SDimitry Andric if (NamespaceTok->is(TT_NamespaceMacro)) { 340b57cec5SDimitry Andric // Collects all the non-comment tokens between opening parenthesis 35a7dea167SDimitry Andric // and closing parenthesis or comma. 360b57cec5SDimitry Andric assert(Tok && Tok->is(tok::l_paren) && "expected an opening parenthesis"); 370b57cec5SDimitry Andric Tok = Tok->getNextNonComment(); 380b57cec5SDimitry Andric while (Tok && !Tok->isOneOf(tok::r_paren, tok::comma)) { 390b57cec5SDimitry Andric name += Tok->TokenText; 400b57cec5SDimitry Andric Tok = Tok->getNextNonComment(); 410b57cec5SDimitry Andric } 420b57cec5SDimitry Andric } else { 43a7dea167SDimitry Andric // For `namespace [[foo]] A::B::inline C {` or 44a7dea167SDimitry Andric // `namespace MACRO1 MACRO2 A::B::inline C {`, returns "A::B::inline C". 45a7dea167SDimitry Andric // Peek for the first '::' (or '{') and then return all tokens from one 46a7dea167SDimitry Andric // token before that up until the '{'. 47a7dea167SDimitry Andric const FormatToken *FirstNSTok = Tok; 48a7dea167SDimitry Andric while (Tok && !Tok->is(tok::l_brace) && !Tok->is(tok::coloncolon)) { 49a7dea167SDimitry Andric FirstNSTok = Tok; 50a7dea167SDimitry Andric Tok = Tok->getNextNonComment(); 51a7dea167SDimitry Andric } 52a7dea167SDimitry Andric 53a7dea167SDimitry Andric Tok = FirstNSTok; 540b57cec5SDimitry Andric while (Tok && !Tok->is(tok::l_brace)) { 550b57cec5SDimitry Andric name += Tok->TokenText; 56a7dea167SDimitry Andric if (Tok->is(tok::kw_inline)) 57a7dea167SDimitry Andric name += " "; 580b57cec5SDimitry Andric Tok = Tok->getNextNonComment(); 590b57cec5SDimitry Andric } 600b57cec5SDimitry Andric } 610b57cec5SDimitry Andric return name; 620b57cec5SDimitry Andric } 630b57cec5SDimitry Andric 640b57cec5SDimitry Andric std::string computeEndCommentText(StringRef NamespaceName, bool AddNewline, 65fe6060f1SDimitry Andric const FormatToken *NamespaceTok, 66fe6060f1SDimitry Andric unsigned SpacesToAdd) { 670b57cec5SDimitry Andric std::string text = "//"; 68fe6060f1SDimitry Andric text.append(SpacesToAdd, ' '); 690b57cec5SDimitry Andric text += NamespaceTok->TokenText; 700b57cec5SDimitry Andric if (NamespaceTok->is(TT_NamespaceMacro)) 710b57cec5SDimitry Andric text += "("; 720b57cec5SDimitry Andric else if (!NamespaceName.empty()) 730b57cec5SDimitry Andric text += ' '; 740b57cec5SDimitry Andric text += NamespaceName; 750b57cec5SDimitry Andric if (NamespaceTok->is(TT_NamespaceMacro)) 760b57cec5SDimitry Andric text += ")"; 770b57cec5SDimitry Andric if (AddNewline) 780b57cec5SDimitry Andric text += '\n'; 790b57cec5SDimitry Andric return text; 800b57cec5SDimitry Andric } 810b57cec5SDimitry Andric 820b57cec5SDimitry Andric bool hasEndComment(const FormatToken *RBraceTok) { 830b57cec5SDimitry Andric return RBraceTok->Next && RBraceTok->Next->is(tok::comment); 840b57cec5SDimitry Andric } 850b57cec5SDimitry Andric 860b57cec5SDimitry Andric bool validEndComment(const FormatToken *RBraceTok, StringRef NamespaceName, 870b57cec5SDimitry Andric const FormatToken *NamespaceTok) { 880b57cec5SDimitry Andric assert(hasEndComment(RBraceTok)); 890b57cec5SDimitry Andric const FormatToken *Comment = RBraceTok->Next; 900b57cec5SDimitry Andric 910b57cec5SDimitry Andric // Matches a valid namespace end comment. 920b57cec5SDimitry Andric // Valid namespace end comments don't need to be edited. 93480093f4SDimitry Andric static const llvm::Regex NamespaceCommentPattern = 94480093f4SDimitry Andric llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *" 950b57cec5SDimitry Andric "namespace( +([a-zA-Z0-9:_]+))?\\.? *(\\*/)?$", 960b57cec5SDimitry Andric llvm::Regex::IgnoreCase); 97480093f4SDimitry Andric static const llvm::Regex NamespaceMacroCommentPattern = 98480093f4SDimitry Andric llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *" 990b57cec5SDimitry Andric "([a-zA-Z0-9_]+)\\(([a-zA-Z0-9:_]*)\\)\\.? *(\\*/)?$", 1000b57cec5SDimitry Andric llvm::Regex::IgnoreCase); 1010b57cec5SDimitry Andric 1020b57cec5SDimitry Andric SmallVector<StringRef, 8> Groups; 1030b57cec5SDimitry Andric if (NamespaceTok->is(TT_NamespaceMacro) && 104480093f4SDimitry Andric NamespaceMacroCommentPattern.match(Comment->TokenText, &Groups)) { 1050b57cec5SDimitry Andric StringRef NamespaceTokenText = Groups.size() > 4 ? Groups[4] : ""; 1060b57cec5SDimitry Andric // The name of the macro must be used. 1070b57cec5SDimitry Andric if (NamespaceTokenText != NamespaceTok->TokenText) 1080b57cec5SDimitry Andric return false; 1090b57cec5SDimitry Andric } else if (NamespaceTok->isNot(tok::kw_namespace) || 110480093f4SDimitry Andric !NamespaceCommentPattern.match(Comment->TokenText, &Groups)) { 1110b57cec5SDimitry Andric // Comment does not match regex. 1120b57cec5SDimitry Andric return false; 1130b57cec5SDimitry Andric } 1140b57cec5SDimitry Andric StringRef NamespaceNameInComment = Groups.size() > 5 ? Groups[5] : ""; 1150b57cec5SDimitry Andric // Anonymous namespace comments must not mention a namespace name. 1160b57cec5SDimitry Andric if (NamespaceName.empty() && !NamespaceNameInComment.empty()) 1170b57cec5SDimitry Andric return false; 1180b57cec5SDimitry Andric StringRef AnonymousInComment = Groups.size() > 3 ? Groups[3] : ""; 1190b57cec5SDimitry Andric // Named namespace comments must not mention anonymous namespace. 1200b57cec5SDimitry Andric if (!NamespaceName.empty() && !AnonymousInComment.empty()) 1210b57cec5SDimitry Andric return false; 1225ffd83dbSDimitry Andric if (NamespaceNameInComment == NamespaceName) 1235ffd83dbSDimitry Andric return true; 1245ffd83dbSDimitry Andric 1255ffd83dbSDimitry Andric // Has namespace comment flowed onto the next line. 1265ffd83dbSDimitry Andric // } // namespace 1275ffd83dbSDimitry Andric // // verylongnamespacenamethatdidnotfitonthepreviouscommentline 1285ffd83dbSDimitry Andric if (!(Comment->Next && Comment->Next->is(TT_LineComment))) 1295ffd83dbSDimitry Andric return false; 1305ffd83dbSDimitry Andric 1315ffd83dbSDimitry Andric static const llvm::Regex CommentPattern = llvm::Regex( 1325ffd83dbSDimitry Andric "^/[/*] *( +([a-zA-Z0-9:_]+))?\\.? *(\\*/)?$", llvm::Regex::IgnoreCase); 1335ffd83dbSDimitry Andric 1345ffd83dbSDimitry Andric // Pull out just the comment text. 1355ffd83dbSDimitry Andric if (!CommentPattern.match(Comment->Next->TokenText, &Groups)) { 1365ffd83dbSDimitry Andric return false; 1375ffd83dbSDimitry Andric } 1385ffd83dbSDimitry Andric NamespaceNameInComment = Groups.size() > 2 ? Groups[2] : ""; 1395ffd83dbSDimitry Andric 1405ffd83dbSDimitry Andric return (NamespaceNameInComment == NamespaceName); 1410b57cec5SDimitry Andric } 1420b57cec5SDimitry Andric 1430b57cec5SDimitry Andric void addEndComment(const FormatToken *RBraceTok, StringRef EndCommentText, 1440b57cec5SDimitry Andric const SourceManager &SourceMgr, 1450b57cec5SDimitry Andric tooling::Replacements *Fixes) { 1460b57cec5SDimitry Andric auto EndLoc = RBraceTok->Tok.getEndLoc(); 1470b57cec5SDimitry Andric auto Range = CharSourceRange::getCharRange(EndLoc, EndLoc); 1480b57cec5SDimitry Andric auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText)); 1490b57cec5SDimitry Andric if (Err) { 1500b57cec5SDimitry Andric llvm::errs() << "Error while adding namespace end comment: " 1510b57cec5SDimitry Andric << llvm::toString(std::move(Err)) << "\n"; 1520b57cec5SDimitry Andric } 1530b57cec5SDimitry Andric } 1540b57cec5SDimitry Andric 1550b57cec5SDimitry Andric void updateEndComment(const FormatToken *RBraceTok, StringRef EndCommentText, 1560b57cec5SDimitry Andric const SourceManager &SourceMgr, 1570b57cec5SDimitry Andric tooling::Replacements *Fixes) { 1580b57cec5SDimitry Andric assert(hasEndComment(RBraceTok)); 1590b57cec5SDimitry Andric const FormatToken *Comment = RBraceTok->Next; 1600b57cec5SDimitry Andric auto Range = CharSourceRange::getCharRange(Comment->getStartOfNonWhitespace(), 1610b57cec5SDimitry Andric Comment->Tok.getEndLoc()); 1620b57cec5SDimitry Andric auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText)); 1630b57cec5SDimitry Andric if (Err) { 1640b57cec5SDimitry Andric llvm::errs() << "Error while updating namespace end comment: " 1650b57cec5SDimitry Andric << llvm::toString(std::move(Err)) << "\n"; 1660b57cec5SDimitry Andric } 1670b57cec5SDimitry Andric } 1680b57cec5SDimitry Andric } // namespace 1690b57cec5SDimitry Andric 1700b57cec5SDimitry Andric const FormatToken * 1710b57cec5SDimitry Andric getNamespaceToken(const AnnotatedLine *Line, 1720b57cec5SDimitry Andric const SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) { 1730b57cec5SDimitry Andric if (!Line->Affected || Line->InPPDirective || !Line->startsWith(tok::r_brace)) 1740b57cec5SDimitry Andric return nullptr; 1750b57cec5SDimitry Andric size_t StartLineIndex = Line->MatchingOpeningBlockLineIndex; 1760b57cec5SDimitry Andric if (StartLineIndex == UnwrappedLine::kInvalidIndex) 1770b57cec5SDimitry Andric return nullptr; 1780b57cec5SDimitry Andric assert(StartLineIndex < AnnotatedLines.size()); 1790b57cec5SDimitry Andric const FormatToken *NamespaceTok = AnnotatedLines[StartLineIndex]->First; 1800b57cec5SDimitry Andric if (NamespaceTok->is(tok::l_brace)) { 1810b57cec5SDimitry Andric // "namespace" keyword can be on the line preceding '{', e.g. in styles 1820b57cec5SDimitry Andric // where BraceWrapping.AfterNamespace is true. 1830eae32dcSDimitry Andric if (StartLineIndex > 0) { 1840b57cec5SDimitry Andric NamespaceTok = AnnotatedLines[StartLineIndex - 1]->First; 1850eae32dcSDimitry Andric if (AnnotatedLines[StartLineIndex - 1]->endsWith(tok::semi)) 1860eae32dcSDimitry Andric return nullptr; 1870b57cec5SDimitry Andric } 1880eae32dcSDimitry Andric } 1890eae32dcSDimitry Andric 1900b57cec5SDimitry Andric return NamespaceTok->getNamespaceToken(); 1910b57cec5SDimitry Andric } 1920b57cec5SDimitry Andric 1930b57cec5SDimitry Andric StringRef 1940b57cec5SDimitry Andric getNamespaceTokenText(const AnnotatedLine *Line, 1950b57cec5SDimitry Andric const SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) { 1960b57cec5SDimitry Andric const FormatToken *NamespaceTok = getNamespaceToken(Line, AnnotatedLines); 1970b57cec5SDimitry Andric return NamespaceTok ? NamespaceTok->TokenText : StringRef(); 1980b57cec5SDimitry Andric } 1990b57cec5SDimitry Andric 2000b57cec5SDimitry Andric NamespaceEndCommentsFixer::NamespaceEndCommentsFixer(const Environment &Env, 2010b57cec5SDimitry Andric const FormatStyle &Style) 2020b57cec5SDimitry Andric : TokenAnalyzer(Env, Style) {} 2030b57cec5SDimitry Andric 2040b57cec5SDimitry Andric std::pair<tooling::Replacements, unsigned> NamespaceEndCommentsFixer::analyze( 2050b57cec5SDimitry Andric TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines, 2060b57cec5SDimitry Andric FormatTokenLexer &Tokens) { 2070b57cec5SDimitry Andric const SourceManager &SourceMgr = Env.getSourceManager(); 2080b57cec5SDimitry Andric AffectedRangeMgr.computeAffectedLines(AnnotatedLines); 2090b57cec5SDimitry Andric tooling::Replacements Fixes; 2105ffd83dbSDimitry Andric 2115ffd83dbSDimitry Andric // Spin through the lines and ensure we have balanced braces. 2125ffd83dbSDimitry Andric int Braces = 0; 2135ffd83dbSDimitry Andric for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I) { 2145ffd83dbSDimitry Andric FormatToken *Tok = AnnotatedLines[I]->First; 2155ffd83dbSDimitry Andric while (Tok) { 2165ffd83dbSDimitry Andric Braces += Tok->is(tok::l_brace) ? 1 : Tok->is(tok::r_brace) ? -1 : 0; 2175ffd83dbSDimitry Andric Tok = Tok->Next; 2185ffd83dbSDimitry Andric } 2195ffd83dbSDimitry Andric } 2205ffd83dbSDimitry Andric // Don't attempt to comment unbalanced braces or this can 2215ffd83dbSDimitry Andric // lead to comments being placed on the closing brace which isn't 2225ffd83dbSDimitry Andric // the matching brace of the namespace. (occurs during incomplete editing). 2235ffd83dbSDimitry Andric if (Braces != 0) { 2245ffd83dbSDimitry Andric return {Fixes, 0}; 2255ffd83dbSDimitry Andric } 2265ffd83dbSDimitry Andric 2270b57cec5SDimitry Andric std::string AllNamespaceNames = ""; 2280b57cec5SDimitry Andric size_t StartLineIndex = SIZE_MAX; 2290b57cec5SDimitry Andric StringRef NamespaceTokenText; 2300b57cec5SDimitry Andric unsigned int CompactedNamespacesCount = 0; 2310b57cec5SDimitry Andric for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I) { 2320b57cec5SDimitry Andric const AnnotatedLine *EndLine = AnnotatedLines[I]; 2330b57cec5SDimitry Andric const FormatToken *NamespaceTok = 2340b57cec5SDimitry Andric getNamespaceToken(EndLine, AnnotatedLines); 2350b57cec5SDimitry Andric if (!NamespaceTok) 2360b57cec5SDimitry Andric continue; 2370b57cec5SDimitry Andric FormatToken *RBraceTok = EndLine->First; 2380b57cec5SDimitry Andric if (RBraceTok->Finalized) 2390b57cec5SDimitry Andric continue; 2400b57cec5SDimitry Andric RBraceTok->Finalized = true; 2410b57cec5SDimitry Andric const FormatToken *EndCommentPrevTok = RBraceTok; 2420b57cec5SDimitry Andric // Namespaces often end with '};'. In that case, attach namespace end 2430b57cec5SDimitry Andric // comments to the semicolon tokens. 2440b57cec5SDimitry Andric if (RBraceTok->Next && RBraceTok->Next->is(tok::semi)) { 2450b57cec5SDimitry Andric EndCommentPrevTok = RBraceTok->Next; 2460b57cec5SDimitry Andric } 2470b57cec5SDimitry Andric if (StartLineIndex == SIZE_MAX) 2480b57cec5SDimitry Andric StartLineIndex = EndLine->MatchingOpeningBlockLineIndex; 2490b57cec5SDimitry Andric std::string NamespaceName = computeName(NamespaceTok); 2500b57cec5SDimitry Andric if (Style.CompactNamespaces) { 2510b57cec5SDimitry Andric if (CompactedNamespacesCount == 0) 2520b57cec5SDimitry Andric NamespaceTokenText = NamespaceTok->TokenText; 2530b57cec5SDimitry Andric if ((I + 1 < E) && 2540b57cec5SDimitry Andric NamespaceTokenText == 2550b57cec5SDimitry Andric getNamespaceTokenText(AnnotatedLines[I + 1], AnnotatedLines) && 2560b57cec5SDimitry Andric StartLineIndex - CompactedNamespacesCount - 1 == 2570b57cec5SDimitry Andric AnnotatedLines[I + 1]->MatchingOpeningBlockLineIndex && 2580b57cec5SDimitry Andric !AnnotatedLines[I + 1]->First->Finalized) { 2590b57cec5SDimitry Andric if (hasEndComment(EndCommentPrevTok)) { 2600b57cec5SDimitry Andric // remove end comment, it will be merged in next one 2610b57cec5SDimitry Andric updateEndComment(EndCommentPrevTok, std::string(), SourceMgr, &Fixes); 2620b57cec5SDimitry Andric } 2630b57cec5SDimitry Andric CompactedNamespacesCount++; 2640b57cec5SDimitry Andric AllNamespaceNames = "::" + NamespaceName + AllNamespaceNames; 2650b57cec5SDimitry Andric continue; 2660b57cec5SDimitry Andric } 2670b57cec5SDimitry Andric NamespaceName += AllNamespaceNames; 2680b57cec5SDimitry Andric CompactedNamespacesCount = 0; 2690b57cec5SDimitry Andric AllNamespaceNames = std::string(); 2700b57cec5SDimitry Andric } 2710b57cec5SDimitry Andric // The next token in the token stream after the place where the end comment 2720b57cec5SDimitry Andric // token must be. This is either the next token on the current line or the 2730b57cec5SDimitry Andric // first token on the next line. 2740b57cec5SDimitry Andric const FormatToken *EndCommentNextTok = EndCommentPrevTok->Next; 2750b57cec5SDimitry Andric if (EndCommentNextTok && EndCommentNextTok->is(tok::comment)) 2760b57cec5SDimitry Andric EndCommentNextTok = EndCommentNextTok->Next; 2770b57cec5SDimitry Andric if (!EndCommentNextTok && I + 1 < E) 2780b57cec5SDimitry Andric EndCommentNextTok = AnnotatedLines[I + 1]->First; 2790b57cec5SDimitry Andric bool AddNewline = EndCommentNextTok && 2800b57cec5SDimitry Andric EndCommentNextTok->NewlinesBefore == 0 && 2810b57cec5SDimitry Andric EndCommentNextTok->isNot(tok::eof); 2820b57cec5SDimitry Andric const std::string EndCommentText = 283fe6060f1SDimitry Andric computeEndCommentText(NamespaceName, AddNewline, NamespaceTok, 284fe6060f1SDimitry Andric Style.SpacesInLineCommentPrefix.Minimum); 2850b57cec5SDimitry Andric if (!hasEndComment(EndCommentPrevTok)) { 286fe6060f1SDimitry Andric bool isShort = I - StartLineIndex <= Style.ShortNamespaceLines + 1; 2870b57cec5SDimitry Andric if (!isShort) 2880b57cec5SDimitry Andric addEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes); 2890b57cec5SDimitry Andric } else if (!validEndComment(EndCommentPrevTok, NamespaceName, 2900b57cec5SDimitry Andric NamespaceTok)) { 2910b57cec5SDimitry Andric updateEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes); 2920b57cec5SDimitry Andric } 2930b57cec5SDimitry Andric StartLineIndex = SIZE_MAX; 2940b57cec5SDimitry Andric } 2950b57cec5SDimitry Andric return {Fixes, 0}; 2960b57cec5SDimitry Andric } 2970b57cec5SDimitry Andric 2980b57cec5SDimitry Andric } // namespace format 2990b57cec5SDimitry Andric } // namespace clang 300