NamespaceEndCommentsFixer.cpp 12 KB

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