10#include "clang/AST/ASTContext.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12#include "clang/Lex/Lexer.h"
13#include "clang/Lex/Token.h"
15#include "../utils/LexerUtils.h"
22 if (
const auto *D =
Node.getDeclContext()->getEnclosingNamespaceContext())
23 if (D->isStdNamespace())
25 if (
Node.getLocation().isInvalid())
27 return Node.getASTContext().getSourceManager().isInSystemHeader(
35 StrictMode(Options.getLocalOrGlobal(
"StrictMode", false)),
36 IgnoreSingleArgument(Options.get(
"IgnoreSingleArgument", false)),
37 CommentBoolLiterals(Options.get(
"CommentBoolLiterals", false)),
38 CommentIntegerLiterals(Options.get(
"CommentIntegerLiterals", false)),
39 CommentFloatLiterals(Options.get(
"CommentFloatLiterals", false)),
40 CommentStringLiterals(Options.get(
"CommentStringLiterals", false)),
41 CommentUserDefinedLiterals(
42 Options.get(
"CommentUserDefinedLiterals", false)),
43 CommentCharacterLiterals(Options.get(
"CommentCharacterLiterals", false)),
44 CommentNullPtrs(Options.get(
"CommentNullPtrs", false)),
45 IdentRE(
"^(/\\* *)([_A-Za-z][_A-Za-z0-9]*)( *= *\\*/)$") {}
49 Options.
store(Opts,
"IgnoreSingleArgument", IgnoreSingleArgument);
50 Options.
store(Opts,
"CommentBoolLiterals", CommentBoolLiterals);
51 Options.
store(Opts,
"CommentIntegerLiterals", CommentIntegerLiterals);
52 Options.
store(Opts,
"CommentFloatLiterals", CommentFloatLiterals);
53 Options.
store(Opts,
"CommentStringLiterals", CommentStringLiterals);
54 Options.
store(Opts,
"CommentUserDefinedLiterals", CommentUserDefinedLiterals);
55 Options.
store(Opts,
"CommentCharacterLiterals", CommentCharacterLiterals);
61 callExpr(unless(cxxOperatorCallExpr()), unless(userDefinedLiteral()),
65 unless(hasDeclaration(functionDecl(
66 hasAnyName(
"NewCallback",
"NewPermanentCallback")))),
71 unless(hasDeclaration(isFromStdNamespaceOrSystemHeader())))
74 Finder->addMatcher(cxxConstructExpr(unless(hasDeclaration(
75 isFromStdNamespaceOrSystemHeader())))
80static std::vector<std::pair<SourceLocation, StringRef>>
82 std::vector<std::pair<SourceLocation, StringRef>> Comments;
83 auto &SM = Ctx->getSourceManager();
84 std::pair<FileID, unsigned> BeginLoc = SM.getDecomposedLoc(
Range.getBegin()),
85 EndLoc = SM.getDecomposedLoc(
Range.getEnd());
87 if (BeginLoc.first != EndLoc.first)
91 StringRef Buffer = SM.getBufferData(BeginLoc.first, &Invalid);
95 const char *StrData = Buffer.data() + BeginLoc.second;
97 Lexer TheLexer(SM.getLocForStartOfFile(BeginLoc.first), Ctx->getLangOpts(),
98 Buffer.begin(), StrData, Buffer.end());
99 TheLexer.SetCommentRetentionState(
true);
103 if (TheLexer.LexFromRawLexer(Tok))
105 if (Tok.getLocation() ==
Range.getEnd() || Tok.is(tok::eof))
108 if (Tok.is(tok::comment)) {
109 std::pair<FileID, unsigned> CommentLoc =
110 SM.getDecomposedLoc(Tok.getLocation());
111 assert(CommentLoc.first == BeginLoc.first);
112 Comments.emplace_back(
114 StringRef(Buffer.begin() + CommentLoc.second, Tok.getLength()));
124static std::vector<std::pair<SourceLocation, StringRef>>
126 std::vector<std::pair<SourceLocation, StringRef>> Comments;
127 while (
Loc.isValid()) {
129 Loc, Ctx->getSourceManager(), Ctx->getLangOpts(),
131 if (Tok.isNot(tok::comment))
133 Loc = Tok.getLocation();
134 Comments.emplace_back(
136 Lexer::getSourceText(CharSourceRange::getCharRange(
137 Loc,
Loc.getLocWithOffset(Tok.getLength())),
138 Ctx->getSourceManager(), Ctx->getLangOpts()));
144 StringRef
ArgName,
unsigned ArgIndex) {
145 std::string ArgNameLowerStr =
ArgName.lower();
146 StringRef ArgNameLower = ArgNameLowerStr;
148 unsigned UpperBound = (
ArgName.size() + 2) / 3 + 1;
149 unsigned ThisED = ArgNameLower.edit_distance(
150 Params[ArgIndex]->getIdentifier()->getName().lower(),
152 if (ThisED >= UpperBound)
155 for (
unsigned I = 0,
E = Params.size(); I !=
E; ++I) {
158 IdentifierInfo *II = Params[I]->getIdentifier();
162 const unsigned Threshold = 2;
166 unsigned OtherED = ArgNameLower.edit_distance(II->getName().lower(),
169 if (OtherED < ThisED + Threshold)
176static bool sameName(StringRef InComment, StringRef InDecl,
bool StrictMode) {
178 return InComment == InDecl;
179 InComment = InComment.trim(
'_');
180 InDecl = InDecl.trim(
'_');
182 return InComment.compare_insensitive(InDecl) == 0;
186 return Expect !=
nullptr && Expect->getLocation().isMacroID() &&
187 Expect->getNameInfo().getName().isIdentifier() &&
188 Expect->getName().starts_with(
"gmock_");
191 const CXXMethodDecl *Expect) {
193 return Mock !=
nullptr && Mock->getNextDeclInContext() == Expect &&
194 Mock->getNumParams() == Expect->getNumParams() &&
195 Mock->getLocation().isMacroID() &&
196 Mock->getNameInfo().getName().isIdentifier() &&
197 Mock->getName() == Expect->getName().substr(strlen(
"gmock_"));
207 const DeclContext *Ctx = Method->getDeclContext();
208 if (Ctx ==
nullptr || !Ctx->isRecord())
210 for (
const auto *D : Ctx->decls()) {
211 if (D->getNextDeclInContext() == Method) {
212 const auto *Previous = dyn_cast<CXXMethodDecl>(D);
218 if (
const auto *Next =
219 dyn_cast_or_null<CXXMethodDecl>(Method->getNextDeclInContext())) {
231 if (
const auto *Method = dyn_cast<CXXMethodDecl>(Func)) {
235 if (MockedMethod->size_overridden_methods() > 0) {
236 return *MockedMethod->begin_overridden_methods();
246bool ArgumentCommentCheck::shouldAddComment(
const Expr *Arg)
const {
247 Arg = Arg->IgnoreImpCasts();
248 if (isa<UnaryOperator>(Arg))
249 Arg = cast<UnaryOperator>(Arg)->getSubExpr();
250 if (Arg->getExprLoc().isMacroID())
252 return (CommentBoolLiterals && isa<CXXBoolLiteralExpr>(Arg)) ||
253 (CommentIntegerLiterals && isa<IntegerLiteral>(Arg)) ||
254 (CommentFloatLiterals && isa<FloatingLiteral>(Arg)) ||
255 (CommentUserDefinedLiterals && isa<UserDefinedLiteral>(Arg)) ||
256 (CommentCharacterLiterals && isa<CharacterLiteral>(Arg)) ||
257 (CommentStringLiterals && isa<StringLiteral>(Arg)) ||
258 (CommentNullPtrs && isa<CXXNullPtrLiteralExpr>(Arg));
261void ArgumentCommentCheck::checkCallArgs(ASTContext *Ctx,
262 const FunctionDecl *OriginalCallee,
263 SourceLocation ArgBeginLoc,
264 llvm::ArrayRef<const Expr *>
Args) {
265 const FunctionDecl *Callee =
resolveMocks(OriginalCallee);
269 Callee = Callee->getFirstDecl();
270 unsigned NumArgs = std::min<unsigned>(
Args.size(), Callee->getNumParams());
271 if ((NumArgs == 0) || (IgnoreSingleArgument && NumArgs == 1))
274 auto MakeFileCharRange = [Ctx](SourceLocation Begin, SourceLocation End) {
275 return Lexer::makeFileCharRange(CharSourceRange::getCharRange(Begin, End),
276 Ctx->getSourceManager(),
280 for (
unsigned I = 0; I < NumArgs; ++I) {
281 const ParmVarDecl *PVD = Callee->getParamDecl(I);
282 IdentifierInfo *II = PVD->getIdentifier();
285 if (FunctionDecl *Template = Callee->getTemplateInstantiationPattern()) {
290 if (Template->getNumParams() <= I ||
291 Template->getParamDecl(I)->isParameterPack()) {
296 CharSourceRange BeforeArgument =
297 MakeFileCharRange(ArgBeginLoc,
Args[I]->getBeginLoc());
298 ArgBeginLoc =
Args[I]->getEndLoc();
300 std::vector<std::pair<SourceLocation, StringRef>> Comments;
301 if (BeforeArgument.isValid()) {
305 CharSourceRange ArgsRange =
306 MakeFileCharRange(
Args[I]->getBeginLoc(),
Args[I]->getEndLoc());
310 for (
auto Comment : Comments) {
311 llvm::SmallVector<StringRef, 2> Matches;
312 if (IdentRE.match(Comment.second, &Matches) &&
313 !
sameName(Matches[2], II->getName(), StrictMode)) {
315 DiagnosticBuilder Diag =
316 diag(Comment.first,
"argument name '%0' in comment does not "
317 "match parameter name %1")
319 if (
isLikelyTypo(Callee->parameters(), Matches[2], I)) {
320 Diag << FixItHint::CreateReplacement(
321 Comment.first, (Matches[1] + II->getName() + Matches[3]).str());
324 diag(PVD->getLocation(),
"%0 declared here", DiagnosticIDs::Note) << II;
325 if (OriginalCallee != Callee) {
326 diag(OriginalCallee->getLocation(),
327 "actual callee (%0) is declared here", DiagnosticIDs::Note)
334 if (Comments.empty() && shouldAddComment(
Args[I])) {
335 std::string ArgComment =
336 (llvm::Twine(
"/*") + II->getName() +
"=*/").str();
337 DiagnosticBuilder Diag =
339 "argument comment missing for literal argument %0")
341 << FixItHint::CreateInsertion(
Args[I]->getBeginLoc(), ArgComment);
347 const auto *
E = Result.Nodes.getNodeAs<Expr>(
"expr");
348 if (
const auto *Call = dyn_cast<CallExpr>(
E)) {
349 const FunctionDecl *Callee = Call->getDirectCallee();
353 checkCallArgs(Result.Context, Callee, Call->getCallee()->getEndLoc(),
354 llvm::ArrayRef(Call->getArgs(), Call->getNumArgs()));
356 const auto *Construct = cast<CXXConstructExpr>(
E);
357 if (Construct->getNumArgs() > 0 &&
358 Construct->getArg(0)->getSourceRange() == Construct->getSourceRange()) {
363 Result.Context, Construct->getConstructor(),
364 Construct->getParenOrBraceRange().getBegin(),
365 llvm::ArrayRef(Construct->getArgs(), Construct->getNumArgs()));
const FunctionDecl * Decl
llvm::SmallString< 256U > Name
CharSourceRange Range
SourceRange for the file name.
::clang::DynTypedNode Node
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
static bool sameName(StringRef InComment, StringRef InDecl, bool StrictMode)
static std::vector< std::pair< SourceLocation, StringRef > > getCommentsBeforeLoc(ASTContext *Ctx, SourceLocation Loc)
static std::vector< std::pair< SourceLocation, StringRef > > getCommentsInRange(ASTContext *Ctx, CharSourceRange Range)
AST_MATCHER(clang::VarDecl, hasConstantDeclaration)
static bool looksLikeExpectMethod(const CXXMethodDecl *Expect)
static const CXXMethodDecl * findMockedMethod(const CXXMethodDecl *Method)
static const FunctionDecl * resolveMocks(const FunctionDecl *Func)
static bool isLikelyTypo(llvm::ArrayRef< ParmVarDecl * > Params, StringRef ArgName, unsigned ArgIndex)
static bool areMockAndExpectMethods(const CXXMethodDecl *Mock, const CXXMethodDecl *Expect)
Token getPreviousToken(SourceLocation Location, const SourceManager &SM, const LangOptions &LangOpts, bool SkipComments)
Returns previous token or tok::unknown if not found.
llvm::StringMap< ClangTidyValue > OptionMap
static constexpr const char ArgName[]