Index: clang-tidy/objc/PropertyDeclarationCheck.cpp =================================================================== --- clang-tidy/objc/PropertyDeclarationCheck.cpp +++ clang-tidy/objc/PropertyDeclarationCheck.cpp @@ -8,12 +8,13 @@ //===----------------------------------------------------------------------===// #include "PropertyDeclarationCheck.h" +#include #include "../utils/OptionsUtils.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/CharInfo.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Regex.h" -#include using namespace clang::ast_matchers; @@ -22,6 +23,16 @@ namespace objc { namespace { + +// For StandardProperty the naming style is 'lowerCamelCase'. +// For CategoryProperty especially in categories of system class, +// to avoid naming conflict, the suggested naming style is +// 'abc_lowerCamelCase' (adding lowercase prefix followed by '_'). +enum NamingStyle { + StandardProperty = 1, + CategoryProperty = 2, +}; + /// The acronyms are from /// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/APIAbbreviations.html#//apple_ref/doc/uid/20001285-BCIHCGAE constexpr char DefaultSpecialAcronyms[] = @@ -44,29 +55,46 @@ "FTP;" "ID"; -/// For now we will only fix 'CamelCase' property to -/// 'camelCase'. For other cases the users need to +/// For now we will only fix 'CamelCase' or 'abc_CamelCase' property to +/// 'camelCase' or 'abc_camelCase'. For other cases the users need to /// come up with a proper name by their own. /// FIXME: provide fix for snake_case to snakeCase -FixItHint generateFixItHint(const ObjCPropertyDecl *Decl) { - if (isupper(Decl->getName()[0])) { - auto NewName = Decl->getName().str(); - NewName[0] = tolower(NewName[0]); - return FixItHint::CreateReplacement( - CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())), - llvm::StringRef(NewName)); +FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, NamingStyle Style) { + auto Name = Decl->getName(); + auto NewName = Decl->getName().str(); + size_t Index = 0; + if (Style == CategoryProperty) { + Index = Name.find_first_of('_') + 1; + NewName.replace(0, Index - 1, Name.substr(0, Index - 1).lower()); + } + if (Index < Name.size()) { + printf("NewName: %s and char to update %c\n", NewName.c_str(), NewName[Index]); + NewName[Index] = tolower(NewName[Index]); + if (NewName != Name) { + return FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())), + llvm::StringRef(NewName)); + } } return FixItHint(); } -std::string validPropertyNameRegex(const std::vector &Acronyms) { +// This method returns the regex to match lowerCamelCase +// property names with special acronyms. +// Due to the feature in ast matcher, we need to use '::' +// to match the start when it is used in matchers. +// Acronyms is the list of special acronyms we need to recognize, +// and UsedInMatcher param indicates whether the regex will be used +// in matchers or not. +std::string validPropertyNameRegex(llvm::ArrayRef Acronyms, + bool UsedInMatcher) { std::vector EscapedAcronyms; EscapedAcronyms.reserve(Acronyms.size()); // In case someone defines a custom prefix which includes a regex // special character, escape all the prefixes. std::transform(Acronyms.begin(), Acronyms.end(), - std::back_inserter(EscapedAcronyms), [](const std::string& s) { - return llvm::Regex::escape(s); }); + std::back_inserter(EscapedAcronyms), + [](const std::string &s) { return llvm::Regex::escape(s); }); // Allow any of these names: // foo // fooBar @@ -75,10 +103,31 @@ // URL // URLString // bundleID - return std::string("::((") + - llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "|") + - ")[A-Z]?)?[a-z]+[a-z0-9]*([A-Z][a-z0-9]+)*" + "(" + - llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "|") + ")?$"; + std::string StartMatcher = UsedInMatcher ? "::" : "^"; + + return StartMatcher + "((" + + llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "|") + + ")[A-Z]?)?[a-z]+[a-z0-9]*([A-Z][a-z0-9]+)*" + "(" + + llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "|") + + ")?$"; +} + +bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) { + auto RegexExp = llvm::Regex("^[a-zA-Z]+_[a-zA-Z0-9][a-zA-Z0-9_]+$"); + return RegexExp.match(PropertyName); +} + +bool prefixedPropertyNameValid(llvm::StringRef PropertyName, + llvm::ArrayRef Acronyms) { + size_t Start = PropertyName.find_first_of('_'); + assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size()); + auto Prefix = PropertyName.substr(0, Start); + if (Prefix.lower() != Prefix) { + return false; + } + auto RegexExp = + llvm::Regex(llvm::StringRef(validPropertyNameRegex(Acronyms, false))); + return RegexExp.match(PropertyName.substr(Start + 1)); } } // namespace @@ -93,7 +142,7 @@ objcPropertyDecl( // the property name should be in Lower Camel Case like // 'lowerCamelCase' - unless(matchesName(validPropertyNameRegex(SpecialAcronyms)))) + unless(matchesName(validPropertyNameRegex(SpecialAcronyms, true)))) .bind("property"), this); } @@ -102,10 +151,26 @@ const auto *MatchedDecl = Result.Nodes.getNodeAs("property"); assert(MatchedDecl->getName().size() > 0); + auto *DeclContext = MatchedDecl->getDeclContext(); + auto *CategoryDecl = llvm::dyn_cast(DeclContext); + if (CategoryDecl != nullptr && + hasCategoryPropertyPrefix(MatchedDecl->getName())) { + if (!prefixedPropertyNameValid(MatchedDecl->getName(), SpecialAcronyms) || + CategoryDecl->IsClassExtension()) { + NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty + : CategoryProperty; + diag(MatchedDecl->getLocation(), + "property name '%0' not using lowerCamelCase style or not prefixed " + "in a category, according to the Apple Coding Guidelines") + << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style); + } + return; + } diag(MatchedDecl->getLocation(), - "property name '%0' should use lowerCamelCase style, according to " - "the Apple Coding Guidelines") - << MatchedDecl->getName() << generateFixItHint(MatchedDecl); + "property name '%0' not using lowerCamelCase style or not prefixed in " + "a category, according to the Apple Coding Guidelines") + << MatchedDecl->getName() + << generateFixItHint(MatchedDecl, StandardProperty); } void PropertyDeclarationCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Index: docs/clang-tidy/checks/objc-property-declaration.rst =================================================================== --- docs/clang-tidy/checks/objc-property-declaration.rst +++ docs/clang-tidy/checks/objc-property-declaration.rst @@ -32,6 +32,15 @@ The corresponding style rule: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingIvarsAndTypes.html#//apple_ref/doc/uid/20001284-1001757 +The check will also accept property declared in category with a prefix of +lowercase letters followed by a '_' to avoid naming conflict. For example: + +.. code-block:: objc +@property(nonatomic, assign) int abc_lowerCamelCase; + +The corresponding style rule: https://developer.apple.com/library/content/qa/qa1908/_index.html + + Options ------- Index: test/clang-tidy/objc-property-declaration-custom.m =================================================================== --- test/clang-tidy/objc-property-declaration-custom.m +++ test/clang-tidy/objc-property-declaration-custom.m @@ -6,9 +6,9 @@ @interface Foo @property(assign, nonatomic) int AbcNotRealPrefix; -// CHECK-MESSAGES: :[[@LINE-1]]:34: warning: property name 'AbcNotRealPrefix' should use lowerCamelCase style, according to the Apple Coding Guidelines [objc-property-declaration] +// CHECK-MESSAGES: :[[@LINE-1]]:34: warning: property name 'AbcNotRealPrefix' not using lowerCamelCase style or not prefixed in a category, according to the Apple Coding Guidelines [objc-property-declaration] // CHECK-FIXES: @property(assign, nonatomic) int abcNotRealPrefix; @property(assign, nonatomic) int ABCCustomPrefix; @property(strong, nonatomic) NSString *ABC_custom_prefix; -// CHECK-MESSAGES: :[[@LINE-1]]:40: warning: property name 'ABC_custom_prefix' should use lowerCamelCase style, according to the Apple Coding Guidelines [objc-property-declaration] +// CHECK-MESSAGES: :[[@LINE-1]]:40: warning: property name 'ABC_custom_prefix' not using lowerCamelCase style or not prefixed in a category, according to the Apple Coding Guidelines [objc-property-declaration] @end Index: test/clang-tidy/objc-property-declaration.m =================================================================== --- test/clang-tidy/objc-property-declaration.m +++ test/clang-tidy/objc-property-declaration.m @@ -3,11 +3,31 @@ @interface Foo @property(assign, nonatomic) int NotCamelCase; -// CHECK-MESSAGES: :[[@LINE-1]]:34: warning: property name 'NotCamelCase' should use lowerCamelCase style, according to the Apple Coding Guidelines [objc-property-declaration] +// CHECK-MESSAGES: :[[@LINE-1]]:34: warning: property name 'NotCamelCase' not using lowerCamelCase style or not prefixed in a category, according to the Apple Coding Guidelines [objc-property-declaration] // CHECK-FIXES: @property(assign, nonatomic) int notCamelCase; @property(assign, nonatomic) int camelCase; @property(strong, nonatomic) NSString *URLString; @property(strong, nonatomic) NSString *bundleID; @property(strong, nonatomic) NSString *URL_string; -// CHECK-MESSAGES: :[[@LINE-1]]:40: warning: property name 'URL_string' should use lowerCamelCase style, according to the Apple Coding Guidelines [objc-property-declaration] +// CHECK-MESSAGES: :[[@LINE-1]]:40: warning: property name 'URL_string' not using lowerCamelCase style or not prefixed in a category, according to the Apple Coding Guidelines [objc-property-declaration] @end + +@interface Foo (Bar) +@property(assign, nonatomic) int abc_NotCamelCase; +// CHECK-MESSAGES: :[[@LINE-1]]:34: warning: property name 'abc_NotCamelCase' not using lowerCamelCase style or not prefixed in a category, according to the Apple Coding Guidelines [objc-property-declaration] +@property(assign, nonatomic) int abCD_camelCase; +// CHECK-MESSAGES: :[[@LINE-1]]:34: warning: property name 'abCD_camelCase' not using lowerCamelCase style or not prefixed in a category, according to the Apple Coding Guidelines [objc-property-declaration] +// CHECK-FIXES: @property(assign, nonatomic) int abcd_camelCase; +@property(assign, nonatomic) int abCD_NotCamelCase; +// CHECK-MESSAGES: :[[@LINE-1]]:34: warning: property name 'abCD_NotCamelCase' not using lowerCamelCase style or not prefixed in a category, according to the Apple Coding Guidelines [objc-property-declaration] +// CHECK-FIXES: @property(assign, nonatomic) int abcd_notCamelCase; +@property(assign, nonatomic) int wrongFormat_; +// CHECK-MESSAGES: :[[@LINE-1]]:34: warning: property name 'wrongFormat_' not using lowerCamelCase style or not prefixed in a category, according to the Apple Coding Guidelines [objc-property-declaration] +@property(strong, nonatomic) NSString *URLStr; +@property(assign, nonatomic) int abc_camelCase; +@end + +@interface Foo () +@property(assign, nonatomic) int abc_inClassExtension; +// CHECK-MESSAGES: :[[@LINE-1]]:34: warning: property name 'abc_inClassExtension' not using lowerCamelCase style or not prefixed in a category, according to the Apple Coding Guidelines [objc-property-declaration] +@end \ No newline at end of file