1 //===--- FasterStrsplitDelimiterCheck.cpp - clang-tidy---------------------===//
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-exception
7 //===----------------------------------------------------------------------===//
9 #include "FasterStrsplitDelimiterCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/Tooling/FixIt.h"
15 using namespace clang::ast_matchers
;
17 namespace clang::tidy::abseil
{
21 AST_MATCHER(StringLiteral
, lengthIsOne
) { return Node
.getLength() == 1; }
23 std::optional
<std::string
> makeCharacterLiteral(const StringLiteral
*Literal
,
24 const ASTContext
&Context
) {
25 assert(Literal
->getLength() == 1 &&
26 "Only single character string should be matched");
27 assert(Literal
->getCharByteWidth() == 1 &&
28 "StrSplit doesn't support wide char");
29 std::string Result
= clang::tooling::fixit::getText(*Literal
, Context
).str();
30 bool IsRawStringLiteral
= StringRef(Result
).starts_with(R
"(R")");
31 // Since raw string literal might contain unescaped non-printable characters,
32 // we normalize them using `StringLiteral::outputString`.
33 if (IsRawStringLiteral) {
35 llvm::raw_string_ostream Stream(Result);
36 Literal->outputString(Stream);
38 // Special case: If the string contains a single quote, we just need to return
39 // a character of the single quote. This is a special case because we need to
40 // escape it in the character literal.
41 if (Result == R"("'")")
42 return std::string(R"('\'')");
44 // Now replace the " with
'.
45 std::string::size_type Pos = Result.find_first_of('"');
46 if (Pos == std::string::npos)
49 Pos = Result.find_last_of('"');
50 if (Pos == std::string::npos)
56 } // anonymous namespace
58 void FasterStrsplitDelimiterCheck::registerMatchers(MatchFinder *Finder) {
59 // Binds to one character string literals.
60 const auto SingleChar =
61 expr(ignoringParenCasts(stringLiteral(lengthIsOne()).bind("Literal")));
63 // Binds to a string_view (either absl or std) that was passed by value and
64 // constructed from string literal.
65 auto StringViewArg = ignoringElidableConstructorCall(ignoringImpCasts(
66 cxxConstructExpr(hasType(recordDecl(hasName("::absl::string_view"))),
67 hasArgument(0, ignoringParenImpCasts(SingleChar)))));
69 // Need to ignore the elidable constructor as otherwise there is no match for
72 expr(has(ignoringElidableConstructorCall(
73 ignoringParenCasts(cxxBindTemporaryExpr(has(cxxConstructExpr(
74 hasType(recordDecl(hasName("::absl::ByAnyChar"))),
75 hasArgument(0, StringViewArg))))))))
78 // Find uses of absl::StrSplit(..., "x") and absl::StrSplit(...,
79 // absl::ByAnyChar("x")) to transform them into absl::StrSplit(..., 'x
').
82 callExpr(callee(functionDecl(hasName("::absl::StrSplit"))),
83 hasArgument(1, anyOf(ByAnyCharArg, SingleChar)),
84 unless(isInTemplateInstantiation()))
88 // Find uses of absl::MaxSplits("x", N) and
89 // absl::MaxSplits(absl::ByAnyChar("x"), N) to transform them into
90 // absl::MaxSplits('x
', N).
93 callExpr(callee(functionDecl(hasName("::absl::MaxSplits"))),
94 hasArgument(0, anyOf(ByAnyCharArg,
95 ignoringParenCasts(SingleChar))),
96 unless(isInTemplateInstantiation()))),
100 void FasterStrsplitDelimiterCheck::check(
101 const MatchFinder::MatchResult &Result) {
102 const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("Literal");
104 if (Literal->getBeginLoc().isMacroID() || Literal->getEndLoc().isMacroID())
107 std::optional<std::string> Replacement =
108 makeCharacterLiteral(Literal, *Result.Context);
111 SourceRange Range = Literal->getSourceRange();
113 if (const auto *ByAnyChar = Result.Nodes.getNodeAs<Expr>("ByAnyChar"))
114 Range = ByAnyChar->getSourceRange();
117 Literal->getBeginLoc(),
118 "%select{absl::StrSplit()|absl::MaxSplits()}0 called with a string "
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),
126 } // namespace clang::tidy::abseil