//===--- FasterStrsplitDelimiterCheck.cpp - clang-tidy---------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "FasterStrsplitDelimiterCheck.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" using namespace clang::ast_matchers; namespace clang { namespace tidy { namespace abseil { namespace { AST_MATCHER(StringLiteral, lengthIsOne) { return Node.getLength() == 1; } ::internal::Matcher constructExprWithArg(llvm::StringRef ClassName, const ::internal::Matcher &Arg) { auto ConstrExpr = cxxConstructExpr(hasType(recordDecl(hasName(ClassName))), hasArgument(0, ignoringParenCasts(Arg))); return anyOf(ConstrExpr, cxxBindTemporaryExpr(has(ConstrExpr))); } ::internal::Matcher copyConstructExprWithArg(llvm::StringRef ClassName, const ::internal::Matcher &Arg) { return constructExprWithArg(ClassName, constructExprWithArg(ClassName, Arg)); } llvm::Optional makeCharacterLiteral(const StringLiteral *Literal) { std::string Result; { llvm::raw_string_ostream Stream(Result); Literal->outputString(Stream); } // Special case: If the string contains a single quote, we just need to return // a character of the single quote. This is a special case because we need to // escape it in the character literal. if (Result == R"("'")") return std::string(R"('\'')"); assert(Result.size() == 3 || (Result.size() == 4 && Result.substr(0, 2) == "\"\\")); // Now replace the " with '. auto Pos = Result.find_first_of('"'); if (Pos == Result.npos) return llvm::None; Result[Pos] = '\''; Pos = Result.find_last_of('"'); if (Pos == Result.npos) return llvm::None; Result[Pos] = '\''; return Result; } } // anonymous namespace void FasterStrsplitDelimiterCheck::registerMatchers(MatchFinder *Finder) { if (!getLangOpts().CPlusPlus) return; // Binds to one character string literals. const auto SingleChar = expr(ignoringParenCasts(stringLiteral(lengthIsOne()).bind("Literal"))); // Binds to a string_view (either absl or std) that was passed by value and // contructed from string literal. auto StringViewArg = copyConstructExprWithArg("::absl::string_view", SingleChar); auto ByAnyCharArg = expr(copyConstructExprWithArg("::absl::ByAnyChar", StringViewArg)) .bind("ByAnyChar"); // Find uses of absl::StrSplit(..., "x") and absl::StrSplit(..., // absl::ByAnyChar("x")) to transform them into absl::StrSplit(..., 'x'). Finder->addMatcher(callExpr(callee(functionDecl(hasName("::absl::StrSplit"))), hasArgument(1, anyOf(ByAnyCharArg, SingleChar)), unless(isInTemplateInstantiation())) .bind("StrSplit"), this); // Find uses of absl::MaxSplits("x", N) and // absl::MaxSplits(absl::ByAnyChar("x"), N) to transform them into // absl::MaxSplits('x', N). Finder->addMatcher( callExpr( callee(functionDecl(hasName("::absl::MaxSplits"))), hasArgument(0, anyOf(ByAnyCharArg, ignoringParenCasts(SingleChar))), unless(isInTemplateInstantiation())), this); } void FasterStrsplitDelimiterCheck::check( const MatchFinder::MatchResult &Result) { const auto *Literal = Result.Nodes.getNodeAs("Literal"); if (Literal->getBeginLoc().isMacroID() || Literal->getEndLoc().isMacroID()) return; llvm::Optional Replacement = makeCharacterLiteral(Literal); if (!Replacement) return; SourceRange Range = Literal->getSourceRange(); if (const auto *ByAnyChar = Result.Nodes.getNodeAs("ByAnyChar")) Range = ByAnyChar->getSourceRange(); diag( Literal->getBeginLoc(), "%select{absl::StrSplit()|absl::MaxSplits()}0 called with a string " "literal " "consisting of a single character; consider using the character overload") << (Result.Nodes.getNodeAs("StrSplit") ? 0 : 1) << FixItHint::CreateReplacement(CharSourceRange::getTokenRange(Range), *Replacement); } } // namespace abseil } // namespace tidy } // namespace clang