1// RUN: %clang_cc1 -analyze -fblocks -analyzer-store=region -analyzer-checker=optin.osx.cocoa.localizability.NonLocalizedStringChecker -analyzer-checker=alpha.osx.cocoa.localizability.PluralMisuseChecker -verify  %s
2
3// The larger set of tests in located in localization.m. These are tests
4// specific for non-aggressive reporting.
5
6// These declarations were reduced using Delta-Debugging from Foundation.h
7// on Mac OS X.
8
9#define nil ((id)0)
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)]
18@interface NSObject
19+ (id)alloc;
20- (id)init;
21@end
22@interface NSString : NSObject
23- (NSString *)stringByAppendingFormat:(NSString *)format, ...;
24+ (instancetype)stringWithFormat:(NSString *)format, ...;
25@end
26@interface NSBundle : NSObject
27+ (NSBundle *)mainBundle;
28- (NSString *)localizedStringForKey:(NSString *)key
29                              value:(NSString *)value
30                              table:(NSString *)tableName;
31@end
32@interface UILabel : NSObject
33@property(nullable, nonatomic, copy) NSString *text;
34@end
35@interface TestObject : NSObject
36@property(strong) NSString *text;
37@end
38
39@interface LocalizationTestSuite : NSObject
40int random();
41@property (assign) int unreadArticlesCount;
42@end
43#define MCLocalizedString(s) NSLocalizedString(s,nil);
44// Test cases begin here
45@implementation LocalizationTestSuite
46
47NSString *KHLocalizedString(NSString* key, NSString* comment) {
48    return NSLocalizedString(key, comment);
49}
50
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];
55
56  [testLabel setText:[argumentObject text]]; // no-warning
57  [testLabel setText:argumentObject.text];   // no-warning
58}
59
60- (void)testLocalizationErrorDetectedOnPathway {
61  UILabel *testLabel = [[UILabel alloc] init];
62  NSString *bar = NSLocalizedString(@"Hello", @"Comment");
63
64  if (random()) {
65    bar = @"Unlocalized string";
66  }
67
68  [testLabel setText:bar]; // expected-warning {{User-facing text should use localized string macro}}
69}
70
71- (void)testOneCharacterStringsDoNotGiveAWarning {
72  UILabel *testLabel = [[UILabel alloc] init];
73  NSString *bar = NSLocalizedString(@"Hello", @"Comment");
74
75  if (random()) {
76    bar = @"-";
77  }
78
79  [testLabel setText:bar]; // no-warning
80}
81
82- (void)testOneCharacterUTFStringsDoNotGiveAWarning {
83  UILabel *testLabel = [[UILabel alloc] init];
84  NSString *bar = NSLocalizedString(@"Hello", @"Comment");
85
86  if (random()) {
87    bar = @"\u2014";
88  }
89
90  [testLabel setText:bar]; // no-warning
91}
92
93
94// Suppress diagnostic about user-facing string constants when the method name
95// contains the term "Debug".
96- (void)debugScreen:(UILabel *)label {
97  label.text = @"Unlocalized";
98}
99
100// Plural Misuse Checker Tests
101// These tests are modeled off incorrect uses of the many-one pattern
102// from real projects.
103
104- (NSString *)test1:(int)plural {
105    if (plural) {
106        return MCLocalizedString(@"TYPE_PLURAL"); // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}}
107    }
108    return MCLocalizedString(@"TYPE");
109}
110
111- (NSString *)test2:(int)numOfReminders {
112    if (numOfReminders > 0) {
113        return [NSString stringWithFormat:@"%@, %@", @"Test", (numOfReminders != 1) ? [NSString stringWithFormat:NSLocalizedString(@"%@ Reminders", @"Plural count of reminders"), numOfReminders] : [NSString stringWithFormat:NSLocalizedString(@"1 reminder", @"One reminder")]]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}}
114    }
115    return nil;
116}
117
118- (void)test3 {
119    NSString *count;
120    if (self.unreadArticlesCount > 1)
121    {
122        count = [count stringByAppendingFormat:@"%@", KHLocalizedString(@"New Stories", @"Plural count for new stories")]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}}
123    } else {
124        count = [count stringByAppendingFormat:@"%@",  KHLocalizedString(@"New Story", @"One new story")]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}}
125    }
126}
127
128- (NSString *)test4:(int)count {
129    if ( count == 1 )
130    {
131        return [NSString stringWithFormat:KHLocalizedString(@"value.singular",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}}
132    } else {
133        return [NSString stringWithFormat:KHLocalizedString(@"value.plural",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}}
134    }
135}
136
137- (NSString *)test5:(int)count {
138	int test = count == 1;
139    if (test)
140    {
141        return [NSString stringWithFormat:KHLocalizedString(@"value.singular",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}}
142    } else {
143        return [NSString stringWithFormat:KHLocalizedString(@"value.plural",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}}
144    }
145}
146
147// 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)
148
149- (NSString *)test6:(int)sectionIndex {
150	int someOtherVariable = 0;
151    if (sectionIndex == 1)
152    {
153		// Do some other crazy stuff
154		if (someOtherVariable)
155        	return KHLocalizedString(@"OK",nil); // no-warning
156    } else {
157        return KHLocalizedString(@"value.plural",nil); // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}}
158    }
159	return nil;
160}
161
162// False positives that we are not accounting for involve matching the heuristic
163// of having 1 or 2 in the RHS of a BinaryOperator and having a localized string
164// in the body of the IfStmt. This is seen a lot when checking for the section
165// indexpath of something like a UITableView
166
167// - (NSString *)testNotAccountedFor:(int)sectionIndex {
168//     if (sectionIndex == 1)
169//     {
170//         return KHLocalizedString(@"1",nil); // false-positive
171//     } else if (sectionIndex == 2) {
172//     	return KHLocalizedString(@"2",nil); // false-positive
173//     } else if (sectionIndex == 3) {
174// 		return KHLocalizedString(@"3",nil); // no-false-positive
175// 	}
176// }
177
178// Potential test-cases to support in the future
179
180// - (NSString *)test7:(int)count {
181//     BOOL plural = count != 1;
182//     return KHLocalizedString(plural ? @"PluralString" : @"SingularString", @"");
183// }
184//
185// - (NSString *)test8:(BOOL)plural {
186//     return KHLocalizedString(([NSString stringWithFormat:@"RELATIVE_DATE_%@_%@", ((1 == 1) ? @"FUTURE" : @"PAST"), plural ? @"PLURAL" : @"SINGULAR"]));
187// }
188//
189//
190//
191// - (void)test9:(int)numberOfTimesEarned {
192//     NSString* localizedDescriptionKey;
193//     if (numberOfTimesEarned == 1) {
194//         localizedDescriptionKey = @"SINGULAR_%@";
195//     } else {
196//         localizedDescriptionKey = @"PLURAL_%@_%@";
197//     }
198//     NSLocalizedString(localizedDescriptionKey, nil);
199// }
200//
201// - (NSString *)test10 {
202//     NSInteger count = self.problems.count;
203//     NSString *title = [NSString stringWithFormat:@"%ld Problems", (long) count];
204//     if (count < 2) {
205//         if (count == 0) {
206//             title = [NSString stringWithFormat:@"No Problems Found"];
207//         } else {
208//             title = [NSString stringWithFormat:@"%ld Problem", (long) count];
209//         }
210//     }
211//     return title;
212// }
213
214@end
215
216
217// Suppress diagnostic about user-facing string constants when the class name
218// contains "Debug"
219@interface MyDebugView : NSObject
220@end
221
222@implementation MyDebugView
223- (void)setupScreen:(UILabel *)label {
224  label.text = @"Unlocalized";
225}
226@end
227