/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/Format/NamespaceEndCommentsFixer.cpp
Line | Count | Source (jump to first uncovered line) |
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 "clang/Basic/TokenKinds.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 { |
26 | | // Iterates all tokens starting from StartTok to EndTok and apply Fn to all |
27 | | // tokens between them including StartTok and EndTok. Returns the token after |
28 | | // EndTok. |
29 | | const FormatToken * |
30 | | processTokens(const FormatToken *Tok, tok::TokenKind StartTok, |
31 | | tok::TokenKind EndTok, |
32 | 14 | llvm::function_ref<void(const FormatToken *)> Fn) { |
33 | 14 | if (!Tok || Tok->isNot(StartTok)) |
34 | 0 | return Tok; |
35 | 14 | int NestLevel = 0; |
36 | 98 | do { |
37 | 98 | if (Tok->is(StartTok)) |
38 | 26 | ++NestLevel; |
39 | 72 | else if (Tok->is(EndTok)) |
40 | 26 | --NestLevel; |
41 | 98 | if (Fn) |
42 | 9 | Fn(Tok); |
43 | 98 | Tok = Tok->getNextNonComment(); |
44 | 98 | } while (Tok && NestLevel > 0); |
45 | 14 | return Tok; |
46 | 14 | } |
47 | | |
48 | 2.29k | const FormatToken *skipAttribute(const FormatToken *Tok) { |
49 | 2.29k | if (!Tok) |
50 | 66 | return nullptr; |
51 | 2.22k | if (Tok->is(tok::kw___attribute)) { |
52 | 1 | Tok = Tok->getNextNonComment(); |
53 | 1 | Tok = processTokens(Tok, tok::l_paren, tok::r_paren, nullptr); |
54 | 2.22k | } else if (Tok->is(tok::l_square)) { |
55 | 10 | Tok = processTokens(Tok, tok::l_square, tok::r_square, nullptr); |
56 | 10 | } |
57 | 2.22k | return Tok; |
58 | 2.29k | } |
59 | | |
60 | | // Computes the name of a namespace given the namespace token. |
61 | | // Returns "" for anonymous namespace. |
62 | 835 | std::string computeName(const FormatToken *NamespaceTok) { |
63 | 835 | assert(NamespaceTok && |
64 | 835 | NamespaceTok->isOneOf(tok::kw_namespace, TT_NamespaceMacro) && |
65 | 835 | "expecting a namespace token"); |
66 | 0 | std::string name; |
67 | 835 | const FormatToken *Tok = NamespaceTok->getNextNonComment(); |
68 | 835 | if (NamespaceTok->is(TT_NamespaceMacro)) { |
69 | | // Collects all the non-comment tokens between opening parenthesis |
70 | | // and closing parenthesis or comma. |
71 | 79 | assert(Tok && Tok->is(tok::l_paren) && "expected an opening parenthesis"); |
72 | 0 | Tok = Tok->getNextNonComment(); |
73 | 161 | while (Tok && !Tok->isOneOf(tok::r_paren, tok::comma)) { |
74 | 82 | name += Tok->TokenText; |
75 | 82 | Tok = Tok->getNextNonComment(); |
76 | 82 | } |
77 | 79 | return name; |
78 | 79 | } |
79 | 756 | Tok = skipAttribute(Tok); |
80 | | |
81 | 756 | std::string FirstNSName; |
82 | | // For `namespace [[foo]] A::B::inline C {` or |
83 | | // `namespace MACRO1 MACRO2 A::B::inline C {`, returns "A::B::inline C". |
84 | | // Peek for the first '::' (or '{' or '(')) and then return all tokens from |
85 | | // one token before that up until the '{'. A '(' might be a macro with |
86 | | // arguments. |
87 | 756 | const FormatToken *FirstNSTok = nullptr; |
88 | 1.40k | while (Tok && !Tok->isOneOf(tok::l_brace, tok::coloncolon, tok::l_paren)1.34k ) { |
89 | 652 | if (FirstNSTok) |
90 | 4 | FirstNSName += FirstNSTok->TokenText; |
91 | 652 | FirstNSTok = Tok; |
92 | 652 | Tok = Tok->getNextNonComment(); |
93 | 652 | } |
94 | | |
95 | 756 | if (FirstNSTok) |
96 | 648 | Tok = FirstNSTok; |
97 | 756 | Tok = skipAttribute(Tok); |
98 | | |
99 | 756 | FirstNSTok = nullptr; |
100 | | // Add everything from '(' to ')'. |
101 | 756 | auto AddToken = [&name](const FormatToken *Tok) { name += Tok->TokenText; }9 ; |
102 | 756 | bool IsPrevColoncolon = false; |
103 | 756 | bool HasColoncolon = false; |
104 | 756 | bool IsPrevInline = false; |
105 | 756 | bool NameFinished = false; |
106 | | // If we found '::' in name, then it's the name. Otherwise, we can't tell |
107 | | // which one is name. For example, `namespace A B {`. |
108 | 1.53k | while (Tok && Tok->isNot(tok::l_brace)1.47k ) { |
109 | 782 | if (FirstNSTok) { |
110 | 132 | if (!IsPrevInline && HasColoncolon119 && !IsPrevColoncolon75 ) { |
111 | 36 | if (FirstNSTok->is(tok::l_paren)) { |
112 | 3 | FirstNSTok = Tok = |
113 | 3 | processTokens(FirstNSTok, tok::l_paren, tok::r_paren, AddToken); |
114 | 3 | continue; |
115 | 3 | } |
116 | 33 | if (FirstNSTok->isNot(tok::coloncolon)) { |
117 | 1 | NameFinished = true; |
118 | 1 | break; |
119 | 1 | } |
120 | 33 | } |
121 | 128 | name += FirstNSTok->TokenText; |
122 | 128 | IsPrevColoncolon = FirstNSTok->is(tok::coloncolon); |
123 | 128 | HasColoncolon = HasColoncolon || IsPrevColoncolon44 ; |
124 | 128 | if (FirstNSTok->is(tok::kw_inline)) { |
125 | 4 | name += " "; |
126 | 4 | IsPrevInline = true; |
127 | 4 | } |
128 | 128 | } |
129 | 778 | FirstNSTok = Tok; |
130 | 778 | Tok = Tok->getNextNonComment(); |
131 | 778 | const FormatToken *TokAfterAttr = skipAttribute(Tok); |
132 | 778 | if (TokAfterAttr != Tok) |
133 | 0 | FirstNSTok = Tok = TokAfterAttr; |
134 | 778 | } |
135 | 756 | if (!NameFinished && FirstNSTok755 && FirstNSTok->isNot(tok::l_brace)649 ) |
136 | 646 | name += FirstNSTok->TokenText; |
137 | 756 | if (FirstNSName.empty() || HasColoncolon4 ) |
138 | 753 | return name; |
139 | 3 | return name.empty() ? FirstNSName1 : FirstNSName + " " + name2 ; |
140 | 756 | } |
141 | | |
142 | | std::string computeEndCommentText(StringRef NamespaceName, bool AddNewline, |
143 | | const FormatToken *NamespaceTok, |
144 | 799 | unsigned SpacesToAdd) { |
145 | 799 | std::string text = "//"; |
146 | 799 | text.append(SpacesToAdd, ' '); |
147 | 799 | text += NamespaceTok->TokenText; |
148 | 799 | if (NamespaceTok->is(TT_NamespaceMacro)) |
149 | 71 | text += "("; |
150 | 728 | else if (!NamespaceName.empty()) |
151 | 625 | text += ' '; |
152 | 799 | text += NamespaceName; |
153 | 799 | if (NamespaceTok->is(TT_NamespaceMacro)) |
154 | 71 | text += ")"; |
155 | 799 | if (AddNewline) |
156 | 57 | text += '\n'; |
157 | 799 | return text; |
158 | 799 | } |
159 | | |
160 | 1.33k | bool hasEndComment(const FormatToken *RBraceTok) { |
161 | 1.33k | return RBraceTok->Next && RBraceTok->Next->is(tok::comment)940 ; |
162 | 1.33k | } |
163 | | |
164 | | bool validEndComment(const FormatToken *RBraceTok, StringRef NamespaceName, |
165 | 427 | const FormatToken *NamespaceTok) { |
166 | 427 | assert(hasEndComment(RBraceTok)); |
167 | 0 | const FormatToken *Comment = RBraceTok->Next; |
168 | | |
169 | | // Matches a valid namespace end comment. |
170 | | // Valid namespace end comments don't need to be edited. |
171 | 427 | static const llvm::Regex NamespaceCommentPattern = |
172 | 427 | llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *" |
173 | 427 | "namespace( +([a-zA-Z0-9:_]+))?\\.? *(\\*/)?$", |
174 | 427 | llvm::Regex::IgnoreCase); |
175 | 427 | static const llvm::Regex NamespaceMacroCommentPattern = |
176 | 427 | llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *" |
177 | 427 | "([a-zA-Z0-9_]+)\\(([a-zA-Z0-9:_]*)\\)\\.? *(\\*/)?$", |
178 | 427 | llvm::Regex::IgnoreCase); |
179 | | |
180 | 427 | SmallVector<StringRef, 8> Groups; |
181 | 427 | if (NamespaceTok->is(TT_NamespaceMacro) && |
182 | 427 | NamespaceMacroCommentPattern.match(Comment->TokenText, &Groups)63 ) { |
183 | 56 | StringRef NamespaceTokenText = Groups.size() > 4 ? Groups[4] : ""0 ; |
184 | | // The name of the macro must be used. |
185 | 56 | if (NamespaceTokenText != NamespaceTok->TokenText) |
186 | 4 | return false; |
187 | 371 | } else if (NamespaceTok->isNot(tok::kw_namespace) || |
188 | 371 | !NamespaceCommentPattern.match(Comment->TokenText, &Groups)364 ) { |
189 | | // Comment does not match regex. |
190 | 16 | return false; |
191 | 16 | } |
192 | 407 | StringRef NamespaceNameInComment = Groups.size() > 5 ? Groups[5] : ""0 ; |
193 | | // Anonymous namespace comments must not mention a namespace name. |
194 | 407 | if (NamespaceName.empty() && !NamespaceNameInComment.empty()92 ) |
195 | 4 | return false; |
196 | 403 | StringRef AnonymousInComment = Groups.size() > 3 ? Groups[3] : ""0 ; |
197 | | // Named namespace comments must not mention anonymous namespace. |
198 | 403 | if (!NamespaceName.empty() && !AnonymousInComment.empty()315 ) |
199 | 2 | return false; |
200 | 401 | if (NamespaceNameInComment == NamespaceName) |
201 | 373 | return true; |
202 | | |
203 | | // Has namespace comment flowed onto the next line. |
204 | | // } // namespace |
205 | | // // verylongnamespacenamethatdidnotfitonthepreviouscommentline |
206 | 28 | if (!(Comment->Next && Comment->Next->is(TT_LineComment)1 )) |
207 | 27 | return false; |
208 | | |
209 | 1 | static const llvm::Regex CommentPattern = llvm::Regex( |
210 | 1 | "^/[/*] *( +([a-zA-Z0-9:_]+))?\\.? *(\\*/)?$", llvm::Regex::IgnoreCase); |
211 | | |
212 | | // Pull out just the comment text. |
213 | 1 | if (!CommentPattern.match(Comment->Next->TokenText, &Groups)) |
214 | 0 | return false; |
215 | 1 | NamespaceNameInComment = Groups.size() > 2 ? Groups[2] : ""0 ; |
216 | | |
217 | 1 | return NamespaceNameInComment == NamespaceName; |
218 | 1 | } |
219 | | |
220 | | void addEndComment(const FormatToken *RBraceTok, StringRef EndCommentText, |
221 | | const SourceManager &SourceMgr, |
222 | 224 | tooling::Replacements *Fixes) { |
223 | 224 | auto EndLoc = RBraceTok->Tok.getEndLoc(); |
224 | 224 | auto Range = CharSourceRange::getCharRange(EndLoc, EndLoc); |
225 | 224 | auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText)); |
226 | 224 | if (Err) { |
227 | 0 | llvm::errs() << "Error while adding namespace end comment: " |
228 | 0 | << llvm::toString(std::move(Err)) << "\n"; |
229 | 0 | } |
230 | 224 | } |
231 | | |
232 | | void updateEndComment(const FormatToken *RBraceTok, StringRef EndCommentText, |
233 | | const SourceManager &SourceMgr, |
234 | 69 | tooling::Replacements *Fixes) { |
235 | 69 | assert(hasEndComment(RBraceTok)); |
236 | 0 | const FormatToken *Comment = RBraceTok->Next; |
237 | 69 | auto Range = CharSourceRange::getCharRange(Comment->getStartOfNonWhitespace(), |
238 | 69 | Comment->Tok.getEndLoc()); |
239 | 69 | auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText)); |
240 | 69 | if (Err) { |
241 | 0 | llvm::errs() << "Error while updating namespace end comment: " |
242 | 0 | << llvm::toString(std::move(Err)) << "\n"; |
243 | 0 | } |
244 | 69 | } |
245 | | } // namespace |
246 | | |
247 | | const FormatToken * |
248 | | getNamespaceToken(const AnnotatedLine *Line, |
249 | 76.6k | const SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) { |
250 | 76.6k | if (!Line->Affected || Line->InPPDirective69.7k || !Line->startsWith(tok::r_brace)65.0k ) |
251 | 61.9k | return nullptr; |
252 | 14.6k | size_t StartLineIndex = Line->MatchingOpeningBlockLineIndex; |
253 | 14.6k | if (StartLineIndex == UnwrappedLine::kInvalidIndex) |
254 | 124 | return nullptr; |
255 | 14.5k | assert(StartLineIndex < AnnotatedLines.size()); |
256 | 0 | const FormatToken *NamespaceTok = AnnotatedLines[StartLineIndex]->First; |
257 | 14.5k | if (NamespaceTok->is(tok::l_brace)) { |
258 | | // "namespace" keyword can be on the line preceding '{', e.g. in styles |
259 | | // where BraceWrapping.AfterNamespace is true. |
260 | 2.42k | if (StartLineIndex > 0) { |
261 | 2.16k | NamespaceTok = AnnotatedLines[StartLineIndex - 1]->First; |
262 | 2.16k | if (AnnotatedLines[StartLineIndex - 1]->endsWith(tok::semi)) |
263 | 63 | return nullptr; |
264 | 2.16k | } |
265 | 2.42k | } |
266 | | |
267 | 14.4k | return NamespaceTok->getNamespaceToken(); |
268 | 14.5k | } |
269 | | |
270 | | StringRef |
271 | | getNamespaceTokenText(const AnnotatedLine *Line, |
272 | 81 | const SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) { |
273 | 81 | const FormatToken *NamespaceTok = getNamespaceToken(Line, AnnotatedLines); |
274 | 81 | return NamespaceTok ? NamespaceTok->TokenText40 : StringRef()41 ; |
275 | 81 | } |
276 | | |
277 | | NamespaceEndCommentsFixer::NamespaceEndCommentsFixer(const Environment &Env, |
278 | | const FormatStyle &Style) |
279 | 17.9k | : TokenAnalyzer(Env, Style) {} |
280 | | |
281 | | std::pair<tooling::Replacements, unsigned> NamespaceEndCommentsFixer::analyze( |
282 | | TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines, |
283 | 18.1k | FormatTokenLexer &Tokens) { |
284 | 18.1k | const SourceManager &SourceMgr = Env.getSourceManager(); |
285 | 18.1k | AffectedRangeMgr.computeAffectedLines(AnnotatedLines); |
286 | 18.1k | tooling::Replacements Fixes; |
287 | | |
288 | | // Spin through the lines and ensure we have balanced braces. |
289 | 18.1k | int Braces = 0; |
290 | 71.3k | for (AnnotatedLine *Line : AnnotatedLines) { |
291 | 71.3k | FormatToken *Tok = Line->First; |
292 | 386k | while (Tok) { |
293 | 315k | Braces += Tok->is(tok::l_brace) ? 115.3k : Tok->is(tok::r_brace)299k ? -115.2k : 0284k ; |
294 | 315k | Tok = Tok->Next; |
295 | 315k | } |
296 | 71.3k | } |
297 | | // Don't attempt to comment unbalanced braces or this can |
298 | | // lead to comments being placed on the closing brace which isn't |
299 | | // the matching brace of the namespace. (occurs during incomplete editing). |
300 | 18.1k | if (Braces != 0) |
301 | 109 | return {Fixes, 0}; |
302 | | |
303 | 18.0k | std::string AllNamespaceNames; |
304 | 18.0k | size_t StartLineIndex = SIZE_MAX; |
305 | 18.0k | StringRef NamespaceTokenText; |
306 | 18.0k | unsigned int CompactedNamespacesCount = 0; |
307 | 88.8k | for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I70.8k ) { |
308 | 70.8k | const AnnotatedLine *EndLine = AnnotatedLines[I]; |
309 | 70.8k | const FormatToken *NamespaceTok = |
310 | 70.8k | getNamespaceToken(EndLine, AnnotatedLines); |
311 | 70.8k | if (!NamespaceTok) |
312 | 70.0k | continue; |
313 | 836 | FormatToken *RBraceTok = EndLine->First; |
314 | 836 | if (RBraceTok->Finalized) |
315 | 1 | continue; |
316 | 835 | RBraceTok->Finalized = true; |
317 | 835 | const FormatToken *EndCommentPrevTok = RBraceTok; |
318 | | // Namespaces often end with '};'. In that case, attach namespace end |
319 | | // comments to the semicolon tokens. |
320 | 835 | if (RBraceTok->Next && RBraceTok->Next->is(tok::semi)474 ) |
321 | 47 | EndCommentPrevTok = RBraceTok->Next; |
322 | 835 | if (StartLineIndex == SIZE_MAX) |
323 | 799 | StartLineIndex = EndLine->MatchingOpeningBlockLineIndex; |
324 | 835 | std::string NamespaceName = computeName(NamespaceTok); |
325 | 835 | if (Style.CompactNamespaces) { |
326 | 81 | if (CompactedNamespacesCount == 0) |
327 | 45 | NamespaceTokenText = NamespaceTok->TokenText; |
328 | 81 | if ((I + 1 < E) && |
329 | 81 | NamespaceTokenText == |
330 | 81 | getNamespaceTokenText(AnnotatedLines[I + 1], AnnotatedLines) && |
331 | 81 | StartLineIndex - CompactedNamespacesCount - 1 == |
332 | 37 | AnnotatedLines[I + 1]->MatchingOpeningBlockLineIndex && |
333 | 81 | !AnnotatedLines[I + 1]->First->Finalized36 ) { |
334 | 36 | if (hasEndComment(EndCommentPrevTok)) { |
335 | | // remove end comment, it will be merged in next one |
336 | 16 | updateEndComment(EndCommentPrevTok, std::string(), SourceMgr, &Fixes); |
337 | 16 | } |
338 | 36 | ++CompactedNamespacesCount; |
339 | 36 | if (!NamespaceName.empty()) |
340 | 34 | AllNamespaceNames = "::" + NamespaceName + AllNamespaceNames; |
341 | 36 | continue; |
342 | 36 | } |
343 | 45 | NamespaceName += AllNamespaceNames; |
344 | 45 | CompactedNamespacesCount = 0; |
345 | 45 | AllNamespaceNames = std::string(); |
346 | 45 | } |
347 | | // The next token in the token stream after the place where the end comment |
348 | | // token must be. This is either the next token on the current line or the |
349 | | // first token on the next line. |
350 | 799 | const FormatToken *EndCommentNextTok = EndCommentPrevTok->Next; |
351 | 799 | if (EndCommentNextTok && EndCommentNextTok->is(tok::comment)428 ) |
352 | 427 | EndCommentNextTok = EndCommentNextTok->Next; |
353 | 799 | if (!EndCommentNextTok && I + 1 < E797 ) |
354 | 797 | EndCommentNextTok = AnnotatedLines[I + 1]->First; |
355 | 799 | bool AddNewline = EndCommentNextTok && |
356 | 799 | EndCommentNextTok->NewlinesBefore == 0 && |
357 | 799 | EndCommentNextTok->isNot(tok::eof)534 ; |
358 | 799 | const std::string EndCommentText = |
359 | 799 | computeEndCommentText(NamespaceName, AddNewline, NamespaceTok, |
360 | 799 | Style.SpacesInLineCommentPrefix.Minimum); |
361 | 799 | if (!hasEndComment(EndCommentPrevTok)) { |
362 | 372 | bool isShort = I - StartLineIndex <= Style.ShortNamespaceLines + 1; |
363 | 372 | if (!isShort) |
364 | 224 | addEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes); |
365 | 427 | } else if (!validEndComment(EndCommentPrevTok, NamespaceName, |
366 | 427 | NamespaceTok)) { |
367 | 53 | updateEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes); |
368 | 53 | } |
369 | 799 | StartLineIndex = SIZE_MAX; |
370 | 799 | } |
371 | 18.0k | return {Fixes, 0}; |
372 | 18.1k | } |
373 | | |
374 | | } // namespace format |
375 | | } // namespace clang |