1 //===--- NamespaceEndCommentsFixer.cpp --------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 /// 9 /// \file 10 /// This file implements NamespaceEndCommentsFixer, a TokenAnalyzer that 11 /// fixes namespace end comments. 12 /// 13 //===----------------------------------------------------------------------===// 14 15 #include "NamespaceEndCommentsFixer.h" 16 #include "llvm/Support/Debug.h" 17 #include "llvm/Support/Regex.h" 18 19 #define DEBUG_TYPE "namespace-end-comments-fixer" 20 21 namespace clang { 22 namespace format { 23 24 namespace { 25 // The maximal number of unwrapped lines that a short namespace spans. 26 // Short namespaces don't need an end comment. 27 static const int kShortNamespaceMaxLines = 1; 28 29 // Computes the name of a namespace given the namespace token. 30 // Returns "" for anonymous namespace. 31 std::string computeName(const FormatToken *NamespaceTok) { 32 assert(NamespaceTok && NamespaceTok->is(tok::kw_namespace) && 33 "expecting a namespace token"); 34 std::string name = ""; 35 // Collects all the non-comment tokens between 'namespace' and '{'. 36 const FormatToken *Tok = NamespaceTok->getNextNonComment(); 37 while (Tok && !Tok->is(tok::l_brace)) { 38 name += Tok->TokenText; 39 Tok = Tok->getNextNonComment(); 40 } 41 return name; 42 } 43 44 std::string computeEndCommentText(StringRef NamespaceName, bool AddNewline) { 45 std::string text = "// namespace"; 46 if (!NamespaceName.empty()) { 47 text += ' '; 48 text += NamespaceName; 49 } 50 if (AddNewline) 51 text += '\n'; 52 return text; 53 } 54 55 bool hasEndComment(const FormatToken *RBraceTok) { 56 return RBraceTok->Next && RBraceTok->Next->is(tok::comment); 57 } 58 59 bool validEndComment(const FormatToken *RBraceTok, StringRef NamespaceName) { 60 assert(hasEndComment(RBraceTok)); 61 const FormatToken *Comment = RBraceTok->Next; 62 63 // Matches a valid namespace end comment. 64 // Valid namespace end comments don't need to be edited. 65 static llvm::Regex *const NamespaceCommentPattern = 66 new llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *" 67 "namespace( +([a-zA-Z0-9:_]+))?\\.? *(\\*/)?$", 68 llvm::Regex::IgnoreCase); 69 SmallVector<StringRef, 7> Groups; 70 if (NamespaceCommentPattern->match(Comment->TokenText, &Groups)) { 71 StringRef NamespaceNameInComment = Groups.size() > 5 ? Groups[5] : ""; 72 // Anonymous namespace comments must not mention a namespace name. 73 if (NamespaceName.empty() && !NamespaceNameInComment.empty()) 74 return false; 75 StringRef AnonymousInComment = Groups.size() > 3 ? Groups[3] : ""; 76 // Named namespace comments must not mention anonymous namespace. 77 if (!NamespaceName.empty() && !AnonymousInComment.empty()) 78 return false; 79 return NamespaceNameInComment == NamespaceName; 80 } 81 return false; 82 } 83 84 void addEndComment(const FormatToken *RBraceTok, StringRef EndCommentText, 85 const SourceManager &SourceMgr, 86 tooling::Replacements *Fixes) { 87 auto EndLoc = RBraceTok->Tok.getEndLoc(); 88 auto Range = CharSourceRange::getCharRange(EndLoc, EndLoc); 89 auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText)); 90 if (Err) { 91 llvm::errs() << "Error while adding namespace end comment: " 92 << llvm::toString(std::move(Err)) << "\n"; 93 } 94 } 95 96 void updateEndComment(const FormatToken *RBraceTok, StringRef EndCommentText, 97 const SourceManager &SourceMgr, 98 tooling::Replacements *Fixes) { 99 assert(hasEndComment(RBraceTok)); 100 const FormatToken *Comment = RBraceTok->Next; 101 auto Range = CharSourceRange::getCharRange(Comment->getStartOfNonWhitespace(), 102 Comment->Tok.getEndLoc()); 103 auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText)); 104 if (Err) { 105 llvm::errs() << "Error while updating namespace end comment: " 106 << llvm::toString(std::move(Err)) << "\n"; 107 } 108 } 109 } // namespace 110 111 const FormatToken * 112 getNamespaceToken(const AnnotatedLine *Line, 113 const SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) { 114 if (!Line->Affected || Line->InPPDirective || !Line->startsWith(tok::r_brace)) 115 return nullptr; 116 size_t StartLineIndex = Line->MatchingOpeningBlockLineIndex; 117 if (StartLineIndex == UnwrappedLine::kInvalidIndex) 118 return nullptr; 119 assert(StartLineIndex < AnnotatedLines.size()); 120 const FormatToken *NamespaceTok = AnnotatedLines[StartLineIndex]->First; 121 if (NamespaceTok->is(tok::l_brace)) { 122 // "namespace" keyword can be on the line preceding '{', e.g. in styles 123 // where BraceWrapping.AfterNamespace is true. 124 if (StartLineIndex > 0) 125 NamespaceTok = AnnotatedLines[StartLineIndex - 1]->First; 126 } 127 return NamespaceTok->getNamespaceToken(); 128 } 129 130 NamespaceEndCommentsFixer::NamespaceEndCommentsFixer(const Environment &Env, 131 const FormatStyle &Style) 132 : TokenAnalyzer(Env, Style) {} 133 134 std::pair<tooling::Replacements, unsigned> NamespaceEndCommentsFixer::analyze( 135 TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines, 136 FormatTokenLexer &Tokens) { 137 const SourceManager &SourceMgr = Env.getSourceManager(); 138 AffectedRangeMgr.computeAffectedLines(AnnotatedLines); 139 tooling::Replacements Fixes; 140 std::string AllNamespaceNames = ""; 141 size_t StartLineIndex = SIZE_MAX; 142 unsigned int CompactedNamespacesCount = 0; 143 for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I) { 144 const AnnotatedLine *EndLine = AnnotatedLines[I]; 145 const FormatToken *NamespaceTok = 146 getNamespaceToken(EndLine, AnnotatedLines); 147 if (!NamespaceTok) 148 continue; 149 FormatToken *RBraceTok = EndLine->First; 150 if (RBraceTok->Finalized) 151 continue; 152 RBraceTok->Finalized = true; 153 const FormatToken *EndCommentPrevTok = RBraceTok; 154 // Namespaces often end with '};'. In that case, attach namespace end 155 // comments to the semicolon tokens. 156 if (RBraceTok->Next && RBraceTok->Next->is(tok::semi)) { 157 EndCommentPrevTok = RBraceTok->Next; 158 } 159 if (StartLineIndex == SIZE_MAX) 160 StartLineIndex = EndLine->MatchingOpeningBlockLineIndex; 161 std::string NamespaceName = computeName(NamespaceTok); 162 if (Style.CompactNamespaces) { 163 if ((I + 1 < E) && 164 getNamespaceToken(AnnotatedLines[I + 1], AnnotatedLines) && 165 StartLineIndex - CompactedNamespacesCount - 1 == 166 AnnotatedLines[I + 1]->MatchingOpeningBlockLineIndex && 167 !AnnotatedLines[I + 1]->First->Finalized) { 168 if (hasEndComment(EndCommentPrevTok)) { 169 // remove end comment, it will be merged in next one 170 updateEndComment(EndCommentPrevTok, std::string(), SourceMgr, &Fixes); 171 } 172 CompactedNamespacesCount++; 173 AllNamespaceNames = "::" + NamespaceName + AllNamespaceNames; 174 continue; 175 } 176 NamespaceName += AllNamespaceNames; 177 CompactedNamespacesCount = 0; 178 AllNamespaceNames = std::string(); 179 } 180 // The next token in the token stream after the place where the end comment 181 // token must be. This is either the next token on the current line or the 182 // first token on the next line. 183 const FormatToken *EndCommentNextTok = EndCommentPrevTok->Next; 184 if (EndCommentNextTok && EndCommentNextTok->is(tok::comment)) 185 EndCommentNextTok = EndCommentNextTok->Next; 186 if (!EndCommentNextTok && I + 1 < E) 187 EndCommentNextTok = AnnotatedLines[I + 1]->First; 188 bool AddNewline = EndCommentNextTok && 189 EndCommentNextTok->NewlinesBefore == 0 && 190 EndCommentNextTok->isNot(tok::eof); 191 const std::string EndCommentText = 192 computeEndCommentText(NamespaceName, AddNewline); 193 if (!hasEndComment(EndCommentPrevTok)) { 194 bool isShort = I - StartLineIndex <= kShortNamespaceMaxLines + 1; 195 if (!isShort) 196 addEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes); 197 } else if (!validEndComment(EndCommentPrevTok, NamespaceName)) { 198 updateEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes); 199 } 200 StartLineIndex = SIZE_MAX; 201 } 202 return {Fixes, 0}; 203 } 204 205 } // namespace format 206 } // namespace clang 207