Skip to content

Commit 95a9299

Browse files
committed
Adds the NSDateFormatter checker to clang-tidy
Differential Revision: https://reviews.llvm.org/D126097
1 parent 803386d commit 95a9299

File tree

7 files changed

+544
-1
lines changed

7 files changed

+544
-1
lines changed

clang-tools-extra/clang-tidy/objc/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ add_clang_library(clangTidyObjCModule
1010
ForbiddenSubclassingCheck.cpp
1111
MissingHashCheck.cpp
1212
NSInvocationArgumentLifetimeCheck.cpp
13+
NSDateFormatterCheck.cpp
1314
ObjCTidyModule.cpp
1415
PropertyDeclarationCheck.cpp
1516
SuperSelfCheck.cpp
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//===--- NSDateFormatterCheck.h - clang-tidy --------------------*- C++ -*-===//
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+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_OBJC_NSDATEFORMATTERCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_OBJC_NSDATEFORMATTERCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang {
15+
namespace tidy {
16+
namespace objc {
17+
18+
/// Checks the string pattern used as a date format specifier and reports
19+
/// warnings if it contains any incorrect sub-pattern.
20+
///
21+
/// For the user-facing documentation see:
22+
/// http://clang.llvm.org/extra/clang-tidy/checks/objc-NSDateFormatter.html
23+
class NSDateFormatterCheck : public ClangTidyCheck {
24+
public:
25+
NSDateFormatterCheck(StringRef Name, ClangTidyContext *Context)
26+
: ClangTidyCheck(Name, Context) {}
27+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
28+
return LangOpts.ObjC;
29+
}
30+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
31+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
32+
};
33+
34+
} // namespace objc
35+
} // namespace tidy
36+
} // namespace clang
37+
38+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_OBJC_NSDATEFORMATTERCHECK_H

