1 //===--- PropertyDeclarationCheck.cpp - clang-tidy-------------------------===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #include "PropertyDeclarationCheck.h" 11 #include <algorithm> 12 #include "../utils/OptionsUtils.h" 13 #include "clang/AST/ASTContext.h" 14 #include "clang/ASTMatchers/ASTMatchFinder.h" 15 #include "clang/Basic/CharInfo.h" 16 #include "llvm/ADT/STLExtras.h" 17 #include "llvm/ADT/StringExtras.h" 18 #include "llvm/Support/Regex.h" 19 20 using namespace clang::ast_matchers; 21 22 namespace clang { 23 namespace tidy { 24 namespace objc { 25 26 namespace { 27 28 // For StandardProperty the naming style is 'lowerCamelCase'. 29 // For CategoryProperty especially in categories of system class, 30 // to avoid naming conflict, the suggested naming style is 31 // 'abc_lowerCamelCase' (adding lowercase prefix followed by '_'). 32 enum NamingStyle { 33 StandardProperty = 1, 34 CategoryProperty = 2, 35 }; 36 37 /// The acronyms are from 38 /// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/APIAbbreviations.html#//apple_ref/doc/uid/20001285-BCIHCGAE 39 /// 40 /// Keep this list sorted. 41 constexpr llvm::StringLiteral DefaultSpecialAcronyms[] = { 42 "ACL", 43 "API", 44 "ARGB", 45 "ASCII", 46 "BGRA", 47 "CMYK", 48 "DNS", 49 "FPS", 50 "FTP", 51 "GIF", 52 "GPS", 53 "HD", 54 "HDR", 55 "HTML", 56 "HTTP", 57 "HTTPS", 58 "HUD", 59 "ID", 60 "JPG", 61 "JS", 62 "LAN", 63 "LZW", 64 "MDNS", 65 "MIDI", 66 "OS", 67 "PDF", 68 "PIN", 69 "PNG", 70 "POI", 71 "PSTN", 72 "PTR", 73 "QA", 74 "QOS", 75 "RGB", 76 "RGBA", 77 "RGBX", 78 "ROM", 79 "RPC", 80 "RTF", 81 "RTL", 82 "SDK", 83 "SSO", 84 "TCP", 85 "TIFF", 86 "TTS", 87 "UI", 88 "URI", 89 "URL", 90 "VC", 91 "VOIP", 92 "VPN", 93 "VR", 94 "WAN", 95 "XML", 96 }; 97 98 /// For now we will only fix 'CamelCase' or 'abc_CamelCase' property to 99 /// 'camelCase' or 'abc_camelCase'. For other cases the users need to 100 /// come up with a proper name by their own. 101 /// FIXME: provide fix for snake_case to snakeCase 102 FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, NamingStyle Style) { 103 auto Name = Decl->getName(); 104 auto NewName = Decl->getName().str(); 105 size_t Index = 0; 106 if (Style == CategoryProperty) { 107 Index = Name.find_first_of('_') + 1; 108 NewName.replace(0, Index - 1, Name.substr(0, Index - 1).lower()); 109 } 110 if (Index < Name.size()) { 111 NewName[Index] = tolower(NewName[Index]); 112 if (NewName != Name) { 113 return FixItHint::CreateReplacement( 114 CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())), 115 llvm::StringRef(NewName)); 116 } 117 } 118 return FixItHint(); 119 } 120 121 std::string AcronymsGroupRegex(llvm::ArrayRef<std::string> EscapedAcronyms) { 122 return "(" + 123 llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "s?|") + 124 "s?)"; 125 } 126 127 std::string validPropertyNameRegex(llvm::ArrayRef<std::string> EscapedAcronyms, 128 bool UsedInMatcher) { 129 // Allow any of these names: 130 // foo 131 // fooBar 132 // url 133 // urlString 134 // URL 135 // URLString 136 // bundleID 137 std::string StartMatcher = UsedInMatcher ? "::" : "^"; 138 std::string AcronymsMatcher = AcronymsGroupRegex(EscapedAcronyms); 139 return StartMatcher + "(" + AcronymsMatcher + "[A-Z]?)?[a-z]+[a-z0-9]*(" + 140 AcronymsMatcher + "|([A-Z][a-z0-9]+))*$"; 141 } 142 143 bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) { 144 auto RegexExp = llvm::Regex("^[a-zA-Z]+_[a-zA-Z0-9][a-zA-Z0-9_]+$"); 145 return RegexExp.match(PropertyName); 146 } 147 148 bool prefixedPropertyNameValid(llvm::StringRef PropertyName, 149 llvm::ArrayRef<std::string> Acronyms) { 150 size_t Start = PropertyName.find_first_of('_'); 151 assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size()); 152 auto Prefix = PropertyName.substr(0, Start); 153 if (Prefix.lower() != Prefix) { 154 return false; 155 } 156 auto RegexExp = 157 llvm::Regex(llvm::StringRef(validPropertyNameRegex(Acronyms, false))); 158 return RegexExp.match(PropertyName.substr(Start + 1)); 159 } 160 } // namespace 161 162 PropertyDeclarationCheck::PropertyDeclarationCheck(StringRef Name, 163 ClangTidyContext *Context) 164 : ClangTidyCheck(Name, Context), 165 SpecialAcronyms( 166 utils::options::parseStringList(Options.get("Acronyms", ""))), 167 IncludeDefaultAcronyms(Options.get("IncludeDefaultAcronyms", true)), 168 EscapedAcronyms() {} 169 170 void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) { 171 if (IncludeDefaultAcronyms) { 172 EscapedAcronyms.reserve(llvm::array_lengthof(DefaultSpecialAcronyms) + 173 SpecialAcronyms.size()); 174 // No need to regex-escape the default acronyms. 175 EscapedAcronyms.insert(EscapedAcronyms.end(), 176 std::begin(DefaultSpecialAcronyms), 177 std::end(DefaultSpecialAcronyms)); 178 } else { 179 EscapedAcronyms.reserve(SpecialAcronyms.size()); 180 } 181 // In case someone defines a prefix which includes a regex 182 // special character, regex-escape all the user-defined prefixes. 183 std::transform(SpecialAcronyms.begin(), SpecialAcronyms.end(), 184 std::back_inserter(EscapedAcronyms), 185 [](const std::string &s) { return llvm::Regex::escape(s); }); 186 Finder->addMatcher( 187 objcPropertyDecl( 188 // the property name should be in Lower Camel Case like 189 // 'lowerCamelCase' 190 unless(matchesName(validPropertyNameRegex(EscapedAcronyms, true)))) 191 .bind("property"), 192 this); 193 } 194 195 void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) { 196 const auto *MatchedDecl = 197 Result.Nodes.getNodeAs<ObjCPropertyDecl>("property"); 198 assert(MatchedDecl->getName().size() > 0); 199 auto *DeclContext = MatchedDecl->getDeclContext(); 200 auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext); 201 if (CategoryDecl != nullptr && 202 hasCategoryPropertyPrefix(MatchedDecl->getName())) { 203 if (!prefixedPropertyNameValid(MatchedDecl->getName(), EscapedAcronyms) || 204 CategoryDecl->IsClassExtension()) { 205 NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty 206 : CategoryProperty; 207 diag(MatchedDecl->getLocation(), 208 "property name '%0' not using lowerCamelCase style or not prefixed " 209 "in a category, according to the Apple Coding Guidelines") 210 << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style); 211 } 212 return; 213 } 214 diag(MatchedDecl->getLocation(), 215 "property name '%0' not using lowerCamelCase style or not prefixed in " 216 "a category, according to the Apple Coding Guidelines") 217 << MatchedDecl->getName() 218 << generateFixItHint(MatchedDecl, StandardProperty); 219 } 220 221 void PropertyDeclarationCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 222 Options.store(Opts, "Acronyms", 223 utils::options::serializeStringList(SpecialAcronyms)); 224 Options.store(Opts, "IncludeDefaultAcronyms", IncludeDefaultAcronyms); 225 } 226 227 } // namespace objc 228 } // namespace tidy 229 } // namespace clang 230