| //===--- FasterStrsplitDelimiterCheck.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 "FasterStrsplitDelimiterCheck.h" | 
 | #include "clang/AST/ASTContext.h" | 
 | #include "clang/ASTMatchers/ASTMatchFinder.h" | 
 | #include "clang/Tooling/FixIt.h" | 
 | #include <optional> | 
 |  | 
 | using namespace clang::ast_matchers; | 
 |  | 
 | namespace clang::tidy::abseil { | 
 |  | 
 | namespace { | 
 |  | 
 | AST_MATCHER(StringLiteral, lengthIsOne) { return Node.getLength() == 1; } | 
 |  | 
 | std::optional<std::string> makeCharacterLiteral(const StringLiteral *Literal, | 
 |                                                 const ASTContext &Context) { | 
 |   assert(Literal->getLength() == 1 && | 
 |          "Only single character string should be matched"); | 
 |   assert(Literal->getCharByteWidth() == 1 && | 
 |          "StrSplit doesn't support wide char"); | 
 |   std::string Result = clang::tooling::fixit::getText(*Literal, Context).str(); | 
 |   bool IsRawStringLiteral = StringRef(Result).startswith(R"(R")"); | 
 |   // Since raw string literal might contain unescaped non-printable characters, | 
 |   // we normalize them using `StringLiteral::outputString`. | 
 |   if (IsRawStringLiteral) { | 
 |     Result.clear(); | 
 |     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"('\'')"); | 
 |  | 
 |   // Now replace the " with '. | 
 |   std::string::size_type Pos = Result.find_first_of('"'); | 
 |   if (Pos == std::string::npos) | 
 |     return std::nullopt; | 
 |   Result[Pos] = '\''; | 
 |   Pos = Result.find_last_of('"'); | 
 |   if (Pos == std::string::npos) | 
 |     return std::nullopt; | 
 |   Result[Pos] = '\''; | 
 |   return Result; | 
 | } | 
 |  | 
 | } // anonymous namespace | 
 |  | 
 | void FasterStrsplitDelimiterCheck::registerMatchers(MatchFinder *Finder) { | 
 |   // 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 | 
 |   // constructed from string literal. | 
 |   auto StringViewArg = ignoringElidableConstructorCall(ignoringImpCasts( | 
 |       cxxConstructExpr(hasType(recordDecl(hasName("::absl::string_view"))), | 
 |                        hasArgument(0, ignoringParenImpCasts(SingleChar))))); | 
 |  | 
 |   // Need to ignore the elidable constructor as otherwise there is no match for | 
 |   // c++14 and earlier. | 
 |   auto ByAnyCharArg = | 
 |       expr(has(ignoringElidableConstructorCall( | 
 |                ignoringParenCasts(cxxBindTemporaryExpr(has(cxxConstructExpr( | 
 |                    hasType(recordDecl(hasName("::absl::ByAnyChar"))), | 
 |                    hasArgument(0, StringViewArg)))))))) | 
 |           .bind("ByAnyChar"); | 
 |  | 
 |   // Find uses of absl::StrSplit(..., "x") and absl::StrSplit(..., | 
 |   // absl::ByAnyChar("x")) to transform them into absl::StrSplit(..., 'x'). | 
 |   Finder->addMatcher( | 
 |       traverse(TK_AsIs, | 
 |                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( | 
 |       traverse(TK_AsIs, | 
 |                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; | 
 |  | 
 |   std::optional<std::string> Replacement = | 
 |       makeCharacterLiteral(Literal, *Result.Context); | 
 |   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 clang::tidy::abseil |