1 // RUN: %clang_analyze_cc1 -fblocks -analyzer-output=text -analyzer-checker=optin.osx.cocoa.localizability.NonLocalizedStringChecker -analyzer-checker=alpha.osx.cocoa.localizability.PluralMisuseChecker -verify %s
3 // The larger set of tests in located in localization.m. These are tests
4 // specific for non-aggressive reporting.
6 // These declarations were reduced using Delta-Debugging from Foundation.h
10 #define NSLocalizedString(key, comment) \
11 [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil]
12 #define NSLocalizedStringFromTable(key, tbl, comment) \
13 [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:(tbl)]
14 #define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) \
15 [bundle localizedStringForKey:(key) value:@"" table:(tbl)]
16 #define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment) \
17 [bundle localizedStringForKey:(key) value:(val) table:(tbl)]
22 @interface NSString : NSObject
23 - (NSString *)stringByAppendingFormat:(NSString *)format, ...;
24 + (instancetype)stringWithFormat:(NSString *)format, ...;
26 @interface NSBundle : NSObject
27 + (NSBundle *)mainBundle;
28 - (NSString *)localizedStringForKey:(NSString *)key
29 value:(NSString *)value
30 table:(NSString *)tableName;
32 @interface UILabel : NSObject
33 @property(nullable, nonatomic, copy) NSString *text;
35 @interface TestObject : NSObject
36 @property(strong) NSString *text;
39 @interface LocalizationTestSuite : NSObject
41 @property (assign) int unreadArticlesCount;
43 #define MCLocalizedString(s) NSLocalizedString(s,nil);
44 // Test cases begin here
45 @implementation LocalizationTestSuite
47 NSString *KHLocalizedString(NSString* key, NSString* comment) {
48 return NSLocalizedString(key, comment);
51 // An object passed in as an parameter's string member
52 // should not be considered unlocalized
53 - (void)testObjectAsArgument:(TestObject *)argumentObject {
54 UILabel *testLabel = [[UILabel alloc] init];
56 [testLabel setText:[argumentObject text]]; // no-warning
57 [testLabel setText:argumentObject.text]; // no-warning
60 - (void)testLocalizationErrorDetectedOnPathway {
61 UILabel *testLabel = [[UILabel alloc] init];
62 NSString *bar = NSLocalizedString(@"Hello", @"Comment");
64 if (random()) { // expected-note {{Assuming the condition is true}} expected-note {{Taking true branch}}
65 bar = @"Unlocalized string"; // expected-note {{Non-localized string literal here}}
68 [testLabel setText:bar]; // expected-warning {{User-facing text should use localized string macro}} expected-note {{User-facing}}
71 - (void)testMultipleUnlocalizedStringsInSamePath {
72 UILabel *testLabel = [[UILabel alloc] init];
73 NSString *bar = @"Unlocalized string"; // no-note
75 bar = @"Unlocalized string"; // expected-note {{Non-localized string literal here}}
77 NSString *other = @"Other unlocalized string."; // no-note
80 NSString *same = @"Unlocalized string"; // no-note
83 [testLabel setText:bar]; // expected-warning {{User-facing text should use localized string macro}} expected-note {{User-facing}}
86 - (void)testOneCharacterStringsDoNotGiveAWarning {
87 UILabel *testLabel = [[UILabel alloc] init];
88 NSString *bar = NSLocalizedString(@"Hello", @"Comment");
94 [testLabel setText:bar]; // no-warning
97 - (void)testOneCharacterUTFStringsDoNotGiveAWarning {
98 UILabel *testLabel = [[UILabel alloc] init];
99 NSString *bar = NSLocalizedString(@"Hello", @"Comment");
105 [testLabel setText:bar]; // no-warning
109 // Suppress diagnostic about user-facing string constants when the method name
110 // contains the term "Debug".
111 - (void)debugScreen:(UILabel *)label {
112 label.text = @"Unlocalized";
115 // Plural Misuse Checker Tests
116 // These tests are modeled off incorrect uses of the many-one pattern
117 // from real projects.
119 - (NSString *)test1:(int)plural {
121 return MCLocalizedString(@"TYPE_PLURAL"); // expected-warning {{Plural cases are not supported across all languages. Use a .stringsdict file}} expected-note {{Plural}}
123 return MCLocalizedString(@"TYPE");
126 - (NSString *)test2:(int)numOfReminders {
127 if (numOfReminders > 0) {
128 return [NSString stringWithFormat:@"%@, %@", @"Test", (numOfReminders != 1) ? [NSString stringWithFormat:NSLocalizedString(@"%@ Reminders", @"Plural count of reminders"), numOfReminders] : [NSString stringWithFormat:NSLocalizedString(@"1 reminder", @"One reminder")]]; // expected-warning 2 {{Plural cases are not supported across all languages. Use a .stringsdict file}} expected-note 2 {{Plural}}
135 if (self.unreadArticlesCount > 1)
137 count = [count stringByAppendingFormat:@"%@", KHLocalizedString(@"New Stories", @"Plural count for new stories")]; // expected-warning {{Plural cases are not supported across all languages. Use a .stringsdict file}} expected-note {{Plural}}
139 count = [count stringByAppendingFormat:@"%@", KHLocalizedString(@"New Story", @"One new story")]; // expected-warning {{Plural cases are not supported across all languages. Use a .stringsdict file}} expected-note {{Plural}}
143 - (NSString *)test4:(int)count {
146 return [NSString stringWithFormat:KHLocalizedString(@"value.singular",nil), count]; // expected-warning {{Plural cases are not supported across all languages. Use a .stringsdict file}} expected-note {{Plural}}
148 return [NSString stringWithFormat:KHLocalizedString(@"value.plural",nil), count]; // expected-warning {{Plural cases are not supported across all languages. Use a .stringsdict file}} expected-note {{Plural}}
152 - (NSString *)test5:(int)count {
153 int test = count == 1;
156 return [NSString stringWithFormat:KHLocalizedString(@"value.singular",nil), count]; // expected-warning {{Plural cases are not supported across all languages. Use a .stringsdict file}} expected-note {{Plural}}
158 return [NSString stringWithFormat:KHLocalizedString(@"value.plural",nil), count]; // expected-warning {{Plural cases are not supported across all languages. Use a .stringsdict file}} expected-note {{Plural}}
162 // This tests the heuristic that the direct parent IfStmt must match the isCheckingPlurality confition to avoid false positives generated from complex code (generally the pattern we're looking for is simple If-Else)
164 - (NSString *)test6:(int)sectionIndex {
165 int someOtherVariable = 0;
166 if (sectionIndex == 1)
168 // Do some other crazy stuff
169 if (someOtherVariable)
170 return KHLocalizedString(@"OK",nil); // no-warning
172 return KHLocalizedString(@"value.plural",nil); // expected-warning {{Plural cases are not supported across all languages. Use a .stringsdict file}} expected-note {{Plural}}
177 // False positives that we are not accounting for involve matching the heuristic
178 // of having 1 or 2 in the RHS of a BinaryOperator and having a localized string
179 // in the body of the IfStmt. This is seen a lot when checking for the section
180 // indexpath of something like a UITableView
182 // - (NSString *)testNotAccountedFor:(int)sectionIndex {
183 // if (sectionIndex == 1)
185 // return KHLocalizedString(@"1",nil); // false-positive
186 // } else if (sectionIndex == 2) {
187 // return KHLocalizedString(@"2",nil); // false-positive
188 // } else if (sectionIndex == 3) {
189 // return KHLocalizedString(@"3",nil); // no-false-positive
193 // Potential test-cases to support in the future
195 // - (NSString *)test7:(int)count {
196 // BOOL plural = count != 1;
197 // return KHLocalizedString(plural ? @"PluralString" : @"SingularString", @"");
200 // - (NSString *)test8:(BOOL)plural {
201 // return KHLocalizedString(([NSString stringWithFormat:@"RELATIVE_DATE_%@_%@", ((1 == 1) ? @"FUTURE" : @"PAST"), plural ? @"PLURAL" : @"SINGULAR"]));
206 // - (void)test9:(int)numberOfTimesEarned {
207 // NSString* localizedDescriptionKey;
208 // if (numberOfTimesEarned == 1) {
209 // localizedDescriptionKey = @"SINGULAR_%@";
211 // localizedDescriptionKey = @"PLURAL_%@_%@";
213 // NSLocalizedString(localizedDescriptionKey, nil);
216 // - (NSString *)test10 {
217 // NSInteger count = self.problems.count;
218 // NSString *title = [NSString stringWithFormat:@"%ld Problems", (long) count];
221 // title = [NSString stringWithFormat:@"No Problems Found"];
223 // title = [NSString stringWithFormat:@"%ld Problem", (long) count];
232 // Suppress diagnostic about user-facing string constants when the class name
234 @interface MyDebugView : NSObject
237 @implementation MyDebugView
238 - (void)setupScreen:(UILabel *)label {
239 label.text = @"Unlocalized"; // no-warning