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