forked from OSchip/llvm-project
175 lines
6.0 KiB
C++
175 lines
6.0 KiB
C++
//===--- ArgumentCommentCheck.cpp - clang-tidy ----------------------------===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "ArgumentCommentCheck.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
#include "clang/Lex/Lexer.h"
|
|
#include "clang/Lex/Token.h"
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
namespace clang {
|
|
namespace tidy {
|
|
|
|
ArgumentCommentCheck::ArgumentCommentCheck()
|
|
: IdentRE("^(/\\* *)([_A-Za-z][_A-Za-z0-9]*)( *= *\\*/)$") {}
|
|
|
|
void ArgumentCommentCheck::registerMatchers(MatchFinder *Finder) {
|
|
Finder->addMatcher(
|
|
callExpr(unless(operatorCallExpr()),
|
|
// NewCallback's arguments relate to the pointed function, don't
|
|
// check them against NewCallback's parameter names.
|
|
// FIXME: Make this configurable.
|
|
unless(hasDeclaration(functionDecl(anyOf(
|
|
hasName("NewCallback"), hasName("NewPermanentCallback"))))))
|
|
.bind("expr"),
|
|
this);
|
|
Finder->addMatcher(constructExpr().bind("expr"), this);
|
|
}
|
|
|
|
std::vector<std::pair<SourceLocation, StringRef>>
|
|
ArgumentCommentCheck::getCommentsInRange(ASTContext *Ctx, SourceRange Range) {
|
|
std::vector<std::pair<SourceLocation, StringRef>> Comments;
|
|
auto &SM = Ctx->getSourceManager();
|
|
std::pair<FileID, unsigned> BeginLoc = SM.getDecomposedLoc(Range.getBegin()),
|
|
EndLoc = SM.getDecomposedLoc(Range.getEnd());
|
|
|
|
if (BeginLoc.first != EndLoc.first)
|
|
return Comments;
|
|
|
|
bool Invalid = false;
|
|
StringRef Buffer = SM.getBufferData(BeginLoc.first, &Invalid);
|
|
if (Invalid)
|
|
return Comments;
|
|
|
|
const char *StrData = Buffer.data() + BeginLoc.second;
|
|
|
|
Lexer TheLexer(SM.getLocForStartOfFile(BeginLoc.first), Ctx->getLangOpts(),
|
|
Buffer.begin(), StrData, Buffer.end());
|
|
TheLexer.SetCommentRetentionState(true);
|
|
|
|
while (true) {
|
|
Token Tok;
|
|
if (TheLexer.LexFromRawLexer(Tok))
|
|
break;
|
|
if (Tok.getLocation() == Range.getEnd() || Tok.getKind() == tok::eof)
|
|
break;
|
|
|
|
if (Tok.getKind() == tok::comment) {
|
|
std::pair<FileID, unsigned> CommentLoc =
|
|
SM.getDecomposedLoc(Tok.getLocation());
|
|
assert(CommentLoc.first == BeginLoc.first);
|
|
Comments.emplace_back(
|
|
Tok.getLocation(),
|
|
StringRef(Buffer.begin() + CommentLoc.second, Tok.getLength()));
|
|
}
|
|
}
|
|
|
|
return Comments;
|
|
}
|
|
|
|
bool
|
|
ArgumentCommentCheck::isLikelyTypo(llvm::ArrayRef<ParmVarDecl *> Params,
|
|
StringRef ArgName, unsigned ArgIndex) {
|
|
std::string ArgNameLower = ArgName.lower();
|
|
unsigned UpperBound = (ArgName.size() + 2) / 3 + 1;
|
|
unsigned ThisED = StringRef(ArgNameLower).edit_distance(
|
|
Params[ArgIndex]->getIdentifier()->getName().lower(),
|
|
/*AllowReplacements=*/true, UpperBound);
|
|
if (ThisED >= UpperBound)
|
|
return false;
|
|
|
|
for (unsigned I = 0, E = Params.size(); I != E; ++I) {
|
|
if (I == ArgIndex)
|
|
continue;
|
|
IdentifierInfo *II = Params[I]->getIdentifier();
|
|
if (!II)
|
|
continue;
|
|
|
|
const unsigned Threshold = 2;
|
|
// Other parameters must be an edit distance at least Threshold more away
|
|
// from this parameter. This gives us greater confidence that this is a typo
|
|
// of this parameter and not one with a similar name.
|
|
unsigned OtherED = StringRef(ArgNameLower).edit_distance(
|
|
II->getName().lower(),
|
|
/*AllowReplacements=*/true, ThisED + Threshold);
|
|
if (OtherED < ThisED + Threshold)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ArgumentCommentCheck::checkCallArgs(ASTContext *Ctx,
|
|
const FunctionDecl *Callee,
|
|
SourceLocation ArgBeginLoc,
|
|
llvm::ArrayRef<const Expr *> Args) {
|
|
Callee = Callee->getFirstDecl();
|
|
for (unsigned i = 0,
|
|
e = std::min<unsigned>(Args.size(), Callee->getNumParams());
|
|
i != e; ++i) {
|
|
const ParmVarDecl *PVD = Callee->getParamDecl(i);
|
|
IdentifierInfo *II = PVD->getIdentifier();
|
|
if (!II)
|
|
continue;
|
|
|
|
SourceLocation BeginSLoc, EndSLoc = Args[i]->getLocStart();
|
|
if (i == 0)
|
|
BeginSLoc = ArgBeginLoc;
|
|
else
|
|
BeginSLoc = Args[i - 1]->getLocEnd();
|
|
if (BeginSLoc.isMacroID() || EndSLoc.isMacroID())
|
|
continue;
|
|
|
|
for (auto Comment :
|
|
getCommentsInRange(Ctx, SourceRange(BeginSLoc, EndSLoc))) {
|
|
llvm::SmallVector<StringRef, 2> Matches;
|
|
if (IdentRE.match(Comment.second, &Matches)) {
|
|
if (Matches[2] != II->getName()) {
|
|
{
|
|
DiagnosticBuilder Diag =
|
|
diag(Comment.first, "argument name '%0' in comment does not "
|
|
"match parameter name %1")
|
|
<< Matches[2] << II;
|
|
if (isLikelyTypo(Callee->parameters(), Matches[2], i)) {
|
|
Diag << FixItHint::CreateReplacement(
|
|
Comment.first,
|
|
(Matches[1] + II->getName() + Matches[3]).str());
|
|
}
|
|
}
|
|
diag(PVD->getLocation(), "%0 declared here", DiagnosticIDs::Note)
|
|
<< II;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ArgumentCommentCheck::check(const MatchFinder::MatchResult &Result) {
|
|
const Expr *E = Result.Nodes.getNodeAs<Expr>("expr");
|
|
if (auto Call = dyn_cast<CallExpr>(E)) {
|
|
const FunctionDecl *Callee = Call->getDirectCallee();
|
|
if (!Callee)
|
|
return;
|
|
|
|
checkCallArgs(Result.Context, Callee, Call->getCallee()->getLocEnd(),
|
|
llvm::makeArrayRef(Call->getArgs(), Call->getNumArgs()));
|
|
} else {
|
|
auto Construct = cast<CXXConstructExpr>(E);
|
|
checkCallArgs(
|
|
Result.Context, Construct->getConstructor(),
|
|
Construct->getParenOrBraceRange().getBegin(),
|
|
llvm::makeArrayRef(Construct->getArgs(), Construct->getNumArgs()));
|
|
}
|
|
}
|
|
|
|
} // namespace tidy
|
|
} // namespace clang
|