//===--- 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