9
9
#include " ContainerContainsCheck.h"
10
10
#include " clang/AST/ASTContext.h"
11
11
#include " clang/ASTMatchers/ASTMatchFinder.h"
12
+ #include " clang/Lex/Lexer.h"
12
13
13
14
using namespace clang ::ast_matchers;
14
15
15
16
namespace clang ::tidy::readability {
16
17
void ContainerContainsCheck::registerMatchers (MatchFinder *Finder) {
17
- const auto HasContainsMatchingParamType = hasMethod (
18
- cxxMethodDecl (isConst (), parameterCountIs (1 ), returns (booleanType ()),
19
- hasName (" contains" ), unless (isDeleted ()), isPublic (),
20
- hasParameter (0 , hasType (hasUnqualifiedDesugaredType (
21
- equalsBoundNode (" parameterType" ))))));
18
+ const auto Literal0 = integerLiteral (equals (0 ));
19
+ const auto Literal1 = integerLiteral (equals (1 ));
20
+
21
+ const auto ClassWithContains = cxxRecordDecl (
22
+ hasMethod (cxxMethodDecl (isConst (), parameterCountIs (1 ), isPublic (),
23
+ unless (isDeleted ()), returns (booleanType ()),
24
+ hasAnyName (" contains" , " Contains" ))
25
+ .bind (" contains_fun" )));
22
26
23
27
const auto CountCall =
24
- cxxMemberCallExpr (
25
- argumentCountIs (1 ),
26
- callee (cxxMethodDecl (
27
- hasName (" count" ),
28
- hasParameter (0 , hasType (hasUnqualifiedDesugaredType (
29
- type ().bind (" parameterType" )))),
30
- ofClass (cxxRecordDecl (HasContainsMatchingParamType)))))
28
+ cxxMemberCallExpr (argumentCountIs (1 ),
29
+ callee (cxxMethodDecl (hasAnyName (" count" , " Count" ),
30
+ ofClass (ClassWithContains))))
31
31
.bind (" call" );
32
32
33
33
const auto FindCall =
34
+ // Either one argument, or assume the second argument is the position to
35
+ // start searching from.
34
36
cxxMemberCallExpr (
35
- argumentCountIs (1 ),
36
- callee (cxxMethodDecl (
37
- hasName (" find" ),
38
- hasParameter (0 , hasType (hasUnqualifiedDesugaredType (
39
- type ().bind (" parameterType" )))),
40
- ofClass (cxxRecordDecl (HasContainsMatchingParamType)))))
37
+ anyOf (argumentCountIs (1 ),
38
+ allOf (argumentCountIs (2 ), hasArgument (1 , Literal0))),
39
+ callee (cxxMethodDecl (hasAnyName (" find" , " Find" ),
40
+ ofClass (ClassWithContains))))
41
41
.bind (" call" );
42
42
43
43
const auto EndCall = cxxMemberCallExpr (
44
- argumentCountIs (0 ),
45
- callee (
46
- cxxMethodDecl (hasName (" end" ),
47
- // In the matchers below, FindCall should always appear
48
- // before EndCall so 'parameterType' is properly bound.
49
- ofClass (cxxRecordDecl (HasContainsMatchingParamType)))));
44
+ argumentCountIs (0 ), callee (cxxMethodDecl (hasAnyName (" end" , " End" ),
45
+ ofClass (ClassWithContains))));
50
46
51
- const auto Literal0 = integerLiteral ( equals ( 0 ));
52
- const auto Literal1 = integerLiteral ( equals ( 1 ));
47
+ const auto StringNpos = anyOf ( declRefExpr ( to ( varDecl ( hasName ( " npos " )))),
48
+ memberExpr ( member ( hasName ( " npos " )) ));
53
49
54
50
auto AddSimpleMatcher = [&](auto Matcher) {
55
51
Finder->addMatcher (
@@ -94,12 +90,14 @@ void ContainerContainsCheck::registerMatchers(MatchFinder *Finder) {
94
90
binaryOperation (hasLHS (Literal1), hasOperatorName (" >" ), hasRHS (CountCall))
95
91
.bind (" negativeComparison" ));
96
92
97
- // Find membership tests based on `find() == end()`.
93
+ // Find membership tests based on `find() == end()` or `find() == npos` .
98
94
AddSimpleMatcher (
99
- binaryOperation (hasOperatorName (" !=" ), hasOperands (FindCall, EndCall))
95
+ binaryOperation (hasOperatorName (" !=" ),
96
+ hasOperands (FindCall, anyOf (EndCall, StringNpos)))
100
97
.bind (" positiveComparison" ));
101
98
AddSimpleMatcher (
102
- binaryOperation (hasOperatorName (" ==" ), hasOperands (FindCall, EndCall))
99
+ binaryOperation (hasOperatorName (" ==" ),
100
+ hasOperands (FindCall, anyOf (EndCall, StringNpos)))
103
101
.bind (" negativeComparison" ));
104
102
}
105
103
@@ -114,29 +112,39 @@ void ContainerContainsCheck::check(const MatchFinder::MatchResult &Result) {
114
112
" only one of PositiveComparison or NegativeComparison should be set" );
115
113
bool Negated = NegativeComparison != nullptr ;
116
114
const auto *Comparison = Negated ? NegativeComparison : PositiveComparison;
115
+ const StringRef ContainsFunName =
116
+ Result.Nodes .getNodeAs <CXXMethodDecl>(" contains_fun" )->getName ();
117
+ const Expr *SearchExpr = Call->getArg (0 )->IgnoreParenImpCasts ();
117
118
118
119
// Diagnose the issue.
119
- auto Diag =
120
- diag (Call-> getExprLoc (), " use 'contains' to check for membership " ) ;
120
+ auto Diag = diag (Call-> getExprLoc (), " use '%0' to check for membership " )
121
+ << ContainsFunName ;
121
122
122
123
// Don't fix it if it's in a macro invocation. Leave fixing it to the user.
123
124
SourceLocation FuncCallLoc = Comparison->getEndLoc ();
124
125
if (!FuncCallLoc.isValid () || FuncCallLoc.isMacroID ())
125
126
return ;
126
127
127
- // Create the fix it.
128
- const auto *Member = cast<MemberExpr>(Call->getCallee ());
129
- Diag << FixItHint::CreateReplacement (
130
- Member->getMemberNameInfo ().getSourceRange (), " contains" );
131
- SourceLocation ComparisonBegin = Comparison->getSourceRange ().getBegin ();
132
- SourceLocation ComparisonEnd = Comparison->getSourceRange ().getEnd ();
133
- SourceLocation CallBegin = Call->getSourceRange ().getBegin ();
134
- SourceLocation CallEnd = Call->getSourceRange ().getEnd ();
128
+ const StringRef SearchExprText = Lexer::getSourceText (
129
+ CharSourceRange::getTokenRange (SearchExpr->getSourceRange ()),
130
+ *Result.SourceManager , Result.Context ->getLangOpts ());
131
+
132
+ // Remove everything before the function call.
133
+ Diag << FixItHint::CreateRemoval (CharSourceRange::getCharRange (
134
+ Comparison->getBeginLoc (), Call->getBeginLoc ()));
135
+
136
+ // Rename the function to `contains`.
137
+ Diag << FixItHint::CreateReplacement (Call->getExprLoc (), ContainsFunName);
138
+
139
+ // Replace arguments and everything after the function call.
135
140
Diag << FixItHint::CreateReplacement (
136
- CharSourceRange::getCharRange (ComparisonBegin, CallBegin),
137
- Negated ? " !" : " " );
138
- Diag << FixItHint::CreateRemoval (CharSourceRange::getTokenRange (
139
- CallEnd.getLocWithOffset (1 ), ComparisonEnd));
141
+ CharSourceRange::getTokenRange (Call->getArg (0 )->getBeginLoc (),
142
+ Comparison->getEndLoc ()),
143
+ (SearchExprText + " )" ).str ());
144
+
145
+ // Add negation if necessary.
146
+ if (Negated)
147
+ Diag << FixItHint::CreateInsertion (Call->getBeginLoc (), " !" );
140
148
}
141
149
142
150
} // namespace clang::tidy::readability
0 commit comments