blob: b61c1b9926b3be2f719a522b73d91c08e3af34a5 [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",
Yan Zhang6a528852018-05-15 18:13:51 +000045 "AR",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000046 "ARGB",
47 "ASCII",
48 "BGRA",
Ben Hamiltond4fb9512018-05-01 14:48:51 +000049 "CA",
50 "CF",
51 "CG",
52 "CI",
53 "CV",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000054 "CMYK",
55 "DNS",
56 "FPS",
57 "FTP",
58 "GIF",
Ben Hamiltond4fb9512018-05-01 14:48:51 +000059 "GL",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000060 "GPS",
Yan Zhangee630392018-02-27 18:35:53 +000061 "GUID",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000062 "HD",
63 "HDR",
64 "HTML",
65 "HTTP",
66 "HTTPS",
67 "HUD",
68 "ID",
69 "JPG",
70 "JS",
71 "LAN",
72 "LZW",
73 "MDNS",
74 "MIDI",
Ben Hamiltond4fb9512018-05-01 14:48:51 +000075 "NS",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000076 "OS",
77 "PDF",
78 "PIN",
79 "PNG",
80 "POI",
81 "PSTN",
82 "PTR",
83 "QA",
84 "QOS",
85 "RGB",
86 "RGBA",
87 "RGBX",
88 "ROM",
89 "RPC",
90 "RTF",
91 "RTL",
Ben Hamiltond4fb9512018-05-01 14:48:51 +000092 "SC",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000093 "SDK",
94 "SSO",
95 "TCP",
96 "TIFF",
97 "TTS",
98 "UI",
99 "URI",
100 "URL",
Yan Zhangee630392018-02-27 18:35:53 +0000101 "UUID",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000102 "VC",
103 "VOIP",
104 "VPN",
105 "VR",
Yan Zhang03136142018-04-18 20:09:10 +0000106 "W",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000107 "WAN",
Yan Zhang03136142018-04-18 20:09:10 +0000108 "X",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000109 "XML",
Yan Zhang03136142018-04-18 20:09:10 +0000110 "Y",
111 "Z",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000112};
Ben Hamilton52161a52017-11-13 23:54:31 +0000113
Yan Zhang75b3b542018-01-30 01:44:00 +0000114/// For now we will only fix 'CamelCase' or 'abc_CamelCase' property to
115/// 'camelCase' or 'abc_camelCase'. For other cases the users need to
Ben Hamilton52161a52017-11-13 23:54:31 +0000116/// come up with a proper name by their own.
117/// FIXME: provide fix for snake_case to snakeCase
Yan Zhang75b3b542018-01-30 01:44:00 +0000118FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, NamingStyle Style) {
119 auto Name = Decl->getName();
120 auto NewName = Decl->getName().str();
121 size_t Index = 0;
122 if (Style == CategoryProperty) {
123 Index = Name.find_first_of('_') + 1;
124 NewName.replace(0, Index - 1, Name.substr(0, Index - 1).lower());
125 }
126 if (Index < Name.size()) {
127 NewName[Index] = tolower(NewName[Index]);
128 if (NewName != Name) {
129 return FixItHint::CreateReplacement(
130 CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())),
131 llvm::StringRef(NewName));
132 }
Ben Hamilton52161a52017-11-13 23:54:31 +0000133 }
134 return FixItHint();
135}
136
Yan Zhangae5bc5ae2018-02-06 21:40:38 +0000137std::string AcronymsGroupRegex(llvm::ArrayRef<std::string> EscapedAcronyms) {
138 return "(" +
139 llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "s?|") +
140 "s?)";
141}
142
Yan Zhang75b3b542018-01-30 01:44:00 +0000143std::string validPropertyNameRegex(llvm::ArrayRef<std::string> EscapedAcronyms,
144 bool UsedInMatcher) {
Ben Hamilton52161a52017-11-13 23:54:31 +0000145 // Allow any of these names:
146 // foo
147 // fooBar
148 // url
149 // urlString
150 // URL
151 // URLString
Yan Zhang8c298d22018-01-17 00:19:35 +0000152 // bundleID
Yan Zhang75b3b542018-01-30 01:44:00 +0000153 std::string StartMatcher = UsedInMatcher ? "::" : "^";
Yan Zhangae5bc5ae2018-02-06 21:40:38 +0000154 std::string AcronymsMatcher = AcronymsGroupRegex(EscapedAcronyms);
155 return StartMatcher + "(" + AcronymsMatcher + "[A-Z]?)?[a-z]+[a-z0-9]*(" +
156 AcronymsMatcher + "|([A-Z][a-z0-9]+))*$";
Yan Zhang75b3b542018-01-30 01:44:00 +0000157}
158
159bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) {
160 auto RegexExp = llvm::Regex("^[a-zA-Z]+_[a-zA-Z0-9][a-zA-Z0-9_]+$");
161 return RegexExp.match(PropertyName);
162}
163
164bool prefixedPropertyNameValid(llvm::StringRef PropertyName,
165 llvm::ArrayRef<std::string> Acronyms) {
166 size_t Start = PropertyName.find_first_of('_');
167 assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size());
168 auto Prefix = PropertyName.substr(0, Start);
169 if (Prefix.lower() != Prefix) {
170 return false;
171 }
172 auto RegexExp =
173 llvm::Regex(llvm::StringRef(validPropertyNameRegex(Acronyms, false)));
174 return RegexExp.match(PropertyName.substr(Start + 1));
Ben Hamilton52161a52017-11-13 23:54:31 +0000175}
176} // namespace
177
178PropertyDeclarationCheck::PropertyDeclarationCheck(StringRef Name,
179 ClangTidyContext *Context)
180 : ClangTidyCheck(Name, Context),
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000181 SpecialAcronyms(
182 utils::options::parseStringList(Options.get("Acronyms", ""))),
Yan Zhang75b3b542018-01-30 01:44:00 +0000183 IncludeDefaultAcronyms(Options.get("IncludeDefaultAcronyms", true)),
184 EscapedAcronyms() {}
Ben Hamilton52161a52017-11-13 23:54:31 +0000185
186void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) {
Yan Zhangc7faee72018-03-07 18:59:25 +0000187 // this check should only be applied to ObjC sources.
188 if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) {
189 return;
190 }
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000191 if (IncludeDefaultAcronyms) {
192 EscapedAcronyms.reserve(llvm::array_lengthof(DefaultSpecialAcronyms) +
193 SpecialAcronyms.size());
194 // No need to regex-escape the default acronyms.
195 EscapedAcronyms.insert(EscapedAcronyms.end(),
196 std::begin(DefaultSpecialAcronyms),
197 std::end(DefaultSpecialAcronyms));
198 } else {
199 EscapedAcronyms.reserve(SpecialAcronyms.size());
200 }
201 // In case someone defines a prefix which includes a regex
202 // special character, regex-escape all the user-defined prefixes.
203 std::transform(SpecialAcronyms.begin(), SpecialAcronyms.end(),
204 std::back_inserter(EscapedAcronyms),
205 [](const std::string &s) { return llvm::Regex::escape(s); });
Ben Hamilton52161a52017-11-13 23:54:31 +0000206 Finder->addMatcher(
207 objcPropertyDecl(
208 // the property name should be in Lower Camel Case like
209 // 'lowerCamelCase'
Yan Zhang75b3b542018-01-30 01:44:00 +0000210 unless(matchesName(validPropertyNameRegex(EscapedAcronyms, true))))
Ben Hamilton52161a52017-11-13 23:54:31 +0000211 .bind("property"),
212 this);
213}
214
215void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) {
216 const auto *MatchedDecl =
217 Result.Nodes.getNodeAs<ObjCPropertyDecl>("property");
218 assert(MatchedDecl->getName().size() > 0);
Yan Zhang75b3b542018-01-30 01:44:00 +0000219 auto *DeclContext = MatchedDecl->getDeclContext();
220 auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext);
Yan Zhang4f9ead22018-05-04 18:14:08 +0000221
222 auto AcronymsRegex =
223 llvm::Regex("^" + AcronymsGroupRegex(EscapedAcronyms) + "$");
224 if (AcronymsRegex.match(MatchedDecl->getName())) {
225 return;
226 }
Yan Zhang75b3b542018-01-30 01:44:00 +0000227 if (CategoryDecl != nullptr &&
228 hasCategoryPropertyPrefix(MatchedDecl->getName())) {
229 if (!prefixedPropertyNameValid(MatchedDecl->getName(), EscapedAcronyms) ||
230 CategoryDecl->IsClassExtension()) {
231 NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty
232 : CategoryProperty;
233 diag(MatchedDecl->getLocation(),
234 "property name '%0' not using lowerCamelCase style or not prefixed "
235 "in a category, according to the Apple Coding Guidelines")
236 << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style);
237 }
238 return;
239 }
Ben Hamilton52161a52017-11-13 23:54:31 +0000240 diag(MatchedDecl->getLocation(),
Yan Zhang75b3b542018-01-30 01:44:00 +0000241 "property name '%0' not using lowerCamelCase style or not prefixed in "
242 "a category, according to the Apple Coding Guidelines")
243 << MatchedDecl->getName()
244 << generateFixItHint(MatchedDecl, StandardProperty);
Ben Hamilton52161a52017-11-13 23:54:31 +0000245}
246
247void PropertyDeclarationCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
248 Options.store(Opts, "Acronyms",
249 utils::options::serializeStringList(SpecialAcronyms));
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000250 Options.store(Opts, "IncludeDefaultAcronyms", IncludeDefaultAcronyms);
Ben Hamilton52161a52017-11-13 23:54:31 +0000251}
252
253} // namespace objc
254} // namespace tidy
255} // namespace clang