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