10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/Tooling/FixIt.h"
22 AST_MATCHER(StringLiteral, lengthIsOne) {
return Node.getLength() == 1; }
24 llvm::Optional<std::string> makeCharacterLiteral(
const StringLiteral *Literal,
25 const ASTContext &Context) {
26 assert(Literal->getLength() == 1 &&
27 "Only single character string should be matched");
28 assert(Literal->getCharByteWidth() == 1 &&
29 "StrSplit doesn't support wide char");
31 bool IsRawStringLiteral = StringRef(Result).startswith(R
"(R")");
34 if (IsRawStringLiteral) {
36 llvm::raw_string_ostream Stream(Result);
37 Literal->outputString(Stream);
42 if (Result == R
"("'")")
43 return std::string(R
"('\'')");
46 std::string::size_type
Pos = Result.find_first_of(
'"');
47 if (
Pos == Result.npos)
50 Pos = Result.find_last_of(
'"');
51 if (
Pos == Result.npos)
59 void FasterStrsplitDelimiterCheck::registerMatchers(MatchFinder *Finder) {
61 const auto SingleChar =
62 expr(ignoringParenCasts(stringLiteral(lengthIsOne()).bind(
"Literal")));
66 auto StringViewArg = ignoringElidableConstructorCall(ignoringImpCasts(
67 cxxConstructExpr(hasType(recordDecl(hasName(
"::absl::string_view"))),
68 hasArgument(0, ignoringParenImpCasts(SingleChar)))));
73 expr(has(ignoringElidableConstructorCall(
74 ignoringParenCasts(cxxBindTemporaryExpr(has(cxxConstructExpr(
75 hasType(recordDecl(hasName(
"::absl::ByAnyChar"))),
76 hasArgument(0, StringViewArg))))))))
83 callExpr(callee(functionDecl(hasName(
"::absl::StrSplit"))),
84 hasArgument(1, anyOf(ByAnyCharArg, SingleChar)),
85 unless(isInTemplateInstantiation()))
94 callExpr(callee(functionDecl(hasName(
"::absl::MaxSplits"))),
95 hasArgument(0, anyOf(ByAnyCharArg,
96 ignoringParenCasts(SingleChar))),
97 unless(isInTemplateInstantiation()))),
102 const MatchFinder::MatchResult &Result) {
103 const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>(
"Literal");
105 if (Literal->getBeginLoc().isMacroID() || Literal->getEndLoc().isMacroID())
108 llvm::Optional<std::string> Replacement =
109 makeCharacterLiteral(Literal, *Result.Context);
112 SourceRange
Range = Literal->getSourceRange();
114 if (
const auto *ByAnyChar = Result.Nodes.getNodeAs<Expr>(
"ByAnyChar"))
115 Range = ByAnyChar->getSourceRange();
118 Literal->getBeginLoc(),
119 "%select{absl::StrSplit()|absl::MaxSplits()}0 called with a string "
121 "consisting of a single character; consider using the character overload")
122 << (Result.Nodes.getNodeAs<CallExpr>(
"StrSplit") ? 0 : 1)
123 << FixItHint::CreateReplacement(CharSourceRange::getTokenRange(
Range),