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 | |