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
generateFixItHint(const ObjCPropertyDecl * Decl,NamingStyle Style)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 
validPropertyNameRegex(bool UsedInMatcher)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 
hasCategoryPropertyPrefix(llvm::StringRef PropertyName)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 
prefixedPropertyNameValid(llvm::StringRef PropertyName)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 
registerMatchers(MatchFinder * Finder)100 void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) {
101   Finder->addMatcher(objcPropertyDecl(
102                          // the property name should be in Lower Camel Case like
103                          // 'lowerCamelCase'
104                          unless(matchesName(validPropertyNameRegex(true))))
105                          .bind("property"),
106                      this);
107 }
108 
check(const MatchFinder::MatchResult & Result)109 void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) {
110   const auto *MatchedDecl =
111       Result.Nodes.getNodeAs<ObjCPropertyDecl>("property");
112   assert(MatchedDecl->getName().size() > 0);
113   auto *DeclContext = MatchedDecl->getDeclContext();
114   auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext);
115 
116   if (CategoryDecl != nullptr &&
117       hasCategoryPropertyPrefix(MatchedDecl->getName())) {
118     if (!prefixedPropertyNameValid(MatchedDecl->getName()) ||
119         CategoryDecl->IsClassExtension()) {
120       NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty
121                                                            : CategoryProperty;
122       diag(MatchedDecl->getLocation(),
123            "property name '%0' not using lowerCamelCase style or not prefixed "
124            "in a category, according to the Apple Coding Guidelines")
125           << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style);
126     }
127     return;
128   }
129   diag(MatchedDecl->getLocation(),
130        "property name '%0' not using lowerCamelCase style or not prefixed in "
131        "a category, according to the Apple Coding Guidelines")
132       << MatchedDecl->getName()
133       << generateFixItHint(MatchedDecl, StandardProperty);
134 }
135 
136 }  // namespace objc
137 }  // namespace tidy
138 }  // namespace clang
139