10#include "clang/AST/ASTContext.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12#include "clang/Tooling/FixIt.h"
21AST_MATCHER(StringLiteral, lengthIsOne) {
return Node.getLength() == 1; }
23std::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")");
33 if (IsRawStringLiteral) {
35 llvm::raw_string_ostream Stream(Result);
36 Literal->outputString(Stream);
41 if (Result == R
"("'")")
42 return std::string(R
"('\'')");
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)
60 const auto SingleChar =
61 expr(ignoringParenCasts(stringLiteral(lengthIsOne()).bind(
"Literal")));
65 auto StringViewArg = ignoringElidableConstructorCall(ignoringImpCasts(
66 cxxConstructExpr(hasType(recordDecl(hasName(
"::absl::string_view"))),
67 hasArgument(0, ignoringParenImpCasts(SingleChar)))));
72 expr(has(ignoringElidableConstructorCall(
73 ignoringParenCasts(cxxBindTemporaryExpr(has(cxxConstructExpr(
74 hasType(recordDecl(hasName(
"::absl::ByAnyChar"))),
75 hasArgument(0, StringViewArg))))))))
82 callExpr(callee(functionDecl(hasName(
"::absl::StrSplit"))),
83 hasArgument(1, anyOf(ByAnyCharArg, SingleChar)),
84 unless(isInTemplateInstantiation()))
93 callExpr(callee(functionDecl(hasName(
"::absl::MaxSplits"))),
94 hasArgument(0, anyOf(ByAnyCharArg,
95 ignoringParenCasts(SingleChar))),
96 unless(isInTemplateInstantiation()))),
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),
CharSourceRange Range
SourceRange for the file name.
::clang::DynTypedNode Node
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
AST_MATCHER(Type, isCharType)