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 | |