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 40 int random(); 41 @property (assign) int unreadArticlesCount; 42 @end 43 #define MCLocalizedString(s) NSLocalizedString(s,nil); 44 // Test cases begin here 45 @implementation LocalizationTestSuite 46 47 NSString *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 // Plural Misuse Checker Tests 94 // These tests are modeled off incorrect uses of the many-one pattern 95 // from real projects. 96 97 - (NSString *)test1:(int)plural { 98 if (plural) { 99 return MCLocalizedString(@"TYPE_PLURAL"); // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} 100 } 101 return MCLocalizedString(@"TYPE"); 102 } 103 104 - (NSString *)test2:(int)numOfReminders { 105 if (numOfReminders > 0) { 106 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}} 107 } 108 return nil; 109 } 110 111 - (void)test3 { 112 NSString *count; 113 if (self.unreadArticlesCount > 1) 114 { 115 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}} 116 } else { 117 count = [count stringByAppendingFormat:@"%@", KHLocalizedString(@"New Story", @"One new story")]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} 118 } 119 } 120 121 - (NSString *)test4:(int)count { 122 if ( count == 1 ) 123 { 124 return [NSString stringWithFormat:KHLocalizedString(@"value.singular",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} 125 } else { 126 return [NSString stringWithFormat:KHLocalizedString(@"value.plural",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} 127 } 128 } 129 130 - (NSString *)test5:(int)count { 131 int test = count == 1; 132 if (test) 133 { 134 return [NSString stringWithFormat:KHLocalizedString(@"value.singular",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} 135 } else { 136 return [NSString stringWithFormat:KHLocalizedString(@"value.plural",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} 137 } 138 } 139 140 // 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) 141 142 - (NSString *)test6:(int)sectionIndex { 143 int someOtherVariable = 0; 144 if (sectionIndex == 1) 145 { 146 // Do some other crazy stuff 147 if (someOtherVariable) 148 return KHLocalizedString(@"OK",nil); // no-warning 149 } else { 150 return KHLocalizedString(@"value.plural",nil); // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} 151 } 152 return nil; 153 } 154 155 // False positives that we are not accounting for involve matching the heuristic 156 // of having 1 or 2 in the RHS of a BinaryOperator and having a localized string 157 // in the body of the IfStmt. This is seen a lot when checking for the section 158 // indexpath of something like a UITableView 159 160 // - (NSString *)testNotAccountedFor:(int)sectionIndex { 161 // if (sectionIndex == 1) 162 // { 163 // return KHLocalizedString(@"1",nil); // false-positive 164 // } else if (sectionIndex == 2) { 165 // return KHLocalizedString(@"2",nil); // false-positive 166 // } else if (sectionIndex == 3) { 167 // return KHLocalizedString(@"3",nil); // no-false-positive 168 // } 169 // } 170 171 // Potential test-cases to support in the future 172 173 // - (NSString *)test7:(int)count { 174 // BOOL plural = count != 1; 175 // return KHLocalizedString(plural ? @"PluralString" : @"SingularString", @""); 176 // } 177 // 178 // - (NSString *)test8:(BOOL)plural { 179 // return KHLocalizedString(([NSString stringWithFormat:@"RELATIVE_DATE_%@_%@", ((1 == 1) ? @"FUTURE" : @"PAST"), plural ? @"PLURAL" : @"SINGULAR"])); 180 // } 181 // 182 // 183 // 184 // - (void)test9:(int)numberOfTimesEarned { 185 // NSString* localizedDescriptionKey; 186 // if (numberOfTimesEarned == 1) { 187 // localizedDescriptionKey = @"SINGULAR_%@"; 188 // } else { 189 // localizedDescriptionKey = @"PLURAL_%@_%@"; 190 // } 191 // NSLocalizedString(localizedDescriptionKey, nil); 192 // } 193 // 194 // - (NSString *)test10 { 195 // NSInteger count = self.problems.count; 196 // NSString *title = [NSString stringWithFormat:@"%ld Problems", (long) count]; 197 // if (count < 2) { 198 // if (count == 0) { 199 // title = [NSString stringWithFormat:@"No Problems Found"]; 200 // } else { 201 // title = [NSString stringWithFormat:@"%ld Problem", (long) count]; 202 // } 203 // } 204 // return title; 205 // } 206 207 @end 208