| //===--- 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(StringRef Name, |
| ClangTidyContext *Context) |
| : ClangTidyCheck(Name, Context), |
| 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; |
| if (auto Template = Callee->getTemplateInstantiationPattern()) { |
| // Don't warn on arguments for parameters instantiated from template |
| // parameter packs. If we find more arguments than the template definition |
| // has, it also means that they correspond to a parameter pack. |
| if (Template->getNumParams() <= i || |
| Template->getParamDecl(i)->isParameterPack()) { |
| 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 |