Skip to content

Commit cd6de85

Browse files
committed
[clang-tidy] rewrite matchers in modernize-use-starts-ends-with
Rewrite the AST matchers for slightly more composability and performance. Furthermore, check that the `starts_with` and `ends_with` functions return a `bool`. There is one behavioral change, in that the methods of a class (and transitive classes) are searched once for a matching `starts_with`/`ends_with` function, picking the first it can find. Previously, the matchers would try to find `starts_with`, then `startsWith`, and finally, `startswith`. Now, the first of the three that is encountered will be the matched method.
1 parent 6fd229a commit cd6de85

File tree

2 files changed

+27
-36
lines changed

2 files changed

+27
-36
lines changed

clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
#include "UseStartsEndsWithCheck.h"
1010

1111
#include "../utils/ASTUtils.h"
12-
#include "../utils/OptionsUtils.h"
12+
#include "../utils/Matchers.h"
13+
#include "clang/ASTMatchers/ASTMatchers.h"
1314
#include "clang/Lex/Lexer.h"
15+
#include "llvm/ADT/ArrayRef.h"
1416

1517
#include <string>
1618

@@ -82,34 +84,25 @@ UseStartsEndsWithCheck::UseStartsEndsWithCheck(StringRef Name,
8284
void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
8385
const auto ZeroLiteral = integerLiteral(equals(0));
8486

85-
const auto HasStartsWithMethodWithName = [](const std::string &Name) {
86-
return hasMethod(
87-
cxxMethodDecl(hasName(Name), isConst(), parameterCountIs(1))
88-
.bind("starts_with_fun"));
89-
};
90-
const auto HasStartsWithMethod =
91-
anyOf(HasStartsWithMethodWithName("starts_with"),
92-
HasStartsWithMethodWithName("startsWith"),
93-
HasStartsWithMethodWithName("startswith"));
94-
const auto OnClassWithStartsWithFunction =
95-
on(hasType(hasCanonicalType(hasDeclaration(cxxRecordDecl(
96-
anyOf(HasStartsWithMethod,
97-
hasAnyBase(hasType(hasCanonicalType(
98-
hasDeclaration(cxxRecordDecl(HasStartsWithMethod)))))))))));
99-
100-
const auto HasEndsWithMethodWithName = [](const std::string &Name) {
101-
return hasMethod(
102-
cxxMethodDecl(hasName(Name), isConst(), parameterCountIs(1))
103-
.bind("ends_with_fun"));
104-
};
105-
const auto HasEndsWithMethod = anyOf(HasEndsWithMethodWithName("ends_with"),
106-
HasEndsWithMethodWithName("endsWith"),
107-
HasEndsWithMethodWithName("endswith"));
87+
const auto ClassTypeWithMethod =
88+
[](const StringRef MethodBoundName,
89+
const llvm::ArrayRef<StringRef> &Methods) {
90+
const auto Method =
91+
cxxMethodDecl(isConst(), parameterCountIs(1),
92+
returns(booleanType()), hasAnyName(Methods))
93+
.bind(MethodBoundName);
94+
return qualType(hasCanonicalType(hasDeclaration(cxxRecordDecl(
95+
anyOf(hasMethod(Method),
96+
hasAnyBase(hasType(hasCanonicalType(
97+
hasDeclaration(cxxRecordDecl(hasMethod(Method)))))))))));
98+
};
99+
100+
const auto OnClassWithStartsWithFunction = on(hasType(ClassTypeWithMethod(
101+
"starts_with_fun", {"starts_with", "startsWith", "startswith"})));
102+
108103
const auto OnClassWithEndsWithFunction =
109-
on(expr(hasType(hasCanonicalType(hasDeclaration(cxxRecordDecl(
110-
anyOf(HasEndsWithMethod,
111-
hasAnyBase(hasType(hasCanonicalType(hasDeclaration(
112-
cxxRecordDecl(HasEndsWithMethod)))))))))))
104+
on(expr(hasType(ClassTypeWithMethod(
105+
"ends_with_fun", {"ends_with", "endsWith", "endswith"})))
113106
.bind("haystack"));
114107

115108
// Case 1: X.find(Y) [!=]= 0 -> starts_with.
@@ -145,7 +138,7 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
145138
// All cases comparing to 0.
146139
Finder->addMatcher(
147140
binaryOperator(
148-
hasAnyOperatorName("==", "!="),
141+
matchers::isEqualityOperator(),
149142
hasOperands(cxxMemberCallExpr(anyOf(FindExpr, RFindExpr, CompareExpr,
150143
CompareEndsWithExpr))
151144
.bind("find_expr"),
@@ -156,7 +149,7 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
156149
// Case 5: X.rfind(Y) [!=]= LEN(X) - LEN(Y) -> ends_with.
157150
Finder->addMatcher(
158151
binaryOperator(
159-
hasAnyOperatorName("==", "!="),
152+
matchers::isEqualityOperator(),
160153
hasOperands(
161154
cxxMemberCallExpr(
162155
anyOf(
@@ -190,9 +183,8 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
190183
const CXXMethodDecl *ReplacementFunction =
191184
StartsWithFunction ? StartsWithFunction : EndsWithFunction;
192185

193-
if (ComparisonExpr->getBeginLoc().isMacroID()) {
186+
if (ComparisonExpr->getBeginLoc().isMacroID())
194187
return;
195-
}
196188

197189
const bool Neg = ComparisonExpr->getOpcode() == BO_NE;
198190

@@ -220,9 +212,8 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
220212
(ReplacementFunction->getName() + "(").str());
221213

222214
// Add possible negation '!'.
223-
if (Neg) {
215+
if (Neg)
224216
Diagnostic << FixItHint::CreateInsertion(FindExpr->getBeginLoc(), "!");
225-
}
226217
}
227218

228219
} // namespace clang::tidy::modernize

clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ void test(std::string s, std::string_view sv, sub_string ss, sub_sub_string sss,
150150
// CHECK-FIXES: puv.starts_with("a");
151151

152152
puvf.find("a") == 0;
153-
// CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
154-
// CHECK-FIXES: puvf.starts_with("a");
153+
// CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use startsWith
154+
// CHECK-FIXES: puvf.startsWith("a");
155155

156156
// Here, the subclass has startsWith, the superclass has starts_with.
157157
// We prefer the version from the subclass.

0 commit comments

Comments
 (0)