[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
Fri Nov 22 05:40:28 PST 2024


https://github.com/hjanuschka updated https://github.com/llvm/llvm-project/pull/116033

>From 23b4bcdf52041aad1c5581e0f7dc01028770a154 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 1/9] [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        | 159 ++++++++++++------
 6 files changed, 216 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..d4e0d28a8c53e7 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,53 @@ 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]
+
+}
\ No newline at end of file

>From f2dc9b2312157adef82abbfbdbe9f46fc7712402 Mon Sep 17 00:00:00 2001
From: Helmut Januschka <helmut at januschka.com>
Date: Mon, 18 Nov 2024 10:45:51 +0100
Subject: [PATCH 2/9] feedback

---
 .../modernize/UseStartsEndsWithCheck.cpp      |  89 ++++---------
 .../modernize/UseStartsEndsWithCheck.h        |   1 -
 clang-tools-extra/docs/ReleaseNotes.rst       |   8 +-
 .../modernize/use-starts-ends-with.cpp        | 121 +++++++++---------
 4 files changed, 88 insertions(+), 131 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
index 12ff31dfa03541..ae52dfcec1b699 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
@@ -30,17 +30,6 @@ 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) {
@@ -82,6 +71,18 @@ struct NotLengthExprForStringNode {
   ASTContext *Context;
 };
 
+static bool isNegativeComparison(const Expr *ComparisonExpr) {
+  // Handle direct != operator
+  if (const auto *BO = llvm::dyn_cast<BinaryOperator>(ComparisonExpr)) {
+    return BO->getOpcode() == BO_NE;
+  }
+
+  if (const auto *Op = llvm::dyn_cast<CXXOperatorCallExpr>(ComparisonExpr)) {
+    return Op->getOperator() == OO_ExclaimEqual;
+  }
+  return false;
+}
+
 AST_MATCHER_P(Expr, lengthExprForStringNode, std::string, ID) {
   return Builder->removeBindings(NotLengthExprForStringNode(
       ID, DynTypedNode::create(Node), &(Finder->getASTContext())));
@@ -186,58 +187,19 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
   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"))))
+          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<Expr>("expr");
   const auto *FindExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>("find_expr");
@@ -262,10 +224,11 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
       CharSourceRange::getTokenRange(SearchExpr->getSourceRange()),
       *Result.SourceManager, Result.Context->getLangOpts());
 
-  auto Diag = diag(ComparisonExpr->getBeginLoc(),
-                   "use %0 instead of %1 %select{==|!=}2 ")
-              << ReplacementFunction->getName() << FindFun->getNameAsString()
-              << Neg;
+  auto Diag = diag(FindExpr->getExprLoc(),
+                   FindFun->getName() == "substr"
+                       ? "use %0 instead of %1() %select{==|!=}2"
+                       : "use %0 instead of %1() %select{==|!=}2 0")
+              << ReplacementFunction->getName() << FindFun->getName() << Neg;
 
   // Remove everything before the function call.
   Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.h b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.h
index 9a19206847adbd..17c2999bda84cd 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.h
+++ b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.h
@@ -25,7 +25,6 @@ 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 62dbc7646740bc..3747afde64a5b9 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -243,11 +243,9 @@ Changes in existing checks
   ``NULL``/``__null`` (but not ``0``) when used with a templated type.
 
 - 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`` 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.
+	  <clang-tidy/checks/modernize/use-starts-ends-with>` check to handle two new
+	  cases from ``rfind`` and ``compare`` to ``ends_with``, and one new case from
+	  ``substr`` to ``starts_with``.
 
 - Improved :doc:`modernize-use-std-format
   <clang-tidy/checks/modernize/use-std-format>` check to support replacing
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 d4e0d28a8c53e7..00ba18a305d63f 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,224 +36,221 @@ 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 == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find() == 0
   // CHECK-FIXES: s.starts_with("a");
 
   (((((s)).find("a")))) == ((0));
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: ((s)).starts_with("a");
 
   (s + "a").find("a") == ((0));
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: (s + "a").starts_with("a");
 
   s.find(s) == 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: s.starts_with(s);
 
   s.find("aaa") != 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find != [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: !s.starts_with("aaa");
 
   s.find(foo(foo(bar()))) != 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find != [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_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 instead of find == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: if (s.starts_with("...."))
 
   0 != s.find("a");
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find != [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_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 == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of rfind() == 0
   // CHECK-FIXES: s.starts_with("a");
 
   s.rfind(s, 0) == 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of rfind == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: s.starts_with(s);
 
   s.rfind("aaa", 0) != 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of rfind != [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: !s.starts_with("aaa");
 
   s.rfind(foo(foo(bar())), 0) != 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of rfind != [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_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 instead of rfind == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: if (s.starts_with("...."))
 
   0 != s.rfind("a", 0);
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of rfind != [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_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 instead of find == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_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 instead of find == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_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 instead of find == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: s.starts_with("a")
 
   #define PREFIX "a"
   s.find(PREFIX) == 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: s.starts_with(PREFIX)
 
   #define ZERO 0
   s.find("a") == ZERO;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: s.starts_with("a")
 
   sv.find("a") == 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: sv.starts_with("a");
 
   sv.rfind("a", 0) != 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of rfind != [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: !sv.starts_with("a");
 
   ss.find("a") == 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: ss.starts_with("a");
 
   sss.find("a") == 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: ss.starts_with("a");
 
   sl.find("a") == 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: sl.starts_with("a");
 
   slc.find("a") == 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use startsWith instead of find == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use startsWith
   // CHECK-FIXES: slc.startsWith("a");
 
   puv.find("a") == 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: puv.starts_with("a");
 
   puvf.find("a") == 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of find == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_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 == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare() == 0
   // 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 != [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare() != 0
   // CHECK-FIXES: !s.starts_with("a");
 
   s.compare(0, strlen("a"), "a") == 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_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 instead of compare == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_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 instead of compare == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_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 instead of compare == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: s.starts_with("a");
 
   s.compare(0, s.size(), s) == 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: s.starts_with(s);
 
   s.compare(0, s.length(), s) == 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: s.starts_with(s);
 
   0 != s.compare(0, sv.length(), 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);
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_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 instead of compare == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: s.starts_with(s);
 
   s.compare(ZERO, LENGTH(s), s) == ZERO;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
   // CHECK-FIXES: s.starts_with(s);
 
   s.compare(ZERO, LENGTH(sv), sv) != 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of compare != [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_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 instead of compare == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 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 instead of compare == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 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 instead of compare == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
   // CHECK-FIXES: s.ends_with(suffix);
 
   s.rfind("suffix") == s.size() - 6;
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with instead of rfind == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 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 instead of rfind == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 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) == s.size() - suffix.size();
-  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with instead of rfind == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 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 instead of rfind == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 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-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 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 instead of rfind == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 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 instead of rfind == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 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-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 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 instead of rfind == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 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 instead of rfind == [modernize-use-starts-ends-with]
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
   // CHECK-FIXES: t.s.ends_with(suffix);
 
   // Expressions that don't trigger the check are here.
@@ -276,17 +273,17 @@ void test_substr() {
     
     // 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-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-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-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
@@ -298,11 +295,11 @@ void test_substr() {
 
     // 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-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-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
@@ -316,6 +313,6 @@ void test_substr() {
     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]
+    // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr() == [modernize-use-starts-ends-with]
 
 }
\ No newline at end of file

>From de9d06223f1410f52f99d19a570c955aedc4481b Mon Sep 17 00:00:00 2001
From: Helmut Januschka <helmut at januschka.com>
Date: Mon, 18 Nov 2024 10:59:33 +0100
Subject: [PATCH 3/9] feedback

---
 .../test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 00ba18a305d63f..c95d93ee380cfa 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
@@ -315,4 +315,4 @@ void test_substr() {
     "prefix" == SUBSTR(STR(), 0, 6);
     // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr() == [modernize-use-starts-ends-with]
 
-}
\ No newline at end of file
+}

>From f948b16bfd789a4097828dad33c575b38fe709a7 Mon Sep 17 00:00:00 2001
From: Helmut Januschka <helmut at januschka.com>
Date: Mon, 18 Nov 2024 11:03:15 +0100
Subject: [PATCH 4/9] feedback

---
 .../clang-tidy/modernize/UseStartsEndsWithCheck.cpp              | 1 -
 1 file changed, 1 deletion(-)

diff --git a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
index ae52dfcec1b699..a78a98a45ae94d 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
@@ -72,7 +72,6 @@ struct NotLengthExprForStringNode {
 };
 
 static bool isNegativeComparison(const Expr *ComparisonExpr) {
-  // Handle direct != operator
   if (const auto *BO = llvm::dyn_cast<BinaryOperator>(ComparisonExpr)) {
     return BO->getOpcode() == BO_NE;
   }

>From 79838fcd4a5151adc2e5ac2ae7fed6c5653f1219 Mon Sep 17 00:00:00 2001
From: Helmut Januschka <helmut at januschka.com>
Date: Mon, 18 Nov 2024 19:29:46 +0100
Subject: [PATCH 5/9] feedback

---
 .../modernize/UseStartsEndsWithCheck.cpp      | 29 ++++++++++---------
 clang-tools-extra/docs/ReleaseNotes.rst       |  6 ++--
 2 files changed, 18 insertions(+), 17 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
index a78a98a45ae94d..e01d92fde7347b 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
@@ -18,6 +18,17 @@
 using namespace clang::ast_matchers;
 
 namespace clang::tidy::modernize {
+
+static bool isNegativeComparison(const Expr *ComparisonExpr) {
+  if (const auto *BO = llvm::dyn_cast<BinaryOperator>(ComparisonExpr))
+    return BO->getOpcode() == BO_NE;
+
+  if (const auto *Op = llvm::dyn_cast<CXXOperatorCallExpr>(ComparisonExpr))
+    return Op->getOperator() == OO_ExclaimEqual;
+
+  return false;
+}
+
 struct NotLengthExprForStringNode {
   NotLengthExprForStringNode(std::string ID, DynTypedNode Node,
                              ASTContext *Context)
@@ -71,17 +82,6 @@ struct NotLengthExprForStringNode {
   ASTContext *Context;
 };
 
-static bool isNegativeComparison(const Expr *ComparisonExpr) {
-  if (const auto *BO = llvm::dyn_cast<BinaryOperator>(ComparisonExpr)) {
-    return BO->getOpcode() == BO_NE;
-  }
-
-  if (const auto *Op = llvm::dyn_cast<CXXOperatorCallExpr>(ComparisonExpr)) {
-    return Op->getOperator() == OO_ExclaimEqual;
-  }
-  return false;
-}
-
 AST_MATCHER_P(Expr, lengthExprForStringNode, std::string, ID) {
   return Builder->removeBindings(NotLengthExprForStringNode(
       ID, DynTypedNode::create(Node), &(Finder->getASTContext())));
@@ -183,6 +183,7 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
           .bind("expr"),
       this);
 
+  // Case 6: X.substr(0, LEN(Y)) [!=]= Y -> ends_with.
   Finder->addMatcher(
       cxxOperatorCallExpr(
           hasAnyOperatorName("==", "!="),
@@ -216,7 +217,7 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
   if (ComparisonExpr->getBeginLoc().isMacroID())
     return;
 
-  bool Neg = isNegativeComparison(ComparisonExpr);
+  const bool Neg = isNegativeComparison(ComparisonExpr);
 
   // Retrieve the source text of the search expression.
   const auto SearchExprText = Lexer::getSourceText(
@@ -244,9 +245,9 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
       (SearchExprText + ")").str());
 
   // Add negation if necessary.
-  if (Neg) {
+  if (Neg)
     Diag << FixItHint::CreateInsertion(FindExpr->getBeginLoc(), "!");
-  }
+
 }
 
 } // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 3747afde64a5b9..bde68281946986 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -243,9 +243,9 @@ Changes in existing checks
   ``NULL``/``__null`` (but not ``0``) when used with a templated type.
 
 - Improved :doc:`modernize-use-starts-ends-with
-	  <clang-tidy/checks/modernize/use-starts-ends-with>` check to handle two new
-	  cases from ``rfind`` and ``compare`` to ``ends_with``, and one new case from
-	  ``substr`` to ``starts_with``.
+  <clang-tidy/checks/modernize/use-starts-ends-with>` check to handle two new
+  cases from ``rfind`` and ``compare`` to ``ends_with``, and one new case from
+  ``substr`` to ``starts_with``.
 
 - Improved :doc:`modernize-use-std-format
   <clang-tidy/checks/modernize/use-std-format>` check to support replacing

>From dcb89af8bdce47dd3962b06193958034ce238200 Mon Sep 17 00:00:00 2001
From: Helmut Januschka <helmut at januschka.com>
Date: Mon, 18 Nov 2024 20:27:05 +0100
Subject: [PATCH 6/9] feedback

---
 .../modernize/UseStartsEndsWithCheck.cpp      |  3 ++-
 .../modernize/use-starts-ends-with.cpp        | 19 ++++++-------------
 2 files changed, 8 insertions(+), 14 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
index e01d92fde7347b..c8d34bb8dd325b 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
@@ -214,7 +214,8 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
   const CXXMethodDecl *ReplacementFunction =
       StartsWithFunction ? StartsWithFunction : EndsWithFunction;
 
-  if (ComparisonExpr->getBeginLoc().isMacroID())
+  if (ComparisonExpr->getBeginLoc().isMacroID() ||
+      FindExpr->getBeginLoc().isMacroID())
     return;
 
   const bool Neg = isNegativeComparison(ComparisonExpr);
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 c95d93ee380cfa..299e06a3921fa9 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
@@ -91,16 +91,6 @@ void test(std::string s, std::string_view sv, sub_string ss, sub_sub_string sss,
   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_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-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-FIXES: if (STRING.starts_with("ala"))
-
   #define FIND find
   s.FIND("a") == 0;
   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
@@ -265,6 +255,12 @@ 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();
+
+  #define STR(x) std::string(x)
+  0 == STR(s).find("a");
+
+  #define STRING s
+  if (0 == STRING.find("ala")) { /* do something */}
 }
 
 void test_substr() {
@@ -311,8 +307,5 @@ void test_substr() {
 
     #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]
-
 }

>From 115a760c1875dde19e880b631cf7879dc4dfbcbd Mon Sep 17 00:00:00 2001
From: Helmut Januschka <helmut at januschka.com>
Date: Mon, 18 Nov 2024 20:37:37 +0100
Subject: [PATCH 7/9] Update
 clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp

Co-authored-by: Nicolas van Kempen <nvankemp at gmail.com>
---
 .../test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 299e06a3921fa9..a0f8a57eb395ce 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
@@ -298,7 +298,7 @@ void test_substr() {
     // 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
+    // Tests to verify macro behavior
     #define STARTS_WITH(X, Y) (X).substr(0, (Y).size()) == (Y)
     STARTS_WITH(str, prefix);
 

>From 3bed9be2521268defeacc3cd7eafa6570a4651d6 Mon Sep 17 00:00:00 2001
From: Helmut Januschka <helmut at januschka.com>
Date: Thu, 21 Nov 2024 23:24:02 +0100
Subject: [PATCH 8/9] feedback

---
 .../clang-tidy/modernize/UseStartsEndsWithCheck.cpp         | 4 +++-
 .../clang-tidy/checks/modernize/use-starts-ends-with.rst    | 2 +-
 .../clang-tidy/checkers/modernize/use-starts-ends-with.cpp  | 6 ++++++
 3 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
index c8d34bb8dd325b..a3aec464530aee 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
@@ -240,6 +240,9 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
                                        ReplacementFunction->getName());
 
   // Replace arguments and everything after the function call.
+  if (FindExpr->getNumArgs() == 0) {
+    return;
+  }
   Diag << FixItHint::CreateReplacement(
       CharSourceRange::getTokenRange(FindExpr->getArg(0)->getBeginLoc(),
                                      ComparisonExpr->getEndLoc()),
@@ -248,7 +251,6 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
   // Add negation if necessary.
   if (Neg)
     Diag << FixItHint::CreateInsertion(FindExpr->getBeginLoc(), "!");
-
 }
 
 } // namespace clang::tidy::modernize
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 abbe5b91b5e60d..78cd900885ac3f 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,7 +7,7 @@ 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``.
 
-The check handles the following expressions:
+Covered scenarios:
 
 ==================================================== =====================
 Expression                                           Replacement
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 a0f8a57eb395ce..2e7486941d7cf2 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
@@ -298,6 +298,10 @@ void test_substr() {
     // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr() == [modernize-use-starts-ends-with]
     // CHECK-FIXES: str.starts_with(prefix);
 
+    str.substr(0, prefix.length()) == 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);
@@ -308,4 +312,6 @@ void test_substr() {
     #define STR() str
     SUBSTR(STR(), 0, 6) == "prefix";
     "prefix" == SUBSTR(STR(), 0, 6);
+
+    str.substr(0, strlen("hello123")) == "hello";
 }

>From 522d4b3802f0184a60d6b2376fc8c887f89b13ea Mon Sep 17 00:00:00 2001
From: Helmut Januschka <helmut at januschka.com>
Date: Fri, 22 Nov 2024 14:40:06 +0100
Subject: [PATCH 9/9] feedback

---
 .../clang-tidy/modernize/UseStartsEndsWithCheck.cpp       | 8 ++++----
 .../checkers/modernize/use-starts-ends-with.cpp           | 5 +++++
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
index a3aec464530aee..1fa6d2d1bb00be 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
@@ -183,7 +183,7 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
           .bind("expr"),
       this);
 
