| //===--- 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<Expr> | 
 | constructExprWithArg(llvm::StringRef ClassName, | 
 |                      const ::internal::Matcher<Expr> &Arg) { | 
 |   auto ConstrExpr = cxxConstructExpr(hasType(recordDecl(hasName(ClassName))), | 
 |                                      hasArgument(0, ignoringParenCasts(Arg))); | 
 |  | 
 |   return anyOf(ConstrExpr, cxxBindTemporaryExpr(has(ConstrExpr))); | 
 | } | 
 |  | 
 | ::internal::Matcher<Expr> | 
 | copyConstructExprWithArg(llvm::StringRef ClassName, | 
 |                          const ::internal::Matcher<Expr> &Arg) { | 
 |   return constructExprWithArg(ClassName, constructExprWithArg(ClassName, Arg)); | 
 | } | 
 |  | 
 | llvm::Optional<std::string> 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<StringLiteral>("Literal"); | 
 |  | 
 |   if (Literal->getBeginLoc().isMacroID() || Literal->getEndLoc().isMacroID()) | 
 |     return; | 
 |  | 
 |   llvm::Optional<std::string> Replacement = makeCharacterLiteral(Literal); | 
 |   if (!Replacement) | 
 |     return; | 
 |   SourceRange Range = Literal->getSourceRange(); | 
 |  | 
 |   if (const auto *ByAnyChar = Result.Nodes.getNodeAs<Expr>("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<CallExpr>("StrSplit") ? 0 : 1) | 
 |       << FixItHint::CreateReplacement(CharSourceRange::getTokenRange(Range), | 
 |                                       *Replacement); | 
 | } | 
 |  | 
 | } // namespace abseil | 
 | } // namespace tidy | 
 | } // namespace clang |