clang-tools 19.0.0git
UseStartsEndsWithCheck.cpp
Go to the documentation of this file.
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//===----------------------------------------------------------------------===//
8
10
11#include "../utils/OptionsUtils.h"
12#include "clang/Lex/Lexer.h"
13
14#include <string>
15
16using namespace clang::ast_matchers;
17
18namespace clang::tidy::modernize {
19
21 ClangTidyContext *Context)
22 : ClangTidyCheck(Name, Context) {}
23
25 const auto ZeroLiteral = integerLiteral(equals(0));
26 const auto HasStartsWithMethodWithName = [](const std::string &Name) {
27 return hasMethod(
28 cxxMethodDecl(hasName(Name), isConst(), parameterCountIs(1))
29 .bind("starts_with_fun"));
30 };
31 const auto HasStartsWithMethod =
32 anyOf(HasStartsWithMethodWithName("starts_with"),
33 HasStartsWithMethodWithName("startsWith"),
34 HasStartsWithMethodWithName("startswith"));
35 const auto ClassWithStartsWithFunction = cxxRecordDecl(anyOf(
36 HasStartsWithMethod, hasAnyBase(hasType(hasCanonicalType(hasDeclaration(
37 cxxRecordDecl(HasStartsWithMethod)))))));
38
39 const auto FindExpr = cxxMemberCallExpr(
40 // A method call with no second argument or the second argument is zero...
41 anyOf(argumentCountIs(1), hasArgument(1, ZeroLiteral)),
42 // ... named find...
43 callee(cxxMethodDecl(hasName("find")).bind("find_fun")),
44 // ... on a class with a starts_with function.
45 on(hasType(
46 hasCanonicalType(hasDeclaration(ClassWithStartsWithFunction)))));
47
48 const auto RFindExpr = cxxMemberCallExpr(
49 // A method call with a second argument of zero...
50 hasArgument(1, ZeroLiteral),
51 // ... named rfind...
52 callee(cxxMethodDecl(hasName("rfind")).bind("find_fun")),
53 // ... on a class with a starts_with function.
54 on(hasType(
55 hasCanonicalType(hasDeclaration(ClassWithStartsWithFunction)))));
56
57 const auto FindOrRFindExpr =
58 cxxMemberCallExpr(anyOf(FindExpr, RFindExpr)).bind("find_expr");
59
60 Finder->addMatcher(
61 // Match [=!]= with a zero on one side and a string.(r?)find on the other.
62 binaryOperator(hasAnyOperatorName("==", "!="),
63 hasOperands(FindOrRFindExpr, ZeroLiteral))
64 .bind("expr"),
65 this);
66}
67
68void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
69 const auto *ComparisonExpr = Result.Nodes.getNodeAs<BinaryOperator>("expr");
70 const auto *FindExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>("find_expr");
71 const auto *FindFun = Result.Nodes.getNodeAs<CXXMethodDecl>("find_fun");
72 const auto *StartsWithFunction =
73 Result.Nodes.getNodeAs<CXXMethodDecl>("starts_with_fun");
74
75 if (ComparisonExpr->getBeginLoc().isMacroID()) {
76 return;
77 }
78
79 const bool Neg = ComparisonExpr->getOpcode() == BO_NE;
80
81 auto Diagnostic =
82 diag(FindExpr->getBeginLoc(), "use %0 instead of %1() %select{==|!=}2 0")
83 << StartsWithFunction->getName() << FindFun->getName() << Neg;
84
85 // Remove possible zero second argument and ' [!=]= 0' suffix.
86 Diagnostic << FixItHint::CreateReplacement(
87 CharSourceRange::getTokenRange(
88 Lexer::getLocForEndOfToken(FindExpr->getArg(0)->getEndLoc(), 0,
89 *Result.SourceManager, getLangOpts()),
90 ComparisonExpr->getEndLoc()),
91 ")");
92
93 // Remove possible '0 [!=]= ' prefix.
94 Diagnostic << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
95 ComparisonExpr->getBeginLoc(), FindExpr->getBeginLoc()));
96
97 // Replace '(r?)find' with 'starts_with'.
98 Diagnostic << FixItHint::CreateReplacement(
99 CharSourceRange::getTokenRange(FindExpr->getExprLoc(),
100 FindExpr->getExprLoc()),
101 StartsWithFunction->getName());
102
103 // Add possible negation '!'.
104 if (Neg) {
105 Diagnostic << FixItHint::CreateInsertion(FindExpr->getBeginLoc(), "!");
106 }
107}
108
109} // namespace clang::tidy::modernize
llvm::SmallString< 256U > Name
DiagnosticCallback Diagnostic
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
const LangOptions & getLangOpts() const
Returns the language options from the context.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
UseStartsEndsWithCheck(StringRef Name, ClangTidyContext *Context)