xref: /llvm-project/clang-tools-extra/clang-tidy/readability/ContainerContainsCheck.cpp (revision 3605d9a456185f4af78c01a2684b822b57bca9b0)
13696c70eSAdrian Vogelsgesang //===--- ContainerContainsCheck.cpp - clang-tidy --------------------------===//
23696c70eSAdrian Vogelsgesang //
33696c70eSAdrian Vogelsgesang // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
43696c70eSAdrian Vogelsgesang // See https://llvm.org/LICENSE.txt for license information.
53696c70eSAdrian Vogelsgesang // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
63696c70eSAdrian Vogelsgesang //
73696c70eSAdrian Vogelsgesang //===----------------------------------------------------------------------===//
83696c70eSAdrian Vogelsgesang 
93696c70eSAdrian Vogelsgesang #include "ContainerContainsCheck.h"
103696c70eSAdrian Vogelsgesang #include "clang/AST/ASTContext.h"
113696c70eSAdrian Vogelsgesang #include "clang/ASTMatchers/ASTMatchFinder.h"
123696c70eSAdrian Vogelsgesang 
133696c70eSAdrian Vogelsgesang using namespace clang::ast_matchers;
143696c70eSAdrian Vogelsgesang 
157d2ea6c4SCarlos Galvez namespace clang::tidy::readability {
163696c70eSAdrian Vogelsgesang void ContainerContainsCheck::registerMatchers(MatchFinder *Finder) {
171be4c971SNicolas van Kempen   const auto HasContainsMatchingParamType = hasMethod(
181be4c971SNicolas van Kempen       cxxMethodDecl(isConst(), parameterCountIs(1), returns(booleanType()),
191be4c971SNicolas van Kempen                     hasName("contains"), unless(isDeleted()), isPublic(),
201be4c971SNicolas van Kempen                     hasParameter(0, hasType(hasUnqualifiedDesugaredType(
211be4c971SNicolas van Kempen                                         equalsBoundNode("parameterType"))))));
223696c70eSAdrian Vogelsgesang 
233696c70eSAdrian Vogelsgesang   const auto CountCall =
241be4c971SNicolas van Kempen       cxxMemberCallExpr(
251be4c971SNicolas van Kempen           argumentCountIs(1),
261be4c971SNicolas van Kempen           callee(cxxMethodDecl(
271be4c971SNicolas van Kempen               hasName("count"),
281be4c971SNicolas van Kempen               hasParameter(0, hasType(hasUnqualifiedDesugaredType(
291be4c971SNicolas van Kempen                                   type().bind("parameterType")))),
301be4c971SNicolas van Kempen               ofClass(cxxRecordDecl(HasContainsMatchingParamType)))))
313696c70eSAdrian Vogelsgesang           .bind("call");
323696c70eSAdrian Vogelsgesang 
333696c70eSAdrian Vogelsgesang   const auto FindCall =
341be4c971SNicolas van Kempen       cxxMemberCallExpr(
351be4c971SNicolas van Kempen           argumentCountIs(1),
361be4c971SNicolas van Kempen           callee(cxxMethodDecl(
371be4c971SNicolas van Kempen               hasName("find"),
381be4c971SNicolas van Kempen               hasParameter(0, hasType(hasUnqualifiedDesugaredType(
391be4c971SNicolas van Kempen                                   type().bind("parameterType")))),
401be4c971SNicolas van Kempen               ofClass(cxxRecordDecl(HasContainsMatchingParamType)))))
413696c70eSAdrian Vogelsgesang           .bind("call");
423696c70eSAdrian Vogelsgesang 
431be4c971SNicolas van Kempen   const auto EndCall = cxxMemberCallExpr(
441be4c971SNicolas van Kempen       argumentCountIs(0),
451be4c971SNicolas van Kempen       callee(
461be4c971SNicolas van Kempen           cxxMethodDecl(hasName("end"),
471be4c971SNicolas van Kempen                         // In the matchers below, FindCall should always appear
481be4c971SNicolas van Kempen                         // before EndCall so 'parameterType' is properly bound.
491be4c971SNicolas van Kempen                         ofClass(cxxRecordDecl(HasContainsMatchingParamType)))));
503696c70eSAdrian Vogelsgesang 
513696c70eSAdrian Vogelsgesang   const auto Literal0 = integerLiteral(equals(0));
523696c70eSAdrian Vogelsgesang   const auto Literal1 = integerLiteral(equals(1));
533696c70eSAdrian Vogelsgesang 
543696c70eSAdrian Vogelsgesang   auto AddSimpleMatcher = [&](auto Matcher) {
553696c70eSAdrian Vogelsgesang     Finder->addMatcher(
563696c70eSAdrian Vogelsgesang         traverse(TK_IgnoreUnlessSpelledInSource, std::move(Matcher)), this);
573696c70eSAdrian Vogelsgesang   };
583696c70eSAdrian Vogelsgesang 
593696c70eSAdrian Vogelsgesang   // Find membership tests which use `count()`.
603696c70eSAdrian Vogelsgesang   Finder->addMatcher(implicitCastExpr(hasImplicitDestinationType(booleanType()),
613696c70eSAdrian Vogelsgesang                                       hasSourceExpression(CountCall))
623696c70eSAdrian Vogelsgesang                          .bind("positiveComparison"),
633696c70eSAdrian Vogelsgesang                      this);
643696c70eSAdrian Vogelsgesang   AddSimpleMatcher(
65*3605d9a4SNicolas van Kempen       binaryOperation(hasOperatorName("!="), hasOperands(CountCall, Literal0))
663696c70eSAdrian Vogelsgesang           .bind("positiveComparison"));
673696c70eSAdrian Vogelsgesang   AddSimpleMatcher(
68*3605d9a4SNicolas van Kempen       binaryOperation(hasLHS(CountCall), hasOperatorName(">"), hasRHS(Literal0))
693696c70eSAdrian Vogelsgesang           .bind("positiveComparison"));
703696c70eSAdrian Vogelsgesang   AddSimpleMatcher(
71*3605d9a4SNicolas van Kempen       binaryOperation(hasLHS(Literal0), hasOperatorName("<"), hasRHS(CountCall))
723696c70eSAdrian Vogelsgesang           .bind("positiveComparison"));
73*3605d9a4SNicolas van Kempen   AddSimpleMatcher(binaryOperation(hasLHS(CountCall), hasOperatorName(">="),
74*3605d9a4SNicolas van Kempen                                    hasRHS(Literal1))
753696c70eSAdrian Vogelsgesang                        .bind("positiveComparison"));
76*3605d9a4SNicolas van Kempen   AddSimpleMatcher(binaryOperation(hasLHS(Literal1), hasOperatorName("<="),
77*3605d9a4SNicolas van Kempen                                    hasRHS(CountCall))
783696c70eSAdrian Vogelsgesang                        .bind("positiveComparison"));
793696c70eSAdrian Vogelsgesang 
803696c70eSAdrian Vogelsgesang   // Find inverted membership tests which use `count()`.
813696c70eSAdrian Vogelsgesang   AddSimpleMatcher(
82*3605d9a4SNicolas van Kempen       binaryOperation(hasOperatorName("=="), hasOperands(CountCall, Literal0))
83*3605d9a4SNicolas van Kempen           .bind("negativeComparison"));
84*3605d9a4SNicolas van Kempen   AddSimpleMatcher(binaryOperation(hasLHS(CountCall), hasOperatorName("<="),
85*3605d9a4SNicolas van Kempen                                    hasRHS(Literal0))
86*3605d9a4SNicolas van Kempen                        .bind("negativeComparison"));
87*3605d9a4SNicolas van Kempen   AddSimpleMatcher(binaryOperation(hasLHS(Literal0), hasOperatorName(">="),
88*3605d9a4SNicolas van Kempen                                    hasRHS(CountCall))
893696c70eSAdrian Vogelsgesang                        .bind("negativeComparison"));
903696c70eSAdrian Vogelsgesang   AddSimpleMatcher(
91*3605d9a4SNicolas van Kempen       binaryOperation(hasLHS(CountCall), hasOperatorName("<"), hasRHS(Literal1))
923696c70eSAdrian Vogelsgesang           .bind("negativeComparison"));
933696c70eSAdrian Vogelsgesang   AddSimpleMatcher(
94*3605d9a4SNicolas van Kempen       binaryOperation(hasLHS(Literal1), hasOperatorName(">"), hasRHS(CountCall))
953696c70eSAdrian Vogelsgesang           .bind("negativeComparison"));
963696c70eSAdrian Vogelsgesang 
973696c70eSAdrian Vogelsgesang   // Find membership tests based on `find() == end()`.
983696c70eSAdrian Vogelsgesang   AddSimpleMatcher(
99*3605d9a4SNicolas van Kempen       binaryOperation(hasOperatorName("!="), hasOperands(FindCall, EndCall))
1003696c70eSAdrian Vogelsgesang           .bind("positiveComparison"));
1013696c70eSAdrian Vogelsgesang   AddSimpleMatcher(
102*3605d9a4SNicolas van Kempen       binaryOperation(hasOperatorName("=="), hasOperands(FindCall, EndCall))
1033696c70eSAdrian Vogelsgesang           .bind("negativeComparison"));
1043696c70eSAdrian Vogelsgesang }
1053696c70eSAdrian Vogelsgesang 
1063696c70eSAdrian Vogelsgesang void ContainerContainsCheck::check(const MatchFinder::MatchResult &Result) {
1073696c70eSAdrian Vogelsgesang   // Extract the information about the match
1083696c70eSAdrian Vogelsgesang   const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>("call");
1093696c70eSAdrian Vogelsgesang   const auto *PositiveComparison =
1103696c70eSAdrian Vogelsgesang       Result.Nodes.getNodeAs<Expr>("positiveComparison");
1113696c70eSAdrian Vogelsgesang   const auto *NegativeComparison =
1123696c70eSAdrian Vogelsgesang       Result.Nodes.getNodeAs<Expr>("negativeComparison");
11307c66359SSam McCall   assert((!PositiveComparison || !NegativeComparison) &&
1143696c70eSAdrian Vogelsgesang          "only one of PositiveComparison or NegativeComparison should be set");
1153696c70eSAdrian Vogelsgesang   bool Negated = NegativeComparison != nullptr;
1163696c70eSAdrian Vogelsgesang   const auto *Comparison = Negated ? NegativeComparison : PositiveComparison;
1173696c70eSAdrian Vogelsgesang 
1183696c70eSAdrian Vogelsgesang   // Diagnose the issue.
1193696c70eSAdrian Vogelsgesang   auto Diag =
1203696c70eSAdrian Vogelsgesang       diag(Call->getExprLoc(), "use 'contains' to check for membership");
1213696c70eSAdrian Vogelsgesang 
1223696c70eSAdrian Vogelsgesang   // Don't fix it if it's in a macro invocation. Leave fixing it to the user.
1233696c70eSAdrian Vogelsgesang   SourceLocation FuncCallLoc = Comparison->getEndLoc();
1243696c70eSAdrian Vogelsgesang   if (!FuncCallLoc.isValid() || FuncCallLoc.isMacroID())
1253696c70eSAdrian Vogelsgesang     return;
1263696c70eSAdrian Vogelsgesang 
1273696c70eSAdrian Vogelsgesang   // Create the fix it.
1283696c70eSAdrian Vogelsgesang   const auto *Member = cast<MemberExpr>(Call->getCallee());
1293696c70eSAdrian Vogelsgesang   Diag << FixItHint::CreateReplacement(
1303696c70eSAdrian Vogelsgesang       Member->getMemberNameInfo().getSourceRange(), "contains");
1313696c70eSAdrian Vogelsgesang   SourceLocation ComparisonBegin = Comparison->getSourceRange().getBegin();
1323696c70eSAdrian Vogelsgesang   SourceLocation ComparisonEnd = Comparison->getSourceRange().getEnd();
1333696c70eSAdrian Vogelsgesang   SourceLocation CallBegin = Call->getSourceRange().getBegin();
1343696c70eSAdrian Vogelsgesang   SourceLocation CallEnd = Call->getSourceRange().getEnd();
1353696c70eSAdrian Vogelsgesang   Diag << FixItHint::CreateReplacement(
1363696c70eSAdrian Vogelsgesang       CharSourceRange::getCharRange(ComparisonBegin, CallBegin),
1373696c70eSAdrian Vogelsgesang       Negated ? "!" : "");
1388ac84a95SThomas Schenker   Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
1398ac84a95SThomas Schenker       CallEnd.getLocWithOffset(1), ComparisonEnd));
1403696c70eSAdrian Vogelsgesang }
1413696c70eSAdrian Vogelsgesang 
1427d2ea6c4SCarlos Galvez } // namespace clang::tidy::readability
143