diff options
Diffstat (limited to 'clang-tools-extra/clang-tidy/misc/ArgumentCommentCheck.cpp')
| -rw-r--r-- | clang-tools-extra/clang-tidy/misc/ArgumentCommentCheck.cpp | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/clang-tools-extra/clang-tidy/misc/ArgumentCommentCheck.cpp b/clang-tools-extra/clang-tidy/misc/ArgumentCommentCheck.cpp new file mode 100644 index 00000000000..4ef9f8f1b82 --- /dev/null +++ b/clang-tools-extra/clang-tidy/misc/ArgumentCommentCheck.cpp @@ -0,0 +1,166 @@ +//===--- 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 "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.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 { + +void ArgumentCommentCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(callExpr(unless(operatorCallExpr())).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 (const auto &Param : Params) { + if (&Param - Params.begin() == ArgIndex) + continue; + IdentifierInfo *II = Param->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 |

