Krasimir Georgiev | 7cb267a | 2017-02-27 13:28:36 +0000 | [diff] [blame] | 1 | //===--- NamespaceEndCommentsFixer.cpp --------------------------*- C++ -*-===// |
| 2 | // |
| 3 | // The LLVM Compiler Infrastructure |
| 4 | // |
| 5 | // This file is distributed under the University of Illinois Open Source |
| 6 | // License. See LICENSE.TXT for details. |
| 7 | // |
| 8 | //===----------------------------------------------------------------------===// |
| 9 | /// |
| 10 | /// \file |
| 11 | /// \brief This file implements NamespaceEndCommentsFixer, a TokenAnalyzer that |
| 12 | /// fixes namespace end comments. |
| 13 | /// |
| 14 | //===----------------------------------------------------------------------===// |
| 15 | |
| 16 | #include "NamespaceEndCommentsFixer.h" |
| 17 | #include "llvm/Support/Debug.h" |
| 18 | #include "llvm/Support/Regex.h" |
| 19 | |
| 20 | #define DEBUG_TYPE "namespace-end-comments-fixer" |
| 21 | |
| 22 | namespace clang { |
| 23 | namespace format { |
| 24 | |
| 25 | namespace { |
Krasimir Georgiev | 9163fe2 | 2017-03-02 09:54:44 +0000 | [diff] [blame^] | 26 | // The maximal number of unwrapped lines that a short namespace spans. |
Krasimir Georgiev | 7cb267a | 2017-02-27 13:28:36 +0000 | [diff] [blame] | 27 | // Short namespaces don't need an end comment. |
| 28 | static const int kShortNamespaceMaxLines = 1; |
| 29 | |
| 30 | // Matches a valid namespace end comment. |
| 31 | // Valid namespace end comments don't need to be edited. |
| 32 | static llvm::Regex kNamespaceCommentPattern = |
| 33 | llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *" |
| 34 | "namespace( +([a-zA-Z0-9:_]+))?\\.? *(\\*/)?$", |
| 35 | llvm::Regex::IgnoreCase); |
| 36 | |
| 37 | // Computes the name of a namespace given the namespace token. |
| 38 | // Returns "" for anonymous namespace. |
| 39 | std::string computeName(const FormatToken *NamespaceTok) { |
| 40 | assert(NamespaceTok && NamespaceTok->is(tok::kw_namespace) && |
| 41 | "expecting a namespace token"); |
| 42 | std::string name = ""; |
| 43 | // Collects all the non-comment tokens between 'namespace' and '{'. |
| 44 | const FormatToken *Tok = NamespaceTok->getNextNonComment(); |
| 45 | while (Tok && !Tok->is(tok::l_brace)) { |
| 46 | name += Tok->TokenText; |
| 47 | Tok = Tok->getNextNonComment(); |
| 48 | } |
| 49 | return name; |
| 50 | } |
| 51 | |
| 52 | std::string computeEndCommentText(StringRef NamespaceName, bool AddNewline) { |
| 53 | std::string text = "// namespace"; |
| 54 | if (!NamespaceName.empty()) { |
| 55 | text += ' '; |
| 56 | text += NamespaceName; |
| 57 | } |
| 58 | if (AddNewline) |
| 59 | text += '\n'; |
| 60 | return text; |
| 61 | } |
| 62 | |
Krasimir Georgiev | 7cb267a | 2017-02-27 13:28:36 +0000 | [diff] [blame] | 63 | bool hasEndComment(const FormatToken *RBraceTok) { |
| 64 | return RBraceTok->Next && RBraceTok->Next->is(tok::comment); |
| 65 | } |
| 66 | |
| 67 | bool validEndComment(const FormatToken *RBraceTok, StringRef NamespaceName) { |
| 68 | assert(hasEndComment(RBraceTok)); |
| 69 | const FormatToken *Comment = RBraceTok->Next; |
| 70 | SmallVector<StringRef, 7> Groups; |
| 71 | if (kNamespaceCommentPattern.match(Comment->TokenText, &Groups)) { |
| 72 | StringRef NamespaceNameInComment = Groups.size() > 5 ? Groups[5] : ""; |
| 73 | // Anonymous namespace comments must not mention a namespace name. |
| 74 | if (NamespaceName.empty() && !NamespaceNameInComment.empty()) |
| 75 | return false; |
| 76 | StringRef AnonymousInComment = Groups.size() > 3 ? Groups[3] : ""; |
| 77 | // Named namespace comments must not mention anonymous namespace. |
| 78 | if (!NamespaceName.empty() && !AnonymousInComment.empty()) |
| 79 | return false; |
| 80 | return NamespaceNameInComment == NamespaceName; |
| 81 | } |
| 82 | return false; |
| 83 | } |
| 84 | |
| 85 | void addEndComment(const FormatToken *RBraceTok, StringRef EndCommentText, |
| 86 | const SourceManager &SourceMgr, |
| 87 | tooling::Replacements *Fixes) { |
| 88 | auto EndLoc = RBraceTok->Tok.getEndLoc(); |
| 89 | auto Range = CharSourceRange::getCharRange(EndLoc, EndLoc); |
| 90 | auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText)); |
| 91 | if (Err) { |
| 92 | llvm::errs() << "Error while adding namespace end comment: " |
| 93 | << llvm::toString(std::move(Err)) << "\n"; |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | void updateEndComment(const FormatToken *RBraceTok, StringRef EndCommentText, |
| 98 | const SourceManager &SourceMgr, |
| 99 | tooling::Replacements *Fixes) { |
| 100 | assert(hasEndComment(RBraceTok)); |
| 101 | const FormatToken *Comment = RBraceTok->Next; |
| 102 | auto Range = CharSourceRange::getCharRange(Comment->getStartOfNonWhitespace(), |
| 103 | Comment->Tok.getEndLoc()); |
| 104 | auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText)); |
| 105 | if (Err) { |
| 106 | llvm::errs() << "Error while updating namespace end comment: " |
| 107 | << llvm::toString(std::move(Err)) << "\n"; |
| 108 | } |
| 109 | } |
| 110 | } // namespace |
| 111 | |
| 112 | NamespaceEndCommentsFixer::NamespaceEndCommentsFixer(const Environment &Env, |
| 113 | const FormatStyle &Style) |
| 114 | : TokenAnalyzer(Env, Style) {} |
| 115 | |
| 116 | tooling::Replacements NamespaceEndCommentsFixer::analyze( |
| 117 | TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines, |
| 118 | FormatTokenLexer &Tokens) { |
| 119 | const SourceManager &SourceMgr = Env.getSourceManager(); |
| 120 | AffectedRangeMgr.computeAffectedLines(AnnotatedLines.begin(), |
| 121 | AnnotatedLines.end()); |
| 122 | tooling::Replacements Fixes; |
| 123 | for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I) { |
| 124 | if (!AnnotatedLines[I]->Affected || AnnotatedLines[I]->InPPDirective || |
| 125 | !AnnotatedLines[I]->startsWith(tok::r_brace)) |
| 126 | continue; |
| 127 | const AnnotatedLine *EndLine = AnnotatedLines[I]; |
| 128 | size_t StartLineIndex = EndLine->MatchingOpeningBlockLineIndex; |
| 129 | if (StartLineIndex == UnwrappedLine::kInvalidIndex) |
| 130 | continue; |
| 131 | assert(StartLineIndex < E); |
| 132 | const FormatToken *NamespaceTok = AnnotatedLines[StartLineIndex]->First; |
| 133 | // Detect "(inline)? namespace" in the beginning of a line. |
| 134 | if (NamespaceTok->is(tok::kw_inline)) |
| 135 | NamespaceTok = NamespaceTok->getNextNonComment(); |
| 136 | if (NamespaceTok->isNot(tok::kw_namespace)) |
| 137 | continue; |
| 138 | const FormatToken *RBraceTok = EndLine->First; |
| 139 | const std::string NamespaceName = computeName(NamespaceTok); |
| 140 | bool AddNewline = (I + 1 < E) && |
| 141 | AnnotatedLines[I + 1]->First->NewlinesBefore == 0 && |
| 142 | AnnotatedLines[I + 1]->First->isNot(tok::eof); |
| 143 | const std::string EndCommentText = |
| 144 | computeEndCommentText(NamespaceName, AddNewline); |
| 145 | if (!hasEndComment(RBraceTok)) { |
Krasimir Georgiev | 9163fe2 | 2017-03-02 09:54:44 +0000 | [diff] [blame^] | 146 | bool isShort = I - StartLineIndex <= kShortNamespaceMaxLines + 1; |
| 147 | if (!isShort) |
Krasimir Georgiev | 7cb267a | 2017-02-27 13:28:36 +0000 | [diff] [blame] | 148 | addEndComment(RBraceTok, EndCommentText, SourceMgr, &Fixes); |
| 149 | continue; |
| 150 | } |
| 151 | if (!validEndComment(RBraceTok, NamespaceName)) |
| 152 | updateEndComment(RBraceTok, EndCommentText, SourceMgr, &Fixes); |
| 153 | } |
| 154 | return Fixes; |
| 155 | } |
| 156 | |
| 157 | } // namespace format |
| 158 | } // namespace clang |