136 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "PropertyDeclarationCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/ASTMatchers/ASTMatchFinder.h"12#include "llvm/Support/Regex.h"13 14using namespace clang::ast_matchers;15 16namespace clang::tidy::objc {17 18namespace {19 20// For StandardProperty the naming style is 'lowerCamelCase'.21// For CategoryProperty especially in categories of system class,22// to avoid naming conflict, the suggested naming style is23// 'abc_lowerCamelCase' (adding lowercase prefix followed by '_').24// Regardless of the style, all acronyms and initialisms should be capitalized.25enum NamingStyle {26 StandardProperty = 1,27 CategoryProperty = 2,28};29 30} // namespace31 32/// For now we will only fix 'CamelCase' or 'abc_CamelCase' property to33/// 'camelCase' or 'abc_camelCase'. For other cases the users need to34/// come up with a proper name by their own.35/// FIXME: provide fix for snake_case to snakeCase36static FixItHint generateFixItHint(const ObjCPropertyDecl *Decl,37 NamingStyle Style) {38 auto Name = Decl->getName();39 auto NewName = Decl->getName().str();40 size_t Index = 0;41 if (Style == CategoryProperty) {42 const size_t UnderScorePos = Name.find_first_of('_');43 if (UnderScorePos != llvm::StringRef::npos) {44 Index = UnderScorePos + 1;45 NewName.replace(0, Index - 1, Name.substr(0, Index - 1).lower());46 }47 }48 if (Index < Name.size()) {49 NewName[Index] = tolower(NewName[Index]);50 if (NewName != Name) {51 return FixItHint::CreateReplacement(52 CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())),53 llvm::StringRef(NewName));54 }55 }56 return {};57}58 59static std::string validPropertyNameRegex(bool UsedInMatcher) {60 // Allow any of these names:61 // foo62 // fooBar63 // url64 // urlString65 // ID66 // IDs67 // URL68 // URLString69 // bundleID70 // CIColor71 //72 // Disallow names of this form:73 // LongString74 //75 // aRbITRaRyCapS is allowed to avoid generating false positives for names76 // like isVitaminBSupplement, CProgrammingLanguage, and isBeforeM.77 const std::string StartMatcher = UsedInMatcher ? "::" : "^";78 return StartMatcher + "([a-z]|[A-Z][A-Z0-9])[a-z0-9A-Z]*$";79}80 81static bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) {82 auto RegexExp =83 llvm::Regex("^[a-zA-Z][a-zA-Z0-9]*_[a-zA-Z0-9][a-zA-Z0-9_]+$");84 return RegexExp.match(PropertyName);85}86 87static bool prefixedPropertyNameValid(llvm::StringRef PropertyName) {88 const size_t Start = PropertyName.find_first_of('_');89 assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size());90 auto Prefix = PropertyName.substr(0, Start);91 if (Prefix.lower() != Prefix) {92 return false;93 }94 auto RegexExp = llvm::Regex(llvm::StringRef(validPropertyNameRegex(false)));95 return RegexExp.match(PropertyName.substr(Start + 1));96}97 98void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) {99 Finder->addMatcher(objcPropertyDecl(100 // the property name should be in Lower Camel Case like101 // 'lowerCamelCase'102 unless(matchesName(validPropertyNameRegex(true))))103 .bind("property"),104 this);105}106 107void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) {108 const auto *MatchedDecl =109 Result.Nodes.getNodeAs<ObjCPropertyDecl>("property");110 assert(!MatchedDecl->getName().empty());111 auto *DeclContext = MatchedDecl->getDeclContext();112 auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext);113 114 if (CategoryDecl != nullptr &&115 hasCategoryPropertyPrefix(MatchedDecl->getName())) {116 if (!prefixedPropertyNameValid(MatchedDecl->getName()) ||117 CategoryDecl->IsClassExtension()) {118 const NamingStyle Style = CategoryDecl->IsClassExtension()119 ? StandardProperty120 : CategoryProperty;121 diag(MatchedDecl->getLocation(),122 "property name '%0' not using lowerCamelCase style or not prefixed "123 "in a category, according to the Apple Coding Guidelines")124 << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style);125 }126 return;127 }128 diag(MatchedDecl->getLocation(),129 "property name '%0' not using lowerCamelCase style or not prefixed in "130 "a category, according to the Apple Coding Guidelines")131 << MatchedDecl->getName()132 << generateFixItHint(MatchedDecl, StandardProperty);133}134 135} // namespace clang::tidy::objc136