[clang-tools-extra] [clang-tidy] Add new performance-use-starts-ends-with check (PR #72385)

Nicolas van Kempen via cfe-commits cfe-commits at lists.llvm.org
Fri Dec 1 02:56:31 PST 2023


https://github.com/nicovank updated https://github.com/llvm/llvm-project/pull/72385

>From 316b45d612c63f8eea8f847d1cd39992898516f3 Mon Sep 17 00:00:00 2001
From: Nicolas van Kempen <nvankempen at fb.com>
Date: Wed, 15 Nov 2023 01:13:10 -0800
Subject: [PATCH] [clang-tidy] Add new modernize-use-starts-ends-with check

Match .find() and .rfind() calls compared to 0, and suggests replacing them with
starts_with.
---
 .../abseil/StringFindStartswithCheck.h        |   5 +-
 .../clang-tidy/modernize/CMakeLists.txt       |   1 +
 .../modernize/ModernizeTidyModule.cpp         |   3 +
 .../modernize/UseStartsEndsWithCheck.cpp      | 113 ++++++++++++
 .../modernize/UseStartsEndsWithCheck.h        |  37 ++++
 clang-tools-extra/docs/ReleaseNotes.rst       |   7 +
 .../checks/abseil/string-find-startswith.rst  |   4 +
 .../docs/clang-tidy/checks/list.rst           |   1 +
 .../checks/modernize/use-starts-ends-with.rst |  22 +++
 .../checkers/Inputs/Headers/stddef.h          |   2 +-
 .../clang-tidy/checkers/Inputs/Headers/string |  16 +-
 .../abseil/string-find-startswith.cpp         |   2 +-
 .../modernize/use-starts-ends-with.cpp        | 167 ++++++++++++++++++
 .../readability/container-size-empty.cpp      |   4 +-
 14 files changed, 378 insertions(+), 6 deletions(-)
 create mode 100644 clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.h
 create mode 100644 clang-tools-extra/docs/clang-tidy/checks/modernize/use-starts-ends-with.rst
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp

diff --git a/clang-tools-extra/clang-tidy/abseil/StringFindStartswithCheck.h b/clang-tools-extra/clang-tidy/abseil/StringFindStartswithCheck.h
index 923b5caece5439b..de3bd4d42220009 100644
--- a/clang-tools-extra/clang-tidy/abseil/StringFindStartswithCheck.h
+++ b/clang-tools-extra/clang-tidy/abseil/StringFindStartswithCheck.h
@@ -21,7 +21,6 @@ namespace clang::tidy::abseil {
 
 // Find string.find(...) == 0 comparisons and suggest replacing with StartsWith.
 // FIXME(niko): Add similar check for EndsWith
-// FIXME(niko): Add equivalent modernize checks for C++20's std::starts_With
 class StringFindStartswithCheck : public ClangTidyCheck {
 public:
   using ClangTidyCheck::ClangTidyCheck;
@@ -31,6 +30,10 @@ class StringFindStartswithCheck : public ClangTidyCheck {
   void registerMatchers(ast_matchers::MatchFinder *Finder) override;
   void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
   void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    // Prefer modernize-use-starts-ends-with when C++20 is available.
+    return LangOpts.CPlusPlus && !LangOpts.CPlusPlus20;
+  }
 
 private:
   const std::vector<StringRef> StringLikeClasses;
diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
index 717c400c4790330..c40065358d2dc3d 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -38,6 +38,7 @@ add_clang_library(clangTidyModernizeModule
   UseNoexceptCheck.cpp
   UseNullptrCheck.cpp
   UseOverrideCheck.cpp
+  UseStartsEndsWithCheck.cpp
   UseStdPrintCheck.cpp
   UseTrailingReturnTypeCheck.cpp
   UseTransparentFunctorsCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
index 73751cf2705068d..e994ffd2a75c857 100644
--- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -39,6 +39,7 @@
 #include "UseNoexceptCheck.h"
 #include "UseNullptrCheck.h"
 #include "UseOverrideCheck.h"
+#include "UseStartsEndsWithCheck.h"
 #include "UseStdPrintCheck.h"
 #include "UseTrailingReturnTypeCheck.h"
 #include "UseTransparentFunctorsCheck.h"
@@ -66,6 +67,8 @@ class ModernizeModule : public ClangTidyModule {
     CheckFactories.registerCheck<MakeSharedCheck>("modernize-make-shared");
     CheckFactories.registerCheck<MakeUniqueCheck>("modernize-make-unique");
     CheckFactories.registerCheck<PassByValueCheck>("modernize-pass-by-value");
+    CheckFactories.registerCheck<UseStartsEndsWithCheck>(
+        "modernize-use-starts-ends-with");
     CheckFactories.registerCheck<UseStdPrintCheck>("modernize-use-std-print");
     CheckFactories.registerCheck<RawStringLiteralCheck>(
         "modernize-raw-string-literal");
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
new file mode 100644
index 000000000000000..83451e09e26eaa8
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
@@ -0,0 +1,113 @@
+//===--- UseStartsEndsWithCheck.cpp - clang-tidy --------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UseStartsEndsWithCheck.h"
+
+#include "../utils/OptionsUtils.h"
+#include "clang/Lex/Lexer.h"
+
+#include <string>
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+UseStartsEndsWithCheck::UseStartsEndsWithCheck(StringRef Name,
+                                               ClangTidyContext *Context)
+    : ClangTidyCheck(Name, Context) {}
+
+void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
+  const auto ZeroLiteral = integerLiteral(equals(0));
+  const auto HasStartsWithMethodWithName = [](const std::string &Name) {
+    return hasMethod(
+        cxxMethodDecl(hasName(Name), isConst(), parameterCountIs(1))
+            .bind("starts_with_fun"));
+  };
+  const auto HasStartsWithMethod =
+      anyOf(HasStartsWithMethodWithName("starts_with"),
+            HasStartsWithMethodWithName("startsWith"),
+            HasStartsWithMethodWithName("startswith"));
+  const auto ClassWithStartsWithFunction = cxxRecordDecl(anyOf(
+      HasStartsWithMethod, hasAnyBase(hasType(hasCanonicalType(hasDeclaration(
+                               cxxRecordDecl(HasStartsWithMethod)))))));
+
+  const auto FindExpr = cxxMemberCallExpr(
+      // A method call with no second argument or the second argument is zero...
+      anyOf(argumentCountIs(1), hasArgument(1, ZeroLiteral)),
+      // ... named find...
+      callee(cxxMethodDecl(hasName("find")).bind("find_fun")),
+      // ... on a class with a starts_with function.
+      on(hasType(
+          hasCanonicalType(hasDeclaration(ClassWithStartsWithFunction)))));
+
+  const auto RFindExpr = cxxMemberCallExpr(
+      // A method call with a second argument of zero...
+      hasArgument(1, ZeroLiteral),
+      // ... named rfind...
+      callee(cxxMethodDecl(hasName("rfind")).bind("rfind_fun")),
+      // ... on a class with a starts_with function.
+      on(hasType(
+          hasCanonicalType(hasDeclaration(ClassWithStartsWithFunction)))));
+
+  const auto FindOrRFindExpr =
+      cxxMemberCallExpr(anyOf(FindExpr, RFindExpr)).bind("find_expr");
+
+  Finder->addMatcher(
+      // Match [=!]= with a zero on one side and a string.(r?)find on the other.
+      binaryOperator(hasAnyOperatorName("==", "!="),
+                     hasOperands(FindOrRFindExpr, ZeroLiteral))
+          .bind("expr"),
+      this);
+}
+
+void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *ComparisonExpr = Result.Nodes.getNodeAs<BinaryOperator>("expr");
+  const auto *FindExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>("find_expr");
+  const auto *FindFun = Result.Nodes.getNodeAs<CXXMethodDecl>("find_fun");
+  const auto *RFindFun = Result.Nodes.getNodeAs<CXXMethodDecl>("rfind_fun");
+  assert(!FindFun != !RFindFun); // XOR.
+  const auto *StartsWithFunction =
+      Result.Nodes.getNodeAs<CXXMethodDecl>("starts_with_fun");
+
+  if (ComparisonExpr->getBeginLoc().isMacroID()) {
+    return;
+  }
+
+  const bool Rev = RFindFun != nullptr;
+  const bool Neg = ComparisonExpr->getOpcode() == BO_NE;
+
+  auto Diagnostic =
+      diag(FindExpr->getBeginLoc(),
+           "use %0 instead of %select{find()|rfind()}1 %select{==|!=}2 0")
+      << StartsWithFunction->getName() << Rev << Neg;
+
+  // Remove possible zero second argument and ' [!=]= 0' suffix.
+  Diagnostic << FixItHint::CreateReplacement(
+      CharSourceRange::getTokenRange(
+          Lexer::getLocForEndOfToken(FindExpr->getArg(0)->getEndLoc(), 0,
+                                     *Result.SourceManager, getLangOpts()),
+          ComparisonExpr->getEndLoc()),
+      ")");
+
+  // Remove possible '0 [!=]= ' prefix.
+  Diagnostic << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
+      ComparisonExpr->getBeginLoc(), FindExpr->getBeginLoc()));
+
+  // Replace '(r?)find' with 'starts_with'.
+  Diagnostic << FixItHint::CreateReplacement(
+      CharSourceRange::getTokenRange(FindExpr->getExprLoc(),
+                                     FindExpr->getExprLoc()),
+      StartsWithFunction->getName());
+
+  // Add possible negation '!'.
+  if (Neg) {
+    Diagnostic << 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
new file mode 100644
index 000000000000000..34e97177682575c
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.h
@@ -0,0 +1,37 @@
+//===--- UseStartsEndsWithCheck.h - clang-tidy ------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTARTSENDSWITHCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTARTSENDSWITHCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::modernize {
+
+/// Checks whether a ``find`` or ``rfind`` result is compared with 0 and
+/// suggests replacing with ``starts_with`` when the method exists in the class.
+/// Notably, this will work with ``std::string`` and ``std::string_view``.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-starts-ends-with.html
+class UseStartsEndsWithCheck : public ClangTidyCheck {
+public:
+  UseStartsEndsWithCheck(StringRef Name, ClangTidyContext *Context);
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    return LangOpts.CPlusPlus;
+  }
+  std::optional<TraversalKind> getCheckTraversalKind() const override {
+    return TK_IgnoreUnlessSpelledInSource;
+  }
+};
+
+} // namespace clang::tidy::modernize
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTARTSENDSWITHCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 353c6fe20269274..4d6d66bc9240ae1 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -186,6 +186,13 @@ New checks
 
   Replace ``enable_if`` with C++20 requires clauses.
 
+- New :doc:`modernize-use-starts-ends-with
+  <clang-tidy/checks/modernize/use-starts-ends-with>` check.
+
+  Checks whether a ``find`` or ``rfind`` result is compared with 0 and suggests
+  replacing with ``starts_with`` when the method exists in the class. Notably,
+  this will work with ``std::string`` and ``std::string_view``.
+
 - New :doc:`performance-enum-size
   <clang-tidy/checks/performance/enum-size>` check.
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/abseil/string-find-startswith.rst b/clang-tools-extra/docs/clang-tidy/checks/abseil/string-find-startswith.rst
index c82c38772a5c9a8..41a7ab500d7ce04 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/abseil/string-find-startswith.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/abseil/string-find-startswith.rst
@@ -8,6 +8,10 @@ corresponding ``std::string_view`` methods) result is compared with 0, and
 suggests replacing with ``absl::StartsWith()``. This is both a readability and
 performance issue.
 
