[clang-tools-extra] [clang-tidy] Enhance modernize-use-starts-ends-with to handle substr patterns (PR #116033)
Helmut Januschka via cfe-commits
cfe-commits at lists.llvm.org
Sat Nov 16 03:09:14 PST 2024
https://github.com/hjanuschka updated https://github.com/llvm/llvm-project/pull/116033
>From 7c446c6ce13e8c8c4fda22b70d5eef03852c5572 Mon Sep 17 00:00:00 2001
From: Helmut Januschka <helmut at januschka.com>
Date: Wed, 13 Nov 2024 12:52:36 +0100
Subject: [PATCH] [clang-tidy] Enhance modernize-use-starts-ends-with with
substr detection
Enhances the modernize-use-starts-ends-with check to detect substr-based
patterns that can be replaced with starts_with() (C++20). This improves code
readability and efficiency by avoiding temporary string creation.
New patterns detected:
str.substr(0, n) == "foo" -> str.starts_with("foo")
"foo" == str.substr(0, n) -> str.starts_with("foo")
str.substr(0, n) != "foo" -> !str.starts_with("foo")
str.substr(0, strlen("foo")) == "foo" -> str.starts_with("foo")
str.substr(0, prefix.size()) == "foo" -> str.starts_with("foo")
The enhancement:
- Integrates with existing starts_with patterns
- Handles substr with zero first argument
- Supports length via literals, strlen(), and size()/length()
- Validates string literal length matches
- Handles both == and != operators
Part of modernize-use-starts-ends-with check.
---
.../modernize/UseStartsEndsWithCheck.cpp | 114 +++++++++---
.../modernize/UseStartsEndsWithCheck.h | 1 +
clang-tools-extra/docs/ReleaseNotes.rst | 5 +-
.../checks/modernize/use-starts-ends-with.rst | 34 ++--
.../clang-tidy/checkers/Inputs/Headers/string | 2 +
.../modernize/use-starts-ends-with.cpp | 167 ++++++++++++------
6 files changed, 224 insertions(+), 99 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
index 1231f954298adc..12ff31dfa03541 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
@@ -30,6 +30,17 @@ struct NotLengthExprForStringNode {
IntegerLiteralSizeNode->getValue().getZExtValue();
}
+ if (const auto *DeclRefNode = Node.get<DeclRefExpr>()) {
+ if (const auto *VD = dyn_cast<VarDecl>(DeclRefNode->getDecl())) {
+ if (VD->hasInit() && VD->getType().isConstQualified()) {
+ if (const auto *Init = dyn_cast<IntegerLiteral>(VD->getInit())) {
+ return StringLiteralNode->getLength() !=
+ Init->getValue().getZExtValue();
+ }
+ }
+ }
+ }
+
if (const auto *StrlenNode = Node.get<CallExpr>()) {
if (StrlenNode->getDirectCallee()->getName() != "strlen" ||
StrlenNode->getNumArgs() != 1) {
@@ -171,10 +182,64 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
hasRHS(lengthExprForStringNode("needle")))))
.bind("expr"),
this);
+
+ Finder->addMatcher(
+ cxxOperatorCallExpr(
+ hasAnyOperatorName("==", "!="),
+ anyOf(
+ hasOperands(
+ cxxMemberCallExpr(
+ argumentCountIs(2), hasArgument(0, ZeroLiteral),
+ hasArgument(1, lengthExprForStringNode("needle")),
+ callee(
+ cxxMethodDecl(hasName("substr"),
+ ofClass(OnClassWithStartsWithFunction))
+ .bind("find_fun")))
+ .bind("find_expr"),
+ expr().bind("needle")),
+ hasOperands(expr().bind("needle"),
+ cxxMemberCallExpr(
+ argumentCountIs(2), hasArgument(0, ZeroLiteral),
+ hasArgument(1, lengthExprForStringNode("needle")),
+ callee(cxxMethodDecl(
+ hasName("substr"),
+ ofClass(OnClassWithStartsWithFunction))
+ .bind("find_fun")))
+ .bind("find_expr"))))
+ .bind("expr"),
+ this);
+}
+
+bool UseStartsEndsWithCheck::isNegativeComparison(const Expr* ComparisonExpr) {
+ // Handle direct != operator
+ if (const auto *BO = llvm::dyn_cast<BinaryOperator>(ComparisonExpr)) {
+ return BO->getOpcode() == BO_NE;
+ }
+
+ // Handle operator!= call
+ if (const auto *Op = llvm::dyn_cast<CXXOperatorCallExpr>(ComparisonExpr)) {
+ return Op->getOperator() == OO_ExclaimEqual;
+ }
+
+ // Handle rewritten !(expr == expr)
+ if (const auto *UO = llvm::dyn_cast<UnaryOperator>(ComparisonExpr)) {
+ if (UO->getOpcode() == UO_LNot) {
+ if (const auto *InnerBO =
+ llvm::dyn_cast<BinaryOperator>(UO->getSubExpr()->IgnoreParens())) {
+ return InnerBO->getOpcode() == BO_EQ;
+ }
+ if (const auto *InnerOp =
+ llvm::dyn_cast<CXXOperatorCallExpr>(UO->getSubExpr()->IgnoreParens())) {
+ return InnerOp->getOperator() == OO_EqualEqual;
+ }
+ }
+ }
+
+ return false;
}
void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
- const auto *ComparisonExpr = Result.Nodes.getNodeAs<BinaryOperator>("expr");
+ const auto *ComparisonExpr = Result.Nodes.getNodeAs<Expr>("expr");
const auto *FindExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>("find_expr");
const auto *FindFun = Result.Nodes.getNodeAs<CXXMethodDecl>("find_fun");
const auto *SearchExpr = Result.Nodes.getNodeAs<Expr>("needle");
@@ -183,40 +248,43 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
const auto *EndsWithFunction =
Result.Nodes.getNodeAs<CXXMethodDecl>("ends_with_fun");
assert(bool(StartsWithFunction) != bool(EndsWithFunction));
+
const CXXMethodDecl *ReplacementFunction =
StartsWithFunction ? StartsWithFunction : EndsWithFunction;
if (ComparisonExpr->getBeginLoc().isMacroID())
return;
- const bool Neg = ComparisonExpr->getOpcode() == BO_NE;
+ bool Neg = isNegativeComparison(ComparisonExpr);
- auto Diagnostic =
- diag(FindExpr->getExprLoc(), "use %0 instead of %1() %select{==|!=}2 0")
- << ReplacementFunction->getName() << FindFun->getName() << Neg;
+ // Retrieve the source text of the search expression.
+ const auto SearchExprText = Lexer::getSourceText(
+ CharSourceRange::getTokenRange(SearchExpr->getSourceRange()),
+ *Result.SourceManager, Result.Context->getLangOpts());
- // Remove possible arguments after search expression and ' [!=]= .+' suffix.
- Diagnostic << FixItHint::CreateReplacement(
- CharSourceRange::getTokenRange(
- Lexer::getLocForEndOfToken(SearchExpr->getEndLoc(), 0,
- *Result.SourceManager, getLangOpts()),
- ComparisonExpr->getEndLoc()),
- ")");
+ auto Diag = diag(ComparisonExpr->getBeginLoc(),
+ "use %0 instead of %1 %select{==|!=}2 ")
+ << ReplacementFunction->getName() << FindFun->getNameAsString()
+ << Neg;
- // Remove possible '.+ [!=]= ' prefix.
- Diagnostic << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
+ // Remove everything before the function call.
+ Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
ComparisonExpr->getBeginLoc(), FindExpr->getBeginLoc()));
- // Replace method name by '(starts|ends)_with'.
- // Remove possible arguments before search expression.
- Diagnostic << FixItHint::CreateReplacement(
- CharSourceRange::getCharRange(FindExpr->getExprLoc(),
- SearchExpr->getBeginLoc()),
- (ReplacementFunction->getName() + "(").str());
+ // Rename the function to `starts_with` or `ends_with`.
+ Diag << FixItHint::CreateReplacement(FindExpr->getExprLoc(),
+ ReplacementFunction->getName());
- // Add possible negation '!'.
- if (Neg)
- Diagnostic << FixItHint::CreateInsertion(FindExpr->getBeginLoc(), "!");
+ // Replace arguments and everything after the function call.
+ Diag << FixItHint::CreateReplacement(
+ CharSourceRange::getTokenRange(FindExpr->getArg(0)->getBeginLoc(),
+ ComparisonExpr->getEndLoc()),
+ (SearchExprText + ")").str());
+
+ // Add negation if necessary.
+ if (Neg) {
+ Diag << FixItHint::CreateInsertion(FindExpr->getBeginLoc(), "!");
+ }
}
} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.h b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.h
index 17c2999bda84cd..9a19206847adbd 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.h
+++ b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.h
@@ -25,6 +25,7 @@ class UseStartsEndsWithCheck : public ClangTidyCheck {
UseStartsEndsWithCheck(StringRef Name, ClangTidyContext *Context);
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ bool isNegativeComparison(const Expr* ComparisonExpr);
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus;
}
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index db971f08ca3dbc..62dbc7646740bc 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -244,7 +244,10 @@ Changes in existing checks
- Improved :doc:`modernize-use-starts-ends-with
<clang-tidy/checks/modernize/use-starts-ends-with>` check to handle two cases
- that can be replaced with ``ends_with``
+ that can be replaced with ``ends_with`` and detect patterns using ``substr``
+ that can be replaced with ``starts_with``. Now handles cases like
+ ``str.substr(0, n) == "literal"``, with support for length determination through
+ integer literals, ``strlen()``, and ``size()``/``length()`` member functions.
- Improved :doc:`modernize-use-std-format
<clang-tidy/checks/modernize/use-std-format>` check to support replacing
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-starts-ends-with.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-starts-ends-with.rst
index 721e927e29849f..abbe5b91b5e60d 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-starts-ends-with.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-starts-ends-with.rst
@@ -7,26 +7,16 @@ Checks for common roundabout ways to express ``starts_with`` and ``ends_with``
and suggests replacing with the simpler method when it is available. Notably,
this will work with ``std::string`` and ``std::string_view``.
-.. code-block:: c++
+The check handles the following expressions:
- std::string s = "...";
- if (s.find("prefix") == 0) { /* do something */ }
- if (s.rfind("prefix", 0) == 0) { /* do something */ }
- if (s.compare(0, strlen("prefix"), "prefix") == 0) { /* do something */ }
- if (s.compare(s.size() - strlen("suffix"), strlen("suffix"), "suffix") == 0) {
- /* do something */
- }
- if (s.rfind("suffix") == (s.length() - 6)) {
- /* do something */
- }
-
-becomes
-
-.. code-block:: c++
-
- std::string s = "...";
- if (s.starts_with("prefix")) { /* do something */ }
- if (s.starts_with("prefix")) { /* do something */ }
- if (s.starts_with("prefix")) { /* do something */ }
- if (s.ends_with("suffix")) { /* do something */ }
- if (s.ends_with("suffix")) { /* do something */ }
+==================================================== =====================
+Expression Replacement
+---------------------------------------------------- ---------------------
+``u.find(v) == 0`` ``u.starts_with(v)``
+``u.rfind(v, 0) != 0`` ``!u.starts_with(v)``
+``u.compare(0, v.size(), v) == 0`` ``u.starts_with(v)``
+``u.substr(0, v.size()) == v`` ``u.starts_with(v)``
+``v != u.substr(0, v.size())`` ``!u.starts_with(v)``
+``u.compare(u.size() - v.size(), v.size(), v) == 0`` ``u.ends_with(v)``
+``u.rfind(v) == u.size() - v.size()`` ``u.ends_with(v)``
+==================================================== =====================
diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string
index a0e8ebbb267cd0..51f68174169067 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string
@@ -60,6 +60,8 @@ struct basic_string {
_Type& insert(size_type pos, const C* s);
_Type& insert(size_type pos, const C* s, size_type n);
+ _Type substr(size_type pos = 0, size_type count = npos) const;
+
constexpr bool starts_with(std::basic_string_view<C, T> sv) const noexcept;
constexpr bool starts_with(C ch) const noexcept;
constexpr bool starts_with(const C* s) const;
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp
index 91477241e82e54..d6a7461fbfc11f 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp
@@ -36,221 +36,224 @@ void test(std::string s, std::string_view sv, sub_string ss, sub_sub_string sss,
string_like sl, string_like_camel slc, prefer_underscore_version puv,
prefer_underscore_version_flip puvf) {
s.find("a") == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find() == 0
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.starts_with("a");
(((((s)).find("a")))) == ((0));
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
// CHECK-FIXES: ((s)).starts_with("a");
(s + "a").find("a") == ((0));
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
// CHECK-FIXES: (s + "a").starts_with("a");
s.find(s) == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.starts_with(s);
s.find("aaa") != 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find != [modernize-use-starts-ends-with]
// CHECK-FIXES: !s.starts_with("aaa");
s.find(foo(foo(bar()))) != 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find != [modernize-use-starts-ends-with]
// CHECK-FIXES: !s.starts_with(foo(foo(bar())));
if (s.find("....") == 0) { /* do something */ }
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
// CHECK-FIXES: if (s.starts_with("...."))
0 != s.find("a");
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find != [modernize-use-starts-ends-with]
// CHECK-FIXES: !s.starts_with("a");
s.rfind("a", 0) == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of rfind() == 0
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of rfind == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.starts_with("a");
s.rfind(s, 0) == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of rfind == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.starts_with(s);
s.rfind("aaa", 0) != 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of rfind != [modernize-use-starts-ends-with]
// CHECK-FIXES: !s.starts_with("aaa");
s.rfind(foo(foo(bar())), 0) != 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of rfind != [modernize-use-starts-ends-with]
// CHECK-FIXES: !s.starts_with(foo(foo(bar())));
if (s.rfind("....", 0) == 0) { /* do something */ }
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of rfind == [modernize-use-starts-ends-with]
// CHECK-FIXES: if (s.starts_with("...."))
0 != s.rfind("a", 0);
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of rfind != [modernize-use-starts-ends-with]
// CHECK-FIXES: !s.starts_with("a");
#define STR(x) std::string(x)
0 == STR(s).find("a");
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
// CHECK-FIXES: STR(s).starts_with("a");
#define STRING s
if (0 == STRING.find("ala")) { /* do something */}
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
// CHECK-FIXES: if (STRING.starts_with("ala"))
#define FIND find
s.FIND("a") == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.starts_with("a")
#define PREFIX "a"
s.find(PREFIX) == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.starts_with(PREFIX)
#define ZERO 0
s.find("a") == ZERO;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.starts_with("a")
sv.find("a") == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
// CHECK-FIXES: sv.starts_with("a");
sv.rfind("a", 0) != 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of rfind != [modernize-use-starts-ends-with]
// CHECK-FIXES: !sv.starts_with("a");
ss.find("a") == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
// CHECK-FIXES: ss.starts_with("a");
sss.find("a") == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
// CHECK-FIXES: ss.starts_with("a");
sl.find("a") == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
// CHECK-FIXES: sl.starts_with("a");
slc.find("a") == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use startsWith
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use startsWith instead of find == [modernize-use-starts-ends-with]
// CHECK-FIXES: slc.startsWith("a");
puv.find("a") == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
// CHECK-FIXES: puv.starts_with("a");
puvf.find("a") == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
// CHECK-FIXES: puvf.starts_with("a");
s.compare(0, 1, "a") == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare() == 0
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.starts_with("a");
s.compare(0, 1, "a") != 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare() != 0
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare != [modernize-use-starts-ends-with]
// CHECK-FIXES: !s.starts_with("a");
s.compare(0, strlen("a"), "a") == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.starts_with("a");
s.compare(0, std::strlen("a"), "a") == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.starts_with("a");
s.compare(0, std::strlen(("a")), "a") == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.starts_with("a");
s.compare(0, std::strlen(("a")), (("a"))) == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.starts_with("a");
s.compare(0, s.size(), s) == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.starts_with(s);
s.compare(0, s.length(), s) == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.starts_with(s);
0 != s.compare(0, sv.length(), sv);
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
- // CHECK-FIXES: s.starts_with(sv);
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare != [modernize-use-starts-ends-with]
+ // CHECK-FIXES: !s.starts_with(sv);
#define LENGTH(x) (x).length()
s.compare(0, LENGTH(s), s) == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.starts_with(s);
s.compare(ZERO, LENGTH(s), s) == ZERO;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.starts_with(s);
s.compare(ZERO, LENGTH(sv), sv) != 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare != [modernize-use-starts-ends-with]
// CHECK-FIXES: !s.starts_with(sv);
s.compare(s.size() - 6, 6, "suffix") == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with instead of compare == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.ends_with("suffix");
s.compare(s.size() - 6, strlen("abcdef"), "suffix") == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with instead of compare == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.ends_with("suffix");
std::string suffix = "suffix";
s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with instead of compare == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.ends_with(suffix);
s.rfind("suffix") == s.size() - 6;
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with instead of rfind == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.ends_with("suffix");
s.rfind("suffix") == s.size() - strlen("suffix");
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with instead of rfind == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.ends_with("suffix");
s.rfind(suffix) == s.size() - suffix.size();
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with instead of rfind == [modernize-use-starts-ends-with]
+ // CHECK-FIXES: s.ends_with(suffix);
+ s.rfind(suffix) == s.size() - suffix.size();
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with instead of rfind == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.ends_with(suffix);
s.rfind(suffix, std::string::npos) == s.size() - suffix.size();
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with instead of rfind == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.ends_with(suffix);
s.rfind(suffix) == (s.size() - suffix.size());
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with instead of rfind == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.ends_with(suffix);
s.rfind(suffix, s.npos) == (s.size() - suffix.size());
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with instead of rfind == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.ends_with(suffix);
s.rfind(suffix, s.npos) == (((s.size()) - (suffix.size())));
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with instead of rfind == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.ends_with(suffix);
s.rfind(suffix) != s.size() - suffix.size();
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with instead of rfind != [modernize-use-starts-ends-with]
// CHECK-FIXES: !s.ends_with(suffix);
(s.size() - suffix.size()) == s.rfind(suffix);
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with instead of rfind == [modernize-use-starts-ends-with]
// CHECK-FIXES: s.ends_with(suffix);
struct S {
std::string s;
} t;
t.s.rfind(suffix) == (t.s.size() - suffix.size());
- // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with instead of rfind == [modernize-use-starts-ends-with]
// CHECK-FIXES: t.s.ends_with(suffix);
// Expressions that don't trigger the check are here.
@@ -266,3 +269,61 @@ void test(std::string s, std::string_view sv, sub_string ss, sub_sub_string sss,
s.compare(0, 1, "ab") == 0;
s.rfind(suffix, 1) == s.size() - suffix.size();
}
+
+void test_substr() {
+ std::string str("hello world");
+ std::string prefix = "hello";
+
+ // Basic pattern
+ str.substr(0, 5) == "hello";
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr == [modernize-use-starts-ends-with]
+ // CHECK-FIXES: str.starts_with("hello");
+
+ // With string literal on left side
+ "hello" == str.substr(0, 5);
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr == [modernize-use-starts-ends-with]
+ // CHECK-FIXES: str.starts_with("hello");
+
+ // Inequality comparison
+ str.substr(0, 5) != "world";
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr != [modernize-use-starts-ends-with]
+ // CHECK-FIXES: !str.starts_with("world");
+
+ // Ensure non-zero start position is not transformed
+ str.substr(1, 5) == "hello";
+ str.substr(0, 4) == "hello"; // Length mismatch
+
+ size_t len = 5;
+ str.substr(0, len) == "hello"; // Non-constant length
+
+ // String literal with size calculation
+ str.substr(0, strlen("hello")) == "hello";
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr == [modernize-use-starts-ends-with]
+ // CHECK-FIXES: str.starts_with("hello");
+
+ str.substr(0, prefix.size()) == prefix;
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr == [modernize-use-starts-ends-with]
+ // CHECK-FIXES: str.starts_with(prefix);
+
+ // Tests to verify macro behavior
+ #define STARTS_WITH(X, Y) (X).substr(0, (Y).size()) == (Y)
+ STARTS_WITH(str, prefix);
+
+ #define SUBSTR(X, A, B) (X).substr((A), (B))
+ SUBSTR(str, 0, 6) == "prefix";
+
+ #define STR() str
+ SUBSTR(STR(), 0, 6) == "prefix";
+
+ "prefix" == SUBSTR(STR(), 0, 6);
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr == [modernize-use-starts-ends-with]
+
+ // Test != operator rewriting
+ str.substr(0, 5) != "hello";
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr != [modernize-use-starts-ends-with]
+ // CHECK-FIXES: !str.starts_with("hello");
+
+ // Test rewritten form
+ !(str.substr(0, 5) == "hello");
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr == [modernize-use-starts-ends-with]
+}
\ No newline at end of file
More information about the cfe-commits
mailing list