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