+``starts_with`` was added as a built-in function on those types in C++20. If
+available, prefer enabling :doc:`modernize-use-starts-ends-with
+<../modernize/use-starts-ends-with>` instead of this check.
+
 .. code-block:: c++
 
   string s = "...";
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 6f987ba1672e3f2..df2d5d15238d63c 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -292,6 +292,7 @@ Clang-Tidy Checks
    :doc:`modernize-use-noexcept <modernize/use-noexcept>`, "Yes"
    :doc:`modernize-use-nullptr <modernize/use-nullptr>`, "Yes"
    :doc:`modernize-use-override <modernize/use-override>`, "Yes"
+   :doc:`modernize-use-starts-ends-with <modernize/use-starts-ends-with>`, "Yes"
    :doc:`modernize-use-std-print <modernize/use-std-print>`, "Yes"
    :doc:`modernize-use-trailing-return-type <modernize/use-trailing-return-type>`, "Yes"
    :doc:`modernize-use-transparent-functors <modernize/use-transparent-functors>`, "Yes"
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
new file mode 100644
index 000000000000000..53622dbf76687ba
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-starts-ends-with.rst
@@ -0,0 +1,22 @@
+.. title:: clang-tidy - modernize-use-starts-ends-with
+
+modernize-use-starts-ends-with
+================================
+
+Checks whether a ``find`` or ``rfind`` result is compared with 0 and suggests
+replacing with ``starts_with`` when the method exists in the class. Notably,
+this will work with ``std::string`` and ``std::string_view``.
+
+.. code-block:: c++
+
+  std::string s = "...";
+  if (s.find("prefix") == 0) { /* do something */ }
+  if (s.rfind("prefix", 0) == 0) { /* do something */ }
+
+becomes
+
+.. code-block:: c++
+
+  std::string s = "...";
+  if (s.starts_with("prefix")) { /* do something */ }
+  if (s.starts_with("prefix")) { /* do something */ }
diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stddef.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stddef.h
index 24b8ef012d4a00f..11feddf0f0676a5 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stddef.h
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stddef.h
@@ -12,4 +12,4 @@
 typedef __PTRDIFF_TYPE__ ptrdiff_t;
 typedef __SIZE_TYPE__ size_t;
 
