xref: /freebsd/contrib/llvm-project/clang/lib/Format/NamespaceEndCommentsFixer.cpp (revision f2530c80db7b29b95368fce956b3a778f096b368)
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   assert(NamespaceTok &&
33          NamespaceTok->isOneOf(tok::kw_namespace, TT_NamespaceMacro) &&
34          "expecting a namespace token");
35   std::string name = "";
36   const FormatToken *Tok = NamespaceTok->getNextNonComment();
37   if (NamespaceTok->is(TT_NamespaceMacro)) {
38     // Collects all the non-comment tokens between opening parenthesis
39     // and closing parenthesis or comma
40     assert(Tok && Tok->is(tok::l_paren) && "expected an opening parenthesis");
41     Tok = Tok->getNextNonComment();
42     while (Tok && !Tok->isOneOf(tok::r_paren, tok::comma)) {
43       name += Tok->TokenText;
44       Tok = Tok->getNextNonComment();
45     }
46   } else {
47     // Collects all the non-comment tokens between 'namespace' and '{'.
48     while (Tok && !Tok->is(tok::l_brace)) {
49       name += Tok->TokenText;
50       Tok = Tok->getNextNonComment();
51     }
52   }
53   return name;
54 }
55 
56 std::string computeEndCommentText(StringRef NamespaceName, bool AddNewline,
57                                   const FormatToken *NamespaceTok) {
58   std::string text = "// ";
59   text += NamespaceTok->TokenText;
60   if (NamespaceTok->is(TT_NamespaceMacro))
61     text += "(";
62   else if (!NamespaceName.empty())
63     text += ' ';
64   text += NamespaceName;
65   if (NamespaceTok->is(TT_NamespaceMacro))
66     text += ")";
67   if (AddNewline)
68     text += '\n';
69   return text;
70 }
71 
72 bool hasEndComment(const FormatToken *RBraceTok) {
73   return RBraceTok->Next && RBraceTok->Next->is(tok::comment);
74 }
75 
76 bool validEndComment(const FormatToken *RBraceTok, StringRef NamespaceName,
77                      const FormatToken *NamespaceTok) {
78   assert(hasEndComment(RBraceTok));
79   const FormatToken *Comment = RBraceTok->Next;
80 
81   // Matches a valid namespace end comment.
82   // Valid namespace end comments don't need to be edited.
83   static llvm::Regex *const NamespaceCommentPattern =
84       new llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *"
85                       "namespace( +([a-zA-Z0-9:_]+))?\\.? *(\\*/)?$",
86                       llvm::Regex::IgnoreCase);
87   static llvm::Regex *const NamespaceMacroCommentPattern =
88       new llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *"
89                       "([a-zA-Z0-9_]+)\\(([a-zA-Z0-9:_]*)\\)\\.? *(\\*/)?$",
90                       llvm::Regex::IgnoreCase);
91 
92   SmallVector<StringRef, 8> Groups;
93   if (NamespaceTok->is(TT_NamespaceMacro) &&
94       NamespaceMacroCommentPattern->match(Comment->TokenText, &Groups)) {
95     StringRef NamespaceTokenText = Groups.size() > 4 ? Groups[4] : "";
96     // The name of the macro must be used.
97     if (NamespaceTokenText != NamespaceTok->TokenText)
98       return false;
99   } else if (NamespaceTok->isNot(tok::kw_namespace) ||
100              !NamespaceCommentPattern->match(Comment->TokenText, &Groups)) {
101     // Comment does not match regex.
102     return false;
103   }
104   StringRef NamespaceNameInComment = Groups.size() > 5 ? Groups[5] : "";
105   // Anonymous namespace comments must not mention a namespace name.
106   if (NamespaceName.empty() && !NamespaceNameInComment.empty())
107     return false;
108   StringRef AnonymousInComment = Groups.size() > 3 ? Groups[3] : "";
109   // Named namespace comments must not mention anonymous namespace.
110   if (!NamespaceName.empty() && !AnonymousInComment.empty())
111     return false;
112   return NamespaceNameInComment == NamespaceName;
113 }
114 
115 void addEndComment(const FormatToken *RBraceTok, StringRef EndCommentText,
116                    const SourceManager &SourceMgr,
117                    tooling::Replacements *Fixes) {
118   auto EndLoc = RBraceTok->Tok.getEndLoc();
119   auto Range = CharSourceRange::getCharRange(EndLoc, EndLoc);
120   auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText));
121   if (Err) {
122     llvm::errs() << "Error while adding namespace end comment: "
123                  << llvm::toString(std::move(Err)) << "\n";
124   }
125 }
126 
127 void updateEndComment(const FormatToken *RBraceTok, StringRef EndCommentText,
128                       const SourceManager &SourceMgr,
129                       tooling::Replacements *Fixes) {
130   assert(hasEndComment(RBraceTok));
131   const FormatToken *Comment = RBraceTok->Next;
132   auto Range = CharSourceRange::getCharRange(Comment->getStartOfNonWhitespace(),
133                                              Comment->Tok.getEndLoc());
134   auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText));
135   if (Err) {
136     llvm::errs() << "Error while updating namespace end comment: "
137                  << llvm::toString(std::move(Err)) << "\n";
138   }
139 }
140 } // namespace
141 
142 const FormatToken *
143 getNamespaceToken(const AnnotatedLine *Line,
144                   const SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) {
145   if (!Line->Affected || Line->InPPDirective || !Line->startsWith(tok::r_brace))
146     return nullptr;
147   size_t StartLineIndex = Line->MatchingOpeningBlockLineIndex;
148   if (StartLineIndex == UnwrappedLine::kInvalidIndex)
149     return nullptr;
150   assert(StartLineIndex < AnnotatedLines.size());
151   const FormatToken *NamespaceTok = AnnotatedLines[StartLineIndex]->First;
152   if (NamespaceTok->is(tok::l_brace)) {
153     // "namespace" keyword can be on the line preceding '{', e.g. in styles
154     // where BraceWrapping.AfterNamespace is true.
155     if (StartLineIndex > 0)
156       NamespaceTok = AnnotatedLines[StartLineIndex - 1]->First;
157   }
158   return NamespaceTok->getNamespaceToken();
159 }
160 
161 StringRef
162 getNamespaceTokenText(const AnnotatedLine *Line,
163                       const SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) {
164   const FormatToken *NamespaceTok = getNamespaceToken(Line, AnnotatedLines);
165   return NamespaceTok ? NamespaceTok->TokenText : StringRef();
166 }
167 
168 NamespaceEndCommentsFixer::NamespaceEndCommentsFixer(const Environment &Env,
169                                                      const FormatStyle &Style)
170     : TokenAnalyzer(Env, Style) {}
171 
172 std::pair<tooling::Replacements, unsigned> NamespaceEndCommentsFixer::analyze(
173     TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
174     FormatTokenLexer &Tokens) {
175   const SourceManager &SourceMgr = Env.getSourceManager();
176   AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
177   tooling::Replacements Fixes;
178   std::string AllNamespaceNames = "";
179   size_t StartLineIndex = SIZE_MAX;
180   StringRef NamespaceTokenText;
181   unsigned int CompactedNamespacesCount = 0;
182   for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I) {
183     const AnnotatedLine *EndLine = AnnotatedLines[I];
184     const FormatToken *NamespaceTok =
185         getNamespaceToken(EndLine, AnnotatedLines);
186     if (!NamespaceTok)
187       continue;
188     FormatToken *RBraceTok = EndLine->First;
189     if (RBraceTok->Finalized)
190       continue;
191     RBraceTok->Finalized = true;
192     const FormatToken *EndCommentPrevTok = RBraceTok;
193     // Namespaces often end with '};'. In that case, attach namespace end
194     // comments to the semicolon tokens.
195     if (RBraceTok->Next && RBraceTok->Next->is(tok::semi)) {
196       EndCommentPrevTok = RBraceTok->Next;
197     }
198     if (StartLineIndex == SIZE_MAX)
199       StartLineIndex = EndLine->MatchingOpeningBlockLineIndex;
200     std::string NamespaceName = computeName(NamespaceTok);
201     if (Style.CompactNamespaces) {
202       if (CompactedNamespacesCount == 0)
203         NamespaceTokenText = NamespaceTok->TokenText;
204       if ((I + 1 < E) &&
205           NamespaceTokenText ==
206               getNamespaceTokenText(AnnotatedLines[I + 1], AnnotatedLines) &&
207           StartLineIndex - CompactedNamespacesCount - 1 ==
208               AnnotatedLines[I + 1]->MatchingOpeningBlockLineIndex &&
209           !AnnotatedLines[I + 1]->First->Finalized) {
210         if (hasEndComment(EndCommentPrevTok)) {
211           // remove end comment, it will be merged in next one
212           updateEndComment(EndCommentPrevTok, std::string(), SourceMgr, &Fixes);
213         }
214         CompactedNamespacesCount++;
215         AllNamespaceNames = "::" + NamespaceName + AllNamespaceNames;
216         continue;
217       }
218       NamespaceName += AllNamespaceNames;
219       CompactedNamespacesCount = 0;
220       AllNamespaceNames = std::string();
221     }
222     // The next token in the token stream after the place where the end comment
223     // token must be. This is either the next token on the current line or the
224     // first token on the next line.
225     const FormatToken *EndCommentNextTok = EndCommentPrevTok->Next;
226     if (EndCommentNextTok && EndCommentNextTok->is(tok::comment))
227       EndCommentNextTok = EndCommentNextTok->Next;
228     if (!EndCommentNextTok && I + 1 < E)
229       EndCommentNextTok = AnnotatedLines[I + 1]->First;
230     bool AddNewline = EndCommentNextTok &&
231                       EndCommentNextTok->NewlinesBefore == 0 &&
232                       EndCommentNextTok->isNot(tok::eof);
233     const std::string EndCommentText =
234         computeEndCommentText(NamespaceName, AddNewline, NamespaceTok);
235     if (!hasEndComment(EndCommentPrevTok)) {
236       bool isShort = I - StartLineIndex <= kShortNamespaceMaxLines + 1;
237       if (!isShort)
238         addEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes);
239     } else if (!validEndComment(EndCommentPrevTok, NamespaceName,
240                                 NamespaceTok)) {
241       updateEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes);
242     }
243     StartLineIndex = SIZE_MAX;
244   }
245   return {Fixes, 0};
246 }
247 
248 } // namespace format
249 } // namespace clang
250