| //===--- PropertyDeclarationCheck.cpp - clang-tidy-------------------------===// | 
 | // | 
 | //                     The LLVM Compiler Infrastructure | 
 | // | 
 | // This file is distributed under the University of Illinois Open Source | 
 | // License. See LICENSE.TXT for details. | 
 | // | 
 | //===----------------------------------------------------------------------===// | 
 |  | 
 | #include "PropertyDeclarationCheck.h" | 
 | #include <algorithm> | 
 | #include "../utils/OptionsUtils.h" | 
 | #include "clang/AST/ASTContext.h" | 
 | #include "clang/ASTMatchers/ASTMatchFinder.h" | 
 | #include "clang/Basic/CharInfo.h" | 
 | #include "llvm/ADT/STLExtras.h" | 
 | #include "llvm/ADT/StringExtras.h" | 
 | #include "llvm/Support/Regex.h" | 
 |  | 
 | using namespace clang::ast_matchers; | 
 |  | 
 | namespace clang { | 
 | namespace tidy { | 
 | 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 aggregated from multiple sources including | 
 | /// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/APIAbbreviations.html#//apple_ref/doc/uid/20001285-BCIHCGAE | 
 | /// | 
 | /// Keep this list sorted. | 
 | constexpr llvm::StringLiteral DefaultSpecialAcronyms[] = { | 
 |     "[2-9]G", | 
 |     "ACL", | 
 |     "API", | 
 |     "AR", | 
 |     "ARGB", | 
 |     "ASCII", | 
 |     "AV", | 
 |     "BGRA", | 
 |     "CA", | 
 |     "CF", | 
 |     "CG", | 
 |     "CI", | 
 |     "CRC", | 
 |     "CV", | 
 |     "CMYK", | 
 |     "DNS", | 
 |     "FPS", | 
 |     "FTP", | 
 |     "GIF", | 
 |     "GL", | 
 |     "GPS", | 
 |     "GUID", | 
 |     "HD", | 
 |     "HDR", | 
 |     "HMAC", | 
 |     "HTML", | 
 |     "HTTP", | 
 |     "HTTPS", | 
 |     "HUD", | 
 |     "ID", | 
 |     "JPG", | 
 |     "JS", | 
 |     "LAN", | 
 |     "LZW", | 
 |     "MAC", | 
 |     "MD", | 
 |     "MDNS", | 
 |     "MIDI", | 
 |     "NS", | 
 |     "OS", | 
 |     "PDF", | 
 |     "PIN", | 
 |     "PNG", | 
 |     "POI", | 
 |     "PSTN", | 
 |     "PTR", | 
 |     "QA", | 
 |     "QOS", | 
 |     "RGB", | 
 |     "RGBA", | 
 |     "RGBX", | 
 |     "RIPEMD", | 
 |     "ROM", | 
 |     "RPC", | 
 |     "RTF", | 
 |     "RTL", | 
 |     "SC", | 
 |     "SDK", | 
 |     "SHA", | 
 |     "SQL", | 
 |     "SSO", | 
 |     "TCP", | 
 |     "TIFF", | 
 |     "TTS", | 
 |     "UI", | 
 |     "URI", | 
 |     "URL", | 
 |     "UUID", | 
 |     "VC", | 
 |     "VOIP", | 
 |     "VPN", | 
 |     "VR", | 
 |     "W", | 
 |     "WAN", | 
 |     "X", | 
 |     "XML", | 
 |     "Y", | 
 |     "Z", | 
 | }; | 
 |  | 
 | /// 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, 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()) { | 
 |     NewName[Index] = tolower(NewName[Index]); | 
 |     if (NewName != Name) { | 
 |       return FixItHint::CreateReplacement( | 
 |           CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())), | 
 |           llvm::StringRef(NewName)); | 
 |     } | 
 |   } | 
 |   return FixItHint(); | 
 | } | 
 |  | 
 | std::string AcronymsGroupRegex(llvm::ArrayRef<std::string> EscapedAcronyms) { | 
 |   return "(" + | 
 |          llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "s?|") + | 
 |          "s?)"; | 
 | } | 
 |  | 
 | std::string validPropertyNameRegex(llvm::ArrayRef<std::string> EscapedAcronyms, | 
 |                                    bool UsedInMatcher) { | 
 |   // Allow any of these names: | 
 |   // foo | 
 |   // fooBar | 
 |   // url | 
 |   // urlString | 
 |   // URL | 
 |   // URLString | 
 |   // bundleID | 
 |   std::string StartMatcher = UsedInMatcher ? "::" : "^"; | 
 |   std::string AcronymsMatcher = AcronymsGroupRegex(EscapedAcronyms); | 
 |   return StartMatcher + "(" + AcronymsMatcher + "[A-Z]?)?[a-z]+[a-z0-9]*(" + | 
 |          AcronymsMatcher + "|([A-Z][a-z0-9]+)|A|I)*$"; | 
 | } | 
 |  | 
 | 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<std::string> 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 | 
 |  | 
 | PropertyDeclarationCheck::PropertyDeclarationCheck(StringRef Name, | 
 |                                                    ClangTidyContext *Context) | 
 |     : ClangTidyCheck(Name, Context), | 
 |       SpecialAcronyms( | 
 |           utils::options::parseStringList(Options.get("Acronyms", ""))), | 
 |       IncludeDefaultAcronyms(Options.get("IncludeDefaultAcronyms", true)), | 
 |       EscapedAcronyms() {} | 
 |  | 
 | void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) { | 
 |   // this check should only be applied to ObjC sources. | 
 |   if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) { | 
 |     return; | 
 |   } | 
 |   if (IncludeDefaultAcronyms) { | 
 |     EscapedAcronyms.reserve(llvm::array_lengthof(DefaultSpecialAcronyms) + | 
 |                             SpecialAcronyms.size()); | 
 |     // No need to regex-escape the default acronyms. | 
 |     EscapedAcronyms.insert(EscapedAcronyms.end(), | 
 |                            std::begin(DefaultSpecialAcronyms), | 
 |                            std::end(DefaultSpecialAcronyms)); | 
 |   } else { | 
 |     EscapedAcronyms.reserve(SpecialAcronyms.size()); | 
 |   } | 
 |   // In case someone defines a prefix which includes a regex | 
 |   // special character, regex-escape all the user-defined prefixes. | 
 |   std::transform(SpecialAcronyms.begin(), SpecialAcronyms.end(), | 
 |                  std::back_inserter(EscapedAcronyms), | 
 |                  [](const std::string &s) { return llvm::Regex::escape(s); }); | 
 |   Finder->addMatcher( | 
 |       objcPropertyDecl( | 
 |           // the property name should be in Lower Camel Case like | 
 |           // 'lowerCamelCase' | 
 |           unless(matchesName(validPropertyNameRegex(EscapedAcronyms, true)))) | 
 |           .bind("property"), | 
 |       this); | 
 | } | 
 |  | 
 | void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) { | 
 |   const auto *MatchedDecl = | 
 |       Result.Nodes.getNodeAs<ObjCPropertyDecl>("property"); | 
 |   assert(MatchedDecl->getName().size() > 0); | 
 |   auto *DeclContext = MatchedDecl->getDeclContext(); | 
 |   auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext); | 
 |  | 
 |   auto AcronymsRegex = | 
 |       llvm::Regex("^" + AcronymsGroupRegex(EscapedAcronyms) + "$"); | 
 |   if (AcronymsRegex.match(MatchedDecl->getName())) { | 
 |     return; | 
 |   } | 
 |   if (CategoryDecl != nullptr && | 
 |       hasCategoryPropertyPrefix(MatchedDecl->getName())) { | 
 |     if (!prefixedPropertyNameValid(MatchedDecl->getName(), EscapedAcronyms) || | 
 |         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' 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) { | 
 |   Options.store(Opts, "Acronyms", | 
 |                 utils::options::serializeStringList(SpecialAcronyms)); | 
 |   Options.store(Opts, "IncludeDefaultAcronyms", IncludeDefaultAcronyms); | 
 | } | 
 |  | 
 | }  // namespace objc | 
 | }  // namespace tidy | 
 | }  // namespace clang |