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