-#endif _STDDEF_H_
+#endif // _STDDEF_H_
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 d0aac7b78ec939b..f2e4159a224513f 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string
@@ -10,8 +10,13 @@ typedef unsigned __INT32_TYPE__ char32;
 namespace std {
 template <typename T>
 class allocator {};
+
 template <typename T>
 class char_traits {};
+
+template <typename C, typename T = char_traits<C>>
+struct basic_string_view;
+
 template <typename C, typename T = char_traits<C>, typename A = allocator<C>>
 struct basic_string {
   typedef size_t size_type;
@@ -52,6 +57,10 @@ struct basic_string {
   _Type& insert(size_type pos, const C* s);
   _Type& insert(size_type pos, const C* s, size_type n);
 
+  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;
+
   _Type& operator[](size_type);
   const _Type& operator[](size_type) const;
 
@@ -68,7 +77,7 @@ typedef basic_string<wchar_t> wstring;
 typedef basic_string<char16> u16string;
 typedef basic_string<char32> u32string;
 
-template <typename C, typename T = char_traits<C>>
+template <typename C, typename T>
 struct basic_string_view {
   typedef size_t size_type;
   typedef basic_string_view<C, T> _Type;
@@ -86,8 +95,13 @@ struct basic_string_view {
   size_type rfind(const C* s, size_type pos, size_type count) const;
   size_type rfind(const C* s, size_type pos = npos) const;
 
+  constexpr bool starts_with(basic_string_view sv) const noexcept;
+  constexpr bool starts_with(C ch) const noexcept;
+  constexpr bool starts_with(const C* s) const;
+
   static constexpr size_t npos = -1;
 };
+
 typedef basic_string_view<char> string_view;
 typedef basic_string_view<wchar_t> wstring_view;
 typedef basic_string_view<char16> u16string_view;
diff --git a/clang-tools-extra/test/clang-tidy/checkers/abseil/string-find-startswith.cpp b/clang-tools-extra/test/clang-tidy/checkers/abseil/string-find-startswith.cpp
index 417598790bc007f..aabb30fe34f782c 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/abseil/string-find-startswith.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/abseil/string-find-startswith.cpp
@@ -1,4 +1,4 @@
-// RUN: %check_clang_tidy %s abseil-string-find-startswith %t -- \
+// RUN: %check_clang_tidy -std=c++17 %s abseil-string-find-startswith %t -- \
 // RUN:   -config="{CheckOptions: \
 // RUN:     {abseil-string-find-startswith.StringLikeClasses: \
 // RUN:       '::std::basic_string;::std::basic_string_view;::basic_string'}}" \
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
new file mode 100644
index 000000000000000..65ed9ed895bc473
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp
@@ -0,0 +1,167 @@
+// RUN: %check_clang_tidy -std=c++20 %s modernize-use-starts-ends-with %t -- \
+// RUN:   -- -isystem %clang_tidy_headers
+
+#include <string>
+
+std::string foo(std::string);
+std::string bar();
+
+class sub_string : public std::string {};
+class sub_sub_string : public sub_string {};
+
+struct string_like {
+  bool starts_with(const char *s) const;
+  size_t find(const char *s, size_t pos = 0) const;
+};
+
+struct string_like_camel {
+  bool startsWith(const char *s) const;
+  size_t find(const char *s, size_t pos = 0) const;
+};
+
+struct prefer_underscore_version {
+  bool starts_with(const char *s) const;
+  bool startsWith(const char *s) const;
+  size_t find(const char *s, size_t pos = 0) const;
+};
+
+struct prefer_underscore_version_flip {
+  bool startsWith(const char *s) const;
+  bool starts_with(const char *s) const;
+  size_t find(const char *s, size_t pos = 0) const;
+};
+
+struct prefer_underscore_version_inherit : public string_like {
+  bool startsWith(const char *s) const;
+};
+
+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,
+          prefer_underscore_version_inherit puvi) {
+  s.find("a") == 0;
+  // 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
+  // CHECK-FIXES: ((s)).starts_with("a");
+
+  (s + "a").find("a") == ((0));
+  // 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
+  // CHECK-FIXES: s.starts_with(s);
+
+  s.find("aaa") != 0;
+  // 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
+  // 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-FIXES: if (s.starts_with("...."))
+
+  0 != s.find("a");
+  // 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() == 0
+  // CHECK-FIXES: s.starts_with("a");
+
+  s.rfind(s, 0) == 0;
+  // 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
+  // CHECK-FIXES: !s.starts_with("aaa");
+
+  s.rfind(foo(foo(bar())), 0) != 0;
+  // 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
+  // CHECK-FIXES: if (s.starts_with("...."))
+
+  0 != s.rfind("a", 0);
+  // 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
+  // CHECK-FIXES: s.starts_with("a")
+
+  #define PREFIX "a"
+  s.find(PREFIX) == 0;
+  // 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
+  // CHECK-FIXES: s.starts_with("a")
+
+  sv.find("a") == 0;
+  // 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
+  // CHECK-FIXES: !sv.starts_with("a");
+
+  ss.find("a") == 0;
+  // 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
+  // CHECK-FIXES: ss.starts_with("a");
+
+  sl.find("a") == 0;
+  // 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
+  // CHECK-FIXES: slc.startsWith("a");
+
+  puv.find("a") == 0;
+  // 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
+  // CHECK-FIXES: puvf.starts_with("a");
+
+  // Here, the subclass has startsWith, the superclass has starts_with.
+  // We prefer the version from the subclass.
+  puvi.find("a") == 0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use startsWith
+  // CHECK-FIXES: puvi.startsWith("a");
+
+  // Expressions that don't trigger the check are here.
+  #define EQ(x, y) ((x) == (y))
+  EQ(s.find("a"), 0);
+
+  #define DOTFIND(x, y) (x).find(y)
+  DOTFIND(s, "a") == 0;
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/container-size-empty.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/container-size-empty.cpp
index 29ac86cf1b369c2..3b9e06084183054 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/readability/container-size-empty.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/container-size-empty.cpp
@@ -528,12 +528,12 @@ template <typename T> void f() {
   if (s.size())
     ;
   // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty]
-  // CHECK-MESSAGES: string:28:8: note: method 'basic_string'::empty() defined here
+  // CHECK-MESSAGES: string:{{[0-9]+}}:8: note: method 'basic_string'::empty() defined here
   // CHECK-FIXES: {{^  }}if (!s.empty()){{$}}
   if (s.length())
     ;
   // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used to check for emptiness instead of 'length' [readability-container-size-empty]
-  // CHECK-MESSAGES: string:28:8: note: method 'basic_string'::empty() defined here
+  // CHECK-MESSAGES: string:{{[0-9]+}}:8: note: method 'basic_string'::empty() defined here
   // CHECK-FIXES: {{^  }}if (!s.empty()){{$}}
 }
 



More information about the cfe-commits mailing list