1 //===--- UseStartsEndsWithCheck.cpp - clang-tidy --------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "UseStartsEndsWithCheck.h"
11 #include "../utils/ASTUtils.h"
12 #include "../utils/Matchers.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/Lex/Lexer.h"
18 using namespace clang::ast_matchers
;
20 namespace clang::tidy::modernize
{
21 struct NotLengthExprForStringNode
{
22 NotLengthExprForStringNode(std::string ID
, DynTypedNode Node
,
24 : ID(std::move(ID
)), Node(std::move(Node
)), Context(Context
) {}
25 bool operator()(const internal::BoundNodesMap
&Nodes
) const {
26 // Match a string literal and an integer size or strlen() call.
27 if (const auto *StringLiteralNode
= Nodes
.getNodeAs
<StringLiteral
>(ID
)) {
28 if (const auto *IntegerLiteralSizeNode
= Node
.get
<IntegerLiteral
>()) {
29 return StringLiteralNode
->getLength() !=
30 IntegerLiteralSizeNode
->getValue().getZExtValue();
33 if (const auto *StrlenNode
= Node
.get
<CallExpr
>()) {
34 if (StrlenNode
->getDirectCallee()->getName() != "strlen" ||
35 StrlenNode
->getNumArgs() != 1) {
39 if (const auto *StrlenArgNode
= dyn_cast
<StringLiteral
>(
40 StrlenNode
->getArg(0)->IgnoreParenImpCasts())) {
41 return StrlenArgNode
->getLength() != StringLiteralNode
->getLength();
46 // Match a string variable and a call to length() or size().
47 if (const auto *ExprNode
= Nodes
.getNodeAs
<Expr
>(ID
)) {
48 if (const auto *MemberCallNode
= Node
.get
<CXXMemberCallExpr
>()) {
49 const CXXMethodDecl
*MethodDeclNode
= MemberCallNode
->getMethodDecl();
50 const StringRef Name
= MethodDeclNode
->getName();
51 if (!MethodDeclNode
->isConst() || MethodDeclNode
->getNumParams() != 0 ||
52 (Name
!= "size" && Name
!= "length")) {
56 if (const auto *OnNode
=
57 dyn_cast
<Expr
>(MemberCallNode
->getImplicitObjectArgument())) {
58 return !utils::areStatementsIdentical(OnNode
->IgnoreParenImpCasts(),
59 ExprNode
->IgnoreParenImpCasts(),
74 AST_MATCHER_P(Expr
, lengthExprForStringNode
, std::string
, ID
) {
75 return Builder
->removeBindings(NotLengthExprForStringNode(
76 ID
, DynTypedNode::create(Node
), &(Finder
->getASTContext())));
79 UseStartsEndsWithCheck::UseStartsEndsWithCheck(StringRef Name
,
80 ClangTidyContext
*Context
)
81 : ClangTidyCheck(Name
, Context
) {}
83 void UseStartsEndsWithCheck::registerMatchers(MatchFinder
*Finder
) {
84 const auto ZeroLiteral
= integerLiteral(equals(0));
86 const auto ClassTypeWithMethod
= [](const StringRef MethodBoundName
,
87 const auto... Methods
) {
88 return cxxRecordDecl(anyOf(
89 hasMethod(cxxMethodDecl(isConst(), parameterCountIs(1),
90 returns(booleanType()), hasAnyName(Methods
))
91 .bind(MethodBoundName
))...));
94 const auto OnClassWithStartsWithFunction
=
95 ClassTypeWithMethod("starts_with_fun", "starts_with", "startsWith",
96 "startswith", "StartsWith");
98 const auto OnClassWithEndsWithFunction
= ClassTypeWithMethod(
99 "ends_with_fun", "ends_with", "endsWith", "endswith", "EndsWith");
101 // Case 1: X.find(Y) [!=]= 0 -> starts_with.
102 const auto FindExpr
= cxxMemberCallExpr(
103 anyOf(argumentCountIs(1), hasArgument(1, ZeroLiteral
)),
105 cxxMethodDecl(hasName("find"), ofClass(OnClassWithStartsWithFunction
))
107 hasArgument(0, expr().bind("needle")));
109 // Case 2: X.rfind(Y, 0) [!=]= 0 -> starts_with.
110 const auto RFindExpr
= cxxMemberCallExpr(
111 hasArgument(1, ZeroLiteral
),
112 callee(cxxMethodDecl(hasName("rfind"),
113 ofClass(OnClassWithStartsWithFunction
))
115 hasArgument(0, expr().bind("needle")));
117 // Case 3: X.compare(0, LEN(Y), Y) [!=]= 0 -> starts_with.
118 const auto CompareExpr
= cxxMemberCallExpr(
119 argumentCountIs(3), hasArgument(0, ZeroLiteral
),
120 callee(cxxMethodDecl(hasName("compare"),
121 ofClass(OnClassWithStartsWithFunction
))
123 hasArgument(2, expr().bind("needle")),
124 hasArgument(1, lengthExprForStringNode("needle")));
126 // Case 4: X.compare(LEN(X) - LEN(Y), LEN(Y), Y) [!=]= 0 -> ends_with.
127 const auto CompareEndsWithExpr
= cxxMemberCallExpr(
129 callee(cxxMethodDecl(hasName("compare"),
130 ofClass(OnClassWithEndsWithFunction
))
132 on(expr().bind("haystack")), hasArgument(2, expr().bind("needle")),
133 hasArgument(1, lengthExprForStringNode("needle")),
135 binaryOperator(hasOperatorName("-"),
136 hasLHS(lengthExprForStringNode("haystack")),
137 hasRHS(lengthExprForStringNode("needle")))));
139 // All cases comparing to 0.
142 matchers::isEqualityOperator(),
143 hasOperands(cxxMemberCallExpr(anyOf(FindExpr
, RFindExpr
, CompareExpr
,
144 CompareEndsWithExpr
))
150 // Case 5: X.rfind(Y) [!=]= LEN(X) - LEN(Y) -> ends_with.
153 matchers::isEqualityOperator(),
158 allOf(argumentCountIs(2),
161 anyOf(declRefExpr(to(varDecl(hasName("npos")))),
162 memberExpr(member(hasName("npos"))))))),
163 callee(cxxMethodDecl(hasName("rfind"),
164 ofClass(OnClassWithEndsWithFunction
))
166 on(expr().bind("haystack")),
167 hasArgument(0, expr().bind("needle")))
169 binaryOperator(hasOperatorName("-"),
170 hasLHS(lengthExprForStringNode("haystack")),
171 hasRHS(lengthExprForStringNode("needle")))))
176 void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult
&Result
) {
177 const auto *ComparisonExpr
= Result
.Nodes
.getNodeAs
<BinaryOperator
>("expr");
178 const auto *FindExpr
= Result
.Nodes
.getNodeAs
<CXXMemberCallExpr
>("find_expr");
179 const auto *FindFun
= Result
.Nodes
.getNodeAs
<CXXMethodDecl
>("find_fun");
180 const auto *SearchExpr
= Result
.Nodes
.getNodeAs
<Expr
>("needle");
181 const auto *StartsWithFunction
=
182 Result
.Nodes
.getNodeAs
<CXXMethodDecl
>("starts_with_fun");
183 const auto *EndsWithFunction
=
184 Result
.Nodes
.getNodeAs
<CXXMethodDecl
>("ends_with_fun");
185 assert(bool(StartsWithFunction
) != bool(EndsWithFunction
));
186 const CXXMethodDecl
*ReplacementFunction
=
187 StartsWithFunction
? StartsWithFunction
: EndsWithFunction
;
189 if (ComparisonExpr
->getBeginLoc().isMacroID())
192 const bool Neg
= ComparisonExpr
->getOpcode() == BO_NE
;
195 diag(FindExpr
->getExprLoc(), "use %0 instead of %1() %select{==|!=}2 0")
196 << ReplacementFunction
->getName() << FindFun
->getName() << Neg
;
198 // Remove possible arguments after search expression and ' [!=]= .+' suffix.
199 Diagnostic
<< FixItHint::CreateReplacement(
200 CharSourceRange::getTokenRange(
201 Lexer::getLocForEndOfToken(SearchExpr
->getEndLoc(), 0,
202 *Result
.SourceManager
, getLangOpts()),
203 ComparisonExpr
->getEndLoc()),
206 // Remove possible '.+ [!=]= ' prefix.
207 Diagnostic
<< FixItHint::CreateRemoval(CharSourceRange::getCharRange(
208 ComparisonExpr
->getBeginLoc(), FindExpr
->getBeginLoc()));
210 // Replace method name by '(starts|ends)_with'.
211 // Remove possible arguments before search expression.
212 Diagnostic
<< FixItHint::CreateReplacement(
213 CharSourceRange::getCharRange(FindExpr
->getExprLoc(),
214 SearchExpr
->getBeginLoc()),
215 (ReplacementFunction
->getName() + "(").str());
217 // Add possible negation '!'.
219 Diagnostic
<< FixItHint::CreateInsertion(FindExpr
->getBeginLoc(), "!");
222 } // namespace clang::tidy::modernize