clang-tools  10.0.0
NamespaceCommentCheck.cpp
Go to the documentation of this file.
1 //===--- NamespaceCommentCheck.cpp - clang-tidy ---------------------------===//
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 
10 #include "../utils/LexerUtils.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchers.h"
13 #include "clang/Basic/SourceLocation.h"
14 #include "clang/Basic/TokenKinds.h"
15 #include "clang/Lex/Lexer.h"
16 #include "llvm/ADT/StringExtras.h"
17 
18 using namespace clang::ast_matchers;
19 
20 namespace clang {
21 namespace tidy {
22 namespace readability {
23 
24 NamespaceCommentCheck::NamespaceCommentCheck(StringRef Name,
25  ClangTidyContext *Context)
26  : ClangTidyCheck(Name, Context),
27  NamespaceCommentPattern("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *"
28  "namespace( +([a-zA-Z0-9_:]+))?\\.? *(\\*/)?$",
29  llvm::Regex::IgnoreCase),
30  ShortNamespaceLines(Options.get("ShortNamespaceLines", 1u)),
31  SpacesBeforeComments(Options.get("SpacesBeforeComments", 1u)) {}
32 
33 void NamespaceCommentCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
34  Options.store(Opts, "ShortNamespaceLines", ShortNamespaceLines);
35  Options.store(Opts, "SpacesBeforeComments", SpacesBeforeComments);
36 }
37 
38 void NamespaceCommentCheck::registerMatchers(MatchFinder *Finder) {
39  // Only register the matchers for C++; the functionality currently does not
40  // provide any benefit to other languages, despite being benign.
41  if (getLangOpts().CPlusPlus)
42  Finder->addMatcher(namespaceDecl().bind("namespace"), this);
43 }
44 
45 static bool locationsInSameFile(const SourceManager &Sources,
46  SourceLocation Loc1, SourceLocation Loc2) {
47  return Loc1.isFileID() && Loc2.isFileID() &&
48  Sources.getFileID(Loc1) == Sources.getFileID(Loc2);
49 }
50 
51 static llvm::Optional<std::string>
52 getNamespaceNameAsWritten(SourceLocation &Loc, const SourceManager &Sources,
53  const LangOptions &LangOpts) {
54  // Loc should be at the begin of the namespace decl (usually, `namespace`
55  // token). We skip the first token right away, but in case of `inline
56  // namespace` or `namespace a::inline b` we can see both `inline` and
57  // `namespace` keywords, which we just ignore. Nested parens/squares before
58  // the opening brace can result from attributes.
59  std::string Result;
60  int Nesting = 0;
61  while (llvm::Optional<Token> T = utils::lexer::findNextTokenSkippingComments(
62  Loc, Sources, LangOpts)) {
63  Loc = T->getLocation();
64  if (T->is(tok::l_brace))
65  break;
66 
67  if (T->isOneOf(tok::l_square, tok::l_paren)) {
68  ++Nesting;
69  } else if (T->isOneOf(tok::r_square, tok::r_paren)) {
70  --Nesting;
71  } else if (Nesting == 0) {
72  if (T->is(tok::raw_identifier)) {
73  StringRef ID = T->getRawIdentifier();
74  if (ID != "namespace" && ID != "inline")
75  Result.append(ID);
76  } else if (T->is(tok::coloncolon)) {
77  Result.append("::");
78  } else { // Any other kind of token is unexpected here.
79  return llvm::None;
80  }
81  }
82  }
83  return Result;
84 }
85 
86 void NamespaceCommentCheck::check(const MatchFinder::MatchResult &Result) {
87  const auto *ND = Result.Nodes.getNodeAs<NamespaceDecl>("namespace");
88  const SourceManager &Sources = *Result.SourceManager;
89 
90  // Ignore namespaces inside macros and namespaces split across files.
91  if (ND->getBeginLoc().isMacroID() ||
92  !locationsInSameFile(Sources, ND->getBeginLoc(), ND->getRBraceLoc()))
93  return;
94 
95  // Don't require closing comments for namespaces spanning less than certain
96  // number of lines.
97  unsigned StartLine = Sources.getSpellingLineNumber(ND->getBeginLoc());
98  unsigned EndLine = Sources.getSpellingLineNumber(ND->getRBraceLoc());
99  if (EndLine - StartLine + 1 <= ShortNamespaceLines)
100  return;
101 
102  // Find next token after the namespace closing brace.
103  SourceLocation AfterRBrace = Lexer::getLocForEndOfToken(
104  ND->getRBraceLoc(), /*Offset=*/0, Sources, getLangOpts());
105  SourceLocation Loc = AfterRBrace;
106  SourceLocation LBraceLoc = ND->getBeginLoc();
107 
108  // Currently for nested namepsace (n1::n2::...) the AST matcher will match foo
109  // then bar instead of a single match. So if we got a nested namespace we have
110  // to skip the next ones.
111  for (const auto &EndOfNameLocation : Ends) {
112  if (Sources.isBeforeInTranslationUnit(ND->getLocation(), EndOfNameLocation))
113  return;
114  }
115 
116  llvm::Optional<std::string> NamespaceNameAsWritten =
117  getNamespaceNameAsWritten(LBraceLoc, Sources, getLangOpts());
118  if (!NamespaceNameAsWritten)
119  return;
120 
121  if (NamespaceNameAsWritten->empty() != ND->isAnonymousNamespace()) {
122  // Apparently, we didn't find the correct namespace name. Give up.
123  return;
124  }
125 
126  Ends.push_back(LBraceLoc);
127 
128  Token Tok;
129  // Skip whitespace until we find the next token.
130  while (Lexer::getRawToken(Loc, Tok, Sources, getLangOpts()) ||
131  Tok.is(tok::semi)) {
132  Loc = Loc.getLocWithOffset(1);
133  }
134 
135  if (!locationsInSameFile(Sources, ND->getRBraceLoc(), Loc))
136  return;
137 
138  bool NextTokenIsOnSameLine = Sources.getSpellingLineNumber(Loc) == EndLine;
139  // If we insert a line comment before the token in the same line, we need
140  // to insert a line break.
141  bool NeedLineBreak = NextTokenIsOnSameLine && Tok.isNot(tok::eof);
142 
143  SourceRange OldCommentRange(AfterRBrace, AfterRBrace);
144  std::string Message = "%0 not terminated with a closing comment";
145 
146  // Try to find existing namespace closing comment on the same line.
147  if (Tok.is(tok::comment) && NextTokenIsOnSameLine) {
148  StringRef Comment(Sources.getCharacterData(Loc), Tok.getLength());
149  SmallVector<StringRef, 7> Groups;
150  if (NamespaceCommentPattern.match(Comment, &Groups)) {
151  StringRef NamespaceNameInComment = Groups.size() > 5 ? Groups[5] : "";
152  StringRef Anonymous = Groups.size() > 3 ? Groups[3] : "";
153 
154  if ((ND->isAnonymousNamespace() && NamespaceNameInComment.empty()) ||
155  (*NamespaceNameAsWritten == NamespaceNameInComment &&
156  Anonymous.empty())) {
157  // Check if the namespace in the comment is the same.
158  // FIXME: Maybe we need a strict mode, where we always fix namespace
159  // comments with different format.
160  return;
161  }
162 
163  // Otherwise we need to fix the comment.
164  NeedLineBreak = Comment.startswith("/*");
165  OldCommentRange =
166  SourceRange(AfterRBrace, Loc.getLocWithOffset(Tok.getLength()));
167  Message =
168  (llvm::Twine(
169  "%0 ends with a comment that refers to a wrong namespace '") +
170  NamespaceNameInComment + "'")
171  .str();
172  } else if (Comment.startswith("//")) {
173  // Assume that this is an unrecognized form of a namespace closing line
174  // comment. Replace it.
175  NeedLineBreak = false;
176  OldCommentRange =
177  SourceRange(AfterRBrace, Loc.getLocWithOffset(Tok.getLength()));
178  Message = "%0 ends with an unrecognized comment";
179  }
180  // If it's a block comment, just move it to the next line, as it can be
181  // multi-line or there may be other tokens behind it.
182  }
183 
184  std::string NamespaceNameForDiag =
185  ND->isAnonymousNamespace() ? "anonymous namespace"
186  : ("namespace '" + *NamespaceNameAsWritten + "'");
187 
188  std::string Fix(SpacesBeforeComments, ' ');
189  Fix.append("// namespace");
190  if (!ND->isAnonymousNamespace())
191  Fix.append(" ").append(*NamespaceNameAsWritten);
192  if (NeedLineBreak)
193  Fix.append("\n");
194 
195  // Place diagnostic at an old comment, or closing brace if we did not have it.
196  SourceLocation DiagLoc =
197  OldCommentRange.getBegin() != OldCommentRange.getEnd()
198  ? OldCommentRange.getBegin()
199  : ND->getRBraceLoc();
200 
201  diag(DiagLoc, Message) << NamespaceNameForDiag
202  << FixItHint::CreateReplacement(
203  CharSourceRange::getCharRange(OldCommentRange),
204  Fix);
205  diag(ND->getLocation(), "%0 starts here", DiagnosticIDs::Note)
206  << NamespaceNameForDiag;
207 }
208 
209 } // namespace readability
210 } // namespace tidy
211 } // namespace clang
SourceLocation Loc
&#39;#&#39; location in the include directive
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Some operations such as code completion produce a set of candidates.
constexpr llvm::StringLiteral Message
Base class for all clang-tidy checks.
const LangOptions & getLangOpts() const
Returns the language options from the context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
static bool locationsInSameFile(const SourceManager &Sources, SourceLocation Loc1, SourceLocation Loc2)
static constexpr llvm::StringLiteral Name
std::map< std::string, std::string > OptionMap
Optional< Token > findNextTokenSkippingComments(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
Definition: LexerUtils.cpp:78
static llvm::Optional< std::string > getNamespaceNameAsWritten(SourceLocation &Loc, const SourceManager &Sources, const LangOptions &LangOpts)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
static cl::opt< bool > Fix("fix", cl::desc(R"( Apply suggested fixes. Without -fix-errors clang-tidy will bail out if any compilation errors were found. )"), cl::init(false), cl::cat(ClangTidyCategory))
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.