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