242 lines · plain
1// RUN: %clang_analyze_cc1 -fblocks -analyzer-output=text -analyzer-checker=optin.osx.cocoa.localizability.NonLocalizedStringChecker -analyzer-checker=alpha.osx.cocoa.localizability.PluralMisuseChecker -verify %s2 3// The larger set of tests in located in localization.m. These are tests4// specific for non-aggressive reporting.5 6// These declarations were reduced using Delta-Debugging from Foundation.h7// 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 NSObject19+ (id)alloc;20- (id)init;21@end22@interface NSString : NSObject23- (NSString *)stringByAppendingFormat:(NSString *)format, ...;24+ (instancetype)stringWithFormat:(NSString *)format, ...;25@end26@interface NSBundle : NSObject27+ (NSBundle *)mainBundle;28- (NSString *)localizedStringForKey:(NSString *)key29 value:(NSString *)value30 table:(NSString *)tableName;31@end32@interface UILabel : NSObject33@property(nullable, nonatomic, copy) NSString *text;34@end35@interface TestObject : NSObject36@property(strong) NSString *text;37@end38 39@interface LocalizationTestSuite : NSObject40int random(void);41@property (assign) int unreadArticlesCount;42@end43#define MCLocalizedString(s) NSLocalizedString(s,nil);44// Test cases begin here45@implementation LocalizationTestSuite46 47NSString *KHLocalizedString(NSString* key, NSString* comment) {48 return NSLocalizedString(key, comment);49}50 51// An object passed in as an parameter's string member52// should not be considered unlocalized53- (void)testObjectAsArgument:(TestObject *)argumentObject {54 UILabel *testLabel = [[UILabel alloc] init];55 56 [testLabel setText:[argumentObject text]]; // no-warning57 [testLabel setText:argumentObject.text]; // no-warning58}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-note74 75 bar = @"Unlocalized string"; // expected-note {{Non-localized string literal here}}76 77 NSString *other = @"Other unlocalized string."; // no-note78 (void)other;79 80 NSString *same = @"Unlocalized string"; // no-note81 (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-warning95}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-warning106}107 108 109// Suppress diagnostic about user-facing string constants when the method name110// contains the term "Debug".111- (void)debugScreen:(UILabel *)label {112 label.text = @"Unlocalized";113}114 115// Plural Misuse Checker Tests116// These tests are modeled off incorrect uses of the many-one pattern117// 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 stuff169 if (someOtherVariable)170 return KHLocalizedString(@"OK",nil); // no-warning171 } 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 heuristic178// 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 section180// indexpath of something like a UITableView181 182// - (NSString *)testNotAccountedFor:(int)sectionIndex {183// if (sectionIndex == 1)184// {185// return KHLocalizedString(@"1",nil); // false-positive186// } else if (sectionIndex == 2) {187// return KHLocalizedString(@"2",nil); // false-positive188// } else if (sectionIndex == 3) {189// return KHLocalizedString(@"3",nil); // no-false-positive190// }191// }192 193// Potential test-cases to support in the future194 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@end230 231 232// Suppress diagnostic about user-facing string constants when the class name233// contains "Debug"234@interface MyDebugView : NSObject235@end236 237@implementation MyDebugView238- (void)setupScreen:(UILabel *)label {239 label.text = @"Unlocalized"; // no-warning240}241@end242