clang-tools-extra/clang-tidy/objc/ObjCTidyModule.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "DeallocInCategoryCheck.h"
1515
#include "ForbiddenSubclassingCheck.h"
1616
#include "MissingHashCheck.h"
17+
#include "NSDateFormatterCheck.h"
1718
#include "NSInvocationArgumentLifetimeCheck.h"
1819
#include "PropertyDeclarationCheck.h"
1920
#include "SuperSelfCheck.h"
@@ -37,6 +38,7 @@ class ObjCModule : public ClangTidyModule {
3738
"objc-forbidden-subclassing");
3839
CheckFactories.registerCheck<MissingHashCheck>(
3940
"objc-missing-hash");
41+
CheckFactories.registerCheck<NSDateFormatterCheck>("objc-nsdate-formatter");
4042
CheckFactories.registerCheck<NSInvocationArgumentLifetimeCheck>(
4143
"objc-nsinvocation-argument-lifetime");
4244
CheckFactories.registerCheck<PropertyDeclarationCheck>(

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,6 @@ Clang-Tidy Checks
239239
`llvmlibc-implementation-in-namespace <llvmlibc/implementation-in-namespace.html>`_,
240240
`llvmlibc-restrict-system-libc-headers <llvmlibc/restrict-system-libc-headers.html>`_, "Yes"
241241
`misc-confusable-identifiers <misc/confusable-identifiers.html>`_,
242-
`misc-const-correctness <misc/const-correctness.html>`_, "Yes"
243242
`misc-definitions-in-headers <misc/definitions-in-headers.html>`_, "Yes"
244243
`misc-misleading-bidirectional <misc/misleading-bidirectional.html>`_,
245244
`misc-misleading-identifier <misc/misleading-identifier.html>`_,
@@ -295,6 +294,7 @@ Clang-Tidy Checks
295294
`objc-dealloc-in-category <objc/dealloc-in-category.html>`_,
296295
`objc-forbidden-subclassing <objc/forbidden-subclassing.html>`_,
297296
`objc-missing-hash <objc/missing-hash.html>`_,
297+
`objc-nsdate-formatter <objc/nsdate-formatter.html>`_,
298298
`objc-nsinvocation-argument-lifetime <objc/nsinvocation-argument-lifetime.html>`_, "Yes"
299299
`objc-property-declaration <objc/property-declaration.html>`_, "Yes"
300300
`objc-super-self <objc/super-self.html>`_, "Yes"
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
.. title:: clang-tidy - objc-nsdate-formatter
2+
3+
objc-nsdate-formatter
4+
=====================
5+
6+
When ``NSDateFormatter`` is used to convert an ``NSDate`` type to a ``String`` type, the user
7+
can specify a custom format string. Certain format specifiers are undesirable
8+
despite being legal. See http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns for all legal date patterns.
9+
10+
This checker reports as warnings the following string patterns in a date format specifier:
11+
12+
#. yyyy + ww : Calendar year specified with week of a week year (unless YYYY is also specified).
13+
14+
* | **Example 1:** Input Date: `29 December 2014` ; Format String: `yyyy-ww`;
15+
| Output string: `2014-01` (Wrong because it’s not the first week of 2014)
16+
17+
* | **Example 2:** Input Date: `29 December 2014` ; Format String: `dd-MM-yyyy (ww-YYYY)`;
18+
| Output string: `29-12-2014 (01-2015)` (This is correct)
19+
20+
#. F without ee/EE : Numeric day of week in a month without actual day.
21+
22+
* | **Example:** Input Date: `29 December 2014` ; Format String: `F-MM`;
23+
| Output string: `5-12` (Wrong because it reads as *5th ___ of Dec* in English)
24+
25+
#. F without MM : Numeric day of week in a month without month.
26+
27+
* | **Example:** Input Date: `29 December 2014` ; Format String: `F-EE`
28+
| Output string: `5-Mon` (Wrong because it reads as *5th Mon of ___* in English)
29+
30+
#. WW without MM : Week of the month without the month.
31+
32+
* | **Example:** Input Date: `29 December 2014` ; Format String: `WW-yyyy`
33+
| Output string: `05-2014` (Wrong because it reads as *5th Week of ___* in English)
34+
35+
#. YYYY + QQ : Week year specified with quarter of normal year (unless yyyy is also specified).
36+
37+
* | **Example 1:** Input Date: `29 December 2014` ; Format String: `YYYY-QQ`
38+
| Output string: `2015-04` (Wrong because it’s not the 4th quarter of 2015)
39+
40+
* | **Example 2:** Input Date: `29 December 2014` ; Format String: `ww-YYYY (QQ-yyyy)`
41+
| Output string: `01-2015 (04-2014)` (This is correct)
42+
43+
#. YYYY + MM : Week year specified with Month of a calendar year (unless yyyy is also specified).
44+
45+
* | **Example 1:** Input Date: `29 December 2014` ; Format String: `YYYY-MM`
46+
| Output string: `2015-12` (Wrong because it’s not the 12th month of 2015)
47+
48+
* | **Example 2:** Input Date: `29 December 2014` ; Format String: `ww-YYYY (MM-yyyy)`
49+
| Output string: `01-2015 (12-2014)` (This is correct)
50+
51+
#. YYYY + DD : Week year with day of a calendar year (unless yyyy is also specified).
52+
53+
* | **Example 1:** Input Date: `29 December 2014` ; Format String: `YYYY-DD`
54+
| Output string: `2015-363` (Wrong because it’s not the 363rd day of 2015)
55+
56+
* | **Example 2:** Input Date: `29 December 2014` ; Format String: `ww-YYYY (DD-yyyy)`
57+
| Output string: `01-2015 (363-2014)` (This is correct)
58+
59+
#. YYYY + WW : Week year with week of a calendar year (unless yyyy is also specified).
60+
61+
* | **Example 1:** Input Date: `29 December 2014` ; Format String: `YYYY-WW`
62+
| Output string: `2015-05` (Wrong because it’s not the 5th week of 2015)
63+
64+
* | **Example 2:** Input Date: `29 December 2014` ; Format String: `ww-YYYY (WW-MM-yyyy)`
65+
| Output string: `01-2015 (05-12-2014)` (This is correct)
66+
67+
#. YYYY + F : Week year with day of week in a calendar month (unless yyyy is also specified).
68+
69+
* | **Example 1:** Input Date: `29 December 2014` ; Format String: `YYYY-ww-F-EE`
70+
| Output string: `2015-01-5-Mon` (Wrong because it’s not the 5th Monday of January in 2015)
71+
72+
* | **Example 2:** Input Date: `29 December 2014` ; Format String: `ww-YYYY (F-EE-MM-yyyy)`
73+
| Output string: `01-2015 (5-Mon-12-2014)` (This is correct)

0 commit comments

Comments
 (0)