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