[clang-tidy][NFC] fix release note order (#117484)
[llvm-project.git] / clang-tools-extra / clang-tidy / modernize / UseStartsEndsWithCheck.cpp
blob1231f954298adce742cf5e599ff2d08d9c90def6
1 //===--- UseStartsEndsWithCheck.cpp - clang-tidy --------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
9 #include "UseStartsEndsWithCheck.h"
11 #include "../utils/ASTUtils.h"
12 #include "../utils/Matchers.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/Lex/Lexer.h"
16 #include <string>
18 using namespace clang::ast_matchers;
20 namespace clang::tidy::modernize {
21 struct NotLengthExprForStringNode {
22 NotLengthExprForStringNode(std::string ID, DynTypedNode Node,
23 ASTContext *Context)
24 : ID(std::move(ID)), Node(std::move(Node)), Context(Context) {}
25 bool operator()(const internal::BoundNodesMap &Nodes) const {
26 // Match a string literal and an integer size or strlen() call.
27 if (const auto *StringLiteralNode = Nodes.getNodeAs<StringLiteral>(ID)) {
28 if (const auto *IntegerLiteralSizeNode = Node.get<IntegerLiteral>()) {
29 return StringLiteralNode->getLength() !=
30 IntegerLiteralSizeNode->getValue().getZExtValue();
33 if (const auto *StrlenNode = Node.get<CallExpr>()) {
34 if (StrlenNode->getDirectCallee()->getName() != "strlen" ||
35 StrlenNode->getNumArgs() != 1) {
36 return true;
39 if (const auto *StrlenArgNode = dyn_cast<StringLiteral>(
40 StrlenNode->getArg(0)->IgnoreParenImpCasts())) {
41 return StrlenArgNode->getLength() != StringLiteralNode->getLength();
46 // Match a string variable and a call to length() or size().
47 if (const auto *ExprNode = Nodes.getNodeAs<Expr>(ID)) {
48 if (const auto *MemberCallNode = Node.get<CXXMemberCallExpr>()) {
49 const CXXMethodDecl *MethodDeclNode = MemberCallNode->getMethodDecl();
50 const StringRef Name = MethodDeclNode->getName();
51 if (!MethodDeclNode->isConst() || MethodDeclNode->getNumParams() != 0 ||
52 (Name != "size" && Name != "length")) {
53 return true;
56 if (const auto *OnNode =
57 dyn_cast<Expr>(MemberCallNode->getImplicitObjectArgument())) {
58 return !utils::areStatementsIdentical(OnNode->IgnoreParenImpCasts(),
59 ExprNode->IgnoreParenImpCasts(),
60 *Context);
65 return true;
68 private:
69 std::string ID;
70 DynTypedNode Node;
71 ASTContext *Context;
74 AST_MATCHER_P(Expr, lengthExprForStringNode, std::string, ID) {
75 return Builder->removeBindings(NotLengthExprForStringNode(
76 ID, DynTypedNode::create(Node), &(Finder->getASTContext())));
79 UseStartsEndsWithCheck::UseStartsEndsWithCheck(StringRef Name,
80 ClangTidyContext *Context)
81 : ClangTidyCheck(Name, Context) {}
83 void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
84 const auto ZeroLiteral = integerLiteral(equals(0));
86 const auto ClassTypeWithMethod = [](const StringRef MethodBoundName,
87 const auto... Methods) {
88 return cxxRecordDecl(anyOf(
89 hasMethod(cxxMethodDecl(isConst(), parameterCountIs(1),
90 returns(booleanType()), hasAnyName(Methods))
91 .bind(MethodBoundName))...));
94 const auto OnClassWithStartsWithFunction =
95 ClassTypeWithMethod("starts_with_fun", "starts_with", "startsWith",
96 "startswith", "StartsWith");
98 const auto OnClassWithEndsWithFunction = ClassTypeWithMethod(
99 "ends_with_fun", "ends_with", "endsWith", "endswith", "EndsWith");
101 // Case 1: X.find(Y) [!=]= 0 -> starts_with.
102 const auto FindExpr = cxxMemberCallExpr(
103 anyOf(argumentCountIs(1), hasArgument(1, ZeroLiteral)),
104 callee(
105 cxxMethodDecl(hasName("find"), ofClass(OnClassWithStartsWithFunction))
106 .bind("find_fun")),
107 hasArgument(0, expr().bind("needle")));
109 // Case 2: X.rfind(Y, 0) [!=]= 0 -> starts_with.
110 const auto RFindExpr = cxxMemberCallExpr(
111 hasArgument(1, ZeroLiteral),
112 callee(cxxMethodDecl(hasName("rfind"),
113 ofClass(OnClassWithStartsWithFunction))
114 .bind("find_fun")),
115 hasArgument(0, expr().bind("needle")));
117 // Case 3: X.compare(0, LEN(Y), Y) [!=]= 0 -> starts_with.
118 const auto CompareExpr = cxxMemberCallExpr(
119 argumentCountIs(3), hasArgument(0, ZeroLiteral),
120 callee(cxxMethodDecl(hasName("compare"),
121 ofClass(OnClassWithStartsWithFunction))
122 .bind("find_fun")),
123 hasArgument(2, expr().bind("needle")),
124 hasArgument(1, lengthExprForStringNode("needle")));
126 // Case 4: X.compare(LEN(X) - LEN(Y), LEN(Y), Y) [!=]= 0 -> ends_with.
127 const auto CompareEndsWithExpr = cxxMemberCallExpr(
128 argumentCountIs(3),
129 callee(cxxMethodDecl(hasName("compare"),
130 ofClass(OnClassWithEndsWithFunction))
131 .bind("find_fun")),
132 on(expr().bind("haystack")), hasArgument(2, expr().bind("needle")),
133 hasArgument(1, lengthExprForStringNode("needle")),
134 hasArgument(0,
135 binaryOperator(hasOperatorName("-"),
136 hasLHS(lengthExprForStringNode("haystack")),
137 hasRHS(lengthExprForStringNode("needle")))));
139 // All cases comparing to 0.
140 Finder->addMatcher(
141 binaryOperator(
142 matchers::isEqualityOperator(),
143 hasOperands(cxxMemberCallExpr(anyOf(FindExpr, RFindExpr, CompareExpr,
144 CompareEndsWithExpr))
145 .bind("find_expr"),
146 ZeroLiteral))
147 .bind("expr"),
148 this);
150 // Case 5: X.rfind(Y) [!=]= LEN(X) - LEN(Y) -> ends_with.
151 Finder->addMatcher(
152 binaryOperator(
153 matchers::isEqualityOperator(),
154 hasOperands(
155 cxxMemberCallExpr(
156 anyOf(
157 argumentCountIs(1),
158 allOf(argumentCountIs(2),
159 hasArgument(
161 anyOf(declRefExpr(to(varDecl(hasName("npos")))),
162 memberExpr(member(hasName("npos"))))))),
163 callee(cxxMethodDecl(hasName("rfind"),
164 ofClass(OnClassWithEndsWithFunction))
165 .bind("find_fun")),
166 on(expr().bind("haystack")),
167 hasArgument(0, expr().bind("needle")))
168 .bind("find_expr"),
169 binaryOperator(hasOperatorName("-"),
170 hasLHS(lengthExprForStringNode("haystack")),
171 hasRHS(lengthExprForStringNode("needle")))))
172 .bind("expr"),
173 this);
176 void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
177 const auto *ComparisonExpr = Result.Nodes.getNodeAs<BinaryOperator>("expr");
178 const auto *FindExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>("find_expr");
179 const auto *FindFun = Result.Nodes.getNodeAs<CXXMethodDecl>("find_fun");
180 const auto *SearchExpr = Result.Nodes.getNodeAs<Expr>("needle");
181 const auto *StartsWithFunction =
182 Result.Nodes.getNodeAs<CXXMethodDecl>("starts_with_fun");
183 const auto *EndsWithFunction =
184 Result.Nodes.getNodeAs<CXXMethodDecl>("ends_with_fun");
185 assert(bool(StartsWithFunction) != bool(EndsWithFunction));
186 const CXXMethodDecl *ReplacementFunction =
187 StartsWithFunction ? StartsWithFunction : EndsWithFunction;
189 if (ComparisonExpr->getBeginLoc().isMacroID())
190 return;
192 const bool Neg = ComparisonExpr->getOpcode() == BO_NE;
194 auto Diagnostic =
195 diag(FindExpr->getExprLoc(), "use %0 instead of %1() %select{==|!=}2 0")
196 << ReplacementFunction->getName() << FindFun->getName() << Neg;
198 // Remove possible arguments after search expression and ' [!=]= .+' suffix.
199 Diagnostic << FixItHint::CreateReplacement(
200 CharSourceRange::getTokenRange(
201 Lexer::getLocForEndOfToken(SearchExpr->getEndLoc(), 0,
202 *Result.SourceManager, getLangOpts()),
203 ComparisonExpr->getEndLoc()),
204 ")");
206 // Remove possible '.+ [!=]= ' prefix.
207 Diagnostic << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
208 ComparisonExpr->getBeginLoc(), FindExpr->getBeginLoc()));
210 // Replace method name by '(starts|ends)_with'.
211 // Remove possible arguments before search expression.
212 Diagnostic << FixItHint::CreateReplacement(
213 CharSourceRange::getCharRange(FindExpr->getExprLoc(),
214 SearchExpr->getBeginLoc()),
215 (ReplacementFunction->getName() + "(").str());
217 // Add possible negation '!'.
218 if (Neg)
219 Diagnostic << FixItHint::CreateInsertion(FindExpr->getBeginLoc(), "!");
222 } // namespace clang::tidy::modernize