-
Notifications
You must be signed in to change notification settings - Fork 10.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[clang-tidy] Add new modernize-use-starts-ends-with check (#72385)
Make a modernize version of abseil-string-find-startswith using the available C++20 `std::string::starts_with` and `std::string_view::starts_with`. Following up from #72283.
- Loading branch information
Showing
14 changed files
with
374 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
109 changes: 109 additions & 0 deletions
109
clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <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("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<BinaryOperator>("expr"); | ||
const auto *FindExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>("find_expr"); | ||
const auto *FindFun = Result.Nodes.getNodeAs<CXXMethodDecl>("find_fun"); | ||
const auto *StartsWithFunction = | ||
Result.Nodes.getNodeAs<CXXMethodDecl>("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 |
37 changes: 37 additions & 0 deletions
37
clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
clang-tools-extra/docs/clang-tidy/checks/modernize/use-starts-ends-with.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 */ } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,4 +12,4 @@ | |
typedef __PTRDIFF_TYPE__ ptrdiff_t; | ||
typedef __SIZE_TYPE__ size_t; | ||
|
||
#endif _STDDEF_H_ | ||
#endif // _STDDEF_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
clang-tools-extra/test/clang-tidy/checkers/abseil/string-find-startswith.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.