clang-tools 10.0.0

FasterStrsplitDelimiterCheck.cpp
Go to the documentation of this file.
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
14 using namespace clang::ast_matchers;
15
16 namespace clang {
17 namespace tidy {
18 namespace abseil {
19
20 namespace {
21
22 AST_MATCHER(StringLiteral, lengthIsOne) { return Node.getLength() == 1; }
23
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");
30 std::string Result = clang::tooling::fixit::getText(*Literal, Context).str();
31 bool IsRawStringLiteral = StringRef(Result).startswith(R"(R")");
32 // Since raw string literal might contain unescaped non-printable characters,
33 // we normalize them using `StringLiteral::outputString`.
34 if (IsRawStringLiteral) {
35 Result.clear();
36 llvm::raw_string_ostream Stream(Result);
37 Literal->outputString(Stream);
38 }
39 // Special case: If the string contains a single quote, we just need to return
40 // a character of the single quote. This is a special case because we need to
41 // escape it in the character literal.
42 if (Result == R"("'")")
43 return std::string(R"('\'')");
44
45 // Now replace the " with '.
46 std::string::size_type Pos = Result.find_first_of('"');
47 if (Pos == Result.npos)
48 return llvm::None;
49 Result[Pos] = '\'';
50 Pos = Result.find_last_of('"');
51 if (Pos == Result.npos)
52 return llvm::None;
53 Result[Pos] = '\'';
54 return Result;
55 }
56
57 } // anonymous namespace
58
59 void FasterStrsplitDelimiterCheck::registerMatchers(MatchFinder *Finder) {
60 if (!getLangOpts().CPlusPlus)
61 return;
62
63 // Binds to one character string literals.
64 const auto SingleChar =
65 expr(ignoringParenCasts(stringLiteral(lengthIsOne()).bind("Literal")));
66
67 // Binds to a string_view (either absl or std) that was passed by value and
68 // constructed from string literal.
69 auto StringViewArg = ignoringElidableConstructorCall(ignoringImpCasts(
70 cxxConstructExpr(hasType(recordDecl(hasName("::absl::string_view"))),
71 hasArgument(0, ignoringParenImpCasts(SingleChar)))));
72
73 // Need to ignore the elidable constructor as otherwise there is no match for
74 // c++14 and earlier.
75 auto ByAnyCharArg =
76 expr(has(ignoringElidableConstructorCall(
77 ignoringParenCasts(cxxBindTemporaryExpr(has(cxxConstructExpr(
78 hasType(recordDecl(hasName("::absl::ByAnyChar"))),
79 hasArgument(0, StringViewArg))))))))
80 .bind("ByAnyChar");
81
82 // Find uses of absl::StrSplit(..., "x") and absl::StrSplit(...,
83 // absl::ByAnyChar("x")) to transform them into absl::StrSplit(..., 'x').
84 Finder->addMatcher(callExpr(callee(functionDecl(hasName("::absl::StrSplit"))),
85 hasArgument(1, anyOf(ByAnyCharArg, SingleChar)),
86 unless(isInTemplateInstantiation()))
87 .bind("StrSplit"),
88 this);
89
90 // Find uses of absl::MaxSplits("x", N) and
91 // absl::MaxSplits(absl::ByAnyChar("x"), N) to transform them into
92 // absl::MaxSplits('x', N).
93 Finder->addMatcher(
94 callExpr(
95 callee(functionDecl(hasName("::absl::MaxSplits"))),
96 hasArgument(0, anyOf(ByAnyCharArg, ignoringParenCasts(SingleChar))),
97 unless(isInTemplateInstantiation())),
98 this);
99 }
100
101 void FasterStrsplitDelimiterCheck::check(
102 const MatchFinder::MatchResult &Result) {
103 const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("Literal");
104
105 if (Literal->getBeginLoc().isMacroID() || Literal->getEndLoc().isMacroID())
106 return;
107
108 llvm::Optional<std::string> Replacement =
109 makeCharacterLiteral(Literal, *Result.Context);
110 if (!Replacement)
111 return;
112 SourceRange Range = Literal->getSourceRange();
113
114 if (const auto *ByAnyChar = Result.Nodes.getNodeAs<Expr>("ByAnyChar"))
115 Range = ByAnyChar->getSourceRange();
116
117 diag(
118 Literal->getBeginLoc(),
119 "%select{absl::StrSplit()|absl::MaxSplits()}0 called with a string "
120 "literal "
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),
124 *Replacement);
125 }
126
127 } // namespace abseil
128 } // namespace tidy
129 } // namespace clang
clang::ast_matchers
Definition: AbseilMatcher.h:14
clang::ast_matchers::AST_MATCHER
AST_MATCHER(Expr, isMacroID)
Definition: PreferIsaOrDynCastInConditionalsCheck.cpp:18
FasterStrsplitDelimiterCheck.h
clang::clangd::getTokenRange
llvm::Optional< Range > getTokenRange(const SourceManager &SM, const LangOptions &LangOpts, SourceLocation TokLoc)
Returns the taken range at TokLoc.
Definition: SourceCode.cpp:227
Pos
Position Pos
Definition: SourceCode.cpp:772
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
Range
CharSourceRange Range
SourceRange for the file name.
Definition: IncludeOrderCheck.cpp:38