1 //===--- NamespaceEndCommentsFixer.cpp --------------------------*- C++ -*-===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 ///
10 /// \file
11 /// \brief This file implements NamespaceEndCommentsFixer, a TokenAnalyzer that
12 /// fixes namespace end comments.
13 ///
14 //===----------------------------------------------------------------------===//
15 
16 #include "NamespaceEndCommentsFixer.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 // The maximal number of lines that a short namespace spans.
27 // Short namespaces don't need an end comment.
28 static const int kShortNamespaceMaxLines = 1;
29 
30 // Matches a valid namespace end comment.
31 // Valid namespace end comments don't need to be edited.
32 static llvm::Regex kNamespaceCommentPattern =
33     llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *"
34                 "namespace( +([a-zA-Z0-9:_]+))?\\.? *(\\*/)?$",
35                 llvm::Regex::IgnoreCase);
36 
37 // Computes the name of a namespace given the namespace token.
38 // Returns "" for anonymous namespace.
39 std::string computeName(const FormatToken *NamespaceTok) {
40   assert(NamespaceTok && NamespaceTok->is(tok::kw_namespace) &&
41          "expecting a namespace token");
42   std::string name = "";
43   // Collects all the non-comment tokens between 'namespace' and '{'.
44   const FormatToken *Tok = NamespaceTok->getNextNonComment();
45   while (Tok && !Tok->is(tok::l_brace)) {
46     name += Tok->TokenText;
47     Tok = Tok->getNextNonComment();
48   }
49   return name;
50 }
51 
52 std::string computeEndCommentText(StringRef NamespaceName, bool AddNewline) {
53   std::string text = "// namespace";
54   if (!NamespaceName.empty()) {
55     text += ' ';
56     text += NamespaceName;
57   }
58   if (AddNewline)
59     text += '\n';
60   return text;
61 }
62 
63 bool isShort(const FormatToken *NamespaceTok, const FormatToken *RBraceTok,
64              const SourceManager &SourceMgr) {
65   int StartLine =
66       SourceMgr.getSpellingLineNumber(NamespaceTok->Tok.getLocation());
67   int EndLine = SourceMgr.getSpellingLineNumber(RBraceTok->Tok.getLocation());
68   return EndLine - StartLine + 1 <= kShortNamespaceMaxLines;
69 }
70 
71 bool hasEndComment(const FormatToken *RBraceTok) {
72   return RBraceTok->Next && RBraceTok->Next->is(tok::comment);
73 }
74 
75 bool validEndComment(const FormatToken *RBraceTok, StringRef NamespaceName) {
76   assert(hasEndComment(RBraceTok));
77   const FormatToken *Comment = RBraceTok->Next;
78   SmallVector<StringRef, 7> Groups;
79   if (kNamespaceCommentPattern.match(Comment->TokenText, &Groups)) {
80     StringRef NamespaceNameInComment = Groups.size() > 5 ? Groups[5] : "";
81     // Anonymous namespace comments must not mention a namespace name.
82     if (NamespaceName.empty() && !NamespaceNameInComment.empty())
83       return false;
84     StringRef AnonymousInComment = Groups.size() > 3 ? Groups[3] : "";
85     // Named namespace comments must not mention anonymous namespace.
86     if (!NamespaceName.empty() && !AnonymousInComment.empty())
87       return false;
88     return NamespaceNameInComment == NamespaceName;
89   }
90   return false;
91 }
92 
93 void addEndComment(const FormatToken *RBraceTok, StringRef EndCommentText,
94                    const SourceManager &SourceMgr,
95                    tooling::Replacements *Fixes) {
96   auto EndLoc = RBraceTok->Tok.getEndLoc();
97   auto Range = CharSourceRange::getCharRange(EndLoc, EndLoc);
98   auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText));
99   if (Err) {
100     llvm::errs() << "Error while adding namespace end comment: "
101                  << llvm::toString(std::move(Err)) << "\n";
102   }
103 }
104 
105 void updateEndComment(const FormatToken *RBraceTok, StringRef EndCommentText,
106                       const SourceManager &SourceMgr,
107                       tooling::Replacements *Fixes) {
108   assert(hasEndComment(RBraceTok));
109   const FormatToken *Comment = RBraceTok->Next;
110   auto Range = CharSourceRange::getCharRange(Comment->getStartOfNonWhitespace(),
111                                              Comment->Tok.getEndLoc());
112   auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText));
113   if (Err) {
114     llvm::errs() << "Error while updating namespace end comment: "
115                  << llvm::toString(std::move(Err)) << "\n";
116   }
117 }
118 } // namespace
119 
120 NamespaceEndCommentsFixer::NamespaceEndCommentsFixer(const Environment &Env,
121                                                      const FormatStyle &Style)
122     : TokenAnalyzer(Env, Style) {}
123 
124 tooling::Replacements NamespaceEndCommentsFixer::analyze(
125     TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
126     FormatTokenLexer &Tokens) {
127   const SourceManager &SourceMgr = Env.getSourceManager();
128   AffectedRangeMgr.computeAffectedLines(AnnotatedLines.begin(),
129                                         AnnotatedLines.end());
130   tooling::Replacements Fixes;
131   for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I) {
132     if (!AnnotatedLines[I]->Affected || AnnotatedLines[I]->InPPDirective ||
133         !AnnotatedLines[I]->startsWith(tok::r_brace))
134       continue;
135     const AnnotatedLine *EndLine = AnnotatedLines[I];
136     size_t StartLineIndex = EndLine->MatchingOpeningBlockLineIndex;
137     if (StartLineIndex == UnwrappedLine::kInvalidIndex)
138       continue;
139     assert(StartLineIndex < E);
140     const FormatToken *NamespaceTok = AnnotatedLines[StartLineIndex]->First;
141     // Detect "(inline)? namespace" in the beginning of a line.
142     if (NamespaceTok->is(tok::kw_inline))
143       NamespaceTok = NamespaceTok->getNextNonComment();
144     if (NamespaceTok->isNot(tok::kw_namespace))
145       continue;
146     const FormatToken *RBraceTok = EndLine->First;
147     const std::string NamespaceName = computeName(NamespaceTok);
148     bool AddNewline = (I + 1 < E) &&
149                       AnnotatedLines[I + 1]->First->NewlinesBefore == 0 &&
150                       AnnotatedLines[I + 1]->First->isNot(tok::eof);
151     const std::string EndCommentText =
152         computeEndCommentText(NamespaceName, AddNewline);
153     if (!hasEndComment(RBraceTok)) {
154       if (!isShort(NamespaceTok, RBraceTok, SourceMgr))
155         addEndComment(RBraceTok, EndCommentText, SourceMgr, &Fixes);
156       continue;
157     }
158     if (!validEndComment(RBraceTok, NamespaceName))
159       updateEndComment(RBraceTok, EndCommentText, SourceMgr, &Fixes);
160   }
161   return Fixes;
162 }
163 
164 } // namespace format
165 } // namespace clang
166