NSDateFormatterCheck.cpp 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. //===--- NSDateFormatterCheck.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 "NSDateFormatterCheck.h"
  9. #include "clang/AST/ASTContext.h"
  10. #include "clang/ASTMatchers/ASTMatchFinder.h"
  11. #include "clang/ASTMatchers/ASTMatchers.h"
  12. using namespace clang::ast_matchers;
  13. namespace clang::tidy::objc {
  14. void NSDateFormatterCheck::registerMatchers(MatchFinder *Finder) {
  15. // Adding matchers.
  16. Finder->addMatcher(
  17. objcMessageExpr(hasSelector("setDateFormat:"),
  18. hasReceiverType(asString("NSDateFormatter *")),
  19. hasArgument(0, ignoringImpCasts(
  20. objcStringLiteral().bind("str_lit")))),
  21. this);
  22. }
  23. static char ValidDatePatternChars[] = {
  24. 'G', 'y', 'Y', 'u', 'U', 'r', 'Q', 'q', 'M', 'L', 'I', 'w', 'W', 'd',
  25. 'D', 'F', 'g', 'E', 'e', 'c', 'a', 'b', 'B', 'h', 'H', 'K', 'k', 'j',
  26. 'J', 'C', 'm', 's', 'S', 'A', 'z', 'Z', 'O', 'v', 'V', 'X', 'x'};
  27. // Checks if the string pattern used as a date format specifier is valid.
  28. // A string pattern is valid if all the letters(a-z, A-Z) in it belong to the
  29. // set of reserved characters. See:
  30. // https://www.unicode.org/reports/tr35/tr35.html#Invalid_Patterns
  31. bool isValidDatePattern(StringRef Pattern) {
  32. return llvm::all_of(Pattern, [](const auto &PatternChar) {
  33. return !isalpha(PatternChar) ||
  34. llvm::is_contained(ValidDatePatternChars, PatternChar);
  35. });
  36. }
  37. // Checks if the string pattern used as a date format specifier contains
  38. // any incorrect pattern and reports it as a warning.
  39. // See: http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
  40. void NSDateFormatterCheck::check(const MatchFinder::MatchResult &Result) {
  41. // Callback implementation.
  42. const auto *StrExpr = Result.Nodes.getNodeAs<ObjCStringLiteral>("str_lit");
  43. const StringLiteral *SL = cast<ObjCStringLiteral>(StrExpr)->getString();
  44. StringRef SR = SL->getString();
  45. if (!isValidDatePattern(SR)) {
  46. diag(StrExpr->getExprLoc(), "invalid date format specifier");
  47. }
  48. if (SR.contains('y') && SR.contains('w') && !SR.contains('Y')) {
  49. diag(StrExpr->getExprLoc(),
  50. "use of calendar year (y) with week of the year (w); "
  51. "did you mean to use week-year (Y) instead?");
  52. }
  53. if (SR.contains('F')) {
  54. if (!(SR.contains('e') || SR.contains('E'))) {
  55. diag(StrExpr->getExprLoc(),
  56. "day of week in month (F) used without day of the week (e or E); "
  57. "did you forget e (or E) in the format string?");
  58. }
  59. if (!SR.contains('M')) {
  60. diag(StrExpr->getExprLoc(),
  61. "day of week in month (F) used without the month (M); "
  62. "did you forget M in the format string?");
  63. }
  64. }
  65. if (SR.contains('W') && !SR.contains('M')) {
  66. diag(StrExpr->getExprLoc(), "Week of Month (W) used without the month (M); "
  67. "did you forget M in the format string?");
  68. }
  69. if (SR.contains('Y') && SR.contains('Q') && !SR.contains('y')) {
  70. diag(StrExpr->getExprLoc(),
  71. "use of week year (Y) with quarter number (Q); "
  72. "did you mean to use calendar year (y) instead?");
  73. }
  74. if (SR.contains('Y') && SR.contains('M') && !SR.contains('y')) {
  75. diag(StrExpr->getExprLoc(),
  76. "use of week year (Y) with month (M); "
  77. "did you mean to use calendar year (y) instead?");
  78. }
  79. if (SR.contains('Y') && SR.contains('D') && !SR.contains('y')) {
  80. diag(StrExpr->getExprLoc(),
  81. "use of week year (Y) with day of the year (D); "
  82. "did you mean to use calendar year (y) instead?");
  83. }
  84. if (SR.contains('Y') && SR.contains('W') && !SR.contains('y')) {
  85. diag(StrExpr->getExprLoc(),
  86. "use of week year (Y) with week of the month (W); "
  87. "did you mean to use calendar year (y) instead?");
  88. }
  89. if (SR.contains('Y') && SR.contains('F') && !SR.contains('y')) {
  90. diag(StrExpr->getExprLoc(),
  91. "use of week year (Y) with day of the week in month (F); "
  92. "did you mean to use calendar year (y) instead?");
  93. }
  94. }
  95. } // namespace clang::tidy::objc