1// RUN: %clang_analyze_cc1 -fblocks -analyzer-store=region -analyzer-output=text -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()) { // expected-note {{Assuming the condition is true}} expected-note {{Taking true branch}}
65    bar = @"Unlocalized string"; // expected-note {{Non-localized string literal here}}
66  }
67
68  [testLabel setText:bar]; // expected-warning {{User-facing text should use localized string macro}} expected-note {{User-facing}}
69}
70
71- (void)testMultipleUnlocalizedStringsInSamePath {
72  UILabel *testLabel = [[UILabel alloc] init];
73  NSString *bar = @"Unlocalized string"; // no-note
74
75  bar = @"Unlocalized string"; // expected-note {{Non-localized string literal here}}
76
77  NSString *other = @"Other unlocalized string."; // no-note
78  (void)other;
79
80  NSString *same = @"Unlocalized string"; // no-note
81  (void)same;
82
83  [testLabel setText:bar]; // expected-warning {{User-facing text should use localized string macro}} expected-note {{User-facing}}
84}
85
86- (void)testOneCharacterStringsDoNotGiveAWarning {
87  UILabel *testLabel = [[UILabel alloc] init];
88  NSString *bar = NSLocalizedString(@"Hello", @"Comment");
89
90  if (random()) {
91    bar = @"-";
92  }
93
94  [testLabel setText:bar]; // no-warning
95}
96
97- (void)testOneCharacterUTFStringsDoNotGiveAWarning {
98  UILabel *testLabel = [[UILabel alloc] init];
99  NSString *bar = NSLocalizedString(@"Hello", @"Comment");
100
101  if (random()) {
102    bar = @"\u2014";
103  }
104
105  [testLabel setText:bar]; // no-warning
106}
107
108
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";
113}
114
115// Plural Misuse Checker Tests
116// These tests are modeled off incorrect uses of the many-one pattern
117// from real projects.
118
119- (NSString *)test1:(int)plural {
120    if (plural) {
121        return MCLocalizedString(@"TYPE_PLURAL"); // expected-warning {{Plural cases are not supported across all languages. Use a .stringsdict file}} expected-note {{Plural}}
122    }
123    return MCLocalizedString(@"TYPE");
124}
125
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}}
129    }
130    return nil;
131}
132
133- (void)test3 {
134    NSString *count;
135    if (self.unreadArticlesCount > 1)
136    {
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}}
138    } else {
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}}
140    }
141}
142
143- (NSString *)test4:(int)count {
144    if ( count == 1 )
145    {
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}}
147    } else {
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}}
149    }
150}
151
152- (NSString *)test5:(int)count {
153	int test = count == 1;
154    if (test)
155    {
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}}
157    } else {
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}}
159    }
160}
161
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)
163
164- (NSString *)test6:(int)sectionIndex {
165	int someOtherVariable = 0;
166    if (sectionIndex == 1)
167    {
168		// Do some other crazy stuff
169		if (someOtherVariable)
170        	return KHLocalizedString(@"OK",nil); // no-warning
171    } else {
172        return KHLocalizedString(@"value.plural",nil); // expected-warning {{Plural cases are not supported across all languages. Use a .stringsdict file}} expected-note {{Plural}}
173    }
174	return nil;
175}
176
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
181
182// - (NSString *)testNotAccountedFor:(int)sectionIndex {
183//     if (sectionIndex == 1)
184//     {
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
190// 	}
191// }
192
193// Potential test-cases to support in the future
194
195// - (NSString *)test7:(int)count {
196//     BOOL plural = count != 1;
197//     return KHLocalizedString(plural ? @"PluralString" : @"SingularString", @"");
198// }
199//
200// - (NSString *)test8:(BOOL)plural {
201//     return KHLocalizedString(([NSString stringWithFormat:@"RELATIVE_DATE_%@_%@", ((1 == 1) ? @"FUTURE" : @"PAST"), plural ? @"PLURAL" : @"SINGULAR"]));
202// }
203//
204//
205//
206// - (void)test9:(int)numberOfTimesEarned {
207//     NSString* localizedDescriptionKey;
208//     if (numberOfTimesEarned == 1) {
209//         localizedDescriptionKey = @"SINGULAR_%@";
210//     } else {
211//         localizedDescriptionKey = @"PLURAL_%@_%@";
212//     }
213//     NSLocalizedString(localizedDescriptionKey, nil);
214// }
215//
216// - (NSString *)test10 {
217//     NSInteger count = self.problems.count;
218//     NSString *title = [NSString stringWithFormat:@"%ld Problems", (long) count];
219//     if (count < 2) {
220//         if (count == 0) {
221//             title = [NSString stringWithFormat:@"No Problems Found"];
222//         } else {
223//             title = [NSString stringWithFormat:@"%ld Problem", (long) count];
224//         }
225//     }
226//     return title;
227// }
228
229@end
230
231
232// Suppress diagnostic about user-facing string constants when the class name
233// contains "Debug"
234@interface MyDebugView : NSObject
235@end
236
237@implementation MyDebugView
238- (void)setupScreen:(UILabel *)label {
239  label.text = @"Unlocalized"; // no-warning
240}
241@end
242