blob: cafeac42ba13251d4c1bafc07948a532e09f701f [file] [log] [blame]
Ben Hamilton52161a52017-11-13 23:54:31 +00001//===--- PropertyDeclarationCheck.cpp - clang-tidy-------------------------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "PropertyDeclarationCheck.h"
Yan Zhang75b3b542018-01-30 01:44:00 +000011#include <algorithm>
Ben Hamilton52161a52017-11-13 23:54:31 +000012#include "../utils/OptionsUtils.h"
13#include "clang/AST/ASTContext.h"
14#include "clang/ASTMatchers/ASTMatchFinder.h"
Yan Zhang75b3b542018-01-30 01:44:00 +000015#include "clang/Basic/CharInfo.h"
Yan Zhangae5bc5ae2018-02-06 21:40:38 +000016#include "llvm/ADT/STLExtras.h"
Ben Hamilton52161a52017-11-13 23:54:31 +000017#include "llvm/ADT/StringExtras.h"
18#include "llvm/Support/Regex.h"
Ben Hamilton52161a52017-11-13 23:54:31 +000019
20using namespace clang::ast_matchers;
21
22namespace clang {
23namespace tidy {
24namespace objc {
25
26namespace {
Yan Zhang75b3b542018-01-30 01:44:00 +000027
28// For StandardProperty the naming style is 'lowerCamelCase'.
29// For CategoryProperty especially in categories of system class,
30// to avoid naming conflict, the suggested naming style is
31// 'abc_lowerCamelCase' (adding lowercase prefix followed by '_').
32enum NamingStyle {
33 StandardProperty = 1,
34 CategoryProperty = 2,
35};
36
Ben Hamilton52161a52017-11-13 23:54:31 +000037/// The acronyms are from
38/// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/APIAbbreviations.html#//apple_ref/doc/uid/20001285-BCIHCGAE
Ben Hamilton55c3a322018-01-18 20:51:24 +000039///
40/// Keep this list sorted.
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000041constexpr llvm::StringLiteral DefaultSpecialAcronyms[] = {
Yan Zhang03136142018-04-18 20:09:10 +000042 "[2-9]G",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000043 "ACL",
44 "API",
45 "ARGB",
46 "ASCII",
47 "BGRA",
Ben Hamiltond4fb9512018-05-01 14:48:51 +000048 "CA",
49 "CF",
50 "CG",
51 "CI",
52 "CV",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000053 "CMYK",
54 "DNS",
55 "FPS",
56 "FTP",
57 "GIF",
Ben Hamiltond4fb9512018-05-01 14:48:51 +000058 "GL",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000059 "GPS",
Yan Zhangee630392018-02-27 18:35:53 +000060 "GUID",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000061 "HD",
62 "HDR",
63 "HTML",
64 "HTTP",
65 "HTTPS",
66 "HUD",
67 "ID",
68 "JPG",
69 "JS",
70 "LAN",
71 "LZW",
72 "MDNS",
73 "MIDI",
Ben Hamiltond4fb9512018-05-01 14:48:51 +000074 "NS",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000075 "OS",
76 "PDF",
77 "PIN",
78 "PNG",
79 "POI",
80 "PSTN",
81 "PTR",
82 "QA",
83 "QOS",
84 "RGB",
85 "RGBA",
86 "RGBX",
87 "ROM",
88 "RPC",
89 "RTF",
90 "RTL",
Ben Hamiltond4fb9512018-05-01 14:48:51 +000091 "SC",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000092 "SDK",
93 "SSO",
94 "TCP",
95 "TIFF",
96 "TTS",
97 "UI",
98 "URI",
99 "URL",
Yan Zhangee630392018-02-27 18:35:53 +0000100 "UUID",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000101 "VC",
102 "VOIP",
103 "VPN",
104 "VR",
Yan Zhang03136142018-04-18 20:09:10 +0000105 "W",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000106 "WAN",
Yan Zhang03136142018-04-18 20:09:10 +0000107 "X",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000108 "XML",
Yan Zhang03136142018-04-18 20:09:10 +0000109 "Y",
110 "Z",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000111};
Ben Hamilton52161a52017-11-13 23:54:31 +0000112
Yan Zhang75b3b542018-01-30 01:44:00 +0000113/// For now we will only fix 'CamelCase' or 'abc_CamelCase' property to
114/// 'camelCase' or 'abc_camelCase'. For other cases the users need to
Ben Hamilton52161a52017-11-13 23:54:31 +0000115/// come up with a proper name by their own.
116/// FIXME: provide fix for snake_case to snakeCase
Yan Zhang75b3b542018-01-30 01:44:00 +0000117FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, NamingStyle Style) {
118 auto Name = Decl->getName();
119 auto NewName = Decl->getName().str();
120 size_t Index = 0;
121 if (Style == CategoryProperty) {
122 Index = Name.find_first_of('_') + 1;
123 NewName.replace(0, Index - 1, Name.substr(0, Index - 1).lower());
124 }
125 if (Index < Name.size()) {
126 NewName[Index] = tolower(NewName[Index]);
127 if (NewName != Name) {
128 return FixItHint::CreateReplacement(
129 CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())),
130 llvm::StringRef(NewName));
131 }
Ben Hamilton52161a52017-11-13 23:54:31 +0000132 }
133 return FixItHint();
134}
135
Yan Zhangae5bc5ae2018-02-06 21:40:38 +0000136std::string AcronymsGroupRegex(llvm::ArrayRef<std::string> EscapedAcronyms) {
137 return "(" +
138 llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "s?|") +
139 "s?)";
140}
141
Yan Zhang75b3b542018-01-30 01:44:00 +0000142std::string validPropertyNameRegex(llvm::ArrayRef<std::string> EscapedAcronyms,
143 bool UsedInMatcher) {
Ben Hamilton52161a52017-11-13 23:54:31 +0000144 // Allow any of these names:
145 // foo
146 // fooBar
147 // url
148 // urlString
149 // URL
150 // URLString
Yan Zhang8c298d22018-01-17 00:19:35 +0000151 // bundleID
Yan Zhang75b3b542018-01-30 01:44:00 +0000152 std::string StartMatcher = UsedInMatcher ? "::" : "^";
Yan Zhangae5bc5ae2018-02-06 21:40:38 +0000153 std::string AcronymsMatcher = AcronymsGroupRegex(EscapedAcronyms);
154 return StartMatcher + "(" + AcronymsMatcher + "[A-Z]?)?[a-z]+[a-z0-9]*(" +
155 AcronymsMatcher + "|([A-Z][a-z0-9]+))*$";
Yan Zhang75b3b542018-01-30 01:44:00 +0000156}
157
158bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) {
159 auto RegexExp = llvm::Regex("^[a-zA-Z]+_[a-zA-Z0-9][a-zA-Z0-9_]+$");
160 return RegexExp.match(PropertyName);
161}
162
163bool prefixedPropertyNameValid(llvm::StringRef PropertyName,
164 llvm::ArrayRef<std::string> Acronyms) {
165 size_t Start = PropertyName.find_first_of('_');
166 assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size());
167 auto Prefix = PropertyName.substr(0, Start);
168 if (Prefix.lower() != Prefix) {
169 return false;
170 }
171 auto RegexExp =
172 llvm::Regex(llvm::StringRef(validPropertyNameRegex(Acronyms, false)));
173 return RegexExp.match(PropertyName.substr(Start + 1));
Ben Hamilton52161a52017-11-13 23:54:31 +0000174}
175} // namespace
176
177PropertyDeclarationCheck::PropertyDeclarationCheck(StringRef Name,
178 ClangTidyContext *Context)
179 : ClangTidyCheck(Name, Context),
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000180 SpecialAcronyms(
181 utils::options::parseStringList(Options.get("Acronyms", ""))),
Yan Zhang75b3b542018-01-30 01:44:00 +0000182 IncludeDefaultAcronyms(Options.get("IncludeDefaultAcronyms", true)),
183 EscapedAcronyms() {}
Ben Hamilton52161a52017-11-13 23:54:31 +0000184
185void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) {
Yan Zhangc7faee72018-03-07 18:59:25 +0000186 // this check should only be applied to ObjC sources.
187 if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) {
188 return;
189 }
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000190 if (IncludeDefaultAcronyms) {
191 EscapedAcronyms.reserve(llvm::array_lengthof(DefaultSpecialAcronyms) +
192 SpecialAcronyms.size());
193 // No need to regex-escape the default acronyms.
194 EscapedAcronyms.insert(EscapedAcronyms.end(),
195 std::begin(DefaultSpecialAcronyms),
196 std::end(DefaultSpecialAcronyms));
197 } else {
198 EscapedAcronyms.reserve(SpecialAcronyms.size());
199 }
200 // In case someone defines a prefix which includes a regex
201 // special character, regex-escape all the user-defined prefixes.
202 std::transform(SpecialAcronyms.begin(), SpecialAcronyms.end(),
203 std::back_inserter(EscapedAcronyms),
204 [](const std::string &s) { return llvm::Regex::escape(s); });
Ben Hamilton52161a52017-11-13 23:54:31 +0000205 Finder->addMatcher(
206 objcPropertyDecl(
207 // the property name should be in Lower Camel Case like
208 // 'lowerCamelCase'
Yan Zhang75b3b542018-01-30 01:44:00 +0000209 unless(matchesName(validPropertyNameRegex(EscapedAcronyms, true))))
Ben Hamilton52161a52017-11-13 23:54:31 +0000210 .bind("property"),
211 this);
212}
213
214void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) {
215 const auto *MatchedDecl =
216 Result.Nodes.getNodeAs<ObjCPropertyDecl>("property");
217 assert(MatchedDecl->getName().size() > 0);
Yan Zhang75b3b542018-01-30 01:44:00 +0000218 auto *DeclContext = MatchedDecl->getDeclContext();
219 auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext);
Yan Zhang4f9ead22018-05-04 18:14:08 +0000220
221 auto AcronymsRegex =
222 llvm::Regex("^" + AcronymsGroupRegex(EscapedAcronyms) + "$");
223 if (AcronymsRegex.match(MatchedDecl->getName())) {
224 return;
225 }
Yan Zhang75b3b542018-01-30 01:44:00 +0000226 if (CategoryDecl != nullptr &&
227 hasCategoryPropertyPrefix(MatchedDecl->getName())) {
228 if (!prefixedPropertyNameValid(MatchedDecl->getName(), EscapedAcronyms) ||
229 CategoryDecl->IsClassExtension()) {
230 NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty
231 : CategoryProperty;
232 diag(MatchedDecl->getLocation(),
233 "property name '%0' not using lowerCamelCase style or not prefixed "
234 "in a category, according to the Apple Coding Guidelines")
235 << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style);
236 }
237 return;
238 }
Ben Hamilton52161a52017-11-13 23:54:31 +0000239 diag(MatchedDecl->getLocation(),
Yan Zhang75b3b542018-01-30 01:44:00 +0000240 "property name '%0' not using lowerCamelCase style or not prefixed in "
241 "a category, according to the Apple Coding Guidelines")
242 << MatchedDecl->getName()
243 << generateFixItHint(MatchedDecl, StandardProperty);
Ben Hamilton52161a52017-11-13 23:54:31 +0000244}
245
246void PropertyDeclarationCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
247 Options.store(Opts, "Acronyms",
248 utils::options::serializeStringList(SpecialAcronyms));
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000249 Options.store(Opts, "IncludeDefaultAcronyms", IncludeDefaultAcronyms);
Ben Hamilton52161a52017-11-13 23:54:31 +0000250}
251
252} // namespace objc
253} // namespace tidy
254} // namespace clang