| 1 | //===--- StringFindStrContainsCheck.cc - 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 | |
| 9 | #include "StringFindStrContainsCheck.h" |
| 10 | |
| 11 | #include "../utils/OptionsUtils.h" |
| 12 | #include "clang/AST/ASTContext.h" |
| 13 | #include "clang/ASTMatchers/ASTMatchers.h" |
| 14 | #include "clang/Tooling/Transformer/RewriteRule.h" |
| 15 | #include "clang/Tooling/Transformer/Stencil.h" |
| 16 | |
| 17 | // FixItHint - Hint to check documentation script to mark this check as |
| 18 | // providing a FixIt. |
| 19 | |
| 20 | using namespace clang::ast_matchers; |
| 21 | |
| 22 | namespace clang::tidy::abseil { |
| 23 | |
| 24 | using ::clang::transformer::addInclude; |
| 25 | using ::clang::transformer::applyFirst; |
| 26 | using ::clang::transformer::cat; |
| 27 | using ::clang::transformer::changeTo; |
| 28 | using ::clang::transformer::makeRule; |
| 29 | using ::clang::transformer::node; |
| 30 | using ::clang::transformer::RewriteRuleWith; |
| 31 | |
| 32 | namespace { |
| 33 | AST_MATCHER(Type, isCharType) { return Node.isCharType(); } |
| 34 | } // namespace |
| 35 | |
| 36 | static const char DefaultStringLikeClasses[] = "::std::basic_string;" |
| 37 | "::std::basic_string_view;" |
| 38 | "::absl::string_view" ; |
| 39 | static const char [] = "absl/strings/match.h" ; |
| 40 | |
| 41 | static transformer::RewriteRuleWith<std::string> |
| 42 | makeRewriteRule(ArrayRef<StringRef> StringLikeClassNames, |
| 43 | StringRef ) { |
| 44 | auto StringLikeClass = cxxRecordDecl(hasAnyName(StringLikeClassNames)); |
| 45 | auto StringType = |
| 46 | hasUnqualifiedDesugaredType(InnerMatcher: recordType(hasDeclaration(InnerMatcher: StringLikeClass))); |
| 47 | auto CharStarType = |
| 48 | hasUnqualifiedDesugaredType(InnerMatcher: pointerType(pointee(isAnyCharacter()))); |
| 49 | auto CharType = hasUnqualifiedDesugaredType(InnerMatcher: isCharType()); |
| 50 | auto StringNpos = declRefExpr( |
| 51 | to(InnerMatcher: varDecl(hasName(Name: "npos" ), hasDeclContext(InnerMatcher: StringLikeClass)))); |
| 52 | auto StringFind = cxxMemberCallExpr( |
| 53 | callee(InnerMatcher: cxxMethodDecl( |
| 54 | hasName(Name: "find" ), parameterCountIs(N: 2), |
| 55 | hasParameter( |
| 56 | N: 0, InnerMatcher: parmVarDecl(anyOf(hasType(InnerMatcher: StringType), hasType(InnerMatcher: CharStarType), |
| 57 | hasType(InnerMatcher: CharType)))))), |
| 58 | on(InnerMatcher: hasType(InnerMatcher: StringType)), hasArgument(N: 0, InnerMatcher: expr().bind(ID: "parameter_to_find" )), |
| 59 | anyOf(hasArgument(N: 1, InnerMatcher: integerLiteral(equals(Value: 0))), |
| 60 | hasArgument(N: 1, InnerMatcher: cxxDefaultArgExpr())), |
| 61 | onImplicitObjectArgument(InnerMatcher: expr().bind(ID: "string_being_searched" ))); |
| 62 | |
| 63 | RewriteRuleWith<std::string> Rule = applyFirst( |
| 64 | Rules: {makeRule( |
| 65 | M: binaryOperator(hasOperatorName(Name: "==" ), |
| 66 | hasOperands(Matcher1: ignoringParenImpCasts(InnerMatcher: StringNpos), |
| 67 | Matcher2: ignoringParenImpCasts(InnerMatcher: StringFind))), |
| 68 | Edits: {changeTo(Replacement: cat(Parts: "!absl::StrContains(" , Parts: node(ID: "string_being_searched" ), |
| 69 | Parts: ", " , Parts: node(ID: "parameter_to_find" ), Parts: ")" )), |
| 70 | addInclude(Header: AbseilStringsMatchHeader)}, |
| 71 | Metadata: cat(Parts: "use !absl::StrContains instead of find() == npos" )), |
| 72 | makeRule( |
| 73 | M: binaryOperator(hasOperatorName(Name: "!=" ), |
| 74 | hasOperands(Matcher1: ignoringParenImpCasts(InnerMatcher: StringNpos), |
| 75 | Matcher2: ignoringParenImpCasts(InnerMatcher: StringFind))), |
| 76 | Edits: {changeTo(Replacement: cat(Parts: "absl::StrContains(" , Parts: node(ID: "string_being_searched" ), |
| 77 | Parts: ", " , Parts: node(ID: "parameter_to_find" ), Parts: ")" )), |
| 78 | addInclude(Header: AbseilStringsMatchHeader)}, |
| 79 | Metadata: cat(Parts: "use absl::StrContains instead " |
| 80 | "of find() != npos" ))}); |
| 81 | return Rule; |
| 82 | } |
| 83 | |
| 84 | StringFindStrContainsCheck::StringFindStrContainsCheck( |
| 85 | StringRef Name, ClangTidyContext *Context) |
| 86 | : TransformerClangTidyCheck(Name, Context), |
| 87 | StringLikeClassesOption(utils::options::parseStringList( |
| 88 | Option: Options.get(LocalName: "StringLikeClasses" , Default: DefaultStringLikeClasses))), |
| 89 | AbseilStringsMatchHeaderOption(Options.get( |
| 90 | LocalName: "AbseilStringsMatchHeader" , Default: DefaultAbseilStringsMatchHeader)) { |
| 91 | setRule( |
| 92 | makeRewriteRule(StringLikeClassNames: StringLikeClassesOption, AbseilStringsMatchHeader: AbseilStringsMatchHeaderOption)); |
| 93 | } |
| 94 | |
| 95 | bool StringFindStrContainsCheck::isLanguageVersionSupported( |
| 96 | const LangOptions &LangOpts) const { |
| 97 | return LangOpts.CPlusPlus11; |
| 98 | } |
| 99 | |
| 100 | void StringFindStrContainsCheck::storeOptions( |
| 101 | ClangTidyOptions::OptionMap &Opts) { |
| 102 | TransformerClangTidyCheck::storeOptions(Opts); |
| 103 | Options.store(Options&: Opts, LocalName: "StringLikeClasses" , |
| 104 | Value: utils::options::serializeStringList(Strings: StringLikeClassesOption)); |
| 105 | Options.store(Options&: Opts, LocalName: "AbseilStringsMatchHeader" , |
| 106 | Value: AbseilStringsMatchHeaderOption); |
| 107 | } |
| 108 | |
| 109 | } // namespace clang::tidy::abseil |
| 110 | |