1// 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 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 {{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 accross 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 accross 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 accross 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 accross 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 accross 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 accross 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 accross 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 accross 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 accross 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