Home | History | Annotate | Download | only in Analysis
      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