blob: 366d71f4a4ec833448f8484009f1b08968be9887 [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 Hamilton1ca21bb2018-06-07 21:30:56 +000037/// The acronyms are aggregated from multiple sources including
Ben Hamilton52161a52017-11-13 23:54:31 +000038/// 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",
Yan Zhang6a528852018-05-15 18:13:51 +000045 "AR",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000046 "ARGB",
47 "ASCII",
Yan Zhang72cecf92018-06-11 22:44:06 +000048 "AV",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000049 "BGRA",
Ben Hamiltond4fb9512018-05-01 14:48:51 +000050 "CA",
51 "CF",
52 "CG",
53 "CI",
Ben Hamilton7ea884c2018-06-27 19:13:09 +000054 "CRC",
Ben Hamiltond4fb9512018-05-01 14:48:51 +000055 "CV",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000056 "CMYK",
57 "DNS",
58 "FPS",
59 "FTP",
60 "GIF",
Ben Hamiltond4fb9512018-05-01 14:48:51 +000061 "GL",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000062 "GPS",
Yan Zhangee630392018-02-27 18:35:53 +000063 "GUID",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000064 "HD",
65 "HDR",
Ben Hamilton7ea884c2018-06-27 19:13:09 +000066 "HMAC",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000067 "HTML",
68 "HTTP",
69 "HTTPS",
70 "HUD",
71 "ID",
72 "JPG",
73 "JS",
74 "LAN",
75 "LZW",
Ben Hamilton7ea884c2018-06-27 19:13:09 +000076 "MAC",
77 "MD",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000078 "MDNS",
79 "MIDI",
Ben Hamiltond4fb9512018-05-01 14:48:51 +000080 "NS",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000081 "OS",
82 "PDF",
83 "PIN",
84 "PNG",
85 "POI",
86 "PSTN",
87 "PTR",
88 "QA",
89 "QOS",
90 "RGB",
91 "RGBA",
92 "RGBX",
Ben Hamilton7ea884c2018-06-27 19:13:09 +000093 "RIPEMD",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000094 "ROM",
95 "RPC",
96 "RTF",
97 "RTL",
Ben Hamiltond4fb9512018-05-01 14:48:51 +000098 "SC",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000099 "SDK",
Ben Hamilton7ea884c2018-06-27 19:13:09 +0000100 "SHA",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000101 "SSO",
102 "TCP",
103 "TIFF",
104 "TTS",
105 "UI",
106 "URI",
107 "URL",
Yan Zhangee630392018-02-27 18:35:53 +0000108 "UUID",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000109 "VC",
110 "VOIP",
111 "VPN",
112 "VR",
Yan Zhang03136142018-04-18 20:09:10 +0000113 "W",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000114 "WAN",
Yan Zhang03136142018-04-18 20:09:10 +0000115 "X",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000116 "XML",
Yan Zhang03136142018-04-18 20:09:10 +0000117 "Y",
118 "Z",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000119};
Ben Hamilton52161a52017-11-13 23:54:31 +0000120
Yan Zhang75b3b542018-01-30 01:44:00 +0000121/// For now we will only fix 'CamelCase' or 'abc_CamelCase' property to
122/// 'camelCase' or 'abc_camelCase'. For other cases the users need to
Ben Hamilton52161a52017-11-13 23:54:31 +0000123/// come up with a proper name by their own.
124/// FIXME: provide fix for snake_case to snakeCase
Yan Zhang75b3b542018-01-30 01:44:00 +0000125FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, NamingStyle Style) {
126 auto Name = Decl->getName();
127 auto NewName = Decl->getName().str();
128 size_t Index = 0;
129 if (Style == CategoryProperty) {
130 Index = Name.find_first_of('_') + 1;
131 NewName.replace(0, Index - 1, Name.substr(0, Index - 1).lower());
132 }
133 if (Index < Name.size()) {
134 NewName[Index] = tolower(NewName[Index]);
135 if (NewName != Name) {
136 return FixItHint::CreateReplacement(
137 CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())),
138 llvm::StringRef(NewName));
139 }
Ben Hamilton52161a52017-11-13 23:54:31 +0000140 }
141 return FixItHint();
142}
143
Yan Zhangae5bc5ae2018-02-06 21:40:38 +0000144std::string AcronymsGroupRegex(llvm::ArrayRef<std::string> EscapedAcronyms) {
145 return "(" +
146 llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "s?|") +
147 "s?)";
148}
149
Yan Zhang75b3b542018-01-30 01:44:00 +0000150std::string validPropertyNameRegex(llvm::ArrayRef<std::string> EscapedAcronyms,
151 bool UsedInMatcher) {
Ben Hamilton52161a52017-11-13 23:54:31 +0000152 // Allow any of these names:
153 // foo
154 // fooBar
155 // url
156 // urlString
157 // URL
158 // URLString
Yan Zhang8c298d22018-01-17 00:19:35 +0000159 // bundleID
Yan Zhang75b3b542018-01-30 01:44:00 +0000160 std::string StartMatcher = UsedInMatcher ? "::" : "^";
Yan Zhangae5bc5ae2018-02-06 21:40:38 +0000161 std::string AcronymsMatcher = AcronymsGroupRegex(EscapedAcronyms);
162 return StartMatcher + "(" + AcronymsMatcher + "[A-Z]?)?[a-z]+[a-z0-9]*(" +
Yan Zhang72cecf92018-06-11 22:44:06 +0000163 AcronymsMatcher + "|([A-Z][a-z0-9]+)|A|I)*$";
Yan Zhang75b3b542018-01-30 01:44:00 +0000164}
165
166bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) {
167 auto RegexExp = llvm::Regex("^[a-zA-Z]+_[a-zA-Z0-9][a-zA-Z0-9_]+$");
168 return RegexExp.match(PropertyName);
169}
170
171bool prefixedPropertyNameValid(llvm::StringRef PropertyName,
172 llvm::ArrayRef<std::string> Acronyms) {
173 size_t Start = PropertyName.find_first_of('_');
174 assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size());
175 auto Prefix = PropertyName.substr(0, Start);
176 if (Prefix.lower() != Prefix) {
177 return false;
178 }
179 auto RegexExp =
180 llvm::Regex(llvm::StringRef(validPropertyNameRegex(Acronyms, false)));
181 return RegexExp.match(PropertyName.substr(Start + 1));
Ben Hamilton52161a52017-11-13 23:54:31 +0000182}
183} // namespace
184
185PropertyDeclarationCheck::PropertyDeclarationCheck(StringRef Name,
186 ClangTidyContext *Context)
187 : ClangTidyCheck(Name, Context),
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000188 SpecialAcronyms(
189 utils::options::parseStringList(Options.get("Acronyms", ""))),
Yan Zhang75b3b542018-01-30 01:44:00 +0000190 IncludeDefaultAcronyms(Options.get("IncludeDefaultAcronyms", true)),
191 EscapedAcronyms() {}
Ben Hamilton52161a52017-11-13 23:54:31 +0000192
193void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) {
Yan Zhangc7faee72018-03-07 18:59:25 +0000194 // this check should only be applied to ObjC sources.
195 if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) {
196 return;
197 }
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000198 if (IncludeDefaultAcronyms) {
199 EscapedAcronyms.reserve(llvm::array_lengthof(DefaultSpecialAcronyms) +
200 SpecialAcronyms.size());
201 // No need to regex-escape the default acronyms.
202 EscapedAcronyms.insert(EscapedAcronyms.end(),
203 std::begin(DefaultSpecialAcronyms),
204 std::end(DefaultSpecialAcronyms));
205 } else {
206 EscapedAcronyms.reserve(SpecialAcronyms.size());
207 }
208 // In case someone defines a prefix which includes a regex
209 // special character, regex-escape all the user-defined prefixes.
210 std::transform(SpecialAcronyms.begin(), SpecialAcronyms.end(),
211 std::back_inserter(EscapedAcronyms),
212 [](const std::string &s) { return llvm::Regex::escape(s); });
Ben Hamilton52161a52017-11-13 23:54:31 +0000213 Finder->addMatcher(
214 objcPropertyDecl(
215 // the property name should be in Lower Camel Case like
216 // 'lowerCamelCase'
Yan Zhang75b3b542018-01-30 01:44:00 +0000217 unless(matchesName(validPropertyNameRegex(EscapedAcronyms, true))))
Ben Hamilton52161a52017-11-13 23:54:31 +0000218 .bind("property"),
219 this);
220}
221
222void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) {
223 const auto *MatchedDecl =
224 Result.Nodes.getNodeAs<ObjCPropertyDecl>("property");
225 assert(MatchedDecl->getName().size() > 0);
Yan Zhang75b3b542018-01-30 01:44:00 +0000226 auto *DeclContext = MatchedDecl->getDeclContext();
227 auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext);
Yan Zhang4f9ead22018-05-04 18:14:08 +0000228
229 auto AcronymsRegex =
230 llvm::Regex("^" + AcronymsGroupRegex(EscapedAcronyms) + "$");
231 if (AcronymsRegex.match(MatchedDecl->getName())) {
232 return;
233 }
Yan Zhang75b3b542018-01-30 01:44:00 +0000234 if (CategoryDecl != nullptr &&
235 hasCategoryPropertyPrefix(MatchedDecl->getName())) {
236 if (!prefixedPropertyNameValid(MatchedDecl->getName(), EscapedAcronyms) ||
237 CategoryDecl->IsClassExtension()) {
238 NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty
239 : CategoryProperty;
240 diag(MatchedDecl->getLocation(),
241 "property name '%0' not using lowerCamelCase style or not prefixed "
242 "in a category, according to the Apple Coding Guidelines")
243 << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style);
244 }
245 return;
246 }
Ben Hamilton52161a52017-11-13 23:54:31 +0000247 diag(MatchedDecl->getLocation(),
Yan Zhang75b3b542018-01-30 01:44:00 +0000248 "property name '%0' not using lowerCamelCase style or not prefixed in "
249 "a category, according to the Apple Coding Guidelines")
250 << MatchedDecl->getName()
251 << generateFixItHint(MatchedDecl, StandardProperty);
Ben Hamilton52161a52017-11-13 23:54:31 +0000252}
253
254void PropertyDeclarationCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
255 Options.store(Opts, "Acronyms",
256 utils::options::serializeStringList(SpecialAcronyms));
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000257 Options.store(Opts, "IncludeDefaultAcronyms", IncludeDefaultAcronyms);
Ben Hamilton52161a52017-11-13 23:54:31 +0000258}
259
260} // namespace objc
261} // namespace tidy
262} // namespace clang