diff --git a/clang-tools-extra/clang-tidy/abseil/StringFindStartswithCheck.h b/clang-tools-extra/clang-tidy/abseil/StringFindStartswithCheck.h index 923b5caece543..de3bd4d422200 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 StringLikeClasses; diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt index 717c400c47903..c40065358d2dc 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 73751cf270506..e994ffd2a75c8 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("modernize-make-shared"); CheckFactories.registerCheck("modernize-make-unique"); CheckFactories.registerCheck("modernize-pass-by-value"); + CheckFactories.registerCheck( + "modernize-use-starts-ends-with"); CheckFactories.registerCheck("modernize-use-std-print"); CheckFactories.registerCheck( "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 0000000000000..062f6e9911dbe --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp @@ -0,0 +1,109 @@ +//===--- 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 + +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("find_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("expr"); + const auto *FindExpr = Result.Nodes.getNodeAs("find_expr"); + const auto *FindFun = Result.Nodes.getNodeAs("find_fun"); + const auto *StartsWithFunction = + Result.Nodes.getNodeAs("starts_with_fun"); + + if (ComparisonExpr->getBeginLoc().isMacroID()) { + return; + } + + const bool Neg = ComparisonExpr->getOpcode() == BO_NE; + + auto Diagnostic = + diag(FindExpr->getBeginLoc(), "use %0 instead of %1() %select{==|!=}2 0") + << StartsWithFunction->getName() << FindFun->getName() << 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 0000000000000..34e9717768257 --- /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 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 6d5f49dc06254..81c8b686d1cc1 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 + ` 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 ` 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 c82c38772a5c9..41a7ab500d7ce 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 6f987ba1672e3..df2d5d15238d6 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 `, "Yes" :doc:`modernize-use-nullptr `, "Yes" :doc:`modernize-use-override `, "Yes" + :doc:`modernize-use-starts-ends-with `, "Yes" :doc:`modernize-use-std-print `, "Yes" :doc:`modernize-use-trailing-return-type `, "Yes" :doc:`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 0000000000000..7f8a262d2ab3a --- /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 24b8ef012d4a0..11feddf0f0676 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 d0aac7b78ec93..f2e4159a22451 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 class allocator {}; + template class char_traits {}; + +template > +struct basic_string_view; + template , typename A = allocator> 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 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 wstring; typedef basic_string u16string; typedef basic_string u32string; -template > +template struct basic_string_view { typedef size_t size_type; typedef basic_string_view _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 string_view; typedef basic_string_view wstring_view; typedef basic_string_view 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 417598790bc00..aabb30fe34f78 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 0000000000000..65ed9ed895bc4 --- /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 + +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 29ac86cf1b369..3b9e060841830 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 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()){{$}} }