FasterStrsplitDelimiterCheck.cpp 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  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. #include "FasterStrsplitDelimiterCheck.h"
  9. #include "clang/AST/ASTContext.h"
  10. #include "clang/ASTMatchers/ASTMatchFinder.h"
  11. #include "clang/Tooling/FixIt.h"
  12. #include <optional>
  13. using namespace clang::ast_matchers;
  14. namespace clang::tidy::abseil {
  15. namespace {
  16. AST_MATCHER(StringLiteral, lengthIsOne) { return Node.getLength() == 1; }
  17. std::optional<std::string> makeCharacterLiteral(const StringLiteral *Literal,
  18. const ASTContext &Context) {
  19. assert(Literal->getLength() == 1 &&
  20. "Only single character string should be matched");
  21. assert(Literal->getCharByteWidth() == 1 &&
  22. "StrSplit doesn't support wide char");
  23. std::string Result = clang::tooling::fixit::getText(*Literal, Context).str();
  24. bool IsRawStringLiteral = StringRef(Result).startswith(R"(R")");
  25. // Since raw string literal might contain unescaped non-printable characters,
  26. // we normalize them using `StringLiteral::outputString`.
  27. if (IsRawStringLiteral) {
  28. Result.clear();
  29. llvm::raw_string_ostream Stream(Result);
  30. Literal->outputString(Stream);
  31. }
  32. // Special case: If the string contains a single quote, we just need to return
  33. // a character of the single quote. This is a special case because we need to
  34. // escape it in the character literal.
  35. if (Result == R"("'")")
  36. return std::string(R"('\'')");
  37. // Now replace the " with '.
  38. std::string::size_type Pos = Result.find_first_of('"');
  39. if (Pos == Result.npos)
  40. return std::nullopt;
  41. Result[Pos] = '\'';
  42. Pos = Result.find_last_of('"');
  43. if (Pos == Result.npos)
  44. return std::nullopt;
  45. Result[Pos] = '\'';
  46. return Result;
  47. }
  48. } // anonymous namespace
  49. void FasterStrsplitDelimiterCheck::registerMatchers(MatchFinder *Finder) {
  50. // Binds to one character string literals.
  51. const auto SingleChar =
  52. expr(ignoringParenCasts(stringLiteral(lengthIsOne()).bind("Literal")));
  53. // Binds to a string_view (either absl or std) that was passed by value and
  54. // constructed from string literal.
  55. auto StringViewArg = ignoringElidableConstructorCall(ignoringImpCasts(
  56. cxxConstructExpr(hasType(recordDecl(hasName("::absl::string_view"))),
  57. hasArgument(0, ignoringParenImpCasts(SingleChar)))));
  58. // Need to ignore the elidable constructor as otherwise there is no match for
  59. // c++14 and earlier.
  60. auto ByAnyCharArg =
  61. expr(has(ignoringElidableConstructorCall(
  62. ignoringParenCasts(cxxBindTemporaryExpr(has(cxxConstructExpr(
  63. hasType(recordDecl(hasName("::absl::ByAnyChar"))),
  64. hasArgument(0, StringViewArg))))))))
  65. .bind("ByAnyChar");
  66. // Find uses of absl::StrSplit(..., "x") and absl::StrSplit(...,
  67. // absl::ByAnyChar("x")) to transform them into absl::StrSplit(..., 'x').
  68. Finder->addMatcher(
  69. traverse(TK_AsIs,
  70. callExpr(callee(functionDecl(hasName("::absl::StrSplit"))),
  71. hasArgument(1, anyOf(ByAnyCharArg, SingleChar)),
  72. unless(isInTemplateInstantiation()))
  73. .bind("StrSplit")),
  74. this);
  75. // Find uses of absl::MaxSplits("x", N) and
  76. // absl::MaxSplits(absl::ByAnyChar("x"), N) to transform them into
  77. // absl::MaxSplits('x', N).
  78. Finder->addMatcher(
  79. traverse(TK_AsIs,
  80. callExpr(callee(functionDecl(hasName("::absl::MaxSplits"))),
  81. hasArgument(0, anyOf(ByAnyCharArg,
  82. ignoringParenCasts(SingleChar))),
  83. unless(isInTemplateInstantiation()))),
  84. this);
  85. }
  86. void FasterStrsplitDelimiterCheck::check(
  87. const MatchFinder::MatchResult &Result) {
  88. const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("Literal");
  89. if (Literal->getBeginLoc().isMacroID() || Literal->getEndLoc().isMacroID())
  90. return;
  91. std::optional<std::string> Replacement =
  92. makeCharacterLiteral(Literal, *Result.Context);
  93. if (!Replacement)
  94. return;
  95. SourceRange Range = Literal->getSourceRange();
  96. if (const auto *ByAnyChar = Result.Nodes.getNodeAs<Expr>("ByAnyChar"))
  97. Range = ByAnyChar->getSourceRange();
  98. diag(
  99. Literal->getBeginLoc(),
  100. "%select{absl::StrSplit()|absl::MaxSplits()}0 called with a string "
  101. "literal "
  102. "consisting of a single character; consider using the character overload")
  103. << (Result.Nodes.getNodeAs<CallExpr>("StrSplit") ? 0 : 1)
  104. << FixItHint::CreateReplacement(CharSourceRange::getTokenRange(Range),
  105. *Replacement);
  106. }
  107. } // namespace clang::tidy::abseil