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