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 | (0) . __assert_fail ("NamespaceTok && NamespaceTok->is(tok..kw_namespace) && \"expecting a namespace token\"", "/home/seafit/code_projects/clang_source/clang/lib/Format/NamespaceEndCommentsFixer.cpp", 33, __PRETTY_FUNCTION__))" file_link="../../../include/assert.h.html#88" macro="true">assert(NamespaceTok && NamespaceTok->is(tok::kw_namespace) && |
33 | (0) . __assert_fail ("NamespaceTok && NamespaceTok->is(tok..kw_namespace) && \"expecting a namespace token\"", "/home/seafit/code_projects/clang_source/clang/lib/Format/NamespaceEndCommentsFixer.cpp", 33, __PRETTY_FUNCTION__))" file_link="../../../include/assert.h.html#88" macro="true"> "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 | |