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
+ const auto Matcher = expr (anyOf (
43
+ integerLiteral (equals (StringArg->getLength ())),
44
+ callExpr (
45
+ callee (functionDecl (hasName (" strlen" ))), argumentCountIs (1 ),
46
+ hasArgument (0 , stringLiteral (hasSize (StringArg->getLength ()))))));
47
+ return Matcher.matches (*LengthArgExpr, Finder, Builder);
48
+ }
49
+
50
+ if (const auto *StringArg = dyn_cast<DeclRefExpr>(StringArgExpr)) {
51
+ // Match a call to size() or length() on the same variable.
52
+ const auto Matcher = cxxMemberCallExpr (
53
+ on (declRefExpr (to (varDecl (equalsNode (StringArg->getDecl ()))))),
54
+ callee (cxxMethodDecl (hasAnyName (" size" , " length" ), isConst (),
55
+ parameterCountIs (0 ))));
56
+ return Matcher.matches (*LengthArgExpr, Finder, Builder);
57
+ }
58
+
59
+ return false ;
60
+ }
61
+ } // namespace
19
62
20
63
UseStartsEndsWithCheck::UseStartsEndsWithCheck (StringRef Name,
21
64
ClangTidyContext *Context)
@@ -43,7 +86,9 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
43
86
callee (cxxMethodDecl (hasName (" find" )).bind (" find_fun" )),
44
87
// ... on a class with a starts_with function.
45
88
on (hasType (
46
- hasCanonicalType (hasDeclaration (ClassWithStartsWithFunction)))));
89
+ hasCanonicalType (hasDeclaration (ClassWithStartsWithFunction)))),
90
+ // Bind search expression.
91
+ hasArgument (0 , expr ().bind (" search_expr" )));
47
92
48
93
const auto RFindExpr = cxxMemberCallExpr (
49
94
// A method call with a second argument of zero...
@@ -52,15 +97,30 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
52
97
callee (cxxMethodDecl (hasName (" rfind" )).bind (" find_fun" )),
53
98
// ... on a class with a starts_with function.
54
99
on (hasType (
55
- hasCanonicalType (hasDeclaration (ClassWithStartsWithFunction)))));
56
-
57
- const auto FindOrRFindExpr =
58
- cxxMemberCallExpr (anyOf (FindExpr, RFindExpr)).bind (" find_expr" );
100
+ hasCanonicalType (hasDeclaration (ClassWithStartsWithFunction)))),
101
+ // Bind search expression.
102
+ hasArgument (0 , expr ().bind (" search_expr" )));
103
+
104
+ const auto CompareExpr = cxxMemberCallExpr (
105
+ // A method call with a first argument of zero...
106
+ hasArgument (0 , ZeroLiteral),
107
+ // ... named compare...
108
+ callee (cxxMethodDecl (hasName (" compare" )).bind (" find_fun" )),
109
+ // ... on a class with a starts_with function...
110
+ on (hasType (
111
+ hasCanonicalType (hasDeclaration (ClassWithStartsWithFunction)))),
112
+ // ... where the third argument is some string and the second its length.
113
+ HasStringAndLengthArguments (2 , 1 ),
114
+ // Bind search expression.
115
+ hasArgument (2 , expr ().bind (" search_expr" )));
59
116
60
117
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))
118
+ // Match [=!]= with a zero on one side and (r?)find|compare on the other.
119
+ binaryOperator (
120
+ hasAnyOperatorName (" ==" , " !=" ),
121
+ hasOperands (cxxMemberCallExpr (anyOf (FindExpr, RFindExpr, CompareExpr))
122
+ .bind (" find_expr" ),
123
+ ZeroLiteral))
64
124
.bind (" expr" ),
65
125
this );
66
126
}
@@ -69,6 +129,7 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
69
129
const auto *ComparisonExpr = Result.Nodes .getNodeAs <BinaryOperator>(" expr" );
70
130
const auto *FindExpr = Result.Nodes .getNodeAs <CXXMemberCallExpr>(" find_expr" );
71
131
const auto *FindFun = Result.Nodes .getNodeAs <CXXMethodDecl>(" find_fun" );
132
+ const auto *SearchExpr = Result.Nodes .getNodeAs <Expr>(" search_expr" );
72
133
const auto *StartsWithFunction =
73
134
Result.Nodes .getNodeAs <CXXMethodDecl>(" starts_with_fun" );
74
135
@@ -79,13 +140,13 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
79
140
const bool Neg = ComparisonExpr->getOpcode () == BO_NE;
80
141
81
142
auto Diagnostic =
82
- diag (FindExpr->getBeginLoc (), " use %0 instead of %1() %select{==|!=}2 0" )
143
+ diag (FindExpr->getExprLoc (), " use %0 instead of %1() %select{==|!=}2 0" )
83
144
<< StartsWithFunction->getName () << FindFun->getName () << Neg;
84
145
85
- // Remove possible zero second argument and ' [!=]= 0' suffix.
146
+ // Remove possible arguments after search expression and ' [!=]= 0' suffix.
86
147
Diagnostic << FixItHint::CreateReplacement (
87
148
CharSourceRange::getTokenRange (
88
- Lexer::getLocForEndOfToken (FindExpr-> getArg ( 0 ) ->getEndLoc (), 0 ,
149
+ Lexer::getLocForEndOfToken (SearchExpr ->getEndLoc (), 0 ,
89
150
*Result.SourceManager , getLangOpts ()),
90
151
ComparisonExpr->getEndLoc ()),
91
152
" )" );
@@ -94,11 +155,12 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
94
155
Diagnostic << FixItHint::CreateRemoval (CharSourceRange::getCharRange (
95
156
ComparisonExpr->getBeginLoc (), FindExpr->getBeginLoc ()));
96
157
97
- // Replace '(r?)find' with 'starts_with'.
158
+ // Replace method name by 'starts_with'.
159
+ // Remove possible arguments before search expression.
98
160
Diagnostic << FixItHint::CreateReplacement (
99
- CharSourceRange::getTokenRange (FindExpr->getExprLoc (),
100
- FindExpr-> getExprLoc ()),
101
- StartsWithFunction->getName () );
161
+ CharSourceRange::getCharRange (FindExpr->getExprLoc (),
162
+ SearchExpr-> getBeginLoc ()),
163
+ StartsWithFunction->getNameAsString () + " ( " );
102
164
103
165
// Add possible negation '!'.
104
166
if (Neg) {
0 commit comments