-  // Case 6: X.substr(0, LEN(Y)) [!=]= Y -> ends_with.
+  // Case 6: X.substr(0, LEN(Y)) [!=]= Y -> starts_with.
   Finder->addMatcher(
       cxxOperatorCallExpr(
           hasAnyOperatorName("==", "!="),
@@ -218,6 +218,9 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
       FindExpr->getBeginLoc().isMacroID())
     return;
 
+  if (FindExpr->getNumArgs() == 0)
+    return;
+
   const bool Neg = isNegativeComparison(ComparisonExpr);
 
   // Retrieve the source text of the search expression.
@@ -240,9 +243,6 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
                                        ReplacementFunction->getName());
 
   // Replace arguments and everything after the function call.
-  if (FindExpr->getNumArgs() == 0) {
-    return;
-  }
   Diag << FixItHint::CreateReplacement(
       CharSourceRange::getTokenRange(FindExpr->getArg(0)->getBeginLoc(),
                                      ComparisonExpr->getEndLoc()),
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 2e7486941d7cf2..fd519eefe8da35 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
@@ -303,6 +303,11 @@ void test_substr() {
     // CHECK-FIXES: str.starts_with(prefix);
 
     // Tests to verify macro behavior
+    #define MSG "hello"
+    str.substr(0, strlen(MSG)) == MSG;
+    // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr() == [modernize-use-starts-ends-with]
+    // CHECK-FIXES: str.starts_with(MSG);
+
     #define STARTS_WITH(X, Y) (X).substr(0, (Y).size()) == (Y)
     STARTS_WITH(str, prefix);
 



More information about the cfe-commits mailing list