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 <hel...@januschka.com> Date: Wed, 13 Nov 2024 12:52:36 +0100 Subject: [PATCH 1/5] [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 <hel...@januschka.com> Date: Mon, 18 Nov 2024 10:45:51 +0100 Subject: [PATCH 2/5] 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 <hel...@januschka.com> Date: Mon, 18 Nov 2024 10:59:33 +0100 Subject: [PATCH 3/5] 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 <hel...@januschka.com> Date: Mon, 18 Nov 2024 11:03:15 +0100 Subject: [PATCH 4/5] 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 <hel...@januschka.com> Date: Mon, 18 Nov 2024 19:29:46 +0100 Subject: [PATCH 5/5] 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 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits