forked from OSchip/llvm-project
130 lines
4.9 KiB
C++
130 lines
4.9 KiB
C++
//===--- FasterStrsplitDelimiterCheck.cpp - clang-tidy---------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "FasterStrsplitDelimiterCheck.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
#include "clang/Tooling/FixIt.h"
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
namespace clang {
|
|
namespace tidy {
|
|
namespace abseil {
|
|
|
|
namespace {
|
|
|
|
AST_MATCHER(StringLiteral, lengthIsOne) { return Node.getLength() == 1; }
|
|
|
|
llvm::Optional<std::string> makeCharacterLiteral(const StringLiteral *Literal,
|
|
const ASTContext &Context) {
|
|
assert(Literal->getLength() == 1 &&
|
|
"Only single character string should be matched");
|
|
assert(Literal->getCharByteWidth() == 1 &&
|
|
"StrSplit doesn't support wide char");
|
|
std::string Result = clang::tooling::fixit::getText(*Literal, Context).str();
|
|
bool IsRawStringLiteral = StringRef(Result).startswith(R"(R")");
|
|
// Since raw string literal might contain unescaped non-printable characters,
|
|
// we normalize them using `StringLiteral::outputString`.
|
|
if (IsRawStringLiteral) {
|
|
Result.clear();
|
|
llvm::raw_string_ostream Stream(Result);
|
|
Literal->outputString(Stream);
|
|
}
|
|
// Special case: If the string contains a single quote, we just need to return
|
|
// a character of the single quote. This is a special case because we need to
|
|
// escape it in the character literal.
|
|
if (Result == R"("'")")
|
|
return std::string(R"('\'')");
|
|
|
|
// Now replace the " with '.
|
|
std::string::size_type Pos = Result.find_first_of('"');
|
|
if (Pos == Result.npos)
|
|
return llvm::None;
|
|
Result[Pos] = '\'';
|
|
Pos = Result.find_last_of('"');
|
|
if (Pos == Result.npos)
|
|
return llvm::None;
|
|
Result[Pos] = '\'';
|
|
return Result;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
void FasterStrsplitDelimiterCheck::registerMatchers(MatchFinder *Finder) {
|
|
// Binds to one character string literals.
|
|
const auto SingleChar =
|
|
expr(ignoringParenCasts(stringLiteral(lengthIsOne()).bind("Literal")));
|
|
|
|
// Binds to a string_view (either absl or std) that was passed by value and
|
|
// constructed from string literal.
|
|
auto StringViewArg = ignoringElidableConstructorCall(ignoringImpCasts(
|
|
cxxConstructExpr(hasType(recordDecl(hasName("::absl::string_view"))),
|
|
hasArgument(0, ignoringParenImpCasts(SingleChar)))));
|
|
|
|
// Need to ignore the elidable constructor as otherwise there is no match for
|
|
// c++14 and earlier.
|
|
auto ByAnyCharArg =
|
|
expr(has(ignoringElidableConstructorCall(
|
|
ignoringParenCasts(cxxBindTemporaryExpr(has(cxxConstructExpr(
|
|
hasType(recordDecl(hasName("::absl::ByAnyChar"))),
|
|
hasArgument(0, StringViewArg))))))))
|
|
.bind("ByAnyChar");
|
|
|
|
// Find uses of absl::StrSplit(..., "x") and absl::StrSplit(...,
|
|
// absl::ByAnyChar("x")) to transform them into absl::StrSplit(..., 'x').
|
|
Finder->addMatcher(
|
|
traverse(TK_AsIs,
|
|
callExpr(callee(functionDecl(hasName("::absl::StrSplit"))),
|
|
hasArgument(1, anyOf(ByAnyCharArg, SingleChar)),
|
|
unless(isInTemplateInstantiation()))
|
|
.bind("StrSplit")),
|
|
this);
|
|
|
|
// Find uses of absl::MaxSplits("x", N) and
|
|
// absl::MaxSplits(absl::ByAnyChar("x"), N) to transform them into
|
|
// absl::MaxSplits('x', N).
|
|
Finder->addMatcher(
|
|
traverse(TK_AsIs,
|
|
callExpr(callee(functionDecl(hasName("::absl::MaxSplits"))),
|
|
hasArgument(0, anyOf(ByAnyCharArg,
|
|
ignoringParenCasts(SingleChar))),
|
|
unless(isInTemplateInstantiation()))),
|
|
this);
|
|
}
|
|
|
|
void FasterStrsplitDelimiterCheck::check(
|
|
const MatchFinder::MatchResult &Result) {
|
|
const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("Literal");
|
|
|
|
if (Literal->getBeginLoc().isMacroID() || Literal->getEndLoc().isMacroID())
|
|
return;
|
|
|
|
llvm::Optional<std::string> Replacement =
|
|
makeCharacterLiteral(Literal, *Result.Context);
|
|
if (!Replacement)
|
|
return;
|
|
SourceRange Range = Literal->getSourceRange();
|
|
|
|
if (const auto *ByAnyChar = Result.Nodes.getNodeAs<Expr>("ByAnyChar"))
|
|
Range = ByAnyChar->getSourceRange();
|
|
|
|
diag(
|
|
Literal->getBeginLoc(),
|
|
"%select{absl::StrSplit()|absl::MaxSplits()}0 called with a string "
|
|
"literal "
|
|
"consisting of a single character; consider using the character overload")
|
|
<< (Result.Nodes.getNodeAs<CallExpr>("StrSplit") ? 0 : 1)
|
|
<< FixItHint::CreateReplacement(CharSourceRange::getTokenRange(Range),
|
|
*Replacement);
|
|
}
|
|
|
|
} // namespace abseil
|
|
} // namespace tidy
|
|
} // namespace clang
|