clang-tools  10.0.0svn
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 
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  // contructed 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
llvm::Optional< Range > getTokenRange(const SourceManager &SM, const LangOptions &LangOpts, SourceLocation TokLoc)
Returns the taken range at TokLoc.
Definition: SourceCode.cpp:229
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
CharSourceRange Range
SourceRange for the file name.