16
16
using namespace clang ::ast_matchers;
17
17
18
18
namespace clang ::tidy::modernize {
19
+ namespace {
20
+ // Given two argument indices X and Y, matches when a call expression has a
21
+ // string at index X with an expression representing that string's length at
22
+ // index Y. The string can be a string literal or a variable. The length can be
23
+ // matched via an integer literal or a call to strlen() in the case of a string
24
+ // literal, and by a call to size() or length() in the string variable case.
25
+ AST_POLYMORPHIC_MATCHER_P2 (hasStringAndLengthArguments,
26
+ AST_POLYMORPHIC_SUPPORTED_TYPES (
27
+ CallExpr, CXXConstructExpr,
28
+ CXXUnresolvedConstructExpr, ObjCMessageExpr),
29
+ unsigned, StringArgIndex, unsigned, LengthArgIndex) {
30
+ if (StringArgIndex >= Node.getNumArgs () ||
31
+ LengthArgIndex >= Node.getNumArgs ()) {
32
+ return false ;
33
+ }
34
+
35
+ const Expr *StringArgExpr =
36
+ Node.getArg (StringArgIndex)->IgnoreParenImpCasts ();
37
+ const Expr *LengthArgExpr =
38
+ Node.getArg (LengthArgIndex)->IgnoreParenImpCasts ();
39
+
40
+ if (const auto *StringArg = dyn_cast<StringLiteral>(StringArgExpr)) {
41
+ // Match an integer literal equal to the string length or a call to strlen.
42
+
43
+ static const auto Matcher = expr (anyOf (
44
+ integerLiteral ().bind (" integer_literal_size" ),
45
+ callExpr (callee (functionDecl (hasName (" strlen" ))), argumentCountIs (1 ),
46
+ hasArgument (0 , stringLiteral ().bind (" strlen_arg" )))));
47
+
48
+ if (!Matcher.matches (*LengthArgExpr, Finder, Builder)) {
49
+ return false ;
50
+ }
51
+
52
+ return Builder->removeBindings (
53
+ [&](const ast_matchers::internal::BoundNodesMap &Nodes) {
54
+ const auto *IntegerLiteralSize =
55
+ Nodes.getNodeAs <IntegerLiteral>(" integer_literal_size" );
56
+ const auto *StrlenArg = Nodes.getNodeAs <StringLiteral>(" strlen_arg" );
57
+ if (IntegerLiteralSize) {
58
+ return IntegerLiteralSize->getValue ().getZExtValue () !=
59
+ StringArg->getLength ();
60
+ }
61
+ return StrlenArg->getLength () != StringArg->getLength ();
62
+ });
63
+ }
64
+
65
+ if (const auto *StringArg = dyn_cast<DeclRefExpr>(StringArgExpr)) {
66
+ // Match a call to size() or length() on the same variable.
67
+
68
+ static const auto Matcher = cxxMemberCallExpr (
69
+ on (declRefExpr (to (varDecl ().bind (" string_var_decl" )))),
70
+ callee (cxxMethodDecl (hasAnyName (" size" , " length" ), isConst (),
71
+ parameterCountIs (0 ))));
72
+
73
+ if (!Matcher.matches (*LengthArgExpr, Finder, Builder)) {
74
+ return false ;
75
+ }
76
+
77
+ return Builder->removeBindings (
78
+ [&](const ast_matchers::internal::BoundNodesMap &Nodes) {
79
+ const auto *StringVarDecl =
80
+ Nodes.getNodeAs <VarDecl>(" string_var_decl" );
81
+ return StringVarDecl != StringArg->getDecl ();
82
+ });
83
+ }
84
+
85
+ return false ;
86
+ }
87
+ } // namespace
19
88
20
89
UseStartsEndsWithCheck::UseStartsEndsWithCheck (StringRef Name,
21
90
ClangTidyContext *Context)
@@ -43,7 +112,9 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
43
112
callee (cxxMethodDecl (hasName (" find" )).bind (" find_fun" )),
44
113
// ... on a class with a starts_with function.
45
114
on (hasType (
46
- hasCanonicalType (hasDeclaration (ClassWithStartsWithFunction)))));
115
+ hasCanonicalType (hasDeclaration (ClassWithStartsWithFunction)))),
116
+ // Bind search expression.
117
+ hasArgument (0 , expr ().bind (" search_expr" )));
47
118
48
119
const auto RFindExpr = cxxMemberCallExpr (
49
120
// A method call with a second argument of zero...
@@ -52,15 +123,30 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
52
123
callee (cxxMethodDecl (hasName (" rfind" )).bind (" find_fun" )),
53
124
// ... on a class with a starts_with function.
54
125
on (hasType (
55
- hasCanonicalType (hasDeclaration (ClassWithStartsWithFunction)))));
56
-
57
- const auto FindOrRFindExpr =
58
- cxxMemberCallExpr (anyOf (FindExpr, RFindExpr)).bind (" find_expr" );
126
+ hasCanonicalType (hasDeclaration (ClassWithStartsWithFunction)))),
127
+ // Bind search expression.
128
+ hasArgument (0 , expr ().bind (" search_expr" )));
129
+
130
+ const auto CompareExpr = cxxMemberCallExpr (
131
+ // A method call with a first argument of zero...
132
+ hasArgument (0 , ZeroLiteral),
133
+ // ... named compare...
134
+ callee (cxxMethodDecl (hasName (" compare" )).bind (" find_fun" )),
135
+ // ... on a class with a starts_with function...
136
+ on (hasType (
137
+ hasCanonicalType (hasDeclaration (ClassWithStartsWithFunction)))),
138
+ // ... where the third argument is some string and the second its length.
139
+ hasStringAndLengthArguments (2 , 1 ),
140
+ // Bind search expression.
141
+ hasArgument (2 , expr ().bind (" search_expr" )));
59
142
60
143
Finder->addMatcher (
61
- // Match [=!]= with a zero on one side and a string.(r?)find on the other.
62
- binaryOperator (hasAnyOperatorName (" ==" , " !=" ),
63
- hasOperands (FindOrRFindExpr, ZeroLiteral))
144
+ // Match [=!]= with a zero on one side and (r?)find|compare on the other.
145
+ binaryOperator (
146
+ hasAnyOperatorName (" ==" , " !=" ),
147
+ hasOperands (cxxMemberCallExpr (anyOf (FindExpr, RFindExpr, CompareExpr))
148
+ .bind (" find_expr" ),
149
+ ZeroLiteral))
64
150
.bind (" expr" ),
65
151
this );
66
152
}
@@ -69,6 +155,7 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
69
155
const auto *ComparisonExpr = Result.Nodes .getNodeAs <BinaryOperator>(" expr" );
70
156
const auto *FindExpr = Result.Nodes .getNodeAs <CXXMemberCallExpr>(" find_expr" );
71
157
const auto *FindFun = Result.Nodes .getNodeAs <CXXMethodDecl>(" find_fun" );
158
+ const auto *SearchExpr = Result.Nodes .getNodeAs <Expr>(" search_expr" );
72
159
const auto *StartsWithFunction =
73
160
Result.Nodes .getNodeAs <CXXMethodDecl>(" starts_with_fun" );
74
161
@@ -79,13 +166,13 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
79
166
const bool Neg = ComparisonExpr->getOpcode () == BO_NE;
80
167
81
168
auto Diagnostic =
82
- diag (FindExpr->getBeginLoc (), " use %0 instead of %1() %select{==|!=}2 0" )
169
+ diag (FindExpr->getExprLoc (), " use %0 instead of %1() %select{==|!=}2 0" )
83
170
<< StartsWithFunction->getName () << FindFun->getName () << Neg;
84
171
85
- // Remove possible zero second argument and ' [!=]= 0' suffix.
172
+ // Remove possible arguments after search expression and ' [!=]= 0' suffix.
86
173
Diagnostic << FixItHint::CreateReplacement (
87
174
CharSourceRange::getTokenRange (
88
- Lexer::getLocForEndOfToken (FindExpr-> getArg ( 0 ) ->getEndLoc (), 0 ,
175
+ Lexer::getLocForEndOfToken (SearchExpr ->getEndLoc (), 0 ,
89
176
*Result.SourceManager , getLangOpts ()),
90
177
ComparisonExpr->getEndLoc ()),
91
178
" )" );
@@ -94,11 +181,12 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
94
181
Diagnostic << FixItHint::CreateRemoval (CharSourceRange::getCharRange (
95
182
ComparisonExpr->getBeginLoc (), FindExpr->getBeginLoc ()));
96
183
97
- // Replace '(r?)find' with 'starts_with'.
184
+ // Replace method name by 'starts_with'.
185
+ // Remove possible arguments before search expression.
98
186
Diagnostic << FixItHint::CreateReplacement (
99
- CharSourceRange::getTokenRange (FindExpr->getExprLoc (),
100
- FindExpr-> getExprLoc ()),
101
- StartsWithFunction->getName ());
187
+ CharSourceRange::getCharRange (FindExpr->getExprLoc (),
188
+ SearchExpr-> getBeginLoc ()),
189
+ ( StartsWithFunction->getName () + " ( " ). str ());
102
190
103
191
// Add possible negation '!'.
104
192
if (Neg) {
0 commit comments