127 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "FasterStrsplitDelimiterCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/ASTMatchers/ASTMatchFinder.h"12#include "clang/Tooling/FixIt.h"13#include <optional>14 15using namespace clang::ast_matchers;16 17namespace clang::tidy::abseil {18 19namespace {20 21AST_MATCHER(StringLiteral, lengthIsOne) { return Node.getLength() == 1; }22 23} // anonymous namespace24 25static std::optional<std::string>26makeCharacterLiteral(const StringLiteral *Literal, const ASTContext &Context) {27 assert(Literal->getLength() == 1 &&28 "Only single character string should be matched");29 assert(Literal->getCharByteWidth() == 1 &&30 "StrSplit doesn't support wide char");31 std::string Result = clang::tooling::fixit::getText(*Literal, Context).str();32 const bool IsRawStringLiteral = StringRef(Result).starts_with(R"(R")");33 // Since raw string literal might contain unescaped non-printable characters,34 // we normalize them using `StringLiteral::outputString`.35 if (IsRawStringLiteral) {36 Result.clear();37 llvm::raw_string_ostream Stream(Result);38 Literal->outputString(Stream);39 }40 // Special case: If the string contains a single quote, we just need to return41 // a character of the single quote. This is a special case because we need to42 // escape it in the character literal.43 if (Result == R"("'")")44 return std::string(R"('\'')");45 46 // Now replace the " with '.47 std::string::size_type Pos = Result.find_first_of('"');48 if (Pos == std::string::npos)49 return std::nullopt;50 Result[Pos] = '\'';51 Pos = Result.find_last_of('"');52 if (Pos == std::string::npos)53 return std::nullopt;54 Result[Pos] = '\'';55 return Result;56}57 58void FasterStrsplitDelimiterCheck::registerMatchers(MatchFinder *Finder) {59 // Binds to one character string literals.60 const auto SingleChar =61 expr(ignoringParenCasts(stringLiteral(lengthIsOne()).bind("Literal")));62 63 // Binds to a string_view (either absl or std) that was passed by value and64 // constructed from string literal.65 auto StringViewArg = ignoringElidableConstructorCall(ignoringImpCasts(66 cxxConstructExpr(hasType(recordDecl(hasName("::absl::string_view"))),67 hasArgument(0, ignoringParenImpCasts(SingleChar)))));68 69 // Need to ignore the elidable constructor as otherwise there is no match for70 // c++14 and earlier.71 auto ByAnyCharArg =72 expr(has(ignoringElidableConstructorCall(73 ignoringParenCasts(cxxBindTemporaryExpr(has(cxxConstructExpr(74 hasType(recordDecl(hasName("::absl::ByAnyChar"))),75 hasArgument(0, StringViewArg))))))))76 .bind("ByAnyChar");77 78 // Find uses of absl::StrSplit(..., "x") and absl::StrSplit(...,79 // absl::ByAnyChar("x")) to transform them into absl::StrSplit(..., 'x').80 Finder->addMatcher(81 traverse(TK_AsIs,82 callExpr(callee(functionDecl(hasName("::absl::StrSplit"))),83 hasArgument(1, anyOf(ByAnyCharArg, SingleChar)),84 unless(isInTemplateInstantiation()))85 .bind("StrSplit")),86 this);87 88 // Find uses of absl::MaxSplits("x", N) and89 // absl::MaxSplits(absl::ByAnyChar("x"), N) to transform them into90 // absl::MaxSplits('x', N).91 Finder->addMatcher(92 traverse(TK_AsIs,93 callExpr(callee(functionDecl(hasName("::absl::MaxSplits"))),94 hasArgument(0, anyOf(ByAnyCharArg,95 ignoringParenCasts(SingleChar))),96 unless(isInTemplateInstantiation()))),97 this);98}99 100void FasterStrsplitDelimiterCheck::check(101 const MatchFinder::MatchResult &Result) {102 const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("Literal");103 104 if (Literal->getBeginLoc().isMacroID() || Literal->getEndLoc().isMacroID())105 return;106 107 std::optional<std::string> Replacement =108 makeCharacterLiteral(Literal, *Result.Context);109 if (!Replacement)110 return;111 SourceRange Range = Literal->getSourceRange();112 113 if (const auto *ByAnyChar = Result.Nodes.getNodeAs<Expr>("ByAnyChar"))114 Range = ByAnyChar->getSourceRange();115 116 diag(117 Literal->getBeginLoc(),118 "%select{absl::StrSplit()|absl::MaxSplits()}0 called with a string "119 "literal "120 "consisting of a single character; consider using the character overload")121 << (Result.Nodes.getNodeAs<CallExpr>("StrSplit") ? 0 : 1)122 << FixItHint::CreateReplacement(CharSourceRange::getTokenRange(Range),123 *Replacement);124}125 126} // namespace clang::tidy::abseil127