1 //===--- PropertyDeclarationCheck.cpp - clang-tidy-------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "PropertyDeclarationCheck.h" 10 #include <algorithm> 11 #include "../utils/OptionsUtils.h" 12 #include "clang/AST/ASTContext.h" 13 #include "clang/ASTMatchers/ASTMatchFinder.h" 14 #include "clang/Basic/CharInfo.h" 15 #include "llvm/ADT/STLExtras.h" 16 #include "llvm/ADT/StringExtras.h" 17 #include "llvm/Support/Regex.h" 18 19 using namespace clang::ast_matchers; 20 21 namespace clang { 22 namespace tidy { 23 namespace objc { 24 25 namespace { 26 27 // For StandardProperty the naming style is 'lowerCamelCase'. 28 // For CategoryProperty especially in categories of system class, 29 // to avoid naming conflict, the suggested naming style is 30 // 'abc_lowerCamelCase' (adding lowercase prefix followed by '_'). 31 // Regardless of the style, all acronyms and initialisms should be capitalized. 32 enum NamingStyle { 33 StandardProperty = 1, 34 CategoryProperty = 2, 35 }; 36 37 /// For now we will only fix 'CamelCase' or 'abc_CamelCase' property to 38 /// 'camelCase' or 'abc_camelCase'. For other cases the users need to 39 /// come up with a proper name by their own. 40 /// FIXME: provide fix for snake_case to snakeCase 41 FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, NamingStyle Style) { 42 auto Name = Decl->getName(); 43 auto NewName = Decl->getName().str(); 44 size_t Index = 0; 45 if (Style == CategoryProperty) { 46 Index = Name.find_first_of('_') + 1; 47 NewName.replace(0, Index - 1, Name.substr(0, Index - 1).lower()); 48 } 49 if (Index < Name.size()) { 50 NewName[Index] = tolower(NewName[Index]); 51 if (NewName != Name) { 52 return FixItHint::CreateReplacement( 53 CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())), 54 llvm::StringRef(NewName)); 55 } 56 } 57 return FixItHint(); 58 } 59 60 std::string validPropertyNameRegex(bool UsedInMatcher) { 61 // Allow any of these names: 62 // foo 63 // fooBar 64 // url 65 // urlString 66 // ID 67 // IDs 68 // URL 69 // URLString 70 // bundleID 71 // CIColor 72 // 73 // Disallow names of this form: 74 // LongString 75 // 76 // aRbITRaRyCapS is allowed to avoid generating false positives for names 77 // like isVitaminBSupplement, CProgrammingLanguage, and isBeforeM. 78 std::string StartMatcher = UsedInMatcher ? "::" : "^"; 79 return StartMatcher + "([a-z]|[A-Z][A-Z0-9])[a-z0-9A-Z]*$"; 80 } 81 82 bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) { 83 auto RegexExp = 84 llvm::Regex("^[a-zA-Z][a-zA-Z0-9]*_[a-zA-Z0-9][a-zA-Z0-9_]+$"); 85 return RegexExp.match(PropertyName); 86 } 87 88 bool prefixedPropertyNameValid(llvm::StringRef PropertyName) { 89 size_t Start = PropertyName.find_first_of('_'); 90 assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size()); 91 auto Prefix = PropertyName.substr(0, Start); 92 if (Prefix.lower() != Prefix) { 93 return false; 94 } 95 auto RegexExp = llvm::Regex(llvm::StringRef(validPropertyNameRegex(false))); 96 return RegexExp.match(PropertyName.substr(Start + 1)); 97 } 98 } // namespace 99 100 void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) { 101 // this check should only be applied to ObjC sources. 102 if (!getLangOpts().ObjC) return; 103 104 Finder->addMatcher(objcPropertyDecl( 105 // the property name should be in Lower Camel Case like 106 // 'lowerCamelCase' 107 unless(matchesName(validPropertyNameRegex(true)))) 108 .bind("property"), 109 this); 110 } 111 112 void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) { 113 const auto *MatchedDecl = 114 Result.Nodes.getNodeAs<ObjCPropertyDecl>("property"); 115 assert(MatchedDecl->getName().size() > 0); 116 auto *DeclContext = MatchedDecl->getDeclContext(); 117 auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext); 118 119 if (CategoryDecl != nullptr && 120 hasCategoryPropertyPrefix(MatchedDecl->getName())) { 121 if (!prefixedPropertyNameValid(MatchedDecl->getName()) || 122 CategoryDecl->IsClassExtension()) { 123 NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty 124 : CategoryProperty; 125 diag(MatchedDecl->getLocation(), 126 "property name '%0' not using lowerCamelCase style or not prefixed " 127 "in a category, according to the Apple Coding Guidelines") 128 << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style); 129 } 130 return; 131 } 132 diag(MatchedDecl->getLocation(), 133 "property name '%0' not using lowerCamelCase style or not prefixed in " 134 "a category, according to the Apple Coding Guidelines") 135 << MatchedDecl->getName() 136 << generateFixItHint(MatchedDecl, StandardProperty); 137 } 138 139 } // namespace objc 140 } // namespace tidy 141 } // namespace clang 142