xref: /llvm-project/clang-tools-extra/clang-tidy/objc/NSDateFormatterCheck.cpp (revision 7d2ea6c422d3f5712b7253407005e1a465a76946)
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 
registerMatchers(MatchFinder * Finder)18 void NSDateFormatterCheck::registerMatchers(MatchFinder *Finder) {
19   // Adding matchers.
20 
21   Finder->addMatcher(
22       objcMessageExpr(hasSelector("setDateFormat:"),
23                       hasReceiverType(asString("NSDateFormatter *")),
24                       hasArgument(0, ignoringImpCasts(
25                                          objcStringLiteral().bind("str_lit")))),
26       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
isValidDatePattern(StringRef Pattern)38 bool isValidDatePattern(StringRef Pattern) {
39   return llvm::all_of(Pattern, [](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
check(const MatchFinder::MatchResult & Result)48 void NSDateFormatterCheck::check(const MatchFinder::MatchResult &Result) {
49   // Callback implementation.
50   const auto *StrExpr = Result.Nodes.getNodeAs<ObjCStringLiteral>("str_lit");
51   const StringLiteral *SL = cast<ObjCStringLiteral>(StrExpr)->getString();
52   StringRef SR = SL->getString();
53 
54   if (!isValidDatePattern(SR)) {
55     diag(StrExpr->getExprLoc(), "invalid date format specifier");
56   }
57 
58   if (SR.contains('y') && SR.contains('w') && !SR.contains('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('F')) {
64     if (!(SR.contains('e') || SR.contains('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('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('W') && !SR.contains('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('Y') && SR.contains('Q') && !SR.contains('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('Y') && SR.contains('M') && !SR.contains('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('Y') && SR.contains('D') && !SR.contains('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('Y') && SR.contains('W') && !SR.contains('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('Y') && SR.contains('F') && !SR.contains('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