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 // Computes the name of a namespace given the namespace token.
26 // Returns "" for anonymous namespace.
computeName(const FormatToken * NamespaceTok)27 std::string computeName(const FormatToken *NamespaceTok) {
28 assert(NamespaceTok &&
29 NamespaceTok->isOneOf(tok::kw_namespace, TT_NamespaceMacro) &&
30 "expecting a namespace token");
31 std::string name = "";
32 const FormatToken *Tok = NamespaceTok->getNextNonComment();
33 if (NamespaceTok->is(TT_NamespaceMacro)) {
34 // Collects all the non-comment tokens between opening parenthesis
35 // and closing parenthesis or comma.
36 assert(Tok && Tok->is(tok::l_paren) && "expected an opening parenthesis");
37 Tok = Tok->getNextNonComment();
38 while (Tok && !Tok->isOneOf(tok::r_paren, tok::comma)) {
39 name += Tok->TokenText;
40 Tok = Tok->getNextNonComment();
41 }
42 } else {
43 // For `namespace [[foo]] A::B::inline C {` or
44 // `namespace MACRO1 MACRO2 A::B::inline C {`, returns "A::B::inline C".
45 // Peek for the first '::' (or '{') and then return all tokens from one
46 // token before that up until the '{'.
47 const FormatToken *FirstNSTok = Tok;
48 while (Tok && !Tok->is(tok::l_brace) && !Tok->is(tok::coloncolon)) {
49 FirstNSTok = Tok;
50 Tok = Tok->getNextNonComment();
51 }
52
53 Tok = FirstNSTok;
54 while (Tok && !Tok->is(tok::l_brace)) {
55 name += Tok->TokenText;
56 if (Tok->is(tok::kw_inline))
57 name += " ";
58 Tok = Tok->getNextNonComment();
59 }
60 }
61 return name;
62 }
63
computeEndCommentText(StringRef NamespaceName,bool AddNewline,const FormatToken * NamespaceTok,unsigned SpacesToAdd)64 std::string computeEndCommentText(StringRef NamespaceName, bool AddNewline,
65 const FormatToken *NamespaceTok,
66 unsigned SpacesToAdd) {
67 std::string text = "//";
68 text.append(SpacesToAdd, ' ');
69 text += NamespaceTok->TokenText;
70 if (NamespaceTok->is(TT_NamespaceMacro))
71 text += "(";
72 else if (!NamespaceName.empty())
73 text += ' ';
74 text += NamespaceName;
75 if (NamespaceTok->is(TT_NamespaceMacro))
76 text += ")";
77 if (AddNewline)
78 text += '\n';
79 return text;
80 }
81
hasEndComment(const FormatToken * RBraceTok)82 bool hasEndComment(const FormatToken *RBraceTok) {
83 return RBraceTok->Next && RBraceTok->Next->is(tok::comment);
84 }
85
validEndComment(const FormatToken * RBraceTok,StringRef NamespaceName,const FormatToken * NamespaceTok)86 bool validEndComment(const FormatToken *RBraceTok, StringRef NamespaceName,
87 const FormatToken *NamespaceTok) {
88 assert(hasEndComment(RBraceTok));
89 const FormatToken *Comment = RBraceTok->Next;
90
91 // Matches a valid namespace end comment.
92 // Valid namespace end comments don't need to be edited.
93 static const llvm::Regex NamespaceCommentPattern =
94 llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *"
95 "namespace( +([a-zA-Z0-9:_]+))?\\.? *(\\*/)?$",
96 llvm::Regex::IgnoreCase);
97 static const llvm::Regex NamespaceMacroCommentPattern =
98 llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *"
99 "([a-zA-Z0-9_]+)\\(([a-zA-Z0-9:_]*)\\)\\.? *(\\*/)?$",
100 llvm::Regex::IgnoreCase);
101
102 SmallVector<StringRef, 8> Groups;
103 if (NamespaceTok->is(TT_NamespaceMacro) &&
104 NamespaceMacroCommentPattern.match(Comment->TokenText, &Groups)) {
105 StringRef NamespaceTokenText = Groups.size() > 4 ? Groups[4] : "";
106 // The name of the macro must be used.
107 if (NamespaceTokenText != NamespaceTok->TokenText)
108 return false;
109 } else if (NamespaceTok->isNot(tok::kw_namespace) ||
110 !NamespaceCommentPattern.match(Comment->TokenText, &Groups)) {
111 // Comment does not match regex.
112 return false;
113 }
114 StringRef NamespaceNameInComment = Groups.size() > 5 ? Groups[5] : "";
115 // Anonymous namespace comments must not mention a namespace name.
116 if (NamespaceName.empty() && !NamespaceNameInComment.empty())
117 return false;
118 StringRef AnonymousInComment = Groups.size() > 3 ? Groups[3] : "";
119 // Named namespace comments must not mention anonymous namespace.
120 if (!NamespaceName.empty() && !AnonymousInComment.empty())
121 return false;
122 if (NamespaceNameInComment == NamespaceName)
123 return true;
124
125 // Has namespace comment flowed onto the next line.
126 // } // namespace
127 // // verylongnamespacenamethatdidnotfitonthepreviouscommentline
128 if (!(Comment->Next && Comment->Next->is(TT_LineComment)))
129 return false;
130
131 static const llvm::Regex CommentPattern = llvm::Regex(
132 "^/[/*] *( +([a-zA-Z0-9:_]+))?\\.? *(\\*/)?$", llvm::Regex::IgnoreCase);
133
134 // Pull out just the comment text.
135 if (!CommentPattern.match(Comment->Next->TokenText, &Groups)) {
136 return false;
137 }
138 NamespaceNameInComment = Groups.size() > 2 ? Groups[2] : "";
139
140 return (NamespaceNameInComment == NamespaceName);
141 }
142
addEndComment(const FormatToken * RBraceTok,StringRef EndCommentText,const SourceManager & SourceMgr,tooling::Replacements * Fixes)143 void addEndComment(const FormatToken *RBraceTok, StringRef EndCommentText,
144 const SourceManager &SourceMgr,
145 tooling::Replacements *Fixes) {
146 auto EndLoc = RBraceTok->Tok.getEndLoc();
147 auto Range = CharSourceRange::getCharRange(EndLoc, EndLoc);
148 auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText));
149 if (Err) {
150 llvm::errs() << "Error while adding namespace end comment: "
151 << llvm::toString(std::move(Err)) << "\n";
152 }
153 }
154
updateEndComment(const FormatToken * RBraceTok,StringRef EndCommentText,const SourceManager & SourceMgr,tooling::Replacements * Fixes)155 void updateEndComment(const FormatToken *RBraceTok, StringRef EndCommentText,
156 const SourceManager &SourceMgr,
157 tooling::Replacements *Fixes) {
158 assert(hasEndComment(RBraceTok));
159 const FormatToken *Comment = RBraceTok->Next;
160 auto Range = CharSourceRange::getCharRange(Comment->getStartOfNonWhitespace(),
161 Comment->Tok.getEndLoc());
162 auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText));
163 if (Err) {
164 llvm::errs() << "Error while updating namespace end comment: "
165 << llvm::toString(std::move(Err)) << "\n";
166 }
167 }
168 } // namespace
169
170 const FormatToken *
getNamespaceToken(const AnnotatedLine * Line,const SmallVectorImpl<AnnotatedLine * > & AnnotatedLines)171 getNamespaceToken(const AnnotatedLine *Line,
172 const SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) {
173 if (!Line->Affected || Line->InPPDirective || !Line->startsWith(tok::r_brace))
174 return nullptr;
175 size_t StartLineIndex = Line->MatchingOpeningBlockLineIndex;
176 if (StartLineIndex == UnwrappedLine::kInvalidIndex)
177 return nullptr;
178 assert(StartLineIndex < AnnotatedLines.size());
179 const FormatToken *NamespaceTok = AnnotatedLines[StartLineIndex]->First;
180 if (NamespaceTok->is(tok::l_brace)) {
181 // "namespace" keyword can be on the line preceding '{', e.g. in styles
182 // where BraceWrapping.AfterNamespace is true.
183 if (StartLineIndex > 0)
184 NamespaceTok = AnnotatedLines[StartLineIndex - 1]->First;
185 }
186 return NamespaceTok->getNamespaceToken();
187 }
188
189 StringRef
getNamespaceTokenText(const AnnotatedLine * Line,const SmallVectorImpl<AnnotatedLine * > & AnnotatedLines)190 getNamespaceTokenText(const AnnotatedLine *Line,
191 const SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) {
192 const FormatToken *NamespaceTok = getNamespaceToken(Line, AnnotatedLines);
193 return NamespaceTok ? NamespaceTok->TokenText : StringRef();
194 }
195
NamespaceEndCommentsFixer(const Environment & Env,const FormatStyle & Style)196 NamespaceEndCommentsFixer::NamespaceEndCommentsFixer(const Environment &Env,
197 const FormatStyle &Style)
198 : TokenAnalyzer(Env, Style) {}
199
analyze(TokenAnnotator & Annotator,SmallVectorImpl<AnnotatedLine * > & AnnotatedLines,FormatTokenLexer & Tokens)200 std::pair<tooling::Replacements, unsigned> NamespaceEndCommentsFixer::analyze(
201 TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
202 FormatTokenLexer &Tokens) {
203 const SourceManager &SourceMgr = Env.getSourceManager();
204 AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
205 tooling::Replacements Fixes;
206
207 // Spin through the lines and ensure we have balanced braces.
208 int Braces = 0;
209 for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I) {
210 FormatToken *Tok = AnnotatedLines[I]->First;
211 while (Tok) {
212 Braces += Tok->is(tok::l_brace) ? 1 : Tok->is(tok::r_brace) ? -1 : 0;
213 Tok = Tok->Next;
214 }
215 }
216 // Don't attempt to comment unbalanced braces or this can
217 // lead to comments being placed on the closing brace which isn't
218 // the matching brace of the namespace. (occurs during incomplete editing).
219 if (Braces != 0) {
220 return {Fixes, 0};
221 }
222
223 std::string AllNamespaceNames = "";
224 size_t StartLineIndex = SIZE_MAX;
225 StringRef NamespaceTokenText;
226 unsigned int CompactedNamespacesCount = 0;
227 for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I) {
228 const AnnotatedLine *EndLine = AnnotatedLines[I];
229 const FormatToken *NamespaceTok =
230 getNamespaceToken(EndLine, AnnotatedLines);
231 if (!NamespaceTok)
232 continue;
233 FormatToken *RBraceTok = EndLine->First;
234 if (RBraceTok->Finalized)
235 continue;
236 RBraceTok->Finalized = true;
237 const FormatToken *EndCommentPrevTok = RBraceTok;
238 // Namespaces often end with '};'. In that case, attach namespace end
239 // comments to the semicolon tokens.
240 if (RBraceTok->Next && RBraceTok->Next->is(tok::semi)) {
241 EndCommentPrevTok = RBraceTok->Next;
242 }
243 if (StartLineIndex == SIZE_MAX)
244 StartLineIndex = EndLine->MatchingOpeningBlockLineIndex;
245 std::string NamespaceName = computeName(NamespaceTok);
246 if (Style.CompactNamespaces) {
247 if (CompactedNamespacesCount == 0)
248 NamespaceTokenText = NamespaceTok->TokenText;
249 if ((I + 1 < E) &&
250 NamespaceTokenText ==
251 getNamespaceTokenText(AnnotatedLines[I + 1], AnnotatedLines) &&
252 StartLineIndex - CompactedNamespacesCount - 1 ==
253 AnnotatedLines[I + 1]->MatchingOpeningBlockLineIndex &&
254 !AnnotatedLines[I + 1]->First->Finalized) {
255 if (hasEndComment(EndCommentPrevTok)) {
256 // remove end comment, it will be merged in next one
257 updateEndComment(EndCommentPrevTok, std::string(), SourceMgr, &Fixes);
258 }
259 CompactedNamespacesCount++;
260 AllNamespaceNames = "::" + NamespaceName + AllNamespaceNames;
261 continue;
262 }
263 NamespaceName += AllNamespaceNames;
264 CompactedNamespacesCount = 0;
265 AllNamespaceNames = std::string();
266 }
267 // The next token in the token stream after the place where the end comment
268 // token must be. This is either the next token on the current line or the
269 // first token on the next line.
270 const FormatToken *EndCommentNextTok = EndCommentPrevTok->Next;
271 if (EndCommentNextTok && EndCommentNextTok->is(tok::comment))
272 EndCommentNextTok = EndCommentNextTok->Next;
273 if (!EndCommentNextTok && I + 1 < E)
274 EndCommentNextTok = AnnotatedLines[I + 1]->First;
275 bool AddNewline = EndCommentNextTok &&
276 EndCommentNextTok->NewlinesBefore == 0 &&
277 EndCommentNextTok->isNot(tok::eof);
278 const std::string EndCommentText =
279 computeEndCommentText(NamespaceName, AddNewline, NamespaceTok,
280 Style.SpacesInLineCommentPrefix.Minimum);
281 if (!hasEndComment(EndCommentPrevTok)) {
282 bool isShort = I - StartLineIndex <= Style.ShortNamespaceLines + 1;
283 if (!isShort)
284 addEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes);
285 } else if (!validEndComment(EndCommentPrevTok, NamespaceName,
286 NamespaceTok)) {
287 updateEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes);
288 }
289 StartLineIndex = SIZE_MAX;
290 }
291 return {Fixes, 0};
292 }
293
294 } // namespace format
295 } // namespace clang
296