clang-tools  4.0.0
ArgumentCommentCheck.cpp
Go to the documentation of this file.
1 //===--- ArgumentCommentCheck.cpp - clang-tidy ----------------------------===//
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 #include "ArgumentCommentCheck.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Lex/Lexer.h"
14 #include "clang/Lex/Token.h"
15 
16 using namespace clang::ast_matchers;
17 
18 namespace clang {
19 namespace tidy {
20 namespace misc {
21 
22 ArgumentCommentCheck::ArgumentCommentCheck(StringRef Name,
24  : ClangTidyCheck(Name, Context),
25  StrictMode(Options.getLocalOrGlobal("StrictMode", 0) != 0),
26  IdentRE("^(/\\* *)([_A-Za-z][_A-Za-z0-9]*)( *= *\\*/)$") {}
27 
29  Options.store(Opts, "StrictMode", StrictMode);
30 }
31 
33  Finder->addMatcher(
34  callExpr(unless(cxxOperatorCallExpr()),
35  // NewCallback's arguments relate to the pointed function, don't
36  // check them against NewCallback's parameter names.
37  // FIXME: Make this configurable.
38  unless(hasDeclaration(functionDecl(
39  hasAnyName("NewCallback", "NewPermanentCallback")))))
40  .bind("expr"),
41  this);
42  Finder->addMatcher(cxxConstructExpr().bind("expr"), this);
43 }
44 
45 static std::vector<std::pair<SourceLocation, StringRef>>
46 getCommentsInRange(ASTContext *Ctx, CharSourceRange Range) {
47  std::vector<std::pair<SourceLocation, StringRef>> Comments;
48  auto &SM = Ctx->getSourceManager();
49  std::pair<FileID, unsigned> BeginLoc = SM.getDecomposedLoc(Range.getBegin()),
50  EndLoc = SM.getDecomposedLoc(Range.getEnd());
51 
52  if (BeginLoc.first != EndLoc.first)
53  return Comments;
54 
55  bool Invalid = false;
56  StringRef Buffer = SM.getBufferData(BeginLoc.first, &Invalid);
57  if (Invalid)
58  return Comments;
59 
60  const char *StrData = Buffer.data() + BeginLoc.second;
61 
62  Lexer TheLexer(SM.getLocForStartOfFile(BeginLoc.first), Ctx->getLangOpts(),
63  Buffer.begin(), StrData, Buffer.end());
64  TheLexer.SetCommentRetentionState(true);
65 
66  while (true) {
67  Token Tok;
68  if (TheLexer.LexFromRawLexer(Tok))
69  break;
70  if (Tok.getLocation() == Range.getEnd() || Tok.getKind() == tok::eof)
71  break;
72 
73  if (Tok.getKind() == tok::comment) {
74  std::pair<FileID, unsigned> CommentLoc =
75  SM.getDecomposedLoc(Tok.getLocation());
76  assert(CommentLoc.first == BeginLoc.first);
77  Comments.emplace_back(
78  Tok.getLocation(),
79  StringRef(Buffer.begin() + CommentLoc.second, Tok.getLength()));
80  }
81  }
82 
83  return Comments;
84 }
85 
86 bool ArgumentCommentCheck::isLikelyTypo(llvm::ArrayRef<ParmVarDecl *> Params,
87  StringRef ArgName, unsigned ArgIndex) {
88  std::string ArgNameLowerStr = ArgName.lower();
89  StringRef ArgNameLower = ArgNameLowerStr;
90  // The threshold is arbitrary.
91  unsigned UpperBound = (ArgName.size() + 2) / 3 + 1;
92  unsigned ThisED = ArgNameLower.edit_distance(
93  Params[ArgIndex]->getIdentifier()->getName().lower(),
94  /*AllowReplacements=*/true, UpperBound);
95  if (ThisED >= UpperBound)
96  return false;
97 
98  for (unsigned I = 0, E = Params.size(); I != E; ++I) {
99  if (I == ArgIndex)
100  continue;
101  IdentifierInfo *II = Params[I]->getIdentifier();
102  if (!II)
103  continue;
104 
105  const unsigned Threshold = 2;
106  // Other parameters must be an edit distance at least Threshold more away
107  // from this parameter. This gives us greater confidence that this is a typo
108  // of this parameter and not one with a similar name.
109  unsigned OtherED = ArgNameLower.edit_distance(II->getName().lower(),
110  /*AllowReplacements=*/true,
111  ThisED + Threshold);
112  if (OtherED < ThisED + Threshold)
113  return false;
114  }
115 
116  return true;
117 }
118 
119 static bool sameName(StringRef InComment, StringRef InDecl, bool StrictMode) {
120  if (StrictMode)
121  return InComment == InDecl;
122  InComment = InComment.trim('_');
123  InDecl = InDecl.trim('_');
124  // FIXME: compare_lower only works for ASCII.
125  return InComment.compare_lower(InDecl) == 0;
126 }
127 
128 void ArgumentCommentCheck::checkCallArgs(ASTContext *Ctx,
129  const FunctionDecl *Callee,
130  SourceLocation ArgBeginLoc,
131  llvm::ArrayRef<const Expr *> Args) {
132  Callee = Callee->getFirstDecl();
133  for (unsigned I = 0,
134  E = std::min<unsigned>(Args.size(), Callee->getNumParams());
135  I != E; ++I) {
136  const ParmVarDecl *PVD = Callee->getParamDecl(I);
137  IdentifierInfo *II = PVD->getIdentifier();
138  if (!II)
139  continue;
140  if (auto Template = Callee->getTemplateInstantiationPattern()) {
141  // Don't warn on arguments for parameters instantiated from template
142  // parameter packs. If we find more arguments than the template definition
143  // has, it also means that they correspond to a parameter pack.
144  if (Template->getNumParams() <= I ||
145  Template->getParamDecl(I)->isParameterPack()) {
146  continue;
147  }
148  }
149 
150  CharSourceRange BeforeArgument = CharSourceRange::getCharRange(
151  I == 0 ? ArgBeginLoc : Args[I - 1]->getLocEnd(),
152  Args[I]->getLocStart());
153  BeforeArgument = Lexer::makeFileCharRange(
154  BeforeArgument, Ctx->getSourceManager(), Ctx->getLangOpts());
155 
156  for (auto Comment : getCommentsInRange(Ctx, BeforeArgument)) {
157  llvm::SmallVector<StringRef, 2> Matches;
158  if (IdentRE.match(Comment.second, &Matches)) {
159  if (!sameName(Matches[2], II->getName(), StrictMode)) {
160  {
161  DiagnosticBuilder Diag =
162  diag(Comment.first, "argument name '%0' in comment does not "
163  "match parameter name %1")
164  << Matches[2] << II;
165  if (isLikelyTypo(Callee->parameters(), Matches[2], I)) {
166  Diag << FixItHint::CreateReplacement(
167  Comment.first,
168  (Matches[1] + II->getName() + Matches[3]).str());
169  }
170  }
171  diag(PVD->getLocation(), "%0 declared here", DiagnosticIDs::Note)
172  << II;
173  }
174  }
175  }
176  }
177 }
178 
179 void ArgumentCommentCheck::check(const MatchFinder::MatchResult &Result) {
180  const auto *E = Result.Nodes.getNodeAs<Expr>("expr");
181  if (const auto *Call = dyn_cast<CallExpr>(E)) {
182  const FunctionDecl *Callee = Call->getDirectCallee();
183  if (!Callee)
184  return;
185 
186  checkCallArgs(Result.Context, Callee, Call->getCallee()->getLocEnd(),
187  llvm::makeArrayRef(Call->getArgs(), Call->getNumArgs()));
188  } else {
189  const auto *Construct = cast<CXXConstructExpr>(E);
190  checkCallArgs(
191  Result.Context, Construct->getConstructor(),
192  Construct->getParenOrBraceRange().getBegin(),
193  llvm::makeArrayRef(Construct->getArgs(), Construct->getNumArgs()));
194  }
195 }
196 
197 } // namespace misc
198 } // namespace tidy
199 } // namespace clang
const std::string Name
Definition: USRFinder.cpp:164
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
static bool sameName(StringRef InComment, StringRef InDecl, bool StrictMode)
std::unique_ptr< ast_matchers::MatchFinder > Finder
Definition: ClangTidy.cpp:262
static std::vector< std::pair< SourceLocation, StringRef > > getCommentsInRange(ASTContext *Ctx, CharSourceRange Range)
Base class for all clang-tidy checks.
Definition: ClangTidy.h:127
SourceManager & SM
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.
Definition: ClangTidy.cpp:436
std::map< std::string, std::string > OptionMap
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
CharSourceRange Range
SourceRange for the file name.
ClangTidyContext & Context
Definition: ClangTidy.cpp:87
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Definition: ClangTidy.cpp:403
const NamedDecl * Result
Definition: USRFinder.cpp:162