1 //===--- ContainerContainsCheck.cpp - clang-tidy --------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "ContainerContainsCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 using namespace clang::ast_matchers
;
15 namespace clang::tidy::readability
{
17 void ContainerContainsCheck::registerMatchers(MatchFinder
*Finder
) {
18 const auto SupportedContainers
= hasType(
19 hasUnqualifiedDesugaredType(recordType(hasDeclaration(cxxRecordDecl(
20 hasAnyName("::std::set", "::std::unordered_set", "::std::map",
21 "::std::unordered_map", "::std::multiset",
22 "::std::unordered_multiset", "::std::multimap",
23 "::std::unordered_multimap"))))));
25 const auto CountCall
=
26 cxxMemberCallExpr(on(SupportedContainers
),
27 callee(cxxMethodDecl(hasName("count"))),
32 cxxMemberCallExpr(on(SupportedContainers
),
33 callee(cxxMethodDecl(hasName("find"))),
37 const auto EndCall
= cxxMemberCallExpr(on(SupportedContainers
),
38 callee(cxxMethodDecl(hasName("end"))),
41 const auto Literal0
= integerLiteral(equals(0));
42 const auto Literal1
= integerLiteral(equals(1));
44 auto AddSimpleMatcher
= [&](auto Matcher
) {
46 traverse(TK_IgnoreUnlessSpelledInSource
, std::move(Matcher
)), this);
49 // Find membership tests which use `count()`.
50 Finder
->addMatcher(implicitCastExpr(hasImplicitDestinationType(booleanType()),
51 hasSourceExpression(CountCall
))
52 .bind("positiveComparison"),
55 binaryOperator(hasLHS(CountCall
), hasOperatorName("!="), hasRHS(Literal0
))
56 .bind("positiveComparison"));
58 binaryOperator(hasLHS(Literal0
), hasOperatorName("!="), hasRHS(CountCall
))
59 .bind("positiveComparison"));
61 binaryOperator(hasLHS(CountCall
), hasOperatorName(">"), hasRHS(Literal0
))
62 .bind("positiveComparison"));
64 binaryOperator(hasLHS(Literal0
), hasOperatorName("<"), hasRHS(CountCall
))
65 .bind("positiveComparison"));
67 binaryOperator(hasLHS(CountCall
), hasOperatorName(">="), hasRHS(Literal1
))
68 .bind("positiveComparison"));
70 binaryOperator(hasLHS(Literal1
), hasOperatorName("<="), hasRHS(CountCall
))
71 .bind("positiveComparison"));
73 // Find inverted membership tests which use `count()`.
75 binaryOperator(hasLHS(CountCall
), hasOperatorName("=="), hasRHS(Literal0
))
76 .bind("negativeComparison"));
78 binaryOperator(hasLHS(Literal0
), hasOperatorName("=="), hasRHS(CountCall
))
79 .bind("negativeComparison"));
81 binaryOperator(hasLHS(CountCall
), hasOperatorName("<="), hasRHS(Literal0
))
82 .bind("negativeComparison"));
84 binaryOperator(hasLHS(Literal0
), hasOperatorName(">="), hasRHS(CountCall
))
85 .bind("negativeComparison"));
87 binaryOperator(hasLHS(CountCall
), hasOperatorName("<"), hasRHS(Literal1
))
88 .bind("negativeComparison"));
90 binaryOperator(hasLHS(Literal1
), hasOperatorName(">"), hasRHS(CountCall
))
91 .bind("negativeComparison"));
93 // Find membership tests based on `find() == end()`.
95 binaryOperator(hasLHS(FindCall
), hasOperatorName("!="), hasRHS(EndCall
))
96 .bind("positiveComparison"));
98 binaryOperator(hasLHS(FindCall
), hasOperatorName("=="), hasRHS(EndCall
))
99 .bind("negativeComparison"));
102 void ContainerContainsCheck::check(const MatchFinder::MatchResult
&Result
) {
103 // Extract the information about the match
104 const auto *Call
= Result
.Nodes
.getNodeAs
<CXXMemberCallExpr
>("call");
105 const auto *PositiveComparison
=
106 Result
.Nodes
.getNodeAs
<Expr
>("positiveComparison");
107 const auto *NegativeComparison
=
108 Result
.Nodes
.getNodeAs
<Expr
>("negativeComparison");
109 assert((!PositiveComparison
|| !NegativeComparison
) &&
110 "only one of PositiveComparison or NegativeComparison should be set");
111 bool Negated
= NegativeComparison
!= nullptr;
112 const auto *Comparison
= Negated
? NegativeComparison
: PositiveComparison
;
114 // Diagnose the issue.
116 diag(Call
->getExprLoc(), "use 'contains' to check for membership");
118 // Don't fix it if it's in a macro invocation. Leave fixing it to the user.
119 SourceLocation FuncCallLoc
= Comparison
->getEndLoc();
120 if (!FuncCallLoc
.isValid() || FuncCallLoc
.isMacroID())
123 // Create the fix it.
124 const auto *Member
= cast
<MemberExpr
>(Call
->getCallee());
125 Diag
<< FixItHint::CreateReplacement(
126 Member
->getMemberNameInfo().getSourceRange(), "contains");
127 SourceLocation ComparisonBegin
= Comparison
->getSourceRange().getBegin();
128 SourceLocation ComparisonEnd
= Comparison
->getSourceRange().getEnd();
129 SourceLocation CallBegin
= Call
->getSourceRange().getBegin();
130 SourceLocation CallEnd
= Call
->getSourceRange().getEnd();
131 Diag
<< FixItHint::CreateReplacement(
132 CharSourceRange::getCharRange(ComparisonBegin
, CallBegin
),
134 Diag
<< FixItHint::CreateRemoval(CharSourceRange::getCharRange(
135 CallEnd
.getLocWithOffset(1), ComparisonEnd
.getLocWithOffset(1)));
138 } // namespace clang::tidy::readability