99#include " ContainerContainsCheck.h"
1010#include " clang/AST/ASTContext.h"
1111#include " clang/ASTMatchers/ASTMatchFinder.h"
12+ #include " clang/Lex/Lexer.h"
1213
1314using namespace clang ::ast_matchers;
1415
1516namespace clang ::tidy::readability {
1617void 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" )));
2226
2327 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))))
3131 .bind (" call" );
3232
3333 const auto FindCall =
34+ // Either one argument, or assume the second argument is the position to
35+ // start searching from.
3436 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))))
4141 .bind (" call" );
4242
4343 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))));
5046
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 " )) ));
5349
5450 auto AddSimpleMatcher = [&](auto Matcher) {
5551 Finder->addMatcher (
@@ -94,12 +90,14 @@ void ContainerContainsCheck::registerMatchers(MatchFinder *Finder) {
9490 binaryOperation (hasLHS (Literal1), hasOperatorName (" >" ), hasRHS (CountCall))
9591 .bind (" negativeComparison" ));
9692
97- // Find membership tests based on `find() == end()`.
93+ // Find membership tests based on `find() == end()` or `find() == npos` .
9894 AddSimpleMatcher (
99- binaryOperation (hasOperatorName (" !=" ), hasOperands (FindCall, EndCall))
95+ binaryOperation (hasOperatorName (" !=" ),
96+ hasOperands (FindCall, anyOf (EndCall, StringNpos)))
10097 .bind (" positiveComparison" ));
10198 AddSimpleMatcher (
102- binaryOperation (hasOperatorName (" ==" ), hasOperands (FindCall, EndCall))
99+ binaryOperation (hasOperatorName (" ==" ),
100+ hasOperands (FindCall, anyOf (EndCall, StringNpos)))
103101 .bind (" negativeComparison" ));
104102}
105103
@@ -114,29 +112,39 @@ void ContainerContainsCheck::check(const MatchFinder::MatchResult &Result) {
114112 " only one of PositiveComparison or NegativeComparison should be set" );
115113 bool Negated = NegativeComparison != nullptr ;
116114 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 ();
117118
118119 // 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 ;
121122
122123 // Don't fix it if it's in a macro invocation. Leave fixing it to the user.
123124 SourceLocation FuncCallLoc = Comparison->getEndLoc ();
124125 if (!FuncCallLoc.isValid () || FuncCallLoc.isMacroID ())
125126 return ;
126127
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.
135140 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 (), " !" );
140148}
141149
142150} // namespace clang::tidy::readability
0 commit comments