blob: ae9ca013e825501240658080e940599cf823ac36 [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",
48 "CMYK",
49 "DNS",
50 "FPS",
51 "FTP",
52 "GIF",
53 "GPS",
Yan Zhangee630392018-02-27 18:35:53 +000054 "GUID",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000055 "HD",
56 "HDR",
57 "HTML",
58 "HTTP",
59 "HTTPS",
60 "HUD",
61 "ID",
62 "JPG",
63 "JS",
64 "LAN",
65 "LZW",
66 "MDNS",
67 "MIDI",
68 "OS",
69 "PDF",
70 "PIN",
71 "PNG",
72 "POI",
73 "PSTN",
74 "PTR",
75 "QA",
76 "QOS",
77 "RGB",
78 "RGBA",
79 "RGBX",
80 "ROM",
81 "RPC",
82 "RTF",
83 "RTL",
84 "SDK",
85 "SSO",
86 "TCP",
87 "TIFF",
88 "TTS",
89 "UI",
90 "URI",
91 "URL",
Yan Zhangee630392018-02-27 18:35:53 +000092 "UUID",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000093 "VC",
94 "VOIP",
95 "VPN",
96 "VR",
Yan Zhang03136142018-04-18 20:09:10 +000097 "W",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +000098 "WAN",
Yan Zhang03136142018-04-18 20:09:10 +000099 "X",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000100 "XML",
Yan Zhang03136142018-04-18 20:09:10 +0000101 "Y",
102 "Z",
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000103};
Ben Hamilton52161a52017-11-13 23:54:31 +0000104
Yan Zhang75b3b542018-01-30 01:44:00 +0000105/// For now we will only fix 'CamelCase' or 'abc_CamelCase' property to
106/// 'camelCase' or 'abc_camelCase'. For other cases the users need to
Ben Hamilton52161a52017-11-13 23:54:31 +0000107/// come up with a proper name by their own.
108/// FIXME: provide fix for snake_case to snakeCase
Yan Zhang75b3b542018-01-30 01:44:00 +0000109FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, NamingStyle Style) {
110 auto Name = Decl->getName();
111 auto NewName = Decl->getName().str();
112 size_t Index = 0;
113 if (Style == CategoryProperty) {
114 Index = Name.find_first_of('_') + 1;
115 NewName.replace(0, Index - 1, Name.substr(0, Index - 1).lower());
116 }
117 if (Index < Name.size()) {
118 NewName[Index] = tolower(NewName[Index]);
119 if (NewName != Name) {
120 return FixItHint::CreateReplacement(
121 CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())),
122 llvm::StringRef(NewName));
123 }
Ben Hamilton52161a52017-11-13 23:54:31 +0000124 }
125 return FixItHint();
126}
127
Yan Zhangae5bc5ae2018-02-06 21:40:38 +0000128std::string AcronymsGroupRegex(llvm::ArrayRef<std::string> EscapedAcronyms) {
129 return "(" +
130 llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "s?|") +
131 "s?)";
132}
133
Yan Zhang75b3b542018-01-30 01:44:00 +0000134std::string validPropertyNameRegex(llvm::ArrayRef<std::string> EscapedAcronyms,
135 bool UsedInMatcher) {
Ben Hamilton52161a52017-11-13 23:54:31 +0000136 // Allow any of these names:
137 // foo
138 // fooBar
139 // url
140 // urlString
141 // URL
142 // URLString
Yan Zhang8c298d22018-01-17 00:19:35 +0000143 // bundleID
Yan Zhang75b3b542018-01-30 01:44:00 +0000144 std::string StartMatcher = UsedInMatcher ? "::" : "^";
Yan Zhangae5bc5ae2018-02-06 21:40:38 +0000145 std::string AcronymsMatcher = AcronymsGroupRegex(EscapedAcronyms);
146 return StartMatcher + "(" + AcronymsMatcher + "[A-Z]?)?[a-z]+[a-z0-9]*(" +
147 AcronymsMatcher + "|([A-Z][a-z0-9]+))*$";
Yan Zhang75b3b542018-01-30 01:44:00 +0000148}
149
150bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) {
151 auto RegexExp = llvm::Regex("^[a-zA-Z]+_[a-zA-Z0-9][a-zA-Z0-9_]+$");
152 return RegexExp.match(PropertyName);
153}
154
155bool prefixedPropertyNameValid(llvm::StringRef PropertyName,
156 llvm::ArrayRef<std::string> Acronyms) {
157 size_t Start = PropertyName.find_first_of('_');
158 assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size());
159 auto Prefix = PropertyName.substr(0, Start);
160 if (Prefix.lower() != Prefix) {
161 return false;
162 }
163 auto RegexExp =
164 llvm::Regex(llvm::StringRef(validPropertyNameRegex(Acronyms, false)));
165 return RegexExp.match(PropertyName.substr(Start + 1));
Ben Hamilton52161a52017-11-13 23:54:31 +0000166}
167} // namespace
168
169PropertyDeclarationCheck::PropertyDeclarationCheck(StringRef Name,
170 ClangTidyContext *Context)
171 : ClangTidyCheck(Name, Context),
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000172 SpecialAcronyms(
173 utils::options::parseStringList(Options.get("Acronyms", ""))),
Yan Zhang75b3b542018-01-30 01:44:00 +0000174 IncludeDefaultAcronyms(Options.get("IncludeDefaultAcronyms", true)),
175 EscapedAcronyms() {}
Ben Hamilton52161a52017-11-13 23:54:31 +0000176
177void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) {
Yan Zhangc7faee72018-03-07 18:59:25 +0000178 // this check should only be applied to ObjC sources.
179 if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) {
180 return;
181 }
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000182 if (IncludeDefaultAcronyms) {
183 EscapedAcronyms.reserve(llvm::array_lengthof(DefaultSpecialAcronyms) +
184 SpecialAcronyms.size());
185 // No need to regex-escape the default acronyms.
186 EscapedAcronyms.insert(EscapedAcronyms.end(),
187 std::begin(DefaultSpecialAcronyms),
188 std::end(DefaultSpecialAcronyms));
189 } else {
190 EscapedAcronyms.reserve(SpecialAcronyms.size());
191 }
192 // In case someone defines a prefix which includes a regex
193 // special character, regex-escape all the user-defined prefixes.
194 std::transform(SpecialAcronyms.begin(), SpecialAcronyms.end(),
195 std::back_inserter(EscapedAcronyms),
196 [](const std::string &s) { return llvm::Regex::escape(s); });
Ben Hamilton52161a52017-11-13 23:54:31 +0000197 Finder->addMatcher(
198 objcPropertyDecl(
199 // the property name should be in Lower Camel Case like
200 // 'lowerCamelCase'
Yan Zhang75b3b542018-01-30 01:44:00 +0000201 unless(matchesName(validPropertyNameRegex(EscapedAcronyms, true))))
Ben Hamilton52161a52017-11-13 23:54:31 +0000202 .bind("property"),
203 this);
204}
205
206void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) {
207 const auto *MatchedDecl =
208 Result.Nodes.getNodeAs<ObjCPropertyDecl>("property");
209 assert(MatchedDecl->getName().size() > 0);
Yan Zhang75b3b542018-01-30 01:44:00 +0000210 auto *DeclContext = MatchedDecl->getDeclContext();
211 auto *CategoryDecl = llvm::dyn_cast<ObjCCategoryDecl>(DeclContext);
212 if (CategoryDecl != nullptr &&
213 hasCategoryPropertyPrefix(MatchedDecl->getName())) {
214 if (!prefixedPropertyNameValid(MatchedDecl->getName(), EscapedAcronyms) ||
215 CategoryDecl->IsClassExtension()) {
216 NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty
217 : CategoryProperty;
218 diag(MatchedDecl->getLocation(),
219 "property name '%0' not using lowerCamelCase style or not prefixed "
220 "in a category, according to the Apple Coding Guidelines")
221 << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style);
222 }
223 return;
224 }
Ben Hamilton52161a52017-11-13 23:54:31 +0000225 diag(MatchedDecl->getLocation(),
Yan Zhang75b3b542018-01-30 01:44:00 +0000226 "property name '%0' not using lowerCamelCase style or not prefixed in "
227 "a category, according to the Apple Coding Guidelines")
228 << MatchedDecl->getName()
229 << generateFixItHint(MatchedDecl, StandardProperty);
Ben Hamilton52161a52017-11-13 23:54:31 +0000230}
231
232void PropertyDeclarationCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
233 Options.store(Opts, "Acronyms",
234 utils::options::serializeStringList(SpecialAcronyms));
Ben Hamiltonf94c10d2018-01-22 15:45:25 +0000235 Options.store(Opts, "IncludeDefaultAcronyms", IncludeDefaultAcronyms);
Ben Hamilton52161a52017-11-13 23:54:31 +0000236}
237
238} // namespace objc
239} // namespace tidy
240} // namespace clang