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 && 33 NamespaceTok->isOneOf(tok::kw_namespace, TT_NamespaceMacro) && 34 "expecting a namespace token"); 35 std::string name = ""; 36 const FormatToken *Tok = NamespaceTok->getNextNonComment(); 37 if (NamespaceTok->is(TT_NamespaceMacro)) { 38 // Collects all the non-comment tokens between opening parenthesis 39 // and closing parenthesis or comma 40 assert(Tok && Tok->is(tok::l_paren) && "expected an opening parenthesis"); 41 Tok = Tok->getNextNonComment(); 42 while (Tok && !Tok->isOneOf(tok::r_paren, tok::comma)) { 43 name += Tok->TokenText; 44 Tok = Tok->getNextNonComment(); 45 } 46 } else { 47 // Collects all the non-comment tokens between 'namespace' and '{'. 48 while (Tok && !Tok->is(tok::l_brace)) { 49 name += Tok->TokenText; 50 Tok = Tok->getNextNonComment(); 51 } 52 } 53 return name; 54 } 55 56 std::string computeEndCommentText(StringRef NamespaceName, bool AddNewline, 57 const FormatToken *NamespaceTok) { 58 std::string text = "// "; 59 text += NamespaceTok->TokenText; 60 if (NamespaceTok->is(TT_NamespaceMacro)) 61 text += "("; 62 else if (!NamespaceName.empty()) 63 text += ' '; 64 text += NamespaceName; 65 if (NamespaceTok->is(TT_NamespaceMacro)) 66 text += ")"; 67 if (AddNewline) 68 text += '\n'; 69 return text; 70 } 71 72 bool hasEndComment(const FormatToken *RBraceTok) { 73 return RBraceTok->Next && RBraceTok->Next->is(tok::comment); 74 } 75 76 bool validEndComment(const FormatToken *RBraceTok, StringRef NamespaceName, 77 const FormatToken *NamespaceTok) { 78 assert(hasEndComment(RBraceTok)); 79 const FormatToken *Comment = RBraceTok->Next; 80 81 // Matches a valid namespace end comment. 82 // Valid namespace end comments don't need to be edited. 83 static llvm::Regex *const NamespaceCommentPattern = 84 new llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *" 85 "namespace( +([a-zA-Z0-9:_]+))?\\.? *(\\*/)?$", 86 llvm::Regex::IgnoreCase); 87 static llvm::Regex *const NamespaceMacroCommentPattern = 88 new llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *" 89 "([a-zA-Z0-9_]+)\\(([a-zA-Z0-9:_]*)\\)\\.? *(\\*/)?$", 90 llvm::Regex::IgnoreCase); 91 92 SmallVector<StringRef, 8> Groups; 93 if (NamespaceTok->is(TT_NamespaceMacro) && 94 NamespaceMacroCommentPattern->match(Comment->TokenText, &Groups)) { 95 StringRef NamespaceTokenText = Groups.size() > 4 ? Groups[4] : ""; 96 // The name of the macro must be used. 97 if (NamespaceTokenText != NamespaceTok->TokenText) 98 return false; 99 } else if (NamespaceTok->isNot(tok::kw_namespace) || 100 !NamespaceCommentPattern->match(Comment->TokenText, &Groups)) { 101 // Comment does not match regex. 102 return false; 103 } 104 StringRef NamespaceNameInComment = Groups.size() > 5 ? Groups[5] : ""; 105 // Anonymous namespace comments must not mention a namespace name. 106 if (NamespaceName.empty() && !NamespaceNameInComment.empty()) 107 return false; 108 StringRef AnonymousInComment = Groups.size() > 3 ? Groups[3] : ""; 109 // Named namespace comments must not mention anonymous namespace. 110 if (!NamespaceName.empty() && !AnonymousInComment.empty()) 111 return false; 112 return NamespaceNameInComment == NamespaceName; 113 } 114 115 void addEndComment(const FormatToken *RBraceTok, StringRef EndCommentText, 116 const SourceManager &SourceMgr, 117 tooling::Replacements *Fixes) { 118 auto EndLoc = RBraceTok->Tok.getEndLoc(); 119 auto Range = CharSourceRange::getCharRange(EndLoc, EndLoc); 120 auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText)); 121 if (Err) { 122 llvm::errs() << "Error while adding namespace end comment: " 123 << llvm::toString(std::move(Err)) << "\n"; 124 } 125 } 126 127 void updateEndComment(const FormatToken *RBraceTok, StringRef EndCommentText, 128 const SourceManager &SourceMgr, 129 tooling::Replacements *Fixes) { 130 assert(hasEndComment(RBraceTok)); 131 const FormatToken *Comment = RBraceTok->Next; 132 auto Range = CharSourceRange::getCharRange(Comment->getStartOfNonWhitespace(), 133 Comment->Tok.getEndLoc()); 134 auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText)); 135 if (Err) { 136 llvm::errs() << "Error while updating namespace end comment: " 137 << llvm::toString(std::move(Err)) << "\n"; 138 } 139 } 140 } // namespace 141 142 const FormatToken * 143 getNamespaceToken(const AnnotatedLine *Line, 144 const SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) { 145 if (!Line->Affected || Line->InPPDirective || !Line->startsWith(tok::r_brace)) 146 return nullptr; 147 size_t StartLineIndex = Line->MatchingOpeningBlockLineIndex; 148 if (StartLineIndex == UnwrappedLine::kInvalidIndex) 149 return nullptr; 150 assert(StartLineIndex < AnnotatedLines.size()); 151 const FormatToken *NamespaceTok = AnnotatedLines[StartLineIndex]->First; 152 if (NamespaceTok->is(tok::l_brace)) { 153 // "namespace" keyword can be on the line preceding '{', e.g. in styles 154 // where BraceWrapping.AfterNamespace is true. 155 if (StartLineIndex > 0) 156 NamespaceTok = AnnotatedLines[StartLineIndex - 1]->First; 157 } 158 return NamespaceTok->getNamespaceToken(); 159 } 160 161 StringRef 162 getNamespaceTokenText(const AnnotatedLine *Line, 163 const SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) { 164 const FormatToken *NamespaceTok = getNamespaceToken(Line, AnnotatedLines); 165 return NamespaceTok ? NamespaceTok->TokenText : StringRef(); 166 } 167 168 NamespaceEndCommentsFixer::NamespaceEndCommentsFixer(const Environment &Env, 169 const FormatStyle &Style) 170 : TokenAnalyzer(Env, Style) {} 171 172 std::pair<tooling::Replacements, unsigned> NamespaceEndCommentsFixer::analyze( 173 TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines, 174 FormatTokenLexer &Tokens) { 175 const SourceManager &SourceMgr = Env.getSourceManager(); 176 AffectedRangeMgr.computeAffectedLines(AnnotatedLines); 177 tooling::Replacements Fixes; 178 std::string AllNamespaceNames = ""; 179 size_t StartLineIndex = SIZE_MAX; 180 StringRef NamespaceTokenText; 181 unsigned int CompactedNamespacesCount = 0; 182 for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I) { 183 const AnnotatedLine *EndLine = AnnotatedLines[I]; 184 const FormatToken *NamespaceTok = 185 getNamespaceToken(EndLine, AnnotatedLines); 186 if (!NamespaceTok) 187 continue; 188 FormatToken *RBraceTok = EndLine->First; 189 if (RBraceTok->Finalized) 190 continue; 191 RBraceTok->Finalized = true; 192 const FormatToken *EndCommentPrevTok = RBraceTok; 193 // Namespaces often end with '};'. In that case, attach namespace end 194 // comments to the semicolon tokens. 195 if (RBraceTok->Next && RBraceTok->Next->is(tok::semi)) { 196 EndCommentPrevTok = RBraceTok->Next; 197 } 198 if (StartLineIndex == SIZE_MAX) 199 StartLineIndex = EndLine->MatchingOpeningBlockLineIndex; 200 std::string NamespaceName = computeName(NamespaceTok); 201 if (Style.CompactNamespaces) { 202 if (CompactedNamespacesCount == 0) 203 NamespaceTokenText = NamespaceTok->TokenText; 204 if ((I + 1 < E) && 205 NamespaceTokenText == 206 getNamespaceTokenText(AnnotatedLines[I + 1], AnnotatedLines) && 207 StartLineIndex - CompactedNamespacesCount - 1 == 208 AnnotatedLines[I + 1]->MatchingOpeningBlockLineIndex && 209 !AnnotatedLines[I + 1]->First->Finalized) { 210 if (hasEndComment(EndCommentPrevTok)) { 211 // remove end comment, it will be merged in next one 212 updateEndComment(EndCommentPrevTok, std::string(), SourceMgr, &Fixes); 213 } 214 CompactedNamespacesCount++; 215 AllNamespaceNames = "::" + NamespaceName + AllNamespaceNames; 216 continue; 217 } 218 NamespaceName += AllNamespaceNames; 219 CompactedNamespacesCount = 0; 220 AllNamespaceNames = std::string(); 221 } 222 // The next token in the token stream after the place where the end comment 223 // token must be. This is either the next token on the current line or the 224 // first token on the next line. 225 const FormatToken *EndCommentNextTok = EndCommentPrevTok->Next; 226 if (EndCommentNextTok && EndCommentNextTok->is(tok::comment)) 227 EndCommentNextTok = EndCommentNextTok->Next; 228 if (!EndCommentNextTok && I + 1 < E) 229 EndCommentNextTok = AnnotatedLines[I + 1]->First; 230 bool AddNewline = EndCommentNextTok && 231 EndCommentNextTok->NewlinesBefore == 0 && 232 EndCommentNextTok->isNot(tok::eof); 233 const std::string EndCommentText = 234 computeEndCommentText(NamespaceName, AddNewline, NamespaceTok); 235 if (!hasEndComment(EndCommentPrevTok)) { 236 bool isShort = I - StartLineIndex <= kShortNamespaceMaxLines + 1; 237 if (!isShort) 238 addEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes); 239 } else if (!validEndComment(EndCommentPrevTok, NamespaceName, 240 NamespaceTok)) { 241 updateEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes); 242 } 243 StartLineIndex = SIZE_MAX; 244 } 245 return {Fixes, 0}; 246 } 247 248 } // namespace format 249 } // namespace clang 250