ContainerContainsCheck.cpp 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. //===--- ContainerContainsCheck.cpp - clang-tidy --------------------------===//
  2. //
  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
  6. //
  7. //===----------------------------------------------------------------------===//
  8. #include "ContainerContainsCheck.h"
  9. #include "clang/AST/ASTContext.h"
  10. #include "clang/ASTMatchers/ASTMatchFinder.h"
  11. using namespace clang::ast_matchers;
  12. namespace clang::tidy::readability {
  13. void ContainerContainsCheck::registerMatchers(MatchFinder *Finder) {
  14. const auto SupportedContainers = hasType(
  15. hasUnqualifiedDesugaredType(recordType(hasDeclaration(cxxRecordDecl(
  16. hasAnyName("::std::set", "::std::unordered_set", "::std::map",
  17. "::std::unordered_map", "::std::multiset",
  18. "::std::unordered_multiset", "::std::multimap",
  19. "::std::unordered_multimap"))))));
  20. const auto CountCall =
  21. cxxMemberCallExpr(on(SupportedContainers),
  22. callee(cxxMethodDecl(hasName("count"))),
  23. argumentCountIs(1))
  24. .bind("call");
  25. const auto FindCall =
  26. cxxMemberCallExpr(on(SupportedContainers),
  27. callee(cxxMethodDecl(hasName("find"))),
  28. argumentCountIs(1))
  29. .bind("call");
  30. const auto EndCall = cxxMemberCallExpr(on(SupportedContainers),
  31. callee(cxxMethodDecl(hasName("end"))),
  32. argumentCountIs(0));
  33. const auto Literal0 = integerLiteral(equals(0));
  34. const auto Literal1 = integerLiteral(equals(1));
  35. auto AddSimpleMatcher = [&](auto Matcher) {
  36. Finder->addMatcher(
  37. traverse(TK_IgnoreUnlessSpelledInSource, std::move(Matcher)), this);
  38. };
  39. // Find membership tests which use `count()`.
  40. Finder->addMatcher(implicitCastExpr(hasImplicitDestinationType(booleanType()),
  41. hasSourceExpression(CountCall))
  42. .bind("positiveComparison"),
  43. this);
  44. AddSimpleMatcher(
  45. binaryOperator(hasLHS(CountCall), hasOperatorName("!="), hasRHS(Literal0))
  46. .bind("positiveComparison"));
  47. AddSimpleMatcher(
  48. binaryOperator(hasLHS(Literal0), hasOperatorName("!="), hasRHS(CountCall))
  49. .bind("positiveComparison"));
  50. AddSimpleMatcher(
  51. binaryOperator(hasLHS(CountCall), hasOperatorName(">"), hasRHS(Literal0))
  52. .bind("positiveComparison"));
  53. AddSimpleMatcher(
  54. binaryOperator(hasLHS(Literal0), hasOperatorName("<"), hasRHS(CountCall))
  55. .bind("positiveComparison"));
  56. AddSimpleMatcher(
  57. binaryOperator(hasLHS(CountCall), hasOperatorName(">="), hasRHS(Literal1))
  58. .bind("positiveComparison"));
  59. AddSimpleMatcher(
  60. binaryOperator(hasLHS(Literal1), hasOperatorName("<="), hasRHS(CountCall))
  61. .bind("positiveComparison"));
  62. // Find inverted membership tests which use `count()`.
  63. AddSimpleMatcher(
  64. binaryOperator(hasLHS(CountCall), hasOperatorName("=="), hasRHS(Literal0))
  65. .bind("negativeComparison"));
  66. AddSimpleMatcher(
  67. binaryOperator(hasLHS(Literal0), hasOperatorName("=="), hasRHS(CountCall))
  68. .bind("negativeComparison"));
  69. AddSimpleMatcher(
  70. binaryOperator(hasLHS(CountCall), hasOperatorName("<="), hasRHS(Literal0))
  71. .bind("negativeComparison"));
  72. AddSimpleMatcher(
  73. binaryOperator(hasLHS(Literal0), hasOperatorName(">="), hasRHS(CountCall))
  74. .bind("negativeComparison"));
  75. AddSimpleMatcher(
  76. binaryOperator(hasLHS(CountCall), hasOperatorName("<"), hasRHS(Literal1))
  77. .bind("negativeComparison"));
  78. AddSimpleMatcher(
  79. binaryOperator(hasLHS(Literal1), hasOperatorName(">"), hasRHS(CountCall))
  80. .bind("negativeComparison"));
  81. // Find membership tests based on `find() == end()`.
  82. AddSimpleMatcher(
  83. binaryOperator(hasLHS(FindCall), hasOperatorName("!="), hasRHS(EndCall))
  84. .bind("positiveComparison"));
  85. AddSimpleMatcher(
  86. binaryOperator(hasLHS(FindCall), hasOperatorName("=="), hasRHS(EndCall))
  87. .bind("negativeComparison"));
  88. }
  89. void ContainerContainsCheck::check(const MatchFinder::MatchResult &Result) {
  90. // Extract the information about the match
  91. const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>("call");
  92. const auto *PositiveComparison =
  93. Result.Nodes.getNodeAs<Expr>("positiveComparison");
  94. const auto *NegativeComparison =
  95. Result.Nodes.getNodeAs<Expr>("negativeComparison");
  96. assert((!PositiveComparison || !NegativeComparison) &&
  97. "only one of PositiveComparison or NegativeComparison should be set");
  98. bool Negated = NegativeComparison != nullptr;
  99. const auto *Comparison = Negated ? NegativeComparison : PositiveComparison;
  100. // Diagnose the issue.
  101. auto Diag =
  102. diag(Call->getExprLoc(), "use 'contains' to check for membership");
  103. // Don't fix it if it's in a macro invocation. Leave fixing it to the user.
  104. SourceLocation FuncCallLoc = Comparison->getEndLoc();
  105. if (!FuncCallLoc.isValid() || FuncCallLoc.isMacroID())
  106. return;
  107. // Create the fix it.
  108. const auto *Member = cast<MemberExpr>(Call->getCallee());
  109. Diag << FixItHint::CreateReplacement(
  110. Member->getMemberNameInfo().getSourceRange(), "contains");
  111. SourceLocation ComparisonBegin = Comparison->getSourceRange().getBegin();
  112. SourceLocation ComparisonEnd = Comparison->getSourceRange().getEnd();
  113. SourceLocation CallBegin = Call->getSourceRange().getBegin();
  114. SourceLocation CallEnd = Call->getSourceRange().getEnd();
  115. Diag << FixItHint::CreateReplacement(
  116. CharSourceRange::getCharRange(ComparisonBegin, CallBegin),
  117. Negated ? "!" : "");
  118. Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
  119. CallEnd.getLocWithOffset(1), ComparisonEnd.getLocWithOffset(1)));
  120. }
  121. } // namespace clang::tidy::readability