diff options
| author | Devin Coughlin <dcoughlin@apple.com> | 2015-09-23 21:43:21 +0000 |
|---|---|---|
| committer | Devin Coughlin <dcoughlin@apple.com> | 2015-09-23 21:43:21 +0000 |
| commit | 9f21f68bfeedd25af8be00722b47e9d5dca9de8a (patch) | |
| tree | d8b328ccc5a7820bfcfb7f3db03795f5cea592a5 /clang/test | |
| parent | d784e6893cfbdd24b4f701e486adbab9906d4b27 (diff) | |
| download | bcm5719-llvm-9f21f68bfeedd25af8be00722b47e9d5dca9de8a.tar.gz bcm5719-llvm-9f21f68bfeedd25af8be00722b47e9d5dca9de8a.zip | |
[analyzer] Improve localizability checks for iOS / OS X.
Various improvements to the localization checker:
* Adjusted copy to be consistent with diagnostic text in other Apple
API checkers.
* Added in ~150 UIKit / AppKit methods that require localized strings in
UnlocalizedStringsChecker.
* UnlocalizedStringChecker now checks for UI methods up the class hierarchy and
UI methods that conform for a certain Objective-C protocol.
* Added in alpha version of PluralMisuseChecker and some regression tests. False
positives are still not ideal.
(This is the second attempt, with the memory issues on Linux resolved.)
A patch by Kulpreet Chilana!
Differential Revision: http://reviews.llvm.org/D12417
llvm-svn: 248432
Diffstat (limited to 'clang/test')
| -rw-r--r-- | clang/test/Analysis/localization-aggressive.m | 37 | ||||
| -rw-r--r-- | clang/test/Analysis/localization.m | 127 |
2 files changed, 154 insertions, 10 deletions
diff --git a/clang/test/Analysis/localization-aggressive.m b/clang/test/Analysis/localization-aggressive.m index 389f546d3d0..5f8a92b5df8 100644 --- a/clang/test/Analysis/localization-aggressive.m +++ b/clang/test/Analysis/localization-aggressive.m @@ -34,13 +34,21 @@ typedef struct CGPoint CGPoint; value:(NSString *)value table:(NSString *)tableName; @end -@interface UILabel : NSObject -@property(nullable, nonatomic, copy) NSString *text; +@protocol UIAccessibility - (void)accessibilitySetIdentification:(NSString *)ident; +- (void)setAccessibilityLabel:(NSString *)label; +@end +@interface UILabel : NSObject <UIAccessibility> +@property(nullable, nonatomic, copy) NSString *text; @end @interface TestObject : NSObject @property(strong) NSString *text; @end +@interface NSView : NSObject +@property (strong) NSString *toolTip; +@end +@interface NSViewSubclass : NSView +@end @interface LocalizationTestSuite : NSObject NSString *ForceLocalized(NSString *str) @@ -78,7 +86,7 @@ NSString *ForceLocalized(NSString *str) { return str; } bar = @"Unlocalized string"; } - [testLabel setText:bar]; // expected-warning {{String should be localized}} + [testLabel setText:bar]; // expected-warning {{User-facing text should use localized string macro}} } - (void)testLocalizationErrorDetectedOnNSString { @@ -88,7 +96,7 @@ NSString *ForceLocalized(NSString *str) { return str; } bar = @"Unlocalized string"; } - [bar drawAtPoint:CGPointMake(0, 0) withAttributes:nil]; // expected-warning {{String should be localized}} + [bar drawAtPoint:CGPointMake(0, 0) withAttributes:nil]; // expected-warning {{User-facing text should use localized string macro}} } - (void)testNoLocalizationErrorDetectedFromCFunction { @@ -137,7 +145,7 @@ NSString *ForceLocalized(NSString *str) { return str; } // An string literal @"Hello" inline should raise an error - (void)testInlineStringLiteralHasLocalizedState { UILabel *testLabel = [[UILabel alloc] init]; - [testLabel setText:@"Hello"]; // expected-warning {{String should be localized}} + [testLabel setText:@"Hello"]; // expected-warning {{User-facing text should use localized string macro}} } // A nil string should not raise an error @@ -176,7 +184,7 @@ NSString *ForceLocalized(NSString *str) { return str; } - (void)localizedStringAsArgument:(NSString *)argumentString { UILabel *testLabel = [[UILabel alloc] init]; - [testLabel setText:argumentString]; // expected-warning {{String should be localized}} + [testLabel setText:argumentString]; // expected-warning {{User-facing text should use localized string macro}} } // [LocalizationTestSuite unLocalizedStringMethod] returns an unlocalized string @@ -187,7 +195,7 @@ NSString *ForceLocalized(NSString *str) { return str; } UILabel *testLabel = [[UILabel alloc] init]; NSString *bar = NSLocalizedString(@"Hello", @"Comment"); - [testLabel setText:[LocalizationTestSuite unLocalizedStringMethod]]; // expected-warning {{String should be localized}} + [testLabel setText:[LocalizationTestSuite unLocalizedStringMethod]]; // expected-warning {{User-facing text should use localized string macro}} } // This is the reverse situation: accessibilitySetIdentification: doesn't care @@ -198,6 +206,21 @@ NSString *ForceLocalized(NSString *str) { return str; } [testLabel accessibilitySetIdentification:@"UnlocalizedString"]; // no-warning } +// An NSView subclass should raise a warning for methods in NSView that +// require localized strings +- (void)testRequiresLocalizationMethodFromSuperclass { + NSViewSubclass *s = [[NSViewSubclass alloc] init]; + NSString *bar = @"UnlocalizedString"; + + [s setToolTip:bar]; // expected-warning {{User-facing text should use localized string macro}} +} + +- (void)testRequiresLocalizationMethodFromProtocol { + UILabel *testLabel = [[UILabel alloc] init]; + + [testLabel setAccessibilityLabel:@"UnlocalizedString"]; // expected-warning {{User-facing text should use localized string macro}} +} + // EmptyLocalizationContextChecker tests #define HOM(s) YOLOC(s) #define YOLOC(x) NSLocalizedString(x, nil) diff --git a/clang/test/Analysis/localization.m b/clang/test/Analysis/localization.m index 429b0d79747..9270a092c1f 100644 --- a/clang/test/Analysis/localization.m +++ b/clang/test/Analysis/localization.m @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -analyze -fblocks -analyzer-store=region -analyzer-checker=alpha.osx.cocoa.NonLocalizedStringChecker -analyzer-checker=alpha.osx.cocoa.EmptyLocalizationContextChecker -verify %s +// RUN: %clang_cc1 -analyze -fblocks -analyzer-store=region -analyzer-checker=alpha.osx.cocoa.NonLocalizedStringChecker -analyzer-checker=alpha.osx.cocoa.PluralMisuseChecker -verify %s // The larger set of tests in located in localization.m. These are tests // specific for non-aggressive reporting. @@ -20,6 +20,8 @@ - (id)init; @end @interface NSString : NSObject +- (NSString *)stringByAppendingFormat:(NSString *)format, ...; ++ (instancetype)stringWithFormat:(NSString *)format, ...; @end @interface NSBundle : NSObject + (NSBundle *)mainBundle; @@ -36,11 +38,16 @@ @interface LocalizationTestSuite : NSObject int random(); +@property (assign) int unreadArticlesCount; @end - +#define MCLocalizedString(s) NSLocalizedString(s,nil); // Test cases begin here @implementation LocalizationTestSuite +NSString *KHLocalizedString(NSString* key, NSString* comment) { + return NSLocalizedString(key, comment); +} + // An object passed in as an parameter's string member // should not be considered unlocalized - (void)testObjectAsArgument:(TestObject *)argumentObject { @@ -58,7 +65,7 @@ int random(); bar = @"Unlocalized string"; } - [testLabel setText:bar]; // expected-warning {{String should be localized}} + [testLabel setText:bar]; // expected-warning {{User-facing text should use localized string macro}} } - (void)testOneCharacterStringsDoNotGiveAWarning { @@ -83,4 +90,118 @@ int random(); [testLabel setText:bar]; // no-warning } +// Plural Misuse Checker Tests +// These tests are modeled off incorrect uses of the many-one pattern +// from real projects. + +- (NSString *)test1:(int)plural { + if (plural) { + return MCLocalizedString(@"TYPE_PLURAL"); // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} + } + return MCLocalizedString(@"TYPE"); +} + +- (NSString *)test2:(int)numOfReminders { + if (numOfReminders > 0) { + 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}} + } + return nil; +} + +- (void)test3 { + NSString *count; + if (self.unreadArticlesCount > 1) + { + 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}} + } else { + count = [count stringByAppendingFormat:@"%@", KHLocalizedString(@"New Story", @"One new story")]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} + } +} + +- (NSString *)test4:(int)count { + if ( count == 1 ) + { + return [NSString stringWithFormat:KHLocalizedString(@"value.singular",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} + } else { + return [NSString stringWithFormat:KHLocalizedString(@"value.plural",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} + } +} + +- (NSString *)test5:(int)count { + int test = count == 1; + if (test) + { + return [NSString stringWithFormat:KHLocalizedString(@"value.singular",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} + } else { + return [NSString stringWithFormat:KHLocalizedString(@"value.plural",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} + } +} + +// 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) + +- (NSString *)test6:(int)sectionIndex { + int someOtherVariable = 0; + if (sectionIndex == 1) + { + // Do some other crazy stuff + if (someOtherVariable) + return KHLocalizedString(@"OK",nil); // no-warning + } else { + return KHLocalizedString(@"value.plural",nil); // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} + } + return nil; +} + +// False positives that we are not accounting for involve matching the heuristic +// of having 1 or 2 in the RHS of a BinaryOperator and having a localized string +// in the body of the IfStmt. This is seen a lot when checking for the section +// indexpath of something like a UITableView + +// - (NSString *)testNotAccountedFor:(int)sectionIndex { +// if (sectionIndex == 1) +// { +// return KHLocalizedString(@"1",nil); // false-positive +// } else if (sectionIndex == 2) { +// return KHLocalizedString(@"2",nil); // false-positive +// } else if (sectionIndex == 3) { +// return KHLocalizedString(@"3",nil); // no-false-positive +// } +// } + +// Potential test-cases to support in the future + +// - (NSString *)test7:(int)count { +// BOOL plural = count != 1; +// return KHLocalizedString(plural ? @"PluralString" : @"SingularString", @""); +// } +// +// - (NSString *)test8:(BOOL)plural { +// return KHLocalizedString(([NSString stringWithFormat:@"RELATIVE_DATE_%@_%@", ((1 == 1) ? @"FUTURE" : @"PAST"), plural ? @"PLURAL" : @"SINGULAR"])); +// } +// +// +// +// - (void)test9:(int)numberOfTimesEarned { +// NSString* localizedDescriptionKey; +// if (numberOfTimesEarned == 1) { +// localizedDescriptionKey = @"SINGULAR_%@"; +// } else { +// localizedDescriptionKey = @"PLURAL_%@_%@"; +// } +// NSLocalizedString(localizedDescriptionKey, nil); +// } +// +// - (NSString *)test10 { +// NSInteger count = self.problems.count; +// NSString *title = [NSString stringWithFormat:@"%ld Problems", (long) count]; +// if (count < 2) { +// if (count == 0) { +// title = [NSString stringWithFormat:@"No Problems Found"]; +// } else { +// title = [NSString stringWithFormat:@"%ld Problem", (long) count]; +// } +// } +// return title; +// } + @end |

