11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Lex/Lexer.h"
14 #include "clang/Lex/Token.h"
16 using namespace clang::ast_matchers;
22 ArgumentCommentCheck::ArgumentCommentCheck(StringRef
Name,
25 IdentRE(
"^(/\\* *)([_A-Za-z][_A-Za-z0-9]*)( *= *\\*/)$") {}
29 callExpr(unless(cxxOperatorCallExpr()),
33 unless(hasDeclaration(functionDecl(anyOf(
34 hasName(
"NewCallback"), hasName(
"NewPermanentCallback"))))))
37 Finder->addMatcher(cxxConstructExpr().bind(
"expr"),
this);
40 std::vector<std::pair<SourceLocation, StringRef>>
41 ArgumentCommentCheck::getCommentsInRange(ASTContext *Ctx, SourceRange
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());
47 if (BeginLoc.first != EndLoc.first)
51 StringRef Buffer =
SM.getBufferData(BeginLoc.first, &Invalid);
55 const char *StrData = Buffer.data() + BeginLoc.second;
57 Lexer TheLexer(
SM.getLocForStartOfFile(BeginLoc.first), Ctx->getLangOpts(),
58 Buffer.begin(), StrData, Buffer.end());
59 TheLexer.SetCommentRetentionState(
true);
63 if (TheLexer.LexFromRawLexer(Tok))
65 if (Tok.getLocation() == Range.getEnd() || Tok.getKind() == tok::eof)
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(
74 StringRef(Buffer.begin() + CommentLoc.second, Tok.getLength()));
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(),
89 if (ThisED >= UpperBound)
92 for (
unsigned I = 0, E = Params.size(); I != E; ++I) {
95 IdentifierInfo *II = Params[I]->getIdentifier();
99 const unsigned Threshold = 2;
103 unsigned OtherED = StringRef(ArgNameLower).edit_distance(
104 II->getName().lower(),
105 true, ThisED + Threshold);
106 if (OtherED < ThisED + Threshold)
113 void ArgumentCommentCheck::checkCallArgs(ASTContext *Ctx,
114 const FunctionDecl *Callee,
115 SourceLocation ArgBeginLoc,
116 llvm::ArrayRef<const Expr *> Args) {
117 Callee = Callee->getFirstDecl();
119 e = std::min<unsigned>(Args.size(), Callee->getNumParams());
121 const ParmVarDecl *PVD = Callee->getParamDecl(i);
122 IdentifierInfo *II = PVD->getIdentifier();
125 if (
auto Template = Callee->getTemplateInstantiationPattern()) {
129 if (Template->getNumParams() <= i ||
130 Template->getParamDecl(i)->isParameterPack()) {
135 SourceLocation BeginSLoc, EndSLoc = Args[i]->getLocStart();
137 BeginSLoc = ArgBeginLoc;
139 BeginSLoc = Args[i - 1]->getLocEnd();
140 if (BeginSLoc.isMacroID() || EndSLoc.isMacroID())
144 getCommentsInRange(Ctx, SourceRange(BeginSLoc, EndSLoc))) {
145 llvm::SmallVector<StringRef, 2> Matches;
146 if (IdentRE.match(Comment.second, &Matches)) {
147 if (Matches[2] != II->getName()) {
149 DiagnosticBuilder Diag =
150 diag(Comment.first,
"argument name '%0' in comment does not "
151 "match parameter name %1")
153 if (isLikelyTypo(Callee->parameters(), Matches[2], i)) {
154 Diag << FixItHint::CreateReplacement(
156 (Matches[1] + II->getName() + Matches[3]).str());
159 diag(PVD->getLocation(),
"%0 declared here", DiagnosticIDs::Note)
168 const Expr *E = Result.Nodes.getNodeAs<Expr>(
"expr");
169 if (
auto Call = dyn_cast<CallExpr>(E)) {
170 const FunctionDecl *Callee = Call->getDirectCallee();
174 checkCallArgs(Result.Context, Callee, Call->getCallee()->getLocEnd(),
175 llvm::makeArrayRef(Call->getArgs(), Call->getNumArgs()));
177 auto Construct = cast<CXXConstructExpr>(E);
179 Result.Context, Construct->getConstructor(),
180 Construct->getParenOrBraceRange().getBegin(),
181 llvm::makeArrayRef(Construct->getArgs(), Construct->getNumArgs()));
std::unique_ptr< ast_matchers::MatchFinder > Finder
Base class for all clang-tidy checks.
CharSourceRange Range
SourceRange for the file name.
ClangTidyContext & Context
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.