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