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 | } |
Francois Ferrand | e56a829 | 2017-06-14 12:29:47 +0000 | [diff] [blame] | 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; |
Marek Kurdej | caf6fd5 | 2017-09-27 07:51:51 +0000 | [diff] [blame] | 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 | } |
Francois Ferrand | e56a829 | 2017-06-14 12:29:47 +0000 | [diff] [blame] | 127 | // Detect "(inline)? namespace" in the beginning of a line. |
| 128 | if (NamespaceTok->is(tok::kw_inline)) |
| 129 | NamespaceTok = NamespaceTok->getNextNonComment(); |
| 130 | if (!NamespaceTok || NamespaceTok->isNot(tok::kw_namespace)) |
| 131 | return nullptr; |
| 132 | return NamespaceTok; |
| 133 | } |
Krasimir Georgiev | 7cb267a | 2017-02-27 13:28:36 +0000 | [diff] [blame] | 134 | } // namespace |
| 135 | |
| 136 | NamespaceEndCommentsFixer::NamespaceEndCommentsFixer(const Environment &Env, |
| 137 | const FormatStyle &Style) |
| 138 | : TokenAnalyzer(Env, Style) {} |
| 139 | |
Krasimir Georgiev | 9ad83fe | 2017-10-30 14:01:50 +0000 | [diff] [blame] | 140 | std::pair<tooling::Replacements, unsigned> NamespaceEndCommentsFixer::analyze( |
Krasimir Georgiev | 7cb267a | 2017-02-27 13:28:36 +0000 | [diff] [blame] | 141 | TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines, |
| 142 | FormatTokenLexer &Tokens) { |
| 143 | const SourceManager &SourceMgr = Env.getSourceManager(); |
| 144 | AffectedRangeMgr.computeAffectedLines(AnnotatedLines.begin(), |
| 145 | AnnotatedLines.end()); |
| 146 | tooling::Replacements Fixes; |
Francois Ferrand | e56a829 | 2017-06-14 12:29:47 +0000 | [diff] [blame] | 147 | std::string AllNamespaceNames = ""; |
| 148 | size_t StartLineIndex = SIZE_MAX; |
| 149 | unsigned int CompactedNamespacesCount = 0; |
Krasimir Georgiev | 7cb267a | 2017-02-27 13:28:36 +0000 | [diff] [blame] | 150 | for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I) { |
Krasimir Georgiev | 7cb267a | 2017-02-27 13:28:36 +0000 | [diff] [blame] | 151 | const AnnotatedLine *EndLine = AnnotatedLines[I]; |
Francois Ferrand | e56a829 | 2017-06-14 12:29:47 +0000 | [diff] [blame] | 152 | const FormatToken *NamespaceTok = |
| 153 | getNamespaceToken(EndLine, AnnotatedLines); |
| 154 | if (!NamespaceTok) |
Krasimir Georgiev | 7cb267a | 2017-02-27 13:28:36 +0000 | [diff] [blame] | 155 | continue; |
Krasimir Georgiev | bda7739 | 2017-03-06 16:44:45 +0000 | [diff] [blame] | 156 | FormatToken *RBraceTok = EndLine->First; |
| 157 | if (RBraceTok->Finalized) |
| 158 | continue; |
| 159 | RBraceTok->Finalized = true; |
Krasimir Georgiev | eb62118e | 2017-03-07 14:07:43 +0000 | [diff] [blame] | 160 | const FormatToken *EndCommentPrevTok = RBraceTok; |
| 161 | // Namespaces often end with '};'. In that case, attach namespace end |
| 162 | // comments to the semicolon tokens. |
| 163 | if (RBraceTok->Next && RBraceTok->Next->is(tok::semi)) { |
| 164 | EndCommentPrevTok = RBraceTok->Next; |
| 165 | } |
Francois Ferrand | e56a829 | 2017-06-14 12:29:47 +0000 | [diff] [blame] | 166 | if (StartLineIndex == SIZE_MAX) |
| 167 | StartLineIndex = EndLine->MatchingOpeningBlockLineIndex; |
| 168 | std::string NamespaceName = computeName(NamespaceTok); |
| 169 | if (Style.CompactNamespaces) { |
| 170 | if ((I + 1 < E) && |
| 171 | getNamespaceToken(AnnotatedLines[I + 1], AnnotatedLines) && |
| 172 | StartLineIndex - CompactedNamespacesCount - 1 == |
| 173 | AnnotatedLines[I + 1]->MatchingOpeningBlockLineIndex && |
| 174 | !AnnotatedLines[I + 1]->First->Finalized) { |
| 175 | if (hasEndComment(EndCommentPrevTok)) { |
| 176 | // remove end comment, it will be merged in next one |
| 177 | updateEndComment(EndCommentPrevTok, std::string(), SourceMgr, &Fixes); |
| 178 | } |
| 179 | CompactedNamespacesCount++; |
| 180 | AllNamespaceNames = "::" + NamespaceName + AllNamespaceNames; |
| 181 | continue; |
| 182 | } |
Krasimir Georgiev | 3d4c812 | 2017-06-27 14:07:45 +0000 | [diff] [blame] | 183 | NamespaceName += AllNamespaceNames; |
Francois Ferrand | e56a829 | 2017-06-14 12:29:47 +0000 | [diff] [blame] | 184 | CompactedNamespacesCount = 0; |
| 185 | AllNamespaceNames = std::string(); |
| 186 | } |
Krasimir Georgiev | eb62118e | 2017-03-07 14:07:43 +0000 | [diff] [blame] | 187 | // The next token in the token stream after the place where the end comment |
| 188 | // token must be. This is either the next token on the current line or the |
| 189 | // first token on the next line. |
| 190 | const FormatToken *EndCommentNextTok = EndCommentPrevTok->Next; |
| 191 | if (EndCommentNextTok && EndCommentNextTok->is(tok::comment)) |
| 192 | EndCommentNextTok = EndCommentNextTok->Next; |
| 193 | if (!EndCommentNextTok && I + 1 < E) |
| 194 | EndCommentNextTok = AnnotatedLines[I + 1]->First; |
| 195 | bool AddNewline = EndCommentNextTok && |
| 196 | EndCommentNextTok->NewlinesBefore == 0 && |
| 197 | EndCommentNextTok->isNot(tok::eof); |
Krasimir Georgiev | 7cb267a | 2017-02-27 13:28:36 +0000 | [diff] [blame] | 198 | const std::string EndCommentText = |
| 199 | computeEndCommentText(NamespaceName, AddNewline); |
Krasimir Georgiev | eb62118e | 2017-03-07 14:07:43 +0000 | [diff] [blame] | 200 | if (!hasEndComment(EndCommentPrevTok)) { |
Krasimir Georgiev | 9163fe2 | 2017-03-02 09:54:44 +0000 | [diff] [blame] | 201 | bool isShort = I - StartLineIndex <= kShortNamespaceMaxLines + 1; |
| 202 | if (!isShort) |
Krasimir Georgiev | eb62118e | 2017-03-07 14:07:43 +0000 | [diff] [blame] | 203 | addEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes); |
Francois Ferrand | e56a829 | 2017-06-14 12:29:47 +0000 | [diff] [blame] | 204 | } else if (!validEndComment(EndCommentPrevTok, NamespaceName)) { |
Krasimir Georgiev | eb62118e | 2017-03-07 14:07:43 +0000 | [diff] [blame] | 205 | updateEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes); |
Francois Ferrand | e56a829 | 2017-06-14 12:29:47 +0000 | [diff] [blame] | 206 | } |
| 207 | StartLineIndex = SIZE_MAX; |
Krasimir Georgiev | 7cb267a | 2017-02-27 13:28:36 +0000 | [diff] [blame] | 208 | } |
Krasimir Georgiev | 9ad83fe | 2017-10-30 14:01:50 +0000 | [diff] [blame] | 209 | return {Fixes, 0}; |
Krasimir Georgiev | 7cb267a | 2017-02-27 13:28:36 +0000 | [diff] [blame] | 210 | } |
| 211 | |
| 212 | } // namespace format |
| 213 | } // namespace clang |