NamespaceEndCommentsFixer.cpp 11 KB

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