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