summaryrefslogtreecommitdiffstats
path: root/clang-tools-extra/clang-tidy/misc/ArgumentCommentCheck.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clang-tools-extra/clang-tidy/misc/ArgumentCommentCheck.cpp')
-rw-r--r--clang-tools-extra/clang-tidy/misc/ArgumentCommentCheck.cpp166
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
OpenPOWER on IntegriCloud