blob: d7715fa2c695ca1b9401b02c39475fc8cd14675a [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 Hamilton13af7942018-07-12 17:32:55 +0000101 "SQL",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000102 "SSO",
103 "TCP",
104 "TIFF",
105 "TTS",
106 "UI",
107 "URI",
108 "URL",
Yan Zhangee630392018-02-27 18:35:53 +0000109 "UUID",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000110 "VC",
111 "VOIP",
112 "VPN",
113 "VR",
Yan Zhang03136142018-04-18 20:09:10 +0000114 "W",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000115 "WAN",
Yan Zhang03136142018-04-18 20:09:10 +0000116 "X",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000117 "XML",
Yan Zhang03136142018-04-18 20:09:10 +0000118 "Y",
119 "Z",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000120};
Ben Hamilton52161a52017-11-13 23:54:31 +0000121
Yan Zhang75b3b542018-01-30 01:44:00 +0000122/// For now we will only fix 'CamelCase' or 'abc_CamelCase' property to
123/// 'camelCase' or 'abc_camelCase'. For other cases the users need to
Ben Hamilton52161a52017-11-13 23:54:31 +0000124/// come up with a proper name by their own.
125/// FIXME: provide fix for snake_case to snakeCase
Yan Zhang75b3b542018-01-30 01:44:00 +0000126FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, NamingStyle Style) {
127 auto Name = Decl->getName();
128 auto NewName = Decl->getName().str();
129 size_t Index = 0;
130 if (Style == CategoryProperty) {
131 Index = Name.find_first_of('_') + 1;
132 NewName.replace(0, Index - 1, Name.substr(0, Index - 1).lower());
133 }
134 if (Index < Name.size()) {
135 NewName[Index] = tolower(NewName[Index]);
136 if (NewName != Name) {
137 return FixItHint::CreateReplacement(
138 CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())),
139 llvm::StringRef(NewName));
140 }
Ben Hamilton52161a52017-11-13 23:54:31 +0000141 }
142 return FixItHint();
143}
144
Yan Zhangae5bc5ae2018-02-06 21:40:38 +0000145std::string AcronymsGroupRegex(llvm::ArrayRef<std::string> EscapedAcronyms) {
146 return "(" +
147 llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "s?|") +
148 "s?)";
149}
150
Yan Zhang75b3b542018-01-30 01:44:00 +0000151std::string validPropertyNameRegex(llvm::ArrayRef<std::string> EscapedAcronyms,
152 bool UsedInMatcher) {
Ben Hamilton52161a52017-11-13 23:54:31 +0000153 // Allow any of these names:
154 // foo
155 // fooBar
156 // url
157 // urlString
158 // URL
159 // URLString
Yan Zhang8c298d22018-01-17 00:19:35 +0000160 // bundleID
Yan Zhang75b3b542018-01-30 01:44:00 +0000161 std::string StartMatcher = UsedInMatcher ? "::" : "^";
Yan Zhangae5bc5ae2018-02-06 21:40:38 +0000162 std::string AcronymsMatcher = AcronymsGroupRegex(EscapedAcronyms);
163 return StartMatcher + "(" + AcronymsMatcher + "[A-Z]?)?[a-z]+[a-z0-9]*(" +
Yan Zhang72cecf92018-06-11 22:44:06 +0000164 AcronymsMatcher + "|([A-Z][a-z0-9]+)|A|I)*$";
Yan Zhang75b3b542018-01-30 01:44:00 +0000165}
166
167bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) {
168 auto RegexExp = llvm::Regex("^[a-zA-Z]+_[a-zA-Z0-9][a-zA-Z0-9_]+$");
169 return RegexExp.match(PropertyName);
170}
171
172bool prefixedPropertyNameValid(llvm::StringRef PropertyName,
173 llvm::ArrayRef<std::string> Acronyms) {
174 size_t Start = PropertyName.find_first_of('_');
175 assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size());
176 auto Prefix = PropertyName.substr(0, Start);
177 if (Prefix.lower() != Prefix) {
178 return false;
179 }
180 auto RegexExp =
181 llvm::Regex(llvm::StringRef(validPropertyNameRegex(Acronyms, false)));
182 return RegexExp.match(PropertyName.substr(Start + 1));
Ben Hamilton52161a52017-11-13 23:54:31 +0000183}
184} // namespace
185
186PropertyDeclarationCheck::PropertyDeclarationCheck(StringRef Name,
187 ClangTidyContext *Context)
188 : ClangTidyCheck(Name, Context),
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000189 SpecialAcronyms(
190 utils::options::parseStringList(Options.get("Acronyms", ""))),
Yan Zhang75b3b542018-01-30 01:44:00 +0000191 IncludeDefaultAcronyms(Options.get("IncludeDefaultAcronyms", true)),
192 EscapedAcronyms() {}
Ben Hamilton52161a52017-11-13 23:54:31 +0000193
194void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) {
Yan Zhangc7faee72018-03-07 18:59:25 +0000195 // this check should only be applied to ObjC sources.
196 if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) {
197 return;
198 }
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000199 if (IncludeDefaultAcronyms) {
200 EscapedAcronyms.reserve(llvm::array_lengthof(DefaultSpecialAcronyms) +
201 SpecialAcronyms.size());
202 // No need to regex-escape the default acronyms.
203 EscapedAcronyms.insert(EscapedAcronyms.end(),
204 std::begin(DefaultSpecialAcronyms),
205 std::end(DefaultSpecialAcronyms));
206 } else {
207 EscapedAcronyms.reserve(SpecialAcronyms.size());
208 }
209 // In case someone defines a prefix which includes a regex
210 // special character, regex-escape all the user-defined prefixes.
211 std::transform(SpecialAcronyms.begin(), SpecialAcronyms.end(),
212 std::back_inserter(EscapedAcronyms),
213 [](const std::string &s) { return llvm::Regex::escape(s); });
Ben Hamilton52161a52017-11-13 23:54:31 +0000214 Finder->addMatcher(
215 objcPropertyDecl(
216 // the property name should be in Lower Camel Case like
217 // 'lowerCamelCase'
Yan Zhang75b3b542018-01-30 01:44:00 +0000218 unless(matchesName(validPropertyNameRegex(EscapedAcronyms, true))))
Ben Hamilton52161a52017-11-13 23:54:31 +0000219 .bind("property"),
220 this);
221}
222
223void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) {
224 const auto *MatchedDecl =
225 Result.Nodes.getNodeAs<ObjCPropertyDecl>("property");
226 assert(MatchedDecl->getName().size() > 0);
Yan Zhang75b3b542018-01-30 01:44:00 +0000227 auto *DeclContext = MatchedDecl->getDeclContext();
228 auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext);
Yan Zhang4f9ead22018-05-04 18:14:08 +0000229
230 auto AcronymsRegex =
231 llvm::Regex("^" + AcronymsGroupRegex(EscapedAcronyms) + "$");
232 if (AcronymsRegex.match(MatchedDecl->getName())) {
233 return;
234 }
Yan Zhang75b3b542018-01-30 01:44:00 +0000235 if (CategoryDecl != nullptr &&
236 hasCategoryPropertyPrefix(MatchedDecl->getName())) {
237 if (!prefixedPropertyNameValid(MatchedDecl->getName(), EscapedAcronyms) ||
238 CategoryDecl->IsClassExtension()) {
239 NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty
240 : CategoryProperty;
241 diag(MatchedDecl->getLocation(),
242 "property name '%0' not using lowerCamelCase style or not prefixed "
243 "in a category, according to the Apple Coding Guidelines")
244 << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style);
245 }
246 return;
247 }
Ben Hamilton52161a52017-11-13 23:54:31 +0000248 diag(MatchedDecl->getLocation(),
Yan Zhang75b3b542018-01-30 01:44:00 +0000249 "property name '%0' not using lowerCamelCase style or not prefixed in "
250 "a category, according to the Apple Coding Guidelines")
251 << MatchedDecl->getName()
252 << generateFixItHint(MatchedDecl, StandardProperty);
Ben Hamilton52161a52017-11-13 23:54:31 +0000253}
254
255void PropertyDeclarationCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
256 Options.store(Opts, "Acronyms",
257 utils::options::serializeStringList(SpecialAcronyms));
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000258 Options.store(Opts, "IncludeDefaultAcronyms", IncludeDefaultAcronyms);
Ben Hamilton52161a52017-11-13 23:54:31 +0000259}
260
261} // namespace objc
262} // namespace tidy
263} // namespace clang