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,12 @@ namespace objc { namespace { + +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[] = @@ -48,25 +55,41 @@ /// '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) { + for (size_t i = 0; i < Name.size(); ++i) { + if (Name[i] == '_') { + Index = i + 1; + break; + } + if (isUppercase(Name[i])) { + NewName[i] = tolower(Name[i]); + } + } + } + if (Index < Name.size()) { + 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) { +std::string validPropertyNameRegex(const std::vector &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 +98,29 @@ // 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(const llvm::StringRef &PropertyName) { + auto RegexExp = llvm::Regex("^[a-z]+_[a-zA-Z0-9][a-zA-Z0-9_]+$"); + return RegexExp.match(PropertyName); +} + +bool prefixedPropertyNameMatches(const llvm::StringRef &PropertyName, + const std::vector &Acronyms) { + size_t Start = 0; + while (PropertyName[Start] != '_') { + Start++; + } + auto RegexExp = + llvm::Regex(llvm::StringRef(validPropertyNameRegex(Acronyms, false))); + return RegexExp.match(llvm::StringRef(PropertyName.substr(Start + 1))); } } // namespace @@ -93,7 +135,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 +144,27 @@ const auto *MatchedDecl = Result.Nodes.getNodeAs("property"); assert(MatchedDecl->getName().size() > 0); + const auto *DeclContext = MatchedDecl->getDeclContext(); + auto NodeKind = DeclContext->getDeclKind(); + if (NodeKind == Decl::ObjCCategory && + hasCategoryPropertyPrefix(MatchedDecl->getName())) { + const auto *CategoryDecl = (const ObjCCategoryDecl *)(DeclContext); + if (!prefixedPropertyNameMatches(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,22 @@ @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 abc_camelCase; +@end + +@interface Foo () +@property(assign, nonatomic) int abc_camelCase; +// CHECK-MESSAGES: :[[@LINE-1]]:34: warning: property name 'abc_camelCase' 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