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 | llvm::function_ref<void(const FormatToken *)> Fn) { |
33 | if (!Tok || Tok->isNot(Kind: StartTok)) |
34 | return Tok; |
35 | int NestLevel = 0; |
36 | do { |
37 | if (Tok->is(Kind: StartTok)) |
38 | ++NestLevel; |
39 | else if (Tok->is(Kind: EndTok)) |
40 | --NestLevel; |
41 | if (Fn) |
42 | Fn(Tok); |
43 | Tok = Tok->getNextNonComment(); |
44 | } while (Tok && NestLevel > 0); |
45 | return Tok; |
46 | } |
47 | |
48 | const FormatToken *skipAttribute(const FormatToken *Tok) { |
49 | if (!Tok) |
50 | return nullptr; |
51 | if (Tok->isAttribute()) { |
52 | Tok = Tok->getNextNonComment(); |
53 | Tok = processTokens(Tok, StartTok: tok::l_paren, EndTok: tok::r_paren, Fn: nullptr); |
54 | } else if (Tok->is(Kind: tok::l_square)) { |
55 | Tok = processTokens(Tok, StartTok: tok::l_square, EndTok: tok::r_square, Fn: nullptr); |
56 | } |
57 | return Tok; |
58 | } |
59 | |
60 | // Computes the name of a namespace given the namespace token. |
61 | // Returns "" for anonymous namespace. |
62 | std::string computeName(const FormatToken *NamespaceTok) { |
63 | assert(NamespaceTok && |
64 | NamespaceTok->isOneOf(tok::kw_namespace, TT_NamespaceMacro) && |
65 | "expecting a namespace token" ); |
66 | std::string name; |
67 | const FormatToken *Tok = NamespaceTok->getNextNonComment(); |
68 | if (NamespaceTok->is(TT: TT_NamespaceMacro)) { |
69 | // Collects all the non-comment tokens between opening parenthesis |
70 | // and closing parenthesis or comma. |
71 | assert(Tok && Tok->is(tok::l_paren) && "expected an opening parenthesis" ); |
72 | Tok = Tok->getNextNonComment(); |
73 | while (Tok && !Tok->isOneOf(K1: tok::r_paren, K2: tok::comma)) { |
74 | name += Tok->TokenText; |
75 | Tok = Tok->getNextNonComment(); |
76 | } |
77 | return name; |
78 | } |
79 | Tok = skipAttribute(Tok); |
80 | |
81 | 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 | const FormatToken *FirstNSTok = nullptr; |
88 | while (Tok && !Tok->isOneOf(K1: tok::l_brace, K2: tok::coloncolon, Ks: tok::l_paren)) { |
89 | if (FirstNSTok) |
90 | FirstNSName += FirstNSTok->TokenText; |
91 | FirstNSTok = Tok; |
92 | Tok = Tok->getNextNonComment(); |
93 | } |
94 | |
95 | if (FirstNSTok) |
96 | Tok = FirstNSTok; |
97 | Tok = skipAttribute(Tok); |
98 | |
99 | FirstNSTok = nullptr; |
100 | // Add everything from '(' to ')'. |
101 | auto AddToken = [&name](const FormatToken *Tok) { name += Tok->TokenText; }; |
102 | bool IsPrevColoncolon = false; |
103 | bool HasColoncolon = false; |
104 | bool IsPrevInline = false; |
105 | 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 | while (Tok && Tok->isNot(Kind: tok::l_brace)) { |
109 | if (FirstNSTok) { |
110 | if (!IsPrevInline && HasColoncolon && !IsPrevColoncolon) { |
111 | if (FirstNSTok->is(Kind: tok::l_paren)) { |
112 | FirstNSTok = Tok = |
113 | processTokens(Tok: FirstNSTok, StartTok: tok::l_paren, EndTok: tok::r_paren, Fn: AddToken); |
114 | continue; |
115 | } |
116 | if (FirstNSTok->isNot(Kind: tok::coloncolon)) { |
117 | NameFinished = true; |
118 | break; |
119 | } |
120 | } |
121 | name += FirstNSTok->TokenText; |
122 | IsPrevColoncolon = FirstNSTok->is(Kind: tok::coloncolon); |
123 | HasColoncolon = HasColoncolon || IsPrevColoncolon; |
124 | if (FirstNSTok->is(Kind: tok::kw_inline)) { |
125 | name += " " ; |
126 | IsPrevInline = true; |
127 | } |
128 | } |
129 | FirstNSTok = Tok; |
130 | Tok = Tok->getNextNonComment(); |
131 | const FormatToken *TokAfterAttr = skipAttribute(Tok); |
132 | if (TokAfterAttr != Tok) |
133 | FirstNSTok = Tok = TokAfterAttr; |
134 | } |
135 | if (!NameFinished && FirstNSTok && FirstNSTok->isNot(Kind: tok::l_brace)) |
136 | name += FirstNSTok->TokenText; |
137 | if (FirstNSName.empty() || HasColoncolon) |
138 | return name; |
139 | return name.empty() ? FirstNSName : FirstNSName + " " + name; |
140 | } |
141 | |
142 | std::string (StringRef NamespaceName, bool AddNewline, |
143 | const FormatToken *NamespaceTok, |
144 | unsigned SpacesToAdd) { |
145 | std::string text = "//" ; |
146 | text.append(n: SpacesToAdd, c: ' '); |
147 | text += NamespaceTok->TokenText; |
148 | if (NamespaceTok->is(TT: TT_NamespaceMacro)) |
149 | text += "(" ; |
150 | else if (!NamespaceName.empty()) |
151 | text += ' '; |
152 | text += NamespaceName; |
153 | if (NamespaceTok->is(TT: TT_NamespaceMacro)) |
154 | text += ")" ; |
155 | if (AddNewline) |
156 | text += '\n'; |
157 | return text; |
158 | } |
159 | |
160 | bool (const FormatToken *RBraceTok) { |
161 | return RBraceTok->Next && RBraceTok->Next->is(Kind: tok::comment); |
162 | } |
163 | |
164 | bool (const FormatToken *RBraceTok, StringRef NamespaceName, |
165 | const FormatToken *NamespaceTok) { |
166 | assert(hasEndComment(RBraceTok)); |
167 | const FormatToken * = RBraceTok->Next; |
168 | |
169 | // Matches a valid namespace end comment. |
170 | // Valid namespace end comments don't need to be edited. |
171 | static const llvm::Regex = |
172 | llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *" |
173 | "namespace( +([a-zA-Z0-9:_ ]+))?\\.? *(\\*/)?$" , |
174 | llvm::Regex::IgnoreCase); |
175 | static const llvm::Regex = |
176 | llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *" |
177 | "([a-zA-Z0-9_]+)\\(([a-zA-Z0-9:_]*|\".+\")\\)\\.? *(\\*/)?$" , |
178 | llvm::Regex::IgnoreCase); |
179 | |
180 | SmallVector<StringRef, 8> Groups; |
181 | if (NamespaceTok->is(TT: TT_NamespaceMacro) && |
182 | NamespaceMacroCommentPattern.match(String: Comment->TokenText, Matches: &Groups)) { |
183 | StringRef NamespaceTokenText = Groups.size() > 4 ? Groups[4] : "" ; |
184 | // The name of the macro must be used. |
185 | if (NamespaceTokenText != NamespaceTok->TokenText) |
186 | return false; |
187 | } else if (NamespaceTok->isNot(Kind: tok::kw_namespace) || |
188 | !NamespaceCommentPattern.match(String: Comment->TokenText, Matches: &Groups)) { |
189 | // Comment does not match regex. |
190 | return false; |
191 | } |
192 | StringRef = Groups.size() > 5 ? Groups[5].rtrim() : "" ; |
193 | // Anonymous namespace comments must not mention a namespace name. |
194 | if (NamespaceName.empty() && !NamespaceNameInComment.empty()) |
195 | return false; |
196 | StringRef = Groups.size() > 3 ? Groups[3] : "" ; |
197 | // Named namespace comments must not mention anonymous namespace. |
198 | if (!NamespaceName.empty() && !AnonymousInComment.empty()) |
199 | return false; |
200 | if (NamespaceNameInComment == NamespaceName) |
201 | return true; |
202 | |
203 | // Has namespace comment flowed onto the next line. |
204 | // } // namespace |
205 | // // verylongnamespacenamethatdidnotfitonthepreviouscommentline |
206 | if (!(Comment->Next && Comment->Next->is(TT: TT_LineComment))) |
207 | return false; |
208 | |
209 | static const llvm::Regex = llvm::Regex( |
210 | "^/[/*] *( +([a-zA-Z0-9:_]+))?\\.? *(\\*/)?$" , llvm::Regex::IgnoreCase); |
211 | |
212 | // Pull out just the comment text. |
213 | if (!CommentPattern.match(String: Comment->Next->TokenText, Matches: &Groups)) |
214 | return false; |
215 | NamespaceNameInComment = Groups.size() > 2 ? Groups[2] : "" ; |
216 | |
217 | return NamespaceNameInComment == NamespaceName; |
218 | } |
219 | |
220 | void (const FormatToken *RBraceTok, StringRef , |
221 | const SourceManager &SourceMgr, |
222 | tooling::Replacements *Fixes) { |
223 | auto EndLoc = RBraceTok->Tok.getEndLoc(); |
224 | auto Range = CharSourceRange::getCharRange(B: EndLoc, E: EndLoc); |
225 | auto Err = Fixes->add(R: tooling::Replacement(SourceMgr, Range, EndCommentText)); |
226 | if (Err) { |
227 | llvm::errs() << "Error while adding namespace end comment: " |
228 | << llvm::toString(E: std::move(Err)) << "\n" ; |
229 | } |
230 | } |
231 | |
232 | void (const FormatToken *RBraceTok, StringRef , |
233 | const SourceManager &SourceMgr, |
234 | tooling::Replacements *Fixes) { |
235 | assert(hasEndComment(RBraceTok)); |
236 | const FormatToken * = RBraceTok->Next; |
237 | auto Range = CharSourceRange::getCharRange(B: Comment->getStartOfNonWhitespace(), |
238 | E: Comment->Tok.getEndLoc()); |
239 | auto Err = Fixes->add(R: tooling::Replacement(SourceMgr, Range, EndCommentText)); |
240 | if (Err) { |
241 | llvm::errs() << "Error while updating namespace end comment: " |
242 | << llvm::toString(E: std::move(Err)) << "\n" ; |
243 | } |
244 | } |
245 | } // namespace |
246 | |
247 | const FormatToken * |
248 | getNamespaceToken(const AnnotatedLine *Line, |
249 | const SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) { |
250 | if (!Line->Affected || Line->InPPDirective || !Line->startsWith(Tokens: tok::r_brace)) |
251 | return nullptr; |
252 | size_t StartLineIndex = Line->MatchingOpeningBlockLineIndex; |
253 | if (StartLineIndex == UnwrappedLine::kInvalidIndex) |
254 | return nullptr; |
255 | assert(StartLineIndex < AnnotatedLines.size()); |
256 | const FormatToken *NamespaceTok = AnnotatedLines[StartLineIndex]->First; |
257 | if (NamespaceTok->is(Kind: tok::l_brace)) { |
258 | // "namespace" keyword can be on the line preceding '{', e.g. in styles |
259 | // where BraceWrapping.AfterNamespace is true. |
260 | if (StartLineIndex > 0) { |
261 | NamespaceTok = AnnotatedLines[StartLineIndex - 1]->First; |
262 | if (AnnotatedLines[StartLineIndex - 1]->endsWith(Tokens: tok::semi)) |
263 | return nullptr; |
264 | } |
265 | } |
266 | |
267 | return NamespaceTok->getNamespaceToken(); |
268 | } |
269 | |
270 | StringRef |
271 | getNamespaceTokenText(const AnnotatedLine *Line, |
272 | const SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) { |
273 | const FormatToken *NamespaceTok = getNamespaceToken(Line, AnnotatedLines); |
274 | return NamespaceTok ? NamespaceTok->TokenText : StringRef(); |
275 | } |
276 | |
277 | NamespaceEndCommentsFixer::(const Environment &Env, |
278 | const FormatStyle &Style) |
279 | : TokenAnalyzer(Env, Style) {} |
280 | |
281 | std::pair<tooling::Replacements, unsigned> NamespaceEndCommentsFixer::( |
282 | TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines, |
283 | FormatTokenLexer &Tokens) { |
284 | const SourceManager &SourceMgr = Env.getSourceManager(); |
285 | AffectedRangeMgr.computeAffectedLines(Lines&: AnnotatedLines); |
286 | tooling::Replacements Fixes; |
287 | |
288 | // Spin through the lines and ensure we have balanced braces. |
289 | int Braces = 0; |
290 | for (AnnotatedLine *Line : AnnotatedLines) { |
291 | FormatToken *Tok = Line->First; |
292 | while (Tok) { |
293 | Braces += Tok->is(Kind: tok::l_brace) ? 1 : Tok->is(Kind: tok::r_brace) ? -1 : 0; |
294 | Tok = Tok->Next; |
295 | } |
296 | } |
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 | if (Braces != 0) |
301 | return {Fixes, 0}; |
302 | |
303 | std::string AllNamespaceNames; |
304 | size_t StartLineIndex = SIZE_MAX; |
305 | StringRef NamespaceTokenText; |
306 | unsigned int CompactedNamespacesCount = 0; |
307 | for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I) { |
308 | const AnnotatedLine *EndLine = AnnotatedLines[I]; |
309 | const FormatToken *NamespaceTok = |
310 | getNamespaceToken(Line: EndLine, AnnotatedLines); |
311 | if (!NamespaceTok) |
312 | continue; |
313 | FormatToken *RBraceTok = EndLine->First; |
314 | if (RBraceTok->Finalized) |
315 | continue; |
316 | RBraceTok->Finalized = true; |
317 | const FormatToken * = RBraceTok; |
318 | // Namespaces often end with '};'. In that case, attach namespace end |
319 | // comments to the semicolon tokens. |
320 | if (RBraceTok->Next && RBraceTok->Next->is(Kind: tok::semi)) |
321 | EndCommentPrevTok = RBraceTok->Next; |
322 | if (StartLineIndex == SIZE_MAX) |
323 | StartLineIndex = EndLine->MatchingOpeningBlockLineIndex; |
324 | std::string NamespaceName = computeName(NamespaceTok); |
325 | if (Style.CompactNamespaces) { |
326 | if (CompactedNamespacesCount == 0) |
327 | NamespaceTokenText = NamespaceTok->TokenText; |
328 | if ((I + 1 < E) && |
329 | NamespaceTokenText == |
330 | getNamespaceTokenText(Line: AnnotatedLines[I + 1], AnnotatedLines) && |
331 | StartLineIndex - CompactedNamespacesCount - 1 == |
332 | AnnotatedLines[I + 1]->MatchingOpeningBlockLineIndex && |
333 | !AnnotatedLines[I + 1]->First->Finalized) { |
334 | if (hasEndComment(RBraceTok: EndCommentPrevTok)) { |
335 | // remove end comment, it will be merged in next one |
336 | updateEndComment(RBraceTok: EndCommentPrevTok, EndCommentText: std::string(), SourceMgr, Fixes: &Fixes); |
337 | } |
338 | ++CompactedNamespacesCount; |
339 | if (!NamespaceName.empty()) |
340 | AllNamespaceNames = "::" + NamespaceName + AllNamespaceNames; |
341 | continue; |
342 | } |
343 | NamespaceName += AllNamespaceNames; |
344 | CompactedNamespacesCount = 0; |
345 | AllNamespaceNames = std::string(); |
346 | } |
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 | const FormatToken * = EndCommentPrevTok->Next; |
351 | if (EndCommentNextTok && EndCommentNextTok->is(Kind: tok::comment)) |
352 | EndCommentNextTok = EndCommentNextTok->Next; |
353 | if (!EndCommentNextTok && I + 1 < E) |
354 | EndCommentNextTok = AnnotatedLines[I + 1]->First; |
355 | bool AddNewline = EndCommentNextTok && |
356 | EndCommentNextTok->NewlinesBefore == 0 && |
357 | EndCommentNextTok->isNot(Kind: tok::eof); |
358 | const std::string = |
359 | computeEndCommentText(NamespaceName, AddNewline, NamespaceTok, |
360 | SpacesToAdd: Style.SpacesInLineCommentPrefix.Minimum); |
361 | if (!hasEndComment(RBraceTok: EndCommentPrevTok)) { |
362 | unsigned LineCount = 0; |
363 | for (auto J = StartLineIndex + 1; J < I; ++J) |
364 | LineCount += AnnotatedLines[J]->size(); |
365 | if (LineCount > Style.ShortNamespaceLines) { |
366 | addEndComment(RBraceTok: EndCommentPrevTok, |
367 | EndCommentText: std::string(Style.SpacesBeforeTrailingComments, ' ') + |
368 | EndCommentText, |
369 | SourceMgr, Fixes: &Fixes); |
370 | } |
371 | } else if (!validEndComment(RBraceTok: EndCommentPrevTok, NamespaceName, |
372 | NamespaceTok)) { |
373 | updateEndComment(RBraceTok: EndCommentPrevTok, EndCommentText, SourceMgr, Fixes: &Fixes); |
374 | } |
375 | StartLineIndex = SIZE_MAX; |
376 | } |
377 | return {Fixes, 0}; |
378 | } |
379 | |
380 | } // namespace format |
381 | } // namespace clang |
382 | |