1//===--- FasterStrsplitDelimiterCheck.cpp - clang-tidy---------------------===//
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-exception
6//
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
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(Node: *Literal, Context).str();
30 bool IsRawStringLiteral = StringRef(Result).starts_with(Prefix: R"(R")");
31 // Since raw string literal might contain unescaped non-printable characters,
32 // we normalize them using `StringLiteral::outputString`.
33 if (IsRawStringLiteral) {
34 Result.clear();
35 llvm::raw_string_ostream Stream(Result);
36 Literal->outputString(OS&: Stream);
37 }
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"('\'')");
43
44 // Now replace the " with '.
45 std::string::size_type Pos = Result.find_first_of(c: '"');
46 if (Pos == std::string::npos)
47 return std::nullopt;
48 Result[Pos] = '\'';
49 Pos = Result.find_last_of(c: '"');
50 if (Pos == std::string::npos)
51 return std::nullopt;
52 Result[Pos] = '\'';
53 return Result;
54}
55
56} // anonymous namespace
57
58void FasterStrsplitDelimiterCheck::registerMatchers(MatchFinder *Finder) {
59 // Binds to one character string literals.
60 const auto SingleChar =
61 expr(ignoringParenCasts(InnerMatcher: stringLiteral(lengthIsOne()).bind(ID: "Literal")));
62
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(InnerMatcher: ignoringImpCasts(
66 InnerMatcher: cxxConstructExpr(hasType(InnerMatcher: recordDecl(hasName(Name: "::absl::string_view"))),
67 hasArgument(N: 0, InnerMatcher: ignoringParenImpCasts(InnerMatcher: SingleChar)))));
68
69 // Need to ignore the elidable constructor as otherwise there is no match for
70 // c++14 and earlier.
71 auto ByAnyCharArg =
72 expr(has(ignoringElidableConstructorCall(
73 InnerMatcher: ignoringParenCasts(InnerMatcher: cxxBindTemporaryExpr(has(cxxConstructExpr(
74 hasType(InnerMatcher: recordDecl(hasName(Name: "::absl::ByAnyChar"))),
75 hasArgument(N: 0, InnerMatcher: StringViewArg))))))))
76 .bind(ID: "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 NodeMatch: traverse(TK: TK_AsIs,
82 InnerMatcher: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "::absl::StrSplit"))),
83 hasArgument(N: 1, InnerMatcher: anyOf(ByAnyCharArg, SingleChar)),
84 unless(isInTemplateInstantiation()))
85 .bind(ID: "StrSplit")),
86 Action: this);
87
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).
91 Finder->addMatcher(
92 NodeMatch: traverse(TK: TK_AsIs,
93 InnerMatcher: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "::absl::MaxSplits"))),
94 hasArgument(N: 0, InnerMatcher: anyOf(ByAnyCharArg,
95 ignoringParenCasts(InnerMatcher: SingleChar))),
96 unless(isInTemplateInstantiation()))),
97 Action: this);
98}
99
100void FasterStrsplitDelimiterCheck::check(
101 const MatchFinder::MatchResult &Result) {
102 const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>(ID: "Literal");
103
104 if (Literal->getBeginLoc().isMacroID() || Literal->getEndLoc().isMacroID())
105 return;
106
107 std::optional<std::string> Replacement =
108 makeCharacterLiteral(Literal, Context: *Result.Context);
109 if (!Replacement)
110 return;
111 SourceRange Range = Literal->getSourceRange();
112
113 if (const auto *ByAnyChar = Result.Nodes.getNodeAs<Expr>(ID: "ByAnyChar"))
114 Range = ByAnyChar->getSourceRange();
115
116 diag(
117 Loc: Literal->getBeginLoc(),
118 Description: "%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>(ID: "StrSplit") ? 0 : 1)
122 << FixItHint::CreateReplacement(RemoveRange: CharSourceRange::getTokenRange(R: Range),
123 Code: *Replacement);
124}
125
126} // namespace clang::tidy::abseil
127

source code of clang-tools-extra/clang-tidy/abseil/FasterStrsplitDelimiterCheck.cpp