blob: dc15bdbe43f32b35d094f2aad8cef0104933a668 [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",
54 "CV",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000055 "CMYK",
56 "DNS",
57 "FPS",
58 "FTP",
59 "GIF",
Ben Hamiltond4fb9512018-05-01 14:48:51 +000060 "GL",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000061 "GPS",
Yan Zhangee630392018-02-27 18:35:53 +000062 "GUID",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000063 "HD",
64 "HDR",
65 "HTML",
66 "HTTP",
67 "HTTPS",
68 "HUD",
69 "ID",
70 "JPG",
71 "JS",
72 "LAN",
73 "LZW",
74 "MDNS",
75 "MIDI",
Ben Hamiltond4fb9512018-05-01 14:48:51 +000076 "NS",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000077 "OS",
78 "PDF",
79 "PIN",
80 "PNG",
81 "POI",
82 "PSTN",
83 "PTR",
84 "QA",
85 "QOS",
86 "RGB",
87 "RGBA",
88 "RGBX",
89 "ROM",
90 "RPC",
91 "RTF",
92 "RTL",
Ben Hamiltond4fb9512018-05-01 14:48:51 +000093 "SC",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000094 "SDK",
95 "SSO",
96 "TCP",
97 "TIFF",
98 "TTS",
99 "UI",
100 "URI",
101 "URL",
Yan Zhangee630392018-02-27 18:35:53 +0000102 "UUID",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000103 "VC",
104 "VOIP",
105 "VPN",
106 "VR",
Yan Zhang03136142018-04-18 20:09:10 +0000107 "W",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000108 "WAN",
Yan Zhang03136142018-04-18 20:09:10 +0000109 "X",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000110 "XML",
Yan Zhang03136142018-04-18 20:09:10 +0000111 "Y",
112 "Z",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000113};
Ben Hamilton52161a52017-11-13 23:54:31 +0000114
Yan Zhang75b3b542018-01-30 01:44:00 +0000115/// For now we will only fix 'CamelCase' or 'abc_CamelCase' property to
116/// 'camelCase' or 'abc_camelCase'. For other cases the users need to
Ben Hamilton52161a52017-11-13 23:54:31 +0000117/// come up with a proper name by their own.
118/// FIXME: provide fix for snake_case to snakeCase
Yan Zhang75b3b542018-01-30 01:44:00 +0000119FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, NamingStyle Style) {
120 auto Name = Decl->getName();
121 auto NewName = Decl->getName().str();
122 size_t Index = 0;
123 if (Style == CategoryProperty) {
124 Index = Name.find_first_of('_') + 1;
125 NewName.replace(0, Index - 1, Name.substr(0, Index - 1).lower());
126 }
127 if (Index < Name.size()) {
128 NewName[Index] = tolower(NewName[Index]);
129 if (NewName != Name) {
130 return FixItHint::CreateReplacement(
131 CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())),
132 llvm::StringRef(NewName));
133 }
Ben Hamilton52161a52017-11-13 23:54:31 +0000134 }
135 return FixItHint();
136}
137
Yan Zhangae5bc5ae2018-02-06 21:40:38 +0000138std::string AcronymsGroupRegex(llvm::ArrayRef<std::string> EscapedAcronyms) {
139 return "(" +
140 llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "s?|") +
141 "s?)";
142}
143
Yan Zhang75b3b542018-01-30 01:44:00 +0000144std::string validPropertyNameRegex(llvm::ArrayRef<std::string> EscapedAcronyms,
145 bool UsedInMatcher) {
Ben Hamilton52161a52017-11-13 23:54:31 +0000146 // Allow any of these names:
147 // foo
148 // fooBar
149 // url
150 // urlString
151 // URL
152 // URLString
Yan Zhang8c298d22018-01-17 00:19:35 +0000153 // bundleID
Yan Zhang75b3b542018-01-30 01:44:00 +0000154 std::string StartMatcher = UsedInMatcher ? "::" : "^";
Yan Zhangae5bc5ae2018-02-06 21:40:38 +0000155 std::string AcronymsMatcher = AcronymsGroupRegex(EscapedAcronyms);
156 return StartMatcher + "(" + AcronymsMatcher + "[A-Z]?)?[a-z]+[a-z0-9]*(" +
Yan Zhang72cecf92018-06-11 22:44:06 +0000157 AcronymsMatcher + "|([A-Z][a-z0-9]+)|A|I)*$";
Yan Zhang75b3b542018-01-30 01:44:00 +0000158}
159
160bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) {
161 auto RegexExp = llvm::Regex("^[a-zA-Z]+_[a-zA-Z0-9][a-zA-Z0-9_]+$");
162 return RegexExp.match(PropertyName);
163}
164
165bool prefixedPropertyNameValid(llvm::StringRef PropertyName,
166 llvm::ArrayRef<std::string> Acronyms) {
167 size_t Start = PropertyName.find_first_of('_');
168 assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size());
169 auto Prefix = PropertyName.substr(0, Start);
170 if (Prefix.lower() != Prefix) {
171 return false;
172 }
173 auto RegexExp =
174 llvm::Regex(llvm::StringRef(validPropertyNameRegex(Acronyms, false)));
175 return RegexExp.match(PropertyName.substr(Start + 1));
Ben Hamilton52161a52017-11-13 23:54:31 +0000176}
177} // namespace
178
179PropertyDeclarationCheck::PropertyDeclarationCheck(StringRef Name,
180 ClangTidyContext *Context)
181 : ClangTidyCheck(Name, Context),
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000182 SpecialAcronyms(
183 utils::options::parseStringList(Options.get("Acronyms", ""))),
Yan Zhang75b3b542018-01-30 01:44:00 +0000184 IncludeDefaultAcronyms(Options.get("IncludeDefaultAcronyms", true)),
185 EscapedAcronyms() {}
Ben Hamilton52161a52017-11-13 23:54:31 +0000186
187void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) {
Yan Zhangc7faee72018-03-07 18:59:25 +0000188 // this check should only be applied to ObjC sources.
189 if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) {
190 return;
191 }
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000192 if (IncludeDefaultAcronyms) {
193 EscapedAcronyms.reserve(llvm::array_lengthof(DefaultSpecialAcronyms) +
194 SpecialAcronyms.size());
195 // No need to regex-escape the default acronyms.
196 EscapedAcronyms.insert(EscapedAcronyms.end(),
197 std::begin(DefaultSpecialAcronyms),
198 std::end(DefaultSpecialAcronyms));
199 } else {
200 EscapedAcronyms.reserve(SpecialAcronyms.size());
201 }
202 // In case someone defines a prefix which includes a regex
203 // special character, regex-escape all the user-defined prefixes.
204 std::transform(SpecialAcronyms.begin(), SpecialAcronyms.end(),
205 std::back_inserter(EscapedAcronyms),
206 [](const std::string &s) { return llvm::Regex::escape(s); });
Ben Hamilton52161a52017-11-13 23:54:31 +0000207 Finder->addMatcher(
208 objcPropertyDecl(
209 // the property name should be in Lower Camel Case like
210 // 'lowerCamelCase'
Yan Zhang75b3b542018-01-30 01:44:00 +0000211 unless(matchesName(validPropertyNameRegex(EscapedAcronyms, true))))
Ben Hamilton52161a52017-11-13 23:54:31 +0000212 .bind("property"),
213 this);
214}
215
216void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) {
217 const auto *MatchedDecl =
218 Result.Nodes.getNodeAs<ObjCPropertyDecl>("property");
219 assert(MatchedDecl->getName().size() > 0);
Yan Zhang75b3b542018-01-30 01:44:00 +0000220 auto *DeclContext = MatchedDecl->getDeclContext();
221 auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext);
Yan Zhang4f9ead22018-05-04 18:14:08 +0000222
223 auto AcronymsRegex =
224 llvm::Regex("^" + AcronymsGroupRegex(EscapedAcronyms) + "$");
225 if (AcronymsRegex.match(MatchedDecl->getName())) {
226 return;
227 }
Yan Zhang75b3b542018-01-30 01:44:00 +0000228 if (CategoryDecl != nullptr &&
229 hasCategoryPropertyPrefix(MatchedDecl->getName())) {
230 if (!prefixedPropertyNameValid(MatchedDecl->getName(), EscapedAcronyms) ||
231 CategoryDecl->IsClassExtension()) {
232 NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty
233 : CategoryProperty;
234 diag(MatchedDecl->getLocation(),
235 "property name '%0' not using lowerCamelCase style or not prefixed "
236 "in a category, according to the Apple Coding Guidelines")
237 << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style);
238 }
239 return;
240 }
Ben Hamilton52161a52017-11-13 23:54:31 +0000241 diag(MatchedDecl->getLocation(),
Yan Zhang75b3b542018-01-30 01:44:00 +0000242 "property name '%0' not using lowerCamelCase style or not prefixed in "
243 "a category, according to the Apple Coding Guidelines")
244 << MatchedDecl->getName()
245 << generateFixItHint(MatchedDecl, StandardProperty);
Ben Hamilton52161a52017-11-13 23:54:31 +0000246}
247
248void PropertyDeclarationCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
249 Options.store(Opts, "Acronyms",
250 utils::options::serializeStringList(SpecialAcronyms));
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000251 Options.store(Opts, "IncludeDefaultAcronyms", IncludeDefaultAcronyms);
Ben Hamilton52161a52017-11-13 23:54:31 +0000252}
253
254} // namespace objc
255} // namespace tidy
256} // namespace clang