38AST_MATCHER(Decl, isFromStdNamespaceOrSystemHeader) {
39 if (
const auto *D = Node.getDeclContext()->getEnclosingNamespaceContext();
40 D && D->isStdNamespace())
42 if (Node.getLocation().isInvalid())
44 return Node.getASTContext().getSourceManager().isInSystemHeader(
54 if (
const std::optional<StringRef> Value =
55 Options.get(
"CommentIntegerLiterals")) {
56 if (
const std::optional<bool> Bool = llvm::yaml::parseBool(*Value))
57 return *Bool ? IntegerLiteralKind::All : IntegerLiteralKind::None;
59 if (!Value->getAsInteger(10, Number))
60 return Number != 0 ? IntegerLiteralKind::All : IntegerLiteralKind::None;
62 return Options.get(
"CommentIntegerLiterals", IntegerLiteralKind::None);
68 StrictMode(Options.get(
"StrictMode", false)),
69 IgnoreSingleArgument(Options.get(
"IgnoreSingleArgument", false)),
70 CommentAnonymousInitLists(
71 Options.get(
"CommentAnonymousInitLists", false)),
72 CommentBoolLiterals(Options.get(
"CommentBoolLiterals", false)),
73 CommentCharacterLiterals(Options.get(
"CommentCharacterLiterals", false)),
74 CommentFloatLiterals(Options.get(
"CommentFloatLiterals", false)),
75 CommentNullPtrs(Options.get(
"CommentNullPtrs", false)),
76 CommentParenthesizedTemporaries(
77 Options.get(
"CommentParenthesizedTemporaries", false)),
78 CommentStringLiterals(Options.get(
"CommentStringLiterals", false)),
79 CommentTypedInitLists(Options.get(
"CommentTypedInitLists", false)),
80 CommentUserDefinedLiterals(
81 Options.get(
"CommentUserDefinedLiterals", false)),
82 IgnoredParameterNames(
utils::options::parseStringList(
83 Options.get(
"IgnoredParameterNames",
""))),
85 IdentRE(
"^(/\\* *)([_A-Za-z][_A-Za-z0-9]*)( *= *\\*/)$") {
86 for (
const StringRef Name : IgnoredParameterNames) {
87 llvm::Regex Regex(Name);
88 if (!Regex.isValid()) {
89 configurationDiag(
"Invalid regular expression '%0' in "
90 "IgnoredParameterNames")
94 IgnoredParameterNameRegexes.push_back(std::move(Regex));
99 Options.store(Opts,
"StrictMode", StrictMode);
100 Options.store(Opts,
"IgnoreSingleArgument", IgnoreSingleArgument);
101 Options.store(Opts,
"CommentAnonymousInitLists", CommentAnonymousInitLists);
102 Options.store(Opts,
"CommentBoolLiterals", CommentBoolLiterals);
103 Options.store(Opts,
"CommentCharacterLiterals", CommentCharacterLiterals);
104 Options.store(Opts,
"CommentFloatLiterals", CommentFloatLiterals);
105 Options.store(Opts,
"CommentIntegerLiterals", CommentIntegerLiterals);
106 Options.store(Opts,
"CommentNullPtrs", CommentNullPtrs);
107 Options.store(Opts,
"CommentParenthesizedTemporaries",
108 CommentParenthesizedTemporaries);
109 Options.store(Opts,
"CommentStringLiterals", CommentStringLiterals);
110 Options.store(Opts,
"CommentTypedInitLists", CommentTypedInitLists);
111 Options.store(Opts,
"CommentUserDefinedLiterals", CommentUserDefinedLiterals);
112 Options.store(Opts,
"IgnoredParameterNames",
118 callExpr(unless(cxxOperatorCallExpr()), unless(userDefinedLiteral()),
122 unless(hasDeclaration(functionDecl(
123 hasAnyName(
"NewCallback",
"NewPermanentCallback")))),
128 unless(hasDeclaration(isFromStdNamespaceOrSystemHeader())))
131 Finder->addMatcher(cxxConstructExpr(unless(hasDeclaration(
132 isFromStdNamespaceOrSystemHeader())))
138 SourceLocation Loc) {
139 std::vector<CommentToken> Comments;
140 while (Loc.isValid()) {
142 Loc, Ctx->getSourceManager(), Ctx->getLangOpts(),
144 if (!Tok || Tok->isNot(tok::comment))
146 Loc = Tok->getLocation();
149 Lexer::getSourceText(CharSourceRange::getCharRange(
150 Loc, Loc.getLocWithOffset(Tok->getLength())),
151 Ctx->getSourceManager(), Ctx->getLangOpts()),
157template <
typename NamedDeclRange>
159 StringRef TargetName) {
160 const std::string ArgNameLowerStr =
ArgName.lower();
161 const StringRef ArgNameLower = ArgNameLowerStr;
163 const unsigned UpperBound = ((
ArgName.size() + 2) / 3) + 1;
164 const unsigned ThisED =
165 ArgNameLower.edit_distance(TargetName.lower(),
167 if (ThisED >= UpperBound)
170 return llvm::all_of(Candidates, [&](
const auto &Candidate) {
171 const IdentifierInfo *II = Candidate->getIdentifier();
176 if (II->getName() == TargetName)
179 const unsigned Threshold = 2;
183 const unsigned OtherED = ArgNameLower.edit_distance(
184 II->getName().lower(),
185 true, ThisED + Threshold);
186 return OtherED >= ThisED + Threshold;
190static bool sameName(StringRef InComment, StringRef InDecl,
bool StrictMode) {
192 return InComment == InDecl;
193 InComment = InComment.trim(
'_');
194 InDecl = InDecl.trim(
'_');
196 return InComment.compare_insensitive(InDecl) == 0;
200 return Expect !=
nullptr && Expect->getLocation().isMacroID() &&
201 Expect->getNameInfo().getName().isIdentifier() &&
202 Expect->getName().starts_with(
"gmock_");
205 const CXXMethodDecl *Expect) {
207 return Mock !=
nullptr && Mock->getNextDeclInContext() == Expect &&
208 Mock->getNumParams() == Expect->getNumParams() &&
209 Mock->getLocation().isMacroID() &&
210 Mock->getNameInfo().getName().isIdentifier() &&
211 Mock->getName() == Expect->getName().substr(strlen(
"gmock_"));
221 const DeclContext *Ctx = Method->getDeclContext();
222 if (Ctx ==
nullptr || !Ctx->isRecord())
224 for (
const auto *D : Ctx->decls()) {
225 if (D->getNextDeclInContext() == Method) {
226 const auto *Previous = dyn_cast<CXXMethodDecl>(D);
232 if (
const auto *Next =
233 dyn_cast_or_null<CXXMethodDecl>(Method->getNextDeclInContext());
246 if (
const auto *Method = dyn_cast<CXXMethodDecl>(Func)) {
250 if (MockedMethod->size_overridden_methods() > 0)
251 return *MockedMethod->begin_overridden_methods();
260enum class InitListKind {
269 Arg = Arg->IgnoreUnlessSpelledInSource();
271 if (
const auto *StdInit = dyn_cast<CXXStdInitializerListExpr>(Arg))
272 Arg = StdInit->getSubExpr()->IgnoreUnlessSpelledInSource();
274 if (isa<InitListExpr>(Arg))
275 return InitListKind::Anonymous;
277 if (
const auto *Ctor = dyn_cast<CXXConstructExpr>(Arg)) {
278 if (!Ctor->isListInitialization())
279 return InitListKind::None;
281 if (isa<CXXTemporaryObjectExpr>(Ctor))
282 return InitListKind::Typed;
285 return InitListKind::Anonymous;
290 if (
const auto *FuncCast = dyn_cast<CXXFunctionalCastExpr>(Arg)) {
291 const Expr *SubExpr = FuncCast->getSubExpr()->IgnoreImplicit();
292 if (FuncCast->isListInitialization() ||
293 isa<CXXStdInitializerListExpr>(SubExpr))
294 return InitListKind::Typed;
297 return InitListKind::None;
301 Arg = Arg->IgnoreUnlessSpelledInSource();
302 if (
const auto *TempObject = dyn_cast<CXXTemporaryObjectExpr>(Arg))
303 return !TempObject->isListInitialization();
306 const auto *FuncCast = dyn_cast<CXXFunctionalCastExpr>(Arg);
308 isa<CXXParenListInitExpr>(FuncCast->getSubExpr()->IgnoreImplicit());
313ArgumentCommentCheck::CommentKind
314ArgumentCommentCheck::shouldAddComment(
const Expr *Arg)
const {
320 Arg = Arg->IgnoreImplicit();
323 const auto *IntegerLit = dyn_cast<IntegerLiteral>(Arg);
324 const bool IsZero = IntegerLit && IntegerLit->getValue().isZero();
325 if (
const auto *UO = dyn_cast<UnaryOperator>(Arg))
326 Arg = UO->getSubExpr()->IgnoreImplicit();
327 if (Arg->getExprLoc().isMacroID())
328 return CommentKind::None;
330 if ((CommentAnonymousInitLists && Kind == InitListKind::Anonymous) ||
331 (CommentTypedInitLists && Kind == InitListKind::Typed) ||
332 (CommentParenthesizedTemporaries && IsParenthesizedTemporary)) {
333 return CommentKind::NonLiteral;
336 const bool CommentInteger =
337 CommentIntegerLiterals == IntegerLiteralKind::All ||
338 (CommentIntegerLiterals == IntegerLiteralKind::ZeroOnly && IsZero);
339 if ((CommentBoolLiterals && isa<CXXBoolLiteralExpr>(Arg)) ||
340 (CommentInteger && isa<IntegerLiteral>(Arg)) ||
341 (CommentFloatLiterals && isa<FloatingLiteral>(Arg)) ||
342 (CommentUserDefinedLiterals && isa<UserDefinedLiteral>(Arg)) ||
343 (CommentCharacterLiterals && isa<CharacterLiteral>(Arg)) ||
344 (CommentStringLiterals && isa<StringLiteral>(Arg)) ||
345 (CommentNullPtrs && isa<CXXNullPtrLiteralExpr>(Arg))) {
346 return CommentKind::Literal;
349 return CommentKind::None;
352void ArgumentCommentCheck::checkCallArgs(ASTContext *Ctx,
353 const FunctionDecl *OriginalCallee,
354 SourceLocation ArgBeginLoc,
355 llvm::ArrayRef<const Expr *> Args) {
356 const FunctionDecl *Callee =
resolveMocks(OriginalCallee);
360 Callee = Callee->getFirstDecl();
361 if (
const auto *Ctor = dyn_cast<CXXConstructorDecl>(Callee);
362 Ctor && Ctor->isInheritingConstructor()) {
363 if (
const auto *BaseCtor = Ctor->getInheritedConstructor().getConstructor())
364 Callee = BaseCtor->getFirstDecl();
366 const unsigned NumArgs =
367 std::min<unsigned>(Args.size(), Callee->getNumParams());
368 if ((NumArgs == 0) || (IgnoreSingleArgument && NumArgs == 1))
371 const auto MakeFileCharRange = [Ctx](SourceLocation Begin,
372 SourceLocation End) {
373 return Lexer::makeFileCharRange(CharSourceRange::getCharRange(Begin, End),
374 Ctx->getSourceManager(),
378 for (
unsigned I = 0; I < NumArgs; ++I) {
379 const ParmVarDecl *PVD = Callee->getParamDecl(I);
380 const IdentifierInfo *II = PVD->getIdentifier();
381 if (!II || llvm::any_of(IgnoredParameterNameRegexes,
382 [II](
const llvm::Regex &Regex) {
383 return Regex.match(II->getName());
390 if (FunctionDecl *Template = Callee->getTemplateInstantiationPattern();
391 Template && (Template->getNumParams() <= I ||
392 Template->getParamDecl(I)->isParameterPack()))
395 const CharSourceRange BeforeArgument =
396 MakeFileCharRange(ArgBeginLoc, Args[I]->getBeginLoc());
397 ArgBeginLoc = Args[I]->getEndLoc();
399 std::vector<CommentToken> Comments;
400 if (BeforeArgument.isValid()) {
402 BeforeArgument, Ctx->getSourceManager(), Ctx->getLangOpts());
405 const CharSourceRange ArgsRange =
406 MakeFileCharRange(Args[I]->getBeginLoc(), Args[I]->getEndLoc());
410 for (
const auto &Comment : Comments) {
411 SmallVector<StringRef, 2> Matches;
412 if (IdentRE.match(Comment.Text, &Matches) &&
413 !
sameName(Matches[2], II->getName(), StrictMode)) {
415 const DiagnosticBuilder Diag =
416 diag(Comment.Loc,
"argument name '%0' in comment does not "
417 "match parameter name %1")
419 if (
isLikelyTypo(Callee->parameters(), Matches[2], II->getName())) {
420 Diag << FixItHint::CreateReplacement(
422 llvm::Twine(Matches[1] + II->getName() + Matches[3]).str());
425 diag(PVD->getLocation(),
"%0 declared here", DiagnosticIDs::Note) << II;
426 if (OriginalCallee != Callee) {
427 diag(OriginalCallee->getLocation(),
428 "actual callee (%0) is declared here", DiagnosticIDs::Note)
436 const CommentKind Kind = shouldAddComment(Args[I]);
437 if (Comments.empty() && Kind != CommentKind::None) {
438 SmallString<32> ArgComment;
439 llvm::Twine(llvm::Twine(
"/*") + II->getName() +
"=*/")
440 .toStringRef(ArgComment);
441 const DiagnosticBuilder Diag =
442 diag(Args[I]->getBeginLoc(),
443 "argument comment missing for %select{literal argument|"
445 << (Kind == CommentKind::Literal ? 0 : 1) << II
446 << FixItHint::CreateInsertion(Args[I]->getBeginLoc(), ArgComment);
452 const auto *E = Result.Nodes.getNodeAs<Expr>(
"expr");
453 if (
const auto *Call = dyn_cast<CallExpr>(E)) {
454 const FunctionDecl *Callee = Call->getDirectCallee();
458 checkCallArgs(Result.Context, Callee, Call->getCallee()->getEndLoc(),
459 llvm::ArrayRef(Call->getArgs(), Call->getNumArgs()));
461 const auto *Construct = cast<CXXConstructExpr>(E);
462 if (Construct->getNumArgs() > 0 &&
463 Construct->getArg(0)->getSourceRange() == Construct->getSourceRange()) {
468 Result.Context, Construct->getConstructor(),
469 Construct->getParenOrBraceRange().getBegin(),
470 llvm::ArrayRef(Construct->getArgs(), Construct->getNumArgs()));
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.