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 |
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 | RemoveRange: CharSourceRange::getTokenRange(R: SourceRange(Decl->getLocation())), |
52 | Code: llvm::StringRef(NewName)); |
53 | } |
54 | } |
55 | return {}; |
56 | } |
57 | |
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 | |
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(String: PropertyName); |
84 | } |
85 | |
86 | bool prefixedPropertyNameValid(llvm::StringRef PropertyName) { |
87 | size_t Start = PropertyName.find_first_of(C: '_'); |
88 | assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size()); |
89 | auto Prefix = PropertyName.substr(Start: 0, N: Start); |
90 | if (Prefix.lower() != Prefix) { |
91 | return false; |
92 | } |
93 | auto RegexExp = llvm::Regex(llvm::StringRef(validPropertyNameRegex(UsedInMatcher: false))); |
94 | return RegexExp.match(String: PropertyName.substr(Start: Start + 1)); |
95 | } |
96 | } // namespace |
97 | |
98 | void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) { |
99 | Finder->addMatcher(NodeMatch: objcPropertyDecl( |
100 | // the property name should be in Lower Camel Case like |
101 | // 'lowerCamelCase' |
102 | unless(matchesName(RegExp: validPropertyNameRegex(UsedInMatcher: true)))) |
103 | .bind(ID: "property" ), |
104 | Action: this); |
105 | } |
106 | |
107 | void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) { |
108 | const auto *MatchedDecl = |
109 | Result.Nodes.getNodeAs<ObjCPropertyDecl>(ID: "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(Decl: 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(Decl: MatchedDecl, Style: StandardProperty); |
132 | } |
133 | |
134 | } // namespace clang::tidy::objc |
135 | |