Clang